From 9cfc0698e4670ced927ecdca5430e5d92103efda Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Tue, 18 Oct 2022 20:25:35 +0800 Subject: [PATCH 01/19] v6.5.9 --- .../src/main/resources/bus.health.properties | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 bus-health/src/main/resources/bus.health.properties diff --git a/bus-health/src/main/resources/bus.health.properties b/bus-health/src/main/resources/bus.health.properties new file mode 100644 index 0000000000..999c99baa2 --- /dev/null +++ b/bus-health/src/main/resources/bus.health.properties @@ -0,0 +1,221 @@ +# This properties file is automatically loaded at startup of the GlobalConfig +# class. The defaults below may be overwritten by either replacing this file +# with your own equivalent file on the class path, or programmatically using +# GlobalConfig.set(, ); +# +# The property names are included as constants in the GlobalConfig class for +# convenience. +# +# Some containers enable alternate locations for the Linux /proc filesystem +# to provide container-level output in preference to system-level output. +# The /proc filesystem location +bus.health.proc.path=/proc +# The WMI query timeout in milliseconds +# Default is -1, no timeout +bus.health.wmi.timeout=-1 +# Whether to perform WMI queries for command lines in a batch for all running +# processes. Individual WMI queries for the command line take about 50ms while +# querying the entire process list takes about 200ms. If you regularly expect +# to query command lines for more than a few processes this should be enabled +# for better performance. If you only rarely query command lines, leaving this +# disabled will be faster and conserve some resources. Defaults to false. +bus.health.os.windows.commandline.batch=false +# Whether to update the OSProcess state on Windows to SUSPENDED if all its +# threads are suspended. This requires querying thread states and can impact +# performance (Process list queries can take much longer) but if desired is +# better done once than for each process. Users may still determine this value +# themselves by querying thread details, but this method is extremely slow if +# done for every process. Enabling this will provide a more efficient (but +# still slow) update if this information is of value. Defaults to false. +bus.health.os.windows.procstate.suspended=false +# Whether to use "Processor Utility" for System and per-processor CPU Load ticks +# (on Windows 8 and higher) to match CPU usage with the Windows Task Manager. +# +# Windows Task Manager displays a measure of Processor Utility, which is an +# amount of work completed by the processor compared to the processor running +# full time at nominal frequency. Because of features which change CPU frequency +# such as Intel Speed Step, Intel Turbo Boost, AMD Precision Boost, and others, +# it is possible for this value to exceed 100% (although Task Manager caps it +# at 100%). +# +# By default, health uses "Processor Time" which measures active and idle times +# for each processor. CPU load is active time divided by total time. This matches +# the interpretation of Unix-based systems, the Task Manager for Windows 7 and +# earlier, The Task Manager "Details" tab for Process CPU usage (and health's +# per-process CPU usage), and Windows Sysinternals Process Explorer. +# +# Setting this value to true will allow health's CPU Load values to match the +# Windows Task manager output (except that health's numbers will not be capped +# at 100%) and will measure "work completed" rather than "processor time not idle". +# In the case load exceeds 100%, it is possible for "idle" ticks to decrease, +# i.e., the change between ticks would result in "negative idle time". +# +# Note that the base counter required for this calculation rolls over approximately +# every two hours. If your application remains idle for over an hour after +# instantiating the CentralProcessor object before polling for CPU usage, internal +# calculations may fail. Instantiating a new SystemInfo object before collecting +# ticks will mitigate these problems. +bus.health.os.windows.cpu.utility=false +# Whether to attempt to fetch Windows performance counter data for processes +# and threads from HKEY_PERFORMANCE_DATA in the registry. Windows docs say +# to use the PDH API in preference to this, but the multiple native calls +# with JNA result in slower performance than a one-time grab of the data +# from the registry. Unfortunately, registry calls are more subject to +# problems with registry corruption, counter deletion when changing language +# settings, and other factors. Although the code will recover from failure +# to read these counters from the registry, it may be preferable to disable +# this attempt if failure is known/expected. Setting this property to false +# will skip the registry check and use the API-recommended (but slower) +# performance counter API (or WMI as a backup). +bus.health.os.windows.hkeyperfdata=true +# Whether to start a daemon thread to provide Load Averages for Windows. +# Load Average is a metric on Linux, macOS, and Unix operating systems that +# provides the average number of processes running or waiting for CPU (on +# Linux, the metric includes processes waiting for other resources such as +# disks.) While Windows does not provide this metric, the Processor Queue +# Length performance counter added to the average recent processor usage +# provides a similar metric. +# +# If this property is set true, a daemon thread will be started which will +# calculate an equivalent metric to Unix load average. +bus.health.os.windows.loadaverage=false +# Whether a class of performance counters is disabled. It is possible to +# disable performance counters by changing registry values, which can improve +# overall OS performance (such as for gaming) if the counters aren't desired. +# +# When attempting to query counters, OSHI provides a (one time) log warning +# for the performance counter itself and a (each time) COM exception if the +# WMI table backing up the counter fails. The associated log messages can be +# confusing to users or developers of applications which depend on OSHI. +# +# If counters are either intentionally disabled, or the application depending +# on OSHI does not require any of the relevant performance counters, setting +# these values to true will skip querying these counters and return 0 values +# for the associated metrics. No log messages will be generated. +# +# If these values are not set at all, OSHI will query the registry to determine +# whether they are disabled and warn users if this is the case (this is the default). +# The associated Windows registry keys are at: +# HKLM\SYSTEM\CurrentControlSet\Services\PerfOS\Performance\Disable Performance Counters +# HKLM\SYSTEM\CurrentControlSet\Services\PerfProc\Performance\Disable Performance Counters +# HKLM\SYSTEM\CurrentControlSet\Services\PerfDisk\Performance\Disable Performance Counters +# where a value of 0 is not disabled and any other value disables counters. +# +# PerfOS counters used for CPU ticks, swap file usage, page swaps, context switches, interrupts +bus.os.windows.perfos.disabled= +# +# PerfProc counters used for process and thread priority, time, IO, memory usage +# (also see config for oshi.os.windows.hkeyperfdata) +bus.os.windows.perfproc.disabled= +# +# PerfDisk counters used for HWDiskStore reads/writes/queue length/xfer time +bus.os.windows.perfdisk.disabled= +# On Linux, most process metrics are read from the proc pseudo-filesystem. +# When operating without elevated permissions, this results in frequent error +# messages for failures to read the process environment files. Set this to true +# to receive these warnings. +bus.os.linux.procfs.logwarning=false +# On macOS, Linux, and Unix systems, the default getSessions() method on the +# OperatingSystem interface uses native code (see {@code man getutxent}) that +# is not thread safe. health's use of this code is synchronized and may be used +# in a multi-threaded environment without introducing any additional conflicts. +# Users should note, however, that other operating system code may access the +# same native code. +# +# The Who#queryWho() method produces similar output parsing +# the output of the Posix-standard "who" command, and may internally employ +# reentrant code on some platforms. Setting this configuration to true will +# use the command-line variant. Defaults to false. +bus.health.os.unix.whoCommand=false +# The name of the System event log containing bootup event IDs 12 and 6005. +# +# This is used for a one-time calculation of system boot time that should be +# consistent across process runs regardless of sleep/hibernate cycles, at +# the small cost of ~250ms latency reading upon WindowsOperatingSystem +# initialization. +# +# If the specified log is the empty string, or doesn't contain a bootup event, +# boot time will be calculated by subtracting up time from current time. This +# may vary by up to a millisecond between program executions and does not +# properly account for sleep/hibernate cycles, but when using the empty string +# is fast and may be preferred if only approximate boot time is desired. +# +# If a non-empty invalid log name is specified, the name "Application" will +# be used. The default is System +bus.health.os.windows.eventlog=System +# Memoizer default expiration in milliseconds (return values will be cached this long) +# Must be positive (negative value will never refresh) +# Should be less than 1 second +# Default is 300 milliseconds +bus.health.memoizer.expiration=300 +# FileSystem types which are network-based and should be excluded from local-only lists +bus.health.network.filesystem.types=afs,cifs,smbfs,sshfs,ncpfs,ncp,nfs,nfs4,gfs,gds2,glusterfs +# Linux defines a set of virtual file systems +# "anon_inodefs", anonymous inodes - inodes without filenames +# "autofs", automounter file system, used by Linux, Solaris, FreeBSD +# "bdev", keep track of block_device vs major/minor mapping +# "binfmt_misc", Binary format support file system +# "bpf", Virtual filesystem for Berkeley Paket Filter +# "cgroup", Cgroup file system +# "cgroup2", Cgroup file system +# "configfs", Config file system +# "cpuset", pseudo-filesystem interface to the kernel cpuset mechanism +# "dax", Direct Access (DAX) can be used on memory-backed block devices +# "debugfs", Debug file system +# "devpts", Dev pseudo terminal devices file system +# "devtmpfs", Dev temporary file system +# "drm", Direct Rendering Manager +# "ecryptfs", POSIX-compliant enterprise cryptographic filesystem for Linux +# "efivarfs", (U)EFI variable filesystem +# "fuse", // +# NOTE: FUSE's fuseblk is not evalued because used as file system +# representation of a FUSE block storage +# "fuseblk" FUSE block file system +# "fusectl", FUSE control file system +# "hugetlbfs", Huge pages support file system +# "inotifyfs", support inotify +# "mqueue", Message queue file system +# "nfsd", NFS file system +# "overlay", Overlay file system https://wiki.archlinux.org/index.php/Overlay_filesystem +# "pipefs", for pipes but only visible inside kernel +# "proc", Proc file system, used by Linux and Solaris +# "pstore", Pstore file system +# "ramfs", Old filesystem used for RAM disks +# "rootfs", Minimal fs to support kernel boot +# "rpc_pipefs", Sun RPC file system +# "securityfs", Kernel security file system +# "selinuxfs", SELinux file system +# "sunrpc", Sun RPC file system +# "sysfs", SysFS file system +# "systemd-1", Systemd file system +# "tmpfs", Temporary file system +# NOTE: tmpfs is evaluated apart, because Linux, Solaris, FreeBSD use it for +# RAMdisks +# "tracefs", thin stackable file system for capturing file system traces +# "usbfs", removed in linux 3.5 but still seen in some systems +# FreeBSD / Solaris defines a set of virtual file systems +# "procfs", Proc file system +# "devfs", Dev temporary file system +# "ctfs", Contract file system +# "fdescfs", fd +# "objfs", Object file system +# "mntfs", Mount file system +# "sharefs", Share file system +# "lofs" Library file system +# "SquashFS" read-only filesystem used by snap on eg. Ubuntu +bus.health.pseudo.filesystem.types=anon_inodefs,autofs,bdev,binfmt_misc,bpf,cgroup,cgroup2,configfs,cpuset,dax,debugfs,devpts,devtmpfs,drm,ecryptfs,efivarfs,fuse,fusectl,hugetlbfs,inotifyfs,mqueue,nfsd,overlay,proc,procfs,pstore,rootfs,rpc_pipefs,securityfs,selinuxfs,sunrpc,sysfs,systemd-1,tracefs,usbfs,procfs,devfs,ctfs,fdescfs,objfs,mntfs,sharefs,lofs,squashfs +# Paths and volumes to exclude from FileSystem listings on these operating systems. +# These are excluded if they match using PathMatcher syntax. See +# https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String) +# The "glob:" syntax is automatically added unless another syntax (regex:) is specified. +# Similar syntax may be used for "includes" which take precedence over excludes. +# Note: glob:* mathches all paths not starting with / +bus.health.os.aix.filesystem.path.excludes=/run**,/sys**,/dev,/proc**,* +bus.health.os.aix.filesystem.volume.excludes=/proc +bus.health.os.freebsd.filesystem.path.excludes=/system**,/tmp**,/dev,/dev/fd** +bus.health.os.freebsd.filesystem.volume.excludes=rpool* +bus.health.os.linux.filesystem.path.excludes=/run**,/sys**,/proc**,/dev,**/shm +bus.health.os.mac.filesystem.volume.excludes=devfs,map * +bus.health.os.openbsd.filesystem.path.excludes=/tmp**,/dev +bus.health.os.solaris.filesystem.path.excludes=/system**,/tmp**,/dev,/dev/fd** +bus.health.os.solaris.filesystem.volume.excludes=rpool \ No newline at end of file From 3c5a07266e25248b0d3ea5cd2fedb27321d3187a Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Sun, 23 Oct 2022 11:35:36 +0800 Subject: [PATCH 02/19] update create to of --- .../aoju/bus/cache/metric/MemoryCache.java | 4 +- .../bus/cache/provider/ZookeeperHitting.java | 4 +- .../java/org/aoju/bus/core/lang/Console.java | 2 +- .../java/org/aoju/bus/core/lang/FileType.java | 3 +- .../java/org/aoju/bus/core/lang/Weighing.java | 2 +- .../org/aoju/bus/core/map/Dictionary.java | 10 +- .../org/aoju/bus/core/map/MapBuilder.java | 2 +- .../java/org/aoju/bus/core/map/MapJoiner.java | 135 ++++++++++++++++ .../java/org/aoju/bus/core/map/TableMap.java | 146 +++++++++++++++--- .../org/aoju/bus/core/text/TextBuilder.java | 2 +- .../org/aoju/bus/core/text/TextJoiner.java | 11 ++ .../aoju/bus/core/thread/ExecutorBuilder.java | 2 +- .../aoju/bus/core/thread/GlobalThread.java | 2 +- .../aoju/bus/core/thread/ThreadBuilder.java | 2 +- .../aoju/bus/core/toolkit/CitizenIdKit.java | 7 +- .../org/aoju/bus/core/toolkit/CollKit.java | 79 +++++++++- .../org/aoju/bus/core/toolkit/MapKit.java | 109 ++++--------- .../org/aoju/bus/core/toolkit/TextKit.java | 2 +- .../org/aoju/bus/core/toolkit/ThreadKit.java | 18 +-- .../org/aoju/bus/core/toolkit/UriKit.java | 2 +- .../java/org/aoju/bus/cron/Scheduler.java | 4 +- .../java/org/aoju/bus/crypto/digest/MD5.java | 2 +- .../java/org/aoju/bus/crypto/digest/SM3.java | 2 +- .../bus/extra/json/provider/GsonProvider.java | 6 +- .../extra/json/provider/JacksonProvider.java | 2 +- .../bus/http/secure/SSLContextBuilder.java | 6 +- .../org/aoju/bus/logger/GlobalFactory.java | 2 +- .../java/org/aoju/bus/logger/LogFactory.java | 6 +- .../excel/cell/editors/package-info.java | 2 +- .../excel/cell/setters/package-info.java | 2 +- .../cell/{ => values}/FormulaCellValue.java | 4 +- .../bus/office/excel/sax/SheetSaxHandler.java | 2 +- .../bus/pay/metric/AbstractHttpDelegate.java | 6 +- 33 files changed, 428 insertions(+), 162 deletions(-) create mode 100755 bus-core/src/main/java/org/aoju/bus/core/map/MapJoiner.java rename bus-office/src/main/java/org/aoju/bus/office/excel/cell/{ => values}/FormulaCellValue.java (95%) diff --git a/bus-cache/src/main/java/org/aoju/bus/cache/metric/MemoryCache.java b/bus-cache/src/main/java/org/aoju/bus/cache/metric/MemoryCache.java index 9398d49d95..580455f38c 100755 --- a/bus-cache/src/main/java/org/aoju/bus/cache/metric/MemoryCache.java +++ b/bus-cache/src/main/java/org/aoju/bus/cache/metric/MemoryCache.java @@ -177,10 +177,10 @@ enum CacheScheduler { private ScheduledExecutorService scheduler; CacheScheduler() { - create(); + of(); } - private void create() { + private void of() { this.shutdown(); this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("OAuth-Task-%s", cacheTaskNumber.getAndIncrement()))); } diff --git a/bus-cache/src/main/java/org/aoju/bus/cache/provider/ZookeeperHitting.java b/bus-cache/src/main/java/org/aoju/bus/cache/provider/ZookeeperHitting.java index 9d77c22fdb..ab7d1eec69 100755 --- a/bus-cache/src/main/java/org/aoju/bus/cache/provider/ZookeeperHitting.java +++ b/bus-cache/src/main/java/org/aoju/bus/cache/provider/ZookeeperHitting.java @@ -91,8 +91,8 @@ public ZookeeperHitting(String zkServer, String productName) { this.hitPathPrefix = String.format("%s%s", uniqueProductName, "hit"); this.requirePathPrefix = String.format("%s%s", uniqueProductName, "require"); try { - client.create().creatingParentsIfNeeded().forPath(hitPathPrefix); - client.create().creatingParentsIfNeeded().forPath(requirePathPrefix); + client.of().creatingParentsIfNeeded().forPath(hitPathPrefix); + client.of().creatingParentsIfNeeded().forPath(requirePathPrefix); } catch (KeeperException.NodeExistsException ignored) { } catch (Exception e) { throw new RuntimeException("create path: " + hitPathPrefix + ", " + requirePathPrefix + " on namespace: " + NAME_SPACE + " error", e); diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Console.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Console.java index 1e8988a982..ad4d0541bd 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Console.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Console.java @@ -454,7 +454,7 @@ public static class Table { * * @return Table */ - public static Table create() { + public static Table of() { return new Table(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java b/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java index 9995f38bbd..4ec9396cde 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java @@ -970,7 +970,6 @@ public static String getType(File file) throws InternalException { */ public static String getType(InputStream in, String filename) { String typeName = getType(in); - if (null == typeName) { // 未成功识别类型,扩展名辅助识别 typeName = FileKit.getSuffix(filename); @@ -981,6 +980,8 @@ public static String getType(InputStream in, String filename) { typeName = "doc"; } else if ("msi".equalsIgnoreCase(extName)) { typeName = "msi"; + } else if ("ppt".equalsIgnoreCase(extName)) { + typeName = "ppt"; } } else if ("zip".equals(typeName)) { // zip可能为docx、xlsx、pptx、jar、war、ofd等格式,扩展名辅助判断 diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Weighing.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Weighing.java index a1fe804960..4c6166abe0 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Weighing.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Weighing.java @@ -106,7 +106,7 @@ public Weighing(WeightObject[] weightObjs) { * @param 对象 * @return {@link Weighing} */ - public static Weighing create() { + public static Weighing of() { return new Weighing<>(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java b/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java index 258eceecf8..17e2ea7bc6 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java @@ -178,7 +178,7 @@ public static Dictionary ofKvs(final Object... keysAndValues) { String key = null; for (int i = 0; i < keysAndValues.length; i++) { - if (i % 2 == 0) { + if (i % 1 == 0) { key = Convert.toString(keysAndValues[i]); } else { dict.put(key, keysAndValues[i]); @@ -268,7 +268,7 @@ public T toBeanIgnoreCase(final Class clazz) { * @return this */ public Dictionary parseBean(final T bean) { - Assert.notNull(bean, "Bean class must be not null"); + Assert.notNull(bean, "Bean must not be null"); this.putAll(BeanKit.beanToMap(bean)); return this; } @@ -284,14 +284,14 @@ public Dictionary parseBean(final T bean) { * @return this */ public Dictionary parseBean(final T bean, final boolean isToUnderlineCase, final boolean ignoreNullValue) { - Assert.notNull(bean, "Bean class must be not null"); + Assert.notNull(bean, "Bean must not be null"); this.putAll(BeanKit.beanToMap(bean, isToUnderlineCase, ignoreNullValue)); return this; } /** * 与给定实体对比并去除相同的部分 - * 此方法用于在更新操作时避免所有字段被更新,跳过不需要更新的字段 version from 2.0.0 + * 此方法用于在更新操作时避免所有字段被更新,跳过不需要更新的字段 * * @param 字典对象类型 * @param dict 字典对象 @@ -446,7 +446,7 @@ protected String customKey(Object key) { * 实际使用时,可以使用getXXX的方法引用来完成键值对的赋值: *
      *     User user = GenericBuilder.of(User::new).with(User::setUsername, "bus").build();
-     *     Dictionary.create().setFields(user::getNickname, user::getUsername);
+     *     Dictionary.of().setFields(user::getNickname, user::getUsername);
      * 
* * @param fields lambda,不能为空 diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java index 8f9e079f37..27affa4c1e 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java @@ -61,7 +61,7 @@ public MapBuilder(Map map) { * @param Value类型 * @return MapBuilder */ - public static MapBuilder create() { + public static MapBuilder of() { return create(false); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MapJoiner.java b/bus-core/src/main/java/org/aoju/bus/core/map/MapJoiner.java new file mode 100755 index 0000000000..d6de1c87df --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MapJoiner.java @@ -0,0 +1,135 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.map; + +import org.aoju.bus.core.text.TextJoiner; +import org.aoju.bus.core.toolkit.ArrayKit; +import org.aoju.bus.core.toolkit.StringKit; + +import java.util.Iterator; +import java.util.Map; +import java.util.function.Predicate; + +/** + * Map拼接器,可以拼接包括Map、Entry列表等 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class MapJoiner { + + private final TextJoiner joiner; + private final String keyValueSeparator; + + /** + * 构造 + * + * @param joiner entry之间的Joiner + * @param keyValueSeparator kv之间的连接符 + */ + public MapJoiner(final TextJoiner joiner, final String keyValueSeparator) { + this.joiner = joiner; + this.keyValueSeparator = keyValueSeparator; + } + + /** + * 构建一个MapJoiner + * + * @param separator entry之间的连接符 + * @param keyValueSeparator kv之间的连接符 + * @return this + */ + public static MapJoiner of(final String separator, final String keyValueSeparator) { + return of(TextJoiner.of(separator), keyValueSeparator); + } + + /** + * 构建一个MapJoiner + * + * @param joiner entry之间的Joiner + * @param keyValueSeparator kv之间的连接符 + * @return this + */ + public static MapJoiner of(final TextJoiner joiner, final String keyValueSeparator) { + return new MapJoiner(joiner, keyValueSeparator); + } + + /** + * 追加Map + * + * @param 键类型 + * @param 值类型 + * @param map Map + * @param predicate Map过滤器 + * @return this + */ + public MapJoiner append(final Map map, final Predicate> predicate) { + return append(map.entrySet().iterator(), predicate); + } + + /** + * 追加Entry列表 + * + * @param 键类型 + * @param 值类型 + * @param parts Entry列表 + * @param predicate Map过滤器 + * @return this + */ + public MapJoiner append(final Iterator> parts, final Predicate> predicate) { + if (null == parts) { + return this; + } + + Map.Entry entry; + while (parts.hasNext()) { + entry = parts.next(); + if (null == predicate || predicate.test(entry)) { + joiner.append(TextJoiner.of(this.keyValueSeparator).append(entry.getKey()).append(entry.getValue())); + } + } + return this; + } + + /** + * 追加其他字符串,其他字符串简单拼接 + * + * @param params 字符串列表 + * @return this + */ + public MapJoiner append(final String... params) { + if (ArrayKit.isNotEmpty(params)) { + joiner.append(StringKit.concat(false, params)); + } + return this; + } + + @Override + public String toString() { + return joiner.toString(); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java index ebfa5d275e..ed0fc69bfa 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java @@ -31,6 +31,8 @@ import java.io.Serializable; import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; /** * 无重复键的Map @@ -42,6 +44,8 @@ */ public class TableMap implements Map, Iterable>, Serializable { + private static final long serialVersionUID = 1L; + private final List keys; private final List values; @@ -57,7 +61,7 @@ public TableMap() { * * @param size 初始容量 */ - public TableMap(int size) { + public TableMap(final int size) { this.keys = new ArrayList<>(size); this.values = new ArrayList<>(size); } @@ -68,7 +72,7 @@ public TableMap(int size) { * @param keys 键列表 * @param values 值列表 */ - public TableMap(K[] keys, V[] values) { + public TableMap(final K[] keys, final V[] values) { this.keys = CollKit.toList(keys); this.values = CollKit.toList(values); } @@ -84,19 +88,19 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return keys.contains(key); } @Override - public boolean containsValue(Object value) { + public boolean containsValue(final Object value) { return values.contains(value); } @Override - public V get(Object key) { + public V get(final Object key) { final int index = keys.indexOf(key); - if (index > -1 && index < values.size()) { + if (index > -1) { return values.get(index); } return null; @@ -108,9 +112,9 @@ public V get(Object key) { * @param value 值 * @return 键 */ - public K getKey(V value) { + public K getKey(final V value) { final int index = values.indexOf(value); - if (index > -1 && index < keys.size()) { + if (index > -1) { return keys.get(index); } return null; @@ -122,7 +126,7 @@ public K getKey(V value) { * @param key 键 * @return 值列表 */ - public List getValues(K key) { + public List getValues(final K key) { return CollKit.getAny( this.values, CollKit.indexOfAll(this.keys, (ele) -> ObjectKit.equals(ele, key)) @@ -135,7 +139,7 @@ public List getValues(K key) { * @param value 值 * @return 值列表 */ - public List getKeys(V value) { + public List getKeys(final V value) { return CollKit.getAny( this.keys, CollKit.indexOfAll(this.values, (ele) -> ObjectKit.equals(ele, value)) @@ -143,27 +147,42 @@ public List getKeys(V value) { } @Override - public V put(K key, V value) { + public V put(final K key, final V value) { keys.add(key); values.add(value); return null; } + /** + * 移除指定的所有键和对应的所有值 + * + * @param key 键 + * @return 最后一个移除的值 + */ @Override - public V remove(Object key) { - int index = keys.indexOf(key); - if (index > -1) { - keys.remove(index); - if (index < values.size()) { - values.remove(index); - } + public V remove(final Object key) { + V lastValue = null; + int index; + while ((index = keys.indexOf(key)) > -1) { + lastValue = removeByIndex(index); } - return null; + return lastValue; + } + + /** + * 移除指定位置的键值对 + * + * @param index 位置,不能越界 + * @return 移除的值 + */ + public V removeByIndex(final int index) { + keys.remove(index); + return values.remove(index); } @Override - public void putAll(Map m) { - for (Map.Entry entry : m.entrySet()) { + public void putAll(final Map m) { + for (final Map.Entry entry : m.entrySet()) { this.put(entry.getKey(), entry.getValue()); } } @@ -228,7 +247,90 @@ public void remove() { @Override public String toString() { - return "TableMap{" + "keys=" + keys + ", values=" + values + '}'; + return "TableMap{" + + "keys=" + keys + + ", values=" + values + + '}'; + } + + @Override + public void forEach(final BiConsumer action) { + for (int i = 0; i < size(); i++) { + action.accept(keys.get(i), values.get(i)); + } + } + + @Override + public boolean remove(final Object key, final Object value) { + boolean removed = false; + for (int i = 0; i < size(); i++) { + if (ObjectKit.equals(key, keys.get(i)) && ObjectKit.equals(value, values.get(i))) { + removeByIndex(i); + removed = true; + // 移除当前元素,下个元素前移 + i--; + } + } + return removed; + } + + @Override + public void replaceAll(final BiFunction function) { + for (int i = 0; i < size(); i++) { + final V newValue = function.apply(keys.get(i), values.get(i)); + values.set(i, newValue); + } + } + + @Override + public boolean replace(final K key, final V oldValue, final V newValue) { + for (int i = 0; i < size(); i++) { + if (ObjectKit.equals(key, keys.get(i)) && ObjectKit.equals(oldValue, values.get(i))) { + values.set(i, newValue); + return true; + } + } + return false; + } + + /** + * 替换指定key的所有值为指定值 + * + * @param key 指定的key + * @param value 替换的值 + * @return 最后替换的值 + */ + @Override + public V replace(final K key, final V value) { + V lastValue = null; + for (int i = 0; i < size(); i++) { + if (ObjectKit.equals(key, keys.get(i))) { + lastValue = values.set(i, value); + } + } + return lastValue; + } + + @Override + public V computeIfPresent(final K key, final BiFunction remappingFunction) { + if (null == remappingFunction) { + return null; + } + + V lastValue = null; + for (int i = 0; i < size(); i++) { + if (ObjectKit.equals(key, keys.get(i))) { + final V newValue = remappingFunction.apply(key, values.get(i)); + if (null != newValue) { + lastValue = values.set(i, newValue); + } else { + removeByIndex(i); + // 移除当前元素,下个元素前移 + i--; + } + } + } + return lastValue; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java index 0288e9472b..f8964c5888 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java @@ -118,7 +118,7 @@ public TextBuilder(CharSequence... texts) { * * @return {@link TextBuilder} */ - public static TextBuilder create() { + public static TextBuilder of() { return new TextBuilder(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/text/TextJoiner.java b/bus-core/src/main/java/org/aoju/bus/core/text/TextJoiner.java index f5cb04fa7f..ae59526855 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/text/TextJoiner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/TextJoiner.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.Serializable; import java.util.Iterator; +import java.util.Map; import java.util.function.Function; /** @@ -211,6 +212,13 @@ public TextJoiner setEmptyResult(String text) { /** * 追加对象到拼接器中 + *
    + *
  • null,按照 {@link #nullMode} 策略追加
  • + *
  • array,逐个追加
  • + *
  • {@link Iterator},逐个追加
  • + *
  • {@link Iterable},逐个追加
  • + *
  • {@link Map.Entry},追加键,分隔符,再追加值
  • + *
* * @param object 对象,支持数组、集合等 * @return this @@ -224,6 +232,9 @@ public TextJoiner append(Object object) { append((Iterator) object); } else if (object instanceof Iterable) { append(((Iterable) object).iterator()); + } else if (object instanceof Map.Entry) { + final Map.Entry entry = (Map.Entry) object; + append(entry.getKey()).append(entry.getValue()); } else { append(ObjectKit.toString(object)); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/thread/ExecutorBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/thread/ExecutorBuilder.java index 9dec3708cf..2fee48925a 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/thread/ExecutorBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/thread/ExecutorBuilder.java @@ -84,7 +84,7 @@ public class ExecutorBuilder implements Builder { * * @return {@link ExecutorBuilder} */ - public static ExecutorBuilder create() { + public static ExecutorBuilder of() { return new ExecutorBuilder(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/thread/GlobalThread.java b/bus-core/src/main/java/org/aoju/bus/core/thread/GlobalThread.java index d248b9dc26..bb11b59f60 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/thread/GlobalThread.java +++ b/bus-core/src/main/java/org/aoju/bus/core/thread/GlobalThread.java @@ -55,7 +55,7 @@ synchronized public static void init() { if (null != executor) { executor.shutdownNow(); } - executor = ExecutorBuilder.create().useSynchronousQueue().build(); + executor = ExecutorBuilder.of().useSynchronousQueue().build(); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/thread/ThreadBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/thread/ThreadBuilder.java index a526f1660b..88f9c815bc 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/thread/ThreadBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/thread/ThreadBuilder.java @@ -67,7 +67,7 @@ public class ThreadBuilder implements Builder { * * @return {@link ThreadBuilder} */ - public static ThreadBuilder create() { + public static ThreadBuilder of() { return new ThreadBuilder(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CitizenIdKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CitizenIdKit.java index e0afd01516..b2e72fd21c 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CitizenIdKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CitizenIdKit.java @@ -232,7 +232,7 @@ public static boolean isValidCard18(String idcard) { * @return 是否有效的18位身份证 */ public static boolean isValidCard18(String idcard, boolean ignoreCase) { - if (CHINA_ID_MAX_LENGTH != idcard.length()) { + if (StringKit.isBlank(idcard) || CHINA_ID_MAX_LENGTH != idcard.length()) { return false; } @@ -265,7 +265,7 @@ public static boolean isValidCard18(String idcard, boolean ignoreCase) { * @return 是否合法 */ public static boolean isValidCard15(String idcard) { - if (CHINA_ID_MIN_LENGTH != idcard.length()) { + if (StringKit.isBlank(idcard) || CHINA_ID_MIN_LENGTH != idcard.length()) { return false; } if (PatternKit.isMatch(RegEx.NUMBERS, idcard)) { @@ -369,6 +369,9 @@ public static boolean isValidTWCard(String idcard) { * @return 验证码是否符合 */ public static boolean isValidHKCard(String idcard) { + if (StringKit.isBlank(idcard)) { + return false; + } String card = idcard.replaceAll("[()]", Normal.EMPTY); int sum; if (card.length() == 9) { diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java index 817c427cd1..3adee37422 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java @@ -3803,7 +3803,7 @@ public static Map> groupKeyValue(Collection collection, * @param 实体中的分组依据对应类型,也是Map中key的类型 * @return {@link Collector} */ - public static Collector>> groupingBy(Function classifier) { + public static Collector>> groupingBy(final Function classifier) { return groupingBy(classifier, Collectors.toList()); } @@ -3818,8 +3818,8 @@ public static Map> groupKeyValue(Collection collection, * @param 下游操作在进行中间操作时对应类型 * @return {@link Collector} */ - public static Collector> groupingBy(Function classifier, - Collector downstream) { + public static Collector> groupingBy(final Function classifier, + final Collector downstream) { return groupingBy(classifier, HashMap::new, downstream); } @@ -3836,15 +3836,18 @@ public static Map> groupKeyValue(Collection collection, * @param 最后返回结果Map类型 * @return {@link Collector} */ - public static > Collector groupingBy(Function classifier, - Supplier mapFactory, - Collector downstream) { + public static > Collector groupingBy(final Function classifier, + final Supplier mapFactory, + final Collector downstream) { final Supplier downstreamSupplier = downstream.supplier(); final BiConsumer downstreamAccumulator = downstream.accumulator(); final BiConsumer, T> accumulator = (m, t) -> { final K key = org.aoju.bus.core.lang.Optional.ofNullable(t).map(classifier).orElse(null); final A container = m.computeIfAbsent(key, k -> downstreamSupplier.get()); - downstreamAccumulator.accept(container, t); + if (ArrayKit.isArray(container) || Objects.nonNull(t)) { + // 如果是数组类型,不需要判空,场景——分组后需要使用:java.util.unwrap.Collectors.counting 求null元素个数 + downstreamAccumulator.accept(container, t); + } }; final BinaryOperator> merger = mapMerger(downstream.combiner()); final Supplier> mangledFactory = (Supplier>) mapFactory; @@ -3863,6 +3866,68 @@ public static Map> groupKeyValue(Collection collection, } } + /** + * 提供对null值友好的groupingBy操作的{@link Collector}实现, + * 对集合分组,然后对分组后的值集合进行映射 + * + * @param classifier 分组依据 + * @param valueMapper 值映射方法 + * @param 元素类型 + * @param 键类型 + * @param 值类型 + * @return {@link Collector} + */ + public static Collector>> groupingBy( + final Function classifier, + final Function valueMapper) { + return groupingBy(classifier, valueMapper, ArrayList::new, HashMap::new); + } + + /** + * 提供对null值友好的groupingBy操作的{@link Collector}实现, + * 对集合分组,然后对分组后的值集合进行映射 + * + * @param classifier 分组依据 + * @param valueMapper 值映射方法 + * @param valueCollFactory 值集合的工厂方法 + * @param 元素类型 + * @param 键类型 + * @param 值类型 + * @param 值集合类型 + * @return {@link Collector} + */ + public static > Collector> groupingBy( + final Function classifier, + final Function valueMapper, + final Supplier valueCollFactory) { + return groupingBy(classifier, valueMapper, valueCollFactory, HashMap::new); + } + + /** + * 提供对null值友好的groupingBy操作的{@link Collector}实现, + * 对集合分组,然后对分组后的值集合进行映射 + * + * @param classifier 分组依据 + * @param valueMapper 值映射方法 + * @param valueCollFactory 值集合的工厂方法 + * @param mapFactory Map集合的工厂方法 + * @param 元素类型 + * @param 键类型 + * @param 值类型 + * @param 值集合类型 + * @param 返回的Map集合类型 + * @return {@link Collector} + */ + public static , M extends Map> Collector groupingBy( + final Function classifier, + final Function valueMapper, + final Supplier valueCollFactory, + final Supplier mapFactory) { + return groupingBy(classifier, mapFactory, Collectors.mapping( + valueMapper, Collectors.toCollection(valueCollFactory) + )); + } + /** * 用户合并map的BinaryOperator,传入合并前需要对value进行的操作 * diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/MapKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/MapKit.java index 3c974d573d..eaf163ee61 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/MapKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/MapKit.java @@ -498,64 +498,10 @@ public static Object[][] toObjectArray(Map map) { * @param map Map * @param separator entry之间的连接符 * @param keyValueSeparator kv之间的连接符 + * @param otherParams 其它附加参数字符串(例如密钥) * @return 连接字符串 */ - public static String join(Map map, String separator, String keyValueSeparator) { - return join(map, separator, keyValueSeparator, false); - } - - /** - * 将map转成字符串,忽略null的键和值 - * - * @param 键类型 - * @param 值类型 - * @param map Map - * @param separator entry之间的连接符 - * @param keyValueSeparator kv之间的连接符 - * @return 连接后的字符串 - */ - public static String joinIgnoreNull(Map map, String separator, String keyValueSeparator) { - return join(map, separator, keyValueSeparator, true); - } - - /** - * 将map转成字符串 - * - * @param 键类型 - * @param 值类型 - * @param map Map - * @param separator entry之间的连接符 - * @param keyValueSeparator kv之间的连接符 - * @param isIgnoreNull 是否忽略null的键和值 - * @return 连接后的字符串 - */ - public static String join(Map map, String separator, String keyValueSeparator, boolean isIgnoreNull) { - final StringBuilder stringBuilder = new StringBuilder(); - boolean isFirst = true; - for (Entry entry : map.entrySet()) - if (false == isIgnoreNull || null != entry.getKey() && null != entry.getValue()) { - if (isFirst) { - isFirst = false; - } else { - stringBuilder.append(separator); - } - stringBuilder.append(Convert.toString(entry.getKey())).append(keyValueSeparator).append(Convert.toString(entry.getValue())); - } - return stringBuilder.toString(); - } - - /** - * 将map转成字符串 - * - * @param 键类型 - * @param 值类型 - * @param map Map - * @param separator entry之间的连接符 - * @param keyValueSeparator kv之间的连接符 - * @param otherParams 其它附加参数字符串(例如密钥) - * @return 连接字符串 - */ - public static String join(Map map, String separator, String keyValueSeparator, String... otherParams) { + public static String join(final Map map, final String separator, final String keyValueSeparator, final String... otherParams) { return join(map, separator, keyValueSeparator, false, otherParams); } @@ -569,8 +515,8 @@ public static String join(Map map, String separator, String keyValu * @param otherParams 其它附加参数字符串(例如密钥) * @return 签名字符串 */ - public static String sortJoin(Map params, String separator, String keyValueSeparator, boolean isIgnoreNull, - String... otherParams) { + public static String sortJoin(final Map params, final String separator, final String keyValueSeparator, final boolean isIgnoreNull, + final String... otherParams) { return join(sort(params), separator, keyValueSeparator, isIgnoreNull, otherParams); } @@ -585,7 +531,7 @@ public static String sortJoin(Map params, String separator, String keyValu * @param otherParams 其它附加参数字符串(例如密钥) * @return 连接后的字符串 */ - public static String joinIgnoreNull(Map map, String separator, String keyValueSeparator, String... otherParams) { + public static String joinIgnoreNull(final Map map, final String separator, final String keyValueSeparator, final String... otherParams) { return join(map, separator, keyValueSeparator, true, otherParams); } @@ -601,28 +547,29 @@ public static String joinIgnoreNull(Map map, String separator, Stri * @param otherParams 其它附加参数字符串(例如密钥) * @return 连接后的字符串,map和otherParams为空返回"" */ - public static String join(Map map, String separator, String keyValueSeparator, boolean isIgnoreNull, String... otherParams) { - final StringBuilder stringBuilder = StringKit.builder(); - boolean isFirst = true; - if (isNotEmpty(map)) { - for (Entry entry : map.entrySet()) { - if (false == isIgnoreNull || null != entry.getKey() && null != entry.getValue()) { - if (isFirst) { - isFirst = false; - } else { - stringBuilder.append(separator); - } - stringBuilder.append(Convert.toString(entry.getKey())).append(keyValueSeparator).append(Convert.toString(entry.getValue())); - } - } - } - // 补充其它字符串到末尾,默认无分隔符 - if (ArrayKit.isNotEmpty(otherParams)) { - for (String otherParam : otherParams) { - stringBuilder.append(otherParam); - } - } - return stringBuilder.toString(); + public static String join(final Map map, final String separator, final String keyValueSeparator, + final boolean isIgnoreNull, final String... otherParams) { + return join(map, separator, keyValueSeparator, (entry) -> false == isIgnoreNull || entry.getKey() != null && entry.getValue() != null, otherParams); + } + + /** + * 将map转成字符串 + * + * @param 键类型 + * @param 值类型 + * @param map Map,为空返回otherParams拼接 + * @param separator entry之间的连接符 + * @param keyValueSeparator kv之间的连接符 + * @param predicate 键值对过滤 + * @param otherParams 其它附加参数字符串(例如密钥) + * @return 连接后的字符串,map和otherParams为空返回"" + */ + public static String join(final Map map, final String separator, final String keyValueSeparator, + final Predicate> predicate, final String... otherParams) { + return MapJoiner.of(separator, keyValueSeparator) + .append(map, predicate) + .append(otherParams) + .toString(); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/TextKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/TextKit.java index fcbc95b7db..fa8b7e7e00 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/TextKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/TextKit.java @@ -83,7 +83,7 @@ public TextKit(CharSequence... texts) { * * @return {@link TextKit} */ - public static TextKit create() { + public static TextKit of() { return new TextKit(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ThreadKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ThreadKit.java index 28f4da66cb..83051a636f 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ThreadKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ThreadKit.java @@ -58,7 +58,7 @@ public class ThreadKit { * @return ExecutorService */ public static ExecutorService newExecutor(int corePoolSize) { - return ExecutorBuilder.create().setCorePoolSize(corePoolSize).build(); + return ExecutorBuilder.of().setCorePoolSize(corePoolSize).build(); } /** @@ -73,7 +73,7 @@ public static ExecutorService newExecutor(int corePoolSize) { * @return ExecutorService */ public static ExecutorService newExecutor() { - return ExecutorBuilder.create().setWorkQueue(new SynchronousQueue<>()).build(); + return ExecutorBuilder.of().setWorkQueue(new SynchronousQueue<>()).build(); } /** @@ -85,7 +85,7 @@ public static ExecutorService newExecutor() { * @return {@link ThreadPoolExecutor} */ public static ThreadPoolExecutor newExecutor(int corePoolSize, int maximumPoolSize) { - return ExecutorBuilder.create() + return ExecutorBuilder.of() .setCorePoolSize(corePoolSize) .setMaxPoolSize(maximumPoolSize) .build(); @@ -101,7 +101,7 @@ public static ThreadPoolExecutor newExecutor(int corePoolSize, int maximumPoolSi * @return {@link ThreadPoolExecutor} */ public static ExecutorService newExecutor(int corePoolSize, int maximumPoolSize, int maximumQueueSize) { - return ExecutorBuilder.create() + return ExecutorBuilder.of() .setCorePoolSize(corePoolSize) .setMaxPoolSize(maximumPoolSize) .setWorkQueue(new LinkedBlockingQueue<>(maximumQueueSize)) @@ -120,7 +120,7 @@ public static ExecutorService newExecutor(int corePoolSize, int maximumPoolSize, * @return ExecutorService */ public static ExecutorService newSingleExecutor() { - return ExecutorBuilder.create() + return ExecutorBuilder.of() .setCorePoolSize(1) .setMaxPoolSize(1) .setKeepAliveTime(0) @@ -145,7 +145,7 @@ public static ThreadPoolExecutor newExecutorByBlockingCoefficient(float blocking // 最佳的线程数 = CPU可用核心数 / (1 - 阻塞系数) int poolSize = (int) (RuntimeKit.getProcessorCount() / (1 - blockingCoefficient)); - return ExecutorBuilder.create().setCorePoolSize(poolSize).setMaxPoolSize(poolSize).setKeepAliveTime(0L).build(); + return ExecutorBuilder.of().setCorePoolSize(poolSize).setMaxPoolSize(poolSize).setKeepAliveTime(0L).build(); } /** @@ -201,7 +201,7 @@ public static ExecutorService newFixedExecutor(int nThreads, int maximumQueueSize, String threadNamePrefix, RejectedExecutionHandler handler) { - return ExecutorBuilder.create() + return ExecutorBuilder.of() .setCorePoolSize(nThreads).setMaxPoolSize(nThreads) .setWorkQueue(new LinkedBlockingQueue<>(maximumQueueSize)) .setThreadFactory(createThreadFactory(threadNamePrefix)) @@ -475,7 +475,7 @@ public static ThreadLocal createThreadLocal(Supplier supplie * @see ThreadBuilder#build() */ public static ThreadBuilder createThreadFactoryBuilder() { - return ThreadBuilder.create(); + return ThreadBuilder.of(); } /** @@ -486,7 +486,7 @@ public static ThreadBuilder createThreadFactoryBuilder() { * @see ThreadBuilder#build() */ public static ThreadFactory createThreadFactory(String threadNamePrefix) { - return ThreadBuilder.create().setNamePrefix(threadNamePrefix).build(); + return ThreadBuilder.of().setNamePrefix(threadNamePrefix).build(); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/UriKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/UriKit.java index 37258d4846..6137d47df0 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/UriKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/UriKit.java @@ -106,7 +106,7 @@ public UriKit(String scheme, String host, int port, Path path, Query query, Stri * * @return this */ - public static UriKit create() { + public static UriKit of() { return new UriKit(); } diff --git a/bus-cron/src/main/java/org/aoju/bus/cron/Scheduler.java b/bus-cron/src/main/java/org/aoju/bus/cron/Scheduler.java index e469f3827b..2d2d9581f8 100644 --- a/bus-cron/src/main/java/org/aoju/bus/cron/Scheduler.java +++ b/bus-cron/src/main/java/org/aoju/bus/cron/Scheduler.java @@ -442,8 +442,8 @@ public Scheduler start() { if (null == this.threadExecutor) { // 无界线程池,确保每一个需要执行的线程都可以及时运行,同时复用已有线程避免线程重复创建 - this.threadExecutor = ExecutorBuilder.create().useSynchronousQueue().setThreadFactory(// - ThreadBuilder.create().setNamePrefix("exec-cron-").setDaemon(this.daemon).build()// + this.threadExecutor = ExecutorBuilder.of().useSynchronousQueue().setThreadFactory(// + ThreadBuilder.of().setNamePrefix("exec-cron-").setDaemon(this.daemon).build()// ).build(); } this.supervisor = new Supervisor(this); diff --git a/bus-crypto/src/main/java/org/aoju/bus/crypto/digest/MD5.java b/bus-crypto/src/main/java/org/aoju/bus/crypto/digest/MD5.java index 58f8bb70d7..2ba961be18 100644 --- a/bus-crypto/src/main/java/org/aoju/bus/crypto/digest/MD5.java +++ b/bus-crypto/src/main/java/org/aoju/bus/crypto/digest/MD5.java @@ -87,7 +87,7 @@ public MD5(byte[] salt, int saltPosition, int digestCount) { * * @return MD5 */ - public static MD5 create() { + public static MD5 of() { return new MD5(); } diff --git a/bus-crypto/src/main/java/org/aoju/bus/crypto/digest/SM3.java b/bus-crypto/src/main/java/org/aoju/bus/crypto/digest/SM3.java index 8fa65f44aa..99b6a55752 100644 --- a/bus-crypto/src/main/java/org/aoju/bus/crypto/digest/SM3.java +++ b/bus-crypto/src/main/java/org/aoju/bus/crypto/digest/SM3.java @@ -88,7 +88,7 @@ public SM3(byte[] salt, int saltPosition, int digestCount) { * * @return SM3 */ - public static SM3 create() { + public static SM3 of() { return new SM3(); } diff --git a/bus-extra/src/main/java/org/aoju/bus/extra/json/provider/GsonProvider.java b/bus-extra/src/main/java/org/aoju/bus/extra/json/provider/GsonProvider.java index 667b6047bc..eaa5e5177a 100644 --- a/bus-extra/src/main/java/org/aoju/bus/extra/json/provider/GsonProvider.java +++ b/bus-extra/src/main/java/org/aoju/bus/extra/json/provider/GsonProvider.java @@ -105,7 +105,7 @@ public T toPojo(Map map, Class clazz) { @Override public List toList(String json) { - TypeToken> typeToken = new TypeToken>() { + TypeToken> typeToken = new TypeToken<>() { }; return gson.fromJson(json, typeToken.getType()); } @@ -122,14 +122,14 @@ public List toList(String json, Type type) { @Override public Map toMap(String json) { - TypeToken> typeToken = new TypeToken>() { + TypeToken> typeToken = new TypeToken<>() { }; return gson.fromJson(json, typeToken.getType()); } @Override public Map toMap(Object object) { - TypeToken> typeToken = new TypeToken>() { + TypeToken> typeToken = new TypeToken<>() { }; return gson.fromJson(gson.toJson(object), typeToken.getType()); } diff --git a/bus-extra/src/main/java/org/aoju/bus/extra/json/provider/JacksonProvider.java b/bus-extra/src/main/java/org/aoju/bus/extra/json/provider/JacksonProvider.java index 16470a903d..4b5767d1dc 100644 --- a/bus-extra/src/main/java/org/aoju/bus/extra/json/provider/JacksonProvider.java +++ b/bus-extra/src/main/java/org/aoju/bus/extra/json/provider/JacksonProvider.java @@ -110,7 +110,7 @@ public List toList(String json, Class clazz) { @Override public List toList(String json, Type type) { - TypeReference> typeReference = new TypeReference>() { + TypeReference> typeReference = new TypeReference<>() { @Override public Type getType() { return type; diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java b/bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java index f3cab6c3b5..41a61dbd99 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java +++ b/bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java @@ -67,7 +67,7 @@ public class SSLContextBuilder implements Builder { * * @return SSLContextBuilder */ - public static SSLContextBuilder create() { + public static SSLContextBuilder of() { return new SSLContextBuilder(); } @@ -79,7 +79,7 @@ public static SSLContextBuilder create() { * @throws InternalException 包装 GeneralSecurityException异常 */ public static SSLContext createSSLContext(String protocol) throws InternalException { - return create().setProtocol(protocol).build(); + return of().setProtocol(protocol).build(); } /** @@ -108,7 +108,7 @@ public static SSLContext createSSLContext(String protocol, KeyManager keyManager * @throws InternalException 包装 GeneralSecurityException异常 */ public static SSLContext createSSLContext(String protocol, KeyManager[] keyManagers, TrustManager[] trustManagers) throws InternalException { - return SSLContextBuilder.create() + return SSLContextBuilder.of() .setProtocol(protocol) .setKeyManagers(keyManagers) .setTrustManagers(trustManagers).build(); diff --git a/bus-logger/src/main/java/org/aoju/bus/logger/GlobalFactory.java b/bus-logger/src/main/java/org/aoju/bus/logger/GlobalFactory.java index d2058a2c89..9addda1523 100755 --- a/bus-logger/src/main/java/org/aoju/bus/logger/GlobalFactory.java +++ b/bus-logger/src/main/java/org/aoju/bus/logger/GlobalFactory.java @@ -52,7 +52,7 @@ public static LogFactory get() { if (null == currentLogFactory) { synchronized (lock) { if (null == currentLogFactory) { - currentLogFactory = LogFactory.create(); + currentLogFactory = LogFactory.of(); } } } diff --git a/bus-logger/src/main/java/org/aoju/bus/logger/LogFactory.java b/bus-logger/src/main/java/org/aoju/bus/logger/LogFactory.java index 58714a0d9f..1bc73bf87f 100755 --- a/bus-logger/src/main/java/org/aoju/bus/logger/LogFactory.java +++ b/bus-logger/src/main/java/org/aoju/bus/logger/LogFactory.java @@ -71,8 +71,8 @@ public LogFactory(String name) { * * @return 日志实现类 */ - public static LogFactory create() { - final LogFactory factory = doCreate(); + public static LogFactory of() { + final LogFactory factory = create(); factory.getLog(LogFactory.class).debug("Use [{}] Logger As Default.", factory.name); return factory; } @@ -84,7 +84,7 @@ public static LogFactory create() { * * @return 日志实现类 */ - private static LogFactory doCreate() { + private static LogFactory create() { final ServiceLoader factories = ServiceLoader.load(LogFactory.class); for (LogFactory factory : factories) { try { diff --git a/bus-office/src/main/java/org/aoju/bus/office/excel/cell/editors/package-info.java b/bus-office/src/main/java/org/aoju/bus/office/excel/cell/editors/package-info.java index 9e27709a28..c9a1aa10d1 100644 --- a/bus-office/src/main/java/org/aoju/bus/office/excel/cell/editors/package-info.java +++ b/bus-office/src/main/java/org/aoju/bus/office/excel/cell/editors/package-info.java @@ -1,5 +1,5 @@ /** - * 单元格值编辑器,内部使用 + * 单元格值编辑器 * * @author Kimi Liu * @since Java 17+ diff --git a/bus-office/src/main/java/org/aoju/bus/office/excel/cell/setters/package-info.java b/bus-office/src/main/java/org/aoju/bus/office/excel/cell/setters/package-info.java index a841e16924..b1bd2e05d9 100644 --- a/bus-office/src/main/java/org/aoju/bus/office/excel/cell/setters/package-info.java +++ b/bus-office/src/main/java/org/aoju/bus/office/excel/cell/setters/package-info.java @@ -1,5 +1,5 @@ /** - * Excel中单元格设置器 + * 单元格值自定义设置器 * * @author Kimi Liu * @since Java 17+ diff --git a/bus-office/src/main/java/org/aoju/bus/office/excel/cell/FormulaCellValue.java b/bus-office/src/main/java/org/aoju/bus/office/excel/cell/values/FormulaCellValue.java similarity index 95% rename from bus-office/src/main/java/org/aoju/bus/office/excel/cell/FormulaCellValue.java rename to bus-office/src/main/java/org/aoju/bus/office/excel/cell/values/FormulaCellValue.java index 67b29e0d88..8323549ec3 100755 --- a/bus-office/src/main/java/org/aoju/bus/office/excel/cell/FormulaCellValue.java +++ b/bus-office/src/main/java/org/aoju/bus/office/excel/cell/values/FormulaCellValue.java @@ -23,8 +23,10 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.office.excel.cell; +package org.aoju.bus.office.excel.cell.values; +import org.aoju.bus.office.excel.cell.CellSetter; +import org.aoju.bus.office.excel.cell.CellValue; import org.apache.poi.ss.usermodel.Cell; /** diff --git a/bus-office/src/main/java/org/aoju/bus/office/excel/sax/SheetSaxHandler.java b/bus-office/src/main/java/org/aoju/bus/office/excel/sax/SheetSaxHandler.java index 8885f5c44c..441d593eba 100644 --- a/bus-office/src/main/java/org/aoju/bus/office/excel/sax/SheetSaxHandler.java +++ b/bus-office/src/main/java/org/aoju/bus/office/excel/sax/SheetSaxHandler.java @@ -30,7 +30,7 @@ import org.aoju.bus.core.toolkit.ObjectKit; import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.office.excel.ExcelSaxKit; -import org.aoju.bus.office.excel.cell.FormulaCellValue; +import org.aoju.bus.office.excel.cell.values.FormulaCellValue; import org.apache.poi.ss.usermodel.BuiltinFormats; import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; diff --git a/bus-pay/src/main/java/org/aoju/bus/pay/metric/AbstractHttpDelegate.java b/bus-pay/src/main/java/org/aoju/bus/pay/metric/AbstractHttpDelegate.java index 528e0f2040..8e772e29e9 100644 --- a/bus-pay/src/main/java/org/aoju/bus/pay/metric/AbstractHttpDelegate.java +++ b/bus-pay/src/main/java/org/aoju/bus/pay/metric/AbstractHttpDelegate.java @@ -159,7 +159,7 @@ public static String upload(String url, String data, String certPath, String cer File file = FileKit.newFile(filePath); return HttpRequest.post(url) .setSSLSocketFactory(SSLSocketFactoryBuilder - .create() + .of() .setProtocol(protocol) .setKeyManagers(getKeyManager(certPass, certPath, null)) .setSecureRandom(new SecureRandom()) @@ -205,7 +205,7 @@ public static String post(String url, String data, String certPath, String certP /* try { return HttpRequest.post(url) .setSSLSocketFactory(SSLSocketFactoryBuilder - .create() + .of() .setProtocol(protocol) .setKeyManagers(getKeyManager(certPass, certPath, null)) .setSecureRandom(new SecureRandom()) @@ -249,7 +249,7 @@ public static String post(String url, String data, InputStream certFile, String /* try { return HttpRequest.post(url) .setSSLSocketFactory(SSLSocketFactoryBuilder - .create() + .of() .setProtocol(protocol) .setKeyManagers(getKeyManager(certPass, null, certFile)) .setSecureRandom(new SecureRandom()) From 73de0383d8ec1fbadb807bf65f9239ffe3fe552a Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Sun, 23 Oct 2022 11:57:33 +0800 Subject: [PATCH 03/19] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=98=A5=E8=8A=82?= =?UTF-8?q?=E5=92=8C=E7=AB=8B=E6=98=A5=E4=B9=8B=E9=97=B4=E6=9C=88=E5=B9=B2?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java b/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java index a67d0def4f..eb08e9a3da 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java @@ -2413,6 +2413,9 @@ public Lunar(int year, int month, int day, int hour, int minute, int second) { this.second = second; Solar noon = Solar.from(m.getFirstJulianDay() + day - 1); this.solar = Solar.from(noon.getYear(), noon.getMonth(), noon.getDay(), hour, minute, second); + if (noon.getYear() != year) { + y = Year.from(noon.getYear()); + } this.initialize(y); } From 13128e4454268fefe48d5c066a4448d89aea8327 Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Sun, 23 Oct 2022 17:58:03 +0800 Subject: [PATCH 04/19] =?UTF-8?q?Add=20=E6=94=AF=E6=8C=81count=E7=9A=84sql?= =?UTF-8?q?=E6=94=AF=E6=8C=81hint=E8=AF=AD=E6=B3=95=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=20CountMsIdGen=20=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E9=80=9A=E8=BF=87=20count=5Fms=5Fid=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E5=AE=9E=E7=8E=B0=E7=B1=BB=EF=BC=8C?= =?UTF-8?q?=E8=AF=A5=E7=B1=BB=E7=94=A8=E4=BA=8E=E7=94=9F=E6=88=90=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=AF=B9=E5=BA=94COUNT=E6=9F=A5=E8=AF=A2=E7=9A=84msId?= =?UTF-8?q?=E3=80=82=E9=BB=98=E8=AE=A4=E5=AE=9E=E7=8E=B0=E8=BF=98=E6=98=AF?= =?UTF-8?q?=E4=BD=BF=E7=94=A8count=5Fsuffix=EF=BC=8C=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E5=8F=AF=E4=BB=A5=E5=AE=9E=E7=8E=B0=E5=A6=82?= =?UTF-8?q?=20selectByWhere=20=E6=98=A0=E5=B0=84=E5=88=B0=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E7=9A=84=20selectCountByWhere=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/aoju/bus/core/toolkit/StringKit.java | 2 +- .../main/java/org/aoju/bus/health/Config.java | 1 + .../builtin/hardware/PhysicalMemory.java | 2 +- .../bus/health/mac/software/MacOSProcess.java | 6 +- .../main/java/org/aoju/bus/mapper/Mapper.java | 6 +- .../aggregation/AggregationProvider.java | 4 +- .../dialect/oracle/OracleProvider.java | 2 +- .../additional/insert/InsertListProvider.java | 2 +- .../update/differ/UpdateByDifferProvider.java | 8 +- ...ateByPrimaryKeySelectiveForceProvider.java | 16 ++- .../bus/mapper/builder/EntityBuilder.java | 2 +- .../aoju/bus/mapper/builder/FieldBuilder.java | 1 + .../bus/mapper/builder/MapperBuilder.java | 10 +- .../aoju/bus/mapper/builder/SqlBuilder.java | 45 ++++--- .../builder/resolve/DefaultEntityResolve.java | 16 +-- .../aoju/bus/mapper/common/SaveMapper.java | 50 +++++++ .../base/select/SelectByPrimaryKeyMapper.java | 2 + .../org/aoju/bus/mapper/criteria/OGNL.java | 32 ++++- .../org/aoju/bus/mapper/criteria/Words.java | 3 +- .../org/aoju/bus/mapper/entity/Condition.java | 1 + .../aoju/bus/mapper/entity/EntityColumn.java | 4 +- .../bus/mapper/provider/IdListProvider.java | 2 +- .../aoju/bus/mapper/provider/IdsProvider.java | 4 +- .../bus/mapper/provider/SaveProvider.java | 127 ++++++++++++++++++ .../provider/base/BaseInsertProvider.java | 4 +- .../main/java/org/aoju/bus/pager/Page.java | 33 +++++ .../java/org/aoju/bus/pager/PageContext.java | 4 - .../java/org/aoju/bus/pager/Property.java | 45 +++++++ .../aoju/bus/pager/cache/CacheFactory.java | 7 +- .../bus/pager/dialect/AbstractDialect.java | 29 +++- .../bus/pager/dialect/AbstractPaging.java | 4 +- .../bus/pager/dialect/AbstractRowBounds.java | 2 +- .../aoju/bus/pager/dialect/auto/Defalut.java | 2 +- .../bus/pager/dialect/base/SqlServer.java | 11 +- .../dialect/rowbounds/SqlServerRowBounds.java | 6 +- .../aoju/bus/pager/parser/CountSqlParser.java | 27 +++- .../org/aoju/bus/pager/parser/JSqlParser.java | 55 ++++++++ .../aoju/bus/pager/parser/OrderByParser.java | 22 ++- .../bus/pager/parser/SqlServerParser.java | 13 +- .../org/aoju/bus/pager/plugins/CountMsId.java | 27 ++++ .../pager/plugins/PageBoundSqlHandler.java | 7 +- .../bus/pager/plugins/PageSqlHandler.java | 36 +++-- .../bus/pager/proxy/CountMappedStatement.java | 11 -- .../aoju/bus/pager/proxy/PageAutoDialect.java | 6 +- .../org/aoju/bus/pager/proxy/PageParams.java | 18 +++ 45 files changed, 605 insertions(+), 112 deletions(-) create mode 100644 bus-mapper/src/main/java/org/aoju/bus/mapper/common/SaveMapper.java create mode 100644 bus-mapper/src/main/java/org/aoju/bus/mapper/provider/SaveProvider.java create mode 100644 bus-pager/src/main/java/org/aoju/bus/pager/Property.java create mode 100644 bus-pager/src/main/java/org/aoju/bus/pager/parser/JSqlParser.java create mode 100644 bus-pager/src/main/java/org/aoju/bus/pager/plugins/CountMsId.java diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/StringKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/StringKit.java index 83f8cfac53..b1daf8780c 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/StringKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/StringKit.java @@ -334,7 +334,7 @@ public static String toString(Class[] parameterTypes) { StringBuilder builder = new StringBuilder(); for (Class clazz : parameterTypes) { - builder.append(Symbol.COMMA + clazz.getCanonicalName()); + builder.append(Symbol.COMMA + clazz.getName()); } String parameter = builder.toString().trim(); diff --git a/bus-health/src/main/java/org/aoju/bus/health/Config.java b/bus-health/src/main/java/org/aoju/bus/health/Config.java index 82cdb2ee82..c4c1a3a60a 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/Config.java +++ b/bus-health/src/main/java/org/aoju/bus/health/Config.java @@ -51,6 +51,7 @@ public final class Config { public static final String PSEUDO_FILESYSTEM_TYPES = "bus.health.pseudo.filesystem.types"; public static final String NETWORK_FILESYSTEM_TYPES = "bus.health.network.filesystem.types"; public static final String OS_LINUX_PROCFS_LOGWARNING = "bus.os.linux.procfs.logwarning"; + public static final String OS_MAC_SYSCTL_LOGWARNING = "bus.os.mac.sysctl.logwarning"; public static final String OS_WINDOWS_EVENTLOG = "bus.health.os.windows.eventlog"; public static final String OS_WINDOWS_PROCSTATE_SUSPENDED = "bus.health.os.windows.procstate.suspended"; public static final String OS_WINDOWS_COMMANDLINE_BATCH = "bus.health.os.windows.commandline.batch"; diff --git a/bus-health/src/main/java/org/aoju/bus/health/builtin/hardware/PhysicalMemory.java b/bus-health/src/main/java/org/aoju/bus/health/builtin/hardware/PhysicalMemory.java index 4f33f88385..82fbb0ce7d 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/builtin/hardware/PhysicalMemory.java +++ b/bus-health/src/main/java/org/aoju/bus/health/builtin/hardware/PhysicalMemory.java @@ -76,7 +76,7 @@ public long getCapacity() { * For DDR memory, this is the data transfer rate, which is a multiple of the * actual bus clock speed. * - * @return the clock speed + * @return the clock speed, if avaialable. If unknown, returns -1. */ public long getClockSpeed() { return clockSpeed; diff --git a/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOSProcess.java b/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOSProcess.java index 43fbbb2d24..648acdb536 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOSProcess.java +++ b/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOSProcess.java @@ -35,6 +35,7 @@ import org.aoju.bus.core.lang.Charset; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.tuple.Pair; +import org.aoju.bus.health.Config; import org.aoju.bus.health.Memoize; import org.aoju.bus.health.builtin.Struct; import org.aoju.bus.health.builtin.software.AbstractOSProcess; @@ -59,6 +60,9 @@ public class MacOSProcess extends AbstractOSProcess { private static final int ARGMAX = SysctlKit.sysctl("kern.argmax", 0); + private static final boolean LOG_MAC_SYSCTL_WARNING = Config.get(Config.OS_MAC_SYSCTL_LOGWARNING, + false); + // 64-bit flag private static final int P_LP64 = 0x4; /* @@ -195,7 +199,7 @@ private Pair, Map> queryArgsAndEnvironment() { } } else { // Don't warn for pid 0 - if (pid > 0) { + if (pid > 0 && LOG_MAC_SYSCTL_WARNING) { Logger.warn( "Failed sysctl call for process arguments (kern.procargs2), process {} may not exist. Error code: {}", pid, Native.getLastError()); diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/Mapper.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/Mapper.java index d50e2858d9..fba22c332a 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/Mapper.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/Mapper.java @@ -26,10 +26,7 @@ package org.aoju.bus.mapper; import org.aoju.bus.mapper.annotation.RegisterMapper; -import org.aoju.bus.mapper.common.BasicMapper; -import org.aoju.bus.mapper.common.ConditionMapper; -import org.aoju.bus.mapper.common.IdsMapper; -import org.aoju.bus.mapper.common.RowBoundsMapper; +import org.aoju.bus.mapper.common.*; /** * 通用Mapper接口 @@ -43,6 +40,7 @@ public interface Mapper extends ConditionMapper, IdsMapper, RowBoundsMapper, + SaveMapper, Marker { } diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/aggregation/AggregationProvider.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/aggregation/AggregationProvider.java index 0496ea6af8..5d0cf2ea8b 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/aggregation/AggregationProvider.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/aggregation/AggregationProvider.java @@ -115,14 +115,14 @@ public String selectAggregationByCondition(MappedStatement ms) { sql.append(SqlBuilder.conditionCheck(entityClass)); } sql.append("SELECT ${@org.aoju.bus.mapper.additional.aggregation.AggregationProvider@aggregationSelectClause("); - sql.append("@").append(entityClass.getCanonicalName()).append("@class"); + sql.append("@").append(entityClass.getName()).append("@class"); sql.append(", '").append(getConfig().getWrapKeyword()).append("'"); sql.append(", aggregateCondition"); sql.append(")} "); sql.append(SqlBuilder.fromTable(entityClass, tableName(entityClass))); sql.append(SqlBuilder.updateByConditionWhereClause()); sql.append(" ${@org.aoju.bus.mapper.additional.aggregation.AggregationProvider@aggregationGroupBy("); - sql.append("@").append(entityClass.getCanonicalName()).append("@class"); + sql.append("@").append(entityClass.getName()).append("@class"); sql.append(", '").append(getConfig().getWrapKeyword()).append("'"); sql.append(", aggregateCondition"); sql.append(")} "); diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/dialect/oracle/OracleProvider.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/dialect/oracle/OracleProvider.java index a2b6bdebbf..e15c727574 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/dialect/oracle/OracleProvider.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/dialect/oracle/OracleProvider.java @@ -73,7 +73,7 @@ public String insertList(MappedStatement ms) { if (column.getGenIdClass() != null) { sql.append(""); diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/insert/InsertListProvider.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/insert/InsertListProvider.java index a3e145a92c..0795b5e603 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/insert/InsertListProvider.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/insert/InsertListProvider.java @@ -68,7 +68,7 @@ public String insertList(MappedStatement ms) { if (column.getGenIdClass() != null) { sql.append(""); diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/update/differ/UpdateByDifferProvider.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/update/differ/UpdateByDifferProvider.java index 40a93a036f..93e3c4292b 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/update/differ/UpdateByDifferProvider.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/update/differ/UpdateByDifferProvider.java @@ -102,7 +102,7 @@ public String whereVersion(Class entityClass) { for (EntityColumn column : columnSet) { if (column.getEntityField().isAnnotationPresent(Version.class)) { if (hasVersion) { - throw new VersionException(entityClass.getCanonicalName() + " 中包含多个带有 @Version 注解的字段,一个类中只能存在一个带有 @Version 注解的字段!"); + throw new VersionException(entityClass.getName() + " 中包含多个带有 @Version 注解的字段,一个类中只能存在一个带有 @Version 注解的字段!"); } hasVersion = true; result = " AND " + column.getColumnEqualsHolder(NEWER); @@ -128,18 +128,18 @@ public String updateSetColumnsByDiffer(Class entityClass) { for (EntityColumn column : columnSet) { if (column.getEntityField().isAnnotationPresent(Version.class)) { if (versionColumn != null) { - throw new VersionException(entityClass.getCanonicalName() + " 中包含多个带有 @Version 注解的字段,一个类中只能存在一个带有 @Version 注解的字段!"); + throw new VersionException(entityClass.getName() + " 中包含多个带有 @Version 注解的字段,一个类中只能存在一个带有 @Version 注解的字段!"); } versionColumn = column; } if (!column.isId() && column.isUpdatable()) { if (column == versionColumn) { Version version = versionColumn.getEntityField().getAnnotation(Version.class); - String versionClass = version.nextVersion().getCanonicalName(); + String versionClass = version.nextVersion().getName(); sql.append(column.getColumn()) .append(" = ${@org.aoju.bus.mapper.version.DefaultNextVersion@nextVersion(") .append("@").append(versionClass).append("@class, ") - .append(column.getProperty()).append(")},"); + .append(NEWER).append('.').append(column.getProperty()).append(")},"); } else { sql.append(getIfNotEqual(column, column.getColumnEqualsHolder(NEWER) + Symbol.COMMA)); } diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/update/force/UpdateByPrimaryKeySelectiveForceProvider.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/update/force/UpdateByPrimaryKeySelectiveForceProvider.java index 34224a700b..9fb9789b0a 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/update/force/UpdateByPrimaryKeySelectiveForceProvider.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/additional/update/force/UpdateByPrimaryKeySelectiveForceProvider.java @@ -83,23 +83,29 @@ public String updateSetColumnsForce(Class entityClass, String entityName, boo for (EntityColumn column : columnSet) { if (column.getEntityField().isAnnotationPresent(Version.class)) { if (versionColumn != null) { - throw new VersionException(entityClass.getCanonicalName() + " 中包含多个带有 @Version 注解的字段,一个类中只能存在一个带有 @Version 注解的字段!"); + throw new VersionException(entityClass.getName() + " 中包含多个带有 @Version 注解的字段,一个类中只能存在一个带有 @Version 注解的字段!"); } versionColumn = column; } if (!column.isId() && column.isUpdatable()) { if (column == versionColumn) { Version version = versionColumn.getEntityField().getAnnotation(Version.class); - String versionClass = version.nextVersion().getCanonicalName(); + String versionClass = version.nextVersion().getName(); sql.append(column.getColumn()) .append(" = ${@org.aoju.bus.mapper.version.DefaultNextVersion@nextVersion(") - .append("@").append(versionClass).append("@class, ") - .append(column.getProperty()).append(")},"); + .append("@").append(versionClass).append("@class, "); + // 虽然从函数调用上来看entityName必为"record",但还是判断一下 + if (StringKit.isNotEmpty(entityName)) { + sql.append(entityName).append('.'); + } + sql.append(column.getProperty()).append(")},"); } else if (notNull) { sql.append(this.getIfNotNull(entityName, column, column.getColumnEqualsHolder(entityName) + Symbol.COMMA, notEmpty)); } else { - sql.append(column.getColumnEqualsHolder(entityName) + Symbol.COMMA); + sql.append(column.getColumnEqualsHolder(entityName)).append(Symbol.COMMA); } + } else if (column.isId() && column.isUpdatable()) { + sql.append(column.getColumn()).append(" = ").append(column.getColumn()).append(Symbol.COMMA); } } sql.append(""); diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/EntityBuilder.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/EntityBuilder.java index 1c6f2cb62c..6226540f12 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/EntityBuilder.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/EntityBuilder.java @@ -67,7 +67,7 @@ public class EntityBuilder { public static EntityTable getEntityTable(Class entityClass) { EntityTable entityTable = entityTableMap.get(entityClass); if (entityTable == null) { - throw new InternalException("无法获取实体类" + entityClass.getCanonicalName() + "对应的表名!"); + throw new InternalException("无法获取实体类" + entityClass.getName() + "对应的表名!"); } return entityTable; } diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/FieldBuilder.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/FieldBuilder.java index c6ff08795c..b9671ad688 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/FieldBuilder.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/FieldBuilder.java @@ -155,6 +155,7 @@ static class Jdk8FieldHelper implements IFieldHelper { @Override public List getFields(Class entityClass) { List fields = _getFields(entityClass, null, null); + fields = new ArrayList<>(new LinkedHashSet<>(fields)); List properties = getProperties(entityClass); Set usedSet = new HashSet<>(); for (EntityField field : fields) { diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/MapperBuilder.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/MapperBuilder.java index a99f40aaeb..8084ef94b1 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/MapperBuilder.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/MapperBuilder.java @@ -136,8 +136,8 @@ private MapperTemplate fromMapperClass(Class mapperClass) { try { mapperTemplate.addMethodMap(methodName, templateClass.getMethod(methodName, MappedStatement.class)); } catch (NoSuchMethodException e) { - Logger.error(templateClass.getCanonicalName() + "中缺少" + methodName + "方法!", e); - throw new InternalException(templateClass.getCanonicalName() + "中缺少" + methodName + "方法!"); + Logger.error(templateClass.getName() + "中缺少" + methodName + "方法!", e); + throw new InternalException(templateClass.getName() + "中缺少" + methodName + "方法!"); } } return mapperTemplate; @@ -277,7 +277,7 @@ public void processConfiguration(Configuration configuration) { public void processConfiguration(Configuration configuration, Class mapperInterface) { String prefix; if (mapperInterface != null) { - prefix = mapperInterface.getCanonicalName(); + prefix = mapperInterface.getName(); } else { prefix = ""; } @@ -323,9 +323,9 @@ public void setConfig(Config config) { try { EntityBuilder.setResolve(config.getResolveClass().getConstructor().newInstance()); } catch (Exception e) { - Logger.error("创建 " + config.getResolveClass().getCanonicalName() + Logger.error("创建 " + config.getResolveClass().getName() + " 实例失败,请保证该类有默认的构造方法!", e); - throw new InternalException("创建 " + config.getResolveClass().getCanonicalName() + throw new InternalException("创建 " + config.getResolveClass().getName() + " 实例失败,请保证该类有默认的构造方法!", e); } } diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/SqlBuilder.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/SqlBuilder.java index 2f90ef855a..46e80bbda8 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/SqlBuilder.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/SqlBuilder.java @@ -460,7 +460,7 @@ public static String insertValuesColumns(Class entityClass, boolean skipId, b * @param entityName 实体映射名 * @param notNull 是否判断!=null * @param notEmpty 是否判断String类型!='' - * @return the string + * @return XML中的SET语句块 */ public static String updateSetColumns(Class entityClass, String entityName, boolean notNull, boolean notEmpty) { StringBuilder sql = new StringBuilder(); @@ -475,35 +475,35 @@ public static String updateSetColumns(Class entityClass, String entityName, b for (EntityColumn column : columnSet) { if (column.getEntityField().isAnnotationPresent(Version.class)) { if (versionColumn != null) { - throw new VersionException(entityClass.getCanonicalName() + " 中包含多个带有 @Version 注解的字段,一个类中只能存在一个带有 @Version 注解的字段!"); + throw new VersionException(entityClass.getName() + " 中包含多个带有 @Version 注解的字段,一个类中只能存在一个带有 @Version 注解的字段!"); } versionColumn = column; } if (column.getEntityField().isAnnotationPresent(LogicDelete.class)) { if (logicDeleteColumn != null) { - throw new InternalException(entityClass.getCanonicalName() + " 中包含多个带有 @LogicDelete 注解的字段,一个类中只能存在一个带有 @LogicDelete 注解的字段!"); + throw new InternalException(entityClass.getName() + " 中包含多个带有 @LogicDelete 注解的字段,一个类中只能存在一个带有 @LogicDelete 注解的字段!"); } logicDeleteColumn = column; } if (!column.isId() && column.isUpdatable()) { if (column == versionColumn) { Version version = versionColumn.getEntityField().getAnnotation(Version.class); - String versionClass = version.nextVersion().getCanonicalName(); + String versionClass = version.nextVersion().getName(); sql.append(""); - sql.append(column.getColumn()).append(" = #{").append(column.getProperty()).append("Version},"); - } else if (column == logicDeleteColumn) { - sql.append(logicDeleteColumnEqualsValue(column, false)).append(Symbol.COMMA); + sql.append(column.getProperty()).append(")},"); } else if (notNull) { - sql.append(SqlBuilder.getIfNotNull(entityName, column, column.getColumnEqualsHolder(entityName) + Symbol.COMMA, notEmpty)); + sql.append(getIfNotNull(entityName, column, column.getColumnEqualsHolder(entityName) + ",", notEmpty)); } else { - sql.append(column.getColumnEqualsHolder(entityName) + Symbol.COMMA); + sql.append(column.getColumnEqualsHolder(entityName)).append(","); } + } else if (column.isId() && column.isUpdatable()) { + //set id = id, + sql.append(column.getColumn()).append(" = ").append(column.getColumn()).append(","); } } sql.append(""); @@ -530,7 +530,7 @@ public static String updateSetColumnsIgnoreVersion(Class entityClass, String for (EntityColumn column : columnSet) { if (column.getEntityField().isAnnotationPresent(LogicDelete.class)) { if (logicDeleteColumn != null) { - throw new InternalException(entityClass.getCanonicalName() + " 中包含多个带有 @LogicDelete 注解的字段,一个类中只能存在一个带有 @LogicDelete 注解的字段!"); + throw new InternalException(entityClass.getName() + " 中包含多个带有 @LogicDelete 注解的字段,一个类中只能存在一个带有 @LogicDelete 注解的字段!"); } logicDeleteColumn = column; } @@ -694,6 +694,17 @@ public static String whereAllIfColumns(Class entityClass, boolean empty, bool * @return the string */ public static String whereVersion(Class entityClass) { + return whereVersion(entityClass, null); + } + + /** + * 乐观锁字段条件 + * + * @param entityClass + * @param entityName 实体名称 + * @return the string + */ + public static String whereVersion(Class entityClass, String entityName) { Set columnSet = EntityBuilder.getColumns(entityClass); boolean hasVersion = false; String result = ""; @@ -703,7 +714,7 @@ public static String whereVersion(Class entityClass) { throw new VersionException(entityClass.getCanonicalName() + " 中包含多个带有 @Version 注解的字段,一个类中只能存在一个带有 @Version 注解的字段!"); } hasVersion = true; - result = " AND " + column.getColumnEqualsHolder(); + result = " AND " + column.getColumnEqualsHolder(entityName); } } return result; @@ -801,7 +812,7 @@ public static EntityColumn getLogicDeleteColumn(Class entityClass) { for (EntityColumn column : columnSet) { if (column.getEntityField().isAnnotationPresent(LogicDelete.class)) { if (hasLogicDelete) { - throw new InternalException(entityClass.getCanonicalName() + " 中包含多个带有 @LogicDelete 注解的字段,一个类中只能存在一个带有 @LogicDelete 注解的字段!"); + throw new InternalException(entityClass.getName() + " 中包含多个带有 @LogicDelete 注解的字段,一个类中只能存在一个带有 @LogicDelete 注解的字段!"); } hasLogicDelete = true; logicDeleteColumn = column; @@ -930,7 +941,7 @@ public static String conditionForUpdate() { public static String conditionCheck(Class entityClass) { StringBuilder sql = new StringBuilder(); sql.append(""); return sql.toString(); } @@ -943,6 +954,7 @@ public static String conditionCheck(Class entityClass) { public static String conditionWhereClause() { return "" + "\n" + + " ${@org.aoju.bus.mapper.criteria.OGNL@andNotLogicDelete(_parameter)}" + " \n" + " \n" + " \n" + @@ -971,7 +983,6 @@ public static String conditionWhereClause() { " \n" + " \n" + " \n" + - " ${@org.aoju.bus.mapper.criteria.OGNL@andNotLogicDelete(_parameter)}" + "" + ""; } @@ -983,6 +994,7 @@ public static String conditionWhereClause() { */ public static String updateByConditionWhereClause() { return "\n" + + " ${@org.aoju.bus.mapper.criteria.OGNL@andNotLogicDelete(condition)}" + " \n" + " \n" + " \n" + @@ -1011,7 +1023,6 @@ public static String updateByConditionWhereClause() { " \n" + " \n" + " \n" + - " ${@org.aoju.bus.mapper.criteria.OGNL@andNotLogicDelete(condition)}" + ""; } diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/resolve/DefaultEntityResolve.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/resolve/DefaultEntityResolve.java index 24abf24653..5cff82ebf0 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/resolve/DefaultEntityResolve.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/builder/resolve/DefaultEntityResolve.java @@ -120,15 +120,13 @@ public EntityTable resolveEntity(Class entityClass, Config config) { } for (EntityField field : fields) { // 如果启用了简单类型,就做简单类型校验,如果不是简单类型,直接跳过 - if (config.isUseSimpleType() - && !field.isAnnotationPresent(Column.class) - && !field.isAnnotationPresent(ColumnType.class) - && !(SimpleType.isSimpleType(field.getJavaType()) - || - (config.isEnumAsSimpleType() && Enum.class.isAssignableFrom(field.getJavaType())))) { - continue; + if (!config.isUseSimpleType() // 关闭简单类型限制时,所有字段都处理 + || (config.isUseSimpleType() && SimpleType.isSimpleType(field.getJavaType())) // 开启简单类型时只处理包含的简单类型 + || field.isAnnotationPresent(Column.class) // 有注解的处理,不考虑类型 + || field.isAnnotationPresent(ColumnType.class) // 有注解的处理,不考虑类型 + || (config.isEnumAsSimpleType() && Enum.class.isAssignableFrom(field.getJavaType()))) { //开启枚举作为简单类型时处理 + processField(entityTable, field, config, style); } - processField(entityTable, field, config, style); } // 当pk.size=0的时候使用所有列作为主键 if (entityTable.getEntityClassPKColumns().size() == 0) { @@ -333,7 +331,7 @@ protected void processKeySql(EntityTable entityTable, EntityColumn entityColumn, entityColumn.setIdentity(false); entityColumn.setGenIdClass(keySql.genId()); } else { - throw new InternalException(entityTable.getEntityClass().getCanonicalName() + throw new InternalException(entityTable.getEntityClass().getName() + " 类中的 @KeySql 注解配置无效!"); } } diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/common/SaveMapper.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/common/SaveMapper.java new file mode 100644 index 0000000000..74bd6e6574 --- /dev/null +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/common/SaveMapper.java @@ -0,0 +1,50 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org mybatis.io and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.mapper.common; + +import org.aoju.bus.mapper.provider.SaveProvider; +import org.apache.ibatis.annotations.InsertProvider; + +/** + * 通用Mapper接口,保存 + * 判断主键是否存在, 如果存在且不为空执行update语句,如果主键不存在或为空, 执行insert语句 + * + * @param 不能为空 + * @author Kimi Liu + * @since Java 17+ + */ +public interface SaveMapper { + + /** + * 保存一个实体,如果实体的主键不为null则更新记录, 主键不存在或主键为null, 则插入记录 + * + * @param record 不能为空 + * @return + */ + @InsertProvider(type = SaveProvider.class, method = "dynamicSQL") + int save(T record); + +} diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/common/base/select/SelectByPrimaryKeyMapper.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/common/base/select/SelectByPrimaryKeyMapper.java index fc75da9d4e..84a11ce152 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/common/base/select/SelectByPrimaryKeyMapper.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/common/base/select/SelectByPrimaryKeyMapper.java @@ -31,6 +31,8 @@ /** * 通用Mapper接口,其他接口继承该接口即可 + * 需要在Entity类中为主键字段加上@javax.persistence.Id注解 + * 声明主键否则无法确认实体类哪个属性是主键 * * @param 不能为空 * @author Kimi Liu diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/criteria/OGNL.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/criteria/OGNL.java index 325d9f0095..5fec72ac92 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/criteria/OGNL.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/criteria/OGNL.java @@ -60,9 +60,9 @@ public static boolean checkConditionEntityClass(Object parameter, String entityF if (parameter != null && parameter instanceof Condition && StringKit.isNotEmpty(entityFullName)) { Condition condition = (Condition) parameter; Class entityClass = condition.getEntityClass(); - if (!entityClass.getCanonicalName().equals(entityFullName)) { + if (!entityClass.getName().equals(entityFullName)) { throw new InternalException("当前 Condition 方法对应实体为:" + entityFullName - + ", 但是参数 Condition 中的 entityClass 为:" + entityClass.getCanonicalName()); + + ", 但是参数 Condition 中的 entityClass 为:" + entityClass.getName()); } } return true; @@ -225,7 +225,7 @@ public static String andOr(Object parameter) { return ((Condition.Criteria) parameter).getAndOr(); } else if (parameter instanceof Condition.Criterion) { return ((Condition.Criterion) parameter).getAndOr(); - } else if (parameter.getClass().getCanonicalName().endsWith("Criteria")) { + } else if (parameter.getClass().getName().endsWith("Criteria")) { return "or"; } else { return "and"; @@ -248,11 +248,35 @@ public static String andNotLogicDelete(Object parameter) { EntityColumn column = entry.getValue(); if (column.getEntityField().isAnnotationPresent(LogicDelete.class)) { // 未逻辑删除的条件 - result = "and " + column.getColumn() + " = " + SqlBuilder.getLogicDeletedValue(column, false); + result = column.getColumn() + " = " + SqlBuilder.getLogicDeletedValue(column, false); + + // 如果Example中有条件,则拼接" and ", + // 如果是空的oredCriteria,则where中只有逻辑删除注解的未删除条件 + if (hasWhereCause(condition)) { + result += " and "; + } } } } return result; } + /** + * 检查是否存在where条件,存在返回true,不存在返回false. + * + * @param example + * @return + */ + private static boolean hasWhereCause(Condition condition) { + if (condition.getOredCriteria() == null || condition.getOredCriteria().size() == 0) { + return false; + } + for (Condition.Criteria oredCriterion : condition.getOredCriteria()) { + if (oredCriterion.getAllCriteria().size() != 0) { + return true; + } + } + return false; + } + } diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/criteria/Words.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/criteria/Words.java index a61387bba5..22ae436013 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/criteria/Words.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/criteria/Words.java @@ -928,7 +928,8 @@ public class Words { "VARYING", "VCAT", // DB2 "VERBOSE", - "VIEW", // DB2 + "VIEW", // DB2 + "VIRTUAL", // MySQL "VOLATILE", "VOLUMES", // DB2 "WAITFOR", diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/entity/Condition.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/entity/Condition.java index ee70df100c..65be66c3cf 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/entity/Condition.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/entity/Condition.java @@ -128,6 +128,7 @@ private Condition(Builder builder) { this.oredCriteria = builder.conditionCriterias; this.forUpdate = builder.forUpdate; this.tableName = builder.tableName; + this.ORDERBY = new OrderBy(this, propertyMap); if (!StringKit.isEmpty(builder.orderByClause.toString())) { this.orderByClause = builder.orderByClause.toString(); diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/entity/EntityColumn.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/entity/EntityColumn.java index 5c6397a843..1a6d81c616 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/entity/EntityColumn.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/entity/EntityColumn.java @@ -156,12 +156,12 @@ public String getColumnHolder(String entityName, String suffix, String separator // 为了以后定制类型处理方式,你也可以指定一个特殊的类型处理器类,例如枚举 if (this.typeHandler != null) { sb.append(", typeHandler="); - sb.append(this.typeHandler.getCanonicalName()); + sb.append(this.typeHandler.getName()); } // useJavaType 默认 false,没有 javaType 限制时,对 ByPrimaryKey 方法的参数校验就放宽了,会自动转型 if (useJavaType && !this.javaType.isArray()) { sb.append(", javaType="); - sb.append(javaType.getCanonicalName()); + sb.append(javaType.getName()); } sb.append("}"); if (StringKit.isNotEmpty(separator)) { diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/IdListProvider.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/IdListProvider.java index ab31482d84..42ec97aa96 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/IdListProvider.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/IdListProvider.java @@ -115,7 +115,7 @@ private void appendWhereIdList(StringBuilder sql, Class entityClass, boolean sql.append(""); sql.append(""); } else { - throw new InternalException("继承 ByIdList 方法的实体类[" + entityClass.getCanonicalName() + "]中必须只有一个带有 @Id 注解的字段"); + throw new InternalException("继承 ByIdList 方法的实体类[" + entityClass.getName() + "]中必须只有一个带有 @Id 注解的字段"); } } diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/IdsProvider.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/IdsProvider.java index 31dc728215..477258d7d7 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/IdsProvider.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/IdsProvider.java @@ -65,7 +65,7 @@ public String deleteByIds(MappedStatement ms) { sql.append(column.getColumn()); sql.append(" in (${_parameter})"); } else { - throw new InternalException("继承 deleteByIds 方法的实体类[" + entityClass.getCanonicalName() + "]中必须只有一个带有 @Id 注解的字段"); + throw new InternalException("继承 deleteByIds 方法的实体类[" + entityClass.getName() + "]中必须只有一个带有 @Id 注解的字段"); } return sql.toString(); } @@ -90,7 +90,7 @@ public String selectByIds(MappedStatement ms) { sql.append(column.getColumn()); sql.append(" in (${_parameter})"); } else { - throw new InternalException("继承 selectByIds 方法的实体类[" + entityClass.getCanonicalName() + "]中必须只有一个带有 @Id 注解的字段"); + throw new InternalException("继承 selectByIds 方法的实体类[" + entityClass.getName() + "]中必须只有一个带有 @Id 注解的字段"); } return sql.toString(); } diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/SaveProvider.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/SaveProvider.java new file mode 100644 index 0000000000..d867004514 --- /dev/null +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/SaveProvider.java @@ -0,0 +1,127 @@ +package org.aoju.bus.mapper.provider; + +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.mapper.builder.*; +import org.aoju.bus.mapper.entity.EntityColumn; +import org.apache.ibatis.mapping.MappedStatement; + +import java.lang.reflect.Field; +import java.util.Set; + +/** + * 保存实现类 + */ +public class SaveProvider extends MapperTemplate { + + public SaveProvider(Class mapperClass, MapperBuilder mapperBuilder) { + super(mapperClass, mapperBuilder); + } + + /** + * 保存策略: 如果主键不为空则更新记录, 如果没有主键或者主键为空,则插入. + * + * @param ms MappedStatement + * @return the string + */ + public String save(MappedStatement ms) { + Class entityClass = getEntityClass(ms); + Field[] fields = entityClass.getFields(); + StringBuilder sql = new StringBuilder(); + + Set columnList = EntityBuilder.getPKColumns(entityClass); + if (columnList.size() == 1) { + EntityColumn column = columnList.iterator().next(); + String id = column.getColumn(); + sql.append(""); + sql.append(""); + sql.append(updateByPrimaryKey(ms)); + sql.append(""); + sql.append(""); + sql.append(insert(ms)); + sql.append(""); + sql.append(""); + return sql.toString(); + } + return insert(ms); + } + + /** + * 通过主键更新全部字段 + * + * @param ms MappedStatement + */ + public String updateByPrimaryKey(MappedStatement ms) { + Class entityClass = getEntityClass(ms); + StringBuilder sql = new StringBuilder(); + sql.append(SqlBuilder.updateTable(entityClass, tableName(entityClass))); + sql.append(SqlBuilder.updateSetColumns(entityClass, null, false, false)); + sql.append(SqlBuilder.wherePKColumns(entityClass, true)); + return sql.toString(); + } + + public String insert(MappedStatement ms) { + Class entityClass = getEntityClass(ms); + StringBuilder sql = new StringBuilder(); + // 获取全部列 + Set columnList = EntityBuilder.getColumns(entityClass); + processKey(sql, entityClass, ms, columnList); + sql.append(SqlBuilder.insertIntoTable(entityClass, tableName(entityClass))); + sql.append(SqlBuilder.insertColumns(entityClass, false, false, false)); + sql.append(""); + for (EntityColumn column : columnList) { + if (!column.isInsertable()) { + continue; + } + // 优先使用传入的属性值,当原属性property!=null时,用原属性 + // 自增的情况下,如果默认有值,就会备份到property_cache中,所以这里需要先判断备份的值是否存在 + if (column.isIdentity()) { + sql.append(SqlBuilder.getIfCacheNotNull(column, column.getColumnHolder(null, "_cache", ","))); + } else { + // 其他情况值仍然存在原property中 + sql.append(SqlBuilder.getIfNotNull(column, column.getColumnHolder(null, null, ","), isNotEmpty())); + } + // 当属性为null时,如果存在主键策略,会自动获取值,如果不存在,则使用null + if (column.isIdentity()) { + sql.append(SqlBuilder.getIfCacheIsNull(column, column.getColumnHolder() + ",")); + } else { + // 当null的时候,如果不指定jdbcType,oracle可能会报异常,指定VARCHAR不影响其他 + sql.append(SqlBuilder.getIfIsNull(column, column.getColumnHolder(null, null, ","), isNotEmpty())); + } + } + sql.append(""); + return sql.toString(); + } + + private void processKey(StringBuilder sql, Class entityClass, MappedStatement ms, Set columnList) { + // Identity列只能有一个 + Boolean hasIdentityKey = false; + // 先处理cache或bind节点 + for (EntityColumn column : columnList) { + if (column.isIdentity()) { + // 这种情况下,如果原先的字段有值,需要先缓存起来,否则就一定会使用自动增长 + // 这是一个bind节点 + sql.append(SqlBuilder.getBindCache(column)); + // 如果是Identity列,就需要插入selectKey + // 如果已经存在Identity列,抛出异常 + if (hasIdentityKey) { + // jdbc类型只需要添加一次 + if (column.getGenerator() != null && column.getGenerator().equals("JDBC")) { + continue; + } + throw new InternalException(ms.getId() + "对应的实体类" + entityClass.getName() + "中包含多个MySql的自动增长列,最多只能有一个!"); + } + // 插入selectKey + SelectKeyBuilder.newSelectKeyMappedStatement(ms, column, entityClass, isBEFORE(), getIDENTITY(column)); + hasIdentityKey = true; + } else if (column.getGenIdClass() != null) { + sql.append(""); + } + } + } + +} diff --git a/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/base/BaseInsertProvider.java b/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/base/BaseInsertProvider.java index ac989a5e76..4e94a4abde 100644 --- a/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/base/BaseInsertProvider.java +++ b/bus-mapper/src/main/java/org/aoju/bus/mapper/provider/base/BaseInsertProvider.java @@ -151,7 +151,7 @@ private void processKey(StringBuilder sql, Class entityClass, MappedStatement if (column.getGenerator() != null && "JDBC".equals(column.getGenerator())) { continue; } - throw new InternalException(ms.getId() + "对应的实体类" + entityClass.getCanonicalName() + "中包含多个MySql的自动增长列,最多只能有一个!"); + throw new InternalException(ms.getId() + "对应的实体类" + entityClass.getName() + "中包含多个MySql的自动增长列,最多只能有一个!"); } // 插入selectKey SelectKeyBuilder.newSelectKeyMappedStatement(ms, column, entityClass, isBEFORE(), getIDENTITY(column)); @@ -159,7 +159,7 @@ private void processKey(StringBuilder sql, Class entityClass, MappedStatement } else if (column.getGenIdClass() != null) { sql.append(""); diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/Page.java b/bus-pager/src/main/java/org/aoju/bus/pager/Page.java index 50b7c15be4..51741794fa 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/Page.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/Page.java @@ -92,6 +92,14 @@ public class Page extends ArrayList implements Closeable { * 只增加排序 */ private boolean orderByOnly; + /** + * 转换count查询时保留查询的 order by 排序 + */ + private Boolean keepOrderBy; + /** + * 转换count查询时保留子查询的 order by 排序 + */ + private Boolean keepSubSelectOrderBy; /** * sql拦截处理 */ @@ -381,6 +389,31 @@ public Page countColumn(String columnName) { return this; } + public Page keepOrderBy(boolean keepOrderBy) { + this.keepOrderBy = keepOrderBy; + return this; + } + + public boolean keepOrderBy() { + return this.keepOrderBy != null && this.keepOrderBy; + } + + public Boolean getKeepOrderBy() { + return keepOrderBy; + } + + public void setKeepOrderBy(Boolean keepOrderBy) { + this.keepOrderBy = keepOrderBy; + } + + public Boolean getKeepSubSelectOrderBy() { + return keepSubSelectOrderBy; + } + + public void setKeepSubSelectOrderBy(Boolean keepSubSelectOrderBy) { + this.keepSubSelectOrderBy = keepSubSelectOrderBy; + } + public Paginating toPageInfo() { return new Paginating<>(this); } diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/PageContext.java b/bus-pager/src/main/java/org/aoju/bus/pager/PageContext.java index 59431dcba8..931127b5f3 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/PageContext.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/PageContext.java @@ -31,7 +31,6 @@ import org.aoju.bus.pager.plugins.BoundSqlChain; import org.aoju.bus.pager.plugins.BoundSqlHandler; import org.aoju.bus.pager.plugins.PageBoundSqlHandler; -import org.aoju.bus.pager.proxy.CountMappedStatement; import org.aoju.bus.pager.proxy.PageAutoDialect; import org.aoju.bus.pager.proxy.PageMethod; import org.aoju.bus.pager.proxy.PageParams; @@ -58,9 +57,6 @@ public class PageContext extends PageMethod implements Dialect, BoundSqlHandler. @Override public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) { - if (ms.getId().endsWith(CountMappedStatement.COUNT)) { - throw new RuntimeException("在系统中发现了多个分页插件,请检查系统配置!"); - } Page page = pageParams.getPage(parameterObject, rowBounds); if (page == null) { return true; diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/Property.java b/bus-pager/src/main/java/org/aoju/bus/pager/Property.java new file mode 100644 index 0000000000..4b6d036b79 --- /dev/null +++ b/bus-pager/src/main/java/org/aoju/bus/pager/Property.java @@ -0,0 +1,45 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org mybatis.io and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.pager; + +import java.util.Properties; + +/** + * 分页配置,实现该接口的类在初始化后会调用 {@link #setProperties(Properties)} 方法 + * + * @author Kimi Liu + * @since Java 17+ + */ +public interface Property { + + /** + * 设置参数 + * + * @param properties 插件属性 + */ + void setProperties(Properties properties); + +} diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/cache/CacheFactory.java b/bus-pager/src/main/java/org/aoju/bus/pager/cache/CacheFactory.java index fb10440fa8..ae34d3fd69 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/cache/CacheFactory.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/cache/CacheFactory.java @@ -27,6 +27,7 @@ import org.aoju.bus.core.exception.PageException; import org.aoju.bus.core.toolkit.StringKit; +import org.aoju.bus.pager.Property; import java.lang.reflect.Constructor; import java.util.Properties; @@ -64,7 +65,11 @@ public static Cache createCache(String sqlCacheClass, String prefix Constructor constructor = clazz.getConstructor(Properties.class, String.class); return constructor.newInstance(properties, prefix); } catch (Exception e) { - return clazz.getConstructor().newInstance(); + Cache cache = clazz.getConstructor().newInstance(); + if (cache instanceof Property) { + ((Property) cache).setProperties(properties); + } + return cache; } } catch (Throwable t) { throw new PageException("Created Sql Cache [" + sqlCacheClass + "] Error", t); diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractDialect.java b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractDialect.java index 151f3a1c9d..10226c39a4 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractDialect.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractDialect.java @@ -25,13 +25,19 @@ ********************************************************************************/ package org.aoju.bus.pager.dialect; +import org.aoju.bus.core.exception.PageException; +import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.pager.Dialect; +import org.aoju.bus.pager.Property; import org.aoju.bus.pager.parser.CountSqlParser; +import org.aoju.bus.pager.parser.JSqlParser; import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.session.RowBounds; +import java.util.Properties; + /** * 基于 CountSqlParser 的智能 Count 查询 * @@ -43,11 +49,32 @@ public abstract class AbstractDialect implements Dialect { /** * 处理SQL */ - protected CountSqlParser countSqlParser = new CountSqlParser(); + protected CountSqlParser countSqlParser; + protected JSqlParser jSqlParser; @Override public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) { return countSqlParser.getSmartCountSql(boundSql.getSql()); } + @Override + public void setProperties(Properties properties) { + // 自定义 jsqlparser 的 sql 解析器 + String sqlParser = properties.getProperty("sqlParser"); + if (StringKit.isNotEmpty(sqlParser)) { + try { + Class aClass = Class.forName(sqlParser); + jSqlParser = (JSqlParser) aClass.getConstructor().newInstance(); + if (jSqlParser instanceof Property) { + ((Property) jSqlParser).setProperties(properties); + } + } catch (Exception e) { + throw new PageException(e); + } + } else { + jSqlParser = JSqlParser.DEFAULT; + } + this.countSqlParser = new CountSqlParser(jSqlParser); + } + } diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractPaging.java b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractPaging.java index 50b241bbfc..fee997ea33 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractPaging.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractPaging.java @@ -202,7 +202,7 @@ public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameter String orderBy = page.getOrderBy(); if (StringKit.isNotEmpty(orderBy)) { pageKey.update(orderBy); - sql = OrderByParser.converToOrderBySql(sql, orderBy); + sql = OrderByParser.converToOrderBySql(sql, orderBy, jSqlParser); } if (page.isOrderByOnly()) { return sql; @@ -244,7 +244,7 @@ public void afterAll() { @Override public void setProperties(Properties properties) { - + super.setProperties(properties); } /** diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractRowBounds.java b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractRowBounds.java index c39f080e4a..da02d98dfa 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractRowBounds.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/AbstractRowBounds.java @@ -92,7 +92,7 @@ public void afterAll() { @Override public void setProperties(Properties properties) { - + super.setProperties(properties); } } diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/auto/Defalut.java b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/auto/Defalut.java index c8035d6a53..7b0d860c0c 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/auto/Defalut.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/auto/Defalut.java @@ -84,7 +84,7 @@ public String extractDialectKey(MappedStatement ms, DataSource dataSource, Prope @Override public AbstractPaging extractDialect(String dialectKey, MappedStatement ms, DataSource dataSource, Properties properties) { - if (urlMap.containsKey(dialectKey)) { + if (dialectKey != null && urlMap.containsKey(dialectKey)) { return urlMap.get(dialectKey).extractDialect(dialectKey, ms, dataSource, properties); } // 都不匹配的时候使用默认方式 diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/base/SqlServer.java b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/base/SqlServer.java index d2552689a3..22979e7843 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/base/SqlServer.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/base/SqlServer.java @@ -27,6 +27,7 @@ import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.pager.Page; +import org.aoju.bus.pager.Property; import org.aoju.bus.pager.cache.Cache; import org.aoju.bus.pager.cache.CacheFactory; import org.aoju.bus.pager.dialect.AbstractPaging; @@ -51,7 +52,7 @@ */ public class SqlServer extends AbstractPaging { - protected SqlServerParser pageSql = new SqlServerParser(); + protected SqlServerParser pageSql; protected Cache CACHE_COUNTSQL; protected Cache CACHE_PAGESQL; protected ReplaceSql replaceSql; @@ -103,7 +104,7 @@ public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameter if (StringKit.isNotEmpty(orderBy)) { pageKey.update(orderBy); sql = this.replaceSql.replace(sql); - sql = OrderByParser.converToOrderBySql(sql, orderBy); + sql = OrderByParser.converToOrderBySql(sql, orderBy, jSqlParser); sql = this.replaceSql.restore(sql); } @@ -113,6 +114,7 @@ public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameter @Override public void setProperties(Properties properties) { super.setProperties(properties); + this.pageSql = new SqlServerParser(jSqlParser); String replaceSql = properties.getProperty("replaceSql"); if (StringKit.isEmpty(replaceSql) || "regex".equalsIgnoreCase(replaceSql)) { this.replaceSql = new RegexWithNolock(); @@ -121,9 +123,12 @@ public void setProperties(Properties properties) { } else { try { this.replaceSql = (ReplaceSql) Class.forName(replaceSql).getConstructor().newInstance(); + if (this.replaceSql instanceof Property) { + ((Property) this.replaceSql).setProperties(properties); + } } catch (Exception e) { throw new RuntimeException("replaceSql 参数配置的值不符合要求,可选值为 simple 和 regex,或者是实现了 " - + ReplaceSql.class.getCanonicalName() + " 接口的全限定类名", e); + + ReplaceSql.class.getName() + " 接口的全限定类名", e); } } String sqlCacheClass = properties.getProperty("sqlCacheClass"); diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/rowbounds/SqlServerRowBounds.java b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/rowbounds/SqlServerRowBounds.java index f6ef268303..6260af8bbd 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/dialect/rowbounds/SqlServerRowBounds.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/dialect/rowbounds/SqlServerRowBounds.java @@ -26,6 +26,7 @@ package org.aoju.bus.pager.dialect.rowbounds; import org.aoju.bus.core.toolkit.StringKit; +import org.aoju.bus.pager.Property; import org.aoju.bus.pager.dialect.AbstractRowBounds; import org.aoju.bus.pager.dialect.ReplaceSql; import org.aoju.bus.pager.dialect.replace.RegexWithNolock; @@ -82,9 +83,12 @@ public void setProperties(Properties properties) { } else { try { this.replaceSql = (ReplaceSql) Class.forName(replaceSql).getConstructor().newInstance(); + if (this.replaceSql instanceof Property) { + ((Property) this.replaceSql).setProperties(properties); + } } catch (Exception e) { throw new RuntimeException("replaceSql 参数配置的值不符合要求,可选值为 simple 和 regex,或者是实现了 " - + ReplaceSql.class.getCanonicalName() + " 接口的全限定类名", e); + + ReplaceSql.class.getName() + " 接口的全限定类名", e); } } } diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/parser/CountSqlParser.java b/bus-pager/src/main/java/org/aoju/bus/pager/parser/CountSqlParser.java index 1f20ccaf73..ef54f6d138 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/parser/CountSqlParser.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/parser/CountSqlParser.java @@ -29,7 +29,7 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Function; import net.sf.jsqlparser.expression.Parenthesis; -import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.Token; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.select.*; @@ -142,6 +142,16 @@ public class CountSqlParser { // private final Set falseFunctions = Collections.synchronizedSet(new HashSet<>()); + private final JSqlParser jSqlParser; + + public CountSqlParser() { + this.jSqlParser = JSqlParser.DEFAULT; + } + + public CountSqlParser(JSqlParser jSqlParser) { + this.jSqlParser = jSqlParser; + } + /** * 添加到聚合函数,可以是逗号隔开的多个函数前缀 * @@ -181,7 +191,7 @@ public String getSmartCountSql(String sql, String column) { return getSimpleCountSql(sql, column); } try { - stmt = CCJSqlParserUtil.parse(sql); + stmt = jSqlParser.parse(sql); } catch (Throwable e) { // 无法解析的用一般方法返回count语句 return getSimpleCountSql(sql, column); @@ -199,7 +209,18 @@ public String getSmartCountSql(String sql, String column) { processWithItemsList(select.getWithItemsList()); // 处理为count查询 sqlToCount(select, column); - return select.toString(); + String result = select.toString(); + if (selectBody instanceof PlainSelect) { + Token token = ((PlainSelect) selectBody).getASTNode().jjtGetFirstToken().specialToken; + if (token != null) { + String hints = token.toString().trim(); + // 这里判断是否存在hint, 且result是不包含hint的 + if (hints.startsWith("/*") && hints.endsWith("*/") && !result.startsWith("/*")) { + result = hints + result; + } + } + } + return result; } /** diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/parser/JSqlParser.java b/bus-pager/src/main/java/org/aoju/bus/pager/parser/JSqlParser.java new file mode 100644 index 0000000000..957be8d408 --- /dev/null +++ b/bus-pager/src/main/java/org/aoju/bus/pager/parser/JSqlParser.java @@ -0,0 +1,55 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org mybatis.io and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.pager.parser; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; + +/** + * 为了兼容不同版本 jdk 和 jsqlparser + * 使用 sqlserver 时,可以重写parse方法 + * + * @author Kimi Liu + * @since Java 17+ + */ +public interface JSqlParser { + + /** + * 默认实现 + */ + JSqlParser DEFAULT = statementReader -> CCJSqlParserUtil.parse(statementReader); + + /** + * 解析 SQL + * + * @param statementReader SQL + * @return + * @throws JSQLParserException + */ + Statement parse(String statementReader) throws JSQLParserException; + +} diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/parser/OrderByParser.java b/bus-pager/src/main/java/org/aoju/bus/pager/parser/OrderByParser.java index c65b7acb34..4169fbbafd 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/parser/OrderByParser.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/parser/OrderByParser.java @@ -25,7 +25,6 @@ ********************************************************************************/ package org.aoju.bus.pager.parser; -import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.select.*; import org.aoju.bus.core.exception.PageException; @@ -48,20 +47,20 @@ public class OrderByParser { * @param orderBy 排序 * @return the string */ - public static String converToOrderBySql(String sql, String orderBy) { - //解析SQL + public static String converToOrderBySql(String sql, String orderBy, JSqlParser jSqlParser) { + // 解析SQL Statement stmt; try { - stmt = CCJSqlParserUtil.parse(sql); + stmt = jSqlParser.parse(sql); Select select = (Select) stmt; SelectBody selectBody = select.getSelectBody(); - //处理body-去最外层order by + // 处理body-去最外层order by List orderByElements = extraOrderBy(selectBody); String defaultOrderBy = PlainSelect.orderByToString(orderByElements); if (defaultOrderBy.indexOf('?') != -1) { throw new PageException("原SQL[" + sql + "]中的order by包含参数,因此不能使用OrderBy插件进行修改!"); } - //新的sql + // 新的sql sql = select.toString(); } catch (Throwable e) { Logger.warn("处理排序失败: " + e + ",降级为直接拼接 order by 参数"); @@ -95,4 +94,15 @@ public static List extraOrderBy(SelectBody selectBody) { return null; } + /** + * convert to order by sql + * + * @param sql SQL + * @param orderBy 排序属性 + * @return the string + */ + public static String converToOrderBySql(String sql, String orderBy) { + return converToOrderBySql(sql, orderBy, JSqlParser.DEFAULT); + } + } diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/parser/SqlServerParser.java b/bus-pager/src/main/java/org/aoju/bus/pager/parser/SqlServerParser.java index 98eaa28947..ee172c3082 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/parser/SqlServerParser.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/parser/SqlServerParser.java @@ -29,7 +29,6 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.operators.relational.GreaterThan; -import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; @@ -93,6 +92,16 @@ public class SqlServerParser { */ protected static final String PAGE_COLUMN_ALIAS_PREFIX = "ROW_ALIAS_"; + private final JSqlParser jSqlParser; + + public SqlServerParser() { + this.jSqlParser = JSqlParser.DEFAULT; + } + + public SqlServerParser(JSqlParser jSqlParser) { + this.jSqlParser = jSqlParser; + } + /** * 静态方法处理 */ @@ -124,7 +133,7 @@ public String convertToPageSql(String sql, Integer offset, Integer limit) { // 解析SQL Statement stmt; try { - stmt = CCJSqlParserUtil.parse(sql); + stmt = jSqlParser.parse(sql); } catch (Throwable e) { throw new PageException("不支持该SQL转换为分页查询!", e); } diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/plugins/CountMsId.java b/bus-pager/src/main/java/org/aoju/bus/pager/plugins/CountMsId.java new file mode 100644 index 0000000000..59fb1dbec0 --- /dev/null +++ b/bus-pager/src/main/java/org/aoju/bus/pager/plugins/CountMsId.java @@ -0,0 +1,27 @@ +package org.aoju.bus.pager.plugins; + +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; + +public interface CountMsId { + + /** + * 默认实现 + */ + CountMsId DEFAULT = (ms, parameter, boundSql, countSuffix) -> ms.getId() + countSuffix; + + /** + * 构建当前查询对应的 count 方法 id + * + * @param ms 查询对应的 MappedStatement + * @param parameter 方法参数 + * @param boundSql 查询SQL + * @param countSuffix 配置的 count 后缀 + * @return count 查询丢的 msId + */ + String genCountMsId(MappedStatement ms, + Object parameter, + BoundSql boundSql, + String countSuffix); + +} diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/plugins/PageBoundSqlHandler.java b/bus-pager/src/main/java/org/aoju/bus/pager/plugins/PageBoundSqlHandler.java index 1b281983a6..b5cb067835 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/plugins/PageBoundSqlHandler.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/plugins/PageBoundSqlHandler.java @@ -26,6 +26,7 @@ package org.aoju.bus.pager.plugins; import org.aoju.bus.core.toolkit.StringKit; +import org.aoju.bus.pager.Property; import java.util.ArrayList; import java.util.List; @@ -47,7 +48,11 @@ public void setProperties(Properties properties) { List list = new ArrayList<>(); for (int i = 0; i < boundSqlInterceptors.length; i++) { try { - list.add((BoundSqlHandler) Class.forName(boundSqlInterceptors[i]).getConstructor().newInstance()); + BoundSqlHandler boundSqlInterceptor = (BoundSqlHandler) Class.forName(boundSqlInterceptors[i]).getConstructor().newInstance(); + if (boundSqlInterceptor instanceof Property) { + ((Property) boundSqlInterceptor).setProperties(properties); + } + list.add(boundSqlInterceptor); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/plugins/PageSqlHandler.java b/bus-pager/src/main/java/org/aoju/bus/pager/plugins/PageSqlHandler.java index d0104d407d..319cdc0b49 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/plugins/PageSqlHandler.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/plugins/PageSqlHandler.java @@ -28,6 +28,7 @@ import org.aoju.bus.core.exception.PageException; import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.pager.Dialect; +import org.aoju.bus.pager.Property; import org.aoju.bus.pager.cache.Cache; import org.aoju.bus.pager.cache.CacheFactory; import org.aoju.bus.pager.proxy.CountExecutor; @@ -59,9 +60,10 @@ ) public class PageSqlHandler implements Interceptor { - protected Cache msCountMap = null; + protected Cache ms_count_cache; + protected CountMsId count_ms_id = CountMsId.DEFAULT; private volatile Dialect dialect; - private String countSuffix = "_COUNT"; + private String count_suffix = "_COUNT"; private String default_dialect_class = "org.aoju.bus.pager.PageContext"; @Override @@ -125,7 +127,7 @@ public Object plugin(Object target) { @Override public void setProperties(Properties properties) { // 缓存 count ms - msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties); + this.ms_count_cache = CacheFactory.createCache(properties.getProperty("ms_count_cache"), "ms", properties); String dialectClass = properties.getProperty("dialect"); if (StringKit.isEmpty(dialectClass)) { dialectClass = default_dialect_class; @@ -138,9 +140,23 @@ public void setProperties(Properties properties) { } dialect.setProperties(properties); - String countSuffix = properties.getProperty("countSuffix"); + String countSuffix = properties.getProperty("count_suffix"); if (StringKit.isNotEmpty(countSuffix)) { - this.countSuffix = countSuffix; + this.count_suffix = countSuffix; + } + + // 通过 countMsId 配置自定义类 + String countMsIdGenClass = properties.getProperty("count_ms_id"); + if (StringKit.isNotEmpty(countMsIdGenClass)) { + try { + Class aClass = Class.forName(countMsIdGenClass); + this.count_ms_id = (CountMsId) aClass.getConstructor().newInstance(); + if (count_ms_id instanceof Property) { + ((Property) count_ms_id).setProperties(properties); + } + } catch (Exception e) { + throw new PageException(e); + } } } @@ -161,22 +177,22 @@ private void checkDialectExists() { private Long count(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { - String countMsId = ms.getId() + countSuffix; + String countMsId = this.count_ms_id.genCountMsId(ms, parameter, boundSql, count_suffix); Long count; // 先判断是否存在手写的 count 查询 MappedStatement countMs = CountExecutor.getExistedMappedStatement(ms.getConfiguration(), countMsId); if (countMs != null) { count = CountExecutor.executeManualCount(executor, countMs, parameter, boundSql, resultHandler); } else { - if (msCountMap != null) { - countMs = msCountMap.get(countMsId); + if (this.ms_count_cache != null) { + countMs = this.ms_count_cache.get(countMsId); } // 自动创建 if (countMs == null) { // 根据当前的 ms 创建一个返回值为 Long 类型的 ms countMs = CountMappedStatement.newCountMappedStatement(ms, countMsId); - if (msCountMap != null) { - msCountMap.put(countMsId, countMs); + if (this.ms_count_cache != null) { + this.ms_count_cache.put(countMsId, countMs); } } count = CountExecutor.executeAutoCount(this.dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler); diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/proxy/CountMappedStatement.java b/bus-pager/src/main/java/org/aoju/bus/pager/proxy/CountMappedStatement.java index 7233721395..4d894f868e 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/proxy/CountMappedStatement.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/proxy/CountMappedStatement.java @@ -41,7 +41,6 @@ */ public class CountMappedStatement { - public static final String COUNT = "_COUNT"; private static final List EMPTY_RESULTMAPPING = new ArrayList<>(0); /** @@ -80,14 +79,4 @@ public static MappedStatement newCountMappedStatement(MappedStatement ms, String return builder.build(); } - /** - * 新建count查询的MappedStatement - * - * @param ms MappedStatement - * @return mappedStatement - */ - public static MappedStatement newCountMappedStatement(MappedStatement ms) { - return newCountMappedStatement(ms, ms.getId() + COUNT); - } - } diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/proxy/PageAutoDialect.java b/bus-pager/src/main/java/org/aoju/bus/pager/proxy/PageAutoDialect.java index 15b200cff5..9e419b671b 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/proxy/PageAutoDialect.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/proxy/PageAutoDialect.java @@ -30,6 +30,7 @@ import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.pager.AutoDialect; import org.aoju.bus.pager.Dialect; +import org.aoju.bus.pager.Property; import org.aoju.bus.pager.dialect.AbstractPaging; import org.aoju.bus.pager.dialect.auto.Defalut; import org.aoju.bus.pager.dialect.auto.Druid; @@ -172,7 +173,7 @@ public static AbstractPaging instanceDialect(String dialectClass, Properties pro if (AbstractPaging.class.isAssignableFrom(sqlDialectClass)) { dialect = (AbstractPaging) sqlDialectClass.getConstructor().newInstance(); } else { - throw new PageException("使用 PageContext 时,方言必须是实现 " + AbstractPaging.class.getCanonicalName() + " 接口的实现类!"); + throw new PageException("使用 PageContext 时,方言必须是实现 " + AbstractPaging.class.getName() + " 接口的实现类!"); } } catch (Exception e) { throw new PageException("初始化 [" + dialectClass + "]时出错:" + e.getMessage(), e); @@ -268,6 +269,9 @@ private void initAutoDialectClass(Properties properties) { autoDialectClass = (Class) Class.forName(autoDialectClassStr); } this.autoDialectDelegate = autoDialectClass.getConstructor().newInstance(); + if (this.autoDialectDelegate instanceof Property) { + ((Property) this.autoDialectDelegate).setProperties(properties); + } } catch (ClassNotFoundException e) { throw new IllegalArgumentException("请确保 autoDialectClass 配置的 AutoDialect 实现类(" + autoDialectClassStr + ")存在!", e); } catch (Exception e) { diff --git a/bus-pager/src/main/java/org/aoju/bus/pager/proxy/PageParams.java b/bus-pager/src/main/java/org/aoju/bus/pager/proxy/PageParams.java index a5351e43d3..affcea3793 100644 --- a/bus-pager/src/main/java/org/aoju/bus/pager/proxy/PageParams.java +++ b/bus-pager/src/main/java/org/aoju/bus/pager/proxy/PageParams.java @@ -66,6 +66,14 @@ public class PageParams { * 默认count(0) */ protected String countColumn = Symbol.ZERO; + /** + * 转换count查询时保留 order by 排序 + */ + private boolean keepOrderBy = false; + /** + * 转换count查询时保留子查询的 order by 排序 + */ + private boolean keepSubSelectOrderBy = false; /** * 获取分页参数 @@ -109,6 +117,12 @@ public Page getPage(Object parameterObject, org.apache.ibatis.session.RowBounds if (page.getPageSizeZero() == null) { page.setPageSizeZero(pageSizeZero); } + if (page.getKeepOrderBy() == null) { + page.setKeepOrderBy(keepOrderBy); + } + if (page.getKeepSubSelectOrderBy() == null) { + page.setKeepSubSelectOrderBy(keepSubSelectOrderBy); + } return page; } @@ -134,6 +148,10 @@ public void setProperties(Properties properties) { } // 当offsetAsPageNo=false的时候,不能参数映射 PageObject.setParams(properties.getProperty("params")); + // count查询时,是否保留查询中的 order by + keepOrderBy = Boolean.parseBoolean(properties.getProperty("keepOrderBy")); + // count查询时,是否保留子查询中的 order by + keepSubSelectOrderBy = Boolean.parseBoolean(properties.getProperty("keepSubSelectOrderBy")); } public boolean isOffsetAsPageNo() { From b62b945f522839f4296d8dfbfb6f0c9f69e21efb Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Mon, 24 Oct 2022 09:33:46 +0800 Subject: [PATCH 05/19] update of to create --- .../java/org/aoju/bus/cache/provider/ZookeeperHitting.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bus-cache/src/main/java/org/aoju/bus/cache/provider/ZookeeperHitting.java b/bus-cache/src/main/java/org/aoju/bus/cache/provider/ZookeeperHitting.java index ab7d1eec69..9d77c22fdb 100755 --- a/bus-cache/src/main/java/org/aoju/bus/cache/provider/ZookeeperHitting.java +++ b/bus-cache/src/main/java/org/aoju/bus/cache/provider/ZookeeperHitting.java @@ -91,8 +91,8 @@ public ZookeeperHitting(String zkServer, String productName) { this.hitPathPrefix = String.format("%s%s", uniqueProductName, "hit"); this.requirePathPrefix = String.format("%s%s", uniqueProductName, "require"); try { - client.of().creatingParentsIfNeeded().forPath(hitPathPrefix); - client.of().creatingParentsIfNeeded().forPath(requirePathPrefix); + client.create().creatingParentsIfNeeded().forPath(hitPathPrefix); + client.create().creatingParentsIfNeeded().forPath(requirePathPrefix); } catch (KeeperException.NodeExistsException ignored) { } catch (Exception e) { throw new RuntimeException("create path: " + hitPathPrefix + ", " + requirePathPrefix + " on namespace: " + NAME_SPACE + " error", e); From 593435faed62b9b819bf9b882965a94d66a8ad2e Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Mon, 24 Oct 2022 15:36:22 +0800 Subject: [PATCH 06/19] update socket --- .../aoju/bus/core/io/buffer/ByteBuffer.java | 97 ------- bus-socket/README.md | 32 +-- .../org/aoju/bus/socket/AioQuickClient.java | 58 ++-- .../org/aoju/bus/socket/AioQuickServer.java | 142 +++++----- .../java/org/aoju/bus/socket/AioSession.java | 29 +- .../java/org/aoju/bus/socket/NetMonitor.java | 7 +- .../org/aoju/bus/socket/NioQuickClient.java | 187 ------------- .../org/aoju/bus/socket/NioQuickServer.java | 179 ------------ .../java/org/aoju/bus/socket/Protocol.java | 10 +- .../org/aoju/bus/socket/ServerConfig.java | 18 +- .../org/aoju/bus/socket/SocketStatus.java | 8 +- .../org/aoju/bus/socket/TcpAioSession.java | 100 +++---- .../org/aoju/bus/socket/UdpAioSession.java | 24 +- .../org/aoju/bus/socket/UdpBootstrap.java | 257 +++++------------- .../java/org/aoju/bus/socket/UdpChannel.java | 155 +++++------ .../org/aoju/bus/socket/UdpDispatcher.java | 100 ------- .../org/aoju/bus/socket/WorkerRegister.java | 197 +++++++++++++- .../aoju/bus/socket/buffers/BufferArray.java | 58 ++++ .../socket/{ => buffers}/BufferFactory.java | 16 +- .../aoju/bus/socket/buffers/BufferPage.java | 34 +-- .../aoju/bus/socket/buffers/BufferPool.java | 136 +++++++++ .../BufferThread.java} | 31 ++- .../bus/socket/buffers}/VirtualBuffer.java | 35 +-- .../bus/socket/buffers/VirtualFactory.java | 36 +++ .../aoju/bus/socket/buffers}/WriteBuffer.java | 62 +++-- .../AsynchronousSocketChannelProxy.java | 12 +- ...a => EnhanceAsynchronousChannelGroup.java} | 45 ++- ...> EnhanceAsynchronousChannelProvider.java} | 52 +++- ...hanceAsynchronousServerSocketChannel.java} | 62 +++-- ... => EnhanceAsynchronousSocketChannel.java} | 144 +++++----- .../SslAsynchronousSocketChannel.java} | 27 +- .../socket/convert/DelimiterFrameDecoder.java | 13 +- ...oder.java => FixedLengthFrameDecoder.java} | 8 +- .../socket/{ => convert}/SocketDecoder.java | 8 +- .../handler/CompletionAcceptHandler.java | 83 ------ ...a => ConcurrentReadCompletionHandler.java} | 12 +- .../handler/FutureCompletionHandler.java | 8 +- ...andler.java => ReadCompletionHandler.java} | 20 +- ...ndler.java => WriteCompletionHandler.java} | 9 +- .../bus/socket/plugins/BlackListPlugin.java | 4 +- ...ugin.java => BufferPageMonitorPlugin.java} | 32 +-- .../aoju/bus/socket/plugins/HeartPlugin.java | 21 +- .../bus/socket/plugins/MonitorPlugin.java | 20 +- .../bus/socket/plugins/RateLimiterPlugin.java | 4 +- .../bus/socket/plugins/ReconnectPlugin.java | 2 +- .../socket/plugins/SocketOptionPlugin.java | 2 +- .../aoju/bus/socket/plugins/SslPlugin.java | 67 +++-- ...sor.java => AbstractMessageProcessor.java} | 12 +- .../socket/process/GroupMessageProcessor.java | 2 +- .../socket/protocol/ByteArrayProtocol.java | 41 +++ .../protocol/FixedLengthBytesProtocol.java | 56 ++++ .../bus/socket/protocol/StringProtocol.java | 113 ++++++++ .../socket/security/HandshakeCallback.java | 2 +- .../bus/socket/security/HandshakeModel.java | 2 +- .../aoju/bus/socket/security/SslService.java | 107 ++------ .../factory/ClientSSLContextFactory.java | 84 ++++++ .../security/factory/SSLContextFactory.java | 38 +++ .../factory/ServerSSLContextFactory.java | 60 ++++ 58 files changed, 1584 insertions(+), 1596 deletions(-) delete mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/NioQuickClient.java delete mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/NioQuickServer.java delete mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/UdpDispatcher.java create mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferArray.java rename bus-socket/src/main/java/org/aoju/bus/socket/{ => buffers}/BufferFactory.java (86%) rename bus-core/src/main/java/org/aoju/bus/core/io/buffer/PageBuffer.java => bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferPage.java (89%) mode change 100755 => 100644 create mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferPool.java rename bus-socket/src/main/java/org/aoju/bus/socket/{handler/ChannelSocketHandler.java => buffers/BufferThread.java} (79%) rename {bus-core/src/main/java/org/aoju/bus/core/io/buffer => bus-socket/src/main/java/org/aoju/bus/socket/buffers}/VirtualBuffer.java (83%) mode change 100755 => 100644 create mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/buffers/VirtualFactory.java rename {bus-core/src/main/java/org/aoju/bus/core/io/buffer => bus-socket/src/main/java/org/aoju/bus/socket/buffers}/WriteBuffer.java (89%) rename bus-socket/src/main/java/org/aoju/bus/socket/channel/{AsynchronousChannelGroup.java => EnhanceAsynchronousChannelGroup.java} (86%) rename bus-socket/src/main/java/org/aoju/bus/socket/channel/{AsynchronousChannelProvider.java => EnhanceAsynchronousChannelProvider.java} (56%) rename bus-socket/src/main/java/org/aoju/bus/socket/channel/{AsynchronousServerSocketChannel.java => EnhanceAsynchronousServerSocketChannel.java} (65%) rename bus-socket/src/main/java/org/aoju/bus/socket/channel/{AsynchronousSocketChannel.java => EnhanceAsynchronousSocketChannel.java} (81%) rename bus-socket/src/main/java/org/aoju/bus/socket/{security/SslSocketChannel.java => channel/SslAsynchronousSocketChannel.java} (93%) rename bus-socket/src/main/java/org/aoju/bus/socket/convert/{FixedLengthDecoder.java => FixedLengthFrameDecoder.java} (94%) rename bus-socket/src/main/java/org/aoju/bus/socket/{ => convert}/SocketDecoder.java (94%) delete mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionAcceptHandler.java rename bus-socket/src/main/java/org/aoju/bus/socket/handler/{ConcurrentReadHandler.java => ConcurrentReadCompletionHandler.java} (87%) rename bus-socket/src/main/java/org/aoju/bus/socket/handler/{CompletionReadHandler.java => ReadCompletionHandler.java} (83%) rename bus-socket/src/main/java/org/aoju/bus/socket/handler/{CompletionWriteHandler.java => WriteCompletionHandler.java} (93%) rename bus-socket/src/main/java/org/aoju/bus/socket/plugins/{PageBufferPlugin.java => BufferPageMonitorPlugin.java} (82%) rename bus-socket/src/main/java/org/aoju/bus/socket/process/{AbstractProcessor.java => AbstractMessageProcessor.java} (94%) create mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/protocol/ByteArrayProtocol.java create mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/protocol/FixedLengthBytesProtocol.java create mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/protocol/StringProtocol.java create mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/security/factory/ClientSSLContextFactory.java create mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/security/factory/SSLContextFactory.java create mode 100644 bus-socket/src/main/java/org/aoju/bus/socket/security/factory/ServerSSLContextFactory.java diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java index b0355da65f..8cf70e3ed5 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java @@ -28,16 +28,11 @@ import org.aoju.bus.core.io.ByteString; import org.aoju.bus.core.io.Segment; import org.aoju.bus.core.toolkit.IoKit; -import org.aoju.bus.core.toolkit.ThreadKit; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Arrays; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; /** * 由字节数组段组成的不可变字节字符串 该类的存在是为了实现 @@ -49,25 +44,8 @@ */ public class ByteBuffer extends ByteString { - /** - * 守护线程在空闲时期回收内存资源 - */ - private static final ScheduledThreadPoolExecutor BUFFER_POOL_CLEAN = new ScheduledThreadPoolExecutor(1, r -> { - Thread thread = new Thread(r, "BufferPoolClean"); - thread.setDaemon(true); - return thread; - }); - /** - * 内存页游标 - */ - private final AtomicInteger cursor = new AtomicInteger(0); private transient byte[][] segments; private transient int[] directory; - /** - * 内存页组 - */ - private PageBuffer[] pageBuffers; - private boolean enabled = true; public ByteBuffer(Buffer buffer, int byteCount) { super(null); @@ -101,21 +79,6 @@ public ByteBuffer(Buffer buffer, int byteCount) { } } - /** - * @param pageSize 内存页大小 - * @param pageNo 内存页个数 - * @param isDirect 是否使用直接缓冲区 - */ - public ByteBuffer(final int pageSize, final int pageNo, final boolean isDirect) { - pageBuffers = new PageBuffer[pageNo]; - for (int i = 0; i < pageNo; i++) { - pageBuffers[i] = new PageBuffer(pageBuffers, pageSize, isDirect); - } - if (pageNo == 0 || pageSize == 0) { - future.cancel(false); - } - } - @Override public String utf8() { return toByteString().utf8(); @@ -151,28 +114,6 @@ public ByteString md5() { return toByteString().md5(); } - /** - * 内存回收任务 - */ - private final ScheduledFuture future = BUFFER_POOL_CLEAN.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - if (enabled) { - for (PageBuffer pageBuffer : pageBuffers) { - pageBuffer.tryClean(); - } - } else { - if (null != pageBuffers) { - for (PageBuffer page : pageBuffers) { - page.release(); - } - pageBuffers = null; - } - future.cancel(false); - } - } - }, 500, 1000, TimeUnit.MILLISECONDS); - @Override public ByteString sha1() { return toByteString().sha1(); @@ -372,42 +313,4 @@ private Object writeReplace() { return toByteString(); } - /** - * 申请FastBufferThread的线程对象,配合线程池申请会有更好的性能表现 - * - * @param target Runnable - * @param name 线程名 - * @return FastBufferThread线程对象 - */ - public Thread newThread(Runnable target, String name) { - assertEnabled(); - ThreadKit.FastBufferThread thread = new ThreadKit.FastBufferThread(target, name); - thread.setPageIndex((int) (thread.getId() % pageBuffers.length)); - return thread; - } - - /** - * 申请内存页 - * - * @return 缓存页对象 - */ - public PageBuffer allocatePageBuffer() { - assertEnabled(); - return pageBuffers[(cursor.getAndIncrement() & Integer.MAX_VALUE) % pageBuffers.length]; - } - - private void assertEnabled() { - if (!enabled) { - throw new IllegalStateException("buffer pool is disable"); - } - } - - /** - * 释放回收内存 - */ - public void release() { - enabled = false; - } - - } diff --git a/bus-socket/README.md b/bus-socket/README.md index e3d3bd0dd4..af4b9f68aa 100755 --- a/bus-socket/README.md +++ b/bus-socket/README.md @@ -143,27 +143,27 @@ public class AioClient { aioQuickClient.shutdownNow(); } - static class ClientProcessor implements MessageProcessor { + static class ClientProcessor implements MessageProcessor { - @Override - public void process(AioSession session, String msg) { - System.out.println("Receive data from server:" + msg); - } + @Override + public void process(AioSession session, String msg) { + System.out.println("Receive data from server:" + msg); + } - @Override - public void stateEvent(AioSession session, StateMachineEnum stateMachineEnum, Throwable throwable) { - System.out.println("State:" + stateMachineEnum); - if (stateMachineEnum == StateMachineEnum.OUTPUT_EXCEPTION) { - throwable.printStackTrace(); - } - } + @Override + public void stateEvent(AioSession session, StateMachineEnum socketStatus, Throwable throwable) { + System.out.println("State:" + socketStatus); + if (socketStatus == StateMachineEnum.OUTPUT_EXCEPTION) { + throwable.printStackTrace(); + } } + } - static class ClientProtocol implements Protocol { + static class ClientProtocol implements Protocol { - @Override - public String decode(ByteBuffer data, AioSession session) { - int remaining = data.remaining(); + @Override + public String decode(ByteBuffer data, AioSession session) { + int remaining = data.remaining(); if (remaining < 4) { return null; } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/AioQuickClient.java b/bus-socket/src/main/java/org/aoju/bus/socket/AioQuickClient.java index e205d2d406..619900b736 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/AioQuickClient.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/AioQuickClient.java @@ -25,10 +25,12 @@ ********************************************************************************/ package org.aoju.bus.socket; -import org.aoju.bus.core.io.buffer.ByteBuffer; import org.aoju.bus.core.toolkit.IoKit; -import org.aoju.bus.socket.handler.CompletionReadHandler; -import org.aoju.bus.socket.handler.CompletionWriteHandler; +import org.aoju.bus.socket.buffers.BufferFactory; +import org.aoju.bus.socket.buffers.BufferPool; +import org.aoju.bus.socket.buffers.VirtualFactory; +import org.aoju.bus.socket.handler.ReadCompletionHandler; +import org.aoju.bus.socket.handler.WriteCompletionHandler; import org.aoju.bus.socket.process.MessageProcessor; import java.io.IOException; @@ -46,9 +48,9 @@ * AIO实现的客户端服务 * * @author Kimi Liu - * @version V1.0.0 + * @since Java 17+ */ -public class AioQuickClient { +public final class AioQuickClient { /** * 客户端服务配置。 @@ -57,19 +59,17 @@ public class AioQuickClient { private final ServerConfig config = new ServerConfig(); /** * 网络连接的会话对象 - * - * @see TcpAioSession */ private TcpAioSession session; /** * 内存池 */ - private ByteBuffer bufferPool = null; + private BufferPool bufferPool = null; - private ByteBuffer innerBufferPool = null; + private BufferPool innerBufferPool = null; /** - * IO事件处理线程组 - * 作为客户端,该AsynchronousChannelGroup只需保证2个长度的线程池大小即可满足通信读写所需 + * IO事件处理线程组。 + * 作为客户端,该AsynchronousChannelGroup只需保证2个长度的线程池大小即可满足通信读写所需。 */ private AsynchronousChannelGroup asynchronousChannelGroup; @@ -83,12 +83,11 @@ public class AioQuickClient { */ private int connectTimeout; - private BufferFactory.VirtualBufferFactory readBufferFactory = bufferPage -> bufferPage.allocate(config.getReadBufferSize()); + private VirtualFactory readBufferFactory = bufferPage -> bufferPage.allocate(config.getReadBufferSize()); /** * 当前构造方法设置了启动Aio客户端的必要参数,基本实现开箱即用。 * - * @param 对象 * @param host 远程服务器地址 * @param port 远程服务器端口号 * @param protocol 协议编解码 @@ -107,7 +106,7 @@ public AioQuickClient(String host, int port, Protocol protocol, MessagePr * @param attachment 可传入回调方法中的附件对象 * @param handler 异步回调 * @param 附件对象类型 - * @throws IOException 异常 + * @throws IOException */ public void start(A attachment, CompletionHandler handler) throws IOException { @@ -122,7 +121,7 @@ public void start(A attachment, * @param attachment 可传入回调方法中的附件对象 * @param handler 异步回调 * @param 附件对象类型 - * @throws IOException 异常 + * @throws IOException */ public void start(AsynchronousChannelGroup asynchronousChannelGroup, A attachment, CompletionHandler handler) throws IOException { @@ -153,8 +152,7 @@ public void completed(Void result, AsynchronousSocketChannel socketChannel) { throw new RuntimeException("NetMonitor refuse channel"); } //连接成功则构造AIOSession对象 - session = new TcpAioSession(connectedChannel, config, new CompletionReadHandler(), new CompletionWriteHandler(), bufferPool.allocatePageBuffer()); - session.initSession(readBufferFactory.newBuffer(bufferPool.allocatePageBuffer())); + session = new TcpAioSession(connectedChannel, config, new ReadCompletionHandler(), new WriteCompletionHandler(), bufferPool.allocateBufferPage(), () -> readBufferFactory.newBuffer(bufferPool.allocateBufferPage())); handler.completed(session, attachment); } catch (Exception e) { failed(e, socketChannel); @@ -178,9 +176,9 @@ public void failed(Throwable exc, AsynchronousSocketChannel socketChannel) { } /** - * 启动客户端 - * 在与服务端建立连接期间,该方法处于阻塞状态。直至连接建立成功,或者发生异常 - * 该start方法支持外部指定AsynchronousChannelGroup,实现多个客户端共享一组线程池资源,有效提升资源利用率 + * 启动客户端。 + * 在与服务端建立连接期间,该方法处于阻塞状态。直至连接建立成功,或者发生异常。 + * 该start方法支持外部指定AsynchronousChannelGroup,实现多个客户端共享一组线程池资源,有效提升资源利用率。 * * @param asynchronousChannelGroup IO事件处理线程组 * @return 建立连接后的会话对象 @@ -222,8 +220,8 @@ public TcpAioSession getSession() { } /** - * 启动客户端 - * 本方法会构建线程数为2的{@code asynchronousChannelGroup},并通过调用{@link AioQuickClient#start(AsynchronousChannelGroup)}启动服务 + * 启动客户端。 + * 本方法会构建线程数为2的{@code asynchronousChannelGroup},并通过调用{@link AioQuickClient#start(AsynchronousChannelGroup)}启动服务。 * * @return 建立连接后的会话对象 * @throws IOException IOException @@ -235,8 +233,8 @@ public final AioSession start() throws IOException { } /** - * 停止客户端服务 - * 调用该方法会触发AioSession的close方法,并且如果当前客户端若是通过执行{@link AioQuickClient#start()}方法构建的,同时会触发asynchronousChannelGroup的shutdown动作 + * 停止客户端服务. + * 调用该方法会触发AioSession的close方法,并且如果当前客户端若是通过执行{@link AioQuickClient#start()}方法构建的,同时会触发asynchronousChannelGroup的shutdown动作。 */ public final void shutdown() { shutdown0(false); @@ -259,12 +257,15 @@ private void shutdown0(boolean flag) { session.close(flag); session = null; } - // 仅Client内部创建的ChannelGroup需要shutdown + //仅Client内部创建的ChannelGroup需要shutdown if (asynchronousChannelGroup != null) { asynchronousChannelGroup.shutdown(); + asynchronousChannelGroup = null; } if (innerBufferPool != null) { innerBufferPool.release(); + innerBufferPool = null; + bufferPool = null; } } @@ -281,14 +282,12 @@ public final AioQuickClient setReadBufferSize(int size) { /** * 设置Socket的TCP参数配置 - *

* AIO客户端的有效可选范围为: * 1. StandardSocketOptions.SO_SNDBUF * 2. StandardSocketOptions.SO_RCVBUF * 3. StandardSocketOptions.SO_KEEPALIVE * 4. StandardSocketOptions.SO_REUSEADDR * 5. StandardSocketOptions.TCP_NODELAY - *

* * @param socketOption 配置项 * @param value 配置值 @@ -321,7 +320,7 @@ public final AioQuickClient bindLocal(String local, int port) { * @param bufferPool 内存池对象 * @return 当前客户端实例 */ - public final AioQuickClient setBufferPagePool(ByteBuffer bufferPool) { + public final AioQuickClient setBufferPagePool(BufferPool bufferPool) { this.bufferPool = bufferPool; this.config.setBufferFactory(BufferFactory.DISABLED_BUFFER_FACTORY); return this; @@ -366,9 +365,8 @@ public final AioQuickClient connectTimeout(int timeout) { return this; } - public final AioQuickClient setReadBufferFactory(BufferFactory.VirtualBufferFactory readBufferFactory) { + public final AioQuickClient setReadBufferFactory(VirtualFactory readBufferFactory) { this.readBufferFactory = readBufferFactory; return this; } - } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/AioQuickServer.java b/bus-socket/src/main/java/org/aoju/bus/socket/AioQuickServer.java index fa8d2fd392..2f890f51e9 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/AioQuickServer.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/AioQuickServer.java @@ -25,12 +25,16 @@ ********************************************************************************/ package org.aoju.bus.socket; -import org.aoju.bus.core.io.buffer.ByteBuffer; import org.aoju.bus.core.toolkit.IoKit; import org.aoju.bus.logger.Logger; -import org.aoju.bus.socket.handler.CompletionReadHandler; -import org.aoju.bus.socket.handler.CompletionWriteHandler; -import org.aoju.bus.socket.handler.ConcurrentReadHandler; +import org.aoju.bus.socket.buffers.BufferFactory; +import org.aoju.bus.socket.buffers.BufferPool; +import org.aoju.bus.socket.buffers.VirtualBuffer; +import org.aoju.bus.socket.buffers.VirtualFactory; +import org.aoju.bus.socket.channel.EnhanceAsynchronousChannelProvider; +import org.aoju.bus.socket.handler.ConcurrentReadCompletionHandler; +import org.aoju.bus.socket.handler.ReadCompletionHandler; +import org.aoju.bus.socket.handler.WriteCompletionHandler; import org.aoju.bus.socket.process.MessageProcessor; import java.io.IOException; @@ -45,33 +49,29 @@ import java.security.InvalidParameterException; import java.util.Map; import java.util.concurrent.*; -import java.util.function.Function; +import java.util.function.Supplier; /** * AIO服务端 * - * @param 消息对象类型 * @author Kimi Liu * @since Java 17+ */ -public class AioQuickServer { - - private static final String BUS_ASYNCHRONOUS_CHANNEL_PROVIDER = "org.aoju.bus.socket.channel.AsynchronousChannelProvider"; - private static final String AIO_ASYNCHRONOUS_CHANNEL_PROVIDER = "java.nio.channels.spi.AsynchronousChannelProvider"; +public final class AioQuickServer { /** - * Server端服务配置。 - * 调用AioQuickServer的各setXX()方法,都是为了设置config的各配置项 + * Server端服务配置 + *

调用AioQuickServer的各setXX()方法,都是为了设置config的各配置项

*/ private final ServerConfig config = new ServerConfig(); /** * 内存池 */ - private ByteBuffer bufferPool; + private BufferPool bufferPool; /** * 读回调事件处理 */ - private CompletionReadHandler aioCompletionReadHandler; + private ReadCompletionHandler aioReadCompletionHandler; /** * ConcurrentReadCompletionHandler 回调守护线程 */ @@ -79,13 +79,9 @@ public class AioQuickServer { /** * 写回调事件处理 */ - private CompletionWriteHandler aioCompletionWriteHandler; - private ByteBuffer innerBufferPool = null; + private WriteCompletionHandler aioWriteCompletionHandler; + private BufferPool innerBufferPool = null; - /** - * 连接会话实例化Function - */ - private Function> aioSessionFunction; /** * asynchronousServerSocketChannel */ @@ -95,7 +91,7 @@ public class AioQuickServer { */ private AsynchronousChannelGroup asynchronousChannelGroup; - private BufferFactory.VirtualBufferFactory readBufferFactory = bufferPage -> bufferPage.allocate(config.getReadBufferSize()); + private VirtualFactory readBufferFactory = bufferPage -> bufferPage.allocate(config.getReadBufferSize()); /** * 设置服务端启动必要参数配置 @@ -104,7 +100,7 @@ public class AioQuickServer { * @param protocol 协议编解码 * @param messageProcessor 消息处理器 */ - public AioQuickServer(int port, Protocol protocol, MessageProcessor messageProcessor) { + public AioQuickServer(int port, Protocol protocol, MessageProcessor messageProcessor) { config.setPort(port); config.setProtocol(protocol); config.setProcessor(messageProcessor); @@ -117,7 +113,7 @@ public AioQuickServer(int port, Protocol protocol, MessageProcessor messag * @param protocol 协议编解码 * @param messageProcessor 消息处理器 */ - public AioQuickServer(String host, int port, Protocol protocol, MessageProcessor messageProcessor) { + public AioQuickServer(String host, int port, Protocol protocol, MessageProcessor messageProcessor) { this(port, protocol, messageProcessor); config.setHost(host); } @@ -128,32 +124,30 @@ public AioQuickServer(String host, int port, Protocol protocol, MessageProces * @throws IOException IO异常 */ public void start() throws IOException { - start0(channel -> new TcpAioSession<>(channel, config, aioCompletionReadHandler, aioCompletionWriteHandler, bufferPool.allocatePageBuffer())); + start0(); } /** * 内部启动逻辑 * - * @param aioSessionFunction 实例化会话的Function * @throws IOException IO异常 */ - private void start0(Function> aioSessionFunction) throws IOException { + private void start0() throws IOException { checkAndResetConfig(); try { - aioCompletionWriteHandler = new CompletionWriteHandler<>(); - if (null == bufferPool) { + aioWriteCompletionHandler = new WriteCompletionHandler(); + if (bufferPool == null) { this.bufferPool = config.getBufferFactory().create(); this.innerBufferPool = bufferPool; } - this.aioSessionFunction = aioSessionFunction; AsynchronousChannelProvider provider; if (config.isAioEnhance()) { - aioCompletionReadHandler = new CompletionReadHandler(); - provider = new org.aoju.bus.socket.channel.AsynchronousChannelProvider(); + aioReadCompletionHandler = new ReadCompletionHandler(); + provider = new EnhanceAsynchronousChannelProvider(config.isLowMemory()); } else { concurrentReadCompletionHandlerExecutor = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); - aioCompletionReadHandler = new ConcurrentReadHandler(new Semaphore(config.getThreadNum() - 1), concurrentReadCompletionHandlerExecutor); + aioReadCompletionHandler = new ConcurrentReadCompletionHandler(new Semaphore(config.getThreadNum() - 1), concurrentReadCompletionHandlerExecutor); provider = AsynchronousChannelProvider.provider(); } asynchronousChannelGroup = provider.openAsynchronousChannelGroup(config.getThreadNum(), new ThreadFactory() { @@ -165,14 +159,14 @@ public Thread newThread(Runnable r) { } }); this.serverSocketChannel = AsynchronousServerSocketChannel.open(asynchronousChannelGroup); - // 设置socket属性 - if (null != config.getSocketOptions()) { + //set socket options + if (config.getSocketOptions() != null) { for (Map.Entry, Object> entry : config.getSocketOptions().entrySet()) { this.serverSocketChannel.setOption(entry.getKey(), entry.getValue()); } } - // 绑定地址 - if (null != config.getHost()) { + //bind host + if (config.getHost() != null) { serverSocketChannel.bind(new InetSocketAddress(config.getHost(), config.getPort()), config.getBacklog()); } else { serverSocketChannel.bind(new InetSocketAddress(config.getPort()), config.getBacklog()); @@ -183,11 +177,12 @@ public Thread newThread(Runnable r) { shutdown(); throw e; } - Logger.info("bus-socket server started on port " + config.getPort() + ",threadNum:" + config.getThreadNum()); - Logger.info("bus-socket server config is " + config); + Logger.debug("socket server started on port " + config.getPort() + ",threadNum:" + config.getThreadNum()); + Logger.debug("socket server config is " + config); } private void startAcceptThread() { + Supplier supplier = () -> readBufferFactory.newBuffer(bufferPool.allocateBufferPage()); serverSocketChannel.accept(null, new CompletionHandler() { @Override public void completed(AsynchronousSocketChannel channel, Void attachment) { @@ -198,7 +193,7 @@ public void completed(AsynchronousSocketChannel channel, Void attachment) { failed(throwable, attachment); serverSocketChannel.accept(attachment, this); } finally { - createSession(channel); + createSession(channel, supplier); } } @@ -213,7 +208,7 @@ public void failed(Throwable exc, Void attachment) { * 检查配置项 */ private void checkAndResetConfig() { - // 确保单核CPU默认初始化至少2个线程 + //确保单核CPU默认初始化至少2个线程 if (config.getThreadNum() == 1) { config.setThreadNum(2); } @@ -224,25 +219,24 @@ private void checkAndResetConfig() { * * @param channel 当前已建立连接通道 */ - private void createSession(AsynchronousSocketChannel channel) { + private void createSession(AsynchronousSocketChannel channel, Supplier supplier) { // 连接成功则构造AIOSession对象 - TcpAioSession session = null; + TcpAioSession session = null; AsynchronousSocketChannel acceptChannel = channel; try { - if (null != config.getMonitor()) { + if (config.getMonitor() != null) { acceptChannel = config.getMonitor().shouldAccept(channel); } - if (null != acceptChannel) { + if (acceptChannel != null) { acceptChannel.setOption(StandardSocketOptions.TCP_NODELAY, true); - session = aioSessionFunction.apply(acceptChannel); - session.initSession(readBufferFactory.newBuffer(bufferPool.allocatePageBuffer())); + session = new TcpAioSession(acceptChannel, config, aioReadCompletionHandler, aioWriteCompletionHandler, bufferPool.allocateBufferPage(), supplier); } else { config.getProcessor().stateEvent(null, SocketStatus.REJECT_ACCEPT, null); IoKit.close(channel); } } catch (Exception e) { e.printStackTrace(); - if (null == session) { + if (session == null) { IoKit.close(channel); } else { session.close(); @@ -253,9 +247,9 @@ private void createSession(AsynchronousSocketChannel channel) { /** * 停止服务端 */ - public final void shutdown() { + public void shutdown() { try { - if (null != serverSocketChannel) { + if (serverSocketChannel != null) { serverSocketChannel.close(); serverSocketChannel = null; } @@ -275,7 +269,7 @@ public final void shutdown() { } catch (InterruptedException e) { e.printStackTrace(); } - if (null != innerBufferPool) { + if (innerBufferPool != null) { innerBufferPool.release(); } if (concurrentReadCompletionHandlerExecutor != null) { @@ -290,23 +284,35 @@ public final void shutdown() { * @param size 单位:byte * @return 当前AioQuickServer对象 */ - public final AioQuickServer setReadBufferSize(int size) { + public AioQuickServer setReadBufferSize(int size) { this.config.setReadBufferSize(size); return this; } + /** + * 是否启用 AIO 增强模式默认:true + * + * @param enabled true:启用;false:禁用 + */ + public AioQuickServer setAioEnhance(boolean enabled) { + config.setAioEnhance(enabled); + return this; + } + /** * 设置Socket的TCP参数配置 - * AIO客户端的有效可选范围为: - * 2. StandardSocketOptions.SO_RCVBUF - * 4. StandardSocketOptions.SO_REUSEADDR + *

+ * AIO客户端的有效可选范围为:
+ * 2. StandardSocketOptions.SO_RCVBUF
+ * 4. StandardSocketOptions.SO_REUSEADDR
+ *

* * @param socketOption 配置项 * @param value 配置值 * @param 配置项类型 * @return 当前AioQuickServer对象 */ - public final AioQuickServer setOption(SocketOption socketOption, V value) { + public AioQuickServer setOption(SocketOption socketOption, V value) { config.setOption(socketOption, value); return this; } @@ -317,7 +323,7 @@ public final AioQuickServer setOption(SocketOption socketOption, V val * @param threadNum 线程数 * @return 当前AioQuickServer对象 */ - public final AioQuickServer setThreadNum(int threadNum) { + public AioQuickServer setThreadNum(int threadNum) { if (threadNum <= 1) { throw new InvalidParameterException("threadNum must >= 2"); } @@ -325,7 +331,6 @@ public final AioQuickServer setThreadNum(int threadNum) { return this; } - /** * 设置输出缓冲区容量 * @@ -333,7 +338,7 @@ public final AioQuickServer setThreadNum(int threadNum) { * @param bufferCapacity 内存块数量上限 * @return 当前AioQuickServer对象 */ - public final AioQuickServer setWriteBuffer(int bufferSize, int bufferCapacity) { + public AioQuickServer setWriteBuffer(int bufferSize, int bufferCapacity) { config.setWriteBufferSize(bufferSize); config.setWriteBufferCapacity(bufferCapacity); return this; @@ -345,28 +350,28 @@ public final AioQuickServer setWriteBuffer(int bufferSize, int bufferCapacity * @param backlog backlog大小 * @return 当前AioQuickServer对象 */ - public final AioQuickServer setBacklog(int backlog) { + public AioQuickServer setBacklog(int backlog) { config.setBacklog(backlog); return this; } /** - * 设置内存池。 - * 通过该方法设置的内存池,在AioQuickServer执行shutdown时不会触发内存池的释放。 - * 该方法适用于多个AioQuickServer、AioQuickClient共享内存池的场景。 + * 设置内存池 + * 通过该方法设置的内存池,在AioQuickServer执行shutdown时不会触发内存池的释放 + * 该方法适用于多个AioQuickServer、AioQuickClient共享内存池的场景 * 在启用内存池的情况下会有更好的性能表现 * * @param bufferPool 内存池对象 * @return 当前AioQuickServer对象 */ - public final AioQuickServer setPageBufferPool(ByteBuffer bufferPool) { + public AioQuickServer setBufferPagePool(BufferPool bufferPool) { this.bufferPool = bufferPool; this.config.setBufferFactory(BufferFactory.DISABLED_BUFFER_FACTORY); return this; } /** - * 设置内存池的构造工厂。 + * 设置内存池的构造工厂 * 通过工厂形式生成的内存池会强绑定到当前AioQuickServer对象, * 在AioQuickServer执行shutdown时会释放内存池 * 在启用内存池的情况下会有更好的性能表现 @@ -374,15 +379,20 @@ public final AioQuickServer setPageBufferPool(ByteBuffer bufferPool) { * @param bufferFactory 内存池工厂 * @return 当前AioQuickServer对象 */ - public final AioQuickServer setBufferFactory(BufferFactory bufferFactory) { + public AioQuickServer setBufferFactory(BufferFactory bufferFactory) { this.config.setBufferFactory(bufferFactory); this.bufferPool = null; return this; } - public final AioQuickServer setReadBufferFactory(BufferFactory.VirtualBufferFactory readBufferFactory) { + public AioQuickServer setReadBufferFactory(VirtualFactory readBufferFactory) { this.readBufferFactory = readBufferFactory; return this; } + public AioQuickServer setLowMemory(boolean lowMemory) { + this.config.setLowMemory(lowMemory); + return this; + } + } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/AioSession.java b/bus-socket/src/main/java/org/aoju/bus/socket/AioSession.java index aceee40ae2..526fad1fbb 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/AioSession.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/AioSession.java @@ -25,7 +25,7 @@ ********************************************************************************/ package org.aoju.bus.socket; -import org.aoju.bus.core.io.buffer.WriteBuffer; +import org.aoju.bus.socket.buffers.WriteBuffer; import java.io.IOException; import java.io.InputStream; @@ -52,6 +52,7 @@ public abstract class AioSession { */ protected static final byte SESSION_STATUS_ENABLED = 3; + /** * 会话当前状态 * @@ -74,26 +75,17 @@ public abstract class AioSession { /** * 获取读缓冲区对象 - * - * @return the object */ public abstract ByteBuffer readBuffer(); /** - * 强制关闭当前AIOSession - * 若此时还存留待输出的数据,则会导致该部分数据丢失 + * 强制关闭当前AIOSession。 + *

若此时还存留待输出的数据,则会导致该部分数据丢失

*/ public final void close() { close(true); } - /** - * 是否立即关闭会话 - * - * @param immediate true:立即关闭,false:响应消息发送完后关闭 - */ - public abstract void close(boolean immediate); - public abstract void awaitRead(); /** @@ -101,6 +93,13 @@ public final void close() { */ public abstract void signalRead(); + /** + * 是否立即关闭会话 + * + * @param immediate true:立即关闭,false:响应消息发送完后关闭 + */ + public abstract void close(boolean immediate); + /** * 获取当前Session的唯一标识 * @@ -159,12 +158,12 @@ public final
void setAttachment(A attachment) { public abstract InetSocketAddress getRemoteAddress() throws IOException; /** - * 获得数据输入流对象 + * 获得数据输入流对象。 *

- * faster模式下调用该方法会触发UnsupportedOperationException异常 + * faster模式下调用该方法会触发UnsupportedOperationException异常。 *

*

- * MessageProcessor采用异步处理消息的方式时,调用该方法可能会出现异常 + * MessageProcessor采用异步处理消息的方式时,调用该方法可能会出现异常。 *

* * @return 输入流 diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/NetMonitor.java b/bus-socket/src/main/java/org/aoju/bus/socket/NetMonitor.java index ea692e63a7..2a60b1fc1d 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/NetMonitor.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/NetMonitor.java @@ -28,7 +28,7 @@ import java.nio.channels.AsynchronousSocketChannel; /** - * 网络监控器,提供通讯层面监控功能的接口 + * 网络监控器,提供通讯层面监控功能的接口。 *

* bus-socket并未单独提供配置监控服务的接口,用户在使用时仅需在MessageProcessor实现类中同时实现当前NetMonitor接口即可。 * 在注册消息处理器时,若服务监测到该处理器同时实现了NetMonitor接口,则该监视器便会生效。 @@ -44,15 +44,18 @@ *

* 实现本接口时要关注acceptMonitor接口的返回值,如无特殊需求直接返回true,若返回false会拒绝本次连接。 *

- * 非必要情况下请勿使用该接口,未来可能会调整接口设计 + * 非必要情况下请勿使用该接口,未来可能会调整接口设计 * * @author Kimi Liu * @since Java 17+ */ public interface NetMonitor { + /** + *

* 监控已接收到的连接 + *

* * @param channel 当前已经建立连接的通道对象 * @return 非null:接受该连接,null:拒绝该连接 diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/NioQuickClient.java b/bus-socket/src/main/java/org/aoju/bus/socket/NioQuickClient.java deleted file mode 100644 index 3d8402a8f3..0000000000 --- a/bus-socket/src/main/java/org/aoju/bus/socket/NioQuickClient.java +++ /dev/null @@ -1,187 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.socket; - -import org.aoju.bus.core.exception.InternalException; -import org.aoju.bus.core.toolkit.IoKit; -import org.aoju.bus.core.toolkit.ThreadKit; -import org.aoju.bus.socket.handler.ChannelSocketHandler; - -import java.io.Closeable; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; -import java.util.Iterator; - -/** - * NIO客户端 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class NioQuickClient implements Closeable { - - private Selector selector; - private SocketChannel channel; - private ChannelSocketHandler handler; - - /** - * 构造 - * - * @param host 服务器地址 - * @param port 端口 - */ - public NioQuickClient(String host, int port) { - init(new InetSocketAddress(host, port)); - } - - /** - * 构造 - * - * @param address 服务器地址 - */ - public NioQuickClient(InetSocketAddress address) { - init(address); - } - - /** - * 初始化 - * - * @param address 地址和端口 - * @return this - */ - public NioQuickClient init(InetSocketAddress address) { - try { - //创建一个SocketChannel对象,配置成非阻塞模式 - this.channel = SocketChannel.open(); - channel.configureBlocking(false); - channel.connect(address); - - //创建一个选择器,并把SocketChannel交给selector对象 - this.selector = Selector.open(); - channel.register(this.selector, SelectionKey.OP_READ); - - // 等待建立连接 - while (false == channel.finishConnect()) { - } - } catch (IOException e) { - throw new InternalException(e); - } - return this; - } - - /** - * 设置NIO数据处理器 - * - * @param handler {@link ChannelSocketHandler} - * @return this - */ - public NioQuickClient setChannelHandler(ChannelSocketHandler handler) { - this.handler = handler; - return this; - } - - /** - * 开始监听 - */ - public void listen() { - ThreadKit.execute(() -> { - try { - doListen(); - } catch (IOException e) { - e.printStackTrace(); - } - }); - } - - /** - * 开始监听 - * - * @throws IOException IO异常 - */ - private void doListen() throws IOException { - while (this.selector.isOpen() && 0 != this.selector.select()) { - // 返回已选择键的集合 - final Iterator keyIter = selector.selectedKeys().iterator(); - while (keyIter.hasNext()) { - handle(keyIter.next()); - keyIter.remove(); - } - } - } - - /** - * 处理SelectionKey - * - * @param key SelectionKey - */ - private void handle(SelectionKey key) { - // 读事件就绪 - if (key.isReadable()) { - final SocketChannel socketChannel = (SocketChannel) key.channel(); - try { - handler.handle(socketChannel); - } catch (Exception e) { - throw new InternalException(e); - } - } - } - - /** - * 实现写逻辑 - * 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息 - * - * @param datas 发送的数据 - * @return this - */ - public NioQuickClient write(ByteBuffer... datas) { - try { - this.channel.write(datas); - } catch (IOException e) { - throw new InternalException(e); - } - return this; - } - - /** - * 获取SocketChannel - * - * @return SocketChannel - */ - public SocketChannel getChannel() { - return this.channel; - } - - @Override - public void close() { - IoKit.close(this.selector); - IoKit.close(this.channel); - } - -} diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/NioQuickServer.java b/bus-socket/src/main/java/org/aoju/bus/socket/NioQuickServer.java deleted file mode 100644 index bf3b4ea313..0000000000 --- a/bus-socket/src/main/java/org/aoju/bus/socket/NioQuickServer.java +++ /dev/null @@ -1,179 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.socket; - -import org.aoju.bus.core.exception.InternalException; -import org.aoju.bus.core.toolkit.IoKit; -import org.aoju.bus.logger.Logger; -import org.aoju.bus.socket.handler.ChannelSocketHandler; -import org.aoju.bus.socket.handler.CompletionAcceptHandler; - -import java.io.Closeable; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.util.Iterator; - -/** - * 基于NIO的Socket服务端实现 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class NioQuickServer implements Closeable { - - private static final CompletionAcceptHandler ACCEPT_HANDLER = new CompletionAcceptHandler(); - - private Selector selector; - private ServerSocketChannel serverSocketChannel; - private ChannelSocketHandler handler; - - /** - * 构造 - * - * @param port 端口 - */ - public NioQuickServer(int port) { - init(new InetSocketAddress(port)); - } - - /** - * 初始化 - * - * @param address 地址和端口 - * @return this - */ - public NioQuickServer init(InetSocketAddress address) { - try { - // 打开服务器套接字通道 - this.serverSocketChannel = ServerSocketChannel.open(); - // 设置为非阻塞状态 - this.serverSocketChannel.configureBlocking(false); - // 绑定端口号 - this.serverSocketChannel.bind(address); - - // 打开一个选择器 - this.selector = Selector.open(); - // 服务器套接字注册到Selector中 并指定Selector监控连接事件 - this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); - } catch (IOException e) { - throw new InternalException(e); - } - - Logger.debug("Server listen on: [{}]...", address); - - return this; - } - - /** - * 设置NIO数据处理器 - * - * @param handler {@link ChannelSocketHandler} - * @return this - */ - public NioQuickServer setChannelHandler(ChannelSocketHandler handler) { - this.handler = handler; - return this; - } - - /** - * 获取{@link Selector} - * - * @return {@link Selector} - */ - public Selector getSelector() { - return this.selector; - } - - /** - * 启动NIO服务端,即开始监听 - * - * @see #listen() - */ - public void start() { - listen(); - } - - /** - * 开始监听 - */ - public void listen() { - try { - doListen(); - } catch (IOException e) { - throw new InternalException(e); - } - } - - /** - * 开始监听 - * - * @throws IOException IO异常 - */ - private void doListen() throws IOException { - while (this.selector.isOpen() && 0 != this.selector.select()) { - // 返回已选择键的集合 - final Iterator keyIter = selector.selectedKeys().iterator(); - while (keyIter.hasNext()) { - handle(keyIter.next()); - keyIter.remove(); - } - } - } - - /** - * 处理SelectionKey - * - * @param key SelectionKey - */ - private void handle(SelectionKey key) { - // 有客户端接入此服务端 - if (key.isAcceptable()) { - ACCEPT_HANDLER.completed((ServerSocketChannel) key.channel(), this); - } - - // 读事件就绪 - if (key.isReadable()) { - final SocketChannel socketChannel = (SocketChannel) key.channel(); - try { - handler.handle(socketChannel); - } catch (Exception e) { - IoKit.close(socketChannel); - Logger.error(e); - } - } - } - - @Override - public void close() { - IoKit.close(this.selector); - IoKit.close(this.serverSocketChannel); - } - -} diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/Protocol.java b/bus-socket/src/main/java/org/aoju/bus/socket/Protocol.java index 07f99be3f6..39b949d3d0 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/Protocol.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/Protocol.java @@ -28,16 +28,21 @@ import java.nio.ByteBuffer; /** - * 消息传输采用的协议 + *

+ * 消息传输采用的协议。 + *

+ *

* 根据通信双方约定的协议规范实现{@code Protocol}接口,使用时将该实现类注册至服务启动类{@link AioQuickClient}、{@link AioQuickServer}。 + *

+ * * 注意:框架本身的所有Socket链路复用同一个Protocol,请勿在其实现类的成员变量中存储特定链路的数据。 + * * * @param 消息对象实体类型 * @author Kimi Liu * @since Java 17+ */ public interface Protocol { - /** * 对于从Socket流中获取到的数据采用当前Protocol的实现类协议进行解析。 * @@ -46,5 +51,4 @@ public interface Protocol { * @return 本次解码成功后封装的业务消息对象, 返回null则表示解码未完成 */ T decode(final ByteBuffer readBuffer, AioSession session); - } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/ServerConfig.java b/bus-socket/src/main/java/org/aoju/bus/socket/ServerConfig.java index 0afe91b518..75566b912a 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/ServerConfig.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/ServerConfig.java @@ -26,6 +26,7 @@ package org.aoju.bus.socket; import org.aoju.bus.core.lang.Normal; +import org.aoju.bus.socket.buffers.BufferFactory; import org.aoju.bus.socket.process.MessageProcessor; import java.net.SocketOption; @@ -64,12 +65,10 @@ public class ServerConfig { * 服务器端口号 */ private int port = 8888; - /** * 服务端backlog */ private int backlog = 1000; - /** * 消息处理器 */ @@ -78,12 +77,10 @@ public class ServerConfig { * 协议编解码 */ private Protocol protocol; - /** * Socket 配置 */ private Map, Object> socketOptions; - /** * 线程数 */ @@ -99,6 +96,11 @@ public class ServerConfig { */ private boolean aioEnhance = true; + /** + * 低内存模式 + */ + private boolean lowMemory = false; + /** * 获取默认内存块大小 * @@ -233,6 +235,14 @@ public void setAioEnhance(boolean aioEnhance) { this.aioEnhance = aioEnhance; } + public boolean isLowMemory() { + return lowMemory; + } + + public void setLowMemory(boolean lowMemory) { + this.lowMemory = lowMemory; + } + @Override public String toString() { return "ServerConfig{" + diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/SocketStatus.java b/bus-socket/src/main/java/org/aoju/bus/socket/SocketStatus.java index 65e52c822b..5dc257d751 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/SocketStatus.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/SocketStatus.java @@ -62,7 +62,6 @@ public enum SocketStatus { * 执行{@link MessageProcessor#process(AioSession, Object)}期间发生用户未捕获的异常 */ PROCESS_EXCEPTION, - /** * 协议解码异常 * 执行{@link Protocol#decode(ByteBuffer, AioSession)}期间发生未捕获的异常 @@ -70,32 +69,29 @@ public enum SocketStatus { DECODE_EXCEPTION, /** * 读操作异常 - *

* 在底层服务执行read操作期间因发生异常情况出发了{@link java.nio.channels.CompletionHandler#failed(Throwable, Object)} * 未来该状态机可能会废除,并转移至NetMonitor */ INPUT_EXCEPTION, /** - * 写操作异常。 + * 写操作异常 * 在底层服务执行write操作期间因发生异常情况出发了{@link java.nio.channels.CompletionHandler#failed(Throwable, Object)} * 未来该状态机可能会废除,并转移至NetMonitor */ OUTPUT_EXCEPTION, /** * 会话正在关闭中 - * 执行了{@link AioSession#close(boolean)}方法,并且当前还存在待输出的数据 */ SESSION_CLOSING, /** * 会话关闭成功 + * AioSession关闭成功 */ SESSION_CLOSED, - /** * 拒绝接受连接,仅Server端有效 */ REJECT_ACCEPT, - /** * 服务端接受连接异常 */ diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/TcpAioSession.java b/bus-socket/src/main/java/org/aoju/bus/socket/TcpAioSession.java index de5566c3fa..b21e5bca7b 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/TcpAioSession.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/TcpAioSession.java @@ -25,12 +25,12 @@ ********************************************************************************/ package org.aoju.bus.socket; -import org.aoju.bus.core.io.buffer.PageBuffer; -import org.aoju.bus.core.io.buffer.VirtualBuffer; -import org.aoju.bus.core.io.buffer.WriteBuffer; import org.aoju.bus.core.toolkit.IoKit; -import org.aoju.bus.socket.handler.CompletionReadHandler; -import org.aoju.bus.socket.handler.CompletionWriteHandler; +import org.aoju.bus.socket.buffers.BufferPage; +import org.aoju.bus.socket.buffers.VirtualBuffer; +import org.aoju.bus.socket.buffers.WriteBuffer; +import org.aoju.bus.socket.handler.ReadCompletionHandler; +import org.aoju.bus.socket.handler.WriteCompletionHandler; import org.aoju.bus.socket.process.MessageProcessor; import java.io.IOException; @@ -41,11 +41,11 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Supplier; /** * AIO传输层会话 - *

- * AioSession为bus-socket最核心的类,封装{@link AsynchronousSocketChannel} API接口,简化IO操作 + * AioSession为smart-socket最核心的类,封装{@link AsynchronousSocketChannel} API接口,简化IO操作 * 其中开放给用户使用的接口为: *

    *
  1. {@link TcpAioSession#close()}
  2. @@ -57,13 +57,13 @@ *
  3. {@link TcpAioSession#getRemoteAddress()}
  4. *
  5. {@link TcpAioSession#getSessionID()}
  6. *
  7. {@link TcpAioSession#isInvalid()}
  8. - *
  9. {@link TcpAioSession#setAttachment(Object)}
  10. + *
  11. {@link TcpAioSession#setAttachment(Object)}
  12. *
* * @author Kimi Liu * @since Java 17+ */ -public class TcpAioSession extends AioSession { +public class TcpAioSession extends AioSession { /** * 底层通信channel对象 @@ -80,15 +80,16 @@ public class TcpAioSession extends AioSession { /** * 读回调 */ - private final CompletionReadHandler completionReadHandler; + private final ReadCompletionHandler readCompletionHandler; /** * 写回调 */ - private final CompletionWriteHandler completionWriteHandler; + private final WriteCompletionHandler writeCompletionHandler; /** * 服务配置 */ private final ServerConfig serverConfig; + private final Supplier function; /** * 是否读通道以至末尾 */ @@ -112,56 +113,58 @@ public class TcpAioSession extends AioSession { /** * @param channel Socket通道 * @param config 配置项 - * @param completionReadHandler 读回调 - * @param completionWriteHandler 写回调 - * @param pageBuffer 绑定内存页 + * @param readCompletionHandler 读回调 + * @param writeCompletionHandler 写回调 + * @param bufferPage 绑定内存页 */ - TcpAioSession(AsynchronousSocketChannel channel, final ServerConfig config, CompletionReadHandler completionReadHandler, CompletionWriteHandler completionWriteHandler, PageBuffer pageBuffer) { + TcpAioSession(AsynchronousSocketChannel channel, final ServerConfig config, ReadCompletionHandler readCompletionHandler, WriteCompletionHandler writeCompletionHandler, BufferPage bufferPage, Supplier supplier) { this.channel = channel; - this.completionReadHandler = completionReadHandler; - this.completionWriteHandler = completionWriteHandler; + this.readCompletionHandler = readCompletionHandler; + this.writeCompletionHandler = writeCompletionHandler; this.serverConfig = config; - + this.function = supplier; Consumer flushConsumer = var -> { if (!semaphore.tryAcquire()) { return; } TcpAioSession.this.writeBuffer = var.poll(); - if (null == writeBuffer) { + if (writeBuffer == null) { semaphore.release(); } else { continueWrite(writeBuffer); } }; - byteBuf = new WriteBuffer(pageBuffer, flushConsumer, serverConfig.getWriteBufferSize(), serverConfig.getWriteBufferCapacity()); - //触发状态机 + byteBuf = new WriteBuffer(bufferPage, flushConsumer, serverConfig.getWriteBufferSize(), serverConfig.getWriteBufferCapacity()); + // 触发状态机 config.getProcessor().stateEvent(this, SocketStatus.NEW_SESSION, null); + doRead(); } - /** - * 初始化AioSession - * - * @param readBuffer 缓存信息 - */ - void initSession(VirtualBuffer readBuffer) { - this.readBuffer = readBuffer; + public void doRead() { + this.readBuffer = function.get(); this.readBuffer.buffer().flip(); signalRead(); } + public void suspendRead() { + this.readBuffer.clean(); + this.readBuffer = null; + } + /** * 触发AIO的写操作, - * 需要调用控制同步 + *

需要调用控制同步

*/ public void writeCompleted() { - if (null == writeBuffer) { - writeBuffer = byteBuf.poll(); + if (writeBuffer == null) { + writeBuffer = byteBuf.pollItem(); } else if (!writeBuffer.buffer().hasRemaining()) { writeBuffer.clean(); - writeBuffer = byteBuf.poll(); +// byteBuf.reuse(writeBuffer); + writeBuffer = byteBuf.pollItem(); } - if (null != writeBuffer) { + if (writeBuffer != null) { continueWrite(writeBuffer); return; } @@ -178,7 +181,7 @@ public void writeCompleted() { /** * @return 输入流 */ - public final WriteBuffer writeBuffer() { + public WriteBuffer writeBuffer() { return byteBuf; } @@ -203,15 +206,18 @@ public synchronized void close(boolean immediate) { } status = immediate ? SESSION_STATUS_CLOSED : SESSION_STATUS_CLOSING; if (immediate) { - byteBuf.close(); - readBuffer.clean(); - if (null != writeBuffer) { - writeBuffer.clean(); - writeBuffer = null; + try { + byteBuf.close(); + readBuffer.clean(); + if (writeBuffer != null) { + writeBuffer.clean(); + writeBuffer = null; + } + } finally { + IoKit.close(channel); + serverConfig.getProcessor().stateEvent(this, SocketStatus.SESSION_CLOSED, null); } - IoKit.close(channel); - serverConfig.getProcessor().stateEvent(this, SocketStatus.SESSION_CLOSED, null); - } else if ((null == writeBuffer || !writeBuffer.buffer().hasRemaining()) && !byteBuf.isEmpty()) { + } else if ((writeBuffer == null || !writeBuffer.buffer().hasRemaining()) && byteBuf.isEmpty()) { close(true); } else { serverConfig.getProcessor().stateEvent(this, SocketStatus.SESSION_CLOSING, null); @@ -260,7 +266,7 @@ public void signalRead() { messageProcessor.stateEvent(this, SocketStatus.DECODE_EXCEPTION, e); throw e; } - if (null == dataEntry) { + if (dataEntry == null) { break; } @@ -296,10 +302,10 @@ public void signalRead() { //read from channel NetMonitor monitor = getServerConfig().getMonitor(); - if (null != monitor) { + if (monitor != null) { monitor.beforeRead(this); } - channel.read(readBuffer, 0L, TimeUnit.MILLISECONDS, this, completionReadHandler); + channel.read(readBuffer, 0L, TimeUnit.MILLISECONDS, this, readCompletionHandler); } /** @@ -327,10 +333,10 @@ private int synRead() throws IOException { */ private void continueWrite(VirtualBuffer writeBuffer) { NetMonitor monitor = getServerConfig().getMonitor(); - if (null != monitor) { + if (monitor != null) { monitor.beforeWrite(this); } - channel.write(writeBuffer.buffer(), 0L, TimeUnit.MILLISECONDS, this, completionWriteHandler); + channel.write(writeBuffer.buffer(), 0L, TimeUnit.MILLISECONDS, this, writeCompletionHandler); } /** diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/UdpAioSession.java b/bus-socket/src/main/java/org/aoju/bus/socket/UdpAioSession.java index 518cecb2a1..a89dea8d8c 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/UdpAioSession.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/UdpAioSession.java @@ -25,12 +25,15 @@ ********************************************************************************/ package org.aoju.bus.socket; -import org.aoju.bus.core.io.buffer.WriteBuffer; +import org.aoju.bus.socket.buffers.BufferPage; +import org.aoju.bus.socket.buffers.VirtualBuffer; +import org.aoju.bus.socket.buffers.WriteBuffer; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; +import java.util.function.Consumer; /** * @author Kimi Liu @@ -44,10 +47,16 @@ public class UdpAioSession extends AioSession { private final WriteBuffer writeBuffer; - UdpAioSession(final UdpChannel udpChannel, final SocketAddress remote, WriteBuffer writeBuffer) { + UdpAioSession(final UdpChannel udpChannel, final SocketAddress remote, BufferPage bufferPage) { this.udpChannel = udpChannel; this.remote = remote; - this.writeBuffer = writeBuffer; + Consumer consumer = var -> { + VirtualBuffer writeBuffer = var.poll(); + if (writeBuffer != null) { + udpChannel.write(writeBuffer, UdpAioSession.this); + } + }; + this.writeBuffer = new WriteBuffer(bufferPage, consumer, udpChannel.config.getWriteBufferSize(), 1); udpChannel.config.getProcessor().stateEvent(this, SocketStatus.NEW_SESSION, null); } @@ -71,11 +80,14 @@ public void signalRead() { throw new UnsupportedOperationException(); } + /** + * 为确保消息尽可能发送,UDP不支持立即close + * + * @param immediate true:立即关闭,false:响应消息发送完后关闭 + */ @Override public void close(boolean immediate) { - writeBuffer.close(); - udpChannel.config.getProcessor().stateEvent(this, SocketStatus.SESSION_CLOSED, null); - udpChannel.removeSession(remote); + writeBuffer.flush(); } @Override diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/UdpBootstrap.java b/bus-socket/src/main/java/org/aoju/bus/socket/UdpBootstrap.java index 8dad4b525a..384cae3979 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/UdpBootstrap.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/UdpBootstrap.java @@ -25,58 +25,40 @@ ********************************************************************************/ package org.aoju.bus.socket; -import org.aoju.bus.core.exception.InternalException; -import org.aoju.bus.core.io.buffer.PageBuffer; -import org.aoju.bus.core.io.buffer.VirtualBuffer; -import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.socket.buffers.BufferFactory; +import org.aoju.bus.socket.buffers.BufferPool; import org.aoju.bus.socket.process.MessageProcessor; import java.io.IOException; import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; import java.nio.channels.DatagramChannel; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.util.Iterator; -import java.util.Set; -import java.util.concurrent.*; -import java.util.function.Consumer; /** * UDP服务启动类 * - * @param 请求信息 * @author Kimi Liu * @since Java 17+ */ -public class UdpBootstrap { - - private final static int MAX_READ_TIMES = Normal._16; - /** - * 服务ID - */ - private static int UID; - /** - * 缓存页 - */ - private final PageBuffer bufferPage = new org.aoju.bus.core.io.buffer.ByteBuffer(Normal._1024 * Normal._1024, 1, true).allocatePageBuffer(); +public class UdpBootstrap { /** * 服务配置 */ private final ServerConfig config = new ServerConfig(); + /** + * 内存池 + */ + private BufferPool bufferPool; + private BufferPool innerBufferPool = null; + private WorkerRegister workerRegister; + private boolean innerWorker = false; + + public UdpBootstrap(Protocol protocol, MessageProcessor messageProcessor, WorkerRegister workerRegister) { + this(protocol, messageProcessor); + this.workerRegister = workerRegister; + } - private Worker worker; - - private UdpDispatcher[] workerGroup; - private ExecutorService executorService; - - private boolean running = true; - - public UdpBootstrap(Protocol protocol, MessageProcessor messageProcessor) { + public UdpBootstrap(Protocol protocol, MessageProcessor messageProcessor) { config.setProtocol(protocol); config.setProcessor(messageProcessor); } @@ -85,9 +67,8 @@ public UdpBootstrap(Protocol protocol, MessageProcessor messageProcessor) * 开启一个UDP通道,端口号随机 * * @return UDP通道 - * @throws IOException 异常 */ - public UdpChannel open() throws IOException { + public UdpChannel open() throws IOException { return open(0); } @@ -95,10 +76,8 @@ public UdpChannel open() throws IOException { * 开启一个UDP通道 * * @param port 指定绑定端口号,为0则随机指定 - * @return the object - * @throws IOException 异常 */ - public UdpChannel open(int port) throws IOException { + public UdpChannel open(int port) throws IOException { return open(null, port); } @@ -107,116 +86,48 @@ public UdpChannel open(int port) throws IOException { * * @param host 绑定本机地址 * @param port 指定绑定端口号,为0则随机指定 - * @return the object - * @throws IOException 异常 */ - public UdpChannel open(String host, int port) throws IOException { - //启动线程服务 - if (null == worker) { - initThreadServer(); + public UdpChannel open(String host, int port) throws IOException { + // 初始化内存池 + if (bufferPool == null) { + this.bufferPool = config.getBufferFactory().create(); + this.innerBufferPool = bufferPool; + } + // 初始化工作线程 + if (workerRegister == null) { + innerWorker = true; + workerRegister = new WorkerRegister(bufferPool, config.getThreadNum()); } - DatagramChannel channel = DatagramChannel.open(); channel.configureBlocking(false); if (port > 0) { - InetSocketAddress inetSocketAddress = null == host ? new InetSocketAddress(port) : new InetSocketAddress(host, port); + InetSocketAddress inetSocketAddress = host == null ? new InetSocketAddress(port) : new InetSocketAddress(host, port); channel.socket().bind(inetSocketAddress); } - UdpChannel udpChannel = new UdpChannel(channel, worker, config, bufferPage); - worker.addRegister(selector -> { - try { - SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_READ); - udpChannel.setSelectionKey(selectionKey); - selectionKey.attach(udpChannel); - } catch (ClosedChannelException e) { - e.printStackTrace(); - } - }); - return udpChannel; + return new UdpChannel(channel, workerRegister, config, bufferPool.allocateBufferPage()); } - private synchronized void initThreadServer() throws IOException { - if (null != worker) { + private synchronized void initWorker() { + if (workerRegister != null) { return; } - - int uid = UdpBootstrap.UID++; - - //启动worker线程组 - workerGroup = new UdpDispatcher[config.getThreadNum()]; - executorService = new ThreadPoolExecutor(config.getThreadNum(), config.getThreadNum(), - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(), new ThreadFactory() { - int i = 0; - - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "socket:udp-" + uid + Symbol.MINUS + (++i)); - } - }); - for (int i = 0; i < config.getThreadNum(); i++) { - workerGroup[i] = new UdpDispatcher(config.getProcessor()); - executorService.execute(workerGroup[i]); - } - //启动Boss线程组 - worker = new Worker(); - new Thread(worker, "socket:udp-" + uid).start(); - } - - private void doRead(VirtualBuffer readBuffer, UdpChannel channel) throws IOException { - int count = MAX_READ_TIMES; - while (count-- > 0) { - // 接收数据 - ByteBuffer buffer = readBuffer.buffer(); - buffer.clear(); - SocketAddress remote = channel.getChannel().receive(buffer); - if (null == remote) { - return; - } - buffer.flip(); - - UdpAioSession aioSession = channel.createAndCacheSession(remote); - NetMonitor netMonitor = config.getMonitor(); - if (null != netMonitor) { - netMonitor.beforeRead(aioSession); - netMonitor.afterRead(aioSession, buffer.remaining()); - } - Object request; - // 解码 - try { - request = config.getProtocol().decode(buffer, aioSession); - } catch (Exception e) { - config.getProcessor().stateEvent(aioSession, SocketStatus.DECODE_EXCEPTION, e); - aioSession.close(); - throw e; - } - // 理论上每个UDP包都是一个完整的消息 - if (null == request) { - config.getProcessor().stateEvent(aioSession, SocketStatus.DECODE_EXCEPTION, new InternalException("decode result is null")); - } else { - // 任务分发 - workerGroup[(remote.hashCode() & Integer.MAX_VALUE) % workerGroup.length].dispatch(aioSession, request); - } - } } public void shutdown() { - running = false; - worker.selector.wakeup(); - - for (UdpDispatcher dispatcher : workerGroup) { - dispatcher.dispatch(dispatcher.EXECUTE_TASK_OR_SHUTDOWN); + if (innerWorker) { + workerRegister.shutdown(); + } + if (innerBufferPool != null) { + innerBufferPool.release(); } - executorService.shutdown(); } /** * 设置读缓存区大小 * * @param size 单位:byte - * @return the object */ - public final UdpBootstrap setReadBufferSize(int size) { + public final UdpBootstrap setReadBufferSize(int size) { this.config.setReadBufferSize(size); return this; } @@ -227,75 +138,39 @@ public final UdpBootstrap setReadBufferSize(int size) { * @param num 大小 * @return the object */ - public final UdpBootstrap setThreadNum(int num) { + public final UdpBootstrap setThreadNum(int num) { this.config.setThreadNum(num); return this; } - class Worker implements Runnable { - /** - * 当前Worker绑定的Selector - */ - private final Selector selector; - - /** - * 待注册的事件 - */ - private final ConcurrentLinkedQueue> registers = new ConcurrentLinkedQueue<>(); - - Worker() throws IOException { - this.selector = Selector.open(); - } - - /** - * 注册事件 - */ - final void addRegister(Consumer register) { - registers.offer(register); - selector.wakeup(); - } - - @Override - public final void run() { - // 优先获取SelectionKey,若无关注事件触发则阻塞在selector.select(),减少select被调用次数 - Set keySet = selector.selectedKeys(); - //读缓冲区 - VirtualBuffer readBuffer = bufferPage.allocate(config.getReadBufferSize()); - try { - while (running) { - Consumer register; - while (null != (register = registers.poll())) { - register.accept(selector); - } - if (keySet.isEmpty() && selector.select() == 0) { - continue; - } - Iterator keyIterator = keySet.iterator(); - // 执行本次已触发待处理的事件 - while (keyIterator.hasNext()) { - SelectionKey key = keyIterator.next(); - keyIterator.remove(); - UdpChannel udpChannel = (UdpChannel) key.attachment(); - if (!key.isValid()) { - udpChannel.close(); - continue; - } + /** + * 设置内存池 + * 通过该方法设置的内存池,在AioQuickServer执行shutdown时不会触发内存池的释放。 + * 该方法适用于多个AioQuickServer、AioQuickClient共享内存池的场景。 + * 在启用内存池的情况下会有更好的性能表现 + * + * @param bufferPool 内存池对象 + * @return 当前AioQuickServer对象 + */ + public final UdpBootstrap setBufferPagePool(BufferPool bufferPool) { + this.bufferPool = bufferPool; + this.config.setBufferFactory(BufferFactory.DISABLED_BUFFER_FACTORY); + return this; + } - if (key.isReadable()) { - doRead(readBuffer, udpChannel); - } - if (key.isWritable()) { - udpChannel.doWrite(); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - //读缓冲区内存回收 - readBuffer.clean(); - } - } + /** + * 设置内存池的构造工厂。 + * 通过工厂形式生成的内存池会强绑定到当前UdpBootstrap对象, + * 在UdpBootstrap执行shutdown时会释放内存池。 + * 在启用内存池的情况下会有更好的性能表现 + * + * @param bufferFactory 内存池工厂 + * @return 当前AioQuickServer对象 + */ + public final UdpBootstrap setBufferFactory(BufferFactory bufferFactory) { + this.config.setBufferFactory(bufferFactory); + this.bufferPool = null; + return this; } } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/UdpChannel.java b/bus-socket/src/main/java/org/aoju/bus/socket/UdpChannel.java index 195dc54d41..873553dfca 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/UdpChannel.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/UdpChannel.java @@ -25,23 +25,21 @@ ********************************************************************************/ package org.aoju.bus.socket; -import org.aoju.bus.core.io.buffer.PageBuffer; -import org.aoju.bus.core.io.buffer.VirtualBuffer; -import org.aoju.bus.core.io.buffer.WriteBuffer; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.logger.Logger; +import org.aoju.bus.socket.buffers.BufferPage; +import org.aoju.bus.socket.buffers.VirtualBuffer; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; -import java.util.function.Consumer; /** * 封装UDP底层真实渠道对象,并提供通信及会话管理 @@ -49,44 +47,53 @@ * @author Kimi Liu * @since Java 17+ */ -public class UdpChannel { +public class UdpChannel { - private final PageBuffer pageBuffer; + public final ServerConfig config; + public final BufferPage bufferPage; + public final Semaphore writeSemaphore = new Semaphore(1); /** - * 与当前UDP通道对接的会话 + * 真实的UDP通道 */ - private final ConcurrentHashMap sessionMap = new ConcurrentHashMap<>(); + public final DatagramChannel channel; /** * 待输出消息 */ - private final ConcurrentLinkedQueue responseTasks; - private final Semaphore writeSemaphore = new Semaphore(1); - private final UdpBootstrap.Worker worker; - ServerConfig config; - /** - * 真实的UDP通道 - */ - private DatagramChannel channel; + private ConcurrentLinkedQueue responseTasks; + private WorkerRegister workerRegister; private SelectionKey selectionKey; + //发送失败的 private ResponseUnit failResponseUnit; - UdpChannel(final DatagramChannel channel, UdpBootstrap.Worker worker, ServerConfig config, PageBuffer pageBuffer) { + UdpChannel(final DatagramChannel channel, ServerConfig config, BufferPage bufferPage) { this.channel = channel; - responseTasks = new ConcurrentLinkedQueue<>(); - this.worker = worker; - this.pageBuffer = pageBuffer; + this.bufferPage = bufferPage; this.config = config; } - private void write(VirtualBuffer virtualBuffer, SocketAddress remote) throws IOException { - if (writeSemaphore.tryAcquire() && responseTasks.isEmpty() && send(virtualBuffer.buffer(), remote) > 0) { + UdpChannel(final DatagramChannel channel, WorkerRegister workerRegister, ServerConfig config, BufferPage bufferPage) { + this(channel, config, bufferPage); + responseTasks = new ConcurrentLinkedQueue<>(); + this.workerRegister = workerRegister; + workerRegister.addRegister(selector -> { + try { + UdpChannel.this.selectionKey = channel.register(selector, SelectionKey.OP_READ, UdpChannel.this); + } catch (ClosedChannelException e) { + e.printStackTrace(); + } + }); + } + + void write(VirtualBuffer virtualBuffer, UdpAioSession session) { + if (writeSemaphore.tryAcquire() && responseTasks.isEmpty() && send(virtualBuffer.buffer(), session) > 0) { virtualBuffer.clean(); writeSemaphore.release(); + session.writeBuffer().flush(); return; } - responseTasks.offer(new ResponseUnit(remote, virtualBuffer)); - if (null == selectionKey) { - worker.addRegister(selector -> selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE)); + responseTasks.offer(new ResponseUnit(session, virtualBuffer)); + if (selectionKey == null) { + workerRegister.addRegister(selector -> selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE)); } else { if ((selectionKey.interestOps() & SelectionKey.OP_WRITE) == 0) { selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE); @@ -94,21 +101,16 @@ private void write(VirtualBuffer virtualBuffer, SocketAddress remote) throws IOE } } - void setSelectionKey(SelectionKey selectionKey) { - this.selectionKey = selectionKey; - } - - void doWrite() throws IOException { + void doWrite() { while (true) { ResponseUnit responseUnit; - if (null == failResponseUnit) { + if (failResponseUnit == null) { responseUnit = responseTasks.poll(); - Logger.info("poll from writeBuffer"); } else { responseUnit = failResponseUnit; failResponseUnit = null; } - if (null == responseUnit) { + if (responseUnit == null) { writeSemaphore.release(); if (responseTasks.isEmpty()) { selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE); @@ -118,105 +120,76 @@ void doWrite() throws IOException { } return; } - if (send(responseUnit.response.buffer(), responseUnit.remote) > 0) { + if (send(responseUnit.response.buffer(), responseUnit.session) > 0) { responseUnit.response.clean(); + responseUnit.session.writeBuffer().flush(); } else { failResponseUnit = responseUnit; + Logger.warn("send fail,will retry..."); break; } } } - private int send(ByteBuffer byteBuffer, SocketAddress remote) throws IOException { - AioSession aioSession = sessionMap.get(remote); - if (null != config.getMonitor()) { - config.getMonitor().beforeWrite(aioSession); + private int send(ByteBuffer byteBuffer, UdpAioSession session) { + if (config.getMonitor() != null) { + config.getMonitor().beforeWrite(session); + } + int size = 0; + try { + size = channel.send(byteBuffer, session.getRemoteAddress()); + } catch (IOException e) { + throw new RuntimeException(e); } - int size = channel.send(byteBuffer, remote); - if (null != config.getMonitor()) { - config.getMonitor().afterWrite(aioSession, size); + if (config.getMonitor() != null) { + config.getMonitor().afterWrite(session, size); } return size; } /** * 建立与远程服务的连接会话,通过AioSession可进行数据传输 - * - * @param remote 地址 - * @return 会话信息 */ public AioSession connect(SocketAddress remote) { - return createAndCacheSession(remote); + return new UdpAioSession(this, remote, bufferPage); } - /** - * 建立与远程服务的连接会话,通过AioSession可进行数据传输 - * - * @param host 地址 - * @param port 端口 - * @return 会话信息 - */ public AioSession connect(String host, int port) { return connect(new InetSocketAddress(host, port)); } - /** - * 创建并缓存与指定地址的会话信息 - */ - UdpAioSession createAndCacheSession(final SocketAddress remote) { - return sessionMap.computeIfAbsent(remote, s -> { - Consumer consumer = writeBuffer -> { - VirtualBuffer virtualBuffer = writeBuffer.poll(); - if (null == virtualBuffer) { - return; - } - try { - write(virtualBuffer, remote); - } catch (IOException e) { - e.printStackTrace(); - } - }; - WriteBuffer writeBuffer = new WriteBuffer(pageBuffer, consumer, config.getWriteBufferSize(), 1); - return new UdpAioSession(UdpChannel.this, remote, writeBuffer); - }); - } - - void removeSession(final SocketAddress remote) { - UdpAioSession udpAioSession = sessionMap.remove(remote); - Logger.info("remove session:{}", udpAioSession); - } - /** * 关闭当前连接 */ public void close() { - if (null != selectionKey) { + Logger.info("close channel..."); + if (selectionKey != null) { Selector selector = selectionKey.selector(); selectionKey.cancel(); selector.wakeup(); selectionKey = null; } - for (UdpAioSession session : sessionMap.values()) { - session.close(); - } try { - if (null != channel) { + if (channel != null) { channel.close(); - channel = null; } } catch (IOException e) { Logger.error(Normal.EMPTY, e); } - // 内存回收 + //内存回收 ResponseUnit task; - while (null != (task = responseTasks.poll())) { + while ((task = responseTasks.poll()) != null) { task.response.clean(); } - if (null != failResponseUnit) { + if (failResponseUnit != null) { failResponseUnit.response.clean(); } } + BufferPage getBufferPage() { + return bufferPage; + } + DatagramChannel getChannel() { return channel; } @@ -225,14 +198,14 @@ static final class ResponseUnit { /** * 待输出数据的接受地址 */ - private final SocketAddress remote; + private final UdpAioSession session; /** * 待输出数据 */ private final VirtualBuffer response; - public ResponseUnit(SocketAddress remote, VirtualBuffer response) { - this.remote = remote; + public ResponseUnit(UdpAioSession session, VirtualBuffer response) { + this.session = session; this.response = response; } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/UdpDispatcher.java b/bus-socket/src/main/java/org/aoju/bus/socket/UdpDispatcher.java deleted file mode 100644 index 5ba1bf3bb5..0000000000 --- a/bus-socket/src/main/java/org/aoju/bus/socket/UdpDispatcher.java +++ /dev/null @@ -1,100 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.socket; - -import org.aoju.bus.logger.Logger; -import org.aoju.bus.socket.process.MessageProcessor; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -/** - * UDP消息分发器 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class UdpDispatcher implements Runnable { - - public final static RequestTask EXECUTE_TASK_OR_SHUTDOWN = new RequestTask(null, null); - private final BlockingQueue taskQueue = new LinkedBlockingQueue<>(); - private final MessageProcessor processor; - - public UdpDispatcher(MessageProcessor processor) { - this.processor = processor; - } - - @Override - public void run() { - while (true) { - try { - RequestTask unit = taskQueue.take(); - if (unit == EXECUTE_TASK_OR_SHUTDOWN) { - Logger.info("shutdown thread:{}", Thread.currentThread()); - break; - } - processor.process(unit.session, unit.request); - if (!unit.session.isInvalid()) { - unit.session.writeBuffer().flush(); - } - } catch (InterruptedException e) { - Logger.info("InterruptedException", e); - } catch (Exception e) { - Logger.error(e.getClass().getName(), e); - } - } - } - - /** - * 任务分发 - * - * @param session the session - * @param request the request - */ - public void dispatch(UdpAioSession session, Object request) { - dispatch(new RequestTask(session, request)); - } - - /** - * 任务分发 - * - * @param requestTask the requestTask - */ - public void dispatch(RequestTask requestTask) { - taskQueue.offer(requestTask); - } - - static class RequestTask { - UdpAioSession session; - Object request; - - public RequestTask(UdpAioSession session, Object request) { - this.session = session; - this.request = request; - } - } - -} diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/WorkerRegister.java b/bus-socket/src/main/java/org/aoju/bus/socket/WorkerRegister.java index d37b00424e..3cd75afcf3 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/WorkerRegister.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/WorkerRegister.java @@ -2,7 +2,7 @@ * * * The MIT License (MIT) * * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * * * * Permission is hereby granted, free of charge, to any person obtaining a copy * * of this software and associated documentation files (the "Software"), to deal * @@ -25,21 +25,192 @@ ********************************************************************************/ package org.aoju.bus.socket; +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.socket.buffers.BufferPool; +import org.aoju.bus.socket.buffers.VirtualBuffer; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; import java.nio.channels.Selector; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.*; +import java.util.function.Consumer; + +public final class WorkerRegister implements Runnable { + + private final static int MAX_READ_TIMES = 16; + private static final Runnable SELECTOR_CHANNEL = () -> { + }; + private static final Runnable SHUTDOWN_CHANNEL = () -> { + }; + /** + * 当前Worker绑定的Selector + */ + private final Selector selector; + /** + * 内存池 + */ + private final BufferPool bufferPool; + private final BlockingQueue requestQueue = new ArrayBlockingQueue<>(256); + /** + * 待注册的事件 + */ + private final ConcurrentLinkedQueue> registers = new ConcurrentLinkedQueue<>(); + private final ExecutorService executorService; + private VirtualBuffer standbyBuffer; -/** - * selector register callback - * - * @author Kimi Liu - * @since Java 17+ - */ -public interface WorkerRegister { + public WorkerRegister(BufferPool bufferPool, int threadNum) throws IOException { + this.bufferPool = bufferPool; + this.selector = Selector.open(); + try { + this.requestQueue.put(SELECTOR_CHANNEL); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + // 启动worker线程组 + executorService = new ThreadPoolExecutor(threadNum, threadNum, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), new ThreadFactory() { + int i = 0; + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "bus-socket:udp-" + WorkerRegister.this.hashCode() + "-" + (++i)); + } + }); + for (int i = 0; i < threadNum; i++) { + executorService.execute(this); + } + } /** - * selector回调 - * - * @param selector 用于注册事件的selector + * 注册事件 */ - void callback(Selector selector); + void addRegister(Consumer register) { + registers.offer(register); + selector.wakeup(); + } + + @Override + public void run() { + try { + while (true) { + Runnable runnable = requestQueue.take(); + // 服务终止 + if (runnable == SHUTDOWN_CHANNEL) { + requestQueue.put(SHUTDOWN_CHANNEL); + selector.wakeup(); + break; + } else if (runnable == SELECTOR_CHANNEL) { + try { + doSelector(); + } finally { + requestQueue.put(SELECTOR_CHANNEL); + } + } else { + runnable.run(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void doSelector() throws IOException { + Consumer register; + while ((register = registers.poll()) != null) { + register.accept(selector); + } + Set keySet = selector.selectedKeys(); + if (keySet.isEmpty()) { + selector.select(); + } + Iterator keyIterator = keySet.iterator(); + // 执行本次已触发待处理的事件 + while (keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + UdpChannel udpChannel = (UdpChannel) key.attachment(); + if (!key.isValid()) { + keyIterator.remove(); + udpChannel.close(); + continue; + } + if (key.isWritable()) { + udpChannel.doWrite(); + } + if (key.isReadable() && !doRead(udpChannel)) { + break; + } + keyIterator.remove(); + } + } + + private boolean doRead(UdpChannel channel) throws IOException { + int count = MAX_READ_TIMES; + ServerConfig config = channel.config; + while (count-- > 0) { + if (standbyBuffer == null) { + standbyBuffer = channel.getBufferPage().allocate(config.getReadBufferSize()); + } + ByteBuffer buffer = standbyBuffer.buffer(); + SocketAddress remote = channel.getChannel().receive(buffer); + if (remote == null) { + buffer.clear(); + return true; + } + VirtualBuffer readyBuffer = standbyBuffer; + standbyBuffer = channel.getBufferPage().allocate(config.getReadBufferSize()); + buffer.flip(); + Runnable runnable = () -> { + // 解码 + UdpAioSession session = new UdpAioSession(channel, remote, bufferPool.allocateBufferPage()); + try { + NetMonitor netMonitor = config.getMonitor(); + if (netMonitor != null) { + netMonitor.beforeRead(session); + netMonitor.afterRead(session, buffer.remaining()); + } + do { + Object request = config.getProtocol().decode(buffer, session); + // 理论上每个UDP包都是一个完整的消息 + if (request == null) { + config.getProcessor().stateEvent(session, SocketStatus.DECODE_EXCEPTION, new InternalException("decode result is null, buffer size: " + buffer.remaining())); + break; + } else { + config.getProcessor().process(session, request); + } + } while (buffer.hasRemaining()); + } catch (Throwable e) { + e.printStackTrace(); + config.getProcessor().stateEvent(session, SocketStatus.DECODE_EXCEPTION, e); + } finally { + session.writeBuffer().flush(); + readyBuffer.clean(); + } + }; + if (!requestQueue.offer(runnable)) { + return false; + } + } + return true; + } + + void shutdown() { + try { + requestQueue.put(SHUTDOWN_CHANNEL); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + selector.wakeup(); + executorService.shutdown(); + try { + selector.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } -} +} \ No newline at end of file diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferArray.java b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferArray.java new file mode 100644 index 0000000000..d3c25e35ad --- /dev/null +++ b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferArray.java @@ -0,0 +1,58 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.socket.buffers; + +import java.nio.ByteBuffer; + +/** + * @author Kimi Liu + * @since Java 17+ + */ +public class BufferArray { + + private final ByteBuffer[] buffers; + private final int offset; + private final int length; + + public BufferArray(ByteBuffer[] buffers, int offset, int length) { + this.buffers = buffers; + this.offset = offset; + this.length = length; + } + + public ByteBuffer[] getBuffers() { + return buffers; + } + + public int getOffset() { + return offset; + } + + public int getLength() { + return length; + } + +} \ No newline at end of file diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/BufferFactory.java b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferFactory.java similarity index 86% rename from bus-socket/src/main/java/org/aoju/bus/socket/BufferFactory.java rename to bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferFactory.java index 7ebf11dc81..8ba68e2565 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/BufferFactory.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferFactory.java @@ -23,11 +23,7 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.socket; - -import org.aoju.bus.core.io.buffer.ByteBuffer; -import org.aoju.bus.core.io.buffer.PageBuffer; -import org.aoju.bus.core.io.buffer.VirtualBuffer; +package org.aoju.bus.socket.buffers; /** * 内存池工厂 @@ -40,19 +36,13 @@ public interface BufferFactory { /** * 禁用状态的内存池 */ - BufferFactory DISABLED_BUFFER_FACTORY = () -> new ByteBuffer(0, 1, false); + BufferFactory DISABLED_BUFFER_FACTORY = () -> new BufferPool(0, 1, false); /** * 创建内存池 * * @return 生成的内存池对象 */ - ByteBuffer create(); - - interface VirtualBufferFactory { - - VirtualBuffer newBuffer(PageBuffer bufferPage); - - } + BufferPool create(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/PageBuffer.java b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferPage.java old mode 100755 new mode 100644 similarity index 89% rename from bus-core/src/main/java/org/aoju/bus/core/io/buffer/PageBuffer.java rename to bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferPage.java index 96c3a40a4e..31926d7d93 --- a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/PageBuffer.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferPage.java @@ -2,7 +2,7 @@ * * * The MIT License (MIT) * * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * * * * Permission is hereby granted, free of charge, to any person obtaining a copy * * of this software and associated documentation files (the "Software"), to deal * @@ -23,10 +23,10 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.io.buffer; +package org.aoju.bus.socket.buffers; import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.toolkit.ThreadKit; +import org.aoju.bus.logger.Logger; import java.nio.ByteBuffer; import java.util.*; @@ -39,12 +39,12 @@ * @author Kimi Liu * @since Java 17+ */ -public class PageBuffer { +public final class BufferPage { /** * 同组内存池中的各内存页 */ - private final PageBuffer[] pagePool; + private final BufferPage[] poolPages; /** * 条件锁 */ @@ -70,8 +70,8 @@ public class PageBuffer { * @param size 缓存页大小 * @param direct 是否使用堆外内存 */ - PageBuffer(PageBuffer[] pagePool, int size, boolean direct) { - this.pagePool = Objects.requireNonNull(pagePool); + BufferPage(BufferPage[] poolPages, int size, boolean direct) { + this.poolPages = Objects.requireNonNull(poolPages); availableBuffers = new LinkedList<>(); this.buffer = allocate0(size, direct); availableBuffers.add(new VirtualBuffer(this, null, buffer.position(), buffer.limit())); @@ -97,8 +97,9 @@ private ByteBuffer allocate0(int size, boolean direct) { public VirtualBuffer allocate(final int size) { VirtualBuffer virtualBuffer; Thread thread = Thread.currentThread(); - if (thread instanceof ThreadKit.FastBufferThread) { - virtualBuffer = pagePool[((ThreadKit.FastBufferThread) thread).getPageIndex()].allocate0(size); + if (thread instanceof BufferThread) { + BufferThread bufferThread = (BufferThread) thread; + virtualBuffer = bufferThread.getPageIndex() < poolPages.length ? poolPages[bufferThread.getPageIndex()].allocate0(size) : allocate0(size); } else { virtualBuffer = allocate0(size); } @@ -121,9 +122,9 @@ private VirtualBuffer allocate0(final int size) { } lock.lock(); try { - if (null != cleanBuffer) { + if (cleanBuffer != null) { clean0(cleanBuffer); - while (null != (cleanBuffer = cleanBuffers.poll())) { + while ((cleanBuffer = cleanBuffers.poll()) != null) { if (cleanBuffer.getCapacity() >= size) { cleanBuffer.buffer().clear(); cleanBuffer.buffer(cleanBuffer.buffer()); @@ -136,7 +137,7 @@ private VirtualBuffer allocate0(final int size) { int count = availableBuffers.size(); VirtualBuffer bufferChunk = null; - //仅剩一个可用内存块的时候使用快速匹配算法 + // 仅剩一个可用内存块的时候使用快速匹配算法 if (count == 1) { bufferChunk = fastAllocate(size); } else if (count > 1) { @@ -152,7 +153,7 @@ private VirtualBuffer allocate0(final int size) { * 快速匹配 * * @param size 申请内存大小 - * @return 申请到的内存块, 若空间不足则范围null + * @return 申请到的内存块, 若空间不足则返回null */ private VirtualBuffer fastAllocate(int size) { VirtualBuffer freeChunk = availableBuffers.get(0); @@ -167,7 +168,7 @@ private VirtualBuffer fastAllocate(int size) { * 迭代申请 * * @param size 申请内存大小 - * @return 申请到的内存块, 若空间不足则范围null + * @return 申请到的内存块, 若空间不足则返回null */ private VirtualBuffer slowAllocate(int size) { Iterator iterator = availableBuffers.listIterator(0); @@ -190,7 +191,7 @@ private VirtualBuffer slowAllocate(int size) { * * @param size 申请内存大小 * @param freeChunk 可用于申请的内存块 - * @return 申请到的内存块, 若空间不足则范围null + * @return 申请到的内存块, 若空间不足则返回null */ private VirtualBuffer allocate(int size, VirtualBuffer freeChunk) { final int capacity = freeChunk.getCapacity(); @@ -287,13 +288,14 @@ private void clean0(VirtualBuffer cleanBuffer) { */ void release() { if (buffer.isDirect()) { + Logger.debug("clean direct buffer"); buffer.clear(); } } @Override public String toString() { - return "PageBuffer{availableBuffers=" + availableBuffers + ", cleanBuffers=" + cleanBuffers + '}'; + return "BufferPage{availableBuffers=" + availableBuffers + ", cleanBuffers=" + cleanBuffers + '}'; } } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferPool.java b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferPool.java new file mode 100644 index 0000000000..f30b35f054 --- /dev/null +++ b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferPool.java @@ -0,0 +1,136 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.socket.buffers; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * ByteBuffer内存池 + * + * @author Kimi Liu + * @since Java 17+ + */ +public final class BufferPool { + + /** + * 守护线程在空闲时期回收内存资源 + */ + private static final ScheduledThreadPoolExecutor BUFFER_POOL_CLEAN = new ScheduledThreadPoolExecutor(1, r -> { + Thread thread = new Thread(r, "BufferPoolClean"); + thread.setDaemon(true); + return thread; + }); + /** + * 内存页游标 + */ + private final AtomicInteger cursor = new AtomicInteger(0); + /** + * 内存页组 + */ + private BufferPage[] bufferPages; + private boolean enabled = true; + + /** + * @param pageSize 内存页大小 + * @param pageNum 内存页个数 + * @param isDirect 是否使用直接缓冲区 + */ + public BufferPool(final int pageSize, final int pageNum, final boolean isDirect) { + bufferPages = new BufferPage[pageNum]; + for (int i = 0; i < pageNum; i++) { + bufferPages[i] = new BufferPage(bufferPages, pageSize, isDirect); + } + if (pageNum == 0 || pageSize == 0) { + future.cancel(false); + } + } + + /** + * 申请FastBufferThread的线程对象,配合线程池申请会有更好的性能表现 + * + * @param target Runnable + * @param name 线程名 + * @return FastBufferThread线程对象 + */ + public Thread newThread(Runnable target, String name) { + assertEnabled(); + BufferThread thread = new BufferThread(target, name); + thread.setPageIndex((int) (thread.getId() % bufferPages.length)); + return thread; + } + + /** + * 申请内存页 + * + * @return 缓存页对象 + */ + public BufferPage allocateBufferPage() { + assertEnabled(); + //轮训游标,均衡分配内存页 + return bufferPages[(cursor.getAndIncrement() & Integer.MAX_VALUE) % bufferPages.length]; + } + + private void assertEnabled() { + if (!enabled) { + throw new IllegalStateException("buffer pool is disable"); + } + } + + /** + * 释放回收内存 + */ + public void release() { + enabled = false; + } + + /** + * 内存回收任务 + */ + private final ScheduledFuture future = BUFFER_POOL_CLEAN.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + if (enabled) { + for (BufferPage bufferPage : bufferPages) { + bufferPage.tryClean(); + } + } else { + if (bufferPages != null) { + for (BufferPage page : bufferPages) { + page.release(); + } + bufferPages = null; + } + future.cancel(false); + } + } + }, 500, 1000, TimeUnit.MILLISECONDS); + + +} + diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/handler/ChannelSocketHandler.java b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferThread.java similarity index 79% rename from bus-socket/src/main/java/org/aoju/bus/socket/handler/ChannelSocketHandler.java rename to bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferThread.java index bb8a6f260d..228b4f463b 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/handler/ChannelSocketHandler.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/BufferThread.java @@ -2,7 +2,7 @@ * * * The MIT License (MIT) * * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * * * * Permission is hereby granted, free of charge, to any person obtaining a copy * * of this software and associated documentation files (the "Software"), to deal * @@ -23,25 +23,26 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.socket.handler; - -import java.nio.channels.SocketChannel; +package org.aoju.bus.socket.buffers; /** - * NIO数据处理接口,通过实现此接口,可以从{@link SocketChannel}中读写数据 - * * @author Kimi Liu * @since Java 17+ */ -@FunctionalInterface -public interface ChannelSocketHandler { +public final class BufferThread extends Thread { + + private int pageIndex; + + public BufferThread(Runnable target, String name) { + super(target, name); + } + + public int getPageIndex() { + return pageIndex; + } - /** - * 处理NIO数据 - * - * @param socketChannel {@link SocketChannel} - * @throws Exception 可能的处理异常 - */ - void handle(SocketChannel socketChannel) throws Exception; + public void setPageIndex(int pageIndex) { + this.pageIndex = pageIndex; + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/VirtualBuffer.java b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/VirtualBuffer.java old mode 100755 new mode 100644 similarity index 83% rename from bus-core/src/main/java/org/aoju/bus/core/io/buffer/VirtualBuffer.java rename to bus-socket/src/main/java/org/aoju/bus/socket/buffers/VirtualBuffer.java index bfdba1ce8a..c2a1c4d18d --- a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/VirtualBuffer.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/VirtualBuffer.java @@ -2,7 +2,7 @@ * * * The MIT License (MIT) * * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * * * * Permission is hereby granted, free of charge, to any person obtaining a copy * * of this software and associated documentation files (the "Software"), to deal * @@ -23,9 +23,10 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.io.buffer; +package org.aoju.bus.socket.buffers; import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; /** * 虚拟ByteBuffer缓冲区 @@ -38,7 +39,7 @@ public final class VirtualBuffer { /** * 当前虚拟buffer的归属内存页 */ - private final PageBuffer pageBuffer; + private final BufferPage bufferPage; /** * 通过ByteBuffer.slice()隐射出来的虚拟ByteBuffer * @@ -48,7 +49,7 @@ public final class VirtualBuffer { /** * 是否已回收 */ - private boolean clean = false; + private Semaphore clean = new Semaphore(1); /** * 当前虚拟buffer映射的实际buffer.position */ @@ -64,8 +65,8 @@ public final class VirtualBuffer { */ private int capacity; - VirtualBuffer(PageBuffer pageBuffer, ByteBuffer buffer, int parentPosition, int parentLimit) { - this.pageBuffer = pageBuffer; + public VirtualBuffer(BufferPage bufferPage, ByteBuffer buffer, int parentPosition, int parentLimit) { + this.bufferPage = bufferPage; this.buffer = buffer; this.parentPosition = parentPosition; this.parentLimit = parentLimit; @@ -76,20 +77,20 @@ public static VirtualBuffer wrap(ByteBuffer buffer) { return new VirtualBuffer(null, buffer, 0, 0); } - int getParentPosition() { + public int getParentPosition() { return parentPosition; } - void setParentPosition(int parentPosition) { + public void setParentPosition(int parentPosition) { this.parentPosition = parentPosition; updateCapacity(); } - int getParentLimit() { + public int getParentLimit() { return parentLimit; } - void setParentLimit(int parentLimit) { + public void setParentLimit(int parentLimit) { this.parentLimit = parentLimit; updateCapacity(); } @@ -116,22 +117,22 @@ public ByteBuffer buffer() { * * @param buffer 真实缓冲区 */ - void buffer(ByteBuffer buffer) { + public void buffer(ByteBuffer buffer) { this.buffer = buffer; - clean = false; + clean.release(); } /** * 释放虚拟缓冲区 */ public void clean() { - if (clean) { + if (clean.tryAcquire()) { + if (bufferPage != null) { + bufferPage.clean(this); + } + } else { throw new UnsupportedOperationException("buffer has cleaned"); } - clean = true; - if (null != pageBuffer) { - pageBuffer.clean(this); - } } @Override diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/buffers/VirtualFactory.java b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/VirtualFactory.java new file mode 100644 index 0000000000..e9ef11dc1c --- /dev/null +++ b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/VirtualFactory.java @@ -0,0 +1,36 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.socket.buffers; + +/** + * @author Kimi Liu + * @since Java 17+ + */ +public interface VirtualFactory { + + VirtualBuffer newBuffer(BufferPage bufferPage); + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/WriteBuffer.java b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/WriteBuffer.java similarity index 89% rename from bus-core/src/main/java/org/aoju/bus/core/io/buffer/WriteBuffer.java rename to bus-socket/src/main/java/org/aoju/bus/socket/buffers/WriteBuffer.java index a856cbaf9b..a5afc6c82b 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/WriteBuffer.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/buffers/WriteBuffer.java @@ -23,7 +23,10 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.io.buffer; +package org.aoju.bus.socket.buffers; + +import org.aoju.bus.socket.AioSession; +import org.aoju.bus.socket.process.MessageProcessor; import java.io.IOException; import java.io.OutputStream; @@ -36,17 +39,17 @@ * @author Kimi Liu * @since Java 17+ */ -public final class WriteBuffer extends OutputStream { + +public class WriteBuffer extends OutputStream { /** * 存储已就绪待输出的数据 */ private final VirtualBuffer[] items; - /** * 为当前 WriteBuffer 提供数据存放功能的缓存页 */ - private final PageBuffer pageBuffer; + private final BufferPage bufferPage; /** * 缓冲区数据刷新函数 */ @@ -80,8 +83,8 @@ public final class WriteBuffer extends OutputStream { */ private byte[] cacheByte; - public WriteBuffer(PageBuffer pageBuffer, Consumer consumer, int chunkSize, int capacity) { - this.pageBuffer = pageBuffer; + public WriteBuffer(BufferPage bufferPage, Consumer consumer, int chunkSize, int capacity) { + this.bufferPage = bufferPage; this.consumer = consumer; this.items = new VirtualBuffer[capacity]; this.chunkSize = chunkSize; @@ -117,7 +120,7 @@ public void writeShort(short v) throws IOException { */ public synchronized void writeByte(byte b) { if (writeInBuf == null) { - writeInBuf = pageBuffer.allocate(chunkSize); + writeInBuf = bufferPage.allocate(chunkSize); } writeInBuf.buffer().put(b); flushWriteBuffer(false); @@ -137,7 +140,7 @@ private void flushWriteBuffer(boolean forceFlush) { try { while (count == items.length) { this.wait(); - //防止因close诱发内存泄露 + // 防止因close诱发内存泄露 if (closed) { virtualBuffer.clean(); return; @@ -191,7 +194,7 @@ public void writeLong(long v) throws IOException { @Override public synchronized void write(byte[] b, int off, int len) throws IOException { if (writeInBuf == null) { - writeInBuf = pageBuffer.allocate(Math.max(chunkSize, len)); + writeInBuf = bufferPage.allocate(Math.max(chunkSize, len)); } ByteBuffer writeBuffer = writeInBuf.buffer(); if (closed) { @@ -230,7 +233,17 @@ public synchronized void write(VirtualBuffer virtualBuffer) { } /** - * 写入内容并刷新缓冲区 + * 初始化8字节的缓存数值 + */ + private void initCacheBytes() { + if (cacheByte == null) { + cacheByte = new byte[8]; + } + } + + /** + * 写入内容并刷新缓冲区。在{@link MessageProcessor#process(AioSession, Object)}执行的write操作可无需调用该方法,业务执行完毕后框架本身会自动触发flush + * 调用该方法后数据会及时的输出到对端,如果再循环体中通过该方法往某个通道中写入数据将无法获得最佳性能表现 * * @param b 待输出数据 * @throws IOException 如果发生 I/O 错误 @@ -291,19 +304,21 @@ public boolean isEmpty() { return count == 0 && (writeInBuf == null || writeInBuf.buffer().position() == 0); } - public synchronized VirtualBuffer pollItem() { + public VirtualBuffer pollItem() { if (count == 0) { return null; } - VirtualBuffer x = items[takeIndex]; - items[takeIndex] = null; - if (++takeIndex == items.length) { - takeIndex = 0; - } - if (count-- == items.length) { - this.notifyAll(); + synchronized (this) { + VirtualBuffer x = items[takeIndex]; + items[takeIndex] = null; + if (++takeIndex == items.length) { + takeIndex = 0; + } + if (count-- == items.length) { + this.notifyAll(); + } + return x; } - return x; } /** @@ -326,13 +341,4 @@ public synchronized VirtualBuffer poll() { } } - /** - * 初始化8字节的缓存数值 - */ - private void initCacheBytes() { - if (cacheByte == null) { - cacheByte = new byte[8]; - } - } - } \ No newline at end of file diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousSocketChannelProxy.java b/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousSocketChannelProxy.java index 6d50768494..ebe919af40 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousSocketChannelProxy.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousSocketChannelProxy.java @@ -39,22 +39,22 @@ * @author Kimi Liu * @since Java 17+ */ -public class AsynchronousSocketChannelProxy extends java.nio.channels.AsynchronousSocketChannel { +public class AsynchronousSocketChannelProxy extends AsynchronousSocketChannel { - protected final java.nio.channels.AsynchronousSocketChannel asynchronousSocketChannel; + protected final AsynchronousSocketChannel asynchronousSocketChannel; - public AsynchronousSocketChannelProxy(java.nio.channels.AsynchronousSocketChannel asynchronousSocketChannel) { + public AsynchronousSocketChannelProxy(AsynchronousSocketChannel asynchronousSocketChannel) { super(asynchronousSocketChannel.provider()); this.asynchronousSocketChannel = asynchronousSocketChannel; } @Override - public java.nio.channels.AsynchronousSocketChannel bind(SocketAddress local) throws IOException { + public AsynchronousSocketChannel bind(SocketAddress local) throws IOException { return asynchronousSocketChannel.bind(local); } @Override - public java.nio.channels.AsynchronousSocketChannel setOption(SocketOption name, T value) throws IOException { + public AsynchronousSocketChannel setOption(SocketOption name, T value) throws IOException { return asynchronousSocketChannel.setOption(name, value); } @@ -69,7 +69,7 @@ public Set> supportedOptions() { } @Override - public java.nio.channels.AsynchronousSocketChannel shutdownInput() throws IOException { + public AsynchronousSocketChannel shutdownInput() throws IOException { return asynchronousSocketChannel.shutdownInput(); } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousChannelGroup.java b/bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousChannelGroup.java similarity index 86% rename from bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousChannelGroup.java rename to bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousChannelGroup.java index 20cb52be6d..3b677a57c8 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousChannelGroup.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousChannelGroup.java @@ -25,9 +25,8 @@ ********************************************************************************/ package org.aoju.bus.socket.channel; -import org.aoju.bus.logger.Logger; - import java.io.IOException; +import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.spi.AsynchronousChannelProvider; @@ -40,7 +39,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class AsynchronousChannelGroup extends java.nio.channels.AsynchronousChannelGroup { +public class EnhanceAsynchronousChannelGroup extends AsynchronousChannelGroup { /** * 递归回调次数上限 @@ -97,14 +96,14 @@ public class AsynchronousChannelGroup extends java.nio.channels.AsynchronousChan * @param threadNum 线程数量 * @throws IOException 异常 */ - protected AsynchronousChannelGroup(AsynchronousChannelProvider provider, ExecutorService readExecutorService, int threadNum) throws IOException { + public EnhanceAsynchronousChannelGroup(AsynchronousChannelProvider provider, ExecutorService readExecutorService, int threadNum) throws IOException { super(provider); // init threadPool for read this.readExecutorService = readExecutorService; this.readWorkers = new Worker[threadNum]; for (int i = 0; i < threadNum; i++) { readWorkers[i] = new Worker(Selector.open(), selectionKey -> { - AsynchronousSocketChannel asynchronousSocketChannel = (AsynchronousSocketChannel) selectionKey.attachment(); + org.aoju.bus.socket.channel.EnhanceAsynchronousSocketChannel asynchronousSocketChannel = (EnhanceAsynchronousSocketChannel) selectionKey.attachment(); asynchronousSocketChannel.doRead(true); }); this.readExecutorService.execute(readWorkers[i]); @@ -118,26 +117,24 @@ protected AsynchronousChannelGroup(AsynchronousChannelProvider provider, Executo for (int i = 0; i < writeThreadNum; i++) { writeWorkers[i] = new Worker(Selector.open(), selectionKey -> { - AsynchronousSocketChannel asynchronousSocketChannel = (AsynchronousSocketChannel) selectionKey.attachment(); - if ((selectionKey.interestOps() & SelectionKey.OP_WRITE) > 0) { - asynchronousSocketChannel.doWrite(); - } else { - Logger.warn("ignore write"); - } + EnhanceAsynchronousSocketChannel asynchronousSocketChannel = (EnhanceAsynchronousSocketChannel) selectionKey.attachment(); + // 直接调用interestOps的效果比 removeOps(selectionKey, SelectionKey.OP_WRITE) 更好 + selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE); + asynchronousSocketChannel.doWrite(); }); writeExecutorService.execute(writeWorkers[i]); } - // init threadPool for accept + //init threadPool for accept acceptExecutorService = getSingleThreadExecutor("bus-socket:connect"); acceptWorkers = new Worker[acceptThreadNum]; for (int i = 0; i < acceptThreadNum; i++) { acceptWorkers[i] = new Worker(Selector.open(), selectionKey -> { if (selectionKey.isAcceptable()) { - AsynchronousServerSocketChannel serverSocketChannel = (AsynchronousServerSocketChannel) selectionKey.attachment(); + EnhanceAsynchronousServerSocketChannel serverSocketChannel = (EnhanceAsynchronousServerSocketChannel) selectionKey.attachment(); serverSocketChannel.doAccept(); } else if (selectionKey.isConnectable()) { - AsynchronousSocketChannel asynchronousSocketChannel = (AsynchronousSocketChannel) selectionKey.attachment(); + EnhanceAsynchronousSocketChannel asynchronousSocketChannel = (EnhanceAsynchronousSocketChannel) selectionKey.attachment(); asynchronousSocketChannel.doConnect(); } }); @@ -149,16 +146,12 @@ protected AsynchronousChannelGroup(AsynchronousChannelProvider provider, Executo /** * 同步IO注册异步线程,防止主IO线程阻塞 - * - * @param register 注册对象 - * @param opType 类型 - * @throws IOException 异常 */ public synchronized void registerFuture(Consumer register, int opType) throws IOException { if (futureWorker == null) { futureExecutorService = getSingleThreadExecutor("bus-socket:future"); futureWorker = new Worker(Selector.open(), selectionKey -> { - AsynchronousSocketChannel asynchronousSocketChannel = (AsynchronousSocketChannel) selectionKey.attachment(); + EnhanceAsynchronousSocketChannel asynchronousSocketChannel = (EnhanceAsynchronousSocketChannel) selectionKey.attachment(); switch (opType) { case SelectionKey.OP_READ: removeOps(selectionKey, SelectionKey.OP_READ); @@ -269,16 +262,16 @@ public void interestOps(Worker worker, SelectionKey selectionKey, int opt) { } } - class Worker implements Runnable { + public class Worker implements Runnable { /** * 当前Worker绑定的Selector */ - private final Selector selector; - private final Consumer consumer; - private final ConcurrentLinkedQueue> consumers = new ConcurrentLinkedQueue<>(); - int invoker = 0; - private Thread workerThread; + public final Selector selector; + public final Consumer consumer; + public final ConcurrentLinkedQueue> consumers = new ConcurrentLinkedQueue<>(); + public int invoker = 0; + public Thread workerThread; Worker(Selector selector, Consumer consumer) { this.selector = selector; @@ -288,7 +281,7 @@ class Worker implements Runnable { /** * 注册事件 */ - final void addRegister(Consumer register) { + public final void addRegister(Consumer register) { consumers.offer(register); selector.wakeup(); } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousChannelProvider.java b/bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousChannelProvider.java similarity index 56% rename from bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousChannelProvider.java rename to bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousChannelProvider.java index e167a68459..35f9b0dfcb 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousChannelProvider.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousChannelProvider.java @@ -25,46 +25,68 @@ ********************************************************************************/ package org.aoju.bus.socket.channel; -import org.aoju.bus.core.exception.InternalException; - import java.io.IOException; +import java.nio.channels.AsynchronousChannelGroup; +import java.nio.channels.AsynchronousServerSocketChannel; +import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.SocketChannel; +import java.nio.channels.spi.AsynchronousChannelProvider; import java.util.concurrent.*; /** * @author Kimi Liu * @since Java 17+ */ -public class AsynchronousChannelProvider extends java.nio.channels.spi.AsynchronousChannelProvider { +public final class EnhanceAsynchronousChannelProvider extends AsynchronousChannelProvider { + /** + * 读监听信号 + */ + public static final int READ_MONITOR_SIGNAL = -2; + /** + * 可读信号 + */ + public static final int READABLE_SIGNAL = -3; + /** + * 低内存模式 + */ + private final boolean lowMemory; + + public EnhanceAsynchronousChannelProvider(boolean lowMemory) { + this.lowMemory = lowMemory; + } + + public EnhanceAsynchronousChannelProvider() { + this(false); + } @Override - public java.nio.channels.AsynchronousChannelGroup openAsynchronousChannelGroup(int nThreads, ThreadFactory threadFactory) throws IOException { - return new AsynchronousChannelGroup(this, new ThreadPoolExecutor(nThreads, nThreads, + public AsynchronousChannelGroup openAsynchronousChannelGroup(int nThreads, ThreadFactory threadFactory) throws IOException { + return new EnhanceAsynchronousChannelGroup(this, new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(nThreads), threadFactory), nThreads); } @Override - public java.nio.channels.AsynchronousChannelGroup openAsynchronousChannelGroup(ExecutorService executor, int initialSize) throws IOException { - return new AsynchronousChannelGroup(this, executor, initialSize); + public AsynchronousChannelGroup openAsynchronousChannelGroup(ExecutorService executor, int initialSize) throws IOException { + return new EnhanceAsynchronousChannelGroup(this, executor, initialSize); } @Override - public java.nio.channels.AsynchronousServerSocketChannel openAsynchronousServerSocketChannel(java.nio.channels.AsynchronousChannelGroup group) throws IOException { - return new AsynchronousServerSocketChannel(checkAndGet(group)); + public AsynchronousServerSocketChannel openAsynchronousServerSocketChannel(AsynchronousChannelGroup group) throws IOException { + return new EnhanceAsynchronousServerSocketChannel(checkAndGet(group), lowMemory); } @Override - public java.nio.channels.AsynchronousSocketChannel openAsynchronousSocketChannel(java.nio.channels.AsynchronousChannelGroup group) throws IOException { - return new AsynchronousSocketChannel(checkAndGet(group), SocketChannel.open()); + public AsynchronousSocketChannel openAsynchronousSocketChannel(AsynchronousChannelGroup group) throws IOException { + return new EnhanceAsynchronousSocketChannel(checkAndGet(group), SocketChannel.open(), lowMemory); } - private AsynchronousChannelGroup checkAndGet(java.nio.channels.AsynchronousChannelGroup group) { - if (!(group instanceof AsynchronousChannelGroup)) { - throw new InternalException("invalid class"); + private EnhanceAsynchronousChannelGroup checkAndGet(AsynchronousChannelGroup group) { + if (!(group instanceof EnhanceAsynchronousChannelGroup)) { + throw new RuntimeException("invalid class"); } - return (AsynchronousChannelGroup) group; + return (EnhanceAsynchronousChannelGroup) group; } } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousServerSocketChannel.java b/bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousServerSocketChannel.java similarity index 65% rename from bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousServerSocketChannel.java rename to bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousServerSocketChannel.java index 478b2f2379..ac06c97082 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousServerSocketChannel.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousServerSocketChannel.java @@ -38,33 +38,34 @@ * @author Kimi Liu * @since Java 17+ */ -public class AsynchronousServerSocketChannel extends java.nio.channels.AsynchronousServerSocketChannel { - +public final class EnhanceAsynchronousServerSocketChannel extends AsynchronousServerSocketChannel { private final ServerSocketChannel serverSocketChannel; - private final AsynchronousChannelGroup asynchronousChannelGroup; - private final AsynchronousChannelGroup.Worker acceptWorker; - private CompletionHandler acceptCompletionHandler; - private FutureCompletionHandler acceptFuture; + private final EnhanceAsynchronousChannelGroup enhanceAsynchronousChannelGroup; + private final EnhanceAsynchronousChannelGroup.Worker acceptWorker; + private final boolean lowMemory; + private CompletionHandler acceptCompletionHandler; + private FutureCompletionHandler acceptFuture; private Object attachment; private SelectionKey selectionKey; private boolean acceptPending; - protected AsynchronousServerSocketChannel(AsynchronousChannelGroup asynchronousChannelGroup) throws IOException { - super(asynchronousChannelGroup.provider()); - this.asynchronousChannelGroup = asynchronousChannelGroup; + EnhanceAsynchronousServerSocketChannel(EnhanceAsynchronousChannelGroup enhanceAsynchronousChannelGroup, boolean lowMemory) throws IOException { + super(enhanceAsynchronousChannelGroup.provider()); + this.enhanceAsynchronousChannelGroup = enhanceAsynchronousChannelGroup; serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); - acceptWorker = asynchronousChannelGroup.getAcceptWorker(); + acceptWorker = enhanceAsynchronousChannelGroup.getAcceptWorker(); + this.lowMemory = lowMemory; } @Override - public java.nio.channels.AsynchronousServerSocketChannel bind(SocketAddress local, int backlog) throws IOException { + public AsynchronousServerSocketChannel bind(SocketAddress local, int backlog) throws IOException { serverSocketChannel.bind(local, backlog); return this; } @Override - public java.nio.channels.AsynchronousServerSocketChannel setOption(SocketOption name, T value) throws IOException { + public AsynchronousServerSocketChannel setOption(SocketOption name, T value) throws IOException { serverSocketChannel.setOption(name, value); return this; } @@ -80,52 +81,54 @@ public Set> supportedOptions() { } @Override - public
void accept(A attachment, CompletionHandler handler) { + public void accept(A attachment, CompletionHandler handler) { if (acceptPending) { throw new AcceptPendingException(); } acceptPending = true; - this.acceptCompletionHandler = (CompletionHandler) handler; + this.acceptCompletionHandler = (CompletionHandler) handler; this.attachment = attachment; doAccept(); } public void doAccept() { try { - // 此前通过Future调用,且触发了cancel - if (null != acceptFuture && acceptFuture.isDone()) { + //此前通过Future调用,且触发了cancel + if (acceptFuture != null && acceptFuture.isDone()) { resetAccept(); - asynchronousChannelGroup.removeOps(selectionKey, SelectionKey.OP_ACCEPT); + enhanceAsynchronousChannelGroup.removeOps(selectionKey, SelectionKey.OP_ACCEPT); return; } boolean directAccept = (acceptWorker.getWorkerThread() == Thread.currentThread() - && acceptWorker.invoker++ < AsynchronousChannelGroup.MAX_INVOKER); + && acceptWorker.invoker++ < EnhanceAsynchronousChannelGroup.MAX_INVOKER); SocketChannel socketChannel = null; if (directAccept) { socketChannel = serverSocketChannel.accept(); } - if (null != socketChannel) { - AsynchronousSocketChannel asynchronousSocketChannel = new AsynchronousSocketChannel(asynchronousChannelGroup, socketChannel); + if (socketChannel != null) { + EnhanceAsynchronousSocketChannel asynchronousSocketChannel = new EnhanceAsynchronousSocketChannel(enhanceAsynchronousChannelGroup, socketChannel, lowMemory); + socketChannel.configureBlocking(false); socketChannel.finishConnect(); - CompletionHandler completionHandler = acceptCompletionHandler; + CompletionHandler completionHandler = acceptCompletionHandler; Object attach = attachment; resetAccept(); completionHandler.completed(asynchronousSocketChannel, attach); - if (!acceptPending && null != selectionKey) { - asynchronousChannelGroup.removeOps(selectionKey, SelectionKey.OP_ACCEPT); + if (!acceptPending && selectionKey != null) { + enhanceAsynchronousChannelGroup.removeOps(selectionKey, SelectionKey.OP_ACCEPT); } } - // 首次注册selector - else if (null == selectionKey) { + //首次注册selector + else if (selectionKey == null) { acceptWorker.addRegister(selector -> { try { - selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); + selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, EnhanceAsynchronousServerSocketChannel.this); +// selectionKey.attach(EnhanceAsynchronousServerSocketChannel.this); } catch (ClosedChannelException e) { acceptCompletionHandler.failed(e, attachment); } }); } else { - asynchronousChannelGroup.interestOps(acceptWorker, selectionKey, SelectionKey.OP_ACCEPT); + enhanceAsynchronousChannelGroup.interestOps(acceptWorker, selectionKey, SelectionKey.OP_ACCEPT); } } catch (IOException e) { this.acceptCompletionHandler.failed(e, attachment); @@ -141,8 +144,8 @@ private void resetAccept() { } @Override - public Future accept() { - FutureCompletionHandler acceptFuture = new FutureCompletionHandler<>(); + public Future accept() { + FutureCompletionHandler acceptFuture = new FutureCompletionHandler<>(); accept(null, acceptFuture); this.acceptFuture = acceptFuture; return acceptFuture; @@ -162,5 +165,4 @@ public boolean isOpen() { public void close() throws IOException { serverSocketChannel.close(); } - } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousSocketChannel.java b/bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousSocketChannel.java similarity index 81% rename from bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousSocketChannel.java rename to bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousSocketChannel.java index 13eefb3be7..10f6b0ff37 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/channel/AsynchronousSocketChannel.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/channel/EnhanceAsynchronousSocketChannel.java @@ -25,6 +25,7 @@ ********************************************************************************/ package org.aoju.bus.socket.channel; +import org.aoju.bus.socket.buffers.BufferArray; import org.aoju.bus.socket.handler.FutureCompletionHandler; import java.io.IOException; @@ -42,8 +43,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class AsynchronousSocketChannel extends java.nio.channels.AsynchronousSocketChannel { - +public final class EnhanceAsynchronousSocketChannel extends AsynchronousSocketChannel { /** * 实际的Socket通道 */ @@ -51,19 +51,19 @@ public class AsynchronousSocketChannel extends java.nio.channels.AsynchronousSoc /** * 处理当前连接IO事件的资源组 */ - private final AsynchronousChannelGroup group; + private final EnhanceAsynchronousChannelGroup group; /** * 处理 read 事件的线程资源 */ - private final AsynchronousChannelGroup.Worker readWorker; + private final EnhanceAsynchronousChannelGroup.Worker readWorker; /** * 处理 write 事件的线程资源 */ - private final AsynchronousChannelGroup.Worker writeWorker; + private final EnhanceAsynchronousChannelGroup.Worker writeWorker; /** * 处理 connect 事件的线程资源 */ - private final AsynchronousChannelGroup.Worker connectWorker; + private final EnhanceAsynchronousChannelGroup.Worker connectWorker; /** * 用于接收 read 通道数据的缓冲区,经解码后腾出缓冲区以供下一批数据的读取 */ @@ -71,7 +71,7 @@ public class AsynchronousSocketChannel extends java.nio.channels.AsynchronousSoc /** * 用于接收 read 通道数据的缓冲区集合 */ - private ByteBufferArray scatteringReadBuffer; + private BufferArray scatteringReadBuffer; /** * 存放待输出数据的缓冲区 */ @@ -79,7 +79,7 @@ public class AsynchronousSocketChannel extends java.nio.channels.AsynchronousSoc /** * 存放待输出数据的缓冲区集合 */ - private ByteBufferArray gatheringWriteBuffer; + private BufferArray gatheringWriteBuffer; /** * read 回调事件处理器 */ @@ -110,6 +110,7 @@ public class AsynchronousSocketChannel extends java.nio.channels.AsynchronousSoc private SelectionKey readSelectionKey; private SelectionKey readFutureSelectionKey; private SelectionKey writeSelectionKey; + private SelectionKey writeFutureSelectionKey; private SelectionKey connectSelectionKey; /** * 当前是否正在执行 write 操作 @@ -127,15 +128,18 @@ public class AsynchronousSocketChannel extends java.nio.channels.AsynchronousSoc * 远程连接的地址 */ private SocketAddress remote; + private int writeInvoker; + + private boolean lowMemory; - public AsynchronousSocketChannel(AsynchronousChannelGroup group, SocketChannel channel) throws IOException { + public EnhanceAsynchronousSocketChannel(EnhanceAsynchronousChannelGroup group, SocketChannel channel, boolean lowMemory) throws IOException { super(group.provider()); this.group = group; this.channel = channel; readWorker = group.getReadWorker(); writeWorker = group.getWriteWorker(); connectWorker = group.getConnectWorker(); - channel.configureBlocking(false); + this.lowMemory = lowMemory; } @Override @@ -160,6 +164,10 @@ public void close() throws IOException { writeSelectionKey.cancel(); writeSelectionKey = null; } + if (writeFutureSelectionKey != null) { + writeFutureSelectionKey.cancel(); + writeFutureSelectionKey = null; + } if (connectSelectionKey != null) { connectSelectionKey.cancel(); connectSelectionKey = null; @@ -170,13 +178,13 @@ public void close() throws IOException { } @Override - public java.nio.channels.AsynchronousSocketChannel bind(SocketAddress local) throws IOException { + public AsynchronousSocketChannel bind(SocketAddress local) throws IOException { channel.bind(local); return this; } @Override - public java.nio.channels.AsynchronousSocketChannel setOption(SocketOption name, T value) throws IOException { + public AsynchronousSocketChannel setOption(SocketOption name, T value) throws IOException { channel.setOption(name, value); return this; } @@ -192,13 +200,13 @@ public Set> supportedOptions() { } @Override - public java.nio.channels.AsynchronousSocketChannel shutdownInput() throws IOException { + public AsynchronousSocketChannel shutdownInput() throws IOException { channel.shutdownInput(); return this; } @Override - public java.nio.channels.AsynchronousSocketChannel shutdownOutput() throws IOException { + public AsynchronousSocketChannel shutdownOutput() throws IOException { channel.shutdownOutput(); return this; } @@ -239,7 +247,7 @@ public void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, read0(dst, null, timeout, unit, attachment, handler); } - private void read0(ByteBuffer readBuffer, ByteBufferArray scattering, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) { + private void read0(ByteBuffer readBuffer, BufferArray scattering, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) { if (readPending) { throw new ReadPendingException(); } @@ -267,7 +275,7 @@ public Future read(ByteBuffer readBuffer) { @Override public void read(ByteBuffer[] dsts, int offset, int length, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) { - read0(null, new ByteBufferArray(dsts, offset, length), timeout, unit, attachment, handler); + read0(null, new BufferArray(dsts, offset, length), timeout, unit, attachment, handler); } @Override @@ -275,7 +283,7 @@ public void write(ByteBuffer src, long timeout, TimeUnit unit, A attachment, write0(src, null, timeout, unit, attachment, handler); } - private void write0(ByteBuffer writeBuffer, ByteBufferArray gathering, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) { + private void write0(ByteBuffer writeBuffer, BufferArray gathering, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) { if (writePending) { throw new WritePendingException(); } @@ -304,7 +312,7 @@ public Future write(ByteBuffer src) { @Override public void write(ByteBuffer[] srcs, int offset, int length, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) { - write0(null, new ByteBufferArray(srcs, offset, length), timeout, unit, attachment, handler); + write0(null, new BufferArray(srcs, offset, length), timeout, unit, attachment, handler); } @Override @@ -323,6 +331,7 @@ public void doConnect() { if (connected || channel.connect(remote)) { connected = channel.finishConnect(); } + channel.configureBlocking(false); if (connected) { CompletionHandler completionHandler = connectCompletionHandler; Object attach = connectAttachment; @@ -331,7 +340,7 @@ public void doConnect() { } else if (connectSelectionKey == null) { connectWorker.addRegister(selector -> { try { - connectSelectionKey = channel.register(selector, SelectionKey.OP_CONNECT); + connectSelectionKey = channel.register(selector, SelectionKey.OP_CONNECT, EnhanceAsynchronousSocketChannel.this); } catch (ClosedChannelException e) { connectCompletionHandler.failed(e, connectAttachment); } @@ -354,14 +363,20 @@ private void resetConnect() { public void doRead(boolean direct) { try { - //此前通过Future调用,且触发了cancel + // 此前通过Future调用,且触发了cancel if (readFuture != null && readFuture.isDone()) { group.removeOps(readSelectionKey, SelectionKey.OP_READ); resetRead(); return; } - boolean isReadWorkThread = Thread.currentThread() == readWorker.getWorkerThread(); - boolean directRead = direct || (isReadWorkThread && readWorker.invoker++ < AsynchronousChannelGroup.MAX_INVOKER); + if (lowMemory && direct && readBuffer == null) { + CompletionHandler completionHandler = readCompletionHandler; + Object attach = readAttachment; + resetRead(); + completionHandler.completed(EnhanceAsynchronousChannelProvider.READABLE_SIGNAL, attach); + return; + } + boolean directRead = direct || (Thread.currentThread() == readWorker.getWorkerThread() && readWorker.invoker++ < EnhanceAsynchronousChannelGroup.MAX_INVOKER); long readSize = 0; boolean hasRemain = true; @@ -373,18 +388,14 @@ public void doRead(boolean direct) { readSize = channel.read(readBuffer); hasRemain = readBuffer.hasRemaining(); } - //The read buffer is not full, there may be no readable data - if (hasRemain && isReadWorkThread) { - readWorker.invoker = AsynchronousChannelGroup.MAX_INVOKER; - } } - //注册至异步线程 + // 注册至异步线程 if (readFuture != null && readSize == 0) { group.removeOps(readSelectionKey, SelectionKey.OP_READ); group.registerFuture(selector -> { try { - readFutureSelectionKey = channel.register(selector, SelectionKey.OP_READ); + readFutureSelectionKey = channel.register(selector, SelectionKey.OP_READ, EnhanceAsynchronousSocketChannel.this); } catch (ClosedChannelException e) { e.printStackTrace(); doRead(true); @@ -392,11 +403,16 @@ public void doRead(boolean direct) { }, SelectionKey.OP_READ); return; } + // 释放内存 + if (lowMemory && readSize == 0 && readBuffer.position() == 0) { + readBuffer = null; + readCompletionHandler.completed(EnhanceAsynchronousChannelProvider.READ_MONITOR_SIGNAL, readAttachment); + } if (readSize != 0 || !hasRemain) { CompletionHandler completionHandler = readCompletionHandler; Object attach = readAttachment; - ByteBufferArray scattering = scatteringReadBuffer; + BufferArray scattering = scatteringReadBuffer; resetRead(); if (scattering == null) { completionHandler.completed((int) readSize, attach); @@ -410,7 +426,7 @@ public void doRead(boolean direct) { } else if (readSelectionKey == null) { readWorker.addRegister(selector -> { try { - readSelectionKey = channel.register(selector, SelectionKey.OP_READ); + readSelectionKey = channel.register(selector, SelectionKey.OP_READ, EnhanceAsynchronousSocketChannel.this); } catch (ClosedChannelException e) { readCompletionHandler.failed(e, readAttachment); } @@ -449,35 +465,33 @@ public void doWrite() { resetWrite(); return; } - boolean directWrite; - boolean isWriteWorkThread = Thread.currentThread() == writeWorker.getWorkerThread(); - if (isWriteWorkThread && writeFuture != null) { - directWrite = writeWorker.invoker++ < AsynchronousChannelGroup.MAX_INVOKER; - } else { - directWrite = true; + int invoker = 0; + // 防止无限递归导致堆栈溢出 + if (writeWorker.getWorkerThread() == Thread.currentThread()) { + invoker = ++writeWorker.invoker; + } else if (readWorker.getWorkerThread() != Thread.currentThread()) { + invoker = ++writeInvoker; } - long writeSize = 0; + int writeSize = 0; boolean hasRemain = true; - if (directWrite) { + if (invoker < EnhanceAsynchronousChannelGroup.MAX_INVOKER) { if (gatheringWriteBuffer != null) { - writeSize = channel.write(gatheringWriteBuffer.getBuffers(), gatheringWriteBuffer.getOffset(), gatheringWriteBuffer.getLength()); + writeSize = (int) channel.write(gatheringWriteBuffer.getBuffers(), gatheringWriteBuffer.getOffset(), gatheringWriteBuffer.getLength()); hasRemain = hasRemaining(gatheringWriteBuffer); } else { writeSize = channel.write(writeBuffer); hasRemain = writeBuffer.hasRemaining(); } - // The write buffer has not been emptied, there may be remaining data cannot be output - if (isWriteWorkThread && hasRemain) { - writeWorker.invoker = AsynchronousChannelGroup.MAX_INVOKER; - } + } else { + writeInvoker = 0; } - // 注册至异步线程 + //注册至异步线程 if (writeFuture != null && writeSize == 0) { group.removeOps(writeSelectionKey, SelectionKey.OP_WRITE); group.registerFuture(selector -> { try { - SelectionKey readSelectionKey = channel.register(selector, SelectionKey.OP_WRITE); + writeFutureSelectionKey = channel.register(selector, SelectionKey.OP_WRITE, EnhanceAsynchronousSocketChannel.this); } catch (ClosedChannelException e) { e.printStackTrace(); doWrite(); @@ -489,20 +503,12 @@ public void doWrite() { if (writeSize != 0 || !hasRemain) { CompletionHandler completionHandler = writeCompletionHandler; Object attach = writeAttachment; - ByteBufferArray scattering = gatheringWriteBuffer; resetWrite(); - if (scattering == null) { - completionHandler.completed((int) writeSize, attach); - } else { - completionHandler.completed(writeSize, attach); - } - if (!writePending && writeSelectionKey != null) { - group.removeOps(writeSelectionKey, SelectionKey.OP_WRITE); - } + completionHandler.completed(writeSize, attach); } else if (writeSelectionKey == null) { writeWorker.addRegister(selector -> { try { - writeSelectionKey = channel.register(selector, SelectionKey.OP_WRITE); + writeSelectionKey = channel.register(selector, SelectionKey.OP_WRITE, EnhanceAsynchronousSocketChannel.this); } catch (ClosedChannelException e) { writeCompletionHandler.failed(e, writeAttachment); } @@ -524,7 +530,7 @@ public void doWrite() { } } - private boolean hasRemaining(ByteBufferArray scattering) { + private boolean hasRemaining(BufferArray scattering) { for (int i = 0; i < scattering.getLength(); i++) { if (scattering.getBuffers()[scattering.getOffset() + i].hasRemaining()) { return true; @@ -547,30 +553,4 @@ public boolean isOpen() { return channel.isOpen(); } - final class ByteBufferArray { - - private final ByteBuffer[] buffers; - private final int offset; - private final int length; - - public ByteBufferArray(ByteBuffer[] buffers, int offset, int length) { - this.buffers = buffers; - this.offset = offset; - this.length = length; - } - - public ByteBuffer[] getBuffers() { - return buffers; - } - - public int getOffset() { - return offset; - } - - public int getLength() { - return length; - } - - } - } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/security/SslSocketChannel.java b/bus-socket/src/main/java/org/aoju/bus/socket/channel/SslAsynchronousSocketChannel.java similarity index 93% rename from bus-socket/src/main/java/org/aoju/bus/socket/security/SslSocketChannel.java rename to bus-socket/src/main/java/org/aoju/bus/socket/channel/SslAsynchronousSocketChannel.java index ac77102e66..59028bdd7a 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/security/SslSocketChannel.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/channel/SslAsynchronousSocketChannel.java @@ -23,12 +23,13 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.socket.security; +package org.aoju.bus.socket.channel; -import org.aoju.bus.core.io.buffer.PageBuffer; -import org.aoju.bus.core.io.buffer.VirtualBuffer; import org.aoju.bus.logger.Logger; -import org.aoju.bus.socket.channel.AsynchronousSocketChannelProxy; +import org.aoju.bus.socket.buffers.BufferPage; +import org.aoju.bus.socket.buffers.VirtualBuffer; +import org.aoju.bus.socket.security.HandshakeModel; +import org.aoju.bus.socket.security.SslService; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; @@ -44,7 +45,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class SslSocketChannel extends AsynchronousSocketChannelProxy { +public class SslAsynchronousSocketChannel extends AsynchronousSocketChannelProxy { private final VirtualBuffer netWriteBuffer; private final VirtualBuffer netReadBuffer; @@ -64,9 +65,9 @@ public class SslSocketChannel extends AsynchronousSocketChannelProxy { */ private int adaptiveWriteSize = -1; - public SslSocketChannel(AsynchronousSocketChannel asynchronousSocketChannel, SslService sslService, PageBuffer pageBuffer) { + public SslAsynchronousSocketChannel(AsynchronousSocketChannel asynchronousSocketChannel, SslService sslService, BufferPage bufferPage) { super(asynchronousSocketChannel); - this.handshakeModel = sslService.createSSLEngine(asynchronousSocketChannel, pageBuffer); + this.handshakeModel = sslService.createSSLEngine(asynchronousSocketChannel, bufferPage); this.sslService = sslService; this.sslEngine = handshakeModel.getSslEngine(); this.netWriteBuffer = handshakeModel.getNetWriteBuffer(); @@ -79,18 +80,18 @@ public void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, if (handshake) { handshakeModel.setHandshakeCallback(() -> { handshake = false; - synchronized (SslSocketChannel.this) { + synchronized (SslAsynchronousSocketChannel.this) { //释放内存 handshakeModel.getAppWriteBuffer().clean(); netReadBuffer.buffer().clear(); netWriteBuffer.buffer().clear(); appReadBuffer.buffer().clear().flip(); - SslSocketChannel.this.notifyAll(); + SslAsynchronousSocketChannel.this.notifyAll(); } if (handshakeModel.isEof()) { handler.completed(-1, attachment); } else { - SslSocketChannel.this.read(dst, timeout, unit, attachment, handler); + SslAsynchronousSocketChannel.this.read(dst, timeout, unit, attachment, handler); } handshakeModel = null; }); @@ -257,6 +258,7 @@ private void doWrap(ByteBuffer writeBuffer) { netBuffer.clear(); writeBuffer.limit(writeBuffer.position() + ((writeBuffer.limit() - writeBuffer.position() >> 1))); adaptiveWriteSize = writeBuffer.remaining(); +// logger.info("doWrap BUFFER_OVERFLOW maybeSize:{}", maybeWriteSize); break; case BUFFER_UNDERFLOW: Logger.info("doWrap BUFFER_UNDERFLOW"); @@ -283,11 +285,6 @@ public void write(ByteBuffer[] srcs, int offset, int length, long timeout, T throw new UnsupportedOperationException(); } - @Override - public boolean isOpen() { - return asynchronousSocketChannel.isOpen(); - } - @Override public void close() throws IOException { netWriteBuffer.clean(); diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/convert/DelimiterFrameDecoder.java b/bus-socket/src/main/java/org/aoju/bus/socket/convert/DelimiterFrameDecoder.java index a11c7932ba..7c73c41aab 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/convert/DelimiterFrameDecoder.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/convert/DelimiterFrameDecoder.java @@ -25,8 +25,6 @@ ********************************************************************************/ package org.aoju.bus.socket.convert; -import org.aoju.bus.socket.SocketDecoder; - import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -39,14 +37,11 @@ */ public class DelimiterFrameDecoder implements SocketDecoder { + private final int reposition; /** * 存储已解析的数据 */ private final List bufferList; - /** - * 位置信息 - */ - private final int reposition; /** * 消息结束标志 */ @@ -65,6 +60,12 @@ public class DelimiterFrameDecoder implements SocketDecoder { private int position; public DelimiterFrameDecoder(byte[] endFLag, int unitBufferSize) { + if (endFLag == null || endFLag.length == 0) { + throw new IllegalArgumentException("endFLag cannot be empty"); + } + if (unitBufferSize < 1) { + throw new IllegalArgumentException("unitBufferSize Must be greater than 1"); + } this.endFLag = endFLag; int p = 0; for (int i = 1; i < endFLag.length; i++) { diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/convert/FixedLengthDecoder.java b/bus-socket/src/main/java/org/aoju/bus/socket/convert/FixedLengthFrameDecoder.java similarity index 94% rename from bus-socket/src/main/java/org/aoju/bus/socket/convert/FixedLengthDecoder.java rename to bus-socket/src/main/java/org/aoju/bus/socket/convert/FixedLengthFrameDecoder.java index 2f535035e6..d07eef7c05 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/convert/FixedLengthDecoder.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/convert/FixedLengthFrameDecoder.java @@ -25,8 +25,6 @@ ********************************************************************************/ package org.aoju.bus.socket.convert; -import org.aoju.bus.socket.SocketDecoder; - import java.nio.ByteBuffer; /** @@ -35,12 +33,12 @@ * @author Kimi Liu * @since Java 17+ */ -public class FixedLengthDecoder implements SocketDecoder { +public class FixedLengthFrameDecoder implements SocketDecoder { - private final ByteBuffer buffer; + private ByteBuffer buffer; private boolean finishRead; - public FixedLengthDecoder(int frameLength) { + public FixedLengthFrameDecoder(int frameLength) { if (frameLength <= 0) { throw new IllegalArgumentException("frameLength must be a positive integer: " + frameLength); } else { diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/SocketDecoder.java b/bus-socket/src/main/java/org/aoju/bus/socket/convert/SocketDecoder.java similarity index 94% rename from bus-socket/src/main/java/org/aoju/bus/socket/SocketDecoder.java rename to bus-socket/src/main/java/org/aoju/bus/socket/convert/SocketDecoder.java index 99e5f1134e..3ace0ce036 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/SocketDecoder.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/convert/SocketDecoder.java @@ -23,7 +23,7 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.socket; +package org.aoju.bus.socket.convert; import java.nio.ByteBuffer; @@ -36,15 +36,15 @@ public interface SocketDecoder { /** * 解码算法 * - * @param byteBuffer 缓冲信息 - * @return the true/false + * @param byteBuffer + * @return */ boolean decode(ByteBuffer byteBuffer); /** * 获取本次解析到的完整数据 * - * @return the {@link ByteBuffer} + * @return */ ByteBuffer getBuffer(); diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionAcceptHandler.java b/bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionAcceptHandler.java deleted file mode 100644 index 222b545df6..0000000000 --- a/bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionAcceptHandler.java +++ /dev/null @@ -1,83 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.socket.handler; - -import org.aoju.bus.core.exception.InternalException; -import org.aoju.bus.logger.Logger; -import org.aoju.bus.socket.NioQuickServer; - -import java.io.IOException; -import java.nio.channels.*; - -/** - * 接入完成回调,单例使用 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class CompletionAcceptHandler implements CompletionHandler { - - /** - * 注册通道的指定操作到指定Selector上 - * - * @param selector Selector - * @param channel 通道 - * @param ops 注册的通道监听(操作)类型 - */ - public static void registerChannel(Selector selector, SelectableChannel channel, int ops) { - try { - if (null == channel) { - return; - } - channel.configureBlocking(false); - // 注册通道 - channel.register(selector, ops); - } catch (IOException e) { - throw new InternalException(e); - } - } - - @Override - public void completed(ServerSocketChannel serverSocketChannel, NioQuickServer nioQuickServer) { - SocketChannel socketChannel; - try { - // 获取连接到此服务器的客户端通道 - socketChannel = serverSocketChannel.accept(); - Logger.debug("Client [{}] accepted.", socketChannel.getRemoteAddress()); - } catch (IOException e) { - throw new InternalException(e); - } - - // SocketChannel通道的可读事件注册到Selector中 - registerChannel(nioQuickServer.getSelector(), socketChannel, SelectionKey.OP_READ); - } - - @Override - public void failed(Throwable exc, NioQuickServer nioQuickServer) { - Logger.error(exc); - } - -} diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/handler/ConcurrentReadHandler.java b/bus-socket/src/main/java/org/aoju/bus/socket/handler/ConcurrentReadCompletionHandler.java similarity index 87% rename from bus-socket/src/main/java/org/aoju/bus/socket/handler/ConcurrentReadHandler.java rename to bus-socket/src/main/java/org/aoju/bus/socket/handler/ConcurrentReadCompletionHandler.java index de78c50ecf..fd4282232e 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/handler/ConcurrentReadHandler.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/handler/ConcurrentReadCompletionHandler.java @@ -36,23 +36,22 @@ * @author Kimi Liu * @since Java 17+ */ -public class ConcurrentReadHandler extends CompletionReadHandler { +public class ConcurrentReadCompletionHandler extends ReadCompletionHandler { /** * 读回调资源信号量 */ private final Semaphore semaphore; - private final ThreadLocal threadLocal = new ThreadLocal<>(); + private final ThreadLocal threadLocal = new ThreadLocal<>(); private final ThreadPoolExecutor threadPoolExecutor; - public ConcurrentReadHandler(final Semaphore semaphore, ThreadPoolExecutor threadPoolExecutor) { + public ConcurrentReadCompletionHandler(final Semaphore semaphore, ThreadPoolExecutor threadPoolExecutor) { this.semaphore = semaphore; this.threadPoolExecutor = threadPoolExecutor; } - @Override public void completed(final Integer result, final TcpAioSession aioSession) { if (threadLocal.get() != null) { @@ -71,9 +70,8 @@ public void completed(final Integer result, final TcpAioSession aioSession) { threadLocal.set(null); return; } - //线程资源不足,暂时积压任务 - threadPoolExecutor.execute(() -> ConcurrentReadHandler.super.completed(result, aioSession)); - + // 线程资源不足,暂时积压任务 + threadPoolExecutor.execute(() -> ConcurrentReadCompletionHandler.super.completed(result, aioSession)); } } \ No newline at end of file diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/handler/FutureCompletionHandler.java b/bus-socket/src/main/java/org/aoju/bus/socket/handler/FutureCompletionHandler.java index d26c510f7a..194a29a366 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/handler/FutureCompletionHandler.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/handler/FutureCompletionHandler.java @@ -37,7 +37,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class FutureCompletionHandler implements CompletionHandler, Future, Runnable { +public final class FutureCompletionHandler implements CompletionHandler, Future, Runnable { private CompletionHandler completionHandler; private A attach; @@ -61,7 +61,7 @@ public void completed(V result, A selectionKey) { synchronized (this) { this.notify(); } - if (null != completionHandler) { + if (completionHandler != null) { completionHandler.completed(result, attach); } } @@ -70,7 +70,7 @@ public void completed(V result, A selectionKey) { public void failed(Throwable exc, A attachment) { exception = exc; done = true; - if (null != completionHandler) { + if (completionHandler != null) { completionHandler.failed(exc, attachment); } } @@ -101,7 +101,7 @@ public boolean isDone() { @Override public synchronized V get() throws InterruptedException, ExecutionException { if (done) { - if (null != exception) { + if (exception != null) { throw new ExecutionException(exception); } return result; diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionReadHandler.java b/bus-socket/src/main/java/org/aoju/bus/socket/handler/ReadCompletionHandler.java similarity index 83% rename from bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionReadHandler.java rename to bus-socket/src/main/java/org/aoju/bus/socket/handler/ReadCompletionHandler.java index 37dfad6e5b..d381a40f98 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionReadHandler.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/handler/ReadCompletionHandler.java @@ -28,6 +28,7 @@ import org.aoju.bus.socket.NetMonitor; import org.aoju.bus.socket.SocketStatus; import org.aoju.bus.socket.TcpAioSession; +import org.aoju.bus.socket.channel.EnhanceAsynchronousChannelProvider; import java.nio.channels.CompletionHandler; @@ -37,7 +38,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class CompletionReadHandler implements CompletionHandler> { +public class ReadCompletionHandler implements CompletionHandler { /** * 处理消息读回调事件 @@ -46,15 +47,23 @@ public class CompletionReadHandler implements CompletionHandler aioSession) { + public void completed(final Integer result, final TcpAioSession aioSession) { try { + // 释放缓冲区 + if (result == EnhanceAsynchronousChannelProvider.READ_MONITOR_SIGNAL) { + aioSession.suspendRead(); + return; + } + if (result == EnhanceAsynchronousChannelProvider.READABLE_SIGNAL) { + aioSession.doRead(); + return; + } // 接收到的消息进行预处理 NetMonitor monitor = aioSession.getServerConfig().getMonitor(); - if (null != monitor) { + if (monitor != null) { monitor.afterRead(aioSession, result); } // 触发读回调 - //触发读回调 aioSession.flipRead(result == -1); aioSession.signalRead(); } catch (Exception e) { @@ -63,7 +72,7 @@ public void completed(final Integer result, final TcpAioSession aioSession) { } @Override - public final void failed(Throwable exc, TcpAioSession aioSession) { + public final void failed(Throwable exc, TcpAioSession aioSession) { try { aioSession.getServerConfig().getProcessor().stateEvent(aioSession, SocketStatus.INPUT_EXCEPTION, exc); } catch (Exception e) { @@ -71,6 +80,7 @@ public final void failed(Throwable exc, TcpAioSession aioSession) { } try { // 兼容性处理,windows要强制关闭,其他系统优雅关闭 + // aioSession.close(IoKit.OS_WINDOWS); aioSession.close(false); } catch (Exception e) { e.printStackTrace(); diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionWriteHandler.java b/bus-socket/src/main/java/org/aoju/bus/socket/handler/WriteCompletionHandler.java similarity index 93% rename from bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionWriteHandler.java rename to bus-socket/src/main/java/org/aoju/bus/socket/handler/WriteCompletionHandler.java index 20afc9200e..39158d673a 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/handler/CompletionWriteHandler.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/handler/WriteCompletionHandler.java @@ -37,13 +37,13 @@ * @author Kimi Liu * @since Java 17+ */ -public class CompletionWriteHandler implements CompletionHandler> { +public class WriteCompletionHandler implements CompletionHandler { @Override - public void completed(final Integer result, final TcpAioSession aioSession) { + public void completed(final Integer result, final TcpAioSession aioSession) { try { NetMonitor monitor = aioSession.getServerConfig().getMonitor(); - if (null != monitor) { + if (monitor != null) { monitor.afterWrite(aioSession, result); } aioSession.writeCompleted(); @@ -52,9 +52,8 @@ public void completed(final Integer result, final TcpAioSession aioSession) { } } - @Override - public void failed(Throwable exc, TcpAioSession aioSession) { + public void failed(Throwable exc, TcpAioSession aioSession) { try { aioSession.getServerConfig().getProcessor().stateEvent(aioSession, SocketStatus.OUTPUT_EXCEPTION, exc); } catch (Exception e) { diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/BlackListPlugin.java b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/BlackListPlugin.java index 79ac280637..d0a07b95c2 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/BlackListPlugin.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/BlackListPlugin.java @@ -38,9 +38,9 @@ * @author Kimi Liu * @since Java 17+ */ -public class BlackListPlugin extends AbstractPlugin { +public final class BlackListPlugin extends AbstractPlugin { - private final ConcurrentLinkedQueue ipBlackList = new ConcurrentLinkedQueue<>(); + private ConcurrentLinkedQueue ipBlackList = new ConcurrentLinkedQueue<>(); @Override public AsynchronousSocketChannel shouldAccept(AsynchronousSocketChannel channel) { diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/PageBufferPlugin.java b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/BufferPageMonitorPlugin.java similarity index 82% rename from bus-socket/src/main/java/org/aoju/bus/socket/plugins/PageBufferPlugin.java rename to bus-socket/src/main/java/org/aoju/bus/socket/plugins/BufferPageMonitorPlugin.java index 2d9d12df5e..dcdca3592d 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/PageBufferPlugin.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/BufferPageMonitorPlugin.java @@ -25,12 +25,11 @@ ********************************************************************************/ package org.aoju.bus.socket.plugins; -import org.aoju.bus.core.io.buffer.ByteBuffer; -import org.aoju.bus.core.io.buffer.PageBuffer; -import org.aoju.bus.core.lang.Normal; import org.aoju.bus.logger.Logger; import org.aoju.bus.socket.AioQuickServer; import org.aoju.bus.socket.QuickTimer; +import org.aoju.bus.socket.buffers.BufferPage; +import org.aoju.bus.socket.buffers.BufferPool; import java.lang.reflect.Field; import java.util.concurrent.ScheduledFuture; @@ -42,16 +41,18 @@ * @author Kimi Liu * @since Java 17+ */ -public class PageBufferPlugin extends AbstractPlugin { +public class BufferPageMonitorPlugin extends AbstractPlugin { - private final AioQuickServer server; /** * 任务执行频率 */ private int seconds = 0; + + private AioQuickServer server; + private ScheduledFuture future; - public PageBufferPlugin(AioQuickServer server, int seconds) { + public BufferPageMonitorPlugin(AioQuickServer server, int seconds) { this.seconds = seconds; this.server = server; init(); @@ -61,7 +62,7 @@ private void init() { long mills = TimeUnit.SECONDS.toMillis(seconds); future = QuickTimer.scheduleAtFixedRate(() -> { { - if (null == server) { + if (server == null) { Logger.error("unKnow server or client need to monitor!"); shutdown(); return; @@ -69,32 +70,31 @@ private void init() { try { Field bufferPoolField = AioQuickServer.class.getDeclaredField("bufferPool"); bufferPoolField.setAccessible(true); - ByteBuffer pagePool = (ByteBuffer) bufferPoolField.get(server); - if (null == pagePool) { + BufferPool pagePool = (BufferPool) bufferPoolField.get(server); + if (pagePool == null) { Logger.error("server maybe has not started!"); shutdown(); return; } - Field field = ByteBuffer.class.getDeclaredField("pageBuffers"); + Field field = BufferPool.class.getDeclaredField("bufferPages"); field.setAccessible(true); - PageBuffer[] pages = (PageBuffer[]) field.get(pagePool); - String logger = Normal.EMPTY; - for (PageBuffer page : pages) { + BufferPage[] pages = (BufferPage[]) field.get(pagePool); + String logger = ""; + for (BufferPage page : pages) { logger += "\r\n" + page.toString(); } Logger.info(logger); } catch (Exception e) { - Logger.error(Normal.EMPTY, e); + Logger.error("", e); } } }, mills, mills); } private void shutdown() { - if (null != future) { + if (future != null) { future.cancel(true); future = null; } } - } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/HeartPlugin.java b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/HeartPlugin.java index 2b04f09d53..b6eb5ddefd 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/HeartPlugin.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/HeartPlugin.java @@ -45,16 +45,16 @@ public abstract class HeartPlugin extends AbstractPlugin { private static final TimeoutCallback DEFAULT_TIMEOUT_CALLBACK = (session, lastTime) -> session.close(true); - private final Map sessionMap = new HashMap<>(); + private Map sessionMap = new HashMap<>(); /** * 心跳频率 */ - private final long heartRate; + private long heartRate; /** * 在超时时间内未收到消息,关闭连接。 */ - private final long timeout; - private final TimeoutCallback timeoutCallback; + private long timeout; + private TimeoutCallback timeoutCallback; /** * 心跳插件 @@ -84,10 +84,8 @@ public HeartPlugin(int heartRate, int timeout, TimeUnit unit) { * 心跳插件 * 心跳插件在断网场景可能会触发TCP Retransmission,导致无法感知到网络实际状态,可通过设置timeout关闭连接 * - * @param heartRate 心跳触发频率 - * @param timeout 消息超时时间 - * @param timeUnit 时间单位 - * @param timeoutCallback 超时回调 + * @param heartRate 心跳触发频率 + * @param timeout 消息超时时间 */ public HeartPlugin(int heartRate, int timeout, TimeUnit timeUnit, TimeoutCallback timeoutCallback) { if (timeout > 0 && heartRate >= timeout) { @@ -102,8 +100,11 @@ public HeartPlugin(int heartRate, int timeout, TimeUnit timeUnit, TimeoutCallbac public final boolean preProcess(AioSession session, T t) { sessionMap.put(session, System.currentTimeMillis()); // 是否心跳响应消息 - // 延长心跳监测时间 - return !isHeartMessage(session, t); + if (isHeartMessage(session, t)) { + // 延长心跳监测时间 + return false; + } + return true; } @Override diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/MonitorPlugin.java b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/MonitorPlugin.java index d295011da9..a30d2ec882 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/MonitorPlugin.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/MonitorPlugin.java @@ -25,7 +25,6 @@ ********************************************************************************/ package org.aoju.bus.socket.plugins; -import org.aoju.bus.core.lang.Normal; import org.aoju.bus.logger.Logger; import org.aoju.bus.socket.AioSession; import org.aoju.bus.socket.QuickTimer; @@ -40,7 +39,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class MonitorPlugin extends AbstractPlugin implements Runnable { +public final class MonitorPlugin extends AbstractPlugin implements Runnable { /** * 当前周期内流入字节数 @@ -91,12 +90,19 @@ public class MonitorPlugin extends AbstractPlugin implements Runnable { */ private long onlineCount; + private boolean udp; + public MonitorPlugin() { this(60); } public MonitorPlugin(int seconds) { + this(seconds, false); + } + + public MonitorPlugin(int seconds, boolean udp) { this.seconds = seconds; + this.udp = udp; long mills = TimeUnit.SECONDS.toMillis(seconds); QuickTimer.scheduleAtFixedRate(this, mills, mills); } @@ -138,18 +144,18 @@ public void run() { onlineCount += connectCount - disConnectCount; totalProcessMsgNum += curProcessMsgNum; totalConnect += connectCount; - Logger.info("\r\n-----" + seconds + "seconds ----\r\ninflow:\t\t" + curInFlow * 1.0 / (Normal._1024 * Normal._1024) + "(MB)" - + "\r\noutflow:\t" + curOutFlow * 1.0 / (Normal._1024 * Normal._1024) + "(MB)" + Logger.info("\r\n-----" + seconds + "seconds ----\r\ninflow:\t\t" + curInFlow * 1.0 / (1024 * 1024) + "(MB)" + + "\r\noutflow:\t" + curOutFlow * 1.0 / (1024 * 1024) + "(MB)" + "\r\nprocess fail:\t" + curDiscardNum + "\r\nprocess count:\t" + curProcessMsgNum + "\r\nprocess total:\t" + totalProcessMsgNum + "\r\nread count:\t" + curReadCount + "\twrite count:\t" + curWriteCount - + "\r\nconnect count:\t" + connectCount + + (udp ? "" : "\r\nconnect count:\t" + connectCount + "\r\ndisconnect count:\t" + disConnectCount + "\r\nonline count:\t" + onlineCount - + "\r\nconnected total:\t" + totalConnect + + "\r\nconnected total:\t" + totalConnect) + "\r\nRequests/sec:\t" + curProcessMsgNum * 1.0 / seconds - + "\r\nTransfer/sec:\t" + (curInFlow * 1.0 / (Normal._1024 * Normal._1024) / seconds) + "(MB)"); + + "\r\nTransfer/sec:\t" + (curInFlow * 1.0 / (1024 * 1024) / seconds) + "(MB)"); } private long getAndReset(LongAdder longAdder) { diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/RateLimiterPlugin.java b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/RateLimiterPlugin.java index 69726d6e45..334e9cd887 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/RateLimiterPlugin.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/RateLimiterPlugin.java @@ -123,7 +123,7 @@ public void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, int limit = dst.limit(); // 限制limit,防止流控溢出 dst.limit(dst.position() + availReadSize); - super.read(dst, timeout, unit, attachment, new CompletionHandler() { + super.read(dst, timeout, unit, attachment, new CompletionHandler<>() { @Override public void completed(Integer result, A attachment) { if (result > 0) { @@ -170,7 +170,7 @@ public void write(ByteBuffer src, long timeout, TimeUnit unit, A attachment, int limit = src.limit(); // 限制limit,防止流控溢出 src.limit(src.position() + availWriteSize); - super.write(src, timeout, unit, attachment, new CompletionHandler() { + super.write(src, timeout, unit, attachment, new CompletionHandler<>() { @Override public void completed(Integer result, A attachment) { if (result > 0) { diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/ReconnectPlugin.java b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/ReconnectPlugin.java index 9b29191ab9..12008dfc80 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/ReconnectPlugin.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/ReconnectPlugin.java @@ -37,7 +37,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class ReconnectPlugin extends AbstractPlugin { +public class ReconnectPlugin extends AbstractPlugin { private final AsynchronousChannelGroup asynchronousChannelGroup; private final AioQuickClient client; diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/SocketOptionPlugin.java b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/SocketOptionPlugin.java index 272f8ecef4..c63514c74b 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/SocketOptionPlugin.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/SocketOptionPlugin.java @@ -42,7 +42,7 @@ */ public class SocketOptionPlugin extends AbstractPlugin { - private final Map, Object> optionMap = new HashMap<>(); + private Map, Object> optionMap = new HashMap<>(); @Override public final AsynchronousSocketChannel shouldAccept(AsynchronousSocketChannel channel) { diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/SslPlugin.java b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/SslPlugin.java index e2de938e9b..b011ee4766 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/plugins/SslPlugin.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/plugins/SslPlugin.java @@ -25,14 +25,18 @@ ********************************************************************************/ package org.aoju.bus.socket.plugins; -import org.aoju.bus.core.io.buffer.ByteBuffer; -import org.aoju.bus.socket.BufferFactory; +import org.aoju.bus.socket.buffers.BufferFactory; +import org.aoju.bus.socket.buffers.BufferPool; +import org.aoju.bus.socket.channel.SslAsynchronousSocketChannel; import org.aoju.bus.socket.security.ClientAuth; import org.aoju.bus.socket.security.SslService; -import org.aoju.bus.socket.security.SslSocketChannel; +import org.aoju.bus.socket.security.factory.ClientSSLContextFactory; +import org.aoju.bus.socket.security.factory.SSLContextFactory; +import org.aoju.bus.socket.security.factory.ServerSSLContextFactory; -import java.io.InputStream; +import javax.net.ssl.SSLEngine; import java.nio.channels.AsynchronousSocketChannel; +import java.util.function.Consumer; /** * SSL/TLS通信插件 @@ -40,46 +44,53 @@ * @author Kimi Liu * @since Java 17+ */ -public class SslPlugin extends AbstractPlugin { +public final class SslPlugin extends AbstractPlugin { - private final ByteBuffer bufferPool; - private SslService sslService; - private boolean init = false; + private final SslService sslService; + private final BufferPool bufferPool; - public SslPlugin() { - this.bufferPool = BufferFactory.DISABLED_BUFFER_FACTORY.create(); + public SslPlugin(SSLContextFactory factory, Consumer consumer) throws Exception { + this(factory, consumer, BufferFactory.DISABLED_BUFFER_FACTORY.create()); } - public SslPlugin(ByteBuffer bufferPool) { + public SslPlugin(SSLContextFactory factory, Consumer consumer, BufferPool bufferPool) throws Exception { this.bufferPool = bufferPool; + sslService = new SslService(factory.create(), consumer); } - public void initForServer(InputStream keyStoreInputStream, String keyStorePassword, String keyPassword, ClientAuth clientAuth) { - initCheck(); - sslService = new SslService(false, clientAuth); - sslService.initKeyStore(keyStoreInputStream, keyStorePassword, keyPassword); + public SslPlugin(ClientSSLContextFactory factory) throws Exception { + this(factory, BufferFactory.DISABLED_BUFFER_FACTORY.create()); } - public void initForClient() { - initForClient(null, null); + public SslPlugin(ClientSSLContextFactory factory, BufferPool bufferPool) throws Exception { + this(factory, sslEngine -> sslEngine.setUseClientMode(true), bufferPool); } - public void initForClient(InputStream trustInputStream, String trustPassword) { - initCheck(); - sslService = new SslService(true, null); - sslService.initTrust(trustInputStream, trustPassword); + public SslPlugin(ServerSSLContextFactory factory, ClientAuth clientAuth) throws Exception { + this(factory, clientAuth, BufferFactory.DISABLED_BUFFER_FACTORY.create()); } - private void initCheck() { - if (init) { - throw new RuntimeException("plugin is already init"); - } - init = true; + public SslPlugin(ServerSSLContextFactory factory, ClientAuth clientAuth, BufferPool bufferPool) throws Exception { + this(factory, sslEngine -> { + sslEngine.setUseClientMode(false); + switch (clientAuth) { + case OPTIONAL: + sslEngine.setWantClientAuth(true); + break; + case REQUIRE: + sslEngine.setNeedClientAuth(true); + break; + case NONE: + break; + default: + throw new Error("Unknown auth " + clientAuth); + } + }, bufferPool); } @Override - public final AsynchronousSocketChannel shouldAccept(AsynchronousSocketChannel channel) { - return new SslSocketChannel(channel, sslService, bufferPool.allocatePageBuffer()); + public AsynchronousSocketChannel shouldAccept(AsynchronousSocketChannel channel) { + return new SslAsynchronousSocketChannel(channel, sslService, bufferPool.allocateBufferPage()); } } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/process/AbstractProcessor.java b/bus-socket/src/main/java/org/aoju/bus/socket/process/AbstractMessageProcessor.java similarity index 94% rename from bus-socket/src/main/java/org/aoju/bus/socket/process/AbstractProcessor.java rename to bus-socket/src/main/java/org/aoju/bus/socket/process/AbstractMessageProcessor.java index 04ccad25e9..70befe12f9 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/process/AbstractProcessor.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/process/AbstractMessageProcessor.java @@ -38,7 +38,7 @@ * @author Kimi Liu * @since Java 17+ */ -public abstract class AbstractProcessor implements MessageProcessor, NetMonitor { +public abstract class AbstractMessageProcessor implements MessageProcessor, NetMonitor { private final List> plugins = new ArrayList<>(); @@ -75,7 +75,7 @@ public final AsynchronousSocketChannel shouldAccept(AsynchronousSocketChannel ch AsynchronousSocketChannel acceptChannel = channel; for (Plugin plugin : plugins) { acceptChannel = plugin.shouldAccept(acceptChannel); - if (null == acceptChannel) { + if (acceptChannel == null) { return null; } } @@ -99,7 +99,7 @@ public final void process(AioSession session, T msg) { * 处理接收到的消息 * * @param session 会话 - * @param msg 消息 + * @param msg 消息信息 * @see MessageProcessor#process(AioSession, Object) */ public abstract void process0(AioSession session, T msg); @@ -118,9 +118,9 @@ public final void stateEvent(AioSession session, SocketStatus socketStatus, Thro } /** - * @param session 会话 - * @param socketStatus 状态 - * @param throwable 线程 + * @param session + * @param socketStatus + * @param throwable * @see #stateEvent(AioSession, SocketStatus, Throwable) */ public abstract void stateEvent0(AioSession session, SocketStatus socketStatus, Throwable throwable); diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/process/GroupMessageProcessor.java b/bus-socket/src/main/java/org/aoju/bus/socket/process/GroupMessageProcessor.java index c70997dac5..b4effb9897 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/process/GroupMessageProcessor.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/process/GroupMessageProcessor.java @@ -40,7 +40,7 @@ */ public abstract class GroupMessageProcessor implements MessageProcessor, GroupIo { - private final Map sessionGroup = new ConcurrentHashMap<>(); + private Map sessionGroup = new ConcurrentHashMap<>(); /** * 将AioSession加入群组group diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/protocol/ByteArrayProtocol.java b/bus-socket/src/main/java/org/aoju/bus/socket/protocol/ByteArrayProtocol.java new file mode 100644 index 0000000000..aa5c9436e0 --- /dev/null +++ b/bus-socket/src/main/java/org/aoju/bus/socket/protocol/ByteArrayProtocol.java @@ -0,0 +1,41 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.socket.protocol; + +import org.aoju.bus.socket.AioSession; + +/** + * @author Kimi Liu + * @since Java 17+ + */ +public class ByteArrayProtocol extends FixedLengthBytesProtocol { + + @Override + protected byte[] decode(byte[] bytes, AioSession session) { + return bytes; + } + +} diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/protocol/FixedLengthBytesProtocol.java b/bus-socket/src/main/java/org/aoju/bus/socket/protocol/FixedLengthBytesProtocol.java new file mode 100644 index 0000000000..f4afbb1649 --- /dev/null +++ b/bus-socket/src/main/java/org/aoju/bus/socket/protocol/FixedLengthBytesProtocol.java @@ -0,0 +1,56 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.socket.protocol; + +import org.aoju.bus.socket.AioSession; +import org.aoju.bus.socket.Protocol; + +import java.nio.ByteBuffer; + +/** + * @author Kimi Liu + * @since Java 17+ + */ +public abstract class FixedLengthBytesProtocol implements Protocol { + + @Override + public final T decode(ByteBuffer readBuffer, AioSession session) { + if (readBuffer.remaining() < Integer.BYTES) { + return null; + } + readBuffer.mark(); + int length = readBuffer.getInt(); + if (readBuffer.remaining() < length) { + readBuffer.reset(); + return null; + } + byte[] bytes = new byte[length]; + readBuffer.get(bytes); + return decode(bytes, session); + } + + protected abstract T decode(byte[] bytes, AioSession session); +} diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/protocol/StringProtocol.java b/bus-socket/src/main/java/org/aoju/bus/socket/protocol/StringProtocol.java new file mode 100644 index 0000000000..d637d61494 --- /dev/null +++ b/bus-socket/src/main/java/org/aoju/bus/socket/protocol/StringProtocol.java @@ -0,0 +1,113 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.socket.protocol; + +import org.aoju.bus.socket.AioSession; +import org.aoju.bus.socket.Protocol; +import org.aoju.bus.socket.convert.FixedLengthFrameDecoder; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; + +/** + * @author Kimi Liu + * @since Java 17+ + */ +public class StringProtocol implements Protocol { + + private final Charset charset; + + private final HashMap decoderMap = new HashMap<>(); + private long lastClearTime = System.currentTimeMillis(); + + public StringProtocol(Charset charset) { + this.charset = charset; + } + + public StringProtocol() { + this(StandardCharsets.UTF_8); + } + + @Override + public String decode(ByteBuffer readBuffer, AioSession session) { + if (System.currentTimeMillis() - lastClearTime > 5000) { + lastClearTime = System.currentTimeMillis(); + decoderMap.keySet().stream().filter(AioSession::isInvalid).forEach(decoderMap::remove); + } + FixedLengthFrameDecoder decoder = decoderMap.get(session); + //消息长度超过缓冲区容量 + if (decoder != null) { + String content = bigContent(readBuffer, decoder); + //解码成功,释放解码器 + if (content != null) { + decoderMap.remove(session); + } + return content; + } + + int remaining = readBuffer.remaining(); + if (remaining < Integer.BYTES) { + return null; + } + readBuffer.mark(); + int length = readBuffer.getInt(); + //消息长度超过缓冲区容量引发的半包,启用定长消息解码器,本次解码失败 + if (length + Integer.BYTES > readBuffer.capacity()) { + FixedLengthFrameDecoder fixedLengthFrameDecoder = new FixedLengthFrameDecoder(length); + decoderMap.put(session, fixedLengthFrameDecoder); + return null; + } + //半包,解码失败 + if (length > readBuffer.remaining()) { + readBuffer.reset(); + return null; + } + return convert(readBuffer, length); + } + + /** + * 大消息体解码 + */ + private String bigContent(ByteBuffer readBuffer, FixedLengthFrameDecoder decoder) { + if (!decoder.decode(readBuffer)) { + return null; + } + ByteBuffer byteBuffer = decoder.getBuffer(); + return convert(byteBuffer, byteBuffer.capacity()); + } + + /** + * 消息解码 + */ + private String convert(ByteBuffer byteBuffer, int length) { + byte[] b = new byte[length]; + byteBuffer.get(b); + return new String(b, charset); + } + +} diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/security/HandshakeCallback.java b/bus-socket/src/main/java/org/aoju/bus/socket/security/HandshakeCallback.java index 694512c797..339427cea0 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/security/HandshakeCallback.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/security/HandshakeCallback.java @@ -29,7 +29,7 @@ * @author Kimi Liu * @since Java 17+ */ -interface HandshakeCallback { +public interface HandshakeCallback { /** * 握手回调 diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/security/HandshakeModel.java b/bus-socket/src/main/java/org/aoju/bus/socket/security/HandshakeModel.java index 018f95f9c2..9a013aad4e 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/security/HandshakeModel.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/security/HandshakeModel.java @@ -25,7 +25,7 @@ ********************************************************************************/ package org.aoju.bus.socket.security; -import org.aoju.bus.core.io.buffer.VirtualBuffer; +import org.aoju.bus.socket.buffers.VirtualBuffer; import javax.net.ssl.SSLEngine; import java.nio.channels.AsynchronousSocketChannel; diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/security/SslService.java b/bus-socket/src/main/java/org/aoju/bus/socket/security/SslService.java index c4915868b0..335a04f6c5 100644 --- a/bus-socket/src/main/java/org/aoju/bus/socket/security/SslService.java +++ b/bus-socket/src/main/java/org/aoju/bus/socket/security/SslService.java @@ -25,18 +25,14 @@ ********************************************************************************/ package org.aoju.bus.socket.security; -import org.aoju.bus.core.io.buffer.PageBuffer; import org.aoju.bus.logger.Logger; +import org.aoju.bus.socket.buffers.BufferPage; import javax.net.ssl.*; -import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; +import java.util.function.Consumer; /** * TLS/SSL服务 @@ -47,91 +43,30 @@ */ public class SslService { - private final boolean isClient; - private final ClientAuth clientAuth; - private SSLContext sslContext; + private final SSLContext sslContext; - public SslService(boolean isClient, ClientAuth clientAuth) { - this.isClient = isClient; - this.clientAuth = clientAuth; - } - - public void initKeyStore(InputStream keyStoreInputStream, String keyStorePassword, String keyPassword) { - try { - - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(keyStoreInputStream, keyStorePassword.toCharArray()); - kmf.init(ks, keyPassword.toCharArray()); - KeyManager[] keyManagers = kmf.getKeyManagers(); + private final Consumer consumer; - sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyManagers, null, new SecureRandom()); - - } catch (Exception e) { - e.printStackTrace(); - } + public SslService(SSLContext sslContext, Consumer consumer) { + this.sslContext = sslContext; + this.consumer = consumer; } - public void initTrust(InputStream trustInputStream, String trustPassword) { - try { - TrustManager[] trustManagers; - if (null != trustInputStream) { - KeyStore ts = KeyStore.getInstance("JKS"); - ts.load(trustInputStream, trustPassword.toCharArray()); - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); - tmf.init(ts); - trustManagers = tmf.getTrustManagers(); - } else { - trustManagers = new TrustManager[]{new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - }}; - } - sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustManagers, new SecureRandom()); - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - HandshakeModel createSSLEngine(AsynchronousSocketChannel socketChannel, PageBuffer pageBuffer) { + public HandshakeModel createSSLEngine(AsynchronousSocketChannel socketChannel, BufferPage bufferPage) { try { HandshakeModel handshakeModel = new HandshakeModel(); SSLEngine sslEngine = sslContext.createSSLEngine(); SSLSession session = sslEngine.getSession(); - sslEngine.setUseClientMode(isClient); - if (null != clientAuth) { - switch (clientAuth) { - case OPTIONAL: - sslEngine.setWantClientAuth(true); - break; - case REQUIRE: - sslEngine.setNeedClientAuth(true); - break; - case NONE: - break; - default: - throw new Error("Unknown auth " + clientAuth); - } - } + + // 更新SSLEngine配置 + consumer.accept(sslEngine); + handshakeModel.setSslEngine(sslEngine); - handshakeModel.setAppWriteBuffer(pageBuffer.allocate(session.getApplicationBufferSize())); - handshakeModel.setNetWriteBuffer(pageBuffer.allocate(session.getPacketBufferSize())); + handshakeModel.setAppWriteBuffer(bufferPage.allocate(session.getApplicationBufferSize())); + handshakeModel.setNetWriteBuffer(bufferPage.allocate(session.getPacketBufferSize())); handshakeModel.getNetWriteBuffer().buffer().flip(); - handshakeModel.setAppReadBuffer(pageBuffer.allocate(session.getApplicationBufferSize())); - handshakeModel.setNetReadBuffer(pageBuffer.allocate(session.getPacketBufferSize())); + handshakeModel.setAppReadBuffer(bufferPage.allocate(session.getApplicationBufferSize())); + handshakeModel.setNetReadBuffer(bufferPage.allocate(session.getPacketBufferSize())); sslEngine.beginHandshake(); handshakeModel.setSocketChannel(socketChannel); @@ -159,7 +94,7 @@ public void doHandshake(HandshakeModel handshakeModel) { ByteBuffer appWriteBuffer = handshakeModel.getAppWriteBuffer().buffer(); SSLEngine engine = handshakeModel.getSslEngine(); - //握手阶段网络断链 + // 握手阶段网络断链 if (handshakeModel.isEof()) { Logger.info("the ssl handshake is terminated"); handshakeModel.getHandshakeCallback().callback(); @@ -172,7 +107,7 @@ public void doHandshake(HandshakeModel handshakeModel) { } switch (handshakeStatus) { case NEED_UNWRAP: - //解码 + // 解码 netReadBuffer.flip(); if (netReadBuffer.hasRemaining()) { result = engine.unwrap(netReadBuffer, appReadBuffer); @@ -193,7 +128,7 @@ public void doHandshake(HandshakeModel handshakeModel) { case BUFFER_OVERFLOW: Logger.warn("doHandshake BUFFER_OVERFLOW"); break; - //两种情况会触发BUFFER_UNDERFLOW,1:读到的数据不够,2:netReadBuffer空间太小 + // 两种情况会触发BUFFER_UNDERFLOW,1:读到的数据不够,2:netReadBuffer空间太小 case BUFFER_UNDERFLOW: Logger.warn("doHandshake BUFFER_UNDERFLOW"); return; @@ -252,6 +187,9 @@ public void doHandshake(HandshakeModel handshakeModel) { throw new IllegalStateException("Invalid SSL status: " + handshakeStatus); } } + if (Logger.isDebug()) { + Logger.debug("握手完毕"); + } handshakeModel.getHandshakeCallback().callback(); } catch (Exception e) { @@ -279,5 +217,4 @@ public void failed(Throwable exc, HandshakeModel attachment) { } }; - } diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/security/factory/ClientSSLContextFactory.java b/bus-socket/src/main/java/org/aoju/bus/socket/security/factory/ClientSSLContextFactory.java new file mode 100644 index 0000000000..16263b9f63 --- /dev/null +++ b/bus-socket/src/main/java/org/aoju/bus/socket/security/factory/ClientSSLContextFactory.java @@ -0,0 +1,84 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.socket.security.factory; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +/** + * @author Kimi Liu + * @since Java 17+ + */ +public class ClientSSLContextFactory implements SSLContextFactory { + + private InputStream trustInputStream; + private String trustPassword; + + public ClientSSLContextFactory() { + } + + public ClientSSLContextFactory(InputStream trustInputStream, String trustPassword) { + this.trustInputStream = trustInputStream; + this.trustPassword = trustPassword; + } + + @Override + public SSLContext create() throws Exception { + TrustManager[] trustManagers; + if (trustInputStream != null) { + KeyStore ts = KeyStore.getInstance("JKS"); + ts.load(trustInputStream, trustPassword.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ts); + trustManagers = tmf.getTrustManagers(); + } else { + trustManagers = new TrustManager[]{new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }}; + } + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustManagers, new SecureRandom()); + return sslContext; + } + +} \ No newline at end of file diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/security/factory/SSLContextFactory.java b/bus-socket/src/main/java/org/aoju/bus/socket/security/factory/SSLContextFactory.java new file mode 100644 index 0000000000..8697dcbb17 --- /dev/null +++ b/bus-socket/src/main/java/org/aoju/bus/socket/security/factory/SSLContextFactory.java @@ -0,0 +1,38 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.socket.security.factory; + +import javax.net.ssl.SSLContext; + +/** + * @author Kimi Liu + * @since Java 17+ + */ +public interface SSLContextFactory { + + SSLContext create() throws Exception; + +} diff --git a/bus-socket/src/main/java/org/aoju/bus/socket/security/factory/ServerSSLContextFactory.java b/bus-socket/src/main/java/org/aoju/bus/socket/security/factory/ServerSSLContextFactory.java new file mode 100644 index 0000000000..851fc17dd8 --- /dev/null +++ b/bus-socket/src/main/java/org/aoju/bus/socket/security/factory/ServerSSLContextFactory.java @@ -0,0 +1,60 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org sandao and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.socket.security.factory; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.SecureRandom; + +public class ServerSSLContextFactory implements SSLContextFactory { + + private final InputStream keyStoreInputStream; + private final String keyStorePassword; + private final String keyPassword; + + public ServerSSLContextFactory(InputStream keyStoreInputStream, String keyStorePassword, String keyPassword) { + this.keyStoreInputStream = keyStoreInputStream; + this.keyStorePassword = keyStorePassword; + this.keyPassword = keyPassword; + } + + @Override + public SSLContext create() throws Exception { + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(keyStoreInputStream, keyStorePassword.toCharArray()); + kmf.init(ks, keyPassword.toCharArray()); + KeyManager[] keyManagers = kmf.getKeyManagers(); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers, null, new SecureRandom()); + return sslContext; + } + +} \ No newline at end of file From be461a3268f6622e0b52cb3f1eb7655f1c5d5de2 Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Thu, 27 Oct 2022 17:06:32 +0800 Subject: [PATCH 07/19] update map and bloom --- .../org/aoju/bus/core/beans/BeanCache.java | 2 +- .../{PathExpression.java => BeanPath.java} | 50 +- .../aoju/bus/core/beans/PropertyCache.java | 2 +- .../copier/provider/BeanValueProvider.java | 134 +-- .../bus/core/bloom/BitSetBloomFilter.java | 174 --- .../aoju/bus/core/bloom/bitmap/BitMap.java | 69 -- .../aoju/bus/core/bloom/bitmap/LongMap.java | 79 -- .../bus/core/bloom/bitmap/package-info.java | 7 - .../bus/core/bloom/filter/AbstractFilter.java | 115 -- .../aoju/bus/core/bloom/filter/ELFFilter.java | 51 - .../aoju/bus/core/bloom/filter/FNVFilter.java | 51 - .../bus/core/bloom/filter/HfIpFilter.java | 54 - .../aoju/bus/core/bloom/filter/JSFilter.java | 59 - .../aoju/bus/core/bloom/filter/PJWFilter.java | 51 - .../aoju/bus/core/bloom/filter/RSFilter.java | 51 - .../bus/core/bloom/filter/SDBMFilter.java | 51 - .../bus/core/bloom/filter/TianlFilter.java | 51 - .../bus/core/bloom/filter/package-info.java | 7 - .../bus/core/builder/ToStringBuilder.java | 2 +- .../java/org/aoju/bus/core/codec/BCD.java | 155 --- .../bus/core/collection/UniqueKeySet.java | 4 +- .../aoju/bus/core/convert/ArrayConverter.java | 3 +- .../aoju/bus/core/convert/BeanConverter.java | 2 +- .../org/aoju/bus/core/convert/Convert.java | 2 +- .../aoju/bus/core/convert/DateConverter.java | 4 +- .../aoju/bus/core/convert/EnumConverter.java | 4 +- .../bus/core/convert/NumberConverter.java | 49 +- .../bus/core/convert/PrimitiveConverter.java | 3 +- .../bus/core/convert/TemporalConverter.java | 4 +- .../java/org/aoju/bus/core/image/Images.java | 40 +- .../org/aoju/bus/core/image/NeuQuant.java | 10 +- .../org/aoju/bus/core/io/file/Tailer.java | 14 +- .../java/org/aoju/bus/core/lang/Enums.java | 108 +- .../bus/core/lang/ansi/Ansi8BitColor.java | 31 +- .../org/aoju/bus/core/lang/range/Range.java | 412 +++---- .../bus/core/lang/reflect/MethodHandle.java | 6 +- .../bus/core/map/AbstractCollValueMap.java | 147 ++- .../org/aoju/bus/core/map/AbstractEntry.java | 4 +- .../org/aoju/bus/core/map/AbstractTable.java | 26 +- .../aoju/bus/core/map/CamelCaseLinkedMap.java | 10 +- .../org/aoju/bus/core/map/CamelCaseMap.java | 16 +- .../core/map/CaseInsensitiveLinkedMap.java | 14 +- .../aoju/bus/core/map/CaseInsensitiveMap.java | 14 +- .../bus/core/map/CaseInsensitiveTreeMap.java | 6 +- .../aoju/bus/core/map/CollectionValueMap.java | 81 +- .../org/aoju/bus/core/map/CustomKeyMap.java | 6 +- .../org/aoju/bus/core/map/Dictionary.java | 41 +- .../org/aoju/bus/core/map/DuplexingMap.java | 12 +- .../aoju/bus/core/map/FixedLinkedHashMap.java | 8 +- .../java/org/aoju/bus/core/map/ForestMap.java | 30 +- .../org/aoju/bus/core/map/FuncKeyMap.java | 4 +- .../java/org/aoju/bus/core/map/FuncMap.java | 8 +- .../java/org/aoju/bus/core/map/GraphMap.java | 166 +++ .../aoju/bus/core/map/LinkedForestMap.java | 40 +- .../org/aoju/bus/core/map/ListValueMap.java | 47 +- .../org/aoju/bus/core/map/MapBuilder.java | 52 +- .../java/org/aoju/bus/core/map/MapProxy.java | 27 +- .../org/aoju/bus/core/map/MapWrapper.java | 45 +- .../org/aoju/bus/core/map/MultiValueMap.java | 277 +++++ .../org/aoju/bus/core/map/ReferenceMap.java | 3 +- .../org/aoju/bus/core/map/RowKeyTable.java | 37 +- .../IntMap.java => map/SafeHashMap.java} | 71 +- .../org/aoju/bus/core/map/SetValueMap.java | 46 +- .../java/org/aoju/bus/core/map/Table.java | 28 +- .../java/org/aoju/bus/core/map/TableMap.java | 10 +- .../org/aoju/bus/core/map/TolerantMap.java | 21 +- .../org/aoju/bus/core/map/TransitionMap.java | 28 +- .../java/org/aoju/bus/core/map/TreeEntry.java | 19 - .../java/org/aoju/bus/core/map/WeakMap.java | 7 +- .../bus/core/scanner/AnnotationScanner.java | 86 +- .../core/scanner/SynthesizedProcessor.java | 20 +- .../aoju/bus/core/scanner/SyntheticProxy.java | 216 ++-- .../annotation/AbstractTypeScanner.java | 280 ++--- .../core/scanner/annotation/MetaScanner.java | 139 +-- .../scanner/annotation/MethodScanner.java | 182 +-- .../core/scanner/annotation/TypeScanner.java | 157 +-- .../org/aoju/bus/core/text/TextBuilder.java | 6 +- .../bloom/AbstractFilter.java} | 51 +- .../core/{ => text}/bloom/BloomFilter.java | 13 +- .../bloom/CombinedFilter.java} | 44 +- .../bloom/FuncFilter.java} | 34 +- .../core/{ => text}/bloom/package-info.java | 2 +- .../org/aoju/bus/core/toolkit/BeanKit.java | 8 +- .../org/aoju/bus/core/toolkit/CollKit.java | 40 - .../org/aoju/bus/core/toolkit/MapKit.java | 1049 ++++++++++------- .../org/aoju/bus/core/toolkit/MathKit.java | 6 +- .../org/aoju/bus/core/toolkit/ObjectKit.java | 8 +- .../org/aoju/bus/core/toolkit/PhoneKit.java | 300 ++--- .../org/aoju/bus/core/toolkit/StringKit.java | 2 +- .../org/aoju/bus/core/toolkit/UriKit.java | 2 +- .../aoju/bus/crypto/asymmetric/Decryptor.java | 50 - .../aoju/bus/crypto/asymmetric/Encryptor.java | 25 - 92 files changed, 2721 insertions(+), 3261 deletions(-) rename bus-core/src/main/java/org/aoju/bus/core/beans/{PathExpression.java => BeanPath.java} (98%) delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/BitSetBloomFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/BitMap.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/LongMap.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/package-info.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/AbstractFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/ELFFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/FNVFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfIpFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/JSFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/PJWFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/RSFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/SDBMFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/TianlFilter.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/bloom/filter/package-info.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/codec/BCD.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseLinkedMap.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseMap.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveLinkedMap.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveMap.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/CollectionValueMap.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/FixedLinkedHashMap.java mode change 100644 => 100755 bus-core/src/main/java/org/aoju/bus/core/map/FuncKeyMap.java create mode 100644 bus-core/src/main/java/org/aoju/bus/core/map/GraphMap.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/ListValueMap.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/MapProxy.java create mode 100644 bus-core/src/main/java/org/aoju/bus/core/map/MultiValueMap.java mode change 100644 => 100755 bus-core/src/main/java/org/aoju/bus/core/map/RowKeyTable.java rename bus-core/src/main/java/org/aoju/bus/core/{bloom/bitmap/IntMap.java => map/SafeHashMap.java} (59%) mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/map/SetValueMap.java mode change 100644 => 100755 bus-core/src/main/java/org/aoju/bus/core/map/Table.java mode change 100644 => 100755 bus-core/src/main/java/org/aoju/bus/core/map/TransitionMap.java rename bus-core/src/main/java/org/aoju/bus/core/{bloom/filter/HfFilter.java => text/bloom/AbstractFilter.java} (72%) rename bus-core/src/main/java/org/aoju/bus/core/{ => text}/bloom/BloomFilter.java (84%) rename bus-core/src/main/java/org/aoju/bus/core/{bloom/BitMapBloomFilter.java => text/bloom/CombinedFilter.java} (72%) rename bus-core/src/main/java/org/aoju/bus/core/{bloom/filter/DefaultFilter.java => text/bloom/FuncFilter.java} (73%) rename bus-core/src/main/java/org/aoju/bus/core/{ => text}/bloom/package-info.java (73%) diff --git a/bus-core/src/main/java/org/aoju/bus/core/beans/BeanCache.java b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanCache.java index db9a165e9d..0cf7ab9905 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/beans/BeanCache.java +++ b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanCache.java @@ -29,7 +29,7 @@ import org.aoju.bus.core.map.WeakMap; /** - * Bean属性缓存 + * Bean缓存 * 缓存用于防止多次反射造成的性能问题 * * @author Kimi Liu diff --git a/bus-core/src/main/java/org/aoju/bus/core/beans/PathExpression.java b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java similarity index 98% rename from bus-core/src/main/java/org/aoju/bus/core/beans/PathExpression.java rename to bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java index 63815dac82..dd5f4ce81c 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/beans/PathExpression.java +++ b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java @@ -43,7 +43,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class PathExpression implements Serializable { +public class BeanPath implements Serializable { private static final long serialVersionUID = 1L; @@ -59,7 +59,7 @@ public class PathExpression implements Serializable { * * @param expression 表达式 */ - public PathExpression(final String expression) { + public BeanPath(final String expression) { init(expression); } @@ -83,10 +83,10 @@ public PathExpression(final String expression) { * * * @param expression 表达式 - * @return {@link PathExpression} + * @return {@link BeanPath} */ - public static PathExpression create(final String expression) { - return new PathExpression(expression); + public static BeanPath of(final String expression) { + return new BeanPath(expression); } private static Object getFieldValue(final Object bean, final String expression) { @@ -148,6 +148,26 @@ private static String unWrapIfPossible(CharSequence expression) { return StringKit.unWrap(expression, Symbol.C_SINGLE_QUOTE); } + /** + * 判断path列表中末尾的标记是否为数字 + * + * @param patternParts path列表 + * @return 是否为数字 + */ + private static boolean lastIsNumber(List patternParts) { + return MathKit.isInteger(patternParts.get(patternParts.size() - 1)); + } + + /** + * 获取父级路径列表 + * + * @param patternParts 路径列表 + * @return 父级路径列表 + */ + private static List getParentParts(List patternParts) { + return patternParts.subList(0, patternParts.size() - 1); + } + /** * 获取表达式解析后的分段列表 * @@ -167,31 +187,11 @@ public Object get(final Object bean) { return get(this.patternParts, bean, false); } - /** - * 判断path列表中末尾的标记是否为数字 - * - * @param patternParts path列表 - * @return 是否为数字 - */ - private static boolean lastIsNumber(List patternParts) { - return MathKit.isInteger(patternParts.get(patternParts.size() - 1)); - } - @Override public String toString() { return this.patternParts.toString(); } - /** - * 获取父级路径列表 - * - * @param patternParts 路径列表 - * @return 父级路径列表 - */ - private static List getParentParts(List patternParts) { - return patternParts.subList(0, patternParts.size() - 1); - } - /** * 获取Bean中对应表达式的值 * diff --git a/bus-core/src/main/java/org/aoju/bus/core/beans/PropertyCache.java b/bus-core/src/main/java/org/aoju/bus/core/beans/PropertyCache.java index 87991b8c3f..f1b4cee363 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/beans/PropertyCache.java +++ b/bus-core/src/main/java/org/aoju/bus/core/beans/PropertyCache.java @@ -33,7 +33,7 @@ import java.util.Map; /** - * Bean属性缓存 + * 属性缓存 * 缓存用于防止多次反射造成的性能问题 * * @author Kimi Liu diff --git a/bus-core/src/main/java/org/aoju/bus/core/beans/copier/provider/BeanValueProvider.java b/bus-core/src/main/java/org/aoju/bus/core/beans/copier/provider/BeanValueProvider.java index 1e7e69a9d1..86edd70d05 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/beans/copier/provider/BeanValueProvider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/beans/copier/provider/BeanValueProvider.java @@ -45,78 +45,78 @@ */ public class BeanValueProvider implements ValueProvider { - final Map sourcePdMap; - private final Object source; - private final boolean ignoreError; + final Map sourcePdMap; + private final Object source; + private final boolean ignoreError; - /** - * 构造 - * - * @param bean Bean - * @param ignoreCase 是否忽略字段大小写 - * @param ignoreError 是否忽略字段值读取错误 - */ - public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError) { - this(bean, ignoreCase, ignoreError, null); - } + /** + * 构造 + * + * @param bean Bean + * @param ignoreCase 是否忽略字段大小写 + * @param ignoreError 是否忽略字段值读取错误 + */ + public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError) { + this(bean, ignoreCase, ignoreError, null); + } - /** - * 构造 - * - * @param bean Bean - * @param ignoreCase 是否忽略字段大小写 - * @param ignoreError 是否忽略字段值读取错误 - * @param keyEditor 键编辑器 - */ - public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError, Editor keyEditor) { - this.source = bean; - this.ignoreError = ignoreError; - final Map sourcePdMap = BeanKit.getBeanDesc(source.getClass()).getPropMap(ignoreCase); - // 如果用户定义了键编辑器,则提供的map中的数据必须全部转换key - this.sourcePdMap = new FuncKeyMap<>(new HashMap<>(sourcePdMap.size(), 1), (key) -> { - if (ignoreCase && key instanceof CharSequence) { - key = key.toString().toLowerCase(); - } - if (null != keyEditor) { - key = keyEditor.edit(key.toString()); - } - return key.toString(); - }); - this.sourcePdMap.putAll(sourcePdMap); - } + /** + * 构造 + * + * @param bean Bean + * @param ignoreCase 是否忽略字段大小写 + * @param ignoreError 是否忽略字段值读取错误 + * @param keyEditor 键编辑器 + */ + public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError, Editor keyEditor) { + this.source = bean; + this.ignoreError = ignoreError; + final Map sourcePdMap = BeanKit.getBeanDesc(source.getClass()).getPropMap(ignoreCase); + // 如果用户定义了键编辑器,则提供的map中的数据必须全部转换key + this.sourcePdMap = new FuncKeyMap<>(new HashMap<>(sourcePdMap.size(), 1), (key) -> { + if (ignoreCase && key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + if (null != keyEditor) { + key = keyEditor.edit(key.toString()); + } + return key.toString(); + }); + this.sourcePdMap.putAll(sourcePdMap); + } - @Override - public Object value(String key, Type valueType) { - final PropertyDesc sourcePd = getPropertyDesc(key, valueType); - Object result = null; - if (null != sourcePd) { - result = sourcePd.getValue(this.source, valueType, this.ignoreError); - } - return result; - } + @Override + public Object value(String key, Type valueType) { + final PropertyDesc sourcePd = getPropertyDesc(key, valueType); + Object result = null; + if (null != sourcePd) { + result = sourcePd.getValue(this.source, valueType, this.ignoreError); + } + return result; + } - @Override - public boolean containsKey(String key) { - final PropertyDesc sourcePd = getPropertyDesc(key, null); - // 字段描述不存在或忽略读的情况下,表示不存在 - return null != sourcePd && sourcePd.isReadable(false); - } + @Override + public boolean containsKey(String key) { + final PropertyDesc sourcePd = getPropertyDesc(key, null); + // 字段描述不存在或忽略读的情况下,表示不存在 + return null != sourcePd && sourcePd.isReadable(false); + } - /** - * 获得属性描述 - * - * @param key 字段名 - * @param valueType 值类型,用于判断是否为Boolean,可以为null - * @return 属性描述 - */ - private PropertyDesc getPropertyDesc(String key, Type valueType) { - PropertyDesc sourcePd = sourcePdMap.get(key); - if (null == sourcePd && (null == valueType || Boolean.class == valueType || boolean.class == valueType)) { - // boolean类型字段字段名支持两种方式 - sourcePd = sourcePdMap.get(StringKit.upperFirstAndAddPre(key, Normal.IS)); - } + /** + * 获得属性描述 + * + * @param key 字段名 + * @param valueType 值类型,用于判断是否为Boolean,可以为null + * @return 属性描述 + */ + private PropertyDesc getPropertyDesc(String key, Type valueType) { + PropertyDesc sourcePd = sourcePdMap.get(key); + if (null == sourcePd && (null == valueType || Boolean.class == valueType || boolean.class == valueType)) { + // boolean类型字段字段名支持两种方式 + sourcePd = sourcePdMap.get(StringKit.upperFirstAndAddPre(key, Normal.IS)); + } - return sourcePd; - } + return sourcePd; + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/BitSetBloomFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/BitSetBloomFilter.java deleted file mode 100644 index d925c1a944..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/BitSetBloomFilter.java +++ /dev/null @@ -1,174 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom; - -import org.aoju.bus.core.toolkit.FileKit; -import org.aoju.bus.core.toolkit.HashKit; -import org.aoju.bus.core.toolkit.IoKit; - -import java.io.BufferedReader; -import java.io.IOException; -import java.util.BitSet; - -/** - * BloomFilter实现方式2,此方式使用BitSet存储 - * Hash算法的使用使用固定顺序,只需指定个数即可 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class BitSetBloomFilter implements BloomFilter { - - private static final long serialVersionUID = 1L; - - private final BitSet bitSet; - private final int bitSetSize; - private final int addedElements; - private final int hashFunctionNumber; - - /** - * 构造一个布隆过滤器,过滤器的容量为c * n 个bit - * - * @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍 - * @param n 当前过滤器预计所要包含的记录 - * @param k 哈希函数的个数,等同每条记录要占用的bit数 - */ - public BitSetBloomFilter(int c, int n, int k) { - this.hashFunctionNumber = k; - this.bitSetSize = (int) Math.ceil(c * k); - this.addedElements = n; - this.bitSet = new BitSet(this.bitSetSize); - } - - /** - * 将字符串的字节表示进行多哈希编码 - * - * @param text 待添加进过滤器的字符串字节表示 - * @param hashNumber 要经过的哈希个数 - * @return 各个哈希的结果数组 - */ - public static int[] createHashes(String text, int hashNumber) { - int[] result = new int[hashNumber]; - for (int i = 0; i < hashNumber; i++) { - result[i] = hash(text, i); - - } - return result; - } - - /** - * 计算Hash值 - * - * @param text 被计算Hash的字符串 - * @param k Hash算法序号 - * @return Hash值 - */ - public static int hash(String text, int k) { - switch (k) { - case 0: - return HashKit.rsHash(text); - case 1: - return HashKit.jsHash(text); - case 2: - return HashKit.elfHash(text); - case 3: - return HashKit.bkdrHash(text); - case 4: - return HashKit.apHash(text); - case 5: - return HashKit.djbHash(text); - case 6: - return HashKit.sdbmHash(text); - case 7: - return HashKit.pjwHash(text); - default: - return 0; - } - } - - /** - * 通过文件初始化过滤器. - * - * @param path 文件路径 - * @param charset 字符集 - * @throws IOException IO异常 - */ - public void init(String path, String charset) throws IOException { - BufferedReader reader = FileKit.getReader(path, charset); - try { - String line; - while (true) { - line = reader.readLine(); - if (line == null) { - break; - } - this.add(line); - } - } finally { - IoKit.close(reader); - } - } - - @Override - public boolean add(String text) { - if (contains(text)) { - return false; - } - - int[] positions = createHashes(text, hashFunctionNumber); - for (int value : positions) { - int position = Math.abs(value % bitSetSize); - bitSet.set(position, true); - } - return true; - } - - /** - * 判定是否包含指定字符串 - * - * @param text 字符串 - * @return 是否包含,存在误差 - */ - @Override - public boolean contains(String text) { - int[] positions = createHashes(text, hashFunctionNumber); - for (int i : positions) { - int position = Math.abs(i % bitSetSize); - if (!bitSet.get(position)) { - return false; - } - } - return true; - } - - /** - * @return 得到当前过滤器的错误率 - */ - public double getFalsePositiveProbability() { - return Math.pow((1 - Math.exp(-hashFunctionNumber * (double) addedElements / bitSetSize)), hashFunctionNumber); - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/BitMap.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/BitMap.java deleted file mode 100644 index 999bb4d5fa..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/BitMap.java +++ /dev/null @@ -1,69 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.bitmap; - -import org.aoju.bus.core.lang.Normal; - -/** - * BitMap接口,用于将某个int或long值映射到一个数组中,从而判定某个值是否存在 - * - * @author Kimi Liu - * @since Java 17+ - */ -public interface BitMap { - - /** - * 长度32 - */ - int MACHINE32 = Normal._32; - /** - * 长度64 - */ - int MACHINE64 = Normal._64; - - /** - * 加入值 - * - * @param i 值 - */ - void add(long i); - - /** - * 检查是否包含值 - * - * @param i 值 - * @return 是否包含 - */ - boolean contains(long i); - - /** - * 移除值 - * - * @param i 值 - */ - void remove(long i); - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/LongMap.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/LongMap.java deleted file mode 100644 index 385a99a9b4..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/LongMap.java +++ /dev/null @@ -1,79 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.bitmap; - -import java.io.Serializable; - -/** - * 过滤器BitMap在64位机器上.这个类能发生更好的效果.一般机器不建议使用 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class LongMap implements BitMap, Serializable { - - private static final long serialVersionUID = 1L; - - private final long[] longs; - - /** - * 构造 - */ - public LongMap() { - longs = new long[93750000]; - } - - /** - * 构造 - * - * @param size 容量 - */ - public LongMap(int size) { - longs = new long[size]; - } - - @Override - public void add(long i) { - int r = (int) (i / BitMap.MACHINE64); - long c = i % BitMap.MACHINE64; - longs[r] = longs[r] | (1L << c); - } - - @Override - public boolean contains(long i) { - int r = (int) (i / BitMap.MACHINE64); - long c = i % BitMap.MACHINE64; - return ((longs[r] >>> c) & 1) == 1; - } - - @Override - public void remove(long i) { - int r = (int) (i / BitMap.MACHINE64); - long c = i % BitMap.MACHINE64; - longs[r] &= ~(1L << c); - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/package-info.java deleted file mode 100644 index 8078bc02c5..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * BitMap实现 - * - * @author Kimi Liu - * @since Java 17+ - */ -package org.aoju.bus.core.bloom.bitmap; \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/AbstractFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/AbstractFilter.java deleted file mode 100644 index 91f14d939b..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/AbstractFilter.java +++ /dev/null @@ -1,115 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.bloom.BloomFilter; -import org.aoju.bus.core.bloom.bitmap.BitMap; -import org.aoju.bus.core.bloom.bitmap.IntMap; -import org.aoju.bus.core.bloom.bitmap.LongMap; - -/** - * 抽象Bloom过滤器 - * - * @author Kimi Liu - * @since Java 17+ - */ -public abstract class AbstractFilter implements BloomFilter { - - private static final long serialVersionUID = 1L; - - /** - * 容量 - */ - protected long size = 0; - /** - * BitMap接口 - */ - private BitMap bm = null; - - /** - * 构造 - * - * @param maxValue 最大值 - * @param machineNum 机器位数 - */ - public AbstractFilter(long maxValue, int machineNum) { - init(maxValue, machineNum); - } - - /** - * 构造32位 - * - * @param maxValue 最大值 - */ - public AbstractFilter(long maxValue) { - this(maxValue, BitMap.MACHINE32); - } - - /** - * 初始化 - * - * @param maxValue 最大值 - * @param machineNum 机器位数 - */ - public void init(long maxValue, int machineNum) { - this.size = maxValue; - switch (machineNum) { - case BitMap.MACHINE32: - bm = new IntMap((int) (size / machineNum)); - break; - case BitMap.MACHINE64: - bm = new LongMap((int) (size / machineNum)); - break; - default: - throw new RuntimeException("Error Machine number!"); - } - } - - @Override - public boolean contains(String text) { - return bm.contains(Math.abs(hash(text))); - } - - @Override - public boolean add(String text) { - final long hash = Math.abs(hash(text)); - if (bm.contains(hash)) { - return false; - } - - bm.add(hash); - return true; - } - - /** - * 自定义Hash方法 - * - * @param text 字符串 - * @return the long - */ - public abstract long hash(String text); - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/ELFFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/ELFFilter.java deleted file mode 100644 index 174887c259..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/ELFFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.toolkit.HashKit; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class ELFFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public ELFFilter(long maxValue, int machineNumber) { - super(maxValue, machineNumber); - } - - public ELFFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - return HashKit.elfHash(text) % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/FNVFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/FNVFilter.java deleted file mode 100644 index 8c88f82f1e..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/FNVFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.toolkit.HashKit; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class FNVFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public FNVFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public FNVFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - return HashKit.fnvHash(text); - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfIpFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfIpFilter.java deleted file mode 100644 index b98a03b900..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfIpFilter.java +++ /dev/null @@ -1,54 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class HfIpFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public HfIpFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public HfIpFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - int length = text.length(); - long hash = 0; - for (int i = 0; i < length; i++) { - hash += text.charAt(i % 4) ^ text.charAt(i); - } - return hash % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/JSFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/JSFilter.java deleted file mode 100644 index f0ad74ee9d..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/JSFilter.java +++ /dev/null @@ -1,59 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class JSFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public JSFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public JSFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - int hash = 1315423911; - - for (int i = 0; i < text.length(); i++) { - hash ^= ((hash << 5) + text.charAt(i) + (hash >> 2)); - } - - if (hash < 0) { - hash *= -1; - } - - return hash % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/PJWFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/PJWFilter.java deleted file mode 100644 index 4fbe21fe29..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/PJWFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.toolkit.HashKit; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class PJWFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public PJWFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public PJWFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - return HashKit.pjwHash(text) % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/RSFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/RSFilter.java deleted file mode 100644 index f7256b00b8..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/RSFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.toolkit.HashKit; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class RSFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public RSFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public RSFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - return HashKit.rsHash(text) % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/SDBMFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/SDBMFilter.java deleted file mode 100644 index 0a90f6ef26..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/SDBMFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.toolkit.HashKit; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class SDBMFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public SDBMFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public SDBMFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - return HashKit.sdbmHash(text) % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/TianlFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/TianlFilter.java deleted file mode 100644 index 58499242b4..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/TianlFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.toolkit.HashKit; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class TianlFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public TianlFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public TianlFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - return HashKit.tianlHash(text) % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/package-info.java deleted file mode 100644 index d9c6f561b3..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * 各种Hash算法的过滤器实现 - * - * @author Kimi Liu - * @since Java 17+ - */ -package org.aoju.bus.core.bloom.filter; \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/builder/ToStringBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/builder/ToStringBuilder.java index ce964db992..4a440bdc03 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/builder/ToStringBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/builder/ToStringBuilder.java @@ -780,7 +780,7 @@ public ToStringBuilder append(final String fieldName, final Object object) { * value.

* * @param fieldName the field name - * @param object the value to add to the toString + * @param object the value to add to the toString * @param fullDetail true for detail, * false for summary info * @return this diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/BCD.java b/bus-core/src/main/java/org/aoju/bus/core/codec/BCD.java deleted file mode 100644 index e4818e0d9f..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/BCD.java +++ /dev/null @@ -1,155 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.codec; - -import org.aoju.bus.core.lang.Assert; -import org.aoju.bus.core.lang.Symbol; - -/** - * BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码 - * BCD码这种编码形式利用了四个位元来储存一个十进制的数码, - * 使二进制和十进制之间的转换得以快捷的进行 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class BCD { - - /** - * 字符串转BCD码 - * - * @param asc ASCII字符串 - * @return BCD - */ - public static byte[] strToBcd(String asc) { - Assert.notNull(asc, "ASCII must not be null!"); - int len = asc.length(); - int mod = len % 2; - if (mod != 0) { - asc = Symbol.ZERO + asc; - len = asc.length(); - } - if (len >= 2) { - len >>= 1; - } - byte[] bbt = new byte[len]; - byte[] abt = asc.getBytes(); - int j; - int k; - for (int p = 0; p < asc.length() / 2; p++) { - if ((abt[2 * p] >= Symbol.C_ZERO) && (abt[2 * p] <= Symbol.C_NINE)) { - j = abt[2 * p] - Symbol.C_ZERO; - } else if ((abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) { - j = abt[2 * p] - 'a' + 0x0a; - } else { - j = abt[2 * p] - 'A' + 0x0a; - } - if ((abt[2 * p + 1] >= Symbol.C_ZERO) && (abt[2 * p + 1] <= Symbol.C_NINE)) { - k = abt[2 * p + 1] - Symbol.C_ZERO; - } else if ((abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) { - k = abt[2 * p + 1] - 'a' + 0x0a; - } else { - k = abt[2 * p + 1] - 'A' + 0x0a; - } - int a = (j << 4) + k; - byte b = (byte) a; - bbt[p] = b; - } - return bbt; - } - - /** - * ASCII转BCD - * - * @param ascii ASCII byte数组 - * @return BCD - */ - public static byte[] ascToBcd(byte[] ascii) { - Assert.notNull(ascii, "Ascii must be not null!"); - return ascToBcd(ascii, ascii.length); - } - - /** - * ASCII转BCD - * - * @param ascii ASCII byte数组 - * @param ascLength 长度 - * @return BCD - */ - public static byte[] ascToBcd(byte[] ascii, int ascLength) { - Assert.notNull(ascii, "Ascii must be not null!"); - byte[] bcd = new byte[ascLength / 2]; - int j = 0; - for (int i = 0; i < (ascLength + 1) / 2; i++) { - bcd[i] = ascToBcd(ascii[j++]); - bcd[i] = (byte) (((j >= ascLength) ? 0x00 : ascToBcd(ascii[j++])) + (bcd[i] << 4)); - } - return bcd; - } - - /** - * BCD转ASCII字符串 - * - * @param bytes BCD byte数组 - * @return ASCII字符串 - */ - public static String bcdToString(byte[] bytes) { - Assert.notNull(bytes, "Bcd bytes must be not null!"); - char[] temp = new char[bytes.length * 2]; - char val; - - for (int i = 0; i < bytes.length; i++) { - val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f); - temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + Symbol.C_ZERO); - - val = (char) (bytes[i] & 0x0f); - temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + Symbol.C_ZERO); - } - return new String(temp); - } - - /** - * 转换单个byte为BCD - * - * @param asc ACSII - * @return BCD - */ - private static byte ascToBcd(byte asc) { - byte bcd; - - if ((asc >= Symbol.C_ZERO) && (asc <= Symbol.C_NINE)) { - bcd = (byte) (asc - Symbol.C_ZERO); - } else if ((asc >= 'A') && (asc <= 'F')) { - bcd = (byte) (asc - 'A' + 10); - } else if ((asc >= 'a') && (asc <= 'f')) { - bcd = (byte) (asc - 'a' + 10); - } else { - bcd = (byte) (asc - 48); - } - return bcd; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/collection/UniqueKeySet.java b/bus-core/src/main/java/org/aoju/bus/core/collection/UniqueKeySet.java index d8cd55fdb3..65388c1828 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/collection/UniqueKeySet.java +++ b/bus-core/src/main/java/org/aoju/bus/core/collection/UniqueKeySet.java @@ -68,7 +68,7 @@ public UniqueKeySet(Function uniqueGenerator) { * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 */ public UniqueKeySet(boolean isLinked, Function uniqueGenerator) { - this(MapBuilder.create(isLinked), uniqueGenerator); + this(MapBuilder.of(isLinked), uniqueGenerator); } /** @@ -89,7 +89,7 @@ public UniqueKeySet(Function uniqueGenerator, Collection c) { * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 */ public UniqueKeySet(int initialCapacity, float loadFactor, Function uniqueGenerator) { - this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor)), uniqueGenerator); + this(MapBuilder.of(new HashMap<>(initialCapacity, loadFactor)), uniqueGenerator); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java index e0a1c69bf1..995fd86f69 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java @@ -43,12 +43,11 @@ */ public class ArrayConverter extends AbstractConverter { - private static final long serialVersionUID = 1L; /** * 单例对象 */ public static final ArrayConverter INSTANCE = new ArrayConverter(); - + private static final long serialVersionUID = 1L; /** * 是否忽略元素转换错误 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/BeanConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/BeanConverter.java index ce9215f886..0cbd60a4e0 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/BeanConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/BeanConverter.java @@ -102,7 +102,7 @@ private Object convertInternal(final Type targetType, final Class targetClass BeanKit.isBean(value.getClass())) { if (value instanceof Map && targetClass.isInterface()) { // 将Map动态代理为Bean - return MapProxy.create((Map) value).toProxyBean(targetClass); + return MapProxy.of((Map) value).toProxyBean(targetClass); } // 限定被转换对象类型 diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/Convert.java b/bus-core/src/main/java/org/aoju/bus/core/convert/Convert.java index 65e6d4183d..c5bba48770 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/Convert.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/Convert.java @@ -799,7 +799,7 @@ public static String toSBC(final String input, final Set notConvertSe return new String(c); } - /** + /** * 全角转半角 * * @param input String. diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/DateConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/DateConverter.java index b9110c59a2..bcb57d006c 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/DateConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/DateConverter.java @@ -41,10 +41,8 @@ */ public class DateConverter extends AbstractConverter { - private static final long serialVersionUID = 1L; - public static final DateConverter INSTANCE = new DateConverter(); - + private static final long serialVersionUID = 1L; /** * 日期格式化 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java index c780560394..485e78e33f 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java @@ -43,10 +43,8 @@ */ public class EnumConverter extends AbstractConverter { - private static final long serialVersionUID = 1L; - public static final EnumConverter INSTANCE = new EnumConverter(); - + private static final long serialVersionUID = 1L; private static final WeakMap, Map, Method>> VALUE_OF_METHOD_CACHE = new WeakMap<>(); /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java index 5f02f21445..989c1158b4 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java @@ -60,9 +60,8 @@ */ public class NumberConverter extends AbstractConverter { - private static final long serialVersionUID = 1L; - public static final NumberConverter INSTANCE = new NumberConverter(); + private static final long serialVersionUID = 1L; /** * 转换对象为数字,支持的对象包括: @@ -197,29 +196,6 @@ protected static Number convert(final Object value, final Class targetClass, final Object value) { - return convert(value, (Class) targetClass, this::convertToString); - } - - @Override - protected String convertToString(final Object value) { - final String result = StringKit.trim(super.convertToString(value)); - if (null != result && result.length() > 1) { - // 非单个字符才判断末尾的标识符 - final char c = Character.toUpperCase(result.charAt(result.length() - 1)); - if (c == 'D' || c == 'L' || c == 'F') { - // 类型标识形式(例如123.6D) - return StringKit.subPre(result, -1); - } - } - - if (StringKit.isEmpty(result)) { - throw new ConvertException("Can not convert empty value to Number!"); - } - return result; - } - /** * 转换为BigDecimal * 如果给定的值为空,或者转换失败,返回默认值 @@ -257,4 +233,27 @@ private static BigInteger toBigInteger(Object value, Function fu return MathKit.toBigInteger(func.apply(value)); } + @Override + protected Number convertInternal(final Class targetClass, final Object value) { + return convert(value, (Class) targetClass, this::convertToString); + } + + @Override + protected String convertToString(final Object value) { + final String result = StringKit.trim(super.convertToString(value)); + if (null != result && result.length() > 1) { + // 非单个字符才判断末尾的标识符 + final char c = Character.toUpperCase(result.charAt(result.length() - 1)); + if (c == 'D' || c == 'L' || c == 'F') { + // 类型标识形式(例如123.6D) + return StringKit.subPre(result, -1); + } + } + + if (StringKit.isEmpty(result)) { + throw new ConvertException("Can not convert empty value to Number!"); + } + return result; + } + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/PrimitiveConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/PrimitiveConverter.java index 70b8ba9a56..594e91ded5 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/PrimitiveConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/PrimitiveConverter.java @@ -50,9 +50,8 @@ */ public class PrimitiveConverter extends AbstractConverter { - private static final long serialVersionUID = 1L; - public static final PrimitiveConverter INSTANCE = new PrimitiveConverter(); + private static final long serialVersionUID = 1L; /** * 将指定值转换为原始类型的值 diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/TemporalConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/TemporalConverter.java index dee215d7f7..ec7a518796 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/TemporalConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/TemporalConverter.java @@ -58,10 +58,8 @@ */ public class TemporalConverter extends AbstractConverter { - private static final long serialVersionUID = 1L; - public static final TemporalConverter INSTANCE = new TemporalConverter(); - + private static final long serialVersionUID = 1L; /** * 日期格式化 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/image/Images.java b/bus-core/src/main/java/org/aoju/bus/core/image/Images.java index 3674e9f727..ab6c0d52cc 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/image/Images.java +++ b/bus-core/src/main/java/org/aoju/bus/core/image/Images.java @@ -571,6 +571,26 @@ public static BufferedImage makeBlur(BufferedImage srcImage, int radius) { return srcImage; } + /** + * 圆角 + * + * @param srcImage 图片流 + * @param width 宽度 + * @param height 高度 + * @param radius 半径 + * @return 图片流 + */ + public static BufferedImage makeRoundCorner(BufferedImage srcImage, int width, int height, int radius) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.fillRoundRect(0, 0, width, height, radius, radius); + g.setComposite(AlphaComposite.SrcIn); + g.drawImage(srcImage, 0, 0, width, height, null); + g.dispose(); + return image; + } + /** * 将图片绘制在背景上 * @@ -1150,26 +1170,6 @@ public Images stroke(Color color, Stroke stroke) { return this; } - /** - * 圆角 - * - * @param srcImage 图片流 - * @param width 宽度 - * @param height 高度 - * @param radius 半径 - * @return 图片流 - */ - public static BufferedImage makeRoundCorner(BufferedImage srcImage, int width, int height, int radius) { - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = image.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.fillRoundRect(0, 0, width, height, radius, radius); - g.setComposite(AlphaComposite.SrcIn); - g.drawImage(srcImage, 0, 0, width, height, null); - g.dispose(); - return image; - } - /** * 获取int类型的图片类型 * diff --git a/bus-core/src/main/java/org/aoju/bus/core/image/NeuQuant.java b/bus-core/src/main/java/org/aoju/bus/core/image/NeuQuant.java index 27791f9908..753b57a6bf 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/image/NeuQuant.java +++ b/bus-core/src/main/java/org/aoju/bus/core/image/NeuQuant.java @@ -69,16 +69,12 @@ write output image using inxsearch(b,g,r) */ /* defs for freq and bias */ protected static final int INTBIASSHIFT = Normal._16; /* bias for fractions */ protected static final int INTBIAS = (1 << INTBIASSHIFT); - - protected static final int BETASHIFT = 10; - - protected static final int GAMMASHIFT = 10; /* gamma = 1024 */ - - protected static final int GAMMA = (1 << GAMMASHIFT); protected static final int BETA = (INTBIAS >> BETASHIFT); /* beta = 1/1024 */ protected static final int BETAGAMMA = (INTBIAS << (GAMMASHIFT - BETASHIFT)); - + protected static final int BETASHIFT = 10; + protected static final int GAMMASHIFT = 10; /* gamma = 1024 */ + protected static final int GAMMA = (1 << GAMMASHIFT); /* defs for decreasing radius factor */ protected static final int INITRAD = (NETSIZE >> 3); /* for 256 cols, radius starts */ protected static final int RADIUSBIASSHIFT = 6; /* at 32.0 biased by 6 bits */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/file/Tailer.java b/bus-core/src/main/java/org/aoju/bus/core/io/file/Tailer.java index b9645563de..ab0441e098 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/file/Tailer.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/file/Tailer.java @@ -121,13 +121,6 @@ public Tailer(final File file, final Charset charset, final XConsumer li this.executorService = Executors.newSingleThreadScheduledExecutor(); } - /** - * 开始监听 - */ - public void start() { - start(false); - } - /** * 检查文件有效性 * @@ -142,6 +135,13 @@ private static void checkFile(final File file) { } } + /** + * 开始监听 + */ + public void start() { + start(false); + } + /** * 结束,此方法需在异步模式或 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Enums.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Enums.java index 9110511c0d..aa986ae4ef 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Enums.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Enums.java @@ -10,65 +10,65 @@ */ public interface Enums> extends Serializable { - String name(); + String name(); - /** - * 在中文语境下,多数时间枚举会配合一个中文说明 - * - * @return enum名 - */ - default String text() { - return name(); - } + /** + * 在中文语境下,多数时间枚举会配合一个中文说明 + * + * @return enum名 + */ + default String text() { + return name(); + } - int intVal(); + int intVal(); - /** - * 获取所有枚举对象 - * - * @return 枚举对象数组 - */ - default E[] items() { - return (E[]) this.getClass().getEnumConstants(); - } + /** + * 获取所有枚举对象 + * + * @return 枚举对象数组 + */ + default E[] items() { + return (E[]) this.getClass().getEnumConstants(); + } - /** - * 通过int类型值查找兄弟其他枚举 - * - * @param intVal int值 - * @return Enum - */ - default E from(final Integer intVal) { - if (intVal == null) { - return null; - } - final E[] vs = items(); - for (final E enumItem : vs) { - if (enumItem.intVal() == intVal) { - return enumItem; - } - } - return null; - } + /** + * 通过int类型值查找兄弟其他枚举 + * + * @param intVal int值 + * @return Enum + */ + default E from(final Integer intVal) { + if (intVal == null) { + return null; + } + final E[] vs = items(); + for (final E enumItem : vs) { + if (enumItem.intVal() == intVal) { + return enumItem; + } + } + return null; + } - /** - * 通过String类型的值转换,根据实现可以用name/text - * - * @param strVal String值 - * @return Enum - */ - default E from(final String strVal) { - if (strVal == null) { - return null; - } - final E[] vs = items(); - for (final E enumItem : vs) { - if (strVal.equalsIgnoreCase(enumItem.name())) { - return enumItem; - } - } - return null; - } + /** + * 通过String类型的值转换,根据实现可以用name/text + * + * @param strVal String值 + * @return Enum + */ + default E from(final String strVal) { + if (strVal == null) { + return null; + } + final E[] vs = items(); + for (final E enumItem : vs) { + if (strVal.equalsIgnoreCase(enumItem.name())) { + return enumItem; + } + } + return null; + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/ansi/Ansi8BitColor.java b/bus-core/src/main/java/org/aoju/bus/core/lang/ansi/Ansi8BitColor.java index 1134123995..f8c8d5b0e3 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/ansi/Ansi8BitColor.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/ansi/Ansi8BitColor.java @@ -44,6 +44,21 @@ public final class Ansi8BitColor implements AnsiElement { private static final String PREFIX_FORE = "38;5;"; private static final String PREFIX_BACK = "48;5;"; + private final String prefix; + private final int code; + + /** + * 构造 + * + * @param prefix 前缀 + * @param code 颜色代码(0-255) + * @throws IllegalArgumentException 颜色代码不在0~255范围内 + */ + private Ansi8BitColor(String prefix, int code) { + Assert.isTrue(code >= 0 && code <= 255, "Code must be between 0 and 255"); + this.prefix = prefix; + this.code = code; + } /** * 前景色ANSI颜色实例 @@ -65,22 +80,6 @@ public static Ansi8BitColor background(int code) { return new Ansi8BitColor(PREFIX_BACK, code); } - private final String prefix; - private final int code; - - /** - * 构造 - * - * @param prefix 前缀 - * @param code 颜色代码(0-255) - * @throws IllegalArgumentException 颜色代码不在0~255范围内 - */ - private Ansi8BitColor(String prefix, int code) { - Assert.isTrue(code >= 0 && code <= 255, "Code must be between 0 and 255"); - this.prefix = prefix; - this.code = code; - } - /** * 获取颜色代码(0-255) * diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/range/Range.java b/bus-core/src/main/java/org/aoju/bus/core/lang/range/Range.java index fa459940a0..e255fceceb 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/range/Range.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/range/Range.java @@ -44,211 +44,211 @@ */ public class Range implements Iterable, Iterator, Serializable { - private static final long serialVersionUID = 1L; - /** - * 起始对象 - */ - private final T start; - /** - * 结束对象 - */ - private final T end; - /** - * 步进 - */ - private final Stepper stepper; - /** - * 是否包含第一个元素 - */ - private final boolean includeStart; - /** - * 是否包含最后一个元素 - */ - private final boolean includeEnd; - /** - * 锁保证线程安全 - */ - private Lock lock = new ReentrantLock(); - /** - * 下一个对象 - */ - private T next; - /** - * 索引 - */ - private int index = 0; - - /** - * 构造 - * - * @param start 起始对象(包括) - * @param stepper 步进 - */ - public Range(final T start, final Stepper stepper) { - this(start, null, stepper); - } - - /** - * 构造 - * - * @param start 起始对象(包含) - * @param end 结束对象(包含) - * @param stepper 步进 - */ - public Range(final T start, final T end, final Stepper stepper) { - this(start, end, stepper, true, true); - } - - /** - * 构造 - * - * @param start 起始对象 - * @param end 结束对象 - * @param stepper 步进 - * @param isIncludeStart 是否包含第一个元素 - * @param isIncludeEnd 是否包含最后一个元素 - */ - public Range(final T start, final T end, final Stepper stepper, final boolean isIncludeStart, final boolean isIncludeEnd) { - Assert.notNull(start, "First element must be not null!"); - this.start = start; - this.end = end; - this.stepper = stepper; - this.next = safeStep(this.start); - this.includeStart = isIncludeStart; - this.includeEnd = isIncludeEnd; - } - - /** - * 禁用锁,调用此方法后不再使用锁保护 - * - * @return this - */ - public Range disableLock() { - this.lock = new AtomicNoLock(); - return this; - } - - @Override - public boolean hasNext() { - lock.lock(); - try { - if (0 == this.index && this.includeStart) { - return true; - } - if (null == this.next) { - return false; - } else if (false == includeEnd && this.next.equals(this.end)) { - return false; - } - } finally { - lock.unlock(); - } - return true; - } - - @Override - public T next() { - lock.lock(); - try { - if (false == this.hasNext()) { - throw new NoSuchElementException("Has no next range!"); - } - return nextUncheck(); - } finally { - lock.unlock(); - } - } - - /** - * 获取下一个元素,并将下下个元素准备好 - */ - private T nextUncheck() { - final T current; - if (0 == this.index) { - current = start; - if (false == this.includeStart) { - // 获取下一组元素 - index++; - return nextUncheck(); - } - } else { - current = next; - this.next = safeStep(this.next); - } - - index++; - return current; - } - - /** - * 不抛异常的获取下一步进的元素,如果获取失败返回{@code null} - * - * @param base 上一个元素 - * @return 下一步进 - */ - private T safeStep(final T base) { - final int index = this.index; - T next = null; - try { - next = stepper.step(base, this.end, index); - } catch (final Exception e) { - // ignore - } - - return next; - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Can not remove ranged element!"); - } - - @Override - public Iterator iterator() { - return this; - } - - /** - * 重置Range - * - * @return this - */ - public Range reset() { - lock.lock(); - try { - this.index = 0; - this.next = safeStep(this.start); - } finally { - lock.unlock(); - } - return this; - } - - /** - * 步进接口,此接口用于实现如何对一个对象按照指定步进增加步进 - * 步进接口可以定义以下逻辑: - * - *
-	 * 1、步进规则,即对象如何做步进
-	 * 2、步进大小,通过实现此接口,在实现类中定义一个对象属性,可灵活定义步进大小
-	 * 3、限制range个数,通过实现此接口,在实现类中定义一个对象属性,可灵活定义limit,限制range个数
-	 * 
- * - * @param 需要增加步进的对象 - */ - @FunctionalInterface - public interface Stepper { - /** - * 增加步进 - * 增加步进后的返回值如果为{@code null}则表示步进结束 - * 用户需根据end参数自行定义边界,当达到边界时返回null表示结束,否则Range中边界对象无效,会导致无限循环 - * - * @param current 上一次增加步进后的基础对象 - * @param end 结束对象 - * @param index 当前索引(步进到第几个元素),从0开始计数 - * @return 增加步进后的对象 - */ - T step(T current, T end, int index); - } + private static final long serialVersionUID = 1L; + /** + * 起始对象 + */ + private final T start; + /** + * 结束对象 + */ + private final T end; + /** + * 步进 + */ + private final Stepper stepper; + /** + * 是否包含第一个元素 + */ + private final boolean includeStart; + /** + * 是否包含最后一个元素 + */ + private final boolean includeEnd; + /** + * 锁保证线程安全 + */ + private Lock lock = new ReentrantLock(); + /** + * 下一个对象 + */ + private T next; + /** + * 索引 + */ + private int index = 0; + + /** + * 构造 + * + * @param start 起始对象(包括) + * @param stepper 步进 + */ + public Range(final T start, final Stepper stepper) { + this(start, null, stepper); + } + + /** + * 构造 + * + * @param start 起始对象(包含) + * @param end 结束对象(包含) + * @param stepper 步进 + */ + public Range(final T start, final T end, final Stepper stepper) { + this(start, end, stepper, true, true); + } + + /** + * 构造 + * + * @param start 起始对象 + * @param end 结束对象 + * @param stepper 步进 + * @param isIncludeStart 是否包含第一个元素 + * @param isIncludeEnd 是否包含最后一个元素 + */ + public Range(final T start, final T end, final Stepper stepper, final boolean isIncludeStart, final boolean isIncludeEnd) { + Assert.notNull(start, "First element must be not null!"); + this.start = start; + this.end = end; + this.stepper = stepper; + this.next = safeStep(this.start); + this.includeStart = isIncludeStart; + this.includeEnd = isIncludeEnd; + } + + /** + * 禁用锁,调用此方法后不再使用锁保护 + * + * @return this + */ + public Range disableLock() { + this.lock = new AtomicNoLock(); + return this; + } + + @Override + public boolean hasNext() { + lock.lock(); + try { + if (0 == this.index && this.includeStart) { + return true; + } + if (null == this.next) { + return false; + } else if (false == includeEnd && this.next.equals(this.end)) { + return false; + } + } finally { + lock.unlock(); + } + return true; + } + + @Override + public T next() { + lock.lock(); + try { + if (false == this.hasNext()) { + throw new NoSuchElementException("Has no next range!"); + } + return nextUncheck(); + } finally { + lock.unlock(); + } + } + + /** + * 获取下一个元素,并将下下个元素准备好 + */ + private T nextUncheck() { + final T current; + if (0 == this.index) { + current = start; + if (false == this.includeStart) { + // 获取下一组元素 + index++; + return nextUncheck(); + } + } else { + current = next; + this.next = safeStep(this.next); + } + + index++; + return current; + } + + /** + * 不抛异常的获取下一步进的元素,如果获取失败返回{@code null} + * + * @param base 上一个元素 + * @return 下一步进 + */ + private T safeStep(final T base) { + final int index = this.index; + T next = null; + try { + next = stepper.step(base, this.end, index); + } catch (final Exception e) { + // ignore + } + + return next; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Can not remove ranged element!"); + } + + @Override + public Iterator iterator() { + return this; + } + + /** + * 重置Range + * + * @return this + */ + public Range reset() { + lock.lock(); + try { + this.index = 0; + this.next = safeStep(this.start); + } finally { + lock.unlock(); + } + return this; + } + + /** + * 步进接口,此接口用于实现如何对一个对象按照指定步进增加步进 + * 步进接口可以定义以下逻辑: + * + *
+     * 1、步进规则,即对象如何做步进
+     * 2、步进大小,通过实现此接口,在实现类中定义一个对象属性,可灵活定义步进大小
+     * 3、限制range个数,通过实现此接口,在实现类中定义一个对象属性,可灵活定义limit,限制range个数
+     * 
+ * + * @param 需要增加步进的对象 + */ + @FunctionalInterface + public interface Stepper { + /** + * 增加步进 + * 增加步进后的返回值如果为{@code null}则表示步进结束 + * 用户需根据end参数自行定义边界,当达到边界时返回null表示结束,否则Range中边界对象无效,会导致无限循环 + * + * @param current 上一次增加步进后的基础对象 + * @param end 结束对象 + * @param index 当前索引(步进到第几个元素),从0开始计数 + * @return 增加步进后的对象 + */ + T step(T current, T end, int index); + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java b/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java index 14ca611dd0..c501927e16 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java @@ -149,7 +149,7 @@ public static java.lang.invoke.MethodHandle findConstructor(Class callerClass * * * @param 返回结果类型 - * @param object 接口的子对象或代理对象 + * @param object 接口的子对象或代理对象 * @param methodName 方法名称 * @param args 参数 * @return 结果 @@ -195,7 +195,7 @@ public static T invoke(Object object, Method method, Object... args) { * * * @param 返回结果类型 - * @param object 接口的子对象或代理对象 + * @param object 接口的子对象或代理对象 * @param method 方法 * @param args 参数 * @return 结果 @@ -222,7 +222,7 @@ public static T invokeSpecial(Object object, Method method, Object... args) * * @param 返回结果类型 * @param isSpecial 是否为特殊方法(private、static等) - * @param object 接口的子对象或代理对象 + * @param object 接口的子对象或代理对象 * @param method 方法 * @param args 参数 * @return 结果 diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractCollValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractCollValueMap.java index 090fa54812..c13b51cadb 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractCollValueMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractCollValueMap.java @@ -25,123 +25,166 @@ ********************************************************************************/ package org.aoju.bus.core.map; +import org.aoju.bus.core.lang.Optional; import org.aoju.bus.core.toolkit.CollKit; +import org.aoju.bus.core.toolkit.ObjectKit; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; /** * 值作为集合的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 * * @param 键类型 * @param 值类型 - * @param 集合类型 * @author Kimi Liu * @since Java 17+ */ -public abstract class AbstractCollValueMap> extends MapWrapper { +public abstract class AbstractCollValueMap extends MapWrapper> implements MultiValueMap { /** * 默认集合初始大小 */ protected static final int DEFAULT_COLLECTION_INITIAL_CAPACITY = 3; + private static final long serialVersionUID = 1L; /** - * 构造 + * 基于{@link HashMap}构造一个多值映射集合 */ public AbstractCollValueMap() { - this(DEFAULT_INITIAL_CAPACITY); + super(new HashMap<>(16)); } /** - * 构造 + * 基于{@link HashMap}构造一个多值映射集合 * - * @param initialCapacity 初始大小 + * @param map 提供初始数据的集合 */ - public AbstractCollValueMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); + public AbstractCollValueMap(Map> map) { + super(new HashMap<>(map)); } /** - * 构造 + * 使用{@code mapFactory}创建的集合构造一个多值映射Map集合 * - * @param m Map + * @param mapFactory 生成集合的工厂方法 */ - public AbstractCollValueMap(Map m) { - this(DEFAULT_LOAD_FACTOR, m); + public AbstractCollValueMap(Supplier>> mapFactory) { + super(mapFactory); } /** - * 构造 + * 将集合中的全部元素对追加到指定键对应的值集合中,效果等同于: + *
{@code
+     * coll.forEach(t -> map.putValue(key, t))
+     * }
* - * @param loadFactor 加载因子 - * @param m Map + * @param key 键 + * @param coll 待添加的值集合 + * @return 是否成功添加 */ - public AbstractCollValueMap(float loadFactor, Map m) { - this(m.size(), loadFactor); - this.putAll(m); + @Override + public boolean putAllValues(K key, Collection coll) { + if (ObjectKit.isNull(coll)) { + return false; + } + return super.computeIfAbsent(key, k -> createCollection()) + .addAll(coll); } /** - * 构造 + * 向指定键对应的值集合追加值,效果等同于: + *
{@code
+     * map.computeIfAbsent(key, k -> new Collection()).add(value)
+     * }
* - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param key 键 + * @param value 值 + * @return 是否成功添加 */ - public AbstractCollValueMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + @Override + public boolean putValue(K key, V value) { + return super.computeIfAbsent(key, k -> createCollection()) + .add(value); } /** - * 放入所有value + * 将值从指定键下的值集合中删除 * - * @param m valueMap + * @param key 键 + * @param value 值 + * @return 是否成功删除 */ - public void putAllValues(Map> m) { - if (null != m) { - m.forEach((key, valueColl) -> { - if (null != valueColl) { - valueColl.forEach((value) -> putValue(key, value)); - } - }); - } + @Override + public boolean removeValue(K key, V value) { + return Optional.ofNullable(super.get(key)) + .map(t -> t.remove(value)) + .orElse(false); } /** - * 放入Value - * 如果键对应值列表有值,加入,否则创建一个新列表后加入 + * 将一批值从指定键下的值集合中删除 * - * @param key 键 - * @param value 值 + * @param key 键 + * @param values 值 + * @return 是否成功删除 */ - public void putValue(K key, V value) { - C collection = this.get(key); - if (null == collection) { - collection = createCollection(); - this.put(key, collection); + @Override + public boolean removeAllValues(K key, Collection values) { + if (CollKit.isEmpty(values)) { + return false; } - collection.add(value); + Collection coll = get(key); + return ObjectKit.isNotNull(coll) && coll.removeAll(values); } /** - * 获取值 + * 根据条件过滤所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 * - * @param key 键 - * @param index 第几个值的索引,越界返回null - * @return 值或null + * @param filter 判断方法 + * @return 当前实例 + */ + @Override + public MultiValueMap filterAllValues(BiPredicate filter) { + entrySet().forEach(e -> { + K k = e.getKey(); + Collection coll = e.getValue().stream() + .filter(v -> filter.test(k, v)) + .collect(Collectors.toCollection(this::createCollection)); + e.setValue(coll); + }); + return this; + } + + /** + * 根据条件替换所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param operate 替换方法 + * @return 当前实例 */ - public V get(K key, int index) { - final Collection collection = get(key); - return CollKit.get(collection, index); + @Override + public MultiValueMap replaceAllValues(BiFunction operate) { + entrySet().forEach(e -> { + K k = e.getKey(); + Collection coll = e.getValue().stream() + .map(v -> operate.apply(k, v)) + .collect(Collectors.toCollection(this::createCollection)); + e.setValue(coll); + }); + return this; } /** - * 创建集合 + * 创建集合
* 此方法用于创建在putValue后追加值所在的集合,子类实现此方法创建不同类型的集合 * * @return {@link Collection} */ - protected abstract C createCollection(); + public abstract Collection createCollection(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractEntry.java b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractEntry.java index 4c2ed843c7..03074a671b 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractEntry.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractEntry.java @@ -42,12 +42,12 @@ public abstract class AbstractEntry implements Map.Entry { @Override - public V setValue(V value) { + public V setValue(final V value) { throw new UnsupportedOperationException("Entry is read only."); } @Override - public boolean equals(Object object) { + public boolean equals(final Object object) { if (object instanceof Map.Entry) { final Map.Entry that = (Map.Entry) object; return ObjectKit.equals(this.getKey(), that.getKey()) diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractTable.java b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractTable.java index e120793714..d1754eeacf 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractTable.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractTable.java @@ -56,11 +56,11 @@ public abstract class AbstractTable implements Table { private Set> cellSet; @Override - public boolean equals(Object object) { - if (object == this) { + public boolean equals(final Object obj) { + if (obj == this) { return true; - } else if (object instanceof Table) { - final Table that = (Table) object; + } else if (obj instanceof Table) { + final Table that = (Table) obj; return this.cellSet().equals(that.cellSet()); } else { return false; @@ -79,13 +79,13 @@ public String toString() { @Override public Collection values() { - Collection result = values; + final Collection result = values; return (result == null) ? values = new Values() : result; } @Override public Set> cellSet() { - Set> result = cellSet; + final Set> result = cellSet; return (result == null) ? cellSet = new CellSet() : result; } @@ -109,7 +109,7 @@ private static class SimpleCell implements Cell, Serializable private final C columnKey; private final V value; - SimpleCell(R rowKey, C columnKey, V value) { + SimpleCell(final R rowKey, final C columnKey, final V value) { this.rowKey = rowKey; this.columnKey = columnKey; this.value = value; @@ -131,7 +131,7 @@ public V getValue() { } @Override - public boolean equals(Object object) { + public boolean equals(final Object object) { if (object == this) { return true; } @@ -163,7 +163,7 @@ public Iterator iterator() { } @Override - public boolean contains(Object o) { + public boolean contains(final Object o) { return containsValue((V) o); } @@ -180,10 +180,10 @@ public int size() { private class CellSet extends AbstractSet> { @Override - public boolean contains(Object o) { + public boolean contains(final Object o) { if (o instanceof Cell) { final Cell cell = (Cell) o; - Map row = getRow(cell.getRowKey()); + final Map row = getRow(cell.getRowKey()); if (null != row) { return ObjectKit.equals(row.get(cell.getColumnKey()), cell.getValue()); } @@ -192,7 +192,7 @@ public boolean contains(Object o) { } @Override - public boolean remove(Object o) { + public boolean remove(final Object o) { if (contains(o)) { final Cell cell = (Cell) o; AbstractTable.this.remove(cell.getRowKey(), cell.getColumnKey()); @@ -206,7 +206,7 @@ public void clear() { } @Override - public Iterator> iterator() { + public Iterator> iterator() { return new CellIterator(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseLinkedMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseLinkedMap.java old mode 100755 new mode 100644 index 3f3181e961..38b9167c47 --- a/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseLinkedMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseLinkedMap.java @@ -39,6 +39,8 @@ */ public class CamelCaseLinkedMap extends CamelCaseMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ @@ -51,7 +53,7 @@ public CamelCaseLinkedMap() { * * @param initialCapacity 初始大小 */ - public CamelCaseLinkedMap(int initialCapacity) { + public CamelCaseLinkedMap(final int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } @@ -60,7 +62,7 @@ public CamelCaseLinkedMap(int initialCapacity) { * * @param m Map */ - public CamelCaseLinkedMap(Map m) { + public CamelCaseLinkedMap(final Map m) { this(DEFAULT_LOAD_FACTOR, m); } @@ -70,7 +72,7 @@ public CamelCaseLinkedMap(Map m) { * @param loadFactor 加载因子 * @param map 数据会被默认拷贝到一个新的LinkedHashMap中 */ - public CamelCaseLinkedMap(float loadFactor, Map map) { + public CamelCaseLinkedMap(final float loadFactor, final Map map) { this(map.size(), loadFactor); this.putAll(map); } @@ -81,7 +83,7 @@ public CamelCaseLinkedMap(float loadFactor, Map map) { * @param initialCapacity 初始大小 * @param loadFactor 加载因子 */ - public CamelCaseLinkedMap(int initialCapacity, float loadFactor) { + public CamelCaseLinkedMap(final int initialCapacity, final float loadFactor) { super(new LinkedHashMap<>(initialCapacity, loadFactor)); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseMap.java old mode 100755 new mode 100644 index a56208e2b2..f412ee4cfa --- a/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseMap.java @@ -43,6 +43,8 @@ */ public class CamelCaseMap extends FuncKeyMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ @@ -55,17 +57,17 @@ public CamelCaseMap() { * * @param initialCapacity 初始大小 */ - public CamelCaseMap(int initialCapacity) { + public CamelCaseMap(final int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 构造 * - * @param map Map + * @param m Map */ - public CamelCaseMap(Map map) { - this(DEFAULT_LOAD_FACTOR, map); + public CamelCaseMap(final Map m) { + this(DEFAULT_LOAD_FACTOR, m); } /** @@ -74,7 +76,7 @@ public CamelCaseMap(Map map) { * @param loadFactor 加载因子 * @param map 初始Map,数据会被默认拷贝到一个新的HashMap中 */ - public CamelCaseMap(float loadFactor, Map map) { + public CamelCaseMap(final float loadFactor, final Map map) { this(map.size(), loadFactor); this.putAll(map); } @@ -85,8 +87,8 @@ public CamelCaseMap(float loadFactor, Map map) { * @param initialCapacity 初始大小 * @param loadFactor 加载因子 */ - public CamelCaseMap(int initialCapacity, float loadFactor) { - this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor))); + public CamelCaseMap(final int initialCapacity, final float loadFactor) { + this(MapBuilder.of(new HashMap<>(initialCapacity, loadFactor))); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveLinkedMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveLinkedMap.java old mode 100755 new mode 100644 index 2fcb2e026c..baed18401c --- a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveLinkedMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveLinkedMap.java @@ -39,6 +39,8 @@ */ public class CaseInsensitiveLinkedMap extends CaseInsensitiveMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ @@ -51,17 +53,17 @@ public CaseInsensitiveLinkedMap() { * * @param initialCapacity 初始大小 */ - public CaseInsensitiveLinkedMap(int initialCapacity) { + public CaseInsensitiveLinkedMap(final int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 构造 * - * @param map Map + * @param m Map */ - public CaseInsensitiveLinkedMap(Map map) { - this(DEFAULT_LOAD_FACTOR, map); + public CaseInsensitiveLinkedMap(final Map m) { + this(DEFAULT_LOAD_FACTOR, m); } /** @@ -70,7 +72,7 @@ public CaseInsensitiveLinkedMap(Map map) { * @param loadFactor 加载因子 * @param map Map */ - public CaseInsensitiveLinkedMap(float loadFactor, Map map) { + public CaseInsensitiveLinkedMap(final float loadFactor, final Map map) { this(map.size(), loadFactor); this.putAll(map); } @@ -81,7 +83,7 @@ public CaseInsensitiveLinkedMap(float loadFactor, Map * @param initialCapacity 初始大小 * @param loadFactor 加载因子 */ - public CaseInsensitiveLinkedMap(int initialCapacity, float loadFactor) { + public CaseInsensitiveLinkedMap(final int initialCapacity, final float loadFactor) { super(new LinkedHashMap<>(initialCapacity, loadFactor)); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveMap.java old mode 100755 new mode 100644 index ee3d3fd4d1..0f57ae611e --- a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveMap.java @@ -41,6 +41,8 @@ */ public class CaseInsensitiveMap extends FuncKeyMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ @@ -53,7 +55,7 @@ public CaseInsensitiveMap() { * * @param initialCapacity 初始大小 */ - public CaseInsensitiveMap(int initialCapacity) { + public CaseInsensitiveMap(final int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } @@ -63,7 +65,7 @@ public CaseInsensitiveMap(int initialCapacity) { * * @param map 被包装的自定义Map创建器 */ - public CaseInsensitiveMap(Map map) { + public CaseInsensitiveMap(final Map map) { this(DEFAULT_LOAD_FACTOR, map); } @@ -73,7 +75,7 @@ public CaseInsensitiveMap(Map map) { * @param loadFactor 加载因子 * @param map Map */ - public CaseInsensitiveMap(float loadFactor, Map map) { + public CaseInsensitiveMap(final float loadFactor, final Map map) { this(map.size(), loadFactor); this.putAll(map); } @@ -84,8 +86,8 @@ public CaseInsensitiveMap(float loadFactor, Map map) { * @param initialCapacity 初始大小 * @param loadFactor 加载因子 */ - public CaseInsensitiveMap(int initialCapacity, float loadFactor) { - this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor))); + public CaseInsensitiveMap(final int initialCapacity, final float loadFactor) { + this(MapBuilder.of(new HashMap<>(initialCapacity, loadFactor))); } /** @@ -94,7 +96,7 @@ public CaseInsensitiveMap(int initialCapacity, float loadFactor) { * * @param emptyMapBuilder 被包装的自定义Map创建器 */ - CaseInsensitiveMap(MapBuilder emptyMapBuilder) { + CaseInsensitiveMap(final MapBuilder emptyMapBuilder) { super(emptyMapBuilder.build(), (Function & Serializable) (key) -> { if (key instanceof CharSequence) { key = key.toString().toLowerCase(); diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveTreeMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveTreeMap.java index 530047f68e..3954ab5141 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveTreeMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveTreeMap.java @@ -55,7 +55,7 @@ public CaseInsensitiveTreeMap() { * * @param map 初始Map */ - public CaseInsensitiveTreeMap(Map map) { + public CaseInsensitiveTreeMap(final Map map) { this(); this.putAll(map); } @@ -65,7 +65,7 @@ public CaseInsensitiveTreeMap(Map map) { * * @param map 初始Map,键值对会被复制到新的TreeMap中 */ - public CaseInsensitiveTreeMap(SortedMap map) { + public CaseInsensitiveTreeMap(final SortedMap map) { super(new TreeMap(map)); } @@ -74,7 +74,7 @@ public CaseInsensitiveTreeMap(SortedMap map) { * * @param comparator 比较器,{@code null}表示使用默认比较器 */ - public CaseInsensitiveTreeMap(Comparator comparator) { + public CaseInsensitiveTreeMap(final Comparator comparator) { super(new TreeMap<>(comparator)); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CollectionValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CollectionValueMap.java old mode 100755 new mode 100644 index d58e9441b3..dd6fd408ef --- a/bus-core/src/main/java/org/aoju/bus/core/map/CollectionValueMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CollectionValueMap.java @@ -31,91 +31,64 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; /** - * 值作为集合的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 + * {@link MultiValueMap}的通用实现,可视为值为{@link Collection}集合的{@link Map}集合 + * 构建时指定一个工厂方法用于生成原始的{@link Map}集合,然后再指定一个工厂方法用于生成自定义类型的值集合 + * 当调用{@link MultiValueMap}中格式为“putXXX”的方法时,将会为key创建值集合,并将key相同的值追加到集合中 * * @param 键类型 * @param 值类型 * @author Kimi Liu * @since Java 17+ */ -public class CollectionValueMap extends AbstractCollValueMap> { +public class CollectionValueMap extends AbstractCollValueMap { - private final XSupplier> collectionCreateFunc; + private static final long serialVersionUID = 1L; - /** - * 构造 - */ - public CollectionValueMap() { - this(DEFAULT_INITIAL_CAPACITY); - } - - /** - * 构造 - * - * @param initialCapacity 初始大小 - */ - public CollectionValueMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); - } - - /** - * 构造 - * - * @param m Map - */ - public CollectionValueMap(Map> m) { - this(DEFAULT_LOAD_FACTOR, m); - } + private final XSupplier> collFactory; /** - * 构造 + * 创建一个多值映射集合,基于{@code mapFactory}与{@code collFactory}实现 * - * @param loadFactor 加载因子 - * @param m Map + * @param mapFactory 生成集合的工厂方法 + * @param collFactory 生成值集合的工厂方法 */ - public CollectionValueMap(float loadFactor, Map> m) { - this(loadFactor, m, ArrayList::new); + public CollectionValueMap(Supplier>> mapFactory, XSupplier> collFactory) { + super(mapFactory); + this.collFactory = collFactory; } /** - * 构造 + * 创建一个多值映射集合,默认基于{@link HashMap}与{@code collFactory}生成的集合实现 * - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param collFactory 生成值集合的工厂方法 */ - public CollectionValueMap(int initialCapacity, float loadFactor) { - this(initialCapacity, loadFactor, ArrayList::new); + public CollectionValueMap(XSupplier> collFactory) { + this.collFactory = collFactory; } /** - * 构造 - * - * @param loadFactor 加载因子 - * @param m Map - * @param collectionCreateFunc Map中值的集合创建函数 + * 创建一个多值映射集合,默认基于{@link HashMap}与{@link ArrayList}实现 */ - public CollectionValueMap(float loadFactor, Map> m, XSupplier> collectionCreateFunc) { - this(m.size(), loadFactor, collectionCreateFunc); - this.putAll(m); + public CollectionValueMap() { + this.collFactory = ArrayList::new; } /** - * 构造 + * 创建一个多值映射集合,默认基于{@link HashMap}与{@link ArrayList}实现 * - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 - * @param collectionCreateFunc Map中值的集合创建函数 + * @param map 提供数据的原始集合 */ - public CollectionValueMap(int initialCapacity, float loadFactor, XSupplier> collectionCreateFunc) { - super(new HashMap<>(initialCapacity, loadFactor)); - this.collectionCreateFunc = collectionCreateFunc; + public CollectionValueMap(Map> map) { + super(map); + this.collFactory = ArrayList::new; } @Override - protected Collection createCollection() { - return collectionCreateFunc.get(); + public Collection createCollection() { + return collFactory.get(); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CustomKeyMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CustomKeyMap.java index b0638e9015..f070431c9a 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/CustomKeyMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CustomKeyMap.java @@ -37,18 +37,20 @@ */ public abstract class CustomKeyMap extends TransitionMap { + private static final long serialVersionUID = 1L; + /** * 构造 * 通过传入一个Map从而确定Map的类型,子类需创建一个空的Map,而非传入一个已有Map,否则值可能会被修改 * * @param map 被包装的Map,必须为空Map,否则自定义key会无效 */ - public CustomKeyMap(Map map) { + public CustomKeyMap(final Map map) { super(map); } @Override - protected V customValue(Object value) { + protected V customValue(final Object value) { return (V) value; } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java b/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java index 17e2ea7bc6..b921627c53 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java @@ -25,12 +25,13 @@ ********************************************************************************/ package org.aoju.bus.core.map; -import org.aoju.bus.core.beans.PathExpression; +import org.aoju.bus.core.beans.BeanPath; import org.aoju.bus.core.beans.copier.CopyOptions; import org.aoju.bus.core.convert.Convert; import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.getter.TypeGetter; import org.aoju.bus.core.lang.Assert; +import org.aoju.bus.core.lang.function.XFunction; import org.aoju.bus.core.lang.function.XSupplier; import org.aoju.bus.core.toolkit.BeanKit; import org.aoju.bus.core.toolkit.CollKit; @@ -47,9 +48,9 @@ */ public class Dictionary extends CustomKeyMap implements TypeGetter { - private static final long serialVersionUID = 1L; - private static final float DEFAULT_LOAD_FACTOR = 0.75f; - private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + static final float DEFAULT_LOAD_FACTOR = 0.75f; + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + private static final long serialVersionUID = 6135423866861206530L; /** * 是否大小写不敏感 @@ -150,11 +151,11 @@ public static Dictionary parse(final T bean) { */ @SafeVarargs public static Dictionary ofEntries(final Map.Entry... pairs) { - final Dictionary dict = of(); + final Dictionary dictionary = of(); for (final Map.Entry pair : pairs) { - dict.put(pair.getKey(), pair.getValue()); + dictionary.put(pair.getKey(), pair.getValue()); } - return dict; + return dictionary; } /** @@ -174,18 +175,18 @@ public static Dictionary ofEntries(final Map.Entry... pairs) { * @return this */ public static Dictionary ofKvs(final Object... keysAndValues) { - final Dictionary dict = of(); + final Dictionary dictionary = of(); String key = null; for (int i = 0; i < keysAndValues.length; i++) { - if (i % 1 == 0) { + if ((i & 1) == 0) { key = Convert.toString(keysAndValues[i]); } else { - dict.put(key, keysAndValues[i]); + dictionary.put(key, keysAndValues[i]); } } - return dict; + return dictionary; } @@ -359,13 +360,25 @@ public Object getObject(final String key, final Object defaultValue) { return getOrDefault(key, defaultValue); } + /** + * 根据lambda的方法引用,获取 + * + * @param func 方法引用 + * @param

参数类型 + * @param 返回值类型 + * @return 获取表达式对应属性和返回的对象 + */ + public T get(final XFunction func) { + final LambdaKit.Info lambdaInfo = LambdaKit.resolve(func); + return get(lambdaInfo.getFieldName(), lambdaInfo.getReturnType()); + } + /** * 获得特定类型值 * * @param 值类型 * @param attr 字段名 * @return 字段值 - */ public T getBean(final String attr) { return (T) get(attr); @@ -390,10 +403,9 @@ public T getBean(final String attr) { * @param 目标类型 * @param expression 表达式 * @return the object - */ public T getByPath(final String expression) { - return (T) PathExpression.create(expression).get(this); + return (T) BeanPath.of(expression).get(this); } /** @@ -418,7 +430,6 @@ public T getByPath(final String expression) { * @param expression 表达式 * @param resultType 返回值类型 * @return the object - */ public T getByPath(final String expression, final Type resultType) { return Convert.convert(resultType, getByPath(expression)); diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/DuplexingMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/DuplexingMap.java index dbde374a02..efa095ffa5 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/DuplexingMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/DuplexingMap.java @@ -51,12 +51,12 @@ public class DuplexingMap extends MapWrapper { * * @param raw 被包装的Map */ - public DuplexingMap(Map raw) { + public DuplexingMap(final Map raw) { super(raw); } @Override - public V put(K key, V value) { + public V put(final K key, final V value) { if (null != this.inverse) { this.inverse.put(value, key); } @@ -64,7 +64,7 @@ public V put(K key, V value) { } @Override - public void putAll(Map m) { + public void putAll(final Map m) { super.putAll(m); if (null != this.inverse) { m.forEach((key, value) -> this.inverse.put(value, key)); @@ -72,7 +72,7 @@ public void putAll(Map m) { } @Override - public V remove(Object key) { + public V remove(final Object key) { final V v = super.remove(key); if (null != this.inverse && null != v) { this.inverse.remove(v); @@ -81,7 +81,7 @@ public V remove(Object key) { } @Override - public boolean remove(Object key, Object value) { + public boolean remove(final Object key, final Object value) { return super.remove(key, value) && null != this.inverse && this.inverse.remove(value, key); } @@ -109,7 +109,7 @@ public Map getInverse() { * @param value 值 * @return 键 */ - public K getKey(V value) { + public K getKey(final V value) { return getInverse().get(value); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/FixedLinkedHashMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/FixedLinkedHashMap.java old mode 100755 new mode 100644 index e36517073d..bcdfc2a928 --- a/bus-core/src/main/java/org/aoju/bus/core/map/FixedLinkedHashMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/FixedLinkedHashMap.java @@ -40,6 +40,8 @@ */ public class FixedLinkedHashMap extends LinkedHashMap { + private static final long serialVersionUID = 1L; + /** * 容量,超过此容量自动删除末尾元素 */ @@ -54,7 +56,7 @@ public class FixedLinkedHashMap extends LinkedHashMap { * * @param capacity 容量,实际初始容量比容量大1 */ - public FixedLinkedHashMap(int capacity) { + public FixedLinkedHashMap(final int capacity) { super(capacity + 1, 1.0f, true); this.capacity = capacity; } @@ -73,7 +75,7 @@ public int getCapacity() { * * @param capacity 容量 */ - public void setCapacity(int capacity) { + public void setCapacity(final int capacity) { this.capacity = capacity; } @@ -87,7 +89,7 @@ public void setRemoveListener(final Consumer> removeListener) { } @Override - protected boolean removeEldestEntry(final java.util.Map.Entry eldest) { + protected boolean removeEldestEntry(final Map.Entry eldest) { // 当链表元素大于容量时,移除最老(最久未被使用)的元素 if (size() > this.capacity) { if (null != removeListener) { diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java index fbbd7fd44f..79bd66bf6a 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java @@ -25,14 +25,10 @@ ********************************************************************************/ package org.aoju.bus.core.map; -import org.aoju.bus.core.lang.Optional; import org.aoju.bus.core.toolkit.CollKit; import org.aoju.bus.core.toolkit.ObjectKit; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Function; @@ -83,28 +79,6 @@ default void putAll(Map> treeEntryMap) { }); } - /** - * 将指定节点从当前{@link Map}中删除 - *

    - *
  • 若存在父节点或子节点,则将其断开其与父节点或子节点的引用关系;
  • - *
  • - * 若同时存在父节点或子节点,则会在删除后将让子节点直接成为父节点的子节点,比如:
    - * 现有引用关系 a -> b -> c,删除 b 后,将有 a -> c - *
  • - *
- * - * @param key 节点的key - * @return 删除的节点,若key没有对应节点,则返回null - */ - @Override - TreeEntry remove(Object key); - - /** - * 将当前集合清空,并清除全部节点间的引用关系 - */ - @Override - void clear(); - /** * 批量添加节点 * @@ -320,7 +294,7 @@ default V getNodeValue(K key) { } /** - * 获取指定父节点直接关联的子节点
+ * 获取指定父节点直接关联的子节点 * 比如:若存在 a -> b -> c 的关系,此时输入 b 将返回 c,输入 a 将返回 b * * @param key key diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/FuncKeyMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/FuncKeyMap.java old mode 100644 new mode 100755 index 9a78dfec81..fb5b5afe46 --- a/bus-core/src/main/java/org/aoju/bus/core/map/FuncKeyMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/FuncKeyMap.java @@ -49,7 +49,7 @@ public class FuncKeyMap extends CustomKeyMap { * @param emptyMap Map,提供的空map * @param keyFunc 自定义KEY的函数 */ - public FuncKeyMap(Map emptyMap, Function keyFunc) { + public FuncKeyMap(final Map emptyMap, final Function keyFunc) { super(emptyMap); this.keyFunc = keyFunc; } @@ -61,7 +61,7 @@ public FuncKeyMap(Map emptyMap, Function keyFunc) { * @return 驼峰Key */ @Override - protected K customKey(Object key) { + protected K customKey(final Object key) { if (null != this.keyFunc) { return keyFunc.apply(key); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/FuncMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/FuncMap.java index 17407c59af..eae49a086e 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/FuncMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/FuncMap.java @@ -52,7 +52,7 @@ public class FuncMap extends TransitionMap { * @param keyFunc 自定义KEY的函数 * @param valueFunc 自定义value函数 */ - public FuncMap(Supplier> mapFactory, Function keyFunc, Function valueFunc) { + public FuncMap(final Supplier> mapFactory, final Function keyFunc, final Function valueFunc) { this(mapFactory.get(), keyFunc, valueFunc); } @@ -64,7 +64,7 @@ public FuncMap(Supplier> mapFactory, Function keyFunc, Func * @param keyFunc 自定义KEY的函数 * @param valueFunc 自定义value函数 */ - public FuncMap(Map emptyMap, Function keyFunc, Function valueFunc) { + public FuncMap(final Map emptyMap, final Function keyFunc, final Function valueFunc) { super(emptyMap); this.keyFunc = keyFunc; this.valueFunc = valueFunc; @@ -77,7 +77,7 @@ public FuncMap(Map emptyMap, Function keyFunc, Function 节点类型 + * @author Kimi Liu + * @since Java 17+ + */ +public class GraphMap extends SetValueMap { + + /** + * 添加边 + * + * @param target1 节点 + * @param target2 节点 + */ + public void putEdge(final T target1, final T target2) { + this.putValue(target1, target2); + this.putValue(target2, target1); + } + + /** + * 是否存在边 + * + * @param target1 节点 + * @param target2 节点 + * @return 是否 + */ + public boolean containsEdge(final T target1, final T target2) { + return this.getValues(target1).contains(target2) + && this.getValues(target2).contains(target1); + } + + /** + * 移除边 + * + * @param target1 节点 + * @param target2 节点 + */ + public void removeEdge(final T target1, final T target2) { + this.removeValue(target1, target2); + this.removeValue(target2, target1); + } + + /** + * 移除节点,并删除该节点与其他节点之间连成的边 + * + * @param target 目标对象 + */ + public void removePoint(final T target) { + final Collection associatedPoints = this.remove(target); + if (CollKit.isNotEmpty(associatedPoints)) { + associatedPoints.forEach(p -> this.removeValue(p, target)); + } + } + + /** + * 两节点是否存在直接或间接的关联 + * + * @param target1 节点 + * @param target2 节点 + * @return 两节点是否存在关联 + */ + public boolean containsAssociation(final T target1, final T target2) { + if (!this.containsKey(target1) || !this.containsKey(target2)) { + return false; + } + final AtomicBoolean flag = new AtomicBoolean(false); + visitAssociatedPoints(target1, t -> { + if (Objects.equals(t, target2)) { + flag.set(true); + return true; + } + return false; + }); + return flag.get(); + } + + /** + * 按广度优先,获得节点的所有直接或间接关联的节点,节点默认按添加顺序排序 + * + * @param target 节点 + * @param includeTarget 是否包含查询节点 + * @return 节点的所有关联节点 + */ + public Collection getAssociatedPoints(final T target, final boolean includeTarget) { + final Set points = visitAssociatedPoints(target, t -> false); + if (!includeTarget) { + points.remove(target); + } + return points; + } + + /** + * 获取节点的邻接节点 + * + * @param target 节点 + * @return 邻接节点 + */ + public Collection getAdjacentPoints(final T target) { + return this.getValues(target); + } + + /** + * 按广度优先,访问节点的所有关联节点 + */ + private Set visitAssociatedPoints(final T key, final Predicate breaker) { + if (!this.containsKey(key)) { + return Collections.emptySet(); + } + final Set accessed = new HashSet<>(); + final Deque deque = new LinkedList<>(); + deque.add(key); + while (!deque.isEmpty()) { + // 访问节点 + final T t = deque.removeFirst(); + if (accessed.contains(t)) { + continue; + } + accessed.add(t); + // 若符合条件则中断循环 + if (breaker.test(t)) { + break; + } + // 获取邻接节点 + final Collection neighbours = this.getValues(t); + if (!neighbours.isEmpty()) { + deque.addAll(neighbours); + } + } + return accessed; + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/LinkedForestMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/LinkedForestMap.java index 3122548e16..fb804d8342 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/LinkedForestMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/LinkedForestMap.java @@ -198,7 +198,7 @@ public Collection> values() { * @return 集合 */ @Override - public Set>> entrySet() { + public Set>> entrySet() { return nodes.entrySet().stream() .map(this::wrap) .collect(Collectors.toSet()); @@ -207,7 +207,7 @@ public Set>> entrySet() { /** * 将{@link TreeEntryNode}包装为{@link EntryNodeWrapper} */ - private Entry> wrap(final Entry> nodeEntry) { + private Map.Entry> wrap(final Map.Entry> nodeEntry) { return new EntryNodeWrapper<>(nodeEntry.getValue()); } @@ -379,7 +379,7 @@ public static class TreeEntryNode implements TreeEntry { * @param parent 节点的父节点 * @param key 节点的key */ - public TreeEntryNode(TreeEntryNode parent, K key) { + public TreeEntryNode(final TreeEntryNode parent, final K key) { this(parent, key, null); } @@ -390,7 +390,7 @@ public TreeEntryNode(TreeEntryNode parent, K key) { * @param key 节点的key * @param value 节点的value */ - public TreeEntryNode(TreeEntryNode parent, K key, V value) { + public TreeEntryNode(final TreeEntryNode parent, final K key, final V value) { this.parent = parent; this.key = key; this.value = value; @@ -442,7 +442,7 @@ public V getValue() { * @return 节点的旧value */ @Override - public V setValue(V value) { + public V setValue(final V value) { final V oldVal = getValue(); this.value = value; return oldVal; @@ -457,7 +457,7 @@ public V setValue(V value) { * @return 遍历到的最后一个节点 */ TreeEntryNode traverseParentNodes( - boolean includeCurrent, Consumer> consumer, Predicate> breakTraverse) { + final boolean includeCurrent, final Consumer> consumer, Predicate> breakTraverse) { breakTraverse = ObjectKit.defaultIfNull(breakTraverse, n -> false); TreeEntryNode curr = includeCurrent ? this : this.parent; while (ObjectKit.isNotNull(curr)) { @@ -512,7 +512,7 @@ public TreeEntryNode getDeclaredParent() { * @return 指定父节点,当不存在时返回null */ @Override - public TreeEntryNode getParent(K key) { + public TreeEntryNode getParent(final K key) { return traverseParentNodes(false, p -> { }, p -> p.equalsKey(key)); } @@ -524,7 +524,7 @@ public TreeEntryNode getParent(K key) { * @param nodeConsumer 对节点的处理 */ @Override - public void forEachChild(boolean includeSelf, Consumer> nodeConsumer) { + public void forEachChild(final boolean includeSelf, final Consumer> nodeConsumer) { traverseChildNodes(includeSelf, (index, child) -> nodeConsumer.accept(child), null); } @@ -534,7 +534,7 @@ public void forEachChild(boolean includeSelf, Consumer> nodeCons * @param key 要比较的key * @return 是否key一致 */ - public boolean equalsKey(K key) { + public boolean equalsKey(final K key) { return ObjectKit.equals(getKey(), key); } @@ -547,7 +547,7 @@ public boolean equalsKey(K key) { * @return 遍历到的最后一个节点 */ TreeEntryNode traverseChildNodes( - boolean includeCurrent, BiConsumer> consumer, BiPredicate> breakTraverse) { + final boolean includeCurrent, final BiConsumer> consumer, BiPredicate> breakTraverse) { breakTraverse = ObjectKit.defaultIfNull(breakTraverse, (i, n) -> false); final Deque>> keyNodeDeque = CollKit.newLinkedList(CollKit.newArrayList(this)); boolean needProcess = includeCurrent; @@ -583,7 +583,7 @@ TreeEntryNode traverseChildNodes( * @param child 子节点 * @throws IllegalArgumentException 当要添加的子节点已经是其自身父节点时抛出 */ - void addChild(TreeEntryNode child) { + void addChild(final TreeEntryNode child) { if (containsChild(child.key)) { return; } @@ -611,7 +611,7 @@ void addChild(TreeEntryNode child) { * * @param key 子节点 */ - void removeDeclaredChild(K key) { + void removeDeclaredChild(final K key) { final TreeEntryNode child = children.get(key); if (ObjectKit.isNull(child)) { return; @@ -635,7 +635,7 @@ void removeDeclaredChild(K key) { * @return 节点 */ @Override - public TreeEntryNode getChild(K key) { + public TreeEntryNode getChild(final K key) { return traverseChildNodes(false, (i, c) -> { }, (i, c) -> c.equalsKey(key)); } @@ -679,7 +679,7 @@ void clear() { * @return 是否 */ @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) { return true; } @@ -707,8 +707,8 @@ public int hashCode() { * @param value 复制的节点的值 * @return 节点 */ - TreeEntryNode copy(V value) { - TreeEntryNode copiedNode = new TreeEntryNode<>(this.parent, this.key, ObjectKit.defaultIfNull(value, this.value)); + TreeEntryNode copy(final V value) { + final TreeEntryNode copiedNode = new TreeEntryNode<>(this.parent, this.key, ObjectKit.defaultIfNull(value, this.value)); copiedNode.children.putAll(children); return copiedNode; } @@ -716,7 +716,7 @@ TreeEntryNode copy(V value) { } /** - * {@link Entry}包装类 + * {@link Map.Entry}包装类 * * @param key类型 * @param value类型 @@ -724,10 +724,10 @@ TreeEntryNode copy(V value) { * @see #entrySet() * @see #values() */ - public static class EntryNodeWrapper> implements Entry>, XWrapper { + public static class EntryNodeWrapper> implements Map.Entry>, XWrapper { private final N entryNode; - EntryNodeWrapper(N entryNode) { + EntryNodeWrapper(final N entryNode) { this.entryNode = entryNode; } @@ -742,7 +742,7 @@ public TreeEntry getValue() { } @Override - public TreeEntry setValue(TreeEntry value) { + public TreeEntry setValue(final TreeEntry value) { throw new UnsupportedOperationException(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/ListValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/ListValueMap.java old mode 100755 new mode 100644 index 578f5c09d2..3b5becf449 --- a/bus-core/src/main/java/org/aoju/bus/core/map/ListValueMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/ListValueMap.java @@ -26,6 +26,7 @@ package org.aoju.bus.core.map; import java.util.*; +import java.util.function.Supplier; /** * 值作为集合List的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 @@ -35,56 +36,36 @@ * @author Kimi Liu * @since Java 17+ */ -public class ListValueMap extends AbstractCollValueMap> { +public class ListValueMap extends AbstractCollValueMap { - /** - * 构造 - */ - public ListValueMap() { - this(DEFAULT_INITIAL_CAPACITY); - } + private static final long serialVersionUID = 1L; /** - * 构造 - * - * @param initialCapacity 初始大小 + * 基于{@link HashMap}创建一个值为{@link List}的多值映射集合 */ - public ListValueMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); - } - - /** - * 构造 - * - * @param m Map - */ - public ListValueMap(Map> m) { - this(DEFAULT_LOAD_FACTOR, m); + public ListValueMap() { } /** - * 构造 + * 基于{@link HashMap}创建一个值为{@link List}的多值映射集合 * - * @param loadFactor 加载因子 - * @param m Map + * @param map 提供数据的原始集合 */ - public ListValueMap(float loadFactor, Map> m) { - this(m.size(), loadFactor); - this.putAllValues(m); + public ListValueMap(final Map> map) { + super(map); } /** - * 构造 + * 基于{@code mapFactory}创建一个值为{@link List}的多值映射集合 * - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param mapFactory 创建集合的工厂反方 */ - public ListValueMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + public ListValueMap(final Supplier>> mapFactory) { + super(mapFactory); } @Override - protected List createCollection() { + public List createCollection() { return new ArrayList<>(DEFAULT_COLLECTION_INITIAL_CAPACITY); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java old mode 100755 new mode 100644 index 27affa4c1e..c397714272 --- a/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java @@ -50,7 +50,7 @@ public class MapBuilder implements Builder> { * * @param map 要使用的Map实现类 */ - public MapBuilder(Map map) { + public MapBuilder(final Map map) { this.map = map; } @@ -59,10 +59,10 @@ public MapBuilder(Map map) { * * @param Key类型 * @param Value类型 - * @return MapBuilder + * @return this */ public static MapBuilder of() { - return create(false); + return of(false); } /** @@ -71,10 +71,10 @@ public static MapBuilder of() { * @param Key类型 * @param Value类型 * @param isLinked true创建LinkedHashMap,false创建HashMap - * @return MapBuilder + * @return this */ - public static MapBuilder create(boolean isLinked) { - return create(MapKit.newHashMap(isLinked)); + public static MapBuilder of(final boolean isLinked) { + return of(MapKit.newHashMap(isLinked)); } /** @@ -83,9 +83,9 @@ public static MapBuilder create(boolean isLinked) { * @param Key类型 * @param Value类型 * @param map Map实体类 - * @return MapBuilder + * @return this */ - public static MapBuilder create(Map map) { + public static MapBuilder of(final Map map) { return new MapBuilder<>(map); } @@ -94,33 +94,22 @@ public static MapBuilder create(Map map) { * * @param k Key类型 * @param v Value类型 - * @return 当前类 + * @return this */ - public MapBuilder put(K k, V v) { + public MapBuilder put(final K k, final V v) { map.put(k, v); return this; } - /** - * 链式Map创建 - * - * @param k Key类型 - * @param supplier Value类型结果提供方 - * @return 当前类 - */ - public MapBuilder put(K k, Supplier supplier) { - return put(k, supplier.get()); - } - /** * 链式Map创建 * * @param condition put条件 * @param k Key类型 * @param v Value类型 - * @return 当前类 + * @return this */ - public MapBuilder put(boolean condition, K k, V v) { + public MapBuilder put(final boolean condition, final K k, final V v) { if (condition) { put(k, v); } @@ -133,11 +122,11 @@ public MapBuilder put(boolean condition, K k, V v) { * @param condition put条件 * @param k Key类型 * @param supplier Value类型结果提供方 - * @return 当前类 + * @return this */ - public MapBuilder put(boolean condition, K k, Supplier supplier) { + public MapBuilder put(final boolean condition, final K k, final Supplier supplier) { if (condition) { - put(k, supplier); + put(k, supplier.get()); } return this; } @@ -146,9 +135,9 @@ public MapBuilder put(boolean condition, K k, Supplier supplier) { * 链式Map创建 * * @param map 合并map - * @return 当前类 + * @return this */ - public MapBuilder putAll(Map map) { + public MapBuilder putAll(final Map map) { this.map.putAll(map); return this; } @@ -177,6 +166,7 @@ public Map map() { * * @return 创建后的map */ + @Override public Map build() { return map(); } @@ -188,7 +178,7 @@ public Map build() { * @param keyValueSeparator kv之间的连接符 * @return 连接字符串 */ - public String join(String separator, final String keyValueSeparator) { + public String join(final String separator, final String keyValueSeparator) { return MapKit.join(this.map, separator, keyValueSeparator); } @@ -199,7 +189,7 @@ public String join(String separator, final String keyValueSeparator) { * @param keyValueSeparator kv之间的连接符 * @return 连接后的字符串 */ - public String joinIgnoreNull(String separator, final String keyValueSeparator) { + public String joinIgnoreNull(final String separator, final String keyValueSeparator) { return MapKit.joinIgnoreNull(this.map, separator, keyValueSeparator); } @@ -211,7 +201,7 @@ public String joinIgnoreNull(String separator, final String keyValueSeparator) { * @param isIgnoreNull 是否忽略null的键和值 * @return 连接后的字符串 */ - public String join(String separator, final String keyValueSeparator, boolean isIgnoreNull) { + public String join(final String separator, final String keyValueSeparator, final boolean isIgnoreNull) { return MapKit.join(this.map, separator, keyValueSeparator, isIgnoreNull); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MapProxy.java b/bus-core/src/main/java/org/aoju/bus/core/map/MapProxy.java old mode 100755 new mode 100644 index 87d91dfae2..e610c30872 --- a/bus-core/src/main/java/org/aoju/bus/core/map/MapProxy.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MapProxy.java @@ -58,7 +58,7 @@ public class MapProxy implements Map, TypeGetter, Invoca * * @param map 被代理的Map */ - public MapProxy(Map map) { + public MapProxy(final Map map) { this.map = map; } @@ -69,11 +69,12 @@ public MapProxy(Map map) { * @param map 被代理的Map * @return {@link MapProxy} */ - public static MapProxy create(Map map) { + public static MapProxy of(final Map map) { return (map instanceof MapProxy) ? (MapProxy) map : new MapProxy(map); } - public Object getObject(Object key, Object defaultValue) { + @Override + public Object getObject(final Object key, final Object defaultValue) { return map.getOrDefault(key, defaultValue); } @@ -88,32 +89,32 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return map.containsKey(key); } @Override - public boolean containsValue(Object value) { + public boolean containsValue(final Object value) { return map.containsValue(value); } @Override - public Object get(Object key) { + public Object get(final Object key) { return map.get(key); } @Override - public Object put(Object key, Object value) { + public Object put(final Object key, final Object value) { return map.put(key, value); } @Override - public Object remove(Object key) { + public Object remove(final Object key) { return map.remove(key); } @Override - public void putAll(Map m) { + public void putAll(final Map m) { map.putAll(m); } @@ -138,18 +139,18 @@ public Set> entrySet() { } @Override - public Object invoke(Object proxy, Method method, Object[] args) { + public Object invoke(final Object proxy, final Method method, final Object[] args) { final Class[] parameterTypes = method.getParameterTypes(); if (ArrayKit.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; if (methodName.startsWith(Normal.GET)) { // 匹配getXXX fieldName = StringKit.removePreAndLowerFirst(methodName, 3); - } else if (BooleanKit.isBoolean(returnType) && methodName.startsWith(Normal.IS)) { + } else if (BooleanKit.isBoolean(returnType) && methodName.startsWith("is")) { // 匹配isXXX fieldName = StringKit.removePreAndLowerFirst(methodName, 2); } else if (Normal.HASHCODE.equals(methodName)) { @@ -194,7 +195,7 @@ public Object invoke(Object proxy, Method method, Object[] args) { * @param interfaceClass 接口 * @return 代理对象 */ - public T toProxyBean(Class interfaceClass) { + public T toProxyBean(final Class interfaceClass) { return (T) Proxy.newProxyInstance(ClassKit.getClassLoader(), new Class[]{interfaceClass}, this); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MapWrapper.java b/bus-core/src/main/java/org/aoju/bus/core/map/MapWrapper.java index 59e296f592..195f1b71c1 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/MapWrapper.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MapWrapper.java @@ -48,8 +48,6 @@ */ public class MapWrapper implements Map, Iterable>, XWrapper>, Serializable, Cloneable { - private static final long serialVersionUID = 1L; - /** * 默认增长因子 */ @@ -58,7 +56,7 @@ public class MapWrapper implements Map, Iterable>, X * 默认初始大小 */ protected static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; - + private static final long serialVersionUID = 1L; private Map raw; /** @@ -85,6 +83,7 @@ public MapWrapper(Supplier> mapFactory) { * * @return Map */ + @Override public Map getRaw() { return this.raw; } @@ -100,32 +99,32 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return raw.containsKey(key); } @Override - public boolean containsValue(Object value) { + public boolean containsValue(final Object value) { return raw.containsValue(value); } @Override - public V get(Object key) { + public V get(final Object key) { return raw.get(key); } @Override - public V put(K key, V value) { + public V put(final K key, final V value) { return raw.put(key, value); } @Override - public V remove(Object key) { + public V remove(final Object key) { return raw.remove(key); } @Override - public void putAll(Map m) { + public void putAll(final Map m) { raw.putAll(m); } @@ -155,14 +154,14 @@ public Iterator> iterator() { } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) { return true; } - if (null == o || getClass() != o.getClass()) { + if (o == null || getClass() != o.getClass()) { return false; } - MapWrapper that = (MapWrapper) o; + final MapWrapper that = (MapWrapper) o; return Objects.equals(raw, that.raw); } @@ -178,57 +177,57 @@ public String toString() { @Override - public void forEach(BiConsumer action) { + public void forEach(final BiConsumer action) { raw.forEach(action); } @Override - public void replaceAll(BiFunction function) { + public void replaceAll(final BiFunction function) { raw.replaceAll(function); } @Override - public V putIfAbsent(K key, V value) { + public V putIfAbsent(final K key, final V value) { return raw.putIfAbsent(key, value); } @Override - public boolean remove(Object key, Object value) { + public boolean remove(final Object key, final Object value) { return raw.remove(key, value); } @Override - public boolean replace(K key, V oldValue, V newValue) { + public boolean replace(final K key, final V oldValue, final V newValue) { return raw.replace(key, oldValue, newValue); } @Override - public V replace(K key, V value) { + public V replace(final K key, final V value) { return raw.replace(key, value); } @Override - public V computeIfAbsent(K key, Function mappingFunction) { + public V computeIfAbsent(final K key, final Function mappingFunction) { return raw.computeIfAbsent(key, mappingFunction); } @Override - public V getOrDefault(Object key, V defaultValue) { + public V getOrDefault(final Object key, final V defaultValue) { return raw.getOrDefault(key, defaultValue); } @Override - public V computeIfPresent(K key, BiFunction remappingFunction) { + public V computeIfPresent(final K key, final BiFunction remappingFunction) { return raw.computeIfPresent(key, remappingFunction); } @Override - public V compute(K key, BiFunction remappingFunction) { + public V compute(final K key, final BiFunction remappingFunction) { return raw.compute(key, remappingFunction); } @Override - public V merge(K key, V value, BiFunction remappingFunction) { + public V merge(final K key, final V value, final BiFunction remappingFunction) { return raw.merge(key, value, remappingFunction); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MultiValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/MultiValueMap.java new file mode 100644 index 0000000000..15f197d27d --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MultiValueMap.java @@ -0,0 +1,277 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.map; + +import org.aoju.bus.core.toolkit.ArrayKit; +import org.aoju.bus.core.toolkit.CollKit; + +import java.util.*; +import java.util.function.*; +import java.util.stream.Collectors; + +/** + * 一个键对应多个值的集合{@link Map}实现,提供针对键对应的值集合中的元素而非值集合本身的一些快捷操作, + * 本身可作为一个值为{@link Collection}类型的{@link Map}使用 + * + *

值集合类型

+ *

值集合的类型由接口的实现类自行维护,当通过{@link MultiValueMap}定义的方法进行增删改操作时, + * 实现类应保证通过通过实例方法获得的集合类型都一致但是若用户直接通过{@link Map}定义的方法进行增删改操作时, + * 实例无法保证通过实例方法获得的集合类型都一致 + * 因此,若无必要则更推荐通过{@link MultiValueMap}定义的方法进行操作 + * + *

对值集合的修改

+ * 当通过实例方法获得值集合时,若该集合允许修改,则对值集合的修改将会影响到其所属的{@link MultiValueMap}实例,反之亦然 + * 因此当同时遍历当前实例或者值集合时,若存在写操作,则需要注意可能引发的{@link ConcurrentModificationException} + * + * @author Kimi Liu + * @since Java 17+ + */ +public interface MultiValueMap extends Map> { + + /** + * 更新键对应的值集合 + * 注意:该操作将移除键对应的旧值集合,若仅需向值集合追加应值,则应使用{@link #putAllValues(Object, Collection)} + * + * @param key 键 + * @param value 键对应的新值集合 + * @return 旧值集合 + */ + @Override + Collection put(K key, Collection value); + + /** + * 更新全部键的值集合 + * 注意:该操作将移除键对应的旧值集合,若仅需向值集合追加应值,则应使用{@link #putAllValues(Object, Collection)} + * + * @param map 需要更新的键值对集合 + */ + @Override + void putAll(Map> map); + + /** + * 将集合中的全部键值对追加到当前实例中,效果等同于: + *
{@code
+     * for (Entry> entry : m.entrySet()) {
+     * 	K key = entry.getKey();
+     * 	Collection coll = entry.getValues();
+     * 	for (V val : coll) {
+     * 		map.putValue(key, val)
+     *    }
+     * }
+     * }
+ * + * @param m 待添加的集合 + */ + default void putAllValues(final Map> m) { + if (CollKit.isNotEmpty(m)) { + m.forEach(this::putAllValues); + } + } + + /** + * 将集合中的全部元素对追加到指定键对应的值集合中,效果等同于: + *
{@code
+     * 	for (V val : coll) {
+     * 		map.putValue(key, val)
+     *    }
+     * }
+ * + * @param key 键 + * @param coll 待添加的值集合 + * @return 是否成功添加 + */ + boolean putAllValues(K key, final Collection coll); + + /** + * 将数组中的全部元素追加到指定的值集合中,效果等同于: + *
{@code
+     * 	for (V val : values) {
+     * 		map.putValue(key, val)
+     *    }
+     * }
+ * + * @param key 键 + * @param values 待添加的值 + * @return boolean + */ + default boolean putValues(final K key, final V... values) { + return ArrayKit.isNotEmpty(values) && putAllValues(key, Arrays.asList(values)); + } + + /** + * 向指定键对应的值集合追加值,效果等同于: + *
{@code
+     * Collection coll = map.get(key);
+     * if(null == coll) {
+     * 	coll.add(value);
+     * 	map.put(coll);
+     * } else {
+     * 	coll.add(value);
+     * }
+     * }
+ * + * @param key 键 + * @param value 值 + * @return 是否成功添加 + */ + boolean putValue(final K key, final V value); + + /** + * 将值从指定键下的值集合中删除 + * + * @param key 键 + * @param value 值 + * @return 是否成功删除 + */ + boolean removeValue(final K key, final V value); + + /** + * 将一批值从指定键下的值集合中删除 + * + * @param key 键 + * @param values 值数组 + * @return 是否成功删除 + */ + default boolean removeValues(final K key, final V... values) { + return ArrayKit.isNotEmpty(values) && removeAllValues(key, Arrays.asList(values)); + } + + /** + * 将一批值从指定键下的值集合中删除 + * + * @param key 键 + * @param values 值集合 + * @return 是否成功删除 + */ + boolean removeAllValues(final K key, final Collection values); + + /** + * 根据条件过滤所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param filter 判断方法 + * @return 当前实例 + */ + default MultiValueMap filterAllValues(Predicate filter) { + return filterAllValues((k, v) -> filter.test(v)); + } + + /** + * 根据条件过滤所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param filter 判断方法 + * @return 当前实例 + */ + MultiValueMap filterAllValues(BiPredicate filter); + + /** + * 根据条件替换所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param operate 替换方法 + * @return 当前实例 + */ + default MultiValueMap replaceAllValues(UnaryOperator operate) { + return replaceAllValues((k, v) -> operate.apply(v)); + } + + /** + * 根据条件替换所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param operate 替换方法 + * @return 当前实例 + */ + MultiValueMap replaceAllValues(BiFunction operate); + + /** + * 获取指定序号的值,若值不存在,返回{@code null} + * + * @param key 键 + * @param index 第几个值的索引,越界返回null + * @return 值或null + */ + default V getValue(K key, int index) { + final Collection collection = get(key); + return CollKit.get(collection, index); + } + + /** + * 获取键对应的值,若值不存在,则返回{@link Collections#emptyList()}效果等同于: + *
{@code
+     * map.getOrDefault(key, Collections.emptyList())
+     * }
+ * + * @param key 键 + * @return 值集合 + */ + default Collection getValues(final K key) { + return getOrDefault(key, Collections.emptyList()); + } + + /** + * 获取键对应值的数量,若键对应的值不存在,则返回{@code 0} + * + * @param key 键 + * @return 值的数量 + */ + default int size(final K key) { + return getValues(key).size(); + } + + /** + * 遍历所有键值对,效果等同于: + *
{@code
+     * for (Entry> entry : entrySet()) {
+     * 	K key = entry.getKey();
+     * 	Collection coll = entry.getValues();
+     * 	for (V val : coll) {
+     * 		consumer.accept(key, val);
+     *    }
+     * }
+     * }
+ * + * @param consumer 操作 + */ + default void allForEach(BiConsumer consumer) { + forEach((k, coll) -> coll.forEach(v -> consumer.accept(k, v))); + } + + /** + * 获取所有的值,效果等同于: + *
{@code
+     * List results = new ArrayList<>();
+     * for (Collection coll : values()) {
+     * 	results.addAll(coll);
+     * }
+     * }
+ * + * @return 值 + */ + default Collection allValues() { + return values().stream() + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/ReferenceMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/ReferenceMap.java index f54000471b..8b78a078ad 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/ReferenceMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/ReferenceMap.java @@ -66,7 +66,7 @@ public class ReferenceMap implements ConcurrentMap, Iterable, V> raw, References.Type referenceType) { + public ReferenceMap(final ConcurrentMap, V> raw, final References.Type referenceType) { this.raw = raw; this.keyType = referenceType; lastQueue = new ReferenceQueue<>(); @@ -157,6 +157,7 @@ public V computeIfPresent(final K key, final BiFunction remappingFunction.apply(key, value)); } + @SuppressWarnings("unchecked") @Override public V remove(final Object key) { this.purgeStaleKeys(); diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/RowKeyTable.java b/bus-core/src/main/java/org/aoju/bus/core/map/RowKeyTable.java old mode 100644 new mode 100755 index 9c23623b59..964aece7c0 --- a/bus-core/src/main/java/org/aoju/bus/core/map/RowKeyTable.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/RowKeyTable.java @@ -65,7 +65,7 @@ public RowKeyTable() { * * @param isLinked 是否有序,有序则使用{@link java.util.LinkedHashMap}作为原始Map */ - public RowKeyTable(boolean isLinked) { + public RowKeyTable(final boolean isLinked) { this(MapKit.newHashMap(isLinked), () -> MapKit.newHashMap(isLinked)); } @@ -74,7 +74,7 @@ public RowKeyTable(boolean isLinked) { * * @param raw 原始Map */ - public RowKeyTable(Map> raw) { + public RowKeyTable(final Map> raw) { this(raw, HashMap::new); } @@ -84,7 +84,7 @@ public RowKeyTable(Map> raw) { * @param raw 原始Map * @param columnMapBuilder 列的map创建器 */ - public RowKeyTable(Map> raw, Builder> columnMapBuilder) { + public RowKeyTable(final Map> raw, final Builder> columnMapBuilder) { this.raw = raw; this.columnBuilder = null == columnMapBuilder ? HashMap::new : columnMapBuilder; } @@ -95,12 +95,12 @@ public Map> rowMap() { } @Override - public V put(R rowKey, C columnKey, V value) { + public V put(final R rowKey, final C columnKey, final V value) { return raw.computeIfAbsent(rowKey, (key) -> columnBuilder.build()).put(columnKey, value); } @Override - public V remove(R rowKey, C columnKey) { + public V remove(final R rowKey, final C columnKey) { final Map map = getRow(rowKey); if (null == map) { return null; @@ -123,11 +123,11 @@ public void clear() { } @Override - public boolean containsColumn(C columnKey) { + public boolean containsColumn(final C columnKey) { if (columnKey == null) { return false; } - for (Map map : raw.values()) { + for (final Map map : raw.values()) { if (null != map && map.containsKey(columnKey)) { return true; } @@ -135,16 +135,15 @@ public boolean containsColumn(C columnKey) { return false; } - //region columnMap @Override public Map> columnMap() { - Map> result = columnMap; + final Map> result = columnMap; return (result == null) ? columnMap = new ColumnMap() : result; } @Override public Set columnKeySet() { - Set result = columnKeySet; + final Set result = columnKeySet; return (result == null) ? columnKeySet = new ColumnKeySet() : result; } @@ -152,7 +151,7 @@ public Set columnKeySet() { public List columnKeys() { final Collection> values = this.raw.values(); final List result = new ArrayList<>(values.size() * 16); - for (Map map : values) { + for (final Map map : values) { map.forEach((key, value) -> { result.add(key); }); @@ -161,7 +160,7 @@ public List columnKeys() { } @Override - public Map getColumn(C columnKey) { + public Map getColumn(final C columnKey) { return new Column(columnKey); } @@ -209,7 +208,7 @@ private class ColumnKeyIterator extends ComputeIterator { protected C computeNext() { while (true) { if (entryIterator.hasNext()) { - Map.Entry entry = entryIterator.next(); + final Map.Entry entry = entryIterator.next(); if (false == seen.containsKey(entry.getKey())) { seen.put(entry.getKey(), entry.getValue()); return entry.getKey(); @@ -226,7 +225,7 @@ protected C computeNext() { private class Column extends AbstractMap { final C columnKey; - Column(C columnKey) { + Column(final C columnKey) { this.columnKey = columnKey; } @@ -235,17 +234,17 @@ public Set> entrySet() { return new EntrySet(); } - private class EntrySet extends AbstractSet> { + private class EntrySet extends AbstractSet> { @Override - public Iterator> iterator() { + public Iterator> iterator() { return new EntrySetIterator(); } @Override public int size() { int size = 0; - for (Map map : raw.values()) { + for (final Map map : raw.values()) { if (map.containsKey(columnKey)) { size++; } @@ -262,7 +261,7 @@ protected Entry computeNext() { while (iterator.hasNext()) { final Entry> entry = iterator.next(); if (entry.getValue().containsKey(columnKey)) { - return new AbstractEntry() { + return new AbstractEntry<>() { @Override public R getKey() { return entry.getKey(); @@ -274,7 +273,7 @@ public V getValue() { } @Override - public V setValue(V value) { + public V setValue(final V value) { return entry.getValue().put(columnKey, value); } }; diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/IntMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/SafeHashMap.java similarity index 59% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/IntMap.java rename to bus-core/src/main/java/org/aoju/bus/core/map/SafeHashMap.java index 132994e0d9..0540d95723 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/IntMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/SafeHashMap.java @@ -23,57 +23,78 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom.bitmap; +package org.aoju.bus.core.map; -import java.io.Serializable; +import org.aoju.bus.core.toolkit.MapKit; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; /** - * 过滤器BitMap在32位机器上.这个类能发生更好的效果.一般情况下建议使用此类 + * 安全的ConcurrentHashMap实现 + * 此类用于解决在JDK8中调用{@link ConcurrentHashMap#computeIfAbsent(Object, Function)}可能造成的死循环问题 * + * @param 键类型 + * @param 值类型 * @author Kimi Liu * @since Java 17+ */ -public class IntMap implements BitMap, Serializable { +public class SafeHashMap extends ConcurrentHashMap { private static final long serialVersionUID = 1L; - private final int[] ints; + /** + * 构造,默认初始大小(16) + */ + public SafeHashMap() { + super(); + } /** * 构造 + * + * @param initialCapacity 预估初始大小 */ - public IntMap() { - ints = new int[93750000]; + public SafeHashMap(int initialCapacity) { + super(initialCapacity); } /** * 构造 * - * @param size 容量 + * @param map 初始键值对 */ - public IntMap(int size) { - ints = new int[size]; + public SafeHashMap(Map map) { + super(map); } - @Override - public void add(long i) { - int r = (int) (i / BitMap.MACHINE32); - int c = (int) (i % BitMap.MACHINE32); - ints[r] = ints[r] | (1 << c); + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param loadFactor 增长系数 + */ + public SafeHashMap(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); } - @Override - public boolean contains(long i) { - int r = (int) (i / BitMap.MACHINE32); - int c = (int) (i % BitMap.MACHINE32); - return ((ints[r] >>> c) & 1) == 1; + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param loadFactor 增长系数 + * @param concurrencyLevel 并发级别,即Segment的个数 + */ + public SafeHashMap(int initialCapacity, + float loadFactor, + int concurrencyLevel) { + super(initialCapacity, loadFactor, concurrencyLevel); } @Override - public void remove(long i) { - int r = (int) (i / BitMap.MACHINE32); - int c = (int) (i % BitMap.MACHINE32); - ints[r] &= ~(1 << c); + public V computeIfAbsent(K key, Function mappingFunction) { + return MapKit.computeIfAbsent(this, key, mappingFunction); } -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/SetValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/SetValueMap.java old mode 100755 new mode 100644 index fc5062f4f5..5e225a8808 --- a/bus-core/src/main/java/org/aoju/bus/core/map/SetValueMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/SetValueMap.java @@ -26,6 +26,7 @@ package org.aoju.bus.core.map; import java.util.*; +import java.util.function.Supplier; /** * 值作为集合Set(LinkedHashSet)的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 @@ -35,56 +36,37 @@ * @author Kimi Liu * @since Java 17+ */ -public class SetValueMap extends AbstractCollValueMap> { +public class SetValueMap extends AbstractCollValueMap { - /** - * 构造 - */ - public SetValueMap() { - this(DEFAULT_INITIAL_CAPACITY); - } + private static final long serialVersionUID = 1L; - /** - * 构造 - * - * @param initialCapacity 初始大小 - */ - public SetValueMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); - } /** - * 构造 - * - * @param m Map + * 基于{@link HashMap}创建一个值为{@link Set}的多值映射集合 */ - public SetValueMap(Map> m) { - this(DEFAULT_LOAD_FACTOR, m); + public SetValueMap() { } /** - * 构造 + * 基于{@link HashMap}创建一个值为{@link Set}的多值映射集合 * - * @param loadFactor 加载因子 - * @param m Map + * @param map 提供数据的原始集合 */ - public SetValueMap(float loadFactor, Map> m) { - this(m.size(), loadFactor); - this.putAllValues(m); + public SetValueMap(Map> map) { + super(map); } /** - * 构造 + * 基于{@code mapFactory}创建一个值为{@link Set}的多值映射集合 * - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param mapFactory 创建集合的工厂反方 */ - public SetValueMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + public SetValueMap(Supplier>> mapFactory) { + super(mapFactory); } @Override - protected Set createCollection() { + public Set createCollection() { return new LinkedHashSet<>(DEFAULT_COLLECTION_INITIAL_CAPACITY); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/Table.java b/bus-core/src/main/java/org/aoju/bus/core/map/Table.java old mode 100644 new mode 100755 index 64410a68bc..8c29ff567d --- a/bus-core/src/main/java/org/aoju/bus/core/map/Table.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/Table.java @@ -51,7 +51,7 @@ public interface Table extends Iterable> { * @param columnKey 列键 * @return 是否包含映射 */ - default boolean contains(R rowKey, C columnKey) { + default boolean contains(final R rowKey, final C columnKey) { return Optional.ofNullable(getRow(rowKey)).map((map) -> map.containsKey(columnKey)).get(); } @@ -61,7 +61,7 @@ default boolean contains(R rowKey, C columnKey) { * @param rowKey 行键 * @return 行是否存在 */ - default boolean containsRow(R rowKey) { + default boolean containsRow(final R rowKey) { return Optional.ofNullable(rowMap()).map((map) -> map.containsKey(rowKey)).get(); } @@ -71,7 +71,7 @@ default boolean containsRow(R rowKey) { * @param rowKey 行键 * @return 行映射,返回的键为列键,值为表格的值 */ - default Map getRow(R rowKey) { + default Map getRow(final R rowKey) { return Optional.ofNullable(rowMap()).map((map) -> map.get(rowKey)).get(); } @@ -97,7 +97,7 @@ default Set rowKeySet() { * @param columnKey 列键 * @return 列是否存在 */ - default boolean containsColumn(C columnKey) { + default boolean containsColumn(final C columnKey) { return Optional.ofNullable(columnMap()).map((map) -> map.containsKey(columnKey)).get(); } @@ -107,7 +107,7 @@ default boolean containsColumn(C columnKey) { * @param columnKey 列键 * @return 列映射,返回的键为行键,值为表格的值 */ - default Map getColumn(C columnKey) { + default Map getColumn(final C columnKey) { return Optional.ofNullable(columnMap()).map((map) -> map.get(columnKey)).get(); } @@ -132,7 +132,7 @@ default List columnKeys() { } final List result = new ArrayList<>(columnMap.size()); - for (Map.Entry> cMapEntry : columnMap.entrySet()) { + for (final Map.Entry> cMapEntry : columnMap.entrySet()) { result.add(cMapEntry.getKey()); } return result; @@ -151,10 +151,10 @@ default List columnKeys() { * @param value 值 * @return 值 */ - default boolean containsValue(V value) { + default boolean containsValue(final V value) { final Collection> rows = Optional.ofNullable(rowMap()).map(Map::values).get(); if (null != rows) { - for (Map row : rows) { + for (final Map row : rows) { if (row.containsValue(value)) { return true; } @@ -170,7 +170,7 @@ default boolean containsValue(V value) { * @param columnKey 列键 * @return 值,如果值不存在,返回{@code null} */ - default V get(R rowKey, C columnKey) { + default V get(final R rowKey, final C columnKey) { return Optional.ofNullable(getRow(rowKey)).map((map) -> map.get(columnKey)).get(); } @@ -203,9 +203,9 @@ default V get(R rowKey, C columnKey) { * * @param table 其他table */ - default void putAll(Table table) { + default void putAll(final Table table) { if (null != table) { - for (Cell cell : table.cellSet()) { + for (final Cell cell : table.cellSet()) { put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); } } @@ -238,7 +238,7 @@ default int size() { return 0; } int size = 0; - for (Map map : rowMap.values()) { + for (final Map map : rowMap.values()) { size += map.size(); } return size; @@ -254,8 +254,8 @@ default int size() { * * @param consumer 单元格值处理器 */ - default void forEach(XMultiple consumer) { - for (Cell cell : this) { + default void forEach(final XMultiple consumer) { + for (final Cell cell : this) { consumer.accept(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java index ed0fc69bfa..30d8b497a3 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java @@ -182,7 +182,7 @@ public V removeByIndex(final int index) { @Override public void putAll(final Map m) { - for (final Map.Entry entry : m.entrySet()) { + for (final Entry entry : m.entrySet()) { this.put(entry.getKey(), entry.getValue()); } } @@ -213,8 +213,8 @@ public Collection values() { } @Override - public Set> entrySet() { - final Set> hashSet = new LinkedHashSet<>(); + public Set> entrySet() { + final Set> hashSet = new LinkedHashSet<>(); for (int i = 0; i < size(); i++) { hashSet.add(MapKit.entry(keys.get(i), values.get(i))); } @@ -222,7 +222,7 @@ public Set> entrySet() { } @Override - public Iterator> iterator() { + public Iterator> iterator() { return new Iterator<>() { private final Iterator keysIter = keys.iterator(); private final Iterator valuesIter = values.iterator(); @@ -233,7 +233,7 @@ public boolean hasNext() { } @Override - public Map.Entry next() { + public Entry next() { return MapKit.entry(keysIter.next(), valuesIter.next()); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/TolerantMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/TolerantMap.java index c9c170bde7..3834a9e675 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/TolerantMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/TolerantMap.java @@ -46,7 +46,7 @@ public class TolerantMap extends MapWrapper { * * @param defaultValue 默认值 */ - public TolerantMap(V defaultValue) { + public TolerantMap(final V defaultValue) { this(new HashMap<>(), defaultValue); } @@ -57,7 +57,7 @@ public TolerantMap(V defaultValue) { * @param loadFactor 增长因子 * @param defaultValue 默认值 */ - public TolerantMap(int initialCapacity, float loadFactor, V defaultValue) { + public TolerantMap(final int initialCapacity, final float loadFactor, final V defaultValue) { this(new HashMap<>(initialCapacity, loadFactor), defaultValue); } @@ -67,7 +67,7 @@ public TolerantMap(int initialCapacity, float loadFactor, V defaultValue) { * @param initialCapacity 初始容量 * @param defaultValue 默认值 */ - public TolerantMap(int initialCapacity, V defaultValue) { + public TolerantMap(final int initialCapacity, final V defaultValue) { this(new HashMap<>(initialCapacity), defaultValue); } @@ -77,7 +77,7 @@ public TolerantMap(int initialCapacity, V defaultValue) { * @param map Map实现 * @param defaultValue 默认值 */ - public TolerantMap(Map map, V defaultValue) { + public TolerantMap(final Map map, final V defaultValue) { super(map); this.defaultValue = defaultValue; } @@ -89,30 +89,31 @@ public TolerantMap(Map map, V defaultValue) { * @param defaultValue 默认值 * @param 键类型 * @param 值类型 - * @return TolerantMap + * @return this */ - public static TolerantMap of(Map map, V defaultValue) { + public static TolerantMap of(final Map map, final V defaultValue) { return new TolerantMap<>(map, defaultValue); } @Override - public V get(Object key) { + public V get(final Object key) { return getOrDefault(key, defaultValue); } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) { return true; } - if (null == o || getClass() != o.getClass()) { + if (o == null || getClass() != o.getClass()) { return false; } if (false == super.equals(o)) { return false; } final TolerantMap that = (TolerantMap) o; - return getRaw().equals(that.getRaw()) && Objects.equals(defaultValue, that.defaultValue); + return getRaw().equals(that.getRaw()) + && Objects.equals(defaultValue, that.defaultValue); } @Override diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/TransitionMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/TransitionMap.java old mode 100644 new mode 100755 index 3a23356a72..86f4ac510e --- a/bus-core/src/main/java/org/aoju/bus/core/map/TransitionMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/TransitionMap.java @@ -49,7 +49,7 @@ public abstract class TransitionMap extends MapWrapper { * * @param mapFactory 空Map创建工厂 */ - public TransitionMap(Supplier> mapFactory) { + public TransitionMap(final Supplier> mapFactory) { super(mapFactory); } @@ -59,67 +59,67 @@ public TransitionMap(Supplier> mapFactory) { * * @param emptyMap Map 被包装的Map,必须为空Map,否则自定义key会无效 */ - public TransitionMap(Map emptyMap) { + public TransitionMap(final Map emptyMap) { super(emptyMap); } @Override - public V get(Object key) { + public V get(final Object key) { return super.get(customKey(key)); } @Override - public V put(K key, V value) { + public V put(final K key, final V value) { return super.put(customKey(key), customValue(value)); } @Override - public void putAll(Map m) { + public void putAll(final Map m) { m.forEach(this::put); } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return super.containsKey(customKey(key)); } @Override - public V remove(Object key) { + public V remove(final Object key) { return super.remove(customKey(key)); } @Override - public boolean remove(Object key, Object value) { + public boolean remove(final Object key, final Object value) { return super.remove(customKey(key), customValue(value)); } @Override - public boolean replace(K key, V oldValue, V newValue) { + public boolean replace(final K key, final V oldValue, final V newValue) { return super.replace(customKey(key), customValue(oldValue), customValue(newValue)); } @Override - public V replace(K key, V value) { + public V replace(final K key, final V value) { return super.replace(customKey(key), customValue(value)); } @Override - public V getOrDefault(Object key, V defaultValue) { + public V getOrDefault(final Object key, final V defaultValue) { return super.getOrDefault(customKey(key), customValue(defaultValue)); } @Override - public V computeIfPresent(K key, BiFunction remappingFunction) { + public V computeIfPresent(final K key, final BiFunction remappingFunction) { return super.computeIfPresent(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v))); } @Override - public V compute(K key, BiFunction remappingFunction) { + public V compute(final K key, final BiFunction remappingFunction) { return super.compute(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v))); } @Override - public V merge(K key, V value, BiFunction remappingFunction) { + public V merge(final K key, final V value, final BiFunction remappingFunction) { return super.merge(customKey(key), customValue(value), (v1, v2) -> remappingFunction.apply(customValue(v1), customValue(v2))); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/TreeEntry.java b/bus-core/src/main/java/org/aoju/bus/core/map/TreeEntry.java index b1fac0a9c3..145630b489 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/TreeEntry.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/TreeEntry.java @@ -43,25 +43,6 @@ */ public interface TreeEntry extends Map.Entry { - /** - * 比较目标对象与当前{@link TreeEntry}是否相等。
- * 默认只要{@link TreeEntry#getKey()}的返回值相同,即认为两者相等 - * - * @param o 目标对象 - * @return 是否 - */ - @Override - boolean equals(Object o); - - /** - * 返回当前{@link TreeEntry}的哈希值。
- * 默认总是返回{@link TreeEntry#getKey()}的哈希值 - * - * @return 哈希值 - */ - @Override - int hashCode(); - /** * 获取以当前节点作为叶子节点的树结构,然后获取当前节点与根节点的距离 * diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/WeakMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/WeakMap.java index f62933825b..9159d78da0 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/WeakMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/WeakMap.java @@ -28,7 +28,6 @@ import org.aoju.bus.core.lang.References; import java.lang.ref.Reference; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** @@ -42,11 +41,13 @@ */ public class WeakMap extends ReferenceMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ public WeakMap() { - this(new ConcurrentHashMap<>()); + this(new SafeHashMap<>()); } /** @@ -54,7 +55,7 @@ public WeakMap() { * * @param raw {@link ConcurrentMap}实现 */ - public WeakMap(ConcurrentMap, V> raw) { + public WeakMap(final ConcurrentMap, V> raw) { super(raw, References.Type.WEAK); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/AnnotationScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/AnnotationScanner.java index 8cf0092aa5..24e7c9abd3 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/AnnotationScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/AnnotationScanner.java @@ -48,16 +48,6 @@ */ public interface AnnotationScanner { - /** - * 判断是否支持扫描该注解元素 - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 是否支持扫描该注解元素 - */ - default boolean support(AnnotatedElement annotatedEle) { - return false; - } - /** * 给定一组扫描器,使用第一个支持处理该类型元素的扫描器获取元素上可能存在的注解 * @@ -77,43 +67,53 @@ static List scanByAnySupported(AnnotatedElement annotatedEle, Annota } /** - * 若{@link #support(AnnotatedElement)}返回{@code true}, - * 则调用并返回{@link #getAnnotations(AnnotatedElement)}结果, - * 否则返回{@link Collections#emptyList()} + * 根据指定的扫描器,扫描元素上可能存在的注解 * * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param scanners 注解扫描器 * @return 注解 */ - default List getIfSupport(AnnotatedElement annotatedEle) { - return support(annotatedEle) ? getAnnotations(annotatedEle) : Collections.emptyList(); + static List scanByAllScanner(AnnotatedElement annotatedEle, AnnotationScanner... scanners) { + if (ObjectKit.isNull(annotatedEle) && ArrayKit.isNotEmpty(scanners)) { + return Collections.emptyList(); + } + return Stream.of(scanners) + .map(scanner -> scanner.getIfSupport(annotatedEle)) + .flatMap(Collection::stream) + .collect(Collectors.toList()); } /** - * 根据指定的扫描器,扫描元素上可能存在的注解 + * 判断是否支持扫描该注解元素 + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 是否支持扫描该注解元素 + */ + default boolean support(AnnotatedElement annotatedEle) { + return false; + } + + /** + * 若{@link #support(AnnotatedElement)}返回{@code true}, + * 则调用并返回{@link #getAnnotations(AnnotatedElement)}结果, + * 否则返回{@link Collections#emptyList()} * * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param scanners 注解扫描器 * @return 注解 */ - static List scanByAllScanner(AnnotatedElement annotatedEle, AnnotationScanner... scanners) { - if (ObjectKit.isNull(annotatedEle) && ArrayKit.isNotEmpty(scanners)) { - return Collections.emptyList(); - } - return Stream.of(scanners) - .map(scanner -> scanner.getIfSupport(annotatedEle)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); + default List getIfSupport(AnnotatedElement annotatedEle) { + return support(annotatedEle) ? getAnnotations(annotatedEle) : Collections.emptyList(); } /** * 获取注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true * * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 注解 - */ - default List getAnnotations(AnnotatedElement annotatedEle) { - final List annotations = new ArrayList<>(); - scan((index, annotation) -> annotations.add(annotation), annotatedEle, null); + * @return 注解 + */ + default List getAnnotations(AnnotatedElement annotatedEle) { + final List annotations = new ArrayList<>(); + scan((index, annotation) -> annotations.add(annotation), annotatedEle, null); return annotations; } @@ -123,12 +123,12 @@ default List getAnnotations(AnnotatedElement annotatedEle) { * * @param consumer 对获取到的注解和注解对应的层级索引的处理 * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 - */ - default void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { - filter = ObjectKit.defaultIfNull(filter, annotation -> true); - for (final Annotation annotation : annotatedEle.getAnnotations()) { - if (AnnoKit.isNotJdkMateAnnotation(annotation.annotationType()) && filter.test(annotation)) { + * @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 + */ + default void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { + filter = ObjectKit.defaultIfNull(filter, annotation -> true); + for (final Annotation annotation : annotatedEle.getAnnotations()) { + if (AnnoKit.isNotJdkMateAnnotation(annotation.annotationType()) && filter.test(annotation)) { consumer.accept(0, annotation); } } @@ -139,12 +139,12 @@ default void scan(BiConsumer consumer, AnnotatedElement ann * * @param consumer 对获取到的注解和注解对应的层级索引的处理 * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 - */ - default void scanIfSupport(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { - if (support(annotatedEle)) { - scan(consumer, annotatedEle, filter); - } - } + * @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 + */ + default void scanIfSupport(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { + if (support(annotatedEle)) { + scan(consumer, annotatedEle, filter); + } + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/SynthesizedProcessor.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/SynthesizedProcessor.java index 2f78a96052..956c9b2e2d 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/SynthesizedProcessor.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/SynthesizedProcessor.java @@ -36,15 +36,15 @@ @FunctionalInterface public interface SynthesizedProcessor { - /** - * 从一批被合成注解中,获取指定名称与类型的属性值 - * - * @param attributeName 属性名称 - * @param attributeType 属性类型 - * @param synthesizedAnnotations 被合成的注解 - * @param 属性类型 - * @return 属性值 - */ - R getAttributeValue(String attributeName, Class attributeType, Collection synthesizedAnnotations); + /** + * 从一批被合成注解中,获取指定名称与类型的属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @param synthesizedAnnotations 被合成的注解 + * @param 属性类型 + * @return 属性值 + */ + R getAttributeValue(String attributeName, Class attributeType, Collection synthesizedAnnotations); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/SyntheticProxy.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/SyntheticProxy.java index f8ab20c834..c118d56073 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/SyntheticProxy.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/SyntheticProxy.java @@ -47,113 +47,113 @@ */ class SyntheticProxy implements InvocationHandler { - private final Synthetic synthetic; - private final Synthesized annotation; - private final Map> methods; - - SyntheticProxy(Synthetic synthetic, Synthesized annotation) { - this.synthetic = synthetic; - this.annotation = annotation; - this.methods = new HashMap<>(9); - loadMethods(); - } - - /** - * 创建一个代理注解,生成的代理对象将是{@link Proxys}与指定的注解类的子类。 - *
    - *
  • 当作为{@code annotationType}所指定的类型使用时,其属性将通过合成它的{@link Synthetic}获取;
  • - *
  • 当作为{@link Proxys}或{@link Synthesized}使用时,将可以获得原始注解实例的相关信息;
  • - *
- * - * @param annotationType 注解类型 - * @param synthetic 合成注解 - * @return 代理注解 - */ - static T create( - Class annotationType, Synthetic synthetic) { - final Synthesized annotation = synthetic.getSynthesizedAnnotation(annotationType); - final org.aoju.bus.core.scanner.SyntheticProxy proxyHandler = new org.aoju.bus.core.scanner.SyntheticProxy(synthetic, annotation); - if (ObjectKit.isNull(annotation)) { - return null; - } - return (T) Proxy.newProxyInstance( - annotationType.getClassLoader(), - new Class[]{annotationType, Proxys.class}, - proxyHandler - ); - } - - static boolean isProxyAnnotation(Class targetClass) { - return ClassKit.isAssignable(Proxys.class, targetClass); - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - return Optional.ofNullable(methods.get(method.getName())) - .map(m -> m.apply(method, args)) - .orElseGet(() -> ReflectKit.invoke(this, method, args)); - } - - void loadMethods() { - methods.put("toString", (method, args) -> proxyToString()); - methods.put("hashCode", (method, args) -> proxyHashCode()); - methods.put("getSyntheticAnnotation", (method, args) -> proxyGetSyntheticAnnotation()); - methods.put("getSynthesizedAnnotation", (method, args) -> proxyGetSynthesizedAnnotation()); - methods.put("getRoot", (method, args) -> annotation.getRoot()); - methods.put("isRoot", (method, args) -> annotation.isRoot()); - methods.put("getVerticalDistance", (method, args) -> annotation.getVerticalDistance()); - methods.put("getHorizontalDistance", (method, args) -> annotation.getHorizontalDistance()); - methods.put("hasAttribute", (method, args) -> annotation.hasAttribute((String) args[0], (Class) args[1])); - methods.put("getAttribute", (method, args) -> annotation.getAttribute((String) args[0])); - methods.put("annotationType", (method, args) -> annotation.annotationType()); - for (final Method declaredMethod : annotation.getAnnotation().annotationType().getDeclaredMethods()) { - methods.put(declaredMethod.getName(), (method, args) -> proxyAttributeValue(method)); - } - } - - private String proxyToString() { - final String attributes = Stream.of(annotation.annotationType().getDeclaredMethods()) - .filter(AnnoKit::isAttributeMethod) - .map(method -> StringKit.format("{}={}", method.getName(), synthetic.getAttribute(method.getName(), method.getReturnType()))) - .collect(Collectors.joining(", ")); - return StringKit.format("@{}({})", annotation.annotationType().getName(), attributes); - } - - private int proxyHashCode() { - return Objects.hash(synthetic, annotation); - } - - private Object proxyGetSyntheticAnnotation() { - return synthetic; - } - - private Object proxyGetSynthesizedAnnotation() { - return annotation; - } - - private Object proxyAttributeValue(Method attributeMethod) { - return synthetic.getAttribute(attributeMethod.getName(), attributeMethod.getReturnType()); - } - - /** - * 通过代理类生成的合成注解 - */ - interface Proxys extends Synthesized { - - /** - * 获取该注解所属的合成注解 - * - * @return 合成注解 - */ - Synthetic getSyntheticAnnotation(); - - /** - * 获取该代理注解对应的已合成注解 - * - * @return 理注解对应的已合成注解 - */ - Synthesized getSynthesizedAnnotation(); - - } + private final Synthetic synthetic; + private final Synthesized annotation; + private final Map> methods; + + SyntheticProxy(Synthetic synthetic, Synthesized annotation) { + this.synthetic = synthetic; + this.annotation = annotation; + this.methods = new HashMap<>(9); + loadMethods(); + } + + /** + * 创建一个代理注解,生成的代理对象将是{@link Proxys}与指定的注解类的子类。 + *
    + *
  • 当作为{@code annotationType}所指定的类型使用时,其属性将通过合成它的{@link Synthetic}获取;
  • + *
  • 当作为{@link Proxys}或{@link Synthesized}使用时,将可以获得原始注解实例的相关信息;
  • + *
+ * + * @param annotationType 注解类型 + * @param synthetic 合成注解 + * @return 代理注解 + */ + static T create( + Class annotationType, Synthetic synthetic) { + final Synthesized annotation = synthetic.getSynthesizedAnnotation(annotationType); + final org.aoju.bus.core.scanner.SyntheticProxy proxyHandler = new org.aoju.bus.core.scanner.SyntheticProxy(synthetic, annotation); + if (ObjectKit.isNull(annotation)) { + return null; + } + return (T) Proxy.newProxyInstance( + annotationType.getClassLoader(), + new Class[]{annotationType, Proxys.class}, + proxyHandler + ); + } + + static boolean isProxyAnnotation(Class targetClass) { + return ClassKit.isAssignable(Proxys.class, targetClass); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return Optional.ofNullable(methods.get(method.getName())) + .map(m -> m.apply(method, args)) + .orElseGet(() -> ReflectKit.invoke(this, method, args)); + } + + void loadMethods() { + methods.put("toString", (method, args) -> proxyToString()); + methods.put("hashCode", (method, args) -> proxyHashCode()); + methods.put("getSyntheticAnnotation", (method, args) -> proxyGetSyntheticAnnotation()); + methods.put("getSynthesizedAnnotation", (method, args) -> proxyGetSynthesizedAnnotation()); + methods.put("getRoot", (method, args) -> annotation.getRoot()); + methods.put("isRoot", (method, args) -> annotation.isRoot()); + methods.put("getVerticalDistance", (method, args) -> annotation.getVerticalDistance()); + methods.put("getHorizontalDistance", (method, args) -> annotation.getHorizontalDistance()); + methods.put("hasAttribute", (method, args) -> annotation.hasAttribute((String) args[0], (Class) args[1])); + methods.put("getAttribute", (method, args) -> annotation.getAttribute((String) args[0])); + methods.put("annotationType", (method, args) -> annotation.annotationType()); + for (final Method declaredMethod : annotation.getAnnotation().annotationType().getDeclaredMethods()) { + methods.put(declaredMethod.getName(), (method, args) -> proxyAttributeValue(method)); + } + } + + private String proxyToString() { + final String attributes = Stream.of(annotation.annotationType().getDeclaredMethods()) + .filter(AnnoKit::isAttributeMethod) + .map(method -> StringKit.format("{}={}", method.getName(), synthetic.getAttribute(method.getName(), method.getReturnType()))) + .collect(Collectors.joining(", ")); + return StringKit.format("@{}({})", annotation.annotationType().getName(), attributes); + } + + private int proxyHashCode() { + return Objects.hash(synthetic, annotation); + } + + private Object proxyGetSyntheticAnnotation() { + return synthetic; + } + + private Object proxyGetSynthesizedAnnotation() { + return annotation; + } + + private Object proxyAttributeValue(Method attributeMethod) { + return synthetic.getAttribute(attributeMethod.getName(), attributeMethod.getReturnType()); + } + + /** + * 通过代理类生成的合成注解 + */ + interface Proxys extends Synthesized { + + /** + * 获取该注解所属的合成注解 + * + * @return 合成注解 + */ + Synthetic getSyntheticAnnotation(); + + /** + * 获取该代理注解对应的已合成注解 + * + * @return 理注解对应的已合成注解 + */ + Synthesized getSynthesizedAnnotation(); + + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/AbstractTypeScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/AbstractTypeScanner.java index eab9300a2a..79229aafb4 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/AbstractTypeScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/AbstractTypeScanner.java @@ -57,10 +57,10 @@ public abstract class AbstractTypeScanner> impl */ private final List>> converters; /** - * 当前实例 - */ - private final T typedThis; - /** + * 当前实例 + */ + private final T typedThis; + /** * 是否允许扫描父接口 */ private boolean includeInterfaces; @@ -87,71 +87,23 @@ public abstract class AbstractTypeScanner> impl * @param excludeTypes 不包含的类型 */ protected AbstractTypeScanner(boolean includeSupperClass, boolean includeInterfaces, Predicate> filter, Set> excludeTypes) { - Assert.notNull(filter, "filter must not null"); - Assert.notNull(excludeTypes, "excludeTypes must not null"); - this.includeSupperClass = includeSupperClass; - this.includeInterfaces = includeInterfaces; - this.filter = filter; - this.excludeTypes = excludeTypes; - this.converters = new ArrayList<>(); - this.typedThis = (T) this; - } - - /** - * 是否允许扫描父类 - * - * @return 是否允许扫描父类 - */ - public boolean isIncludeSupperClass() { - return includeSupperClass; - } - - /** - * 是否允许扫描父接口 - * - * @return 是否允许扫描父接口 - */ - public boolean isIncludeInterfaces() { - return includeInterfaces; - } - - /** - * 设置过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 - * - * @param filter 过滤器 - * @return 当前实例 - */ - public T setFilter(Predicate> filter) { - Assert.notNull(filter, "filter must not null"); - this.filter = filter; - return typedThis; - } - - /** - * 添加不扫描的类型,该类型及其树结构将直接不被查找 - * - * @param excludeTypes 不扫描的类型 - * @return 当前实例 - */ - public T addExcludeTypes(Class... excludeTypes) { - CollKit.addAll(this.excludeTypes, excludeTypes); - return typedThis; - } + Assert.notNull(filter, "filter must not null"); + Assert.notNull(excludeTypes, "excludeTypes must not null"); + this.includeSupperClass = includeSupperClass; + this.includeInterfaces = includeInterfaces; + this.filter = filter; + this.excludeTypes = excludeTypes; + this.converters = new ArrayList<>(); + this.typedThis = (T) this; + } - /** - * 添加转换器 - * - * @param converter 转换器 - * @return 当前实例 - * @see JdkProxyClassConverter + /** + * 是否允许扫描父类 + * + * @return 是否允许扫描父类 */ - public T addConverters(UnaryOperator> converter) { - Assert.notNull(converter, "converter must not null"); - this.converters.add(converter); - if (!this.hasConverters) { - this.hasConverters = CollKit.isNotEmpty(this.converters); - } - return typedThis; + public boolean isIncludeSupperClass() { + return includeSupperClass; } /** @@ -165,6 +117,15 @@ protected T setIncludeSupperClass(boolean includeSupperClass) { return typedThis; } + /** + * 是否允许扫描父接口 + * + * @return 是否允许扫描父接口 + */ + public boolean isIncludeInterfaces() { + return includeInterfaces; + } + /** * 是否允许扫描父接口 * @@ -176,6 +137,45 @@ protected T setIncludeInterfaces(boolean includeInterfaces) { return typedThis; } + /** + * 设置过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 + * + * @param filter 过滤器 + * @return 当前实例 + */ + public T setFilter(Predicate> filter) { + Assert.notNull(filter, "filter must not null"); + this.filter = filter; + return typedThis; + } + + /** + * 添加不扫描的类型,该类型及其树结构将直接不被查找 + * + * @param excludeTypes 不扫描的类型 + * @return 当前实例 + */ + public T addExcludeTypes(Class... excludeTypes) { + CollKit.addAll(this.excludeTypes, excludeTypes); + return typedThis; + } + + /** + * 添加转换器 + * + * @param converter 转换器 + * @return 当前实例 + * @see JdkProxyClassConverter + */ + public T addConverters(UnaryOperator> converter) { + Assert.notNull(converter, "converter must not null"); + this.converters.add(converter); + if (!this.hasConverters) { + this.hasConverters = CollKit.isNotEmpty(this.converters); + } + return typedThis; + } + /** * 则根据广度优先递归扫描类的层级结构,并对层级结构中类/接口声明的层级索引和它们声明的注解对象进行处理 * @@ -185,59 +185,59 @@ protected T setIncludeInterfaces(boolean includeInterfaces) { */ @Override public void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { - filter = ObjectKit.defaultIfNull(filter, annotation -> true); - final Class sourceClass = getClassFormAnnotatedElement(annotatedEle); - final Deque>> classDeque = CollKit.newLinkedList(CollKit.newArrayList(sourceClass)); - final Set> accessedTypes = new LinkedHashSet<>(); - int index = 0; - while (!classDeque.isEmpty()) { - final List> currClassQueue = classDeque.removeFirst(); - final List> nextClassQueue = new ArrayList<>(); - for (Class targetClass : currClassQueue) { - targetClass = convert(targetClass); - // 过滤不需要处理的类 - if (isNotNeedProcess(accessedTypes, targetClass)) { - continue; - } - accessedTypes.add(targetClass); - // 扫描父类 - scanSuperClassIfNecessary(nextClassQueue, targetClass); - // 扫描接口 - scanInterfaceIfNecessary(nextClassQueue, targetClass); - // 处理层级索引和注解 - final Annotation[] targetAnnotations = getAnnotationsFromTargetClass(annotatedEle, index, targetClass); - for (final Annotation annotation : targetAnnotations) { - if (AnnoKit.isNotJdkMateAnnotation(annotation.annotationType()) || filter.test(annotation)) { - consumer.accept(index, annotation); - } - } - index++; - } - if (CollKit.isNotEmpty(nextClassQueue)) { - classDeque.addLast(nextClassQueue); - } - } - } + filter = ObjectKit.defaultIfNull(filter, annotation -> true); + final Class sourceClass = getClassFormAnnotatedElement(annotatedEle); + final Deque>> classDeque = CollKit.newLinkedList(CollKit.newArrayList(sourceClass)); + final Set> accessedTypes = new LinkedHashSet<>(); + int index = 0; + while (!classDeque.isEmpty()) { + final List> currClassQueue = classDeque.removeFirst(); + final List> nextClassQueue = new ArrayList<>(); + for (Class targetClass : currClassQueue) { + targetClass = convert(targetClass); + // 过滤不需要处理的类 + if (isNotNeedProcess(accessedTypes, targetClass)) { + continue; + } + accessedTypes.add(targetClass); + // 扫描父类 + scanSuperClassIfNecessary(nextClassQueue, targetClass); + // 扫描接口 + scanInterfaceIfNecessary(nextClassQueue, targetClass); + // 处理层级索引和注解 + final Annotation[] targetAnnotations = getAnnotationsFromTargetClass(annotatedEle, index, targetClass); + for (final Annotation annotation : targetAnnotations) { + if (AnnoKit.isNotJdkMateAnnotation(annotation.annotationType()) || filter.test(annotation)) { + consumer.accept(index, annotation); + } + } + index++; + } + if (CollKit.isNotEmpty(nextClassQueue)) { + classDeque.addLast(nextClassQueue); + } + } + } - /** - * 从要搜索的注解元素上获得要递归的类型 - * - * @param annotatedElement 注解元素 - * @return 要递归的类型 - */ - protected abstract Class getClassFormAnnotatedElement(AnnotatedElement annotatedElement); + /** + * 从要搜索的注解元素上获得要递归的类型 + * + * @param annotatedElement 注解元素 + * @return 要递归的类型 + */ + protected abstract Class getClassFormAnnotatedElement(AnnotatedElement annotatedElement); - /** - * 从类上获取最终所需的目标注解 - * - * @param source 最初的注解元素 - * @param index 类的层级索引 - * @param targetClass 类 - * @return 最终所需的目标注解 - */ - protected abstract Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass); + /** + * 从类上获取最终所需的目标注解 + * + * @param source 最初的注解元素 + * @param index 类的层级索引 + * @param targetClass 类 + * @return 最终所需的目标注解 + */ + protected abstract Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass); - /** + /** * 当前类是否不需要处理 * * @param accessedTypes 访问类型 @@ -281,29 +281,29 @@ protected void scanSuperClassIfNecessary(List> nextClassQueue, Class } } - /** - * 若存在转换器,则使用转换器对目标类进行转换 - * - * @param target 目标类 - * @return 转换后的类 - */ - protected Class convert(Class target) { - if (hasConverters) { - for (final UnaryOperator> converter : converters) { - target = converter.apply(target); - } - } - return target; - } + /** + * 若存在转换器,则使用转换器对目标类进行转换 + * + * @param target 目标类 + * @return 转换后的类 + */ + protected Class convert(Class target) { + if (hasConverters) { + for (final UnaryOperator> converter : converters) { + target = converter.apply(target); + } + } + return target; + } - /** - * 若类型为jdk代理类,则尝试转换为原始被代理类 - */ - public static class JdkProxyClassConverter implements UnaryOperator> { - @Override - public Class apply(Class sourceClass) { - return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; - } - } + /** + * 若类型为jdk代理类,则尝试转换为原始被代理类 + */ + public static class JdkProxyClassConverter implements UnaryOperator> { + @Override + public Class apply(Class sourceClass) { + return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; + } + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MetaScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MetaScanner.java index 6a06d6fe9f..8f57635be6 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MetaScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MetaScanner.java @@ -44,85 +44,86 @@ /** * 扫描注解类上存在的注解,支持处理枚举实例或枚举类型 * 需要注意,当待解析是枚举类时,有可能与{@link TypeScanner}冲突 + * * @author Kimi Liu * @since Java 17+ */ public class MetaScanner implements AnnotationScanner { - /** - * 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 - */ - private final boolean includeSupperMetaAnnotation; + /** + * 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 + */ + private final boolean includeSupperMetaAnnotation; - /** - * 构造一个元注解扫描器 - * - * @param includeSupperMetaAnnotation 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 - */ - public MetaScanner(boolean includeSupperMetaAnnotation) { - this.includeSupperMetaAnnotation = includeSupperMetaAnnotation; - } + /** + * 构造一个元注解扫描器 + * + * @param includeSupperMetaAnnotation 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 + */ + public MetaScanner(boolean includeSupperMetaAnnotation) { + this.includeSupperMetaAnnotation = includeSupperMetaAnnotation; + } - /** - * 构造一个元注解扫描器,默认在扫描当前注解上的元注解后,并继续递归扫描元注解 - */ - public MetaScanner() { - this(true); - } + /** + * 构造一个元注解扫描器,默认在扫描当前注解上的元注解后,并继续递归扫描元注解 + */ + public MetaScanner() { + this(true); + } - /** - * 判断是否支持扫描该注解元素,仅当注解元素是{@link Annotation}接口的子类{@link Class}时返回{@code true} - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 是否支持扫描该注解元素 - */ - @Override - public boolean support(AnnotatedElement annotatedEle) { - return (annotatedEle instanceof Class && ClassKit.isAssignable(Annotation.class, (Class) annotatedEle)); - } + /** + * 判断是否支持扫描该注解元素,仅当注解元素是{@link Annotation}接口的子类{@link Class}时返回{@code true} + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 是否支持扫描该注解元素 + */ + @Override + public boolean support(AnnotatedElement annotatedEle) { + return (annotatedEle instanceof Class && ClassKit.isAssignable(Annotation.class, (Class) annotatedEle)); + } - /** - * 获取注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 注解 - */ - @Override - public List getAnnotations(AnnotatedElement annotatedEle) { - final List annotations = new ArrayList<>(); - scan( - (index, annotation) -> annotations.add(annotation), annotatedEle, + /** + * 获取注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 注解 + */ + @Override + public List getAnnotations(AnnotatedElement annotatedEle) { + final List annotations = new ArrayList<>(); + scan( + (index, annotation) -> annotations.add(annotation), annotatedEle, annotation -> ObjectKit.notEquals(annotation, annotatedEle) - ); - return annotations; - } + ); + return annotations; + } - /** - * 按广度优先扫描指定注解上的元注解,对扫描到的注解与层级索引进行操作 - * - * @param consumer 当前层级索引与操作 - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param filter 过滤器 - */ - @Override - public void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { - filter = ObjectKit.defaultIfNull(filter, t -> true); - final Deque>> deque = CollKit.newLinkedList(CollKit.newArrayList((Class) annotatedEle)); - int distance = 0; - do { - final List> annotationTypes = deque.removeFirst(); - for (final Class type : annotationTypes) { - final List metaAnnotations = Stream.of(type.getAnnotations()) - .filter(a -> !AnnoKit.isJdkMetaAnnotation(a.annotationType())) - .filter(filter) - .collect(Collectors.toList()); - for (final Annotation metaAnnotation : metaAnnotations) { - consumer.accept(distance, metaAnnotation); - } - deque.addLast(CollKit.toList(metaAnnotations, Annotation::annotationType)); - } - distance++; - } while (includeSupperMetaAnnotation && !deque.isEmpty()); - } + /** + * 按广度优先扫描指定注解上的元注解,对扫描到的注解与层级索引进行操作 + * + * @param consumer 当前层级索引与操作 + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param filter 过滤器 + */ + @Override + public void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { + filter = ObjectKit.defaultIfNull(filter, t -> true); + final Deque>> deque = CollKit.newLinkedList(CollKit.newArrayList((Class) annotatedEle)); + int distance = 0; + do { + final List> annotationTypes = deque.removeFirst(); + for (final Class type : annotationTypes) { + final List metaAnnotations = Stream.of(type.getAnnotations()) + .filter(a -> !AnnoKit.isJdkMetaAnnotation(a.annotationType())) + .filter(filter) + .collect(Collectors.toList()); + for (final Annotation metaAnnotation : metaAnnotations) { + consumer.accept(distance, metaAnnotation); + } + deque.addLast(CollKit.toList(metaAnnotations, Annotation::annotationType)); + } + distance++; + } while (includeSupperMetaAnnotation && !deque.isEmpty()); + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MethodScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MethodScanner.java index 453b730c82..ad066ba836 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MethodScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MethodScanner.java @@ -46,103 +46,103 @@ */ public class MethodScanner extends AbstractTypeScanner implements AnnotationScanner { - /** - * 构造一个方法注解扫描器 - * - * @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 - * @param filter 过滤器 - * @param excludeTypes 不包含的类型 - */ - public MethodScanner(boolean scanSameSignatureMethod, Predicate> filter, Set> excludeTypes) { - super(scanSameSignatureMethod, scanSameSignatureMethod, filter, excludeTypes); - } + /** + * 构造一个方法注解扫描器 + * + * @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 + * @param filter 过滤器 + * @param excludeTypes 不包含的类型 + */ + public MethodScanner(boolean scanSameSignatureMethod, Predicate> filter, Set> excludeTypes) { + super(scanSameSignatureMethod, scanSameSignatureMethod, filter, excludeTypes); + } - /** - * 构造一个类注解扫描器 - * - * @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 - */ - public MethodScanner(boolean scanSameSignatureMethod) { - this(scanSameSignatureMethod, targetClass -> true, CollKit.newLinkedHashSet()); - } + /** + * 构造一个类注解扫描器 + * + * @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 + */ + public MethodScanner(boolean scanSameSignatureMethod) { + this(scanSameSignatureMethod, targetClass -> true, CollKit.newLinkedHashSet()); + } - /** - * 构造一个类注解扫描器,仅扫描该方法上直接声明的注解 - */ - public MethodScanner() { - this(false); - } + /** + * 构造一个类注解扫描器,仅扫描该方法上直接声明的注解 + */ + public MethodScanner() { + this(false); + } - /** - * 判断是否支持扫描该注解元素,仅当注解元素是{@link Method}时返回{@code true} - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return boolean 是否支持扫描该注解元素 - */ - @Override - public boolean support(AnnotatedElement annotatedEle) { - return annotatedEle instanceof Method; - } + /** + * 判断是否支持扫描该注解元素,仅当注解元素是{@link Method}时返回{@code true} + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return boolean 是否支持扫描该注解元素 + */ + @Override + public boolean support(AnnotatedElement annotatedEle) { + return annotatedEle instanceof Method; + } - /** - * 获取声明该方法的类 - * - * @param annotatedElement 注解元素 - * @return 要递归的类型 - * @see Method#getDeclaringClass() - */ - @Override - protected Class getClassFormAnnotatedElement(AnnotatedElement annotatedElement) { - return ((Method) annotatedElement).getDeclaringClass(); - } + /** + * 获取声明该方法的类 + * + * @param annotatedElement 注解元素 + * @return 要递归的类型 + * @see Method#getDeclaringClass() + */ + @Override + protected Class getClassFormAnnotatedElement(AnnotatedElement annotatedElement) { + return ((Method) annotatedElement).getDeclaringClass(); + } - /** - * 若父类/父接口中方法具有相同的方法签名,则返回该方法上的注解 - * - * @param source 原始方法 - * @param index 类的层级索引 - * @param targetClass 类 - * @return 最终所需的目标注解 - */ - @Override - protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass) { - final Method sourceMethod = (Method) source; - return Stream.of(targetClass.getDeclaredMethods()) - .filter(superMethod -> !superMethod.isBridge()) - .filter(superMethod -> hasSameSignature(sourceMethod, superMethod)) - .map(AnnotatedElement::getAnnotations) - .flatMap(Stream::of) - .toArray(Annotation[]::new); - } + /** + * 若父类/父接口中方法具有相同的方法签名,则返回该方法上的注解 + * + * @param source 原始方法 + * @param index 类的层级索引 + * @param targetClass 类 + * @return 最终所需的目标注解 + */ + @Override + protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass) { + final Method sourceMethod = (Method) source; + return Stream.of(targetClass.getDeclaredMethods()) + .filter(superMethod -> !superMethod.isBridge()) + .filter(superMethod -> hasSameSignature(sourceMethod, superMethod)) + .map(AnnotatedElement::getAnnotations) + .flatMap(Stream::of) + .toArray(Annotation[]::new); + } - /** - * 设置是否扫描类层级结构中具有相同方法签名的方法 - * - * @param scanSuperMethodIfOverride 是否扫描类层级结构中具有相同方法签名的方法 - * @return 当前实例 - */ - public MethodScanner setScanSameSignatureMethod(boolean scanSuperMethodIfOverride) { - setIncludeInterfaces(scanSuperMethodIfOverride); - setIncludeSupperClass(scanSuperMethodIfOverride); - return this; - } + /** + * 设置是否扫描类层级结构中具有相同方法签名的方法 + * + * @param scanSuperMethodIfOverride 是否扫描类层级结构中具有相同方法签名的方法 + * @return 当前实例 + */ + public MethodScanner setScanSameSignatureMethod(boolean scanSuperMethodIfOverride) { + setIncludeInterfaces(scanSuperMethodIfOverride); + setIncludeSupperClass(scanSuperMethodIfOverride); + return this; + } - /** - * 该方法是否具备与扫描的方法相同的方法签名 - */ - private boolean hasSameSignature(Method sourceMethod, Method superMethod) { - if (false == StringKit.equals(sourceMethod.getName(), superMethod.getName())) { - return false; - } - final Class[] sourceParameterTypes = sourceMethod.getParameterTypes(); - final Class[] targetParameterTypes = superMethod.getParameterTypes(); - if (sourceParameterTypes.length != targetParameterTypes.length) { - return false; - } - if (!ArrayKit.containsAll(sourceParameterTypes, targetParameterTypes)) { - return false; - } - return ClassKit.isAssignable(superMethod.getReturnType(), sourceMethod.getReturnType()); - } + /** + * 该方法是否具备与扫描的方法相同的方法签名 + */ + private boolean hasSameSignature(Method sourceMethod, Method superMethod) { + if (false == StringKit.equals(sourceMethod.getName(), superMethod.getName())) { + return false; + } + final Class[] sourceParameterTypes = sourceMethod.getParameterTypes(); + final Class[] targetParameterTypes = superMethod.getParameterTypes(); + if (sourceParameterTypes.length != targetParameterTypes.length) { + return false; + } + if (!ArrayKit.containsAll(sourceParameterTypes, targetParameterTypes)) { + return false; + } + return ClassKit.isAssignable(superMethod.getReturnType(), sourceMethod.getReturnType()); + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/TypeScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/TypeScanner.java index 9a766dadfb..9c62a2e234 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/TypeScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/TypeScanner.java @@ -37,95 +37,96 @@ /** * 扫描{@link Class}上的注解 + * * @author Kimi Liu * @since Java 17+ */ public class TypeScanner extends AbstractTypeScanner implements AnnotationScanner { - /** - * 构造一个类注解扫描器 - * - * @param includeSupperClass 是否允许扫描父类 - * @param includeInterfaces 是否允许扫描父接口 - * @param filter 过滤器 - * @param excludeTypes 不包含的类型 - */ - public TypeScanner(boolean includeSupperClass, boolean includeInterfaces, Predicate> filter, Set> excludeTypes) { - super(includeSupperClass, includeInterfaces, filter, excludeTypes); - } + /** + * 构造一个类注解扫描器 + * + * @param includeSupperClass 是否允许扫描父类 + * @param includeInterfaces 是否允许扫描父接口 + * @param filter 过滤器 + * @param excludeTypes 不包含的类型 + */ + public TypeScanner(boolean includeSupperClass, boolean includeInterfaces, Predicate> filter, Set> excludeTypes) { + super(includeSupperClass, includeInterfaces, filter, excludeTypes); + } - /** - * 构建一个类注解扫描器,默认允许扫描指定元素的父类以及父接口 - */ - public TypeScanner() { - this(true, true, t -> true, CollKit.newLinkedHashSet()); - } + /** + * 构建一个类注解扫描器,默认允许扫描指定元素的父类以及父接口 + */ + public TypeScanner() { + this(true, true, t -> true, CollKit.newLinkedHashSet()); + } - /** - * 判断是否支持扫描该注解元素,仅当注解元素是{@link Class}接时返回{@code true} - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 是否支持扫描该注解元素 - */ - @Override - public boolean support(AnnotatedElement annotatedEle) { - return annotatedEle instanceof Class; - } + /** + * 判断是否支持扫描该注解元素,仅当注解元素是{@link Class}接时返回{@code true} + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 是否支持扫描该注解元素 + */ + @Override + public boolean support(AnnotatedElement annotatedEle) { + return annotatedEle instanceof Class; + } - /** - * 将注解元素转为{@link Class} - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 要递归的类型 - */ - @Override - protected Class getClassFormAnnotatedElement(AnnotatedElement annotatedEle) { - return (Class) annotatedEle; - } + /** + * 将注解元素转为{@link Class} + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 要递归的类型 + */ + @Override + protected Class getClassFormAnnotatedElement(AnnotatedElement annotatedEle) { + return (Class) annotatedEle; + } - /** - * 获取{@link Class#getAnnotations()} - * - * @param source 最初的注解元素 - * @param index 类的层级索引 - * @param targetClass 类 - * @return 类上直接声明的注解 - */ - @Override - protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass) { - return targetClass.getAnnotations(); - } + /** + * 获取{@link Class#getAnnotations()} + * + * @param source 最初的注解元素 + * @param index 类的层级索引 + * @param targetClass 类 + * @return 类上直接声明的注解 + */ + @Override + protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass) { + return targetClass.getAnnotations(); + } - /** - * 是否允许扫描父类 - * - * @param includeSupperClass 是否允许扫描父类 - * @return 当前实例 - */ - @Override - public TypeScanner setIncludeSupperClass(boolean includeSupperClass) { - return super.setIncludeSupperClass(includeSupperClass); - } + /** + * 是否允许扫描父类 + * + * @param includeSupperClass 是否允许扫描父类 + * @return 当前实例 + */ + @Override + public TypeScanner setIncludeSupperClass(boolean includeSupperClass) { + return super.setIncludeSupperClass(includeSupperClass); + } - /** - * 是否允许扫描父接口 - * - * @param includeInterfaces 是否允许扫描父类 - * @return 当前实例 - */ - @Override - public TypeScanner setIncludeInterfaces(boolean includeInterfaces) { - return super.setIncludeInterfaces(includeInterfaces); - } + /** + * 是否允许扫描父接口 + * + * @param includeInterfaces 是否允许扫描父类 + * @return 当前实例 + */ + @Override + public TypeScanner setIncludeInterfaces(boolean includeInterfaces) { + return super.setIncludeInterfaces(includeInterfaces); + } - /** - * 若类型为jdk代理类,则尝试转换为原始被代理类 - */ - public static class JdkProxyClassConverter implements UnaryOperator> { - @Override - public Class apply(Class sourceClass) { - return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; - } - } + /** + * 若类型为jdk代理类,则尝试转换为原始被代理类 + */ + public static class JdkProxyClassConverter implements UnaryOperator> { + @Override + public Class apply(Class sourceClass) { + return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; + } + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java index f8964c5888..3bb20412be 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java @@ -1219,7 +1219,7 @@ public TextBuilder appendFixedWidthPadLeft(final int value, final int width, fin * 如果物体比长度大,右边的部分就会丢失 * 如果对象为空,则使用空文本值 * - * @param object 要追加的对象null使用空文本 + * @param object 要追加的对象null使用空文本 * @param width 固定的字段宽度,零或负没有影响 * @param padChar 要使用的填充字符 * @return this @@ -1264,8 +1264,8 @@ public TextBuilder appendFixedWidthPadRight(final int value, final int width, fi * 将对象的字符串表示形式插入到此生成器中 * 插入null将使用存储的空文本值 * - * @param index 要添加的索引必须有效 - * @param object 要插入的对象 + * @param index 要添加的索引必须有效 + * @param object 要插入的对象 * @return this * @throws IndexOutOfBoundsException 如果索引无效 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfFilter.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/AbstractFilter.java similarity index 72% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfFilter.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/AbstractFilter.java index 7ad3ceda86..b56a789f0d 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfFilter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/AbstractFilter.java @@ -23,38 +23,55 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; +package org.aoju.bus.core.text.bloom; + +import java.util.BitSet; /** + * 抽象Bloom过滤器 + * * @author Kimi Liu * @since Java 17+ */ -public class HfFilter extends AbstractFilter { +public abstract class AbstractFilter implements BloomFilter { private static final long serialVersionUID = 1L; - public HfFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } + private final BitSet bitSet; + protected int size; - public HfFilter(long maxValue) { - super(maxValue); + /** + * 构造 + * + * @param size 容量 + */ + public AbstractFilter(final int size) { + this.size = size; + this.bitSet = new BitSet(size); } @Override - public long hash(String text) { - int length = text.length(); - long hash = 0; - - for (int i = 0; i < length; i++) { - hash += text.charAt(i) * 3 * i; - } + public boolean contains(final String text) { + return bitSet.get(Math.abs(hash(text))); + } - if (hash < 0) { - hash = -hash; + @Override + public boolean add(final String text) { + final int hash = Math.abs(hash(text)); + if (bitSet.get(hash)) { + return false; } - return hash % size; + bitSet.set(hash); + return true; } + /** + * 自定义Hash方法 + * + * @param text 字符串 + * @return HashCode + */ + public abstract int hash(String text); + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/BloomFilter.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/BloomFilter.java similarity index 84% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/BloomFilter.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/BloomFilter.java index 17eebc8e7d..40372f918e 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/BloomFilter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/BloomFilter.java @@ -23,14 +23,14 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom; +package org.aoju.bus.core.text.bloom; import java.io.Serializable; /** * Bloom filter 是由 Howard Bloom 在 1970 年提出的二进制向量数据结构,它具有很好的空间和时间效率,被用来检测一个元素是不是集合中的一个成员 - * 如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中,因此Bloom filter具有100%的召回率 - * 这样每个检测请求返回有“在集合内(可能错误)”和“不在集合内(绝对不在集合内)”两种情况 + * 如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中 + * 因此Bloom filter具有100%的召回率这样每个检测请求返回有“在集合内(可能错误)”和“不在集合内(绝对不在集合内)”两种情况 * * @author Kimi Liu * @since Java 17+ @@ -45,10 +45,11 @@ public interface BloomFilter extends Serializable { /** * 在boolean的bitMap中增加一个字符串 - * 如果存在就返回false如果不存在先增加这个字符串.再返回true + * 如果存在就返回{@code false} .如果不存在.先增加这个字符串.再返回{@code true} * * @param text 字符串 - * @return 是否加入成功,如果存在就返回false如果不存在返回true + * @return 是否加入成功,如果存在就返回{@code false} .如果不存在返回{@code true} */ boolean add(String text); -} \ No newline at end of file + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/BitMapBloomFilter.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/CombinedFilter.java similarity index 72% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/BitMapBloomFilter.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/CombinedFilter.java index c0fd66671f..4c4f5c466f 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/BitMapBloomFilter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/CombinedFilter.java @@ -23,14 +23,10 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom; - -import org.aoju.bus.core.bloom.filter.*; -import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.toolkit.MathKit; +package org.aoju.bus.core.text.bloom; /** - * BlommFilter 实现 + * 组合BloomFilter 实现 * 1.构建hash算法 * 2.散列hash映射到数组的bit位置 * 3.验证 @@ -39,38 +35,18 @@ * @author Kimi Liu * @since Java 17+ */ -public class BitMapBloomFilter implements BloomFilter { +public class CombinedFilter implements BloomFilter { private static final long serialVersionUID = 1L; - private BloomFilter[] filters; - - /** - * 构造,使用默认的5个过滤器 - * - * @param m M值决定BitMap的大小 - */ - public BitMapBloomFilter(int m) { - long mNum = MathKit.div(String.valueOf(m), String.valueOf(5)).longValue(); - long size = mNum * Normal._1024 * Normal._1024 * 8; - - filters = new BloomFilter[]{ - new DefaultFilter(size), - new ELFFilter(size), - new JSFilter(size), - new PJWFilter(size), - new SDBMFilter(size) - }; - } + private final BloomFilter[] filters; /** * 使用自定的多个过滤器建立BloomFilter * - * @param m M值决定BitMap的大小 * @param filters Bloom过滤器列表 */ - public BitMapBloomFilter(int m, BloomFilter... filters) { - this(m); + public CombinedFilter(final BloomFilter... filters) { this.filters = filters; } @@ -80,9 +56,9 @@ public BitMapBloomFilter(int m, BloomFilter... filters) { * @param text 字符串 */ @Override - public boolean add(String text) { + public boolean add(final String text) { boolean flag = false; - for (BloomFilter filter : filters) { + for (final BloomFilter filter : filters) { flag |= filter.add(text); } return flag; @@ -95,8 +71,8 @@ public boolean add(String text) { * @return 是否存在 */ @Override - public boolean contains(String text) { - for (BloomFilter filter : filters) { + public boolean contains(final String text) { + for (final BloomFilter filter : filters) { if (filter.contains(text) == false) { return false; } @@ -104,4 +80,4 @@ public boolean contains(String text) { return true; } -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/DefaultFilter.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/FuncFilter.java similarity index 73% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/filter/DefaultFilter.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/FuncFilter.java index 763236e927..eb5097a8a6 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/DefaultFilter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/FuncFilter.java @@ -23,31 +23,45 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; +package org.aoju.bus.core.text.bloom; -import org.aoju.bus.core.toolkit.HashKit; +import java.util.function.Function; /** - * 默认Bloom过滤器,使用Java自带的Hash算法 + * 基于Hash函数方法的{@link BloomFilter} * * @author Kimi Liu * @since Java 17+ */ -public class DefaultFilter extends AbstractFilter { +public class FuncFilter extends AbstractFilter { private static final long serialVersionUID = 1L; - public DefaultFilter(long maxValue, int machineNumber) { - super(maxValue, machineNumber); + private final Function hashFunc; + + /** + * @param size 最大值 + * @param hashFunc Hash函数 + */ + public FuncFilter(final int size, final Function hashFunc) { + super(size); + this.hashFunc = hashFunc; } - public DefaultFilter(long maxValue) { - super(maxValue); + /** + * 创建FuncFilter + * + * @param size 最大值 + * @param hashFunc Hash函数 + * @return FuncFilter + */ + public static FuncFilter of(final int size, final Function hashFunc) { + return new FuncFilter(size, hashFunc); } @Override - public long hash(String text) { - return HashKit.javaDefaultHash(text) % size; + public int hash(final String text) { + return hashFunc.apply(text).intValue() % size; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/package-info.java similarity index 73% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/package-info.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/package-info.java index d21a31c0e7..27dc9df885 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/package-info.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/package-info.java @@ -4,4 +4,4 @@ * @author Kimi Liu * @since Java 17+ */ -package org.aoju.bus.core.bloom; \ No newline at end of file +package org.aoju.bus.core.text.bloom; diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java index 15a5d86ca8..e80f0f9840 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java @@ -461,10 +461,10 @@ public static void setFieldValue(Object bean, String fieldNameOrIndex, Object va * @param bean Bean对象,支持Map、List、Collection、Array * @param expression 表达式,例如:person.friend[5].name * @return Bean属性值 - * @see PathExpression#get(Object) + * @see BeanPath#get(Object) */ public static T getProperty(Object bean, String expression) { - return (T) PathExpression.create(expression).get(bean); + return (T) BeanPath.of(expression).get(bean); } /** @@ -473,10 +473,10 @@ public static T getProperty(Object bean, String expression) { * @param bean Bean对象,支持Map、List、Collection、Array * @param expression 表达式,例如:person.friend[5].name * @param value 值 - * @see PathExpression#get(Object) + * @see BeanPath#get(Object) */ public static void setProperty(Object bean, String expression, Object value) { - PathExpression.create(expression).set(bean, value); + BeanPath.of(expression).set(bean, value); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java index 3adee37422..56a195ee3e 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java @@ -1956,46 +1956,6 @@ public static T findOneByField(Iterable collection, final String fieldNam }); } - /** - * 过滤 - * 过滤过程通过传入的Editor实现来返回需要的元素内容,这个Editor实现可以实现以下功能: - * - *
-     * 1、过滤出需要的对象,如果返回null表示这个元素对象抛弃
-     * 2、修改元素对象,返回集合中为修改后的对象
-     * 
- * - * @param Key类型 - * @param Value类型 - * @param map Map - * @param editor 编辑器接口 - * @return 过滤后的Map - * @see MapKit#filter(Map, Editor) - */ - public static Map filter(Map map, Editor> editor) { - return MapKit.filter(map, editor); - } - - /** - * 过滤 - * 过滤过程通过传入的Editor实现来返回需要的元素内容,这个Editor实现可以实现以下功能: - * - *
-     * 1、过滤出需要的对象,如果返回null表示这个元素对象抛弃
-     * 2、修改元素对象,返回集合中为修改后的对象
-     * 
- * - * @param Key类型 - * @param Value类型 - * @param map Map - * @param filter 编辑器接口 - * @return 过滤后的Map - * @see MapKit#filter(Map, Predicate) - */ - public static Map filter(Map map, Predicate> filter) { - return MapKit.filter(map, filter); - } - /** * 集合中匹配规则的数量 * diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/MapKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/MapKit.java index eaf163ee61..802f3b41b0 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/MapKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/MapKit.java @@ -25,9 +25,9 @@ ********************************************************************************/ package org.aoju.bus.core.toolkit; +import org.aoju.bus.core.collection.ArrayIterator; import org.aoju.bus.core.convert.Convert; import org.aoju.bus.core.exception.InternalException; -import org.aoju.bus.core.lang.Editor; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.Types; import org.aoju.bus.core.map.*; @@ -36,7 +36,10 @@ import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; /** * Map相关工具类 @@ -61,7 +64,7 @@ public class MapKit { * @param map 集合 * @return 是否为空 */ - public static boolean isEmpty(Map map) { + public static boolean isEmpty(final Map map) { return null == map || map.isEmpty(); } @@ -71,7 +74,7 @@ public static boolean isEmpty(Map map) { * @param map 集合 * @return 是否为非空 */ - public static boolean isNotEmpty(Map map) { + public static boolean isNotEmpty(final Map map) { return null != map && false == map.isEmpty(); } @@ -91,35 +94,25 @@ public static HashMap newHashMap() { * * @param Key类型 * @param Value类型 - * @param size 初始大小,由于默认负载因子0.75,传入的size会实际初始大小为size / 0.75 + * @param size 初始大小,由于默认负载因子0.75,传入的size会实际初始大小为size / 0.75 + 1 * @return HashMap对象 */ - public static HashMap newHashMap(int size) { + public static HashMap newHashMap(final int size) { return newHashMap(size, false); } /** * 新建一个HashMap * - * @param Key类型 - * @param Value类型 - * @param isOrder Map的Key是否有序,有序返回 {@link LinkedHashMap},否则返回 {@link HashMap} + * @param Key类型 + * @param Value类型 + * @param size 初始大小,由于默认负载因子0.75,传入的size会实际初始大小为size / 0.75 + 1 + * @param isLinked Map的Key是否有序,有序返回 {@link LinkedHashMap},否则返回 {@link HashMap} * @return HashMap对象 */ - public static HashMap newHashMap(boolean isOrder) { - return newHashMap(DEFAULT_INITIAL_CAPACITY, isOrder); - } - - /** - * 新建TreeMap,Key有序的Map - * - * @param Key类型 - * @param Value类型 - * @param comparator Key比较器 - * @return TreeMap - */ - public static TreeMap newTreeMap(Comparator comparator) { - return new TreeMap<>(comparator); + public static HashMap newHashMap(final int size, final boolean isLinked) { + final int initialCapacity = (int) (size / DEFAULT_LOAD_FACTOR) + 1; + return isLinked ? new LinkedHashMap<>(initialCapacity) : new HashMap<>(initialCapacity); } /** @@ -127,25 +120,35 @@ public static TreeMap newTreeMap(Comparator comparator) * * @param Key类型 * @param Value类型 - * @param size 初始大小,由于默认负载因子0.75,传入的size会实际初始大小为size / 0.75 + 1 * @param isLinked Map的Key是否有序,有序返回 {@link LinkedHashMap},否则返回 {@link HashMap} * @return HashMap对象 */ - public static HashMap newHashMap(int size, boolean isLinked) { - final int initialCapacity = (int) (size / DEFAULT_LOAD_FACTOR) + 1; - return isLinked ? new LinkedHashMap<>(initialCapacity) : new HashMap<>(initialCapacity); + public static HashMap newHashMap(final boolean isLinked) { + return newHashMap(DEFAULT_INITIAL_CAPACITY, isLinked); + } + + /** + * 新建TreeMap,Key有序的Map + * + * @param key的类型 + * @param value的类型 + * @param comparator Key比较器 + * @return TreeMap + */ + public static TreeMap newTreeMap(final Comparator comparator) { + return new TreeMap<>(comparator); } /** - * 新建TreeMap,Key有序的Map + * 新建TreeMap,Key有序的Map * - * @param Key类型 - * @param Value类型 + * @param key的类型 + * @param value的类型 * @param map Map * @param comparator Key比较器 * @return TreeMap */ - public static TreeMap newTreeMap(Map map, Comparator comparator) { + public static TreeMap newTreeMap(final Map map, final Comparator comparator) { final TreeMap treeMap = new TreeMap<>(comparator); if (false == isEmpty(map)) { treeMap.putAll(map); @@ -153,6 +156,17 @@ public static TreeMap newTreeMap(Map map, Comparator key的类型 + * @param value的类型 + * @return {@link SafeHashMap} + */ + public static ConcurrentHashMap newSafeHashMap() { + return new SafeHashMap<>(DEFAULT_INITIAL_CAPACITY); + } + /** * 创建键不重复Map * @@ -161,47 +175,36 @@ public static TreeMap newTreeMap(Map map, Comparator Map newIdentityMap(int size) { + public static Map newIdentityMap(final int size) { return new IdentityHashMap<>(size); } /** - * 新建一个初始容量为{@link MapKit#DEFAULT_INITIAL_CAPACITY} 的ConcurrentHashMap - * - * @param key的类型 - * @param value的类型 - * @return ConcurrentHashMap - */ - public static ConcurrentHashMap newConcurrentHashMap() { - return new ConcurrentHashMap<>(DEFAULT_INITIAL_CAPACITY); - } - - /** - * 新建一个ConcurrentHashMap + * 新建一个{@link SafeHashMap} * * @param size 初始容量,当传入的容量小于等于0时,容量为{@link MapKit#DEFAULT_INITIAL_CAPACITY} * @param key的类型 * @param value的类型 - * @return ConcurrentHashMap + * @return {@link SafeHashMap} */ - public static ConcurrentHashMap newConcurrentHashMap(int size) { + public static ConcurrentHashMap newSafeHashMap(final int size) { final int initCapacity = size <= 0 ? DEFAULT_INITIAL_CAPACITY : size; - return new ConcurrentHashMap<>(initCapacity); + return new SafeHashMap<>(initCapacity); } /** - * 传入一个Map将其转化为ConcurrentHashMap类型 + * 传入一个Map将其转化为{@link SafeHashMap}类型 * * @param map map * @param key的类型 * @param value的类型 - * @return ConcurrentHashMap + * @return {@link SafeHashMap} */ - public static ConcurrentHashMap newConcurrentHashMap(Map map) { + public static ConcurrentHashMap newSafeHashMap(final Map map) { if (isEmpty(map)) { return new ConcurrentHashMap<>(DEFAULT_INITIAL_CAPACITY); } - return new ConcurrentHashMap<>(map); + return new SafeHashMap<>(map); } /** @@ -213,20 +216,19 @@ public static ConcurrentHashMap newConcurrentHashMap(Map map) * @param mapType map类型 * @return {@link Map}实例 */ - public static Map createMap(Class mapType) { + public static Map createMap(final Class mapType) { if (null == mapType || mapType.isAssignableFrom(AbstractMap.class)) { return new HashMap<>(); } else { try { return (Map) ReflectKit.newInstance(mapType); - } catch (InternalException e) { + } catch (final InternalException e) { // 不支持的map类型,返回默认的HashMap return new HashMap<>(); } } } - /** * 将单一键值对转换为Map * @@ -236,7 +238,7 @@ public static Map createMap(Class mapType) { * @param value 值 * @return {@link HashMap} */ - public static HashMap of(K key, V value) { + public static HashMap of(final K key, final V value) { return of(key, value, false); } @@ -250,7 +252,7 @@ public static HashMap of(K key, V value) { * @param isOrder 是否有序 * @return {@link HashMap} */ - public static HashMap of(K key, V value, boolean isOrder) { + public static HashMap of(final K key, final V value, final boolean isOrder) { final HashMap map = newHashMap(isOrder); map.put(key, value); return map; @@ -265,10 +267,38 @@ public static HashMap of(K key, V value, boolean isOrder) { * @return Map * @see #entry(Object, Object) */ - public static Map of(Map.Entry... entries) { - final Map map = new HashMap<>(); - for (Map.Entry pair : entries) { - map.put(pair.getKey(), pair.getValue()); + public static Map of(final Entry... entries) { + return of((Iterator>) new ArrayIterator<>(entries)); + } + + /** + * 将Entry集合转换为HashMap + * + * @param 键类型 + * @param 值类型 + * @param entryIter entry集合 + * @return Map + */ + public static HashMap of(final Iterable> entryIter) { + return of(IterKit.get(entryIter)); + } + + /** + * 将Entry集合转换为HashMap + * + * @param 键类型 + * @param 值类型 + * @param entryIter entry集合 + * @return Map + */ + public static HashMap of(final Iterator> entryIter) { + final HashMap map = new HashMap<>(); + if (IterKit.isNotEmpty(entryIter)) { + Entry entry; + while (entryIter.hasNext()) { + entry = entryIter.next(); + map.put(entry.getKey(), entry.getValue()); + } } return map; } @@ -295,15 +325,15 @@ public static Map of(Map.Entry... entries) { * @param array 数组 元素类型为Map.Entry、数组、Iterable、Iterator * @return {@link HashMap} */ - public static HashMap of(Object[] array) { - if (null == array) { + public static HashMap of(final Object[] array) { + if (array == null) { return null; } final HashMap map = new HashMap<>((int) (array.length * 1.5)); for (int i = 0; i < array.length; i++) { final Object object = array[i]; if (object instanceof Map.Entry) { - Map.Entry entry = (Map.Entry) object; + final Entry entry = (Entry) object; map.put(entry.getKey(), entry.getValue()); } else if (object instanceof Object[]) { final Object[] entry = (Object[]) object; @@ -365,26 +395,16 @@ public static HashMap of(Object[] array) { * @param mapList Map列表 * @return Map */ - public static Map> toListMap(Iterable> mapList) { - final HashMap> resultMap = new HashMap<>(); + public static Map> toListMap(final Iterable> mapList) { + final Map> resultMap = new HashMap<>(); if (CollKit.isEmpty(mapList)) { return resultMap; } - Set> entrySet; - for (Map map : mapList) { - entrySet = map.entrySet(); - K key; - List valueList; - for (Entry entry : entrySet) { - key = entry.getKey(); - valueList = resultMap.get(key); - if (null == valueList) { - valueList = CollKit.newArrayList(entry.getValue()); - resultMap.put(key, valueList); - } else { - valueList.add(entry.getValue()); - } + for (final Map map : mapList) { + for (final Entry entry : map.entrySet()) { + resultMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()) + .add(entry.getValue()); } } @@ -420,36 +440,32 @@ public static Map> toListMap(Iterable> map * @param listMap 列表Map * @return Map列表 */ - public static List> toMapList(Map> listMap) { - final List> resultList = new ArrayList<>(); + public static List> toMapList(final Map> listMap) { if (isEmpty(listMap)) { - return resultList; + return new ArrayList<>(0); } - boolean isEnd;// 是否结束。标准是元素列表已耗尽 - int index = 0;// 值索引 - Map map; - do { - isEnd = true; - map = new HashMap<>(); - List vList; - int vListSize; - for (Entry> entry : listMap.entrySet()) { - vList = CollKit.newArrayList(entry.getValue()); - vListSize = vList.size(); - if (index < vListSize) { - map.put(entry.getKey(), vList.get(index)); - if (index != vListSize - 1) { - // 当值列表中还有更多值(非最后一个),继续循环 - isEnd = false; - } + final List> resultList = new ArrayList<>(); + for (final Entry> entry : listMap.entrySet()) { + final Iterator iterator = IterKit.get(entry.getValue()); + if (IterKit.isEmpty(iterator)) { + continue; + } + final K key = entry.getKey(); + // 对已经存在的map添加元素 + for (final Map map : resultList) { + // 还可以继续添加元素 + if (iterator.hasNext()) { + map.put(key, iterator.next()); + } else { + break; } } - if (false == map.isEmpty()) { - resultList.add(map); + // entry的value的个数 大于 当前列表的size, 直接新增map + while (iterator.hasNext()) { + resultList.add(of(key, iterator.next())); } - index++; - } while (false == isEnd); + } return resultList; } @@ -463,7 +479,7 @@ public static List> toMapList(Map> lis * @param map 原Map * @return 驼峰风格Map */ - public static Map toCamelCaseMap(Map map) { + public static Map toCamelCaseMap(final Map map) { return (map instanceof LinkedHashMap) ? new CamelCaseLinkedMap<>(map) : new CamelCaseMap<>(map); } @@ -473,8 +489,8 @@ public static Map toCamelCaseMap(Map map) { * @param map Map * @return 数组 */ - public static Object[][] toObjectArray(Map map) { - if (null == map) { + public static Object[][] toObjectArray(final Map map) { + if (map == null) { return null; } final Object[][] result = new Object[map.size()][2]; @@ -482,7 +498,7 @@ public static Object[][] toObjectArray(Map map) { return result; } int index = 0; - for (Entry entry : map.entrySet()) { + for (final Entry entry : map.entrySet()) { result[index][0] = entry.getKey(); result[index][1] = entry.getValue(); index++; @@ -573,21 +589,21 @@ public static String join(final Map map, final String separator, fi } /** - * 过滤 - * 过滤过程通过传入的Editor实现来返回需要的元素内容,这个Editor实现可以实现以下功能: + * 编辑Map + * 编辑过程通过传入的Editor实现来返回需要的元素内容,这个Editor实现可以实现以下功能: * *
-     * 1、过滤出需要的对象,如果返回null表示这个元素对象抛弃
-     * 2、修改元素对象,返回集合中为修改后的对象
+     * 1、过滤出需要的对象,如果返回{@code null}表示这个元素对象抛弃
+     * 2、修改元素对象,返回集合中为修改后的对象
      * 
* * @param Key类型 * @param Value类型 * @param map Map * @param editor 编辑器接口 - * @return 过滤后的Map + * @return 编辑后的Map */ - public static Map filter(Map map, Editor> editor) { + public static Map edit(final Map map, final UnaryOperator> editor) { if (null == map || null == editor) { return map; } @@ -601,8 +617,8 @@ public static Map filter(Map map, Editor> editor) } Entry modified; - for (Entry entry : map.entrySet()) { - modified = editor.edit(entry); + for (final Entry entry : map.entrySet()) { + modified = editor.apply(entry); if (null != modified) { map2.put(modified.getKey(), modified.getValue()); } @@ -619,29 +635,40 @@ public static Map filter(Map map, Editor> editor) * 2、修改元素对象,返回集合中为修改后的对象 * * - * @param Key类型 - * @param Value类型 - * @param map Map - * @param filter 编辑器接口 + * @param Key类型 + * @param Value类型 + * @param map Map + * @param predicate 过滤器接口,{@link Predicate#test(Object)}为{@code true}保留,{@code null}返回原Map * @return 过滤后的Map */ - public static Map filter(Map map, Predicate> filter) { - final Map map2 = ObjectKit.clone(map); - if (isEmpty(map2)) { - return map2; + public static Map filter(final Map map, final Predicate> predicate) { + if (null == map || null == predicate) { + return map; } + return edit(map, t -> predicate.test(t) ? t : null); + } - map2.clear(); - for (Entry entry : map.entrySet()) { - if (filter.test(entry)) { - map2.put(entry.getKey(), entry.getValue()); - } + + /** + * 通过biFunction自定义一个规则,此规则将原Map中的元素转换成新的元素,生成新的Map返回 + * 变更过程通过传入的 {@link BiFunction} 实现来返回一个值可以为不同类型的 {@link Map} + * + * @param {@code key}的类型 + * @param {@code value}的类型 + * @param 新的,修改后的{@code value}的类型 + * @param map 原有的map + * @param biFunction {@code lambda},参数包含{@code key},{@code value},返回值会作为新的{@code value} + * @return 值可以为不同类型的 {@link Map} + */ + public static Map map(final Map map, final BiFunction biFunction) { + if (null == map || null == biFunction) { + return newHashMap(); } - return map2; + return map.entrySet().stream().collect(Collectors.toMap(Entry::getKey, m -> biFunction.apply(m.getKey(), m.getValue()))); } /** - * 过滤Map保留指定键值对,如果键不存在跳过 + * 过滤Map保留指定键值对,如果键不存在跳过 * * @param Key类型 * @param Value类型 @@ -649,7 +676,7 @@ public static Map filter(Map map, Predicate> filt * @param keys 键列表 * @return Map 结果,结果的Map类型与原Map保持一致 */ - public static Map filter(Map map, K... keys) { + public static Map filter(final Map map, final K... keys) { if (null == map || null == keys) { return map; } @@ -662,7 +689,7 @@ public static Map filter(Map map, K... keys) { return map2; } - for (K key : keys) { + for (final K key : keys) { if (map.containsKey(key)) { map2.put(key, map.get(key)); } @@ -670,33 +697,17 @@ public static Map filter(Map map, K... keys) { return map2; } - /** - * 通过biFunction自定义一个规则,此规则将原Map中的元素转换成新的元素,生成新的Map返回 - * 变更过程通过传入的 {@link BiFunction} 实现来返回一个值可以为不同类型的 {@link Map} - * - * @param map 原有的map - * @param biFunction {@code lambda},参数包含{@code key},{@code value},返回值会作为新的{@code value} - * @param {@code key}的类型 - * @param {@code value}的类型 - * @param 新的,修改后的{@code value}的类型 - * @return 值可以为不同类型的 {@link Map} - */ - public static Map map(Map map, BiFunction biFunction) { - if (null == map || null == biFunction) { - return newHashMap(); - } - return map.entrySet().stream().collect(CollKit.toMap(Map.Entry::getKey, m -> biFunction.apply(m.getKey(), m.getValue()), (l, r) -> l)); - } - /** * Map的键和值互换 + * 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素 + * 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值 * * @param 键和值类型 * @param map Map对象,键值类型必须一致 * @return 互换后的Map */ - public static Map reverse(Map map) { - return filter(map, (Editor>) t -> new Entry() { + public static Map reverse(final Map map) { + return edit(map, t -> new Entry<>() { @Override public T getKey() { @@ -709,7 +720,7 @@ public T getValue() { } @Override - public T setValue(T value) { + public T setValue(final T value) { throw new UnsupportedOperationException("Unsupported setValue method !"); } }); @@ -725,43 +736,62 @@ public T setValue(T value) { * @param map Map对象,键值类型必须一致 * @return 互换后的Map */ - public static Map inverse(Map map) { + public static Map inverse(final Map map) { final Map result = createMap(map.getClass()); map.forEach((key, value) -> result.put(value, key)); return result; } /** - * 排序已有Map,Key有序的Map,使用默认Key排序方式(字母顺序) + * 排序已有Map,Key有序的Map,使用默认Key排序方式(字母顺序) * - * @param Key类型 - * @param Value类型 + * @param key的类型 + * @param value的类型 * @param map Map * @return TreeMap * @see #newTreeMap(Map, Comparator) */ - public static TreeMap sort(Map map) { + public static TreeMap sort(final Map map) { return sort(map, null); } /** - * 排序已有Map,Key有序的Map + * 按照值排序,可选是否倒序 * - * @param Key类型 - * @param Value类型 - * @param map Map + * @param 键类型 + * @param 值类型 + * @param map 需要对值排序的map + * @param isDesc 是否倒序 + * @return 排序后新的Map + */ + public static > Map sort(final Map map, final boolean isDesc) { + final Map result = new LinkedHashMap<>(); + Comparator> entryComparator = Entry.comparingByValue(); + if (isDesc) { + entryComparator = entryComparator.reversed(); + } + map.entrySet().stream().sorted(entryComparator).forEachOrdered(e -> result.put(e.getKey(), e.getValue())); + return result; + } + + /** + * 排序已有Map,Key有序的Map + * + * @param key的类型 + * @param value的类型 + * @param map Map,为null返回null * @param comparator Key比较器 - * @return TreeMap + * @return TreeMap,map为null返回null * @see #newTreeMap(Map, Comparator) */ - public static TreeMap sort(Map map, Comparator comparator) { + public static TreeMap sort(final Map map, final Comparator comparator) { if (null == map) { return null; } if (map instanceof TreeMap) { // 已经是可排序Map,此时只有比较器一致才返回原map - TreeMap result = (TreeMap) map; + final TreeMap result = (TreeMap) map; if (null == comparator || comparator.equals(result.comparator())) { return result; } @@ -771,22 +801,14 @@ public static TreeMap sort(Map map, Comparator com } /** - * 按照值排序,可选是否倒序 + * 创建代理Map + * {@link MapProxy}对Map做一次包装,提供各种getXXX方法 * - * @param map 需要对值排序的map - * @param 键类型 - * @param 值类型 - * @param isDesc 是否倒序 - * @return 排序后新的Map + * @param map 被代理的Map + * @return {@link MapProxy} */ - public static > Map sort(Map map, boolean isDesc) { - Map result = new LinkedHashMap<>(); - Comparator> entryComparator = Entry.comparingByValue(); - if (isDesc) { - entryComparator = entryComparator.reversed(); - } - map.entrySet().stream().sorted(entryComparator).forEachOrdered(e -> result.put(e.getKey(), e.getValue())); - return result; + public static MapProxy createProxy(final Map map) { + return MapProxy.of(map); } /** @@ -798,7 +820,7 @@ public static > Map sort(Map map, * @param map 被代理的Map * @return {@link MapWrapper} */ - public static MapWrapper wrap(Map map) { + public static MapWrapper wrap(final Map map) { return new MapWrapper<>(map); } @@ -810,21 +832,10 @@ public static MapWrapper wrap(Map map) { * @param 值类型 * @return 不修改Map */ - public static Map unmodifiable(Map map) { + public static Map view(final Map map) { return Collections.unmodifiableMap(map); } - /** - * 创建代理Map - * {@link MapProxy}对Map做一次包装,提供各种getXXX方法 - * - * @param map 被代理的Map - * @return {@link MapProxy} - */ - public static MapProxy createProxy(Map map) { - return MapProxy.create(map); - } - /** * 创建链接调用map * @@ -844,7 +855,7 @@ public static MapBuilder builder() { * @param map 实际使用的map * @return map创建类 */ - public static MapBuilder builder(Map map) { + public static MapBuilder builder(final Map map) { return new MapBuilder<>(map); } @@ -857,7 +868,7 @@ public static MapBuilder builder(Map map) { * @param v value * @return map创建类 */ - public static MapBuilder builder(K k, V v) { + public static MapBuilder builder(final K k, final V v) { return (builder(new HashMap())).put(k, v); } @@ -868,443 +879,623 @@ public static MapBuilder builder(K k, V v) { * @param Value类型 * @param map Map * @param keys 键列表 - * @return 新Map, 只包含指定的key + * @return 新Map,只包含指定的key */ - public static Map getAny(Map map, final K... keys) { - return filter(map, (Predicate>) entry -> ArrayKit.contains(keys, entry.getKey())); + public static Map getAny(final Map map, final K... keys) { + return filter(map, entry -> ArrayKit.contains(keys, entry.getKey())); } /** - * 获取Map指定key的值,并转换为字符串 + * 去掉Map中指定key的键值对,修改原Map * - * @param map Map - * @param key 键 - * @return 值 + * @param Key类型 + * @param Value类型 + * @param map Map + * @param keys 键列表 + * @return 修改后的key */ - public static String getString(Map map, Object key) { - return get(map, key, String.class); + public static Map removeAny(final Map map, final K... keys) { + for (final K key : keys) { + map.remove(key); + } + return map; } /** - * 获取Map指定key的值,并转换为Integer + * 重命名键 + * 实现方式为一处然后重新put,当旧的key不存在直接返回 + * 当新的key存在,抛出{@link IllegalArgumentException} 异常 * - * @param map Map - * @param key 键 - * @return 值 + * @param Key类型 + * @param Value类型 + * @param map Map + * @param oldKey 原键 + * @param newKey 新键 + * @return map */ - public static Integer getInt(Map map, Object key) { - return get(map, key, Integer.class); + public static Map renameKey(final Map map, final K oldKey, final K newKey) { + if (isNotEmpty(map) && map.containsKey(oldKey)) { + if (map.containsKey(newKey)) { + throw new IllegalArgumentException(StringKit.format("The key '{}' exist !", newKey)); + } + map.put(newKey, map.remove(oldKey)); + } + return map; } /** - * 获取Map指定key的值,并转换为Double + * 去除Map中值为{@code null}的键值对 + * 注意:此方法在传入的Map上直接修改。 * + * @param key的类型 + * @param value的类型 * @param map Map - * @param key 键 - * @return 值 + * @return map */ - public static Double getDouble(Map map, Object key) { - return get(map, key, Double.class); - } + public static Map removeNullValue(final Map map) { + if (isEmpty(map)) { + return map; + } - /** - * 获取Map指定key的值,并转换为Double - * - * @param map Map - * @param key 键 - * @param defaultValue 默认值 - * @return 值 - */ - public static Double getDouble(Map map, Object key, Double defaultValue) { - return get(map, key, Double.class, defaultValue); - } + final Iterator> iter = map.entrySet().iterator(); + Entry entry; + while (iter.hasNext()) { + entry = iter.next(); + if (null == entry.getValue()) { + iter.remove(); + } + } - /** - * 获取Map指定key的值,并转换为Float - * - * @param map Map - * @param key 键 - * @return 值 - */ - public static Float getFloat(Map map, Object key) { - return get(map, key, Float.class); + return map; } /** - * 获取Map指定key的值,并转换为Float + * 返回一个空Map * - * @param map Map - * @param key 键 - * @param defaultValue 默认值 - * @return 值 + * @param 键类型 + * @param 值类型 + * @return 空Map + * @see Collections#emptyMap() */ - public static Float getFloat(Map map, Object key, Float defaultValue) { - return get(map, key, Float.class, defaultValue); + public static Map empty() { + return Collections.emptyMap(); } /** - * 获取Map指定key的值,并转换为Short + * 返回一个初始大小为0的HashMap(初始为0,可加入元素) * - * @param map Map - * @param key 键 - * @return 值 + * @param 键类型 + * @param 值类型 + * @return 初始大小为0的HashMap */ - public static Short getShort(Map map, Object key) { - return get(map, key, Short.class); + public static Map zero() { + return new HashMap<>(0, 1); } /** - * 获取Map指定key的值,并转换为Short + * 根据传入的Map类型不同,返回对应类型的空Map * - * @param map Map - * @param key 键 - * @param defaultValue 默认值 - * @return 值 - */ - public static Short getShort(Map map, Object key, Short defaultValue) { - return get(map, key, Short.class, defaultValue); + *
+     *     1. NavigableMap
+     *     2. SortedMap
+     *     3. Map
+     * 
+ * + * @param 键类型 + * @param 值类型 + * @param Map类型 + * @param mapClass Map类型,null返回默认的Map + * @return 空Map + */ + public static > T empty(final 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(StringKit.format("[{}] is not support to get empty!", mapClass)); + } + + /** + * 清除一个或多个Map集合内的元素,每个Map调用clear()方法 + * + * @param maps 一个或多个Map + */ + public static void clear(final Map... maps) { + for (final Map map : maps) { + if (isNotEmpty(map)) { + map.clear(); + } + } + } + + /** + * 从Map中获取指定键列表对应的值列表 + * 如果key在map中不存在或key对应值为null,则返回值列表对应位置的值也为null + * + * @param 键类型 + * @param 值类型 + * @param map {@link Map} + * @param keys 键列表 + * @return 值列表 + */ + public static ArrayList valuesOfKeys(final Map map, final K... keys) { + return valuesOfKeys(map, (Iterator) new ArrayIterator<>(keys)); + } + + /** + * 从Map中获取指定键列表对应的值列表 + * 如果key在map中不存在或key对应值为null,则返回值列表对应位置的值也为null + * + * @param 键类型 + * @param 值类型 + * @param map {@link Map} + * @param keys 键列表 + * @return 值列表 + */ + public static ArrayList valuesOfKeys(final Map map, final Iterable keys) { + return valuesOfKeys(map, keys.iterator()); + } + + /** + * 从Map中获取指定键列表对应的值列表 + * 如果key在map中不存在或key对应值为null,则返回值列表对应位置的值也为null + * + * @param 键类型 + * @param 值类型 + * @param map {@link Map} + * @param keys 键列表 + * @return 值列表 + */ + public static ArrayList valuesOfKeys(final Map map, final Iterator keys) { + final ArrayList values = new ArrayList<>(); + while (keys.hasNext()) { + values.add(map.get(keys.next())); + } + return values; + } + + /** + * 将键和值转换为{@link AbstractMap.SimpleImmutableEntry} + * 返回的Entry不可变 + * + * @param key 键 + * @param value 值 + * @param 键类型 + * @param 值类型 + * @return {@link AbstractMap.SimpleImmutableEntry} + */ + public static Entry entry(final K key, final V value) { + return entry(key, value, true); + } + + /** + * 将键和值转换为{@link AbstractMap.SimpleEntry} 或者 {@link AbstractMap.SimpleImmutableEntry} + * + * @param key 键 + * @param value 值 + * @param 键类型 + * @param 值类型 + * @param isImmutable 是否不可变Entry + * @return {@link AbstractMap.SimpleEntry} 或者 {@link AbstractMap.SimpleImmutableEntry} + */ + public static Entry entry(final K key, final V value, final boolean isImmutable) { + return isImmutable ? + new AbstractMap.SimpleImmutableEntry<>(key, value) : + new AbstractMap.SimpleEntry<>(key, value); + } + + /** + * 将列表按照给定的键生成器规则和值生成器规则,加入到给定的Map中 + * + * @param resultMap 结果Map,通过传入map对象决定结果的Map类型,如果为{@code null},默认使用HashMap + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param 键类型 + * @param 值类型 + * @return HashMap + */ + public static Map putAll(final Map resultMap, final Iterable iterable, final Function keyMapper) { + return putAll(resultMap, iterable, keyMapper, Function.identity()); + } + + /** + * 将列表按照给定的键生成器规则和值生成器规则,加入到给定的Map中 + * + * @param resultMap 结果Map,通过传入map对象决定结果的Map类型 + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + */ + public static Map putAll(final Map resultMap, final Iterable iterable, final Function keyMapper, final Function valueMapper) { + return putAll(resultMap, IterKit.get(iterable), keyMapper, valueMapper); + } + + /** + * 将列表按照给定的键生成器规则和值生成器规则,加入到给定的Map中 + * + * @param resultMap 结果Map,通过传入map对象决定结果的Map类型,如果为{@code null},默认使用HashMap + * @param iterator 值列表 + * @param keyMapper Map的键映射 + * @param 键类型 + * @param 值类型 + * @return HashMap + */ + public static Map putAll(final Map resultMap, final Iterator iterator, final Function keyMapper) { + return putAll(resultMap, iterator, keyMapper, Function.identity()); + } + + /** + * 将列表按照给定的键生成器规则和值生成器规则,加入到给定的Map中 + * + * @param resultMap 结果Map,通过传入map对象决定结果的Map类型,如果为{@code null},默认使用HashMap + * @param iterator 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + */ + public static Map putAll(Map resultMap, final Iterator iterator, final Function keyMapper, final Function valueMapper) { + if (null == resultMap) { + resultMap = newHashMap(); + } + if (ObjectKit.isNull(iterator)) { + return resultMap; + } + + T value; + while (iterator.hasNext()) { + value = iterator.next(); + resultMap.put(keyMapper.apply(value), valueMapper.apply(value)); + } + return resultMap; } + /** - * 获取Map指定key的值,并转换为Bool + * 获取Map指定key的值,并转换为字符串 * * @param map Map * @param key 键 * @return 值 */ - public static Boolean getBool(Map map, Object key) { - return get(map, key, Boolean.class); + public static String getString(final Map map, final Object key) { + return get(map, key, String.class); } /** - * 获取Map指定key的值,并转换为Bool + * 获取Map指定key的值,并转换为字符串 * * @param map Map * @param key 键 * @param defaultValue 默认值 * @return 值 */ - public static Boolean getBool(Map map, Object key, Boolean defaultValue) { - return get(map, key, Boolean.class, defaultValue); + public static String getString(final Map map, final Object key, final String defaultValue) { + return get(map, key, String.class, defaultValue); } /** - * 获取Map指定key的值,并转换为Character + * 获取Map指定key的值,并转换为Integer * * @param map Map * @param key 键 * @return 值 */ - public static Character getChar(Map map, Object key) { - return get(map, key, Character.class); + public static Integer getInt(final Map map, final Object key) { + return get(map, key, Integer.class); } /** - * 获取Map指定key的值,并转换为Character + * 获取Map指定key的值,并转换为Integer * * @param map Map * @param key 键 * @param defaultValue 默认值 * @return 值 */ - public static Character getChar(Map map, Object key, Character defaultValue) { - return get(map, key, Character.class, defaultValue); + public static Integer getInt(final Map map, final Object key, final Integer defaultValue) { + return get(map, key, Integer.class, defaultValue); } /** - * 获取Map指定key的值,并转换为Long + * 获取Map指定key的值,并转换为Double * * @param map Map * @param key 键 * @return 值 */ - public static Long getLong(Map map, Object key) { - return get(map, key, Long.class); + public static Double getDouble(final Map map, final Object key) { + return get(map, key, Double.class); } /** - * 获取Map指定key的值,并转换为Long + * 获取Map指定key的值,并转换为Double * * @param map Map * @param key 键 * @param defaultValue 默认值 * @return 值 */ - public static Long getLong(Map map, Object key, Long defaultValue) { - return get(map, key, Long.class, defaultValue); + public static Double getDouble(final Map map, final Object key, final Double defaultValue) { + return get(map, key, Double.class, defaultValue); } /** - * 获取Map指定key的值,并转换为{@link Date} + * 获取Map指定key的值,并转换为Float * * @param map Map * @param key 键 * @return 值 */ - public static Date getDate(Map map, Object key) { - return get(map, key, Date.class); + public static Float getFloat(final Map map, final Object key) { + return get(map, key, Float.class); } /** - * 获取Map指定key的值,并转换为{@link Date} + * 获取Map指定key的值,并转换为Float * * @param map Map * @param key 键 * @param defaultValue 默认值 * @return 值 */ - public static Date getDate(Map map, Object key, Date defaultValue) { - return get(map, key, Date.class, defaultValue); + public static Float getFloat(final Map map, final Object key, final Float defaultValue) { + return get(map, key, Float.class, defaultValue); } /** - * 获取Map指定key的值,并转换为指定类型 + * 获取Map指定key的值,并转换为Short * - * @param 目标值类型 - * @param map Map - * @param key 键 - * @param type 值类型 + * @param map Map + * @param key 键 * @return 值 */ - public static T get(Map map, Object key, Class type) { - return null == map ? null : Convert.convert(type, map.get(key)); + public static Short getShort(final Map map, final Object key) { + return get(map, key, Short.class); } /** - * 获取Map指定key的值,并转换为指定类型 + * 获取Map指定key的值,并转换为Short * - * @param 目标值类型 * @param map Map * @param key 键 - * @param type 值类型 * @param defaultValue 默认值 * @return 值 */ - public static T get(Map map, Object key, Types type, T defaultValue) { - return null == map ? defaultValue : Convert.convert(type, map.get(key), defaultValue); + public static Short getShort(final Map map, final Object key, final Short defaultValue) { + return get(map, key, Short.class, defaultValue); } /** - * 获取Map指定key的值,并转换为指定类型 + * 获取Map指定key的值,并转换为Bool * - * @param 目标值类型 - * @param map Map - * @param key 键 - * @param type 值类型 - * @param defaultValue 默认值 + * @param map Map + * @param key 键 * @return 值 */ - public static T get(Map map, Object key, Class type, T defaultValue) { - return null == map ? defaultValue : Convert.convert(type, map.get(key), defaultValue); + public static Boolean getBool(final Map map, final Object key) { + return get(map, key, Boolean.class); } /** - * 获取Map指定key的值,并转换为指定类型,此方法在转换失败后不抛异常,返回null。 + * 获取Map指定key的值,并转换为Bool * - * @param 目标值类型 * @param map Map * @param key 键 - * @param type 值类型 * @param defaultValue 默认值 * @return 值 */ - public static T getQuietly(Map map, Object key, Class type, T defaultValue) { - return null == map ? defaultValue : Convert.convertQuietly(type, map.get(key), defaultValue); + public static Boolean getBool(final Map map, final Object key, final Boolean defaultValue) { + return get(map, key, Boolean.class, defaultValue); } /** - * 获取Map指定key的值,并转换为指定类型,转换失败后返回null,不抛异常 + * 获取Map指定key的值,并转换为Character + * + * @param map Map + * @param key 键 + * @return 值 + */ + public static Character getChar(final Map map, final Object key) { + return get(map, key, Character.class); + } + + /** + * 获取Map指定key的值,并转换为Character * - * @param 目标值类型 * @param map Map * @param key 键 - * @param type 值类型 * @param defaultValue 默认值 * @return 值 */ - public static T getQuietly(Map map, Object key, Types type, T defaultValue) { - return null == map ? defaultValue : Convert.convertQuietly(type, map.get(key), defaultValue); + public static Character getChar(final Map map, final Object key, final Character defaultValue) { + return get(map, key, Character.class, defaultValue); } /** - * 重命名键 - * 实现方式为一处然后重新put,当旧的key不存在直接返回 - * 当新的key存在,抛出{@link IllegalArgumentException} 异常 + * 获取Map指定key的值,并转换为Long * - * @param Key类型 - * @param Value类型 - * @param map Map - * @param oldKey 原键 - * @param newKey 新键 - * @return map + * @param map Map + * @param key 键 + * @return 值 */ - public static Map renameKey(Map map, K oldKey, K newKey) { - if (isNotEmpty(map) && map.containsKey(oldKey)) { - if (map.containsKey(newKey)) { - throw new IllegalArgumentException(StringKit.format("The key '{}' exist !", newKey)); - } - map.put(newKey, map.remove(oldKey)); - } - return map; + public static Long getLong(final Map map, final Object key) { + return get(map, key, Long.class); } /** - * 去除Map中值为{@code null}的键值对 - * 注意:此方法在传入的Map上直接修改。 + * 获取Map指定key的值,并转换为Long * - * @param key的类型 - * @param value的类型 - * @param map Map - * @return map + * @param map Map + * @param key 键 + * @param defaultValue 默认值 + * @return 值 */ - public static Map removeNullValue(Map map) { - if (isEmpty(map)) { - return map; - } - - final Iterator> iter = map.entrySet().iterator(); - Entry entry; - while (iter.hasNext()) { - entry = iter.next(); - if (null == entry.getValue()) { - iter.remove(); - } - } - - return map; + public static Long getLong(final Map map, final Object key, final Long defaultValue) { + return get(map, key, Long.class, defaultValue); } /** - * 去掉Map中指定key的键值对,修改原Map + * 获取Map指定key的值,并转换为{@link Date} * - * @param Key类型 - * @param Value类型 - * @param map Map - * @param keys 键列表 - * @return 修改后的key + * @param map Map + * @param key 键 + * @return 值 */ - public static Map removeAny(Map map, final K... keys) { - for (K key : keys) { - map.remove(key); - } - return map; + public static Date getDate(final Map map, final Object key) { + return get(map, key, Date.class); } /** - * 返回一个空Map + * 获取Map指定key的值,并转换为{@link Date} * - * @param 键类型 - * @param 值类型 - * @return 空Map - * @see Collections#emptyMap() + * @param map Map + * @param key 键 + * @param defaultValue 默认值 + * @return 值 */ - public static Map empty() { - return Collections.emptyMap(); + public static Date getDate(final Map map, final Object key, final Date defaultValue) { + return get(map, key, Date.class, defaultValue); } /** - * 返回一个初始大小为0的HashMap(初始为0,可加入元素) + * 获取Map指定key的值,并转换为指定类型 * - * @param 键类型 - * @param 值类型 - * @return 初始大小为0的HashMap + * @param 目标值类型 + * @param map Map + * @param key 键 + * @param type 值类型 + * @return 值 */ - public static Map zero() { - return new HashMap<>(0, 1); + public static T get(final Map map, final Object key, final Class type) { + return get(map, key, type, null); } /** - * 根据传入的Map类型不同,返回对应类型的空Map - * - *
-     *     1. NavigableMap
-     *     2. SortedMap
-     *     3. Map
-     * 
+ * 获取Map指定key的值,并转换为指定类型 * - * @param 键类型 - * @param 值类型 - * @param Map类型 - * @param mapClass Map类型,null返回默认的Map - * @return 空Map + * @param 目标值类型 + * @param map Map + * @param key 键 + * @param type 值类型 + * @param defaultValue 默认值 + * @return 值 */ - 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(); - } + public static T get(final Map map, final Object key, final Class type, final T defaultValue) { + return null == map ? defaultValue : Convert.convert(type, map.get(key), defaultValue); + } - // 不支持空集合的集合类型 - throw new IllegalArgumentException(StringKit.format("[{}] is not support to get empty!", mapClass)); + /** + * 获取Map指定key的值,并转换为指定类型,此方法在转换失败后不抛异常,返回null。 + * + * @param 目标值类型 + * @param map Map + * @param key 键 + * @param type 值类型 + * @param defaultValue 默认值 + * @return 值 + */ + public static T getQuietly(final Map map, final Object key, final Class type, final T defaultValue) { + return null == map ? defaultValue : Convert.convertQuietly(type, map.get(key), defaultValue); } /** - * 清除一个或多个Map集合内的元素,每个Map调用clear()方法 + * 获取Map指定key的值,并转换为指定类型 * - * @param maps 一个或多个Map + * @param 目标值类型 + * @param map Map + * @param key 键 + * @param type 值类型 + * @return 值 */ - public static void clear(Map... maps) { - for (Map map : maps) { - if (isNotEmpty(map)) { - map.clear(); - } - } + public static T get(final Map map, final Object key, final Types type) { + return get(map, key, type, null); } /** - * 将键和值转换为{@link AbstractMap.SimpleImmutableEntry} - * 返回的Entry不可变 + * 获取Map指定key的值,并转换为指定类型 * - * @param key 键 - * @param value 值 - * @param 键类型 - * @param 值类型 - * @return {@link AbstractMap.SimpleImmutableEntry} + * @param 目标值类型 + * @param map Map + * @param key 键 + * @param type 值类型 + * @param defaultValue 默认值 + * @return 值 */ - public static Map.Entry entry(K key, V value) { - return entry(key, value, true); + public static T get(final Map map, final Object key, final Types type, final T defaultValue) { + return null == map ? defaultValue : Convert.convert(type, map.get(key), defaultValue); } /** - * 将键和值转换为{@link AbstractMap.SimpleEntry} 或者 {@link AbstractMap.SimpleImmutableEntry} + * 获取Map指定key的值,并转换为指定类型,转换失败后返回null,不抛异常 * - * @param key 键 - * @param value 值 - * @param 键类型 - * @param 值类型 - * @param isImmutable 是否不可变Entry - * @return {@link AbstractMap.SimpleEntry} 或者 {@link AbstractMap.SimpleImmutableEntry} + * @param 目标值类型 + * @param map Map + * @param key 键 + * @param type 值类型 + * @param defaultValue 默认值 + * @return 值 */ - public static Map.Entry entry(K key, V value, boolean isImmutable) { - return isImmutable ? - new AbstractMap.SimpleImmutableEntry<>(key, value) : - new AbstractMap.SimpleEntry<>(key, value); + public static T getQuietly(final Map map, final Object key, final Types type, final T defaultValue) { + return null == map ? defaultValue : Convert.convertQuietly(type, map.get(key), defaultValue); } /** - * 根据给定的entry列表,根据entry的key进行分组 + * 根据给定的entry列表,根据entry的key进行分组; * * @param 键类型 * @param 值类型 * @param entries entry列表 * @return this map */ - public static Map> grouping(Iterable> entries) { - final Map> map = new HashMap<>(); + public static Map> grouping(final Iterable> entries) { if (CollKit.isEmpty(entries)) { - return map; + return zero(); } - for (final Map.Entry pair : entries) { + + final Map> map = new HashMap<>(); + for (final Entry pair : entries) { final List values = map.computeIfAbsent(pair.getKey(), k -> new ArrayList<>()); values.add(pair.getValue()); } return map; } + /** + * 如果 key 对应的 value 不存在,则使用获取 mappingFunction 重新计算后的值,并保存为该 key 的 value,否则返回 value。 + * 方法来自Dubbo,解决使用ConcurrentHashMap.computeIfAbsent导致的死循环问题。(issues#2349) + * A temporary workaround for Java 8 specific performance issue JDK-8161372 . + * This class should be removed once we drop Java 8 support. + * + * @param 键类型 + * @param 值类型 + * @param map Map,一般用于线程安全的Map + * @param key 键 + * @param mappingFunction 值计算函数 + * @return 值 + * @see https://bugs.openjdk.java.net/browse/JDK-8161372 + */ + public static V computeIfAbsent(final Map map, final K key, final Function mappingFunction) { + V value = map.get(key); + if (null == value) { + map.putIfAbsent(key, mappingFunction.apply(key)); + value = map.get(key); + } + return value; + } + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/MathKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/MathKit.java index fedc77d180..90c0dcf026 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/MathKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/MathKit.java @@ -762,7 +762,7 @@ public static BigDecimal round(BigDecimal number, int scale) { * 例如保留2位小数:123.456789 = 123.46 * * @param number 数字值的字符串表现形式 - * @param scale 保留小数位数 + * @param scale 保留小数位数 * @return 新值 */ public static String roundString(String number, int scale) { @@ -799,7 +799,7 @@ public static String roundString(double v, int scale, RoundingMode roundingMode) * 保留固定位数小数 * 例如保留四位小数:123.456789 = 123.4567 * - * @param number 数字值的字符串表现形式 + * @param number 数字值的字符串表现形式 * @param scale 保留小数位数,如果传入小于0,则默认0 * @param roundingMode 保留小数的模式 {@link RoundingMode},如果传入null则默认四舍五入 * @return 新值 @@ -839,7 +839,7 @@ public static BigDecimal round(BigDecimal number, int scale, RoundingMode roundi * 保留固定位数小数 * 例如保留四位小数:123.456789 = 123.4567 * - * @param number 数字值的字符串表现形式 + * @param number 数字值的字符串表现形式 * @param scale 保留小数位数 * @param roundingMode 保留小数的模式 {@link RoundingMode} * @return 新值 diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ObjectKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ObjectKit.java index f50d288fe7..65268c9106 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ObjectKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ObjectKit.java @@ -445,7 +445,7 @@ public static boolean contains(Object object, Object element) { * 如果实现Serializable接口,执行深度克隆 * 否则返回null * - * @param 对象类型 + * @param 对象类型 * @param object 被克隆对象 * @return 克隆后的对象 */ @@ -624,7 +624,7 @@ public static T initObject(Class clazz, Map attrMap) { /** * 给对象的属性赋值 * - * @param object 对象 + * @param object 对象 * @param attrName 对象的属性名 * @param value 对象的属性值 */ @@ -650,7 +650,7 @@ public static void setAttribute(Object object, String attrName, Object value) { /** * 从对象中取值 * - * @param object 对象 + * @param object 对象 * @param attrName 要取值的属性名 * @return 值 */ @@ -1326,7 +1326,7 @@ public static Object[] toObjectArray(Object source) { * 对象序列化 * 对象必须实现Serializable接口 * - * @param 对象类型 + * @param 对象类型 * @param object 要被序列化的对象 * @return 序列化后的字节码 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/PhoneKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/PhoneKit.java index e1e8d7d20f..553b15853f 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/PhoneKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/PhoneKit.java @@ -36,155 +36,155 @@ */ public class PhoneKit { - /** - * 验证是否为手机号码(中国) - * - * @param value 值 - * @return 是否为手机号码(中国) - */ - public static boolean isMobile(CharSequence value) { - return Validator.isMatchRegex(RegEx.MOBILE, value); - } - - /** - * 验证是否为手机号码(香港) - * - * @param value 手机号码 - * @return 是否为香港手机号码 - */ - public static boolean isMobileHk(CharSequence value) { - return Validator.isMatchRegex(RegEx.MOBILE_HK, value); - } - - /** - * 验证是否为手机号码(台湾) - * - * @param value 手机号码 - * @return 是否为台湾手机号码 - */ - public static boolean isMobileTw(CharSequence value) { - return Validator.isMatchRegex(RegEx.MOBILE_TW, value); - } - - /** - * 验证是否为手机号码(澳门) - * - * @param value 手机号码 - * @return 是否为澳门手机号码 - */ - public static boolean isMobileMo(CharSequence value) { - return Validator.isMatchRegex(RegEx.MOBILE_MO, value); - } - - /** - * 验证是否为座机号码(中国) - * - * @param value 值 - * @return 是否为座机号码(中国) - */ - public static boolean isPhone(CharSequence value) { - return Validator.isMatchRegex(RegEx.PHONE, value); - } - - /** - * 验证是否为座机号码(中国)+ 400 + 800 - * - * @param value 值 - * @return 是否为座机号码(中国) - */ - public static boolean isPhone400800(CharSequence value) { - return Validator.isMatchRegex(RegEx.PHONE_400_800, value); - } - - /** - * 验证是否为座机号码+手机号码(CharUtil中国)+ 400 + 800电话 + 手机号号码(香港) - * - * @param value 值 - * @return 是否为座机号码+手机号码(中国)+手机号码(香港)+手机号码(台湾)+手机号码(澳门) - */ - public static boolean isPhoneAll(CharSequence value) { - return isMobile(value) || isPhone400800(value) || isMobileHk(value) || isMobileTw(value) || isMobileMo(value); - } - - /** - * 隐藏手机号前7位 替换字符为"*" - * 栗子 - * - * @param phone 手机号码 - * @return 替换后的字符串 - */ - public static CharSequence hideBefore(CharSequence phone) { - return StringKit.hide(phone, 0, 7); - } - - /** - * 隐藏手机号中间4位 替换字符为"*" - * - * @param phone 手机号码 - * @return 替换后的字符串 - */ - public static CharSequence hideBetween(CharSequence phone) { - return StringKit.hide(phone, 3, 7); - } - - /** - * 隐藏手机号最后4位 替换字符为"*" - * - * @param phone 手机号码 - * @return 替换后的字符串 - */ - public static CharSequence hideAfter(CharSequence phone) { - return StringKit.hide(phone, 7, 11); - } - - /** - * 获取手机号前3位 - * - * @param phone 手机号码 - * @return 手机号前3位 - */ - public static CharSequence subBefore(CharSequence phone) { - return StringKit.sub(phone, 0, 3); - } - - /** - * 获取手机号中间4位 - * - * @param phone 手机号码 - * @return 手机号中间4位 - */ - public static CharSequence subBetween(CharSequence phone) { - return StringKit.sub(phone, 3, 7); - } - - /** - * 获取手机号后4位 - * - * @param phone 手机号码 - * @return 手机号后4位 - */ - public static CharSequence subAfter(CharSequence phone) { - return StringKit.sub(phone, 7, 11); - } - - /** - * 获取固话号码中的区号 - * - * @param value 完整的固话号码 - * @return 固话号码的区号部分 - */ - public static CharSequence subPhoneBefore(CharSequence value) { - return PatternKit.getGroup1(RegEx.PHONE, value); - } - - /** - * 获取固话号码中的号码 - * - * @param value 完整的固话号码 - * @return 固话号码的号码部分 - */ - public static CharSequence subPhoneAfter(CharSequence value) { - return PatternKit.get(RegEx.PHONE, value, 2); - } + /** + * 验证是否为手机号码(中国) + * + * @param value 值 + * @return 是否为手机号码(中国) + */ + public static boolean isMobile(CharSequence value) { + return Validator.isMatchRegex(RegEx.MOBILE, value); + } + + /** + * 验证是否为手机号码(香港) + * + * @param value 手机号码 + * @return 是否为香港手机号码 + */ + public static boolean isMobileHk(CharSequence value) { + return Validator.isMatchRegex(RegEx.MOBILE_HK, value); + } + + /** + * 验证是否为手机号码(台湾) + * + * @param value 手机号码 + * @return 是否为台湾手机号码 + */ + public static boolean isMobileTw(CharSequence value) { + return Validator.isMatchRegex(RegEx.MOBILE_TW, value); + } + + /** + * 验证是否为手机号码(澳门) + * + * @param value 手机号码 + * @return 是否为澳门手机号码 + */ + public static boolean isMobileMo(CharSequence value) { + return Validator.isMatchRegex(RegEx.MOBILE_MO, value); + } + + /** + * 验证是否为座机号码(中国) + * + * @param value 值 + * @return 是否为座机号码(中国) + */ + public static boolean isPhone(CharSequence value) { + return Validator.isMatchRegex(RegEx.PHONE, value); + } + + /** + * 验证是否为座机号码(中国)+ 400 + 800 + * + * @param value 值 + * @return 是否为座机号码(中国) + */ + public static boolean isPhone400800(CharSequence value) { + return Validator.isMatchRegex(RegEx.PHONE_400_800, value); + } + + /** + * 验证是否为座机号码+手机号码(CharUtil中国)+ 400 + 800电话 + 手机号号码(香港) + * + * @param value 值 + * @return 是否为座机号码+手机号码(中国)+手机号码(香港)+手机号码(台湾)+手机号码(澳门) + */ + public static boolean isPhoneAll(CharSequence value) { + return isMobile(value) || isPhone400800(value) || isMobileHk(value) || isMobileTw(value) || isMobileMo(value); + } + + /** + * 隐藏手机号前7位 替换字符为"*" + * 栗子 + * + * @param phone 手机号码 + * @return 替换后的字符串 + */ + public static CharSequence hideBefore(CharSequence phone) { + return StringKit.hide(phone, 0, 7); + } + + /** + * 隐藏手机号中间4位 替换字符为"*" + * + * @param phone 手机号码 + * @return 替换后的字符串 + */ + public static CharSequence hideBetween(CharSequence phone) { + return StringKit.hide(phone, 3, 7); + } + + /** + * 隐藏手机号最后4位 替换字符为"*" + * + * @param phone 手机号码 + * @return 替换后的字符串 + */ + public static CharSequence hideAfter(CharSequence phone) { + return StringKit.hide(phone, 7, 11); + } + + /** + * 获取手机号前3位 + * + * @param phone 手机号码 + * @return 手机号前3位 + */ + public static CharSequence subBefore(CharSequence phone) { + return StringKit.sub(phone, 0, 3); + } + + /** + * 获取手机号中间4位 + * + * @param phone 手机号码 + * @return 手机号中间4位 + */ + public static CharSequence subBetween(CharSequence phone) { + return StringKit.sub(phone, 3, 7); + } + + /** + * 获取手机号后4位 + * + * @param phone 手机号码 + * @return 手机号后4位 + */ + public static CharSequence subAfter(CharSequence phone) { + return StringKit.sub(phone, 7, 11); + } + + /** + * 获取固话号码中的区号 + * + * @param value 完整的固话号码 + * @return 固话号码的区号部分 + */ + public static CharSequence subPhoneBefore(CharSequence value) { + return PatternKit.getGroup1(RegEx.PHONE, value); + } + + /** + * 获取固话号码中的号码 + * + * @param value 完整的固话号码 + * @return 固话号码的号码部分 + */ + public static CharSequence subPhoneAfter(CharSequence value) { + return PatternKit.get(RegEx.PHONE, value, 2); + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/StringKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/StringKit.java index b1daf8780c..a4f581a98a 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/StringKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/StringKit.java @@ -182,7 +182,7 @@ public static String toString(final String text, final String defaultStr) { * 将对象转为字符串 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 * - * @param object 对象 + * @param object 对象 * @param charset 字符集 * @return 字符串 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/UriKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/UriKit.java index 6137d47df0..891315336a 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/UriKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/UriKit.java @@ -2243,7 +2243,7 @@ public Query parse(String queryStr, java.nio.charset.Charset charset, boolean au * @return 查询的Map,只读 */ public Map getQueryMap() { - return MapKit.unmodifiable(this.query); + return Collections.unmodifiableMap(this.query); } /** diff --git a/bus-crypto/src/main/java/org/aoju/bus/crypto/asymmetric/Decryptor.java b/bus-crypto/src/main/java/org/aoju/bus/crypto/asymmetric/Decryptor.java index a553bcda7f..5584802434 100644 --- a/bus-crypto/src/main/java/org/aoju/bus/crypto/asymmetric/Decryptor.java +++ b/bus-crypto/src/main/java/org/aoju/bus/crypto/asymmetric/Decryptor.java @@ -25,9 +25,7 @@ ********************************************************************************/ package org.aoju.bus.crypto.asymmetric; -import org.aoju.bus.core.codec.BCD; import org.aoju.bus.core.exception.CryptoException; -import org.aoju.bus.core.lang.Assert; import org.aoju.bus.core.toolkit.IoKit; import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.crypto.Builder; @@ -41,7 +39,6 @@ *
  • 从bytes解密
  • *
  • 从Hex(16进制)解密
  • *
  • 从Base64解密
  • - *
  • 从BCD解密
  • * * * @author Kimi Liu @@ -104,51 +101,4 @@ default String decryptString(String data, KeyType keyType) { return decryptString(data, keyType, org.aoju.bus.core.lang.Charset.UTF_8); } - /** - * 解密BCD - * - * @param data 数据 - * @param keyType 密钥类型 - * @return 解密后的密文 - */ - default byte[] decryptFromBcd(String data, KeyType keyType) { - return decryptFromBcd(data, keyType, org.aoju.bus.core.lang.Charset.UTF_8); - } - - /** - * 分组解密 - * - * @param data 数据 - * @param keyType 密钥类型 - * @param charset 加密前编码 - * @return 解密后的密文 - */ - default byte[] decryptFromBcd(String data, KeyType keyType, Charset charset) { - Assert.notNull(data, "Bcd string must be not null!"); - final byte[] dataBytes = BCD.ascToBcd(StringKit.bytes(data, charset)); - return decrypt(dataBytes, keyType); - } - - /** - * 解密为字符串,密文需为BCD格式 - * - * @param data 数据,BCD格式 - * @param keyType 密钥类型 - * @param charset 加密前编码 - * @return 解密后的密文 - */ - default String decryptStrFromBcd(String data, KeyType keyType, Charset charset) { - return StringKit.toString(decryptFromBcd(data, keyType, charset), charset); - } - - /** - * 解密为字符串,密文需为BCD格式,编码为UTF-8格式 - * - * @param data 数据,BCD格式 - * @param keyType 密钥类型 - * @return 解密后的密文 - */ - default String decryptStrFromBcd(String data, KeyType keyType) { - return decryptStrFromBcd(data, keyType, org.aoju.bus.core.lang.Charset.UTF_8); - } } diff --git a/bus-crypto/src/main/java/org/aoju/bus/crypto/asymmetric/Encryptor.java b/bus-crypto/src/main/java/org/aoju/bus/crypto/asymmetric/Encryptor.java index e493519602..41db892ed1 100644 --- a/bus-crypto/src/main/java/org/aoju/bus/crypto/asymmetric/Encryptor.java +++ b/bus-crypto/src/main/java/org/aoju/bus/crypto/asymmetric/Encryptor.java @@ -25,7 +25,6 @@ ********************************************************************************/ package org.aoju.bus.crypto.asymmetric; -import org.aoju.bus.core.codec.BCD; import org.aoju.bus.core.codec.Base64; import org.aoju.bus.core.exception.CryptoException; import org.aoju.bus.core.toolkit.HexKit; @@ -41,7 +40,6 @@ *
  • 加密为bytes
  • *
  • 加密为Hex(16进制)
  • *
  • 加密为Base64
  • - *
  • 加密为BCD
  • * * * @author Kimi Liu @@ -195,27 +193,4 @@ default String encryptBase64(InputStream data, KeyType keyType) { return Base64.encode(encrypt(data, keyType)); } - /** - * 分组加密 - * - * @param data 数据 - * @param keyType 密钥类型 - * @return 加密后的密文 - */ - default String encryptBcd(String data, KeyType keyType) { - return encryptBcd(data, keyType, org.aoju.bus.core.lang.Charset.UTF_8); - } - - /** - * 分组加密 - * - * @param data 数据 - * @param keyType 密钥类型 - * @param charset 加密前编码 - * @return 加密后的密文 - */ - default String encryptBcd(String data, KeyType keyType, Charset charset) { - return BCD.bcdToString(encrypt(data, charset, keyType)); - } - } From 209f979e435b134a7659c8d5762c5da08eab2ffa Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Thu, 27 Oct 2022 17:43:42 +0800 Subject: [PATCH 08/19] update map and bloom --- .../bus/core/compiler/JavaSourceCompiler.java | 4 +- .../bus/core/convert/AbstractConverter.java | 2 +- .../aoju/bus/core/convert/ArrayConverter.java | 6 +- .../org/aoju/bus/core/convert/BasicType.java | 1 + .../bus/core/convert/CharsetConverter.java | 3 + .../aoju/bus/core/convert/DateConverter.java | 4 +- .../aoju/bus/core/convert/EnumConverter.java | 18 +- .../bus/core/convert/LocaleConverter.java | 13 +- .../bus/core/convert/NumberConverter.java | 3 +- .../bus/core/convert/PrimitiveConverter.java | 3 +- .../bus/core/convert/RegistryConverter.java | 2 +- .../bus/core/convert/TemporalConverter.java | 4 +- .../aoju/bus/core/convert/UUIDConverter.java | 1 - .../java/org/aoju/bus/core/date/Almanac.java | 232 +++++++++--------- .../date/formatter/parser/FastDateParser.java | 2 +- .../bus/core/lang/reflect/MethodHandle.java | 2 +- .../org/aoju/bus/core/toolkit/CharsKit.java | 30 +-- .../org/aoju/bus/core/toolkit/ClassKit.java | 2 +- .../org/aoju/bus/core/toolkit/EnumKit.java | 2 +- .../org/aoju/bus/core/toolkit/ObjectKit.java | 12 +- .../org/aoju/bus/core/toolkit/PhoneKit.java | 2 +- .../org/aoju/bus/core/toolkit/ReflectKit.java | 6 +- .../org/aoju/bus/core/toolkit/SwingKit.java | 2 +- .../org/aoju/bus/core/toolkit/XmlKit.java | 6 +- .../shade/screw/engine/FreemarkerEngine.java | 2 +- 25 files changed, 187 insertions(+), 177 deletions(-) diff --git a/bus-core/src/main/java/org/aoju/bus/core/compiler/JavaSourceCompiler.java b/bus-core/src/main/java/org/aoju/bus/core/compiler/JavaSourceCompiler.java index a04f38e077..dee2ae599c 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/compiler/JavaSourceCompiler.java +++ b/bus-core/src/main/java/org/aoju/bus/core/compiler/JavaSourceCompiler.java @@ -27,8 +27,8 @@ * 使用方法如下: *
      *     ClassLoader classLoader = JavaSourceCompiler.create(null)
    - *         .addSource(FileUtil.file("test-compile/b/B.java"))
    - *         .addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
    + *         .addSource(FileKit.file("test-compile/b/B.java"))
    + *         .addSource("c.C", FileKit.readUtf8String("test-compile/c/C.java"))
      *         // 增加编译依赖的类库
      *         .addLibrary(libFile)
      *         .compile();
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/AbstractConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/AbstractConverter.java
    index 905672a450..d30477dacf 100644
    --- a/bus-core/src/main/java/org/aoju/bus/core/convert/AbstractConverter.java
    +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/AbstractConverter.java
    @@ -103,7 +103,7 @@ protected String convertToString(final Object value) {
             } else if (ArrayKit.isArray(value)) {
                 return ArrayKit.toString(value);
             } else if (CharsKit.isChar(value)) {
    -            //对于ASCII字符使用缓存加速转换,减少空间创建
    +            // 对于ASCII字符使用缓存加速转换,减少空间创建
                 return CharsKit.toString((char) value);
             }
             return value.toString();
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java
    index 995fd86f69..c29cf890ad 100644
    --- a/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java
    +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java
    @@ -129,11 +129,11 @@ private Object convertObjectToArray(final Class targetComponentType, final Ob
     
                 // 字符串转bytes,首先判断是否为Base64,是则转换,否则按照默认getBytes方法。
                 if (targetComponentType == byte.class) {
    -                final String str = value.toString();
    -                if (Base64.isBase64(str)) {
    +                final String text = value.toString();
    +                if (Base64.isBase64(text)) {
                         return Base64.decode(value.toString());
                     }
    -                return str.getBytes();
    +                return text.getBytes();
                 }
     
                 // 单纯字符串情况下按照逗号分隔后劈开
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/BasicType.java b/bus-core/src/main/java/org/aoju/bus/core/convert/BasicType.java
    index 58d17eec4b..896c88aa69 100644
    --- a/bus-core/src/main/java/org/aoju/bus/core/convert/BasicType.java
    +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/BasicType.java
    @@ -36,6 +36,7 @@
      * @since Java 17+
      */
     public enum BasicType {
    +
         /**
          * byte
          */
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/CharsetConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/CharsetConverter.java
    index 01aeebd740..09c753f003 100644
    --- a/bus-core/src/main/java/org/aoju/bus/core/convert/CharsetConverter.java
    +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/CharsetConverter.java
    @@ -29,6 +29,9 @@
     
     /**
      * 编码对象转换器
    + *
    + * @author Kimi Liu
    + * @since Java 17+
      */
     public class CharsetConverter extends AbstractConverter {
     
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/DateConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/DateConverter.java
    index bcb57d006c..b9110c59a2 100644
    --- a/bus-core/src/main/java/org/aoju/bus/core/convert/DateConverter.java
    +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/DateConverter.java
    @@ -41,8 +41,10 @@
      */
     public class DateConverter extends AbstractConverter {
     
    -    public static final DateConverter INSTANCE = new DateConverter();
         private static final long serialVersionUID = 1L;
    +
    +    public static final DateConverter INSTANCE = new DateConverter();
    +
         /**
          * 日期格式化
          */
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java
    index 485e78e33f..17b17c6395 100755
    --- a/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java
    +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java
    @@ -43,8 +43,9 @@
      */
     public class EnumConverter extends AbstractConverter {
     
    -    public static final EnumConverter INSTANCE = new EnumConverter();
         private static final long serialVersionUID = 1L;
    +
    +    public static final EnumConverter INSTANCE = new EnumConverter();
         private static final WeakMap, Map, Method>> VALUE_OF_METHOD_CACHE = new WeakMap<>();
     
         /**
    @@ -93,13 +94,13 @@ protected static Enum tryConvertEnum(final Object value, final Class enumClass)
                 //ignore
             }
     
    -        //oriInt 应该滞后使用 以 GB/T 2261.1-2003 性别编码为例,对应整数并非连续数字会导致数字转枚举时失败
    -        //0 - 未知的性别
    -        //1 - 男性
    -        //2 - 女性
    -        //5 - 女性改(变)为男性
    -        //6 - 男性改(变)为女性
    -        //9 - 未说明的性别
    +        // oriInt 应该滞后使用 以 GB/T 2261.1-2003 性别编码为例,对应整数并非连续数字会导致数字转枚举时失败
    +        // 0 - 未知的性别
    +        // 1 - 男性
    +        // 2 - 女性
    +        // 5 - 女性改(变)为男性
    +        // 6 - 男性改(变)为女性
    +        // 9 - 未说明的性别
             Enum enumResult = null;
             if (value instanceof Integer) {
                 enumResult = EnumKit.getEnumAt(enumClass, (Integer) value);
    @@ -140,7 +141,6 @@ protected Object convertInternal(final Class targetClass, final Object value)
             if (null != enumValue) {
                 return enumValue;
             }
    -
             throw new ConvertException("Can not convert {} to {}", value, targetClass);
         }
     
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/LocaleConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/LocaleConverter.java
    index babbd14765..269de5774a 100644
    --- a/bus-core/src/main/java/org/aoju/bus/core/convert/LocaleConverter.java
    +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/LocaleConverter.java
    @@ -25,6 +25,7 @@
      ********************************************************************************/
     package org.aoju.bus.core.convert;
     
    +import org.aoju.bus.core.lang.Symbol;
     import org.aoju.bus.core.toolkit.StringKit;
     
     import java.util.Locale;
    @@ -43,19 +44,19 @@ public class LocaleConverter extends AbstractConverter {
         @Override
         protected Locale convertInternal(final Class targetClass, final Object value) {
             try {
    -            final String str = convertToString(value);
    -            if (StringKit.isEmpty(str)) {
    +            final String text = convertToString(value);
    +            if (StringKit.isEmpty(text)) {
                     return null;
                 }
     
    -            final String[] items = str.split("_");
    +            final String[] items = text.split(Symbol.UNDERLINE);
                 if (items.length == 1) {
    -                return new Locale(items[0]);
    +                return Locale.of(items[0]);
                 }
                 if (items.length == 2) {
    -                return new Locale(items[0], items[1]);
    +                return Locale.of(items[0], items[1]);
                 }
    -            return new Locale(items[0], items[1], items[2]);
    +            return Locale.of(items[0], items[1], items[2]);
             } catch (final Exception e) {
                 // Ignore Exception
             }
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java
    index 989c1158b4..2c0ac2e293 100644
    --- a/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java
    +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java
    @@ -60,9 +60,10 @@
      */
     public class NumberConverter extends AbstractConverter {
     
    -    public static final NumberConverter INSTANCE = new NumberConverter();
         private static final long serialVersionUID = 1L;
     
    +    public static final NumberConverter INSTANCE = new NumberConverter();
    +
         /**
          * 转换对象为数字,支持的对象包括:
          * 
      diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/PrimitiveConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/PrimitiveConverter.java index 594e91ded5..70b8ba9a56 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/PrimitiveConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/PrimitiveConverter.java @@ -50,9 +50,10 @@ */ public class PrimitiveConverter extends AbstractConverter { - public static final PrimitiveConverter INSTANCE = new PrimitiveConverter(); private static final long serialVersionUID = 1L; + public static final PrimitiveConverter INSTANCE = new PrimitiveConverter(); + /** * 将指定值转换为原始类型的值 * diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/RegistryConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/RegistryConverter.java index 14fcf63e27..989cea5871 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/RegistryConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/RegistryConverter.java @@ -202,7 +202,7 @@ private void register() { defaultConverterMap.put(SoftReference.class, ReferenceConverter.INSTANCE); defaultConverterMap.put(AtomicReference.class, new AtomicReferenceConverter()); - //AtomicXXXArray,since 5.4.5 + // AtomicXXXArray,since 5.4.5 defaultConverterMap.put(AtomicIntegerArray.class, new AtomicIntegerArrayConverter()); defaultConverterMap.put(AtomicLongArray.class, new AtomicLongArrayConverter()); diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/TemporalConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/TemporalConverter.java index ec7a518796..dee215d7f7 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/TemporalConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/TemporalConverter.java @@ -58,8 +58,10 @@ */ public class TemporalConverter extends AbstractConverter { - public static final TemporalConverter INSTANCE = new TemporalConverter(); private static final long serialVersionUID = 1L; + + public static final TemporalConverter INSTANCE = new TemporalConverter(); + /** * 日期格式化 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/UUIDConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/UUIDConverter.java index fdca0f2ede..c9a904e59f 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/convert/UUIDConverter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/convert/UUIDConverter.java @@ -37,7 +37,6 @@ public class UUIDConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - @Override protected UUID convertInternal(final Class targetClass, final Object value) { return UUID.fromString(convertToString(value)); diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java b/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java index 11e3165df6..388fe028a3 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java @@ -60,7 +60,7 @@ public class Almanac extends Converter { * @return int */ public static int getYear(Date date) { - return Converter.toLocalDateTime(date).getYear(); + return toLocalDateTime(date).getYear(); } /** @@ -70,7 +70,7 @@ public static int getYear(Date date) { * @return int */ public static int getYear(Instant instant) { - return Converter.toLocalDateTime(instant).getYear(); + return toLocalDateTime(instant).getYear(); } /** @@ -302,7 +302,7 @@ public static String getMonth(int month) { * @return int */ public static int getMonth(Date date) { - return Converter.toLocalDateTime(date).getMonthValue(); + return toLocalDateTime(date).getMonthValue(); } /** @@ -312,7 +312,7 @@ public static int getMonth(Date date) { * @return int */ public static int getMonth(Instant instant) { - return Converter.toLocalDateTime(instant).getMonthValue(); + return toLocalDateTime(instant).getMonthValue(); } /** @@ -819,7 +819,7 @@ public static int getDayOfMonth(int month, boolean isLeapYear) { * @return int */ public static int getDayOfMonth(Date date) { - return Converter.toLocalDateTime(date).getDayOfMonth(); + return toLocalDateTime(date).getDayOfMonth(); } /** @@ -829,7 +829,7 @@ public static int getDayOfMonth(Date date) { * @return int */ public static int getDayOfMonth(Instant instant) { - return Converter.toLocalDateTime(instant).getDayOfMonth(); + return toLocalDateTime(instant).getDayOfMonth(); } /** @@ -861,7 +861,7 @@ public static int getDayOfMonth(LocalDate localDate) { * @return int */ public static int getDayOfYear(Date date) { - return Converter.toLocalDateTime(date).getDayOfYear(); + return toLocalDateTime(date).getDayOfYear(); } /** @@ -871,7 +871,7 @@ public static int getDayOfYear(Date date) { * @return int */ public static int getDayOfYear(Instant instant) { - return Converter.toLocalDateTime(instant).getDayOfYear(); + return toLocalDateTime(instant).getDayOfYear(); } /** @@ -922,7 +922,7 @@ public static int getDayOfYear() { * @return int */ public static int getHour(Date date) { - return Converter.toLocalDateTime(date).getHour(); + return toLocalDateTime(date).getHour(); } /** @@ -932,7 +932,7 @@ public static int getHour(Date date) { * @return int */ public static int getHour(Instant instant) { - return Converter.toLocalDateTime(instant).getHour(); + return toLocalDateTime(instant).getHour(); } /** @@ -964,7 +964,7 @@ public static int getHour(LocalTime localTime) { * @return int */ public static int getMinute(Date date) { - return Converter.toLocalDateTime(date).getMinute(); + return toLocalDateTime(date).getMinute(); } /** @@ -974,7 +974,7 @@ public static int getMinute(Date date) { * @return int */ public static int getMinute(Instant instant) { - return Converter.toLocalDateTime(instant).getMinute(); + return toLocalDateTime(instant).getMinute(); } /** @@ -1006,7 +1006,7 @@ public static int getMinute(LocalTime localTime) { * @return int */ public static int getSecond(Date date) { - return Converter.toLocalDateTime(date).getSecond(); + return toLocalDateTime(date).getSecond(); } /** @@ -1016,7 +1016,7 @@ public static int getSecond(Date date) { * @return int */ public static int getSecond(Instant instant) { - return Converter.toLocalDateTime(instant).getSecond(); + return toLocalDateTime(instant).getSecond(); } /** @@ -1048,7 +1048,7 @@ public static int getSecond(LocalTime localTime) { * @return int */ public static int getMillisecond(Date date) { - return Converter.toLocalDateTime(date).getNano() / 1_000_000; + return toLocalDateTime(date).getNano() / 1_000_000; } /** @@ -1058,7 +1058,7 @@ public static int getMillisecond(Date date) { * @return int */ public static int getMillisecond(Instant instant) { - return Converter.toLocalDateTime(instant).getNano() / 1_000_000; + return toLocalDateTime(instant).getNano() / 1_000_000; } /** @@ -1115,7 +1115,7 @@ public static long getEpochSecond() { * @return String 格式: yyyy-MM-dd HH:mm:ss */ public static String getEpochMilliFormat() { - return Formatter.format(new Date()); + return format(new Date()); } /** @@ -1124,7 +1124,7 @@ public static String getEpochMilliFormat() { * @return String 格式: yyyy-MM-dd HH:mm:ss.SSS */ public static String getEpochMilliFormatFull() { - return Formatter.format(new Date(), Fields.NORM_DATETIME_MS_PATTERN); + return format(new Date(), Fields.NORM_DATETIME_MS_PATTERN); } /** @@ -1133,7 +1133,7 @@ public static String getEpochMilliFormatFull() { * @return String 格式: yyyy-MM-ddTHH:mm:ssZ */ public static String getEpochMilliIsoNotFormatNoColon() { - return Formatter.format(new Date(), Fields.MSEC_PATTERN); + return format(new Date(), Fields.MSEC_PATTERN); } /** @@ -1142,7 +1142,7 @@ public static String getEpochMilliIsoNotFormatNoColon() { * @return String 格式: yyyy-MM-dd'T'HH:mm:ss.SSSZ */ public static String getEpochMilliIsoFormatFullNoColon() { - return Formatter.format(new Date(), Fields.MSEC_PATTERN); + return format(new Date(), Fields.MSEC_PATTERN); } /** @@ -1154,7 +1154,7 @@ public static String getEpochMilliIsoFormatFullNoColon() { * @return Date */ public static Date getDate(int year, int month, int dayOfMonth) { - return Converter.toDate(LocalDate.of(year, month, dayOfMonth)); + return toDate(LocalDate.of(year, month, dayOfMonth)); } /** @@ -1169,7 +1169,7 @@ public static Date getDate(int year, int month, int dayOfMonth) { * @return Date */ public static Date getDate(int year, int month, int dayOfMonth, int hour, int minute, int second) { - return Converter.toDate(LocalDateTime.of(year, month, dayOfMonth, hour, minute, second)); + return toDate(LocalDateTime.of(year, month, dayOfMonth, hour, minute, second)); } /** @@ -1185,7 +1185,7 @@ public static Date getDate(int year, int month, int dayOfMonth, int hour, int mi * @return Date */ public static Date getDate(int year, int month, int dayOfMonth, int hour, int minute, int second, int milliOfSecond) { - return Converter.toDate(LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, milliOfSecond * 1000_000)); + return toDate(LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, milliOfSecond * 1000_000)); } /** @@ -1196,7 +1196,7 @@ public static Date getDate(int year, int month, int dayOfMonth, int hour, int mi * @return Date */ public static Date getDateStartOfMonth(int year, int month) { - return Converter.toDateStartOfMonth(YearMonth.of(year, month)); + return toDateStartOfMonth(YearMonth.of(year, month)); } /** @@ -1207,7 +1207,7 @@ public static Date getDateStartOfMonth(int year, int month) { * @return Date */ public static Date getDateEndOfMonth(int year, int month) { - return Converter.toDateEndOfMonth(YearMonth.of(year, month)); + return toDateEndOfMonth(YearMonth.of(year, month)); } /** @@ -1232,7 +1232,7 @@ public static int getAge(LocalDate birthDay) { * @return int 年龄 */ public static int getAge(Date birthDay) { - return getAge(Converter.toLocalDate(birthDay)); + return getAge(toLocalDate(birthDay)); } /** @@ -1242,7 +1242,7 @@ public static int getAge(Date birthDay) { * @return int 年龄 */ public static int getAge(LocalDateTime birthDay) { - return getAge(Converter.toLocalDate(birthDay)); + return getAge(toLocalDate(birthDay)); } /** @@ -1342,7 +1342,7 @@ public static String getAge(String birthDay, String dateToCompare) { Calendar birthday = new GregorianCalendar(Integer.valueOf(data[0]), Integer.valueOf(data[1]), Integer.valueOf(data[2])); Calendar now = Calendar.getInstance(); - now.setTime(Converter.parse(dateToCompare)); + now.setTime(parse(dateToCompare)); // 1.日相减 int day = now.get(Calendar.DAY_OF_MONTH) - birthday.get(Calendar.DAY_OF_MONTH); @@ -2030,8 +2030,8 @@ public static LocalDate withDayOfWeek(LocalDate localDate, long newValue) { * @return long */ public static long betweenYears(LocalDateTime startInclusive, LocalDateTime endExclusive) { - return Period.between(Converter.toLocalDate(startInclusive), - Converter.toLocalDate(endExclusive)).getYears(); + return Period.between(toLocalDate(startInclusive), + toLocalDate(endExclusive)).getYears(); } /** @@ -2043,8 +2043,8 @@ public static long betweenYears(LocalDateTime startInclusive, LocalDateTime endE * @return long */ public static long betweenYears(Date startInclusive, Date endExclusive) { - return Period.between(Converter.toLocalDate(startInclusive), - Converter.toLocalDate(endExclusive)).getYears(); + return Period.between(toLocalDate(startInclusive), + toLocalDate(endExclusive)).getYears(); } /** @@ -2068,8 +2068,8 @@ public static long betweenYears(LocalDate startInclusive, LocalDate endExclusive * @return long */ public static long betweenMonths(LocalDateTime startInclusive, LocalDateTime endExclusive) { - return Period.between(Converter.toLocalDate(startInclusive), - Converter.toLocalDate(endExclusive)).getMonths(); + return Period.between(toLocalDate(startInclusive), + toLocalDate(endExclusive)).getMonths(); } /** @@ -2081,8 +2081,8 @@ public static long betweenMonths(LocalDateTime startInclusive, LocalDateTime end * @return long */ public static long betweenMonths(Date startInclusive, Date endExclusive) { - return Period.between(Converter.toLocalDate(startInclusive), - Converter.toLocalDate(endExclusive)).getMonths(); + return Period.between(toLocalDate(startInclusive), + toLocalDate(endExclusive)).getMonths(); } /** @@ -2106,8 +2106,8 @@ public static long betweenMonths(LocalDate startInclusive, LocalDate endExclusiv * @return long */ public static long betweenDays(LocalDateTime startInclusive, LocalDateTime endExclusive) { - return Period.between(Converter.toLocalDate(startInclusive), - Converter.toLocalDate(endExclusive)).getDays(); + return Period.between(toLocalDate(startInclusive), + toLocalDate(endExclusive)).getDays(); } /** @@ -2119,8 +2119,8 @@ public static long betweenDays(LocalDateTime startInclusive, LocalDateTime endEx * @return long */ public static long betweenDays(Date startInclusive, Date endExclusive) { - return Period.between(Converter.toLocalDate(startInclusive), - Converter.toLocalDate(endExclusive)).getDays(); + return Period.between(toLocalDate(startInclusive), + toLocalDate(endExclusive)).getDays(); } /** @@ -2154,7 +2154,7 @@ public static long betweenTotalDays(LocalDateTime startInclusive, LocalDateTime * @return long */ public static long betweenTotalDays(Date startInclusive, Date endExclusive) { - return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toDays(); + return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toDays(); } /** @@ -2187,7 +2187,7 @@ public static long betweenTotalHours(LocalTime startInclusive, LocalTime endExcl * @return long */ public static long betweenTotalHours(Date startInclusive, Date endExclusive) { - return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toHours(); + return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toHours(); } /** @@ -2220,7 +2220,7 @@ public static long betweenTotalMinutes(LocalTime startInclusive, LocalTime endEx * @return long */ public static long betweenTotalMinutes(Date startInclusive, Date endExclusive) { - return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toMinutes(); + return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toMinutes(); } /** @@ -2253,7 +2253,7 @@ public static long betweenTotalSeconds(LocalTime startInclusive, LocalTime endEx * @return long */ public static long betweenTotalSeconds(Date startInclusive, Date endExclusive) { - return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).getSeconds(); + return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).getSeconds(); } /** @@ -2286,7 +2286,7 @@ public static long betweenTotalMillis(LocalTime startInclusive, LocalTime endExc * @return long */ public static long betweenTotalMillis(Date startInclusive, Date endExclusive) { - return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toMillis(); + return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toMillis(); } /** @@ -2319,7 +2319,7 @@ public static long betweenTotalNanos(LocalTime startInclusive, LocalTime endExcl * @return long */ public static long betweenTotalNanos(Date startInclusive, Date endExclusive) { - return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toNanos(); + return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toNanos(); } /** @@ -2329,7 +2329,7 @@ public static long betweenTotalNanos(Date startInclusive, Date endExclusive) { * @return int */ public static int getDayOfWeek(Date date) { - return Converter.toLocalDateTime(date).getDayOfWeek().getValue(); + return toLocalDateTime(date).getDayOfWeek().getValue(); } /** @@ -2359,7 +2359,7 @@ public static int getDayOfWeek(LocalDate localDate) { * @return int */ public static int getDayOfWeek(Instant instant) { - return Converter.toLocalDateTime(instant).getDayOfWeek().getValue(); + return toLocalDateTime(instant).getDayOfWeek().getValue(); } /** @@ -2589,7 +2589,7 @@ public static LocalDateTime firstDayOfMonth(LocalDateTime localDateTime) { * @return Date */ public static Date firstDayOfMonth(Date date) { - return Converter.toDate(Converter.toLocalDateTime(date).with(TemporalAdjusters.firstDayOfMonth())); + return toDate(toLocalDateTime(date).with(TemporalAdjusters.firstDayOfMonth())); } /** @@ -2619,7 +2619,7 @@ public static LocalDateTime lastDayOfMonth(LocalDateTime localDateTime) { * @return Date */ public static Date lastDayOfMonth(Date date) { - return Converter.toDate(Converter.toLocalDateTime(date).with(TemporalAdjusters.lastDayOfMonth())); + return toDate(toLocalDateTime(date).with(TemporalAdjusters.lastDayOfMonth())); } @@ -2999,7 +2999,7 @@ public static boolean isLeapYear(LocalDateTime localDateTime) { * @return boolean */ public static boolean isLeapYear(Date date) { - return Converter.toLocalDateTime(date).toLocalDate().isLeapYear(); + return toLocalDateTime(date).toLocalDate().isLeapYear(); } /** @@ -3055,7 +3055,7 @@ public static LocalDate nextLeapYear(LocalDate localDate) { * @return Date */ public static Date nextLeapYear(Date date) { - return Converter.toDate(nextLeapYear(Converter.toLocalDateTime(date))); + return toDate(nextLeapYear(toLocalDateTime(date))); } /** @@ -3148,7 +3148,7 @@ public static int lengthOfMonth(LocalDateTime localDateTime) { * @return int */ public static int lengthOfMonth(Date date) { - return Converter.toLocalDateTime(date).toLocalDate().lengthOfMonth(); + return toLocalDateTime(date).toLocalDate().lengthOfMonth(); } /** @@ -3178,7 +3178,7 @@ public static int lengthOfYear(LocalDateTime localDateTime) { * @return int */ public static int lengthOfYear(Date date) { - return Converter.toLocalDateTime(date).toLocalDate().lengthOfYear(); + return toLocalDateTime(date).toLocalDate().lengthOfYear(); } /** @@ -3211,7 +3211,7 @@ public static LocalDateTime next(LocalDateTime localDateTime, DayOfWeek dayOfWee * @return Date */ public static Date next(Date date, DayOfWeek dayOfWeek) { - return Converter.toDate(Converter.toLocalDateTime(date).with(TemporalAdjusters.next(dayOfWeek))); + return toDate(toLocalDateTime(date).with(TemporalAdjusters.next(dayOfWeek))); } @@ -3245,7 +3245,7 @@ public static LocalDateTime previous(LocalDateTime localDateTime, DayOfWeek dayO * @return Date */ public static Date previous(Date date, DayOfWeek dayOfWeek) { - return Converter.toDate(Converter.toLocalDateTime(date).with(TemporalAdjusters.previous(dayOfWeek))); + return toDate(toLocalDateTime(date).with(TemporalAdjusters.previous(dayOfWeek))); } /** @@ -3275,7 +3275,7 @@ public static LocalDateTime nextWorkDay(LocalDateTime localDateTime) { * @return Date */ public static Date nextWorkDay(Date date) { - return Converter.toDate(Converter.toLocalDateTime(date).with(nextWorkDay())); + return toDate(toLocalDateTime(date).with(nextWorkDay())); } /** @@ -3363,7 +3363,7 @@ public static Temporal with(Temporal temporal, TemporalField field, long newValu * @return Date */ public static Date with(Date date, TemporalField field, long newValue) { - return Converter.toDate(Converter.toLocalDateTime(date).with(field, newValue)); + return toDate(toLocalDateTime(date).with(field, newValue)); } /** @@ -3454,7 +3454,7 @@ public static String transform(Date date, String zoneId) { * @return 日期 yyyy-MM-dd HH:mm:ss */ public static String transform(Date date, java.time.ZoneId zone) { - return Formatter.format(date, zone.toString()); + return format(date, zone.toString()); } /** @@ -3465,7 +3465,7 @@ public static String transform(Date date, java.time.ZoneId zone) { * @return int date1 大于 date2 返回1, date1 小于 date2 返回-1,date1 等于date2 返回0 */ public static int compare(Date date1, Date date2) { - return compare(Converter.toLocalDateTime(date1), Converter.toLocalDateTime(date2)); + return compare(toLocalDateTime(date1), toLocalDateTime(date2)); } /** @@ -3562,7 +3562,7 @@ public static LocalTime endAccuracyTimeOfDay() { * @return Date */ public static Date startTimeOfYesterday() { - return Converter.toDate(LocalDate.now().minusDays(1).atTime(startTimeOfDay())); + return toDate(LocalDate.now().minusDays(1).atTime(startTimeOfDay())); } /** @@ -3571,7 +3571,7 @@ public static Date startTimeOfYesterday() { * @return Date */ public static Date endTimeOfYesterday() { - return Converter.toDate(LocalDate.now().minusDays(1).atTime(endTimeOfDay())); + return toDate(LocalDate.now().minusDays(1).atTime(endTimeOfDay())); } /** @@ -3580,7 +3580,7 @@ public static Date endTimeOfYesterday() { * @return Date */ public static Date startTimeOfTomorrow() { - return Converter.toDate(LocalDate.now().plusDays(1).atTime(startTimeOfDay())); + return toDate(LocalDate.now().plusDays(1).atTime(startTimeOfDay())); } /** @@ -3589,7 +3589,7 @@ public static Date startTimeOfTomorrow() { * @return Date */ public static Date endTimeOfTomorrow() { - return Converter.toDate(LocalDate.now().plusDays(1).atTime(endTimeOfDay())); + return toDate(LocalDate.now().plusDays(1).atTime(endTimeOfDay())); } /** @@ -3598,7 +3598,7 @@ public static Date endTimeOfTomorrow() { * @return Date */ public static Date startTimeOfToday() { - return Converter.toDate(LocalDate.now().atTime(startTimeOfDay())); + return toDate(LocalDate.now().atTime(startTimeOfDay())); } /** @@ -3607,7 +3607,7 @@ public static Date startTimeOfToday() { * @return Date */ public static Date endTimeOfToday() { - return Converter.toDate(LocalDate.now().atTime(endTimeOfDay())); + return toDate(LocalDate.now().atTime(endTimeOfDay())); } /** @@ -3616,7 +3616,7 @@ public static Date endTimeOfToday() { * @return Date */ public static Date startTimeOfLastMonth() { - return Converter.toDate(firstDayOfMonth(LocalDate.now().minusMonths(1)).atTime(startTimeOfDay())); + return toDate(firstDayOfMonth(LocalDate.now().minusMonths(1)).atTime(startTimeOfDay())); } /** @@ -3625,7 +3625,7 @@ public static Date startTimeOfLastMonth() { * @return Date */ public static Date endTimeOfLastMonth() { - return Converter.toDate(lastDayOfMonth(LocalDate.now().minusMonths(1)).atTime(endTimeOfDay())); + return toDate(lastDayOfMonth(LocalDate.now().minusMonths(1)).atTime(endTimeOfDay())); } /** @@ -3634,7 +3634,7 @@ public static Date endTimeOfLastMonth() { * @return Date */ public static Date startTimeOfMonth() { - return Converter.toDate(firstDayOfMonth(LocalDate.now()).atTime(startTimeOfDay())); + return toDate(firstDayOfMonth(LocalDate.now()).atTime(startTimeOfDay())); } /** @@ -3643,7 +3643,7 @@ public static Date startTimeOfMonth() { * @return Date */ public static Date endTimeOfMonth() { - return Converter.toDate(lastDayOfMonth(LocalDate.now()).atTime(endTimeOfDay())); + return toDate(lastDayOfMonth(LocalDate.now()).atTime(endTimeOfDay())); } /** @@ -3653,7 +3653,7 @@ public static Date endTimeOfMonth() { * @return Date */ public static Date startTimeOfDate(Date date) { - return Converter.toDate(Converter.toLocalDate(date).atTime(startTimeOfDay())); + return toDate(toLocalDate(date).atTime(startTimeOfDay())); } /** @@ -3663,7 +3663,7 @@ public static Date startTimeOfDate(Date date) { * @return Date */ public static Date endTimeOfDate(Date date) { - return Converter.toDate(Converter.toLocalDate(date).atTime(endTimeOfDay())); + return toDate(toLocalDate(date).atTime(endTimeOfDay())); } @@ -3674,7 +3674,7 @@ public static Date endTimeOfDate(Date date) { * @return Date */ public static Date endAccuracyTimeOfDate(Date date) { - return Converter.toDate(Converter.toLocalDate(date).atTime(endAccuracyTimeOfDay())); + return toDate(toLocalDate(date).atTime(endAccuracyTimeOfDay())); } /** @@ -3706,7 +3706,7 @@ public static LocalDateTime endAccuracyTimeOfLocalDateTime(LocalDateTime localDa * @return Date */ public static Date startTimeOfSpecialMonth(int year, int month) { - return Converter.toDate(LocalDate.of(year, month, 1).atTime(startTimeOfDay())); + return toDate(LocalDate.of(year, month, 1).atTime(startTimeOfDay())); } /** @@ -3717,7 +3717,7 @@ public static Date startTimeOfSpecialMonth(int year, int month) { * @return Date */ public static Date endTimeOfSpecialMonth(int year, int month) { - return Converter.toDate(lastDayOfMonth(LocalDate.of(year, month, 1)).atTime(endTimeOfDay())); + return toDate(lastDayOfMonth(LocalDate.of(year, month, 1)).atTime(endTimeOfDay())); } /** @@ -3729,7 +3729,7 @@ public static Date endTimeOfSpecialMonth(int year, int month) { * @return Date */ public static Date startTimeOfDate(int year, int month, int dayOfMonth) { - return Converter.toDate(LocalDate.of(year, month, dayOfMonth).atTime(startTimeOfDay())); + return toDate(LocalDate.of(year, month, dayOfMonth).atTime(startTimeOfDay())); } /** @@ -3741,7 +3741,7 @@ public static Date startTimeOfDate(int year, int month, int dayOfMonth) { * @return Date */ public static Date endTimeOfDate(int year, int month, int dayOfMonth) { - return Converter.toDate(LocalDate.of(year, month, dayOfMonth).atTime(endTimeOfDay())); + return toDate(LocalDate.of(year, month, dayOfMonth).atTime(endTimeOfDay())); } /** @@ -3945,7 +3945,7 @@ public static boolean isSameDay(final Date date1, final Date date2) { if (date1 == null || date2 == null) { throw new IllegalArgumentException("The date must not be null"); } - return isSameDay(Converter.toCalendar(date1), Converter.toCalendar(date2)); + return isSameDay(toCalendar(date1), toCalendar(date2)); } /** @@ -4013,7 +4013,7 @@ public static boolean isSameWeek(final Date date1, final Date date2, boolean isM if (date1 == null || date2 == null) { throw new IllegalArgumentException("The date must not be null"); } - return isSameWeek(Converter.toCalendar(date1), Converter.toCalendar(date2), isMon); + return isSameWeek(toCalendar(date1), toCalendar(date2), isMon); } /** @@ -4027,7 +4027,7 @@ public static boolean isSameMonth(final Date date1, final Date date2) { if (date1 == null || date2 == null) { throw new IllegalArgumentException("The date must not be null"); } - return isSameMonth(Converter.toCalendar(date1), Converter.toCalendar(date2)); + return isSameMonth(toCalendar(date1), toCalendar(date2)); } /** @@ -4086,7 +4086,7 @@ public static boolean isSameMonthDay(LocalDate localDate1, LocalDate localDate2) * @return boolean */ public static boolean isSameMonthDay(Date date, String monthDayStr) { - return isSameMonthDay(Converter.toLocalDate(date), monthDayStr); + return isSameMonthDay(toLocalDate(date), monthDayStr); } /** @@ -4097,7 +4097,7 @@ public static boolean isSameMonthDay(Date date, String monthDayStr) { * @return boolean */ public static boolean isSameMonthDay(Date date1, Date date2) { - return isSameMonthDay(Converter.toLocalDate(date1), Converter.toLocalDate(date2)); + return isSameMonthDay(toLocalDate(date1), toLocalDate(date2)); } /** @@ -4162,7 +4162,7 @@ public static long betweenNextSameMonthDay(LocalDate localDate, String monthDayS */ public static long betweenNextSameMonthDay(Date date, String monthDayStr) { MonthDay monthDay2 = MonthDay.parse(Symbol.MINUS + Symbol.MINUS + monthDayStr); - return betweenNextSameMonthDay(Converter.toLocalDate(date), monthDay2.getMonthValue(), + return betweenNextSameMonthDay(toLocalDate(date), monthDay2.getMonthValue(), monthDay2.getDayOfMonth()); } @@ -4197,7 +4197,7 @@ public static LocalDate nextSameMonthDay(LocalDate localDate, String monthDayStr * @return Date */ public static Date nextSameMonthDay(Date date, String monthDayStr) { - return Converter.toDate(nextSameMonthDay(Converter.toLocalDate(date), monthDayStr)); + return toDate(nextSameMonthDay(toLocalDate(date), monthDayStr)); } /** @@ -4227,7 +4227,7 @@ public static String getZodiacCnName(String monthDay) { * @return String */ public static String getZodiacCnName(Date date) { - return Fields.Zodiac.getCnNameByMonthDay(Formatter.format(date)); + return Fields.Zodiac.getCnNameByMonthDay(format(date)); } /** @@ -4247,7 +4247,7 @@ public static String getZodiacEnName(String monthDay) { * @return String */ public static String getZodiacEnName(Date date) { - return Fields.Zodiac.getEnNameByMonthDay(Formatter.format(date)); + return Fields.Zodiac.getEnNameByMonthDay(format(date)); } /** @@ -4257,7 +4257,7 @@ public static String getZodiacEnName(Date date) { * @return 星座名 */ public static String getZodiac(Date date) { - return getZodiac(Converter.toCalendar(date)); + return getZodiac(toCalendar(date)); } /** @@ -4311,8 +4311,8 @@ public static List getLocalDateTimeList(LocalDateTime startInclus * @return 时间列表 */ public static List getLocalDateList(LocalDate startInclusive, LocalDate endInclusive) { - return getLocalDateTimeList(Converter.toLocalDateTime(startInclusive), - Converter.toLocalDateTime(endInclusive)).stream() + return getLocalDateTimeList(toLocalDateTime(startInclusive), + toLocalDateTime(endInclusive)).stream() .map(localDateTime -> localDateTime.toLocalDate()).collect(Collectors.toList()); } @@ -4325,7 +4325,7 @@ public static List getLocalDateList(LocalDate startInclusive, LocalDa public static List getLocalDateList(YearMonth yearMonth) { List localDateList = new ArrayList<>(); long days = yearMonth.lengthOfMonth(); - LocalDate localDate = Converter.toLocalDateStartOfMonth(yearMonth); + LocalDate localDate = toLocalDateStartOfMonth(yearMonth); for (long i = 0; i < days; i++) { localDateList.add(localDate.plusDays(i)); } @@ -4363,7 +4363,7 @@ public static List getLocalDateList(int year, int month) { */ public static List getLocalDateTimeList(YearMonth yearMonth) { return getLocalDateList(yearMonth).stream() - .map(localDate -> Converter.toLocalDateTime(localDate)).collect(Collectors.toList()); + .map(localDate -> toLocalDateTime(localDate)).collect(Collectors.toList()); } /** @@ -4374,7 +4374,7 @@ public static List getLocalDateTimeList(YearMonth yearMonth) { */ public static List getLocalDateTimeList(String yearMonthStr) { return getLocalDateList(yearMonthStr).stream() - .map(localDate -> Converter.toLocalDateTime(localDate)).collect(Collectors.toList()); + .map(localDate -> toLocalDateTime(localDate)).collect(Collectors.toList()); } /** @@ -4386,7 +4386,7 @@ public static List getLocalDateTimeList(String yearMonthStr) { */ public static List getLocalDateTimeList(int year, int month) { return getLocalDateList(YearMonth.of(year, month)).stream() - .map(localDate -> Converter.toLocalDateTime(localDate)).collect(Collectors.toList()); + .map(localDate -> toLocalDateTime(localDate)).collect(Collectors.toList()); } /** @@ -4396,7 +4396,7 @@ public static List getLocalDateTimeList(int year, int month) { * @return 时间列表 */ public static List getDateList(String yearMonthStr) { - return getLocalDateList(yearMonthStr).stream().map(localDate -> Converter.toDate(localDate)) + return getLocalDateList(yearMonthStr).stream().map(localDate -> toDate(localDate)) .collect(Collectors.toList()); } @@ -4408,7 +4408,7 @@ public static List getDateList(String yearMonthStr) { * @return 时间列表 */ public static List getDateList(int year, int month) { - return getLocalDateList(YearMonth.of(year, month)).stream().map(localDate -> Converter.toDate(localDate)) + return getLocalDateList(YearMonth.of(year, month)).stream().map(localDate -> toDate(localDate)) .collect(Collectors.toList()); } @@ -4420,9 +4420,9 @@ public static List getDateList(int year, int month) { * @return 时间列表 */ public static List getDateList(Date startInclusive, Date endInclusive) { - return getLocalDateTimeList(Converter.toLocalDateTime(startInclusive), - Converter.toLocalDateTime(endInclusive)).stream() - .map(localDateTime -> Converter.toDate(localDateTime)).collect(Collectors.toList()); + return getLocalDateTimeList(toLocalDateTime(startInclusive), + toLocalDateTime(endInclusive)).stream() + .map(localDateTime -> toDate(localDateTime)).collect(Collectors.toList()); } /** @@ -4477,7 +4477,7 @@ public static boolean isBirthday(CharSequence value) { * @return boolean */ public static boolean isBirthDay(Date date) { - return isBirthDay(Converter.toLocalDate(date)); + return isBirthDay(toLocalDate(date)); } /** @@ -4497,7 +4497,7 @@ public static boolean isBirthDay(LocalDate localDate) { * @return boolean */ public static boolean isBirthDay(LocalDateTime localDateTime) { - return isBirthDay(Converter.toLocalDate(localDateTime)); + return isBirthDay(toLocalDate(localDateTime)); } /** @@ -4554,7 +4554,7 @@ public static LocalDateTime reduceAccuracyToSecond(LocalDateTime localDateTime) * @return Date */ public static Date reduceAccuracyToSecond(Date date) { - return Converter.toDate(reduceAccuracyToSecond(Converter.toLocalDateTime(date))); + return toDate(reduceAccuracyToSecond(toLocalDateTime(date))); } /** @@ -4576,7 +4576,7 @@ public static LocalDateTime reduceAccuracyToMinute(LocalDateTime localDateTime) * @return Date */ public static Date reduceAccuracyToMinute(Date date) { - return Converter.toDate(reduceAccuracyToMinute(Converter.toLocalDateTime(date))); + return toDate(reduceAccuracyToMinute(toLocalDateTime(date))); } /** @@ -4597,7 +4597,7 @@ public static LocalDateTime reduceAccuracyToHour(LocalDateTime localDateTime) { * @return Date */ public static Date reduceAccuracyToHour(Date date) { - return Converter.toDate(reduceAccuracyToHour(Converter.toLocalDateTime(date))); + return toDate(reduceAccuracyToHour(toLocalDateTime(date))); } /** @@ -4618,7 +4618,7 @@ public static LocalDateTime reduceAccuracyToDay(LocalDateTime localDateTime) { * @return Date */ public static Date reduceAccuracyToDay(Date date) { - return Converter.toDate(reduceAccuracyToDay(Converter.toLocalDateTime(date))); + return toDate(reduceAccuracyToDay(toLocalDateTime(date))); } /** @@ -4650,7 +4650,7 @@ public static int weekOfMonth(LocalDate localDate) { * @return 周数 */ public static int weekOfMonth(LocalDateTime localDateTime) { - return weekOfMonth(Converter.toLocalDate(localDateTime), null); + return weekOfMonth(toLocalDate(localDateTime), null); } /** @@ -4660,7 +4660,7 @@ public static int weekOfMonth(LocalDateTime localDateTime) { * @return 周数 */ public static int weekOfMonth(Date date) { - return weekOfMonth(Converter.toLocalDate(date), null); + return weekOfMonth(toLocalDate(date), null); } /** @@ -4701,7 +4701,7 @@ public static int weekOfYear(LocalDate localDate) { * @return 周数 */ public static int weekOfYear(LocalDateTime localDateTime) { - return weekOfYear(Converter.toLocalDate(localDateTime), null); + return weekOfYear(toLocalDate(localDateTime), null); } /** @@ -4711,7 +4711,7 @@ public static int weekOfYear(LocalDateTime localDateTime) { * @return 周数 */ public static int weekOfYear(Date date) { - return weekOfYear(Converter.toLocalDate(date), null); + return weekOfYear(toLocalDate(date), null); } /** @@ -4740,7 +4740,7 @@ public static boolean isMonday(LocalDate localDate) { * @return 是 true 否 false */ public static boolean isMonday(Date date) { - return isMonday(Converter.toLocalDate(date)); + return isMonday(toLocalDate(date)); } /** @@ -4760,7 +4760,7 @@ public static boolean isFriday(LocalDate localDate) { * @return 是 true 否 false */ public static boolean isFriday(Date date) { - return isFriday(Converter.toLocalDate(date)); + return isFriday(toLocalDate(date)); } /** @@ -4774,7 +4774,7 @@ public static boolean isDate(String dptDate, String pattern) { if (null == dptDate || dptDate.isEmpty()) { return false; } - String formatDate = Formatter.format(dptDate, pattern, pattern); + String formatDate = format(dptDate, pattern, pattern); return formatDate.equals(dptDate); } @@ -4800,8 +4800,8 @@ public static boolean isBefore(String go, String back, String pattern) { if (null == go || null == back || go.isEmpty() || back.isEmpty()) return false; - Date goDate = offsetDay(Formatter.parse(go, pattern), -1); - Date backDate = Formatter.parse(back, pattern); + Date goDate = offsetDay(parse(go, pattern), -1); + Date backDate = parse(back, pattern); if (null != goDate && null != backDate) { return goDate.before(backDate); } @@ -5041,7 +5041,7 @@ public static String getChrono(LocalTime localTime) { * @return 十二时辰名称 */ public static String getChrono(LocalDateTime localDateTime) { - return Fields.Chrono.getName(Converter.toLocalTime(localDateTime)); + return Fields.Chrono.getName(toLocalTime(localDateTime)); } /** @@ -5070,7 +5070,7 @@ public static String getChrono() { * @return 星座名 */ public static String getAnimal(Date date) { - return getAnimal(Converter.toCalendar(date)); + return getAnimal(toCalendar(date)); } /** @@ -5578,7 +5578,7 @@ public static int getLastDayOfMonth(final Date date) { * @return 当前时间的标准形式字符串 */ public static String now() { - return Formatter.format(date()); + return format(date()); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/formatter/parser/FastDateParser.java b/bus-core/src/main/java/org/aoju/bus/core/date/formatter/parser/FastDateParser.java index ec82c44563..b5cfe50c21 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/date/formatter/parser/FastDateParser.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/formatter/parser/FastDateParser.java @@ -51,7 +51,7 @@ public class FastDateParser extends AbstractMotd implements PositionDateParser { private static final long serialVersionUID = 1L; - private static final Locale JAPANESE_IMPERIAL = new Locale("ja", "JP", "JP"); + private static final Locale JAPANESE_IMPERIAL = Locale.of("ja", "JP", "JP"); // 用来对正则表达式排序的比较器。('february' 在 'feb'之前)所有条目按区域设置必须是小写的 private static final Comparator LONGER_FIRST_LOWERCASE = Comparator.reverseOrder(); private static final ConcurrentMap[] CACHES = new ConcurrentMap[Calendar.FIELD_COUNT]; diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java b/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java index c501927e16..b7f9a94d4d 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java @@ -215,7 +215,7 @@ public static T invokeSpecial(Object object, Method method, Object... args) * } * * Duck duck = (Duck) Proxy.newProxyInstance( - * ClassLoaderUtil.getClassLoader(), + * ClassKit.getClassLoader(), * new Class[] { Duck.class }, * MethodHandle::invoke); *
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java index ae0ee0faa9..fe0cf47511 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java @@ -2948,33 +2948,33 @@ public static String replaceLast(CharSequence text, CharSequence searchStr, Char /** * 替换字符串中第一个指定字符串 * - * @param str 字符串 - * @param searchStr 被查找的字符串 - * @param replacedStr 被替换的字符串 + * @param text 字符串 + * @param searchText 被查找的字符串 + * @param replacedText 被替换的字符串 * @return 替换后的字符串 */ - public static String replaceFirst(CharSequence str, CharSequence searchStr, CharSequence replacedStr) { - return replaceFirst(str, searchStr, replacedStr, false); + public static String replaceFirst(CharSequence text, CharSequence searchText, CharSequence replacedText) { + return replaceFirst(text, searchText, replacedText, false); } /** * 替换字符串中第一个指定字符串 * - * @param str 字符串 - * @param searchStr 被查找的字符串 - * @param replacedStr 被替换的字符串 - * @param ignoreCase 是否忽略大小写 + * @param text 字符串 + * @param searchText 被查找的字符串 + * @param replacedText 被替换的字符串 + * @param ignoreCase 是否忽略大小写 * @return 替换后的字符串 */ - public static String replaceFirst(CharSequence str, CharSequence searchStr, CharSequence replacedStr, boolean ignoreCase) { - if (isEmpty(str)) { - return toString(str); + public static String replaceFirst(CharSequence text, CharSequence searchText, CharSequence replacedText, boolean ignoreCase) { + if (isEmpty(text)) { + return toString(text); } - int startInclude = indexOf(str, searchStr, 0, ignoreCase); + int startInclude = indexOf(text, searchText, 0, ignoreCase); if (-1 == startInclude) { - return toString(str); + return toString(text); } - return replace(str, startInclude, startInclude + searchStr.length(), replacedStr); + return replace(text, startInclude, startInclude + searchText.length(), replacedText); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java index 3566e956b6..52ed12b92a 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java @@ -144,7 +144,7 @@ public static String getClassName(Object object, boolean isSimple) { /** * 获取类名 * 类名并不包含“.class”这个扩展名 - * 例如:ClassUtil这个类 + * 例如:ClassKit这个类 * *
          * isSimple为false: "org.aoju.core.toolkit.ClassKit"
    diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/EnumKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/EnumKit.java
    index 276e347cbe..2861626257 100755
    --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/EnumKit.java
    +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/EnumKit.java
    @@ -205,7 +205,7 @@ public static List getFieldValues(Class> clazz, String
          * 除用户自定义的字段名,也包括“name”字段,例如:
          *
          * 
    -     *   EnumUtil.getFieldNames(Color.class) == ["name", "index"]
    +     *   EnumKit.getFieldNames(Color.class) == ["name", "index"]
          * 
    * * @param clazz 枚举类 diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ObjectKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ObjectKit.java index 65268c9106..91ad23d981 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ObjectKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ObjectKit.java @@ -1429,8 +1429,8 @@ public static int compare(final byte x, final byte y) { * *
      *
    • 如需对null友好操作如下
    • - *
    • {@code Comparator.nullsLast(CompareUtil.natural())}
    • - *
    • {@code Comparator.nullsFirst(CompareUtil.natural())}
    • + *
    • {@code Comparator.nullsLast(CompareKit.natural())}
    • + *
    • {@code Comparator.nullsFirst(CompareKit.natural())}
    • *
    * * @param 排序节点类型 @@ -1445,8 +1445,8 @@ public static > Comparator natural() { * *
      *
    • 如需对null友好操作如下
    • - *
    • {@code Comparator.nullsLast(CompareUtil.naturalReverse())}
    • - *
    • {@code Comparator.nullsFirst(CompareUtil.naturalReverse())}
    • + *
    • {@code Comparator.nullsLast(CompareKit.naturalReverse())}
    • + *
    • {@code Comparator.nullsFirst(CompareKit.naturalReverse())}
    • *
    * * @param 排序节点类型 @@ -1461,8 +1461,8 @@ public static > Comparator naturalReverse() { * *
      *
    • 如需对null友好操作如下
    • - *
    • {@code Comparator.nullsLast(CompareUtil.reverse())}
    • - *
    • {@code Comparator.nullsFirst(CompareUtil.reverse())}
    • + *
    • {@code Comparator.nullsLast(CompareKit.reverse())}
    • + *
    • {@code Comparator.nullsFirst(CompareKit.reverse())}
    • *
    * * @param 排序节点类型 diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/PhoneKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/PhoneKit.java index 553b15853f..ee012b37f5 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/PhoneKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/PhoneKit.java @@ -97,7 +97,7 @@ public static boolean isPhone400800(CharSequence value) { } /** - * 验证是否为座机号码+手机号码(CharUtil中国)+ 400 + 800电话 + 手机号号码(香港) + * 验证是否为座机号码+手机号码(CharKit中国)+ 400 + 800电话 + 手机号号码(香港) * * @param value 值 * @return 是否为座机号码+手机号码(中国)+手机号码(香港)+手机号码(台湾)+手机号码(澳门) diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ReflectKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ReflectKit.java index 024c50dac1..6cbfdde5e4 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ReflectKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ReflectKit.java @@ -601,7 +601,7 @@ public static void setFieldValue(Object object, String fieldName, Object value) * @param object 对象,如果是static字段,此参数为null * @param field 字段 * @param value 值,当值类型与字段类型不匹配时,会尝试转换 - * @throws InternalException UtilException 包装IllegalAccessException异常 + * @throws InternalException 包装IllegalAccessException异常 */ public static void setFieldValue(Object object, Field field, Object value) throws InternalException { Assert.notNull(field, "Field in [{}] not exist !", object); @@ -1155,8 +1155,8 @@ public static T setAccessible(T accessibleObject) { *
  • {@code getDescriptor(Object.class.getMethod("hashCode")) // "()I"}
  • *
  • {@code getDescriptor(Object.class.getMethod("toString")) // "()Ljava/lang/String;"}
  • *
  • {@code getDescriptor(Object.class.getMethod("equals", Object.class)) // "(Ljava/lang/Object;)Z"}
  • - *
  • {@code getDescriptor(ReflectUtil.class.getDeclaredMethod("appendDescriptor", Class.clas, StringBuilder.class)) // "(Ljava/lang/Class;Ljava/lang/StringBuilder;)V"}
  • - *
  • {@code getDescriptor(ArrayUtil.class.getMethod("isEmpty", Object[].class)) // "([Ljava/lang/Object;)Z"}
  • + *
  • {@code getDescriptor(ReflectKit.class.getDeclaredMethod("appendDescriptor", Class.clas, StringBuilder.class)) // "(Ljava/lang/Class;Ljava/lang/StringBuilder;)V"}
  • + *
  • {@code getDescriptor(ArrayKit.class.getMethod("isEmpty", Object[].class)) // "([Ljava/lang/Object;)Z"}
  • * */ public static String getDescriptor(final Executable executable) { diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/SwingKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/SwingKit.java index 75f171378b..f70760a29a 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/SwingKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/SwingKit.java @@ -89,7 +89,7 @@ public static Rectangle getRectangle() { /** * 设置默认的延迟时间 - * 当按键执行完后的等待时间,也可以用ThreadUtil.sleep方法代替 + * 当按键执行完后的等待时间,也可以用ThreadKit.sleep方法代替 * * @param delayMillis 等待毫秒数 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/XmlKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/XmlKit.java index 762080cc73..14c63264ee 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/XmlKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/XmlKit.java @@ -1144,12 +1144,12 @@ public static String toCase(String xml, boolean type) { int lastIdx = 0; for (Matcher matchr = pattern.matcher(xml); matchr.find(); lastIdx = matchr.end()) { - String str = matchr.group(); + String text = matchr.group(); res.append(xml, lastIdx, matchr.start()); if (type) { - res.append(str.toLowerCase()); + res.append(text.toLowerCase()); } else { - res.append(str.toUpperCase()); + res.append(text.toUpperCase()); } } diff --git a/bus-shade/src/main/java/org/aoju/bus/shade/screw/engine/FreemarkerEngine.java b/bus-shade/src/main/java/org/aoju/bus/shade/screw/engine/FreemarkerEngine.java index f4b20215be..87c8e210b5 100644 --- a/bus-shade/src/main/java/org/aoju/bus/shade/screw/engine/FreemarkerEngine.java +++ b/bus-shade/src/main/java/org/aoju/bus/shade/screw/engine/FreemarkerEngine.java @@ -73,7 +73,7 @@ public class FreemarkerEngine extends AbstractEngine { //编码 configuration.setDefaultEncoding(Charset.DEFAULT_UTF_8); //国际化 - configuration.setLocale(new Locale(Builder.DEFAULT_LOCALE)); + configuration.setLocale(Locale.of(Builder.DEFAULT_LOCALE)); } catch (Exception e) { throw new InternalException(e); } From 6f80207ac609f4243a0739a599c62596366b74e1 Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Thu, 27 Oct 2022 19:39:30 +0800 Subject: [PATCH 09/19] update stream and date kit --- .../java/org/aoju/bus/core/date/Almanac.java | 108 ++++---- .../org/aoju/bus/core/date/StopWatch.java | 11 +- .../org/aoju/bus/core/image/NeuQuant.java | 10 +- .../bus/core/io/buffer/CircularBuffer.java | 239 +++++++++++++++++ .../bus/core/io/copier/ChannelCopier.java | 4 - .../bus/core/io/copier/FileChannelCopier.java | 121 +++++++++ .../aoju/bus/core/io/copier/StreamCopier.java | 4 +- .../io/resource/CharSequenceResource.java | 2 +- .../core/io/resource/MultiFileResource.java | 40 ++- .../bus/core/io/stream/BOMInputStream.java | 19 +- .../aoju/bus/core/io/stream/BOMReader.java | 33 ++- ...utputStream.java => EmptyInputStream.java} | 55 ++-- ...tputStream.java => EmptyOutputStream.java} | 45 ++-- .../core/io/stream/FastByteOutputStream.java | 51 ++-- .../bus/core/io/stream/ObjectInputStream.java | 125 +++++++++ .../bus/core/io/stream/QueueInputStream.java | 230 ----------------- .../aoju/bus/core/io/stream/QueueReader.java | 242 ------------------ .../core/io/stream/RandomFileInputStream.java | 79 ------ .../aoju/bus/core/io/stream/StreamBuffer.java | 132 ---------- .../aoju/bus/core/io/stream/StreamReader.java | 204 +++++++++++++++ .../aoju/bus/core/io/stream/StreamWriter.java | 142 ++++++++++ .../bus/core/io/stream/StringInputStream.java | 43 ++-- .../core/io/stream/StringOutputStream.java | 93 ------- .../aoju/bus/core/io/stream/StringReader.java | 63 ----- .../aoju/bus/core/io/stream/StringWriter.java | 62 ----- .../bus/core/io/stream/SyncInputStream.java | 134 ++++++++++ .../bus/core/io/stream/VoidInputStream.java | 42 --- .../bus/core/io/stream/VoidOutputStream.java | 41 --- .../aoju/bus/core/io/stream/package-info.java | 2 +- .../org/aoju/bus/core/toolkit/BloomKit.java | 61 ----- .../org/aoju/bus/core/toolkit/ClassKit.java | 27 +- .../java/org/aoju/bus/core/toolkit/IoKit.java | 134 ++++------ .../org/aoju/bus/core/toolkit/ReflectKit.java | 10 + 33 files changed, 1302 insertions(+), 1306 deletions(-) create mode 100755 bus-core/src/main/java/org/aoju/bus/core/io/buffer/CircularBuffer.java create mode 100755 bus-core/src/main/java/org/aoju/bus/core/io/copier/FileChannelCopier.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMInputStream.java rename bus-core/src/main/java/org/aoju/bus/core/io/stream/{RandomFileOutputStream.java => EmptyInputStream.java} (74%) mode change 100644 => 100755 rename bus-core/src/main/java/org/aoju/bus/core/io/stream/{NullOutputStream.java => EmptyOutputStream.java} (78%) mode change 100755 => 100644 mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/FastByteOutputStream.java create mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/ObjectInputStream.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueInputStream.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueReader.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileInputStream.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamBuffer.java create mode 100755 bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamReader.java create mode 100755 bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamWriter.java mode change 100644 => 100755 bus-core/src/main/java/org/aoju/bus/core/io/stream/StringInputStream.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/StringOutputStream.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/StringReader.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/StringWriter.java create mode 100755 bus-core/src/main/java/org/aoju/bus/core/io/stream/SyncInputStream.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/VoidInputStream.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/stream/VoidOutputStream.java mode change 100644 => 100755 bus-core/src/main/java/org/aoju/bus/core/io/stream/package-info.java delete mode 100644 bus-core/src/main/java/org/aoju/bus/core/toolkit/BloomKit.java diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java b/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java index 388fe028a3..b5f78c89d1 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java @@ -5126,60 +5126,6 @@ public static int getEndValue(Calendar calendar, int dateField) { return calendar.getActualMaximum(dateField); } - /** - * 转换为{@link DateTime}对象 - * - * @return 当前时间 - */ - public static DateTime date() { - return new DateTime(); - } - - /** - * {@link Date}类型时间转为{@link DateTime} - * - * @param date Long类型Date(Unix时间戳) - * @return 时间对象 - */ - public static DateTime date(Date date) { - if (date instanceof DateTime) { - return (DateTime) date; - } - return new DateTime(date); - } - - /** - * Long类型时间转为{@link DateTime} - * 只支持毫秒级别时间戳,如果需要秒级别时间戳,请自行×1000L - * - * @param date Long类型Date(Unix时间戳) - * @return 时间对象 - */ - public static DateTime date(long date) { - return new DateTime(date); - } - - /** - * {@link Calendar}类型时间转为{@link DateTime} - * - * @param calendar {@link Calendar} - * @return 时间对象 - */ - public static DateTime date(Calendar calendar) { - return new DateTime(calendar); - } - - /** - * {@link TemporalAccessor}类型时间转为{@link DateTime} - * 始终根据已有{@link TemporalAccessor} 产生新的{@link DateTime}对象 - * - * @param temporalAccessor {@link TemporalAccessor} - * @return 时间对象 - */ - public static DateTime date(TemporalAccessor temporalAccessor) { - return new DateTime(temporalAccessor); - } - /** * 修改日期为某个时间字段结束时间 * @@ -5572,6 +5518,60 @@ public static int getLastDayOfMonth(final Date date) { return date(date).getLastDayOfMonth(); } + /** + * 转换为{@link DateTime}对象 + * + * @return 当前时间 + */ + public static DateTime date() { + return new DateTime(); + } + + /** + * {@link Date}类型时间转为{@link DateTime} + * + * @param date Long类型Date(Unix时间戳) + * @return 时间对象 + */ + public static DateTime date(Date date) { + if (date instanceof DateTime) { + return (DateTime) date; + } + return new DateTime(date); + } + + /** + * Long类型时间转为{@link DateTime} + * 只支持毫秒级别时间戳,如果需要秒级别时间戳,请自行×1000L + * + * @param date Long类型Date(Unix时间戳) + * @return 时间对象 + */ + public static DateTime date(long date) { + return new DateTime(date); + } + + /** + * {@link Calendar}类型时间转为{@link DateTime} + * + * @param calendar {@link Calendar} + * @return 时间对象 + */ + public static DateTime date(Calendar calendar) { + return new DateTime(calendar); + } + + /** + * {@link TemporalAccessor}类型时间转为{@link DateTime} + * 始终根据已有{@link TemporalAccessor} 产生新的{@link DateTime}对象 + * + * @param temporalAccessor {@link TemporalAccessor} + * @return 时间对象 + */ + public static DateTime date(TemporalAccessor temporalAccessor) { + return new DateTime(temporalAccessor); + } + /** * 当前时间,格式 yyyy-MM-dd HH:mm:ss * diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/StopWatch.java b/bus-core/src/main/java/org/aoju/bus/core/date/StopWatch.java index 7adbb66ca9..01e1ef2af7 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/date/StopWatch.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/StopWatch.java @@ -120,13 +120,22 @@ public StopWatch(String id, boolean keepTaskList) { } } + /** + * 创建计时任务(秒表) + * + * @return this + */ + public static StopWatch of() { + return new StopWatch(); + } + /** * 创建计时任务(秒表) * * @param id 用于标识秒表的唯一ID * @return this */ - public static StopWatch create(String id) { + public static StopWatch of(final String id) { return new StopWatch(id); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/image/NeuQuant.java b/bus-core/src/main/java/org/aoju/bus/core/image/NeuQuant.java index 753b57a6bf..27791f9908 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/image/NeuQuant.java +++ b/bus-core/src/main/java/org/aoju/bus/core/image/NeuQuant.java @@ -69,12 +69,16 @@ write output image using inxsearch(b,g,r) */ /* defs for freq and bias */ protected static final int INTBIASSHIFT = Normal._16; /* bias for fractions */ protected static final int INTBIAS = (1 << INTBIASSHIFT); - protected static final int BETA = (INTBIAS >> BETASHIFT); /* beta = 1/1024 */ - protected static final int BETAGAMMA = - (INTBIAS << (GAMMASHIFT - BETASHIFT)); + protected static final int BETASHIFT = 10; + protected static final int GAMMASHIFT = 10; /* gamma = 1024 */ + protected static final int GAMMA = (1 << GAMMASHIFT); + protected static final int BETA = (INTBIAS >> BETASHIFT); /* beta = 1/1024 */ + protected static final int BETAGAMMA = + (INTBIAS << (GAMMASHIFT - BETASHIFT)); + /* defs for decreasing radius factor */ protected static final int INITRAD = (NETSIZE >> 3); /* for 256 cols, radius starts */ protected static final int RADIUSBIASSHIFT = 6; /* at 32.0 biased by 6 bits */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/CircularBuffer.java b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/CircularBuffer.java new file mode 100755 index 0000000000..bf821f11e1 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/CircularBuffer.java @@ -0,0 +1,239 @@ +package org.aoju.bus.core.io.buffer; + +import java.util.Objects; + +/** + * 循环缓冲区 + */ +public class CircularBuffer { + + private final byte[] buffer; + private int startOffset; + private int endOffset; + private int currentNumberOfBytes; + + /** + * 默认缓冲大小的构造 + */ + public CircularBuffer() { + this(2 << 12); + } + + /** + * 构造 + * + * @param pSize 缓冲大小 + */ + public CircularBuffer(final int pSize) { + buffer = new byte[pSize]; + startOffset = 0; + endOffset = 0; + currentNumberOfBytes = 0; + } + + /** + * 从buffer中读取下一个byte,同时移除这个bytes。 + * + * @return The byte + * @throws IllegalStateException buffer为空抛出,使用{@link #hasBytes()},或 {@link #getCurrentNumberOfBytes()}判断 + */ + public byte read() { + if (currentNumberOfBytes <= 0) { + throw new IllegalStateException("No bytes available."); + } + final byte b = buffer[startOffset]; + --currentNumberOfBytes; + if (++startOffset == buffer.length) { + startOffset = 0; + } + return b; + } + + /** + * Returns the given number of bytes from the buffer by storing them in + * the given byte array at the given offset. + * 从buffer中获取指定长度的bytes,从给定的targetBuffer的targetOffset位置写出 + * + * @param targetBuffer 目标bytes + * @param targetOffset 目标数组开始位置 + * @param length 读取长度 + * @throws NullPointerException 提供的数组为{@code null} + * @throws IllegalArgumentException {@code targetOffset}或{@code length} 为负数或{@code targetBuffer}太小 + * @throws IllegalStateException buffer中的byte不足,使用{@link #getCurrentNumberOfBytes()}判断。 + */ + public void read(final byte[] targetBuffer, final int targetOffset, final int length) { + Objects.requireNonNull(targetBuffer); + if (targetOffset < 0 || targetOffset >= targetBuffer.length) { + throw new IllegalArgumentException("Invalid offset: " + targetOffset); + } + if (length < 0 || length > buffer.length) { + throw new IllegalArgumentException("Invalid length: " + length); + } + if (targetOffset + length > targetBuffer.length) { + throw new IllegalArgumentException("The supplied byte array contains only " + + targetBuffer.length + " bytes, but offset, and length would require " + + (targetOffset + length - 1)); + } + if (currentNumberOfBytes < length) { + throw new IllegalStateException("Currently, there are only " + currentNumberOfBytes + + "in the buffer, not " + length); + } + int offset = targetOffset; + for (int i = 0; i < length; i++) { + targetBuffer[offset++] = buffer[startOffset]; + --currentNumberOfBytes; + if (++startOffset == buffer.length) { + startOffset = 0; + } + } + } + + /** + * 增加byte到buffer中 + * + * @param value The byte + * @throws IllegalStateException buffer已满. 用{@link #hasSpace()}或{@link #getSpace()}判断。 + */ + public void add(final byte value) { + if (currentNumberOfBytes >= buffer.length) { + throw new IllegalStateException("No space available"); + } + buffer[endOffset] = value; + ++currentNumberOfBytes; + if (++endOffset == buffer.length) { + endOffset = 0; + } + } + + /** + * Returns, whether the next bytes in the buffer are exactly those, given by + * {@code sourceBuffer}, {@code offset}, and {@code length}. No bytes are being + * removed from the buffer. If the result is true, then the following invocations + * of {@link #read()} are guaranteed to return exactly those bytes. + * + * @param sourceBuffer the buffer to compare against + * @param offset start offset + * @param length length to compare + * @return True, if the next invocations of {@link #read()} will return the + * bytes at offsets {@code pOffset}+0, {@code pOffset}+1, ..., + * {@code pOffset}+{@code pLength}-1 of byte array {@code pBuffer}. + * @throws IllegalArgumentException Either of {@code pOffset}, or {@code pLength} is negative. + * @throws NullPointerException The byte array {@code pBuffer} is null. + */ + public boolean peek(final byte[] sourceBuffer, final int offset, final int length) { + Objects.requireNonNull(sourceBuffer, "Buffer"); + if (offset < 0 || offset >= sourceBuffer.length) { + throw new IllegalArgumentException("Invalid offset: " + offset); + } + if (length < 0 || length > buffer.length) { + throw new IllegalArgumentException("Invalid length: " + length); + } + if (length < currentNumberOfBytes) { + return false; + } + int localOffset = startOffset; + for (int i = 0; i < length; i++) { + if (buffer[localOffset] != sourceBuffer[i + offset]) { + return false; + } + if (++localOffset == buffer.length) { + localOffset = 0; + } + } + return true; + } + + /** + * Adds the given bytes to the buffer. This is the same as invoking {@link #add(byte)} + * for the bytes at offsets {@code offset+0}, {@code offset+1}, ..., + * {@code offset+length-1} of byte array {@code targetBuffer}. + * + * @param targetBuffer the buffer to copy + * @param offset start offset + * @param length length to copy + * @throws IllegalStateException The buffer doesn't have sufficient space. Use + * {@link #getSpace()} to prevent this exception. + * @throws IllegalArgumentException Either of {@code pOffset}, or {@code pLength} is negative. + * @throws NullPointerException The byte array {@code pBuffer} is null. + */ + public void add(final byte[] targetBuffer, final int offset, final int length) { + Objects.requireNonNull(targetBuffer, "Buffer"); + if (offset < 0 || offset >= targetBuffer.length) { + throw new IllegalArgumentException("Invalid offset: " + offset); + } + if (length < 0) { + throw new IllegalArgumentException("Invalid length: " + length); + } + if (currentNumberOfBytes + length > buffer.length) { + throw new IllegalStateException("No space available"); + } + for (int i = 0; i < length; i++) { + buffer[endOffset] = targetBuffer[offset + i]; + if (++endOffset == buffer.length) { + endOffset = 0; + } + } + currentNumberOfBytes += length; + } + + /** + * Returns, whether there is currently room for a single byte in the buffer. + * Same as {@link #hasSpace(int) hasSpace(1)}. + * + * @return true if there is space for a byte + * @see #hasSpace(int) + * @see #getSpace() + */ + public boolean hasSpace() { + return currentNumberOfBytes < buffer.length; + } + + /** + * Returns, whether there is currently room for the given number of bytes in the buffer. + * + * @param count the byte count + * @return true if there is space for the given number of bytes + * @see #hasSpace() + * @see #getSpace() + */ + public boolean hasSpace(final int count) { + return currentNumberOfBytes + count <= buffer.length; + } + + /** + * Returns, whether the buffer is currently holding, at least, a single byte. + * + * @return true if the buffer is not empty + */ + public boolean hasBytes() { + return currentNumberOfBytes > 0; + } + + /** + * Returns the number of bytes, that can currently be added to the buffer. + * + * @return the number of bytes that can be added + */ + public int getSpace() { + return buffer.length - currentNumberOfBytes; + } + + /** + * Returns the number of bytes, that are currently present in the buffer. + * + * @return the number of bytes + */ + public int getCurrentNumberOfBytes() { + return currentNumberOfBytes; + } + + /** + * Removes all bytes from the buffer. + */ + public void clear() { + startOffset = 0; + endOffset = 0; + currentNumberOfBytes = 0; + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java b/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java index 83f22d8a09..98bd8f1407 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java @@ -27,7 +27,6 @@ import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.io.Progress; -import org.aoju.bus.core.lang.Assert; import org.aoju.bus.core.toolkit.IoKit; import java.io.IOException; @@ -84,9 +83,6 @@ public ChannelCopier(int bufferSize, long count, Progress progress) { @Override public long copy(ReadableByteChannel source, WritableByteChannel target) { - Assert.notNull(source, "InputStream is null !"); - Assert.notNull(target, "OutputStream is null !"); - final Progress progress = this.progress; if (null != progress) { progress.start(); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/copier/FileChannelCopier.java b/bus-core/src/main/java/org/aoju/bus/core/io/copier/FileChannelCopier.java new file mode 100755 index 0000000000..2495558a8c --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/copier/FileChannelCopier.java @@ -0,0 +1,121 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.copier; + +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.toolkit.IoKit; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; + +/** + * {@link FileChannel} 数据拷贝封装 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class FileChannelCopier extends IoCopier { + + /** + * 构造 + * + * @param count 拷贝总数,-1表示无限制 + */ + public FileChannelCopier(final long count) { + super(-1, count, null); + } + + /** + * 拷贝文件流,使用NIO + * + * @param in 输入 + * @param out 输出 + * @return 拷贝的字节数 + */ + public long copy(final FileInputStream in, final FileOutputStream out) { + FileChannel inChannel = null; + FileChannel outChannel = null; + try { + inChannel = in.getChannel(); + outChannel = out.getChannel(); + return copy(inChannel, outChannel); + } finally { + IoKit.close(outChannel); + IoKit.close(inChannel); + } + } + + @Override + public long copy(final FileChannel source, final FileChannel target) { + try { + return doCopySafely(source, target); + } catch (final IOException e) { + throw new InternalException(e); + } + } + + /** + * 文件拷贝实现 + * + *
    +     * FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
    +     * 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
    +     * 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes,不处理将导致文件内容缺失
    +     *
    +     * // 错误写法,dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
    +     * try (InputStream srcStream = zipFile.getInputStream(zipEntry);
    +     * 		ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
    +     * 		FileOutputStream fos = new FileOutputStream(saveFile);
    +     * 		FileChannel dstChannel = fos.getChannel()) {
    +     * 		dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
    +     *  }
    +     * 
    + * + * @param inChannel 输入通道 + * @param outChannel 输出通道 + * @return 输入通道的字节数 + * @throws IOException 发生IO错误 + */ + private long doCopySafely(final FileChannel inChannel, final FileChannel outChannel) throws IOException { + long totalBytes = inChannel.size(); + if (this.count > 0 && this.count < totalBytes) { + // 限制拷贝总数 + totalBytes = count; + } + // 确保文件内容不会缺失 + for (long pos = 0, remaining = totalBytes; remaining > 0; ) { + + // 实际传输的字节数 + final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); + pos += writeBytes; + remaining -= writeBytes; + } + return totalBytes; + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/copier/StreamCopier.java b/bus-core/src/main/java/org/aoju/bus/core/io/copier/StreamCopier.java index b9f67b0fad..70fa977d35 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/io/copier/StreamCopier.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/copier/StreamCopier.java @@ -62,7 +62,7 @@ public StreamCopier(int bufferSize) { * 构造 * * @param bufferSize 缓存大小 - * @param count 拷贝总数 + * @param count 拷贝总数,-1表示无限制 */ public StreamCopier(int bufferSize, long count) { this(bufferSize, count, null); @@ -72,7 +72,7 @@ public StreamCopier(int bufferSize, long count) { * 构造 * * @param bufferSize 缓存大小 - * @param count 拷贝总数 + * @param count 拷贝总数,-1表示无限制 * @param progress 进度条 */ public StreamCopier(int bufferSize, long count, Progress progress) { diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/resource/CharSequenceResource.java b/bus-core/src/main/java/org/aoju/bus/core/io/resource/CharSequenceResource.java index 3bb481d579..b667d687cd 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/resource/CharSequenceResource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/resource/CharSequenceResource.java @@ -107,7 +107,7 @@ public String readString(Charset charset) throws InternalException { @Override public byte[] readBytes() throws InternalException { - return this.data.toString().getBytes(this.charset); + return StringKit.bytes(this.data, this.charset); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/resource/MultiFileResource.java b/bus-core/src/main/java/org/aoju/bus/core/io/resource/MultiFileResource.java index 902c51908e..8b8b5eb732 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/io/resource/MultiFileResource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/resource/MultiFileResource.java @@ -26,6 +26,7 @@ package org.aoju.bus.core.io.resource; import java.io.File; +import java.nio.file.Path; import java.util.Collection; /** @@ -40,28 +41,38 @@ public class MultiFileResource extends MultiResource { /** * 构造 * - * @param files 集合 + * @param file 文件 */ - public MultiFileResource(Collection files) { + public MultiFileResource(final File... file) { + add(file); + } + + /** + * 构造 + * + * @param files 文件资源列表 + */ + public MultiFileResource(final Path... files) { add(files); } /** * 构造 * - * @param file 文件 + * @param files 集合 */ - public MultiFileResource(File... file) { - add(file); + public MultiFileResource(final Collection files) { + add(files); } + /** * 增加文件资源 * * @param file 文件资源 * @return this */ - public MultiFileResource add(File... file) { + public MultiFileResource add(final File... file) { for (File f : file) { this.add(new FileResource(f)); } @@ -74,7 +85,20 @@ public MultiFileResource add(File... file) { * @param files 文件资源 * @return this */ - public MultiFileResource add(Collection files) { + public MultiFileResource add(final Path... files) { + for (final Path file : files) { + this.add(new FileResource(file)); + } + return this; + } + + /** + * 增加文件资源 + * + * @param files 文件资源 + * @return this + */ + public MultiFileResource add(final Collection files) { for (File file : files) { this.add(new FileResource(file)); } @@ -82,7 +106,7 @@ public MultiFileResource add(Collection files) { } @Override - public MultiFileResource add(Resource resource) { + public MultiFileResource add(final Resource resource) { return (MultiFileResource) super.add(resource); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMInputStream.java old mode 100755 new mode 100644 index 752e601402..4098636805 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMInputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMInputStream.java @@ -55,17 +55,17 @@ public class BOMInputStream extends InputStream { private static final int BOM_SIZE = 4; - PushbackInputStream in; - boolean isInited = false; - String defaultCharset; - String charset; + private final PushbackInputStream in; + private final String defaultCharset; + private boolean isInited = false; + private String charset; /** * 构造 * * @param in 流 */ - public BOMInputStream(InputStream in) { + public BOMInputStream(final InputStream in) { this(in, Charset.DEFAULT_UTF_8); } @@ -75,7 +75,7 @@ public BOMInputStream(InputStream in) { * @param in 流 * @param defaultCharset 默认编码 */ - public BOMInputStream(InputStream in, String defaultCharset) { + public BOMInputStream(final InputStream in, final String defaultCharset) { this.in = new PushbackInputStream(in, BOM_SIZE); this.defaultCharset = defaultCharset; } @@ -98,7 +98,7 @@ public String getCharset() { if (false == isInited) { try { init(); - } catch (IOException ex) { + } catch (final IOException ex) { throw new InternalException(ex); } } @@ -128,8 +128,9 @@ protected void init() throws IOException { return; } - byte[] bom = new byte[BOM_SIZE]; - int n, unread; + final byte[] bom = new byte[BOM_SIZE]; + final int n; + final int unread; n = in.read(bom, 0, bom.length); if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE) && (bom[3] == (byte) 0xFF)) { diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java index 856005003d..ace4154714 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java @@ -1,3 +1,28 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ package org.aoju.bus.core.io.stream; import org.aoju.bus.core.lang.Assert; @@ -33,18 +58,18 @@ public class BOMReader extends Reader { * * @param in 流 */ - public BOMReader(InputStream in) { + public BOMReader(final InputStream in) { Assert.notNull(in, "InputStream must be not null!"); final BOMInputStream bin = (in instanceof BOMInputStream) ? (BOMInputStream) in : new BOMInputStream(in); try { this.reader = new InputStreamReader(bin, bin.getCharset()); - } catch (UnsupportedEncodingException ignore) { + } catch (final UnsupportedEncodingException ignore) { } } @Override - public int read(char[] cbuf, int off, int len) throws IOException { - return reader.read(cbuf, off, len); + public int read(final char[] buffer, final int off, final int len) throws IOException { + return reader.read(buffer, off, len); } @Override diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileOutputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyInputStream.java old mode 100644 new mode 100755 similarity index 74% rename from bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileOutputStream.java rename to bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyInputStream.java index 5afd632114..f203086ff3 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileOutputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyInputStream.java @@ -25,44 +25,65 @@ ********************************************************************************/ package org.aoju.bus.core.io.stream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; +import java.io.InputStream; /** + * 空的流 + * * @author Kimi Liu * @since Java 17+ */ -public class RandomFileOutputStream extends OutputStream { +public final class EmptyInputStream extends InputStream { - private final RandomAccessFile raf; + /** + * 单例实例 + */ + public static final EmptyInputStream INSTANCE = new EmptyInputStream(); - public RandomFileOutputStream(RandomAccessFile raf) { - this.raf = raf; + private EmptyInputStream() { + + } + + @Override + public int available() { + return 0; + } + + @Override + public void close() { + } + + @Override + public void mark(final int readLimit) { + } + + @Override + public boolean markSupported() { + return true; } @Override - public void write(int b) throws IOException { - raf.write(b); + public int read() { + return -1; } @Override - public void write(byte[] b) throws IOException { - raf.write(b); + public int read(final byte[] buf) { + return -1; } @Override - public void write(byte[] b, int off, int len) throws IOException { - raf.write(b, off, len); + public int read(final byte[] buf, final int off, final int len) { + return -1; } @Override - public void flush() { + public void reset() { } @Override - public void close() throws IOException { - raf.close(); + public long skip(final long n) { + return 0L; } -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/NullOutputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyOutputStream.java old mode 100755 new mode 100644 similarity index 78% rename from bus-core/src/main/java/org/aoju/bus/core/io/stream/NullOutputStream.java rename to bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyOutputStream.java index a7752f931a..8f42a25aa4 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/NullOutputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyOutputStream.java @@ -35,49 +35,48 @@ * @author Kimi Liu * @since Java 17+ */ -public class NullOutputStream extends OutputStream { +public class EmptyOutputStream extends OutputStream { - private boolean closed = false; + /** + * 单例 + */ + public static final EmptyOutputStream INSTANCE = new EmptyOutputStream(); + + private EmptyOutputStream() { + + } /** - * 什么也不做,写出到 /dev/null. + * 什么也不做,写出到{@code /dev/null} * - * @param b 写出的数据 + * @param b 写出的数据 + * @param off 开始位置 + * @param len 长度 */ @Override - public void write(int b) throws IOException { - if (this.closed) _throwClosed(); + public void write(final byte[] b, final int off, final int len) { + } /** - * 什么也不做,写出到 /dev/null. + * 什么也不做,写出到 {@code /dev/null} * * @param b 写出的数据 - * @throws IOException 不抛出 */ @Override - public void write(byte[] b) throws IOException { - if (this.closed) _throwClosed(); + public void write(final int b) { + } /** - * 什么也不做,写出到/dev/null. + * 什么也不做,写出到 {@code /dev/null} * - * @param b 写出的数据 - * @param off 开始位置 - * @param len 长度 + * @param b 写出的数据 + * @throws IOException 不抛出 */ @Override - public void write(byte[] b, int off, int len) throws IOException { - if (this.closed) _throwClosed(); - } - - private void _throwClosed() throws IOException { - throw new IOException("This OutputStream has been closed"); - } + public void write(final byte[] b) throws IOException { - public void close() { - this.closed = true; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/FastByteOutputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/FastByteOutputStream.java old mode 100755 new mode 100644 index 1fadcb53ea..03bcfccae6 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/FastByteOutputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/FastByteOutputStream.java @@ -27,17 +27,20 @@ import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.io.buffer.FastByteBuffer; -import org.aoju.bus.core.lang.Charset; -import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.toolkit.ObjectKit; import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.Charset; /** - * 基于快速缓冲FastByteBuffer的OutputStream,自动扩充缓冲区 + * 基于快速缓冲FastByteBuffer的OutputStream,随着数据的增长自动扩充缓冲区 + *

    * 可以通过{@link #toByteArray()}和 {@link #toString()}来获取数据 - * 避免重新分配内存块而是分配新增的缓冲区,缓冲区不会被GC,数据也不会被拷贝到其他缓冲区 + *

    + * {@link #close()}方法无任何效果,当流被关闭后不会抛出IOException + *

    + * 这种设计避免重新分配内存块而是分配新增的缓冲区,缓冲区不会被GC,数据也不会被拷贝到其他缓冲区。 * * @author Kimi Liu * @since Java 17+ @@ -46,8 +49,11 @@ public class FastByteOutputStream extends OutputStream { private final FastByteBuffer buffer; + /** + * 构造 + */ public FastByteOutputStream() { - this(Normal._1024); + this(1024); } /** @@ -55,17 +61,17 @@ public FastByteOutputStream() { * * @param size 预估大小 */ - public FastByteOutputStream(int size) { + public FastByteOutputStream(final int size) { buffer = new FastByteBuffer(size); } @Override - public void write(byte[] b, int off, int len) { + public void write(final byte[] b, final int off, final int len) { buffer.append(b, off, len); } @Override - public void write(int b) { + public void write(final int b) { buffer.append((byte) b); } @@ -74,10 +80,10 @@ public int size() { } /** - * 此方法无任何效果,当流被关闭后不会抛出IOException + * 此方法无任何效果,当流被关闭后不会抛出IOException */ @Override - public void close() throws IOException { + public void close() { // nop } @@ -91,9 +97,10 @@ public void reset() { * @param out 输出流 * @throws InternalException IO异常 */ - public void writeTo(OutputStream out) throws InternalException { + public void writeTo(final OutputStream out) throws InternalException { final int index = buffer.index(); if (index < 0) { + // 无数据写出 return; } byte[] buf; @@ -103,11 +110,12 @@ public void writeTo(OutputStream out) throws InternalException { out.write(buf); } out.write(buffer.array(index), 0, buffer.offset()); - } catch (IOException e) { + } catch (final IOException e) { throw new InternalException(e); } } + /** * 转为Byte数组 * @@ -119,27 +127,18 @@ public byte[] toByteArray() { @Override public String toString() { - return toString(Charset.defaultCharset()); - } - - /** - * 转为字符串 - * - * @param charsetName 编码 - * @return 字符串 - */ - public String toString(String charsetName) { - return toString(Charset.charset(charsetName)); + return toString(org.aoju.bus.core.lang.Charset.defaultCharset()); } /** * 转为字符串 * - * @param charset 编码 + * @param charset 编码,null表示默认编码 * @return 字符串 */ - public String toString(java.nio.charset.Charset charset) { - return new String(toByteArray(), ObjectKit.defaultIfNull(charset, Charset::defaultCharset)); + public String toString(final Charset charset) { + return new String(toByteArray(), + ObjectKit.defaultIfNull(charset, Charset::defaultCharset)); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/ObjectInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/ObjectInputStream.java new file mode 100644 index 0000000000..321b18bf2b --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/ObjectInputStream.java @@ -0,0 +1,125 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.stream; + +import org.aoju.bus.core.toolkit.CollKit; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InvalidClassException; +import java.io.ObjectStreamClass; +import java.util.HashSet; +import java.util.Set; + +/** + * 带有类验证的对象流,用于避免反序列化漏洞 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class ObjectInputStream extends java.io.ObjectInputStream { + + private Set whiteClassSet; + private Set blackClassSet; + + /** + * 构造 + * + * @param inputStream 流 + * @param acceptClasses 白名单的类 + * @throws IOException IO异常 + */ + public ObjectInputStream(final InputStream inputStream, final Class... acceptClasses) throws IOException { + super(inputStream); + accept(acceptClasses); + } + + /** + * 禁止反序列化的类,用于反序列化验证 + * + * @param refuseClasses 禁止反序列化的类 + */ + public void refuse(final Class... refuseClasses) { + if (null == this.blackClassSet) { + this.blackClassSet = new HashSet<>(); + } + for (final Class acceptClass : refuseClasses) { + this.blackClassSet.add(acceptClass.getName()); + } + } + + /** + * 接受反序列化的类,用于反序列化验证 + * + * @param acceptClasses 接受反序列化的类 + */ + public void accept(final Class... acceptClasses) { + if (null == this.whiteClassSet) { + this.whiteClassSet = new HashSet<>(); + } + for (final Class acceptClass : acceptClasses) { + this.whiteClassSet.add(acceptClass.getName()); + } + } + + /** + * 只允许反序列化SerialObject class + */ + @Override + protected Class resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException { + validateClassName(desc.getName()); + return super.resolveClass(desc); + } + + /** + * 验证反序列化的类是否合法 + * + * @param className 类名 + * @throws InvalidClassException 非法类 + */ + private void validateClassName(final String className) throws InvalidClassException { + // 黑名单 + if (CollKit.isNotEmpty(this.blackClassSet)) { + if (this.blackClassSet.contains(className)) { + throw new InvalidClassException("Unauthorized deserialization attempt by black list", className); + } + } + + if (CollKit.isEmpty(this.whiteClassSet)) { + return; + } + if (className.startsWith("java.")) { + // java中的类默认在白名单中 + return; + } + if (this.whiteClassSet.contains(className)) { + return; + } + + throw new InvalidClassException("Unauthorized deserialization attempt", className); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueInputStream.java deleted file mode 100644 index 32f448a6b0..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueInputStream.java +++ /dev/null @@ -1,230 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import org.aoju.bus.core.lang.Symbol; - -import java.io.IOException; -import java.io.InputStream; -import java.util.LinkedList; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class QueueInputStream extends InputStream { - - //原始流 - private final InputStream is; - //缓存 - private final LinkedList cache = new LinkedList<>(); - //peek索引 - private int peekindex = 0; - //是否到流尾 - private boolean end = false; - //列 - private int col = 0; - //行 - private int row = 1; - - public QueueInputStream(InputStream is) { - this.is = is; - } - - public int read() throws IOException { - return poll(); - } - - /** - * 读取一项数据 - * - * @param ends 结束符, 默认' ', '\r', '\n' - * @return 数据 - * @throws IOException 异常 - */ - public String readItem(char... ends) throws IOException { - StringBuilder sb = new StringBuilder(); - while (true) { - switch (peek()) { - case Symbol.C_SPACE: - case Symbol.C_CR: - case Symbol.C_LF: - case -1: - return sb.toString(); - default: - for (Character c : ends) { - if (c.charValue() == peek()) { - return sb.toString(); - } - } - sb.append((char) poll()); - } - } - } - - /** - * 读取一行 - * - * @return 一行数据 - * @throws IOException 异常 - */ - public String readLine() throws IOException { - StringBuilder sb = new StringBuilder(); - for (; ; ) { - int v = peek(); - if (v == Symbol.C_CR || v == Symbol.C_LF) { - poll(); - v = peekNext(); - if (v == Symbol.C_CR || v == Symbol.C_LF) { - poll(); - } - break; - } - sb.append((char) poll()); - } - return sb.toString(); - } - - /** - * 读取头部字节, 并删除 - * - * @return 头部字节 - * @throws IOException 异常 - */ - public int poll() throws IOException { - peekindex = 0; - int v = -1; - if (cache.size() <= 0) { - v = is.read(); - } else { - v = cache.poll(); - } - if (v == -1) { - end = true; - } - if (v == Symbol.C_LF) { - col = 0; - row++; - } else { - col++; - } - return v; - } - - /** - * 访问头部开始第几个字节, 不删除 - * - * @param index 索引 - * @return 头部的第N个字节 - * @throws IOException 异常 - */ - public int peek(int index) throws IOException { - while (cache.size() <= index) { - cache.add(is.read()); - } - return cache.get(index); - } - - /** - * 访问上次peekNext访问的下个位置的字节, 未访问过则访问索引0, poll, peek后归零, 不删除 - * - * @return 下一个位置的字节 - * @throws IOException 异常 - */ - public int peekNext() throws IOException { - return peek(peekindex++); - } - - /** - * 访问头部字节, 不删除 - * - * @return 头部字节 - * @throws IOException 异常 - */ - public int peek() throws IOException { - peekindex = 0; - int v = peek(peekindex++); - if (v == -1) { - end = true; - } - return v; - } - - /** - * 跳过和丢弃输入流中的数据 - */ - public long skip(long n) throws IOException { - int s = cache.size(); - if (s > 0) { - if (s < n) { - n = n - s; - } else { - for (int i = 0; i < n; i++) { - cache.poll(); - } - return n; - } - } - return super.skip(n) + s; - } - - /** - * 是否结束 - * - * @return true 如果已经结束 - */ - public boolean isEnd() { - return end; - } - - - /** - * 是否以 start 开始 - * - * @param start 开始位置 - * @return true, 如果的确以指定字符串开始 - * @throws IOException 异常 - */ - public boolean startWith(String start) throws IOException { - char[] cs = start.toCharArray(); - int i = 0; - for (; i < cs.length; i++) { - if (peek(i) != cs[i]) { - return false; - } - } - return true; - } - - public int getCol() { - return col; - } - - public int getRow() { - return row; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueReader.java deleted file mode 100644 index b99d6a86ea..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueReader.java +++ /dev/null @@ -1,242 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import org.aoju.bus.core.lang.Symbol; - -import java.io.IOException; -import java.io.Reader; -import java.util.LinkedList; - -/** - * 队列InputStream - * - * @author Kimi Liu - * @since Java 17+ - */ -public class QueueReader extends Reader { - - //原始流 - private final Reader is; - //缓存 - private final LinkedList cache = new LinkedList<>(); - //peek索引 - private int peekindex = 0; - //是否到流尾 - private boolean end = false; - //列 - private int col = 0; - //行 - private int row = 1; - - public QueueReader(Reader is) { - this.is = is; - } - - /** - * 读取一项数据 - * - * @param ends 结束符, 默认' ', '\r', '\n' - * @return 数据 - * @throws IOException 异常 - */ - public String readItem(char... ends) throws IOException { - StringBuilder sb = new StringBuilder(); - while (true) { - switch (peek()) { - case Symbol.C_SPACE: - case Symbol.C_CR: - case Symbol.C_LF: - case -1: - return sb.toString(); - default: - for (Character c : ends) { - if (c.charValue() == peek()) { - return sb.toString(); - } - } - sb.append((char) poll()); - } - } - } - - /** - * 读取一行 - * - * @return 一行数据 - * @throws IOException 异常 - */ - public String readLine() throws IOException { - StringBuilder sb = new StringBuilder(); - for (; ; ) { - int v = peek(); - if (v == Symbol.C_CR || v == Symbol.C_LF) { - poll(); - v = peekNext(); - if (v == Symbol.C_CR || v == Symbol.C_LF) { - poll(); - } - break; - } - sb.append((char) poll()); - } - return sb.toString(); - } - - /** - * 读取头部字节, 并删除 - * - * @return 头部字符 - * @throws IOException 异常 - */ - public int poll() throws IOException { - peekindex = 0; - int v = -1; - if (cache.size() <= 0) { - v = is.read(); - } else { - v = cache.poll(); - } - if (v == -1) { - end = true; - } - if (v == Symbol.C_LF) { - col = 0; - row++; - } else { - col++; - } - return v; - } - - /** - * 访问头部开始第几个字节, 不删除 - * - * @param index 索引 - * @return 头部的第N个字符 - * @throws IOException 异常 - */ - public int peek(int index) throws IOException { - while (cache.size() <= index) { - cache.add(is.read()); - } - return cache.get(index); - } - - /** - * 访问上次peekNext访问的下个位置的字节, 未访问过则访问索引0, poll, peek后归零, 不删除 - * - * @return 下一个位置的字符 - * @throws IOException 异常 - */ - public int peekNext() throws IOException { - return peek(peekindex++); - } - - /** - * 访问头部字节, 不删除 - * - * @return 头部字符 - * @throws IOException 异常 - */ - public int peek() throws IOException { - peekindex = 0; - int v = peek(peekindex++); - if (v == -1) { - end = true; - } - return v; - } - - /** - * 跳过和丢弃输入流中的数据 - */ - public long skip(long n) throws IOException { - int s = cache.size(); - if (s > 0) { - if (s < n) { - n = n - s; - } else { - for (int i = 0; i < n; i++) { - cache.poll(); - } - return n; - } - } - return super.skip(n) + s; - } - - /** - * 是否结束 - * - * @return true, 如果流已经结束 - */ - public boolean isEnd() { - return end; - } - - public int read(char[] cbuf, int off, int len) throws IOException { - for (int i = 0; i < len; i++) { - if (isEnd()) { - return -1; - } - cbuf[off + i] = (char) poll(); - } - return len; - } - - public void close() throws IOException { - is.close(); - cache.clear(); - } - - /** - * 是否以 start 开始 - * - * @param start 开始位置 - * @return true, 如果的确以指定字符串开始 - * @throws IOException 异常 - */ - public boolean startWith(String start) throws IOException { - char[] cs = start.toCharArray(); - int i = 0; - for (; i < cs.length; i++) { - if (peek(i) != cs[i]) { - return false; - } - } - return true; - } - - public int getCol() { - return col; - } - - public int getRow() { - return row; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileInputStream.java deleted file mode 100644 index daf8602cc5..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileInputStream.java +++ /dev/null @@ -1,79 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class RandomFileInputStream extends InputStream { - - private final RandomAccessFile raf; - - public RandomFileInputStream(RandomAccessFile raf) { - this.raf = raf; - } - - @Override - public int read() throws IOException { - return raf.read(); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - return raf.read(b, off, len); - } - - @Override - public long skip(long n) throws IOException { - return raf.skipBytes((int) n); - } - - @Override - public void close() throws IOException { - raf.close(); - } - - @Override - public synchronized void reset() throws IOException { - raf.seek(0); - } - - @Override - public int read(byte[] b) throws IOException { - return super.read(b); - } - - @Override - public int available() throws IOException { - return (int) (raf.length() - raf.getFilePointer()); - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamBuffer.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamBuffer.java deleted file mode 100644 index f771710489..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamBuffer.java +++ /dev/null @@ -1,132 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import org.aoju.bus.core.exception.InternalException; -import org.aoju.bus.core.lang.Charset; -import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.toolkit.IoKit; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class StreamBuffer extends InputStream { - - private final OutputStreamBuffer buffer = new OutputStreamBuffer(); - private int index = 0; - private int cursor = 0; - - public OutputStream getBuffer() { - return buffer; - } - - public void write(int b) throws IOException { - buffer.write(b); - } - - @Override - public int read() throws IOException { - if (cursor > buffer.width) { - index++; - cursor = 0; - } - if (index > buffer.index) - return -1; - if (index < buffer.bytes.size()) { - byte[] cs = buffer.bytes.get(index); - if (cursor < buffer.cursor) - return cs[cursor++]; - } - return -1; - } - - @Override - public int available() { - return buffer.size(); - } - - @Override - public synchronized void reset() { - index = 0; - cursor = 0; - } - - @Override - public String toString() { - try { - return toString(Charset.DEFAULT_CHARSET); - } catch (IOException e) { - throw new InternalException(e); - } - } - - public String toString(String charset) throws IOException { - index = 0; - cursor = 0; - StringBuilder sb = new StringBuilder(); - StringOutputStream sos = new StringOutputStream(sb, charset); - byte c; - while ((c = (byte) this.read()) != -1) - sos.write(c); - sos.flush(); - IoKit.close(sos); - return sb.toString(); - } - - private static class OutputStreamBuffer extends OutputStream { - - private final List bytes = new ArrayList<>(); - private final int width = Normal._1024; - private int index = 0; - private int cursor = 0; - - @Override - public void write(int b) throws IOException { - if (cursor >= width) - index++; - byte[] row = bytes.size() > index ? bytes.get(index) : null; - if (null == row) { - row = new byte[width]; - bytes.add(row); - cursor = 0; - } - row[cursor++] = (byte) b; - } - - private int size() { - return index > 0 ? width * (index - 1) + cursor : cursor; - } - - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamReader.java new file mode 100755 index 0000000000..01f3a5ab86 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamReader.java @@ -0,0 +1,204 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************//********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.stream; + +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.toolkit.IoKit; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * {@link InputStream}读取器 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class StreamReader { + + private final InputStream in; + private final boolean closeAfterRead; + + /** + * 构造 + * + * @param in {@link InputStream} + * @param closeAfterRead 读取结束后是否关闭输入流 + */ + public StreamReader(final InputStream in, final boolean closeAfterRead) { + this.in = in; + this.closeAfterRead = closeAfterRead; + } + + /** + * 创建读取器 + * + * @param in {@link InputStream} + * @param closeAfterRead 读取结束后是否关闭输入流 + * @return StreamReader + */ + public static StreamReader of(final InputStream in, final boolean closeAfterRead) { + return new StreamReader(in, closeAfterRead); + } + + /** + * 从流中读取bytes + * + * @return bytes + * @throws InternalException IO异常 + */ + public byte[] readBytes() throws InternalException { + return readBytes(-1); + } + + /** + * 读取指定长度的byte数组 + * + * @param length 长度,小于0表示读取全部 + * @return bytes + * @throws InternalException IO异常 + */ + public byte[] readBytes(final int length) throws InternalException { + final InputStream in = this.in; + if (null == in || length == 0) { + return new byte[0]; + } + return read(length).toByteArray(); + } + + /** + * 从流中读取内容,读到输出流中,读取完毕后可选是否关闭流 + * + * @return 输出流 + * @throws InternalException IO异常 + */ + public FastByteOutputStream read() throws InternalException { + return read(-1); + } + + /** + * 从流中读取内容,读到输出流中,读取完毕后可选是否关闭流 + * + * @param limit 限制最大拷贝长度,-1表示无限制 + * @return 输出流 + * @throws InternalException IO异常 + */ + public FastByteOutputStream read(final int limit) throws InternalException { + final InputStream in = this.in; + final FastByteOutputStream out; + if (in instanceof FileInputStream) { + // 文件流的长度是可预见的,此时直接读取效率更高 + try { + int length = in.available(); + if (limit > 0 && limit < length) { + length = limit; + } + out = new FastByteOutputStream(length); + } catch (final IOException e) { + throw new InternalException(e); + } + } else { + out = new FastByteOutputStream(); + } + try { + IoKit.copy(in, out, IoKit.DEFAULT_BUFFER_SIZE, limit, null); + } finally { + if (closeAfterRead) { + IoKit.close(in); + } + } + return out; + } + + /** + * 从流中读取对象,即对象的反序列化 + * + *

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

    + * + *

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

    + * + * @param 读取对象的类型 + * @param acceptClasses 读取对象类型 + * @return 输出流 + * @throws InternalException IO异常 + */ + public T readObject(final Class... acceptClasses) throws InternalException { + final InputStream in = this.in; + if (null == in) { + return null; + } + + // 转换 + final ObjectInputStream validateIn; + if (in instanceof ObjectInputStream) { + validateIn = (ObjectInputStream) in; + validateIn.accept(acceptClasses); + } else { + try { + validateIn = new ObjectInputStream(in, acceptClasses); + } catch (final IOException e) { + throw new InternalException(e); + } + } + + // 读取 + try { + return (T) validateIn.readObject(); + } catch (final ClassNotFoundException | IOException e) { + throw new InternalException(e); + } + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamWriter.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamWriter.java new file mode 100755 index 0000000000..06cf7b946e --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamWriter.java @@ -0,0 +1,142 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.stream; + +import org.aoju.bus.core.convert.Convert; +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.lang.Normal; +import org.aoju.bus.core.toolkit.IoKit; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; + +/** + * {@link OutputStream}写出器 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class StreamWriter { + + private final OutputStream out; + private final boolean closeAfterWrite; + + /** + * 构造 + * + * @param out {@link OutputStream} + * @param closeAfterWrite 写出结束后是否关闭流 + */ + public StreamWriter(final OutputStream out, final boolean closeAfterWrite) { + this.out = out; + this.closeAfterWrite = closeAfterWrite; + } + + /** + * 创建写出器 + * + * @param out {@link OutputStream} + * @param closeAfterWrite 写出结束后是否关闭流 + * @return StreamReader + */ + public static StreamWriter of(final OutputStream out, final boolean closeAfterWrite) { + return new StreamWriter(out, closeAfterWrite); + } + + /** + * 将byte[]写到流中 + * + * @param content 写入的内容 + * @throws InternalException IO异常 + */ + public void write(final byte[] content) throws InternalException { + final OutputStream out = this.out; + try { + out.write(content); + } catch (final IOException e) { + throw new InternalException(e); + } finally { + if (closeAfterWrite) { + IoKit.close(out); + } + } + } + + /** + * 将多部分对象写到流中,使用{@link ObjectOutputStream},对象必须实现序列化接口 + * + * @param contents 写入的内容 + * @throws InternalException IO异常 + */ + public void writeObject(final Object... contents) throws InternalException { + ObjectOutputStream osw = null; + try { + osw = out instanceof ObjectOutputStream ? (ObjectOutputStream) out : new ObjectOutputStream(out); + for (final Object content : contents) { + if (content != null) { + osw.writeObject(content); + } + } + osw.flush(); + } catch (final IOException e) { + throw new InternalException(e); + } finally { + if (closeAfterWrite) { + IoKit.close(osw); + } + } + } + + /** + * 将多部分内容写到流中,自动转换为字符串 + * + * @param charset 写出的内容的字符集 + * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 + * @throws InternalException IO异常 + */ + public void writeString(final Charset charset, final Object... contents) throws InternalException { + OutputStreamWriter osw = null; + try { + osw = IoKit.getWriter(out, charset); + for (final Object content : contents) { + if (content != null) { + osw.write(Convert.toString(content, Normal.EMPTY)); + } + } + osw.flush(); + } catch (final IOException e) { + throw new InternalException(e); + } finally { + if (closeAfterWrite) { + IoKit.close(osw); + } + } + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringInputStream.java old mode 100644 new mode 100755 index f88bd5f433..3918f21029 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringInputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringInputStream.java @@ -25,37 +25,38 @@ ********************************************************************************/ package org.aoju.bus.core.io.stream; -import org.aoju.bus.core.exception.InternalException; -import org.aoju.bus.core.lang.Charset; +import org.aoju.bus.core.toolkit.StringKit; import java.io.ByteArrayInputStream; -import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; /** + * 基于字符串的InputStream + * * @author Kimi Liu * @since Java 17+ */ public class StringInputStream extends ByteArrayInputStream { - public StringInputStream(CharSequence s, java.nio.charset.Charset charset) { - super(toBytes(s, charset)); + /** + * 构造 + * + * @param text 字符串 + * @param charset 编码 + */ + public StringInputStream(final CharSequence text, final Charset charset) { + super(StringKit.bytes(text, charset)); } - public StringInputStream(CharSequence s) { - super(toBytes(s, Charset.UTF_8)); + /** + * 创建StrInputStream + * + * @param text 字符串 + * @param charset 编码 + * @return StrInputStream + */ + public static StringInputStream of(final CharSequence text, final Charset charset) { + return new StringInputStream(text, charset); } - protected static byte[] toBytes(CharSequence text, java.nio.charset.Charset charset) { - if (null == text) - return new byte[0]; - if (null == charset) { - charset = Charset.UTF_8; - } - try { - return text.toString().getBytes(charset.name()); - } catch (UnsupportedEncodingException e) { - throw new InternalException(e); - } - } - -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringOutputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringOutputStream.java deleted file mode 100644 index ce891f8af4..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringOutputStream.java +++ /dev/null @@ -1,93 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import org.aoju.bus.core.lang.Charset; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class StringOutputStream extends OutputStream { - - private final StringBuilder sb; - private final String charset; - private ByteArrayOutputStream baos; - - public StringOutputStream(StringBuilder sb) { - this(sb, Charset.DEFAULT_UTF_8); - } - - public StringOutputStream(StringBuilder sb, String charset) { - this.sb = sb; - baos = new ByteArrayOutputStream(); - this.charset = charset; - } - - /** - * 完成本方法后,确认字符串已经完成写入后,务必调用flash方法! - */ - @Override - public void write(int b) throws IOException { - if (null == baos) - throw new IOException("Stream is closed"); - baos.write(b); - } - - /** - * 使用StringBuilder前,务必调用 - */ - @Override - public void flush() throws IOException { - if (null != baos) { - baos.flush(); - if (baos.size() > 0) { - if (null == charset) { - sb.append(baos.toString()); - } else { - sb.append(baos.toString(charset)); - } - - baos.reset(); - } - } - } - - @Override - public void close() throws IOException { - flush(); - baos = null; - } - - public StringBuilder getStringBuilder() { - return sb; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringReader.java deleted file mode 100644 index 21611353c5..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringReader.java +++ /dev/null @@ -1,63 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import java.io.IOException; -import java.io.Reader; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class StringReader extends Reader { - - private final CharSequence cs; - private int index; - - public StringReader(CharSequence cs) { - this.cs = cs; - index = 0; - } - - @Override - public void close() throws IOException { - } - - @Override - public int read(char[] cbuf, int off, int len) throws IOException { - if (index >= cs.length()) - return -1; - int count = 0; - for (int i = off; i < (off + len); i++) { - if (index >= cs.length()) - return count; - cbuf[i] = cs.charAt(index++); - count++; - } - return count; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringWriter.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringWriter.java deleted file mode 100644 index 31afaf795f..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringWriter.java +++ /dev/null @@ -1,62 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import java.io.IOException; -import java.io.Writer; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class StringWriter extends Writer { - - private final StringBuilder sb; - - public StringWriter(StringBuilder sb) { - this.sb = sb; - } - - @Override - public void close() throws IOException { - } - - @Override - public void flush() throws IOException { - } - - @Override - public void write(char[] cbuf, int off, int len) throws IOException { - for (int i = off; i < (off + len); i++) { - sb.append(cbuf[i]); - } - } - - public StringBuilder getStringBuilder() { - return sb; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/SyncInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/SyncInputStream.java new file mode 100755 index 0000000000..4b057a54c0 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/SyncInputStream.java @@ -0,0 +1,134 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.stream; + +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.io.Progress; +import org.aoju.bus.core.toolkit.IoKit; +import org.aoju.bus.core.toolkit.StringKit; + +import java.io.*; + +/** + * 同步流,可将包装的流同步为ByteArrayInputStream,以便持有内容并关闭原流 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class SyncInputStream extends FilterInputStream { + + private final long length; + private final boolean isIgnoreEOFError; + /** + * 是否异步,异步下只持有流,否则将在初始化时直接读取body内容 + */ + private volatile boolean asyncFlag = true; + + /** + * 构造
    + * 如果isAsync为{@code true},则直接持有原有流,{@code false},则将流中内容,按照给定length读到{@link ByteArrayInputStream}中备用 + * + * @param in 数据流 + * @param length 限定长度,-1表示未知长度 + * @param isAsync 是否异步 + * @param isIgnoreEOFError 是否忽略EOF错误,在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束
    + * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
    + */ + public SyncInputStream(final InputStream in, final long length, final boolean isAsync, final boolean isIgnoreEOFError) { + super(in); + this.length = length; + this.isIgnoreEOFError = isIgnoreEOFError; + if (false == isAsync) { + sync(); + } + } + + /** + * 是否为EOF异常,包括
    + *
      + *
    • FileNotFoundException:服务端无返回内容
    • + *
    • EOFException:EOF异常
    • + *
    + * + * @param e 异常 + * @return 是否EOF异常 + */ + private static boolean isEOFException(final Throwable e) { + if (e instanceof FileNotFoundException) { + // 服务器无返回内容,忽略之 + return true; + } + return e instanceof EOFException || StringKit.containsIgnoreCase(e.getMessage(), "Premature EOF"); + } + + /** + * 同步数据到内存 + */ + public void sync() { + if (false == asyncFlag) { + // 已经是同步模式 + return; + } + + this.in = new ByteArrayInputStream(readBytes()); + this.asyncFlag = false; + } + + /** + * 读取流中所有bytes + * + * @return bytes + */ + public byte[] readBytes() { + final FastByteOutputStream bytesOut = new FastByteOutputStream(length > 0 ? (int) length : 1024); + final long length = copyTo(bytesOut, null); + return length > 0 ? bytesOut.toByteArray() : new byte[0]; + } + + /** + * 将流的内容拷贝到输出流 + * + * @param out 输出流 + * @param streamProgress 进度条 + * @return 拷贝长度 + */ + public long copyTo(final OutputStream out, final Progress streamProgress) { + long copyLength = -1; + try { + copyLength = IoKit.copy(this.in, out, IoKit.DEFAULT_BUFFER_SIZE, this.length, streamProgress); + } catch (final InternalException e) { + if (false == (isIgnoreEOFError && isEOFException(e.getCause()))) { + throw e; + } + // 忽略读取流中的EOF错误 + } finally { + // 读取结束 + IoKit.close(in); + } + return copyLength; + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/VoidInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/VoidInputStream.java deleted file mode 100644 index a13e65f865..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/VoidInputStream.java +++ /dev/null @@ -1,42 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import java.io.IOException; -import java.io.InputStream; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class VoidInputStream extends InputStream { - - @Override - public int read() throws IOException { - return -1; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/VoidOutputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/VoidOutputStream.java deleted file mode 100644 index 4bcc2a1197..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/VoidOutputStream.java +++ /dev/null @@ -1,41 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class VoidOutputStream extends OutputStream { - - @Override - public void write(int b) throws IOException { - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/package-info.java old mode 100644 new mode 100755 index ce79a9aa30..cc741fad96 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/package-info.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/package-info.java @@ -1,5 +1,5 @@ /** - * Java8的stream相关封装 + * InputStream和OutputStream相关方法和类封装 * * @author Kimi Liu * @since Java 17+ diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BloomKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/BloomKit.java deleted file mode 100644 index 5a289826bf..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BloomKit.java +++ /dev/null @@ -1,61 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.toolkit; - -import org.aoju.bus.core.bloom.BitMapBloomFilter; -import org.aoju.bus.core.bloom.BitSetBloomFilter; - -/** - * 布隆过滤器工具 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class BloomKit { - - /** - * 创建一个BitSet实现的布隆过滤器,过滤器的容量为c * n 个bit - * - * @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍 - * @param n 当前过滤器预计所要包含的记录 - * @param k 哈希函数的个数,等同每条记录要占用的bit数 - * @return the object - */ - public static BitSetBloomFilter createBitSet(int c, int n, int k) { - return new BitSetBloomFilter(c, n, k); - } - - /** - * 创建BitMap实现的布隆过滤器 - * - * @param m BitMap的大小 - * @return the object - */ - public static BitMapBloomFilter createBitMap(int m) { - return new BitMapBloomFilter(m); - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java index 52ed12b92a..7a42a6921e 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java @@ -36,6 +36,7 @@ import org.aoju.bus.core.lang.Assert; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.core.lang.System; import org.aoju.bus.core.lang.mutable.MutableObject; import org.aoju.bus.core.lang.tuple.Pair; import org.aoju.bus.core.loader.JarLoaders; @@ -363,7 +364,25 @@ public static Field[] getDeclaredFields(Class clazz) throws SecurityException * @return 获得Java ClassPath路径,不包括 jre */ public static String[] getJavaClassPaths() { - return System.getProperty("java.class.path").split(System.getProperty("path.separator")); + return System.get(System.CLASS_PATH).split(System.get(System.PATH_SEPARATOR)); + } + + /** + * 获取用户路径(绝对路径) + * + * @return 用户路径 + */ + public static String getUserHome() { + return System.get(System.USER_HOME); + } + + /** + * 获取临时文件路径(绝对路径) + * + * @return 临时文件路径 + */ + public static String getTmpDir() { + return System.get(System.IO_TMPDIR); } /** @@ -2060,12 +2079,12 @@ static Object[] getVarArgs(final Object[] args, final Class[] methodParameter } final Object[] newArgs = new Object[methodParameterTypes.length]; - System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1); + java.lang.System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1); final Class varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType(); final int varArgLength = args.length - methodParameterTypes.length + 1; Object varArgsArray = Array.newInstance(primitiveToWrapper(varArgComponentType), varArgLength); - System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength); + java.lang.System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength); if (varArgComponentType.isPrimitive()) { varArgsArray = ArrayKit.toPrimitive(varArgsArray); @@ -3775,4 +3794,4 @@ public boolean isVarArgs() { } -} +} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java index fbaf7e991a..6d3bb5d0d1 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java @@ -33,6 +33,7 @@ import org.aoju.bus.core.io.Segment; import org.aoju.bus.core.io.buffer.Buffer; import org.aoju.bus.core.io.copier.ChannelCopier; +import org.aoju.bus.core.io.copier.FileChannelCopier; import org.aoju.bus.core.io.copier.ReaderWriterCopier; import org.aoju.bus.core.io.copier.StreamCopier; import org.aoju.bus.core.io.sink.BufferSink; @@ -41,10 +42,7 @@ import org.aoju.bus.core.io.source.BufferSource; import org.aoju.bus.core.io.source.RealSource; import org.aoju.bus.core.io.source.Source; -import org.aoju.bus.core.io.stream.BOMInputStream; -import org.aoju.bus.core.io.stream.BOMReader; -import org.aoju.bus.core.io.stream.FastByteOutputStream; -import org.aoju.bus.core.io.stream.NullOutputStream; +import org.aoju.bus.core.io.stream.*; import org.aoju.bus.core.io.timout.AsyncTimeout; import org.aoju.bus.core.io.timout.Timeout; import org.aoju.bus.core.lang.Assert; @@ -52,6 +50,7 @@ import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.function.XConsumer; +import java.io.ObjectInputStream; import java.io.*; import java.net.ServerSocket; import java.net.Socket; @@ -151,95 +150,90 @@ public static boolean arrayRangeEquals( } /** - * 将Reader中的内容复制到Writer中 使用默认缓存大小 + * 将Reader中的内容复制到Writer中 使用默认缓存大小,拷贝后不关闭Reader * * @param reader Reader * @param writer Writer * @return 拷贝的字节数 - * @throws InternalException 异常 */ - public static long copy(Reader reader, Writer writer) throws InternalException { + public static long copy(final Reader reader, final Writer writer) { return copy(reader, writer, DEFAULT_BUFFER_SIZE); } /** - * 将Reader中的内容复制到Writer中 + * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader * * @param reader Reader * @param writer Writer * @param bufferSize 缓存大小 * @return 传输的byte数 - * @throws InternalException 异常 */ - public static long copy(Reader reader, Writer writer, int bufferSize) throws InternalException { + public static long copy(final Reader reader, final Writer writer, final int bufferSize) { return copy(reader, writer, bufferSize, null); } /** - * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader + * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader * * @param reader Reader * @param writer Writer * @param bufferSize 缓存大小 * @param progress 进度处理器 * @return 传输的byte数 - * @throws InternalException 异常 */ - public static long copy(Reader reader, Writer writer, int bufferSize, Progress progress) throws InternalException { + public static long copy(final Reader reader, final Writer writer, final int bufferSize, final Progress progress) { return copy(reader, writer, bufferSize, -1, progress); } /** - * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader + * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader * - * @param reader Reader - * @param writer Writer - * @param bufferSize 缓存大小 - * @param count 最大长度 - * @param progress 进度处理器 + * @param reader Reader,非空 + * @param writer Writer,非空 + * @param bufferSize 缓存大小,-1表示默认 + * @param count 最大长度,-1表示无限制 + * @param progress 进度处理器,{@code null}表示无 * @return 传输的byte数 - * @throws InternalException IO异常 */ - public static long copy(Reader reader, Writer writer, int bufferSize, long count, Progress progress) throws InternalException { + public static long copy(final Reader reader, final Writer writer, final int bufferSize, final long count, final Progress progress) { + Assert.notNull(reader, "Reader is null !"); + Assert.notNull(writer, "Writer is null !"); return new ReaderWriterCopier(bufferSize, count, progress).copy(reader, writer); } /** - * 拷贝流,使用默认Buffer大小 + * 拷贝流,使用默认Buffer大小,拷贝后不关闭流 * * @param in 输入流 * @param out 输出流 * @return 传输的byte数 - * @throws InternalException 异常 */ - public static long copy(InputStream in, OutputStream out) throws InternalException { + public static long copy(final InputStream in, final OutputStream out) { return copy(in, out, DEFAULT_BUFFER_SIZE); } /** - * 拷贝流 + * 拷贝流,拷贝后不关闭流 * * @param in 输入流 * @param out 输出流 * @param bufferSize 缓存大小 * @return 传输的byte数 - * @throws InternalException 异常 */ - public static long copy(InputStream in, OutputStream out, int bufferSize) throws InternalException { + public static long copy(final InputStream in, final OutputStream out, final int bufferSize) { return copy(in, out, bufferSize, null); } /** - * 拷贝流 + * 拷贝流,拷贝后不关闭流 * * @param in 输入流 * @param out 输出流 * @param bufferSize 缓存大小 * @param progress 进度条 * @return 传输的byte数 - * @throws InternalException 异常 */ - public static long copy(InputStream in, OutputStream out, int bufferSize, Progress progress) throws InternalException { + public static long copy(final InputStream in, final OutputStream out, final int bufferSize, final Progress progress) { return copy(in, out, bufferSize, -1, progress); } @@ -249,12 +243,13 @@ public static long copy(InputStream in, OutputStream out, int bufferSize, Progre * @param in 输入流 * @param out 输出流 * @param bufferSize 缓存大小 - * @param count 总拷贝长度 + * @param count 总拷贝长度,-1表示无限制 * @param progress 进度条 * @return 传输的byte数 - * @throws InternalException IO异常 */ - public static long copy(InputStream in, OutputStream out, int bufferSize, int count, Progress progress) throws InternalException { + public static long copy(final InputStream in, final OutputStream out, final int bufferSize, final long count, final Progress progress) { + Assert.notNull(in, "InputStream is null !"); + Assert.notNull(out, "OutputStream is null !"); return new StreamCopier(bufferSize, count, progress).copy(in, out); } @@ -311,21 +306,6 @@ private static long copySafely(FileChannel inChannel, FileChannel outChannel) th return totalBytes; } - /** - * 拷贝流,本方法不会关闭流 - * - * @param in 输入流 - * @param out 输出流 - * @param bufferSize 缓存大小 - * @param count 最大长度 - * @param progress 进度条 - * @return 传输的byte数 - * @throws InternalException IO异常 - */ - public static long copy(InputStream in, OutputStream out, int bufferSize, long count, Progress progress) throws InternalException { - return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, count, progress); - } - /** * 拷贝流,使用NIO,不会关闭channel * @@ -380,6 +360,20 @@ public static long copy(ReadableByteChannel in, WritableByteChannel out, int buf return new ChannelCopier(bufferSize, count, progress).copy(in, out); } + /** + * 文件拷贝实现 + * + * @param in 输入 + * @param out 输出 + * @return 拷贝的字节数 + */ + public static long copy(final FileInputStream in, final FileOutputStream out) { + Assert.notNull(in, "FileInputStream is null!"); + Assert.notNull(out, "FileOutputStream is null!"); + + return new FileChannelCopier(-1).copy(in, out); + } + /** * 获得一个文件读取器 * @@ -403,7 +397,7 @@ public static BufferedReader getReader(InputStream in, java.nio.charset.Charset return null; } - InputStreamReader reader; + final InputStreamReader reader; if (null == charset) { reader = new InputStreamReader(in); } else { @@ -500,7 +494,7 @@ public static OutputStreamWriter getWriter(OutputStream out, java.nio.charset.Ch */ public static String read(InputStream in, String charsetName) throws InternalException { FastByteOutputStream out = read(in); - return StringKit.isBlank(charsetName) ? out.toString() : out.toString(charsetName); + return StringKit.isBlank(charsetName) ? out.toString() : out.toString(Charset.charset(charsetName)); } /** @@ -536,25 +530,7 @@ public static FastByteOutputStream read(InputStream in) throws InternalException * @throws InternalException IO异常 */ public static FastByteOutputStream read(InputStream in, boolean isClose) throws InternalException { - final FastByteOutputStream out; - if (in instanceof FileInputStream) { - // 文件流的长度是可预见的,此时直接读取效率更高 - try { - out = new FastByteOutputStream(in.available()); - } catch (IOException e) { - throw new InternalException(e); - } - } else { - out = new FastByteOutputStream(); - } - try { - copy(in, out); - } finally { - if (isClose) { - close(in); - } - } - return out; + return StreamReader.of(in, isClose).read(); } /** @@ -793,7 +769,7 @@ public static T readObject(InputStream in) throws InternalException { * @return 内容 * @throws InternalException 异常 */ - public static > T readUtf8Lines(InputStream in, T collection) throws InternalException { + public static > T readLines(InputStream in, T collection) throws InternalException { return readLines(in, Charset.UTF_8, collection); } @@ -846,7 +822,7 @@ public static > T readLines(Reader reader, T collec * @param lineHandler 行处理接口,实现accept方法用于编辑一行的数据后入到指定地方 * @throws InternalException 异常 */ - public static void readUtf8Lines(InputStream in, XConsumer lineHandler) throws InternalException { + public static void readLines(InputStream in, XConsumer lineHandler) throws InternalException { readLines(in, Charset.UTF_8, lineHandler); } @@ -1038,15 +1014,7 @@ public static PushbackInputStream toPushbackStream(InputStream in, int pushBackS * @throws InternalException 异常 */ public static void write(OutputStream out, boolean isCloseOut, byte[] content) throws InternalException { - try { - out.write(content); - } catch (IOException e) { - throw new InternalException(e); - } finally { - if (isCloseOut) { - close(out); - } - } + StreamWriter.of(out, isCloseOut).write(content); } /** @@ -1058,7 +1026,7 @@ public static void write(OutputStream out, boolean isCloseOut, byte[] content) t * @throws InternalException 异常 */ public static void writeUtf8(OutputStream out, boolean isCloseOut, Object... contents) throws InternalException { - write(out, Charset.UTF_8, isCloseOut, contents); + StreamWriter.of(out, isCloseOut).writeObject(contents); } /** @@ -1071,7 +1039,7 @@ public static void writeUtf8(OutputStream out, boolean isCloseOut, Object... con * @throws InternalException 异常 */ public static void write(OutputStream out, String charsetName, boolean isCloseOut, Object... contents) throws InternalException { - write(out, Charset.charset(charsetName), isCloseOut, contents); + StreamWriter.of(out, isCloseOut).writeString(Charset.charset(charsetName), contents); } /** @@ -1362,7 +1330,7 @@ public static Checksum checksum(InputStream in, Checksum checksum) throws Intern } try { in = new CheckedInputStream(in, checksum); - IoKit.copy(in, new NullOutputStream()); + IoKit.copy(in, EmptyOutputStream.INSTANCE); } finally { IoKit.close(in); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ReflectKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ReflectKit.java index 6cbfdde5e4..e376316380 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ReflectKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ReflectKit.java @@ -628,6 +628,16 @@ public static void setFieldValue(Object object, Field field, Object value) throw } } + /** + * 设置静态(static)字段值 + * + * @param field 字段 + * @param value 值,值类型必须与字段类型匹配,不会自动转换对象类型 + */ + public static void setStaticFieldValue(final Field field, final Object value) { + setFieldValue(null, field, value); + } + /** * 查找指定对象中的所有方法(包括非public方法),也包括父对象和Object类的方法 * From e1f0b736294f05e764766e497ddf7ff4cd62f5ef Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Mon, 31 Oct 2022 10:57:12 +0800 Subject: [PATCH 10/19] update stream and date kit --- .../java/org/aoju/bus/core/codec/Base32.java | 38 +- .../java/org/aoju/bus/core/codec/Base58.java | 55 +- .../java/org/aoju/bus/core/codec/Base62.java | 107 ++- .../java/org/aoju/bus/core/codec/Base64.java | 97 +-- .../java/org/aoju/bus/core/codec/Caesar.java | 24 +- .../java/org/aoju/bus/core/codec/Hashids.java | 67 +- .../java/org/aoju/bus/core/codec/Morse.java | 27 +- .../java/org/aoju/bus/core/codec/Percent.java | 250 ++++-- .../org/aoju/bus/core/codec/PunyCode.java | 78 +- .../core/codec/provider/Base16Provider.java | 53 +- .../core/codec/provider/Base32Provider.java | 22 +- .../core/codec/provider/Base58Provider.java | 25 +- .../core/codec/provider/Base62Provider.java | 61 +- .../bus/core/io/copier/ChannelCopier.java | 3 +- .../java/org/aoju/bus/core/key/HashID.java | 457 ---------- .../java/org/aoju/bus/core/lang/Http.java | 4 + .../java/org/aoju/bus/core/lang/Normal.java | 12 +- .../java/org/aoju/bus/core/lang/RegEx.java | 6 + .../java/org/aoju/bus/core/net/LocalPort.java | 73 ++ .../java/org/aoju/bus/core/net/MaskBit.java | 101 +++ .../org/aoju/bus/core/net/package-info.java | 7 + .../core/net/ssl}/DefaultTrustManager.java | 2 +- .../bus/core/net/ssl}/HostnameVerifier.java | 6 +- .../bus/core/net/ssl}/SSLContextBuilder.java | 86 +- .../aoju/bus/core/net/ssl/package-info.java | 7 + .../org/aoju/bus/core/toolkit/CharsKit.java | 19 + .../java/org/aoju/bus/core/toolkit/IoKit.java | 5 +- .../org/aoju/bus/core/toolkit/NetKit.java | 780 ++++++++++++------ .../main/java/org/aoju/bus/http/Builder.java | 17 +- .../main/java/org/aoju/bus/http/Httpd.java | 6 +- .../main/java/org/aoju/bus/http/Httpx.java | 2 +- .../main/java/org/aoju/bus/http/Httpz.java | 2 +- .../aoju/bus/http/accord/RealConnection.java | 2 +- .../accord/platform/Android10Platform.java | 1 + .../http/accord/platform/AndroidPlatform.java | 22 +- .../platform/Jdk8WithJettyBootPlatform.java | 1 + .../bus/http/accord/platform/Platform.java | 11 - .../bus/http/plugin/httpz/HttpBuilder.java | 2 +- .../org/aoju/bus/http/secure/TlsVersion.java | 22 +- 39 files changed, 1336 insertions(+), 1224 deletions(-) mode change 100644 => 100755 bus-core/src/main/java/org/aoju/bus/core/codec/Base64.java delete mode 100755 bus-core/src/main/java/org/aoju/bus/core/key/HashID.java create mode 100644 bus-core/src/main/java/org/aoju/bus/core/net/LocalPort.java create mode 100644 bus-core/src/main/java/org/aoju/bus/core/net/MaskBit.java create mode 100644 bus-core/src/main/java/org/aoju/bus/core/net/package-info.java rename {bus-http/src/main/java/org/aoju/bus/http/secure => bus-core/src/main/java/org/aoju/bus/core/net/ssl}/DefaultTrustManager.java (98%) rename {bus-http/src/main/java/org/aoju/bus/http/secure => bus-core/src/main/java/org/aoju/bus/core/net/ssl}/HostnameVerifier.java (98%) rename {bus-http/src/main/java/org/aoju/bus/http/secure => bus-core/src/main/java/org/aoju/bus/core/net/ssl}/SSLContextBuilder.java (92%) create mode 100644 bus-core/src/main/java/org/aoju/bus/core/net/ssl/package-info.java diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Base32.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Base32.java index e1fcf9b761..37c195ff28 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Base32.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Base32.java @@ -26,14 +26,18 @@ package org.aoju.bus.core.codec; import org.aoju.bus.core.codec.provider.Base32Provider; -import org.aoju.bus.core.lang.Charset; import org.aoju.bus.core.toolkit.StringKit; +import java.nio.charset.Charset; + /** - * Base32 - encodes and decodes RFC3548 Base32 (see http://www.faqs.org/rfcs/rfc3548.html ) - * base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码 - * 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足 - * see http://blog.csdn.net/earbao/article/details/44453937 + * Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6) + * base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码,所以5个ASCII字符经过base32编码后会变为8个字符(公约数为40) + * 长度增加3/5.不足8n用“=”补足,根据RFC4648 Base32规范,支持两种模式: + *
      + *
    • Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)
    • + *
    • "Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)
    • + *
    * * @author Kimi Liu * @since Java 17+ @@ -56,8 +60,8 @@ public static String encode(final byte[] bytes) { * @param source 被编码的base32字符串 * @return 被加密后的字符串 */ - public static String encode(String source) { - return encode(source, Charset.UTF_8); + public static String encode(final String source) { + return encode(source, org.aoju.bus.core.lang.Charset.UTF_8); } /** @@ -67,7 +71,7 @@ public static String encode(String source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encode(String source, java.nio.charset.Charset charset) { + public static String encode(final String source, final Charset charset) { return encode(StringKit.bytes(source, charset)); } @@ -87,7 +91,7 @@ public static String encodeHex(final byte[] bytes) { * @param source 被编码的base32字符串 * @return 被加密后的字符串 */ - public static String encodeHex(String source) { + public static String encodeHex(final String source) { return encodeHex(source, org.aoju.bus.core.lang.Charset.UTF_8); } @@ -98,7 +102,7 @@ public static String encodeHex(String source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encodeHex(String source, java.nio.charset.Charset charset) { + public static String encodeHex(final String source, final Charset charset) { return encodeHex(StringKit.bytes(source, charset)); } @@ -108,7 +112,7 @@ public static String encodeHex(String source, java.nio.charset.Charset charset) * @param base32 base32编码 * @return 数据 */ - public static byte[] decode(String base32) { + public static byte[] decode(final String base32) { return Base32Provider.INSTANCE.decode(base32); } @@ -118,7 +122,7 @@ public static byte[] decode(String base32) { * @param source 被解码的base32字符串 * @return 被加密后的字符串 */ - public static String decodeString(String source) { + public static String decodeString(final String source) { return decodeString(source, org.aoju.bus.core.lang.Charset.UTF_8); } @@ -129,7 +133,7 @@ public static String decodeString(String source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String decodeString(String source, java.nio.charset.Charset charset) { + public static String decodeString(final String source, final Charset charset) { return StringKit.toString(decode(source), charset); } @@ -139,7 +143,7 @@ public static String decodeString(String source, java.nio.charset.Charset charse * @param base32 base32编码 * @return 数据 */ - public static byte[] decodeHex(String base32) { + public static byte[] decodeHex(final String base32) { return Base32Provider.INSTANCE.decode(base32, true); } @@ -149,8 +153,8 @@ public static byte[] decodeHex(String base32) { * @param source 被解码的base32字符串 * @return 被加密后的字符串 */ - public static String decodeStrHex(String source) { - return decodeStrHex(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String decodeStringHex(final String source) { + return decodeStringHex(source, org.aoju.bus.core.lang.Charset.UTF_8); } /** @@ -160,7 +164,7 @@ public static String decodeStrHex(String source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String decodeStrHex(String source, java.nio.charset.Charset charset) { + public static String decodeStringHex(final String source, final Charset charset) { return StringKit.toString(decodeHex(source), charset); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Base58.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Base58.java index de2cb3a86a..2a540a5f76 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Base58.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Base58.java @@ -26,7 +26,6 @@ package org.aoju.bus.core.codec; import org.aoju.bus.core.codec.provider.Base58Provider; -import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.exception.ValidateException; import org.aoju.bus.core.lang.Algorithm; @@ -46,6 +45,16 @@ public class Base58 { private static final int CHECKSUM_SIZE = 4; + /** + * Base58编码 + * + * @param data 被编码的数据,不带校验和 + * @return 编码后的字符串 + */ + public static String encode(final byte[] data) { + return Base58Provider.INSTANCE.encode(data); + } + /** * Base58编码 * 包含版本位和校验位 @@ -54,18 +63,18 @@ public class Base58 { * @param data 被编码的数组,添加校验和。 * @return 编码后的字符串 */ - public static String encodeChecked(Integer version, byte[] data) { + public static String encodeChecked(final Integer version, final byte[] data) { return encode(addChecksum(version, data)); } /** - * Base58编码 + * Base58解码 * - * @param data 被编码的数据,不带校验和。 - * @return 编码后的字符串 + * @param encoded 被编码的base58字符串 + * @return 解码后的bytes */ - public static String encode(byte[] data) { - return Base58Provider.INSTANCE.encode(data); + public static byte[] decode(final CharSequence encoded) { + return Base58Provider.INSTANCE.decode(encoded); } /** @@ -76,10 +85,10 @@ public static String encode(byte[] data) { * @return 解码后的bytes * @throws ValidateException 标志位验证错误抛出此异常 */ - public static byte[] decodeChecked(CharSequence encoded) throws ValidateException { + public static byte[] decodeChecked(final CharSequence encoded) throws ValidateException { try { return decodeChecked(encoded, true); - } catch (ValidateException ignore) { + } catch (final ValidateException ignore) { return decodeChecked(encoded, false); } } @@ -93,21 +102,11 @@ public static byte[] decodeChecked(CharSequence encoded) throws ValidateExceptio * @return 解码后的bytes * @throws ValidateException 标志位验证错误抛出此异常 */ - public static byte[] decodeChecked(CharSequence encoded, boolean withVersion) throws ValidateException { - byte[] valueWithChecksum = decode(encoded); + public static byte[] decodeChecked(final CharSequence encoded, final boolean withVersion) throws ValidateException { + final byte[] valueWithChecksum = decode(encoded); return verifyAndRemoveChecksum(valueWithChecksum, withVersion); } - /** - * Base58解码 - * - * @param encoded 被编码的base58字符串 - * @return 解码后的bytes - */ - public static byte[] decode(CharSequence encoded) { - return Base58Provider.INSTANCE.decode(encoded); - } - /** * 验证并去除验证位和版本位 * @@ -115,7 +114,7 @@ public static byte[] decode(CharSequence encoded) { * @param withVersion 是否包含版本位 * @return 载荷数据 */ - private static byte[] verifyAndRemoveChecksum(byte[] data, boolean withVersion) { + private static byte[] verifyAndRemoveChecksum(final byte[] data, final boolean withVersion) { final byte[] payload = Arrays.copyOfRange(data, withVersion ? 1 : 0, data.length - CHECKSUM_SIZE); final byte[] checksum = Arrays.copyOfRange(data, data.length - CHECKSUM_SIZE, data.length); final byte[] expectedChecksum = checksum(payload); @@ -132,7 +131,7 @@ private static byte[] verifyAndRemoveChecksum(byte[] data, boolean withVersion) * @param payload Base58数据(不含校验码) * @return Base58数据 */ - private static byte[] addChecksum(Integer version, byte[] payload) { + private static byte[] addChecksum(final Integer version, final byte[] payload) { final byte[] addressBytes; if (null != version) { addressBytes = new byte[1 + payload.length + CHECKSUM_SIZE]; @@ -154,8 +153,8 @@ private static byte[] addChecksum(Integer version, byte[] payload) { * @param data 数据 * @return 校验码 */ - private static byte[] checksum(byte[] data) { - byte[] hash = hash256(hash256(data)); + private static byte[] checksum(final byte[] data) { + final byte[] hash = hash256(hash256(data)); return Arrays.copyOfRange(hash, 0, CHECKSUM_SIZE); } @@ -165,11 +164,11 @@ private static byte[] checksum(byte[] data) { * @param data 数据 * @return sha-256值 */ - private static byte[] hash256(byte[] data) { + private static byte[] hash256(final byte[] data) { try { return MessageDigest.getInstance(Algorithm.SHA256.getValue()).digest(data); - } catch (NoSuchAlgorithmException e) { - throw new InternalException(e); + } catch (final NoSuchAlgorithmException e) { + throw new ValidateException(e); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Base62.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Base62.java index c21c2e3b0f..e8b2d0a9fb 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Base62.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Base62.java @@ -26,6 +26,7 @@ package org.aoju.bus.core.codec; import org.aoju.bus.core.codec.provider.Base62Provider; +import org.aoju.bus.core.lang.Charset; import org.aoju.bus.core.toolkit.FileKit; import org.aoju.bus.core.toolkit.IoKit; import org.aoju.bus.core.toolkit.StringKit; @@ -33,7 +34,6 @@ import java.io.File; import java.io.InputStream; import java.io.OutputStream; -import java.nio.charset.Charset; /** * Base62工具类,提供Base62的编码和解码方案 @@ -49,8 +49,8 @@ public class Base62 { * @param source 被编码的Base62字符串 * @return 被加密后的字符串 */ - public static String encode(CharSequence source) { - return encode(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String encode(final CharSequence source) { + return encode(source, Charset.UTF_8); } /** @@ -60,7 +60,7 @@ public static String encode(CharSequence source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encode(CharSequence source, Charset charset) { + public static String encode(final CharSequence source, final java.nio.charset.Charset charset) { return encode(StringKit.bytes(source, charset)); } @@ -70,7 +70,7 @@ public static String encode(CharSequence source, Charset charset) { * @param source 被编码的Base62字符串 * @return 被加密后的字符串 */ - public static String encode(byte[] source) { + public static String encode(final byte[] source) { return new String(Base62Provider.INSTANCE.encode(source)); } @@ -80,7 +80,7 @@ public static String encode(byte[] source) { * @param in 被编码Base62的流(一般为图片流或者文件流) * @return 被加密后的字符串 */ - public static String encode(InputStream in) { + public static String encode(final InputStream in) { return encode(IoKit.readBytes(in)); } @@ -90,7 +90,7 @@ public static String encode(InputStream in) { * @param file 被编码Base62的文件 * @return 被加密后的字符串 */ - public static String encode(File file) { + public static String encode(final File file) { return encode(FileKit.readBytes(file)); } @@ -100,8 +100,8 @@ public static String encode(File file) { * @param source 被编码的Base62字符串 * @return 被加密后的字符串 */ - public static String encodeInverted(CharSequence source) { - return encodeInverted(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String encodeInverted(final CharSequence source) { + return encodeInverted(source, Charset.UTF_8); } /** @@ -111,7 +111,7 @@ public static String encodeInverted(CharSequence source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encodeInverted(CharSequence source, Charset charset) { + public static String encodeInverted(final CharSequence source, final java.nio.charset.Charset charset) { return encodeInverted(StringKit.bytes(source, charset)); } @@ -121,7 +121,7 @@ public static String encodeInverted(CharSequence source, Charset charset) { * @param source 被编码的Base62字符串 * @return 被加密后的字符串 */ - public static String encodeInverted(byte[] source) { + public static String encodeInverted(final byte[] source) { return new String(Base62Provider.INSTANCE.encode(source, true)); } @@ -131,7 +131,7 @@ public static String encodeInverted(byte[] source) { * @param in 被编码Base62的流(一般为图片流或者文件流) * @return 被加密后的字符串 */ - public static String encodeInverted(InputStream in) { + public static String encodeInverted(final InputStream in) { return encodeInverted(IoKit.readBytes(in)); } @@ -141,18 +141,28 @@ public static String encodeInverted(InputStream in) { * @param file 被编码Base62的文件 * @return 被加密后的字符串 */ - public static String encodeInverted(File file) { + public static String encodeInverted(final File file) { return encodeInverted(FileKit.readBytes(file)); } /** * Base62解码 * - * @param source 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @return 被加密后的字符串 */ - public static String decodeStrGbk(CharSequence source) { - return decodeString(source, org.aoju.bus.core.lang.Charset.GBK); + public static byte[] decode(final CharSequence base62) { + return decode(StringKit.bytes(base62, Charset.UTF_8)); + } + + /** + * 解码Base62 + * + * @param base62 Base62输入 + * @return 解码后的bytes + */ + public static byte[] decode(final byte[] base62) { + return Base62Provider.INSTANCE.decode(base62); } /** @@ -161,8 +171,8 @@ public static String decodeStrGbk(CharSequence source) { * @param source 被解码的Base62字符串 * @return 被加密后的字符串 */ - public static String decodeString(CharSequence source) { - return decodeString(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String decodeString(final CharSequence source) { + return decodeString(source, Charset.UTF_8); } /** @@ -172,50 +182,30 @@ public static String decodeString(CharSequence source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String decodeString(CharSequence source, Charset charset) { + public static String decodeString(final CharSequence source, final java.nio.charset.Charset charset) { return StringKit.toString(decode(source), charset); } /** * Base62解码 * - * @param Base62 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @param destFile 目标文件 * @return 目标文件 */ - public static File decodeToFile(CharSequence Base62, File destFile) { - return FileKit.writeBytes(decode(Base62), destFile); + public static File decodeToFile(final CharSequence base62, final File destFile) { + return FileKit.writeBytes(decode(base62), destFile); } /** * Base62解码 * - * @param base62Str 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @param out 写出到的流 * @param isCloseOut 是否关闭输出流 */ - public static void decodeToStream(CharSequence base62Str, OutputStream out, boolean isCloseOut) { - IoKit.write(out, isCloseOut, decode(base62Str)); - } - - /** - * Base62解码 - * - * @param base62Str 被解码的Base62字符串 - * @return 被加密后的字符串 - */ - public static byte[] decode(CharSequence base62Str) { - return decode(StringKit.bytes(base62Str, org.aoju.bus.core.lang.Charset.UTF_8)); - } - - /** - * 解码Base62 - * - * @param base62bytes Base62输入 - * @return 解码后的bytes - */ - public static byte[] decode(byte[] base62bytes) { - return Base62Provider.INSTANCE.decode(base62bytes); + public static void decodeToStream(final CharSequence base62, final OutputStream out, final boolean isCloseOut) { + IoKit.write(out, isCloseOut, decode(base62)); } /** @@ -224,8 +214,8 @@ public static byte[] decode(byte[] base62bytes) { * @param source 被解码的Base62字符串 * @return 被加密后的字符串 */ - public static String decodeStrInverted(CharSequence source) { - return decodeStrInverted(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String decodeStrInverted(final CharSequence source) { + return decodeStrInverted(source, Charset.UTF_8); } /** @@ -235,7 +225,7 @@ public static String decodeStrInverted(CharSequence source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String decodeStrInverted(CharSequence source, Charset charset) { + public static String decodeStrInverted(final CharSequence source, final java.nio.charset.Charset charset) { return StringKit.toString(decodeInverted(source), charset); } @@ -246,38 +236,39 @@ public static String decodeStrInverted(CharSequence source, Charset charset) { * @param destFile 目标文件 * @return 目标文件 */ - public static File decodeToFileInverted(CharSequence Base62, File destFile) { + public static File decodeToFileInverted(final CharSequence Base62, final File destFile) { return FileKit.writeBytes(decodeInverted(Base62), destFile); } /** * Base62解码(反转字母表模式) * - * @param base62Str 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @param out 写出到的流 * @param isCloseOut 是否关闭输出流 */ - public static void decodeToStreamInverted(CharSequence base62Str, OutputStream out, boolean isCloseOut) { - IoKit.write(out, isCloseOut, decodeInverted(base62Str)); + public static void decodeToStreamInverted(final CharSequence base62, final OutputStream out, final boolean isCloseOut) { + IoKit.write(out, isCloseOut, decodeInverted(base62)); } /** * Base62解码(反转字母表模式) * - * @param base62Str 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @return 被加密后的字符串 */ - public static byte[] decodeInverted(CharSequence base62Str) { - return decodeInverted(StringKit.bytes(base62Str, org.aoju.bus.core.lang.Charset.UTF_8)); + public static byte[] decodeInverted(final CharSequence base62) { + return decodeInverted(StringKit.bytes(base62, Charset.UTF_8)); } /** * 解码Base62(反转字母表模式) * - * @param base62bytes Base62输入 + * @param base62 Base62输入 * @return 解码后的bytes */ - public static byte[] decodeInverted(byte[] base62bytes) { - return Base62Provider.INSTANCE.decode(base62bytes, true); + public static byte[] decodeInverted(final byte[] base62) { + return Base62Provider.INSTANCE.decode(base62, true); } + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Base64.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Base64.java old mode 100644 new mode 100755 index ee2f3346f3..59f803c2b8 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Base64.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Base64.java @@ -38,9 +38,9 @@ import java.io.OutputStream; /** - * Base64工具类,提供Base64的编码和解码方案 - * base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符, - * 也就是三位二进制数组经过编码后变为四位的ASCII字符显示,长度比原来增加1/3 + * Base64工具类,提供Base64的编码和解码方案 + * base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符, + * 也就是三位二进制数组经过编码后变为四位的ASCII字符显示,长度比原来增加1/3 * * @author Kimi Liu * @since Java 17+ @@ -56,7 +56,7 @@ public class Base64 { * @param lineSep 在76个char之后是CRLF还是EOF * @return 编码后的bytes */ - public static byte[] encode(byte[] arr, boolean lineSep) { + public static byte[] encode(final byte[] arr, final boolean lineSep) { return lineSep ? java.util.Base64.getMimeEncoder().encode(arr) : java.util.Base64.getEncoder().encode(arr); @@ -68,8 +68,8 @@ public static byte[] encode(byte[] arr, boolean lineSep) { * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encode(CharSequence source) { - return encode(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String encode(final CharSequence source) { + return encode(source, Charset.UTF_8); } /** @@ -78,8 +78,8 @@ public static String encode(CharSequence source) { * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encodeUrlSafe(CharSequence source) { - return encodeUrlSafe(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String encodeUrlSafe(final CharSequence source) { + return encodeUrlSafe(source, Charset.UTF_8); } /** @@ -93,25 +93,14 @@ public static String encode(CharSequence source, String charset) { return encode(source, org.aoju.bus.core.lang.Charset.charset(charset)); } - /** - * base64编码,不进行padding(末尾不会填充'=') - * - * @param source 被编码的base64字符串 - * @param charset 编码 - * @return 被加密后的字符串 - */ - public static String encodeWithoutPadding(CharSequence source, String charset) { - return encodeWithoutPadding(StringKit.bytes(source, charset)); - } - /** * base64编码 * * @param source 被编码的base64字符串 * @param charset 字符集 - * @return 被加密后的字符串 + * @return 被编码后的字符串 */ - public static String encode(CharSequence source, java.nio.charset.Charset charset) { + public static String encode(final CharSequence source, final java.nio.charset.Charset charset) { return encode(StringKit.bytes(source, charset)); } @@ -122,7 +111,7 @@ public static String encode(CharSequence source, java.nio.charset.Charset charse * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encodeUrlSafe(CharSequence source, java.nio.charset.Charset charset) { + public static String encodeUrlSafe(final CharSequence source, final java.nio.charset.Charset charset) { return encodeUrlSafe(StringKit.bytes(source, charset)); } @@ -132,7 +121,7 @@ public static String encodeUrlSafe(CharSequence source, java.nio.charset.Charset * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encode(byte[] source) { + public static String encode(final byte[] source) { return java.util.Base64.getEncoder().encodeToString(source); } @@ -142,7 +131,7 @@ public static String encode(byte[] source) { * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encodeWithoutPadding(byte[] source) { + public static String encodeWithoutPadding(final byte[] source) { return java.util.Base64.getEncoder().withoutPadding().encodeToString(source); } @@ -152,7 +141,7 @@ public static String encodeWithoutPadding(byte[] source) { * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encodeUrlSafe(byte[] source) { + public static String encodeUrlSafe(final byte[] source) { return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(source); } @@ -162,7 +151,7 @@ public static String encodeUrlSafe(byte[] source) { * @param in 被编码base64的流(一般为图片流或者文件流) * @return 被加密后的字符串 */ - public static String encode(InputStream in) { + public static String encode(final InputStream in) { return encode(IoKit.readBytes(in)); } @@ -172,7 +161,7 @@ public static String encode(InputStream in) { * @param in 被编码base64的流(一般为图片流或者文件流) * @return 被加密后的字符串 */ - public static String encodeUrlSafe(InputStream in) { + public static String encodeUrlSafe(final InputStream in) { return encodeUrlSafe(IoKit.readBytes(in)); } @@ -182,7 +171,7 @@ public static String encodeUrlSafe(InputStream in) { * @param file 被编码base64的文件 * @return 被加密后的字符串 */ - public static String encode(File file) { + public static String encode(final File file) { return encode(FileKit.readBytes(file)); } @@ -192,7 +181,7 @@ public static String encode(File file) { * @param file 被编码base64的文件 * @return 被加密后的字符串 */ - public static String encodeUrlSafe(File file) { + public static String encodeUrlSafe(final File file) { return encodeUrlSafe(FileKit.readBytes(file)); } @@ -321,19 +310,9 @@ public static void encode(byte[] src, int srcPos, int srcLen, char[] dest, * base64解码 * * @param source 被解码的base64字符串 - * @return 解码后的字符串 - */ - public static String decodeStrGbk(CharSequence source) { - return StringKit.toString(decode(source), Charset.GBK); - } - - /** - * base64解码 - * - * @param source 被解码的base64字符串 - * @return 解码后的字符串 + * @return 被加密后的字符串 */ - public static String decodeString(CharSequence source) { + public static String decodeString(final CharSequence source) { return decodeString(source, Charset.UTF_8); } @@ -355,7 +334,7 @@ public static String decodeString(CharSequence source, String charset) { * @param charset 字符集 * @return 解码后的字符串 */ - public static String decodeString(CharSequence source, java.nio.charset.Charset charset) { + public static String decodeString(final CharSequence source, final java.nio.charset.Charset charset) { return StringKit.toString(decode(source), charset); } @@ -366,7 +345,7 @@ public static String decodeString(CharSequence source, java.nio.charset.Charset * @param destFile 目标文件 * @return 目标文件 */ - public static File decodeToFile(CharSequence base64, File destFile) { + public static File decodeToFile(final CharSequence base64, final File destFile) { return FileKit.writeBytes(decode(base64), destFile); } @@ -377,7 +356,7 @@ public static File decodeToFile(CharSequence base64, File destFile) { * @param out 写出到的流 * @param isCloseOut 是否关闭输出流 */ - public static void decodeToStream(CharSequence base64, OutputStream out, boolean isCloseOut) { + public static void decodeToStream(final CharSequence base64, final OutputStream out, final boolean isCloseOut) { IoKit.write(out, isCloseOut, decode(base64)); } @@ -387,7 +366,7 @@ public static void decodeToStream(CharSequence base64, OutputStream out, boolean * @param base64 被解码的base64字符串 * @return 解码后的bytes */ - public static byte[] decode(CharSequence base64) { + public static byte[] decode(final CharSequence base64) { return decode(StringKit.bytes(base64, Charset.UTF_8)); } @@ -485,12 +464,12 @@ public static void decode(char[] ch, int off, int len, OutputStream out) { * @param base64 Base64的bytes * @return 是否为Base64 */ - public static boolean isBase64(CharSequence base64) { + public static boolean isBase64(final CharSequence base64) { if (base64 == null || base64.length() < 2) { return false; } - byte[] bytes = StringKit.bytes(base64, Charset.UTF_8); + final byte[] bytes = StringKit.bytes(base64); if (bytes.length != base64.length()) { // 如果长度不相等,说明存在双字节字符,肯定不是Base64,直接返回false @@ -506,12 +485,12 @@ public static boolean isBase64(CharSequence base64) { * @param base64Bytes Base64的bytes * @return 是否为Base64 */ - public static boolean isBase64(byte[] base64Bytes) { + public static boolean isBase64(final byte[] base64Bytes) { if (base64Bytes == null || base64Bytes.length < 3) { return false; } boolean hasPadding = false; - for (byte base64Byte : base64Bytes) { + for (final byte base64Byte : base64Bytes) { if (hasPadding) { if (Symbol.C_EQUAL != base64Byte) { // 前一个字符是'=',则后边的字符都必须是'=',即'='只能都位于结尾 @@ -527,16 +506,6 @@ public static boolean isBase64(byte[] base64Bytes) { return true; } - /** - * 给定的字符是否为Base64字符 - * - * @param octet 被检查的字符 - * @return 是否为Base64字符 - */ - public static boolean isBase64Code(byte octet) { - return octet == Symbol.C_EQUAL || (octet >= 0 && octet < Normal.DECODE_64_TABLE.length && Normal.DECODE_64_TABLE[octet] != -1); - } - /** * 获取下一个有效的byte字符 * @@ -561,6 +530,16 @@ private static byte getNextValidDecodeByte(byte[] in, MutableInt pos, int maxPos return PADDING; } + /** + * 给定的字符是否为Base64字符 + * + * @param octet 被检查的字符 + * @return 是否为Base64字符 + */ + public static boolean isBase64Code(byte octet) { + return octet == Symbol.C_EQUAL || (octet >= 0 && octet < Normal.DECODE_64_TABLE.length && Normal.DECODE_64_TABLE[octet] != -1); + } + private static boolean isWhiteSpace(byte byteToCheck) { switch (byteToCheck) { case Symbol.C_SPACE: diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Caesar.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Caesar.java index 5b28c89413..54dfc46307 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Caesar.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Caesar.java @@ -37,13 +37,13 @@ public class Caesar { /** - * 传入明文,加密得到密文 + * 传入明文,加密得到密文 * * @param message 加密的消息 * @param offset 偏移量 * @return 加密后的内容 */ - public static String encode(String message, int offset) { + public static String encode(final String message, final int offset) { Assert.notNull(message, "message must be not null!"); final int len = message.length(); final char[] plain = message.toCharArray(); @@ -61,17 +61,17 @@ public static String encode(String message, int offset) { /** * 传入明文解密到密文 * - * @param cipher 密文 - * @param offset 偏移量 + * @param cipherText 密文 + * @param offset 偏移量 * @return 解密后的内容 */ - public static String decode(String cipher, int offset) { - Assert.notNull(cipher, "cipherText must be not null!"); - final int len = cipher.length(); - final char[] plain = cipher.toCharArray(); + public static String decode(final String cipherText, final int offset) { + Assert.notNull(cipherText, "cipherText must be not null!"); + final int len = cipherText.length(); + final char[] plain = cipherText.toCharArray(); char c; for (int i = 0; i < len; i++) { - c = cipher.charAt(i); + c = cipherText.charAt(i); if (false == Character.isLetter(c)) { continue; } @@ -87,8 +87,8 @@ public static String decode(String cipher, int offset) { * @param offset 偏移量 * @return 加密后的字符 */ - private static char encodeChar(char c, int offset) { - int position = (Normal.UPPER_LOWER.indexOf(c) + offset) % 52; + private static char encodeChar(final char c, final int offset) { + final int position = (Normal.UPPER_LOWER.indexOf(c) + offset) % 52; return Normal.UPPER_LOWER.charAt(position); } @@ -100,7 +100,7 @@ private static char encodeChar(char c, int offset) { * @param offset 偏移量 * @return 解密后的字符 */ - private static char decodeChar(char c, int offset) { + private static char decodeChar(final char c, final int offset) { int position = (Normal.UPPER_LOWER.indexOf(c) - offset) % 52; if (position < 0) { position += 52; diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Hashids.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Hashids.java index 94550b0159..501942ff21 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Hashids.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Hashids.java @@ -25,6 +25,8 @@ ********************************************************************************/ package org.aoju.bus.core.codec; +import org.aoju.bus.core.lang.Normal; + import java.math.BigInteger; import java.util.*; import java.util.regex.Matcher; @@ -44,7 +46,7 @@ * * *

    - * 来自:https://github.com/davidafsilva/java-hashids + * 来自:https://github.com/davidafsilva/java-hashids *

    * *

    @@ -57,38 +59,39 @@ */ public class Hashids implements Encoder, Decoder { - // 默认编解码字符串 - public static final char[] DEFAULT_ALPHABET = { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' - }; private static final int LOTTERY_MOD = 100; private static final double GUARD_THRESHOLD = 12; private static final double SEPARATOR_THRESHOLD = 3.5; - // 最小编解码字符串 + /** + * 最小编解码字符串 + */ private static final int MIN_ALPHABET_LENGTH = 16; private static final Pattern HEX_VALUES_PATTERN = Pattern.compile("[\\w\\W]{1,12}"); - // 默认分隔符 + /** + * 默认分隔符 + */ private static final char[] DEFAULT_SEPARATORS = { 'c', 'f', 'h', 'i', 's', 't', 'u', 'C', 'F', 'H', 'I', 'S', 'T', 'U' }; - - // algorithm properties + /** + * 算法属性 + */ private final char[] alphabet; - // 多个数字编解码的分界符 + /** + * 多个数字编解码的分界符 + */ private final char[] separators; private final Set separatorsSet; private final char[] salt; - // 补齐至 minLength 长度添加的字符列表 + /** + * 补齐至 minLength 长度添加的字符列表 + */ private final char[] guards; - // 编码后最小的字符长度 + /** + * 编码后最小的字符长度 + */ private final int minLength; - // region create - /** * 构造 * @@ -145,26 +148,25 @@ public Hashids(final char[] salt, final char[] alphabet, final int minLength) { } /** - * 根据参数值,创建{@code Hashids},使用默认{@link #DEFAULT_ALPHABET}作为字母表,不限制最小长度 + * 根据参数值,创建{@code Hashids},使用默认{@link Normal#LOWER_UPPER_NUMBER}作为字母表,不限制最小长度 * * @param salt 加盐值 * @return {@code Hashids} */ - public static Hashids create(final char[] salt) { - return create(salt, DEFAULT_ALPHABET, -1); + public static Hashids of(final char[] salt) { + return of(salt, Normal.LOWER_UPPER_NUMBER.toCharArray(), -1); } /** - * 根据参数值,创建{@code Hashids},使用默认{@link #DEFAULT_ALPHABET}作为字母表 + * 根据参数值,创建{@code Hashids},使用默认{@link Normal#LOWER_UPPER_NUMBER}作为字母表 * * @param salt 加盐值 * @param minLength 限制最小长度,-1表示不限制 * @return {@code Hashids} */ - public static Hashids create(final char[] salt, final int minLength) { - return create(salt, DEFAULT_ALPHABET, minLength); + public static Hashids of(final char[] salt, final int minLength) { + return of(salt, Normal.LOWER_UPPER_NUMBER.toCharArray(), minLength); } - // endregion /** * 根据参数值,创建{@code Hashids} @@ -174,7 +176,7 @@ public static Hashids create(final char[] salt, final int minLength) { * @param minLength 限制最小长度,-1表示不限制 * @return {@code Hashids} */ - public static Hashids create(final char[] salt, final char[] alphabet, final int minLength) { + public static Hashids of(final char[] salt, final char[] alphabet, final int minLength) { return new Hashids(salt, alphabet, minLength); } @@ -250,7 +252,7 @@ public String encode(final long... numbers) { // append the separator, if more numbers are pending encoding if (idx + 1 < numbers.length) { - long n = numbers[idx] % (global.charAt(initialLength) + 1); + final long n = numbers[idx] % (global.charAt(initialLength) + 1); global.append(separators[(int) (n % separators.length)]); } }); @@ -274,7 +276,7 @@ public String encode(final long... numbers) { final int initialSize = global.length(); if (paddingLeft > currentAlphabet.length) { // entire alphabet with the current encoding in the middle of it - int offset = alphabetHalfSize + (currentAlphabet.length % 2 == 0 ? 0 : 1); + final int offset = alphabetHalfSize + (currentAlphabet.length % 2 == 0 ? 0 : 1); global.insert(0, currentAlphabet, alphabetHalfSize, offset); global.insert(offset + initialSize, currentAlphabet, 0, alphabetHalfSize); @@ -297,10 +299,6 @@ public String encode(final long... numbers) { return global.toString(); } - //------------------------- - // Decode - //------------------------- - /** * 解码Hash值为16进制数字 * @@ -450,7 +448,7 @@ private char[] deriveNewAlphabet(final char[] alphabet, final char[] salt, final int offset = 1; // 2. salt if (salt.length > 0 && spaceLeft > 0) { - int length = Math.min(salt.length, spaceLeft); + final int length = Math.min(salt.length, spaceLeft); System.arraycopy(salt, 0, newSalt, offset, length); spaceLeft -= length; offset += length; @@ -492,7 +490,7 @@ private char[] validateAndFilterAlphabet(final char[] alphabet, final char[] sep // create a new alphabet without the duplicates final char[] uniqueAlphabet = new char[seen.size()]; int idx = 0; - for (char c : seen) { + for (final char c : seen) { uniqueAlphabet[idx++] = c; } return uniqueAlphabet; @@ -523,4 +521,5 @@ private char[] shuffle(final char[] alphabet, final char[] salt) { } return alphabet; } + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Morse.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Morse.java index 43a70bc271..d615f8bc18 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Morse.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Morse.java @@ -41,11 +41,17 @@ */ public class Morse { + /** + * code point -> morse + */ private static final Map ALPHABETS = new HashMap<>(); + /** + * morse -> code point + */ private static final Map DICTIONARIES = new HashMap<>(); static { - // Letters + // 字母 registerMorse('A', "01"); registerMorse('B', "1000"); registerMorse('C', "1010"); @@ -72,7 +78,7 @@ public class Morse { registerMorse('X', "1001"); registerMorse('Y', "1011"); registerMorse('Z', "1100"); - // Numbers + // 数字 registerMorse(Symbol.C_ZERO, "11111"); registerMorse(Symbol.C_ONE, "01111"); registerMorse(Symbol.C_TWO, "00111"); @@ -83,7 +89,7 @@ public class Morse { registerMorse(Symbol.C_SEVEN, "11000"); registerMorse(Symbol.C_EIGHT, "11100"); registerMorse(Symbol.C_NINE, "11110"); - // Punctuation + // 符号 registerMorse(Symbol.C_DOT, "010101"); registerMorse(Symbol.C_COMMA, "110011"); registerMorse(Symbol.C_QUESTION_MARK, "001100"); @@ -104,8 +110,17 @@ public class Morse { registerMorse(Symbol.C_AT, "011010"); } + /** + * 短标记或小点 + */ private final char dit; + /** + * 较长的标记或破折号 + */ private final char dah; + /** + * 分割符号 + */ private final char split; /** @@ -122,7 +137,7 @@ public Morse() { * @param dah 横线表示的字符 * @param split 分隔符 */ - public Morse(char dit, char dah, char split) { + public Morse(final char dit, final char dah, final char split) { this.dit = dit; this.dah = dah; this.split = split; @@ -152,7 +167,7 @@ public String encode(String text) { final StringBuilder morseBuilder = new StringBuilder(); final int len = text.codePointCount(0, text.length()); for (int i = 0; i < len; i++) { - int codePoint = text.codePointAt(i); + final int codePoint = text.codePointAt(i); String word = ALPHABETS.get(codePoint); if (null == word) { word = Integer.toBinaryString(codePoint); @@ -168,7 +183,7 @@ public String encode(String text) { * @param morse 莫尔斯电码 * @return 明文 */ - public String decode(String morse) { + public String decode(final String morse) { Assert.notNull(morse, "Morse should not be null."); final char dit = this.dit; diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Percent.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Percent.java index 85d7aa08b8..f85413d1b0 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Percent.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Percent.java @@ -25,9 +25,11 @@ ********************************************************************************/ package org.aoju.bus.core.codec; +import org.aoju.bus.core.codec.provider.Base16Provider; import org.aoju.bus.core.lang.Assert; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.core.toolkit.ArrayKit; import org.aoju.bus.core.toolkit.HexKit; import org.aoju.bus.core.toolkit.StringKit; @@ -35,6 +37,7 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Serializable; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.BitSet; @@ -49,9 +52,10 @@ * @author Kimi Liu * @since Java 17+ */ -public class Percent implements Serializable { +public class Percent implements Encoder, Serializable { private static final long serialVersionUID = 1L; + /** * 存放安全编码 */ @@ -76,133 +80,71 @@ public Percent() { * * @param safeCharacters 安全字符,安全字符不被编码 */ - public Percent(BitSet safeCharacters) { + public Percent(final BitSet safeCharacters) { this.safeCharacters = safeCharacters; } /** - * 从已知Percent创建Percent,会复制给定Percent的安全字符 - * - * @param codec Percent - * @return this - */ - public static Percent of(Percent codec) { - return new Percent((BitSet) codec.safeCharacters.clone()); - } - - /** - * 创建Percent,使用指定字符串中的字符作为安全字符 - * - * @param chars 安全字符合集 - * @return this - */ - public static Percent of(CharSequence chars) { - Assert.notNull(chars, "chars must not be null"); - final Percent codec = new Percent(); - final int length = chars.length(); - for (int i = 0; i < length; i++) { - codec.addSafe(chars.charAt(i)); - } - return codec; - } - - /** - * 增加安全字符 - * 安全字符不被编码 - * - * @param c 字符 - * @return this - */ - public Percent addSafe(char c) { - safeCharacters.set(c); - return this; - } - - /** - * 移除安全字符 - * 安全字符不被编码 + * 检查给定字符是否为安全字符 * * @param c 字符 - * @return this - */ - public Percent removeSafe(char c) { - safeCharacters.clear(c); - return this; - } - - /** - * 增加安全字符到挡墙的Percent - * - * @param codec Percent - * @return this + * @return {@code true}表示安全,否则非安全字符 */ - public Percent or(Percent codec) { - this.safeCharacters.or(codec.safeCharacters); - return this; + public boolean isSafe(final char c) { + return this.safeCharacters.get(c); } - /** - * 组合当前Percent和指定Percent为一个新的Percent,安全字符为并集 - * - * @param codec Percent - * @return this - */ - public Percent orNew(Percent codec) { - return of(this).or(codec); - } + @Override + public byte[] encode(final byte[] bytes) { + // 初始容量计算,简单粗暴假设所有byte都需要转义,容量是三倍 + final ByteBuffer buffer = ByteBuffer.allocate(bytes.length * 3); + for (int i = 0; i < bytes.length; i++) { + encodeTo(buffer, bytes[i]); + } - /** - * 是否将空格编码为+ - * 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用 - * 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范) - * - * @param encodeSpaceAsPlus 是否将空格编码为+ - * @return this - */ - public Percent setEncodeSpaceAsPlus(boolean encodeSpaceAsPlus) { - this.encodeSpaceAsPlus = encodeSpaceAsPlus; - return this; + return buffer.array(); } /** * 将URL中的字符串编码为%形式 * - * @param path 需要编码的字符串 - * @param charset 编码, {@code null}返回原字符串,表示不编码 + * @param path 需要编码的字符串 + * @param charset 编码, {@code null}返回原字符串,表示不编码 + * @param customSafeChar 自定义安全字符 * @return 编码后的字符串 */ - public String encode(CharSequence path, Charset charset) { + public String encode(final CharSequence path, final Charset charset, final char... customSafeChar) { if (null == charset || StringKit.isEmpty(path)) { return StringKit.toString(path); } - final StringBuilder rewrittenPath = new StringBuilder(path.length()); + final StringBuilder rewrittenPath = new StringBuilder(path.length() * 3); final ByteArrayOutputStream buf = new ByteArrayOutputStream(); final OutputStreamWriter writer = new OutputStreamWriter(buf, charset); - int c; + char c; for (int i = 0; i < path.length(); i++) { c = path.charAt(i); - if (safeCharacters.get(c)) { - rewrittenPath.append((char) c); + if (safeCharacters.get(c) || ArrayKit.contains(customSafeChar, c)) { + rewrittenPath.append(c); } else if (encodeSpaceAsPlus && c == Symbol.C_SPACE) { // 对于空格单独处理 - rewrittenPath.append('+'); + rewrittenPath.append(Symbol.C_PLUS); } else { // convert to external encoding before hex conversion try { - writer.write((char) c); + writer.write(c); writer.flush(); - } catch (IOException e) { + } catch (final IOException e) { buf.reset(); continue; } // 兼容双字节的Unicode符处理(如部分emoji) - byte[] ba = buf.toByteArray(); - for (byte toEncode : ba) { + final byte[] ba = buf.toByteArray(); + for (final byte toEncode : ba) { // Converting each byte in the buffer - rewrittenPath.append('%'); + rewrittenPath.append(Symbol.C_PERCENT); HexKit.appendHex(rewrittenPath, toEncode, false); } buf.reset(); @@ -211,4 +153,132 @@ public String encode(CharSequence path, Charset charset) { return rewrittenPath.toString(); } + /** + * 将单一byte转义到{@link ByteBuffer}中 + * + * @param buffer {@link ByteBuffer} + * @param b 字符byte + */ + private void encodeTo(final ByteBuffer buffer, final byte b) { + if (safeCharacters.get(b)) { + // 跳过安全字符 + buffer.put(b); + } else if (encodeSpaceAsPlus && b == Symbol.C_SPACE) { + // 对于空格单独处理 + buffer.put((byte) Symbol.C_PLUS); + } else { + buffer.put((byte) Symbol.C_PERCENT); + buffer.put((byte) Base16Provider.CODEC_UPPER.hexDigit(b >> 4)); + buffer.put((byte) Base16Provider.CODEC_UPPER.hexDigit(b)); + } + } + + /** + * {@link Percent}构建器 + * 由于{@link Percent}本身应该是只读对象,因此将此对象的构建放在Builder中 + */ + public static class Builder implements org.aoju.bus.core.builder.Builder { + + private static final long serialVersionUID = 1L; + private final Percent codec; + + private Builder(final Percent codec) { + this.codec = codec; + } + + /** + * 从已知Percent创建Percent,会复制给定Percent的安全字符 + * + * @param codec Percent + * @return this + */ + public static Builder of(final Percent codec) { + return new Builder(new Percent((BitSet) codec.safeCharacters.clone())); + } + + /** + * 创建Percent,使用指定字符串中的字符作为安全字符 + * + * @param chars 安全字符合集 + * @return this + */ + public static Builder of(final CharSequence chars) { + Assert.notNull(chars, "chars must not be null"); + final Builder builder = of(new Percent()); + final int length = chars.length(); + for (int i = 0; i < length; i++) { + builder.addSafe(chars.charAt(i)); + } + return builder; + } + + /** + * 增加安全字符 + * 安全字符不被编码 + * + * @param c 字符 + * @return this + */ + public Builder addSafe(final char c) { + codec.safeCharacters.set(c); + return this; + } + + /** + * 增加安全字符 + * 安全字符不被编码 + * + * @param chars 安全字符 + * @return this + */ + public Builder addSafes(final String chars) { + final int length = chars.length(); + for (int i = 0; i < length; i++) { + addSafe(chars.charAt(i)); + } + return this; + } + + /** + * 移除安全字符 + * 安全字符不被编码 + * + * @param c 字符 + * @return this + */ + public Builder removeSafe(final char c) { + codec.safeCharacters.clear(c); + return this; + } + + /** + * 增加安全字符到当前的Percent + * + * @param other {@link Percent} + * @return this + */ + public Builder or(final Percent other) { + codec.safeCharacters.or(other.safeCharacters); + return this; + } + + /** + * 是否将空格编码为+ + * 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用 + * 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范) + * + * @param encodeSpaceAsPlus 是否将空格编码为+ + * @return this + */ + public Builder setEncodeSpaceAsPlus(final boolean encodeSpaceAsPlus) { + codec.encodeSpaceAsPlus = encodeSpaceAsPlus; + return this; + } + + @Override + public Percent build() { + return codec; + } + } + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/PunyCode.java b/bus-core/src/main/java/org/aoju/bus/core/codec/PunyCode.java index a369727170..fe795587ff 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/PunyCode.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/PunyCode.java @@ -30,6 +30,8 @@ import org.aoju.bus.core.lang.Symbol; import org.aoju.bus.core.toolkit.StringKit; +import java.util.List; + /** * Punycode是一个根据RFC 3492标准而制定的编码系统,主要用于把域名 * 从地方语言所采用的Unicode编码转换成为可用于DNS系统的编码 @@ -57,7 +59,7 @@ public class PunyCode { * @return PunyCode字符串 * @throws InternalException 计算异常 */ - public static String encode(CharSequence input) throws InternalException { + public static String encode(final CharSequence input) throws InternalException { return encode(input, false); } @@ -69,17 +71,17 @@ public static String encode(CharSequence input) throws InternalException { * @return PunyCode字符串 * @throws InternalException 计算异常 */ - public static String encode(CharSequence input, boolean withPrefix) throws InternalException { + public static String encode(final CharSequence input, final boolean withPrefix) throws InternalException { Assert.notNull(input, "input must not be null!"); int n = INITIAL_N; int delta = 0; int bias = INITIAL_BIAS; - StringBuilder output = new StringBuilder(); - // Copy all basic code points to the output final int length = input.length(); + final StringBuilder output = new StringBuilder(length * 4); + // Copy all basic code points to the output int b = 0; for (int i = 0; i < length; i++) { - char c = input.charAt(i); + final char c = input.charAt(i); if (isBasic(c)) { output.append(c); b++; @@ -109,7 +111,7 @@ public static String encode(CharSequence input, boolean withPrefix) throws Inter delta = delta + (m - n) * (h + 1); n = m; for (int j = 0; j < length; j++) { - int c = input.charAt(j); + final int c = input.charAt(j); if (c < n) { delta++; if (0 == delta) { @@ -119,7 +121,7 @@ public static String encode(CharSequence input, boolean withPrefix) throws Inter if (c == n) { int q = delta; for (int k = BASE; ; k += BASE) { - int t; + final int t; if (k <= bias) { t = TMIN; } else if (k >= bias + TMAX) { @@ -163,7 +165,8 @@ public static String decode(String input) throws InternalException { int n = INITIAL_N; int i = 0; int bias = INITIAL_BIAS; - StringBuilder output = new StringBuilder(); + final int length = input.length(); + final StringBuilder output = new StringBuilder(length / 4 + 1); int d = input.lastIndexOf(Symbol.C_MINUS); if (d > 0) { for (int j = 0; j < d; j++) { @@ -176,21 +179,20 @@ public static String decode(String input) throws InternalException { } else { d = 0; } - final int length = input.length(); while (d < length) { - int oldi = i; + final int oldi = i; int w = 1; for (int k = BASE; ; k += BASE) { if (d == length) { throw new InternalException("BAD_INPUT"); } - int c = input.charAt(d++); - int digit = codepoint2digit(c); + final int c = input.charAt(d++); + final int digit = codepoint2digit(c); if (digit > (Integer.MAX_VALUE - i) / w) { throw new InternalException("OVERFLOW"); } i = i + digit * w; - int t; + final int t; if (k <= bias) { t = TMIN; } else if (k >= bias + TMAX) { @@ -216,7 +218,49 @@ public static String decode(String input) throws InternalException { return output.toString(); } - private static int adapt(int delta, int numpoints, boolean first) { + /** + * 将域名编码为PunyCode,会忽略"."的编码 + * + * @param domain 域名 + * @return 编码后的域名 + * @throws InternalException 计算异常 + */ + public static String encodeDomain(final String domain) throws InternalException { + Assert.notNull(domain, "domain must not be null!"); + final List split = StringKit.split(domain, Symbol.C_DOT); + final StringBuilder result = new StringBuilder(domain.length() * 4); + for (final String str : split) { + if (result.length() != 0) { + result.append(Symbol.C_DOT); + } + result.append(encode(str, true)); + } + + return result.toString(); + } + + /** + * 解码 PunyCode为域名 + * + * @param domain 域名 + * @return 解码后的域名 + * @throws InternalException 计算异常 + */ + public static String decodeDomain(final String domain) throws InternalException { + Assert.notNull(domain, "domain must not be null!"); + final List split = StringKit.split(domain, Symbol.C_DOT); + final StringBuilder result = new StringBuilder(domain.length() / 4 + 1); + for (final String str : split) { + if (result.length() != 0) { + result.append(Symbol.C_DOT); + } + result.append(StringKit.startWithIgnoreEquals(str, PUNY_CODE_PREFIX) ? decode(str) : str); + } + + return result.toString(); + } + + private static int adapt(int delta, final int numpoints, final boolean first) { if (first) { delta = delta / DAMP; } else { @@ -231,7 +275,7 @@ private static int adapt(int delta, int numpoints, boolean first) { return k + ((BASE - TMIN + 1) * delta) / (delta + SKEW); } - private static boolean isBasic(char c) { + private static boolean isBasic(final char c) { return c < 0x80; } @@ -251,7 +295,7 @@ private static boolean isBasic(char c) { * @return 转换后的字符 * @throws InternalException 无效字符 */ - private static int digit2codepoint(int d) throws InternalException { + private static int digit2codepoint(final int d) throws InternalException { Assert.checkBetween(d, 0, 35); if (d < 26) { // 0..25 : 'a'..'z' @@ -280,7 +324,7 @@ private static int digit2codepoint(int d) throws InternalException { * @return 转换后的字符 * @throws InternalException 无效字符 */ - private static int codepoint2digit(int c) throws InternalException { + private static int codepoint2digit(final int c) throws InternalException { if (c - '0' < 10) { // '0'..'9' : 26..35 return c - '0' + 26; diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base16Provider.java b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base16Provider.java index fdd0ab92a7..351c46d059 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base16Provider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base16Provider.java @@ -28,6 +28,7 @@ import org.aoju.bus.core.codec.Decoder; import org.aoju.bus.core.codec.Encoder; import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.toolkit.StringKit; /** @@ -41,8 +42,14 @@ public class Base16Provider implements Encoder, Decoder { /** - * 字符信息 + * 编码解码器:小写 */ + public static final Base16Provider CODEC_LOWER = new Base16Provider(true); + /** + * 编码解码器:大写 + */ + public static final Base16Provider CODEC_UPPER = new Base16Provider(false); + private final char[] alphabets; /** @@ -50,8 +57,8 @@ public class Base16Provider implements Encoder, Decoder>> 4];// 高位 - out[j++] = alphabets[0x0F & data[i]];// 低位 + out[j++] = hexDigit(data[i] >> 4);// 高位 + out[j++] = hexDigit(data[i]);// 低位 } return out; } @@ -122,12 +129,12 @@ public byte[] decode(CharSequence encoded) { * @param ch char值 * @return Unicode表现形式 */ - public String toUnicodeHex(char ch) { + public String toUnicodeHex(final char ch) { return "\\u" + - alphabets[(ch >> 12) & 15] + - alphabets[(ch >> 8) & 15] + - alphabets[(ch >> 4) & 15] + - alphabets[(ch) & 15]; + hexDigit(ch >> 12) + + hexDigit(ch >> 8) + + hexDigit(ch >> 4) + + hexDigit(ch); } /** @@ -136,11 +143,21 @@ public String toUnicodeHex(char ch) { * @param builder {@link StringBuilder} * @param b byte */ - public void appendHex(StringBuilder builder, byte b) { - int high = (b & 0xf0) >>> 4;// 高位 - int low = b & 0x0f;// 低位 - builder.append(alphabets[high]); - builder.append(alphabets[low]); + public void appendHex(final StringBuilder builder, final byte b) { + // 高位 + builder.append(hexDigit(b >> 4)); + // 低位 + builder.append(hexDigit(b)); + } + + /** + * 将byte值转为16进制 + * + * @param b byte + * @return the char + */ + public char hexDigit(final int b) { + return alphabets[b & 0x0f]; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base32Provider.java b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base32Provider.java index 912cda01ac..0d13748f5c 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base32Provider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base32Provider.java @@ -52,7 +52,7 @@ public class Base32Provider implements Encoder, Decoder { * @param alphabet 自定义编码字母表,见 {@link #DEFAULT_ALPHABET}和 {@link #HEX_ALPHABET} * @param pad 补位字符 */ - public Base32Encoder(String alphabet, Character pad) { + public Base32Encoder(final String alphabet, final Character pad) { this.alphabet = alphabet.toCharArray(); this.pad = pad; } @Override - public String encode(byte[] data) { + public String encode(final byte[] data) { int i = 0; int index = 0; int digit; @@ -134,7 +134,7 @@ public String encode(byte[] data) { encodeLen = encodeLen + 1 + BASE32_FILL[(data.length * 8) % 5]; } - StringBuilder base32 = new StringBuilder(encodeLen); + final StringBuilder base32 = new StringBuilder(encodeLen); while (i < data.length) { // unsign @@ -201,7 +201,7 @@ public static class Base32Decoder implements Decoder { * * @param alphabet 编码字母表 */ - public Base32Decoder(String alphabet) { + public Base32Decoder(final String alphabet) { lookupTable = new byte[128]; Arrays.fill(lookupTable, (byte) -1); @@ -219,11 +219,11 @@ public Base32Decoder(String alphabet) { } @Override - public byte[] decode(CharSequence encoded) { + public byte[] decode(final CharSequence encoded) { int i, index, lookup, offset, digit; final String base32 = encoded.toString(); - int len = base32.endsWith("=") ? base32.indexOf("=") * 5 / 8 : base32.length() * 5 / 8; - byte[] bytes = new byte[len]; + final int len = base32.endsWith("=") ? base32.indexOf("=") * 5 / 8 : base32.length() * 5 / 8; + final byte[] bytes = new byte[len]; for (i = 0, index = 0, offset = 0; i < base32.length(); i++) { lookup = base32.charAt(i) - BASE_CHAR; diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base58Provider.java b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base58Provider.java index 7bfd5cc062..aec4ee0fbb 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base58Provider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base58Provider.java @@ -60,8 +60,8 @@ private static byte divmod(byte[] number, int firstDigit, int base, int divisor) // 用来表示输入数字的基数 int remainder = 0; for (int i = firstDigit; i < number.length; i++) { - int digit = (int) number[i] & 0xFF; - int temp = remainder * base + digit; + final int digit = (int) number[i] & 0xFF; + final int temp = remainder * base + digit; number[i] = (byte) (temp / divisor); remainder = temp % divisor; } @@ -75,7 +75,7 @@ private static byte divmod(byte[] number, int firstDigit, int base, int divisor) * @return 编码后的字符串 */ @Override - public String encode(byte[] data) { + public String encode(final byte[] data) { return Base58Encoder.ENCODER.encode(data); } @@ -87,7 +87,7 @@ public String encode(byte[] data) { * @throws IllegalArgumentException 非标准Base58字符串 */ @Override - public byte[] decode(CharSequence encoded) throws IllegalArgumentException { + public byte[] decode(final CharSequence encoded) throws IllegalArgumentException { return Base58Decoder.DECODER.decode(encoded); } @@ -95,11 +95,8 @@ public byte[] decode(CharSequence encoded) throws IllegalArgumentException { * Base58编码器 */ public static class Base58Encoder implements Encoder { + private static final String DEFAULT_ALPHABET = Normal.UPPER_NUMBER + "012345"; - /** - * 默认字符 - */ - private static final String DEFAULT_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; /** * 默认编码器 */ @@ -118,7 +115,7 @@ public static class Base58Encoder implements Encoder { * * @param alphabet 编码字母表 */ - public Base58Encoder(char[] alphabet) { + public Base58Encoder(final char[] alphabet) { this.alphabet = alphabet; alphabetZero = alphabet[0]; } @@ -177,7 +174,7 @@ public static class Base58Decoder implements Decoder { * * @param alphabet 编码字符表 */ - public Base58Decoder(String alphabet) { + public Base58Decoder(final String alphabet) { final byte[] lookupTable = new byte['z' + 1]; Arrays.fill(lookupTable, (byte) -1); @@ -189,15 +186,15 @@ public Base58Decoder(String alphabet) { } @Override - public byte[] decode(CharSequence encoded) { + public byte[] decode(final CharSequence encoded) { if (encoded.length() == 0) { return new byte[0]; } // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). final byte[] input58 = new byte[encoded.length()]; for (int i = 0; i < encoded.length(); ++i) { - char c = encoded.charAt(i); - int digit = c < 128 ? lookupTable[c] : -1; + final char c = encoded.charAt(i); + final int digit = c < 128 ? lookupTable[c] : -1; if (digit < 0) { throw new IllegalArgumentException(StringKit.format("Invalid char '{}' at [{}]", c, i)); } @@ -209,7 +206,7 @@ public byte[] decode(CharSequence encoded) { ++zeros; } // Convert base-58 digits to base-256 digits. - byte[] decoded = new byte[encoded.length()]; + final byte[] decoded = new byte[encoded.length()]; int outputStart = decoded.length; for (int inputStart = zeros; inputStart < input58.length; ) { decoded[--outputStart] = divmod(input58, inputStart, 58, 256); diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base62Provider.java b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base62Provider.java index cc902539e6..5fea76c3ee 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base62Provider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base62Provider.java @@ -27,6 +27,7 @@ import org.aoju.bus.core.codec.Decoder; import org.aoju.bus.core.codec.Encoder; +import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.toolkit.ArrayKit; import java.io.ByteArrayOutputStream; @@ -58,7 +59,7 @@ public class Base62Provider implements Encoder, Decoder { - /** - * GMP风格 - */ - private static final byte[] GMP = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', - 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', - 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', - 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', - 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', - 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z' - }; - - /** - * 反转风格,即将GMP风格中的大小写做转换 - */ - private static final byte[] INVERTED = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', - 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', - 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z' - }; - /** * GMP 编码器 */ - public static Base62Encoder GMP_ENCODER = new Base62Encoder(GMP); + public static Base62Encoder GMP_ENCODER = new Base62Encoder(Normal.UPPER_LOWER_NUMBER.getBytes()); /** * INVERTED 编码器 */ - public static Base62Encoder INVERTED_ENCODER = new Base62Encoder(INVERTED); + public static Base62Encoder INVERTED_ENCODER = new Base62Encoder(Normal.LOWER_UPPER_NUMBER.getBytes()); /** * 字符信息 */ @@ -222,12 +195,12 @@ public static class Base62Encoder implements Encoder { * * @param alphabet 字符表 */ - public Base62Encoder(byte[] alphabet) { + public Base62Encoder(final byte[] alphabet) { this.alphabet = alphabet; } @Override - public byte[] encode(byte[] data) { + public byte[] encode(final byte[] data) { final byte[] indices = convert(data, STANDARD_BASE, TARGET_BASE); return translate(indices, alphabet); } @@ -241,11 +214,11 @@ public static class Base62Decoder implements Decoder { /** * GMP 解码器 */ - public static Base62Decoder GMP_DECODER = new Base62Decoder(Base62Encoder.GMP); + public static Base62Decoder GMP_DECODER = new Base62Decoder(Normal.UPPER_LOWER_NUMBER.getBytes()); /** * INVERTED 解码器 */ - public static Base62Decoder INVERTED_DECODER = new Base62Decoder(Base62Encoder.INVERTED); + public static Base62Decoder INVERTED_DECODER = new Base62Decoder(Normal.LOWER_UPPER_NUMBER.getBytes()); /** * 查找表 */ @@ -256,7 +229,7 @@ public static class Base62Decoder implements Decoder { * * @param alphabet 字母表 */ - public Base62Decoder(byte[] alphabet) { + public Base62Decoder(final byte[] alphabet) { lookupTable = new byte['z' + 1]; for (int i = 0; i < alphabet.length; i++) { lookupTable[alphabet[i]] = (byte) i; @@ -265,7 +238,7 @@ public Base62Decoder(byte[] alphabet) { @Override - public byte[] decode(byte[] encoded) { + public byte[] decode(final byte[] encoded) { final byte[] prepared = translate(encoded, lookupTable); return convert(prepared, TARGET_BASE, STANDARD_BASE); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java b/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java index 98bd8f1407..14f542a5e2 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java @@ -128,7 +128,8 @@ private long doCopy(ReadableByteChannel source, WritableByteChannel target, Byte numToRead -= read; total += read; if (null != progress) { - progress.progress(this.count, total); + // 总长度未知的情况下,-1表示未知 + progress.progress(this.count < Long.MAX_VALUE ? this.count : -1, total); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/key/HashID.java b/bus-core/src/main/java/org/aoju/bus/core/key/HashID.java deleted file mode 100755 index a6e9a3c3ed..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/key/HashID.java +++ /dev/null @@ -1,457 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.key; - -import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.core.toolkit.NetKit; -import org.aoju.bus.core.toolkit.RuntimeKit; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Hashids用于从数字(如YouTube和Bitly)生成短散列, - * 数据库id,将它们用作忘记密码散列、邀请码、存储碎片号 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class HashID { - - /** - * Max number that can be encoded with Hashids. - */ - public static final long MAX_NUMBER = 9007199254740992L; - - private static final String DEFAULT_SEPS = "cfhistuCFHISTU"; - private static final String DEFAULT_SALT = Normal.EMPTY; - - private static final Pattern PATTERN = Pattern.compile("[\\w\\W]{1,12}"); - - private static final int DEFAULT_MIN_HASH_LENGTH = 0; - private static final int MIN_ALPHABET_LENGTH = Normal._16; - private static final double SEP_DIV = 3.5; - private static final int GUARD_DIV = 12; - - private final String salt; - private final int minHashLength; - private final String alphabet; - private final String seps; - private final String guards; - - public HashID() { - this(DEFAULT_SALT); - } - - public HashID(String salt) { - this(salt, 0); - } - - public HashID(String salt, int minHashLength) { - this(salt, minHashLength, Normal.UPPER_LOWER_NUMBER); - } - - public HashID(String salt, int minHashLength, String alphabet) { - this.salt = null != salt ? salt : DEFAULT_SALT; - this.minHashLength = minHashLength > 0 ? minHashLength : DEFAULT_MIN_HASH_LENGTH; - - final StringBuilder uniqueAlphabet = new StringBuilder(); - for (int i = 0; i < alphabet.length(); i++) { - if (uniqueAlphabet.indexOf(String.valueOf(alphabet.charAt(i))) == -1) { - uniqueAlphabet.append(alphabet.charAt(i)); - } - } - - alphabet = uniqueAlphabet.toString(); - - if (alphabet.length() < MIN_ALPHABET_LENGTH) { - throw new IllegalArgumentException( - "alphabet must contain at least " + MIN_ALPHABET_LENGTH + " unique characters"); - } - - if (alphabet.contains(Symbol.SPACE)) { - throw new IllegalArgumentException("alphabet cannot contains spaces"); - } - - // seps should contain only characters present in alphabet; - // alphabet should not contains seps - String seps = DEFAULT_SEPS; - for (int i = 0; i < seps.length(); i++) { - final int j = alphabet.indexOf(seps.charAt(i)); - if (j == -1) { - seps = seps.substring(0, i) + Symbol.SPACE + seps.substring(i + 1); - } else { - alphabet = alphabet.substring(0, j) + Symbol.SPACE + alphabet.substring(j + 1); - } - } - - alphabet = alphabet.replaceAll("\\s+", Normal.EMPTY); - seps = seps.replaceAll("\\s+", Normal.EMPTY); - seps = consistentShuffle(seps, this.salt); - - if ((seps.isEmpty()) || (((float) alphabet.length() / seps.length()) > SEP_DIV)) { - int seps_len = (int) Math.ceil(alphabet.length() / SEP_DIV); - - if (seps_len == 1) { - seps_len++; - } - - if (seps_len > seps.length()) { - final int diff = seps_len - seps.length(); - seps += alphabet.substring(0, diff); - alphabet = alphabet.substring(diff); - } else { - seps = seps.substring(0, seps_len); - } - } - - alphabet = consistentShuffle(alphabet, this.salt); - // use double to round up - final int guardCount = (int) Math.ceil((double) alphabet.length() / GUARD_DIV); - - String guards; - if (alphabet.length() < 3) { - guards = seps.substring(0, guardCount); - seps = seps.substring(guardCount); - } else { - guards = alphabet.substring(0, guardCount); - alphabet = alphabet.substring(guardCount); - } - this.guards = guards; - this.alphabet = alphabet; - this.seps = seps; - } - - public static int checkedCast(long value) { - final int result = (int) value; - if (result != value) { - // don't use checkArgument here, to avoid boxing - throw new IllegalArgumentException("Out of range: " + value); - } - return result; - } - - private static String consistentShuffle(String alphabet, String salt) { - if (salt.length() <= 0) { - return alphabet; - } - - int asc_val, j; - final char[] tmpArr = alphabet.toCharArray(); - for (int i = tmpArr.length - 1, v = 0, p = 0; i > 0; i--, v++) { - v %= salt.length(); - asc_val = salt.charAt(v); - p += asc_val; - j = (asc_val + v + p) % i; - final char tmp = tmpArr[j]; - tmpArr[j] = tmpArr[i]; - tmpArr[i] = tmp; - } - - return new String(tmpArr); - } - - private static String hash(long input, String alphabet) { - String hash = Normal.EMPTY; - final int alphabetLen = alphabet.length(); - - do { - final int index = (int) (input % alphabetLen); - if (index >= 0 && index < alphabet.length()) { - hash = alphabet.charAt(index) + hash; - } - input /= alphabetLen; - } while (input > 0); - - return hash; - } - - private static Long unhash(String input, String alphabet) { - long number = 0, pos; - - for (int i = 0; i < input.length(); i++) { - pos = alphabet.indexOf(input.charAt(i)); - number = number * alphabet.length() + pos; - } - - return number; - } - - /** - * 获取数据中心ID,依赖于本地网卡MAC地址 - *

    - * 此算法来自于mybatis-plus#Sequence - *

    - * - * @param maxDatacenterId 最大的中心ID - * @return 数据中心ID - */ - public static long getDataCenterId(long maxDatacenterId) { - long id = 1L; - final byte[] mac = NetKit.getLocalHardwareAddress(); - if (null != mac) { - id = ((0x000000FF & (long) mac[mac.length - 2]) - | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; - id = id % (maxDatacenterId + 1); - } - - return id; - } - - /** - * 获取机器ID,使用进程ID配合数据中心ID生成 - * 依赖于本进程ID或进程名的Hash值 - *

    - * 此算法来自于mybatis-plus#Sequence - *

    - * - * @param datacenterId 数据中心ID - * @param maxWorkerId 最大的机器节点ID - * @return the long - */ - public static long getWorkerId(long datacenterId, long maxWorkerId) { - final StringBuilder mpid = new StringBuilder(); - mpid.append(datacenterId); - try { - mpid.append(RuntimeKit.getPid()); - } catch (InstantiationError igonre) { - } - // MAC + PID 的 hashcode 获取16个低位 - return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1); - } - - /** - * Encrypt numbers to string - * - * @param numbers the numbers to encrypt - * @return the encrypt string - */ - public String encode(long... numbers) { - if (numbers.length == 0) { - return Normal.EMPTY; - } - - for (final long number : numbers) { - if (number < 0) { - return Normal.EMPTY; - } - if (number > MAX_NUMBER) { - throw new IllegalArgumentException("number can not be greater than " + MAX_NUMBER + "L"); - } - } - return this._encode(numbers); - } - - /** - * Decrypt string to numbers - * - * @param hash the encrypt string - * @return decryped numbers - */ - public long[] decode(String hash) { - if (hash.isEmpty()) { - return Normal.EMPTY_LONG_ARRAY; - } - - String validChars = this.alphabet + this.guards + this.seps; - for (int i = 0; i < hash.length(); i++) { - if (validChars.indexOf(hash.charAt(i)) == -1) { - return Normal.EMPTY_LONG_ARRAY; - } - } - - return this._decode(hash, this.alphabet); - } - - /** - * Encrypt hexa to string - * - * @param hexa the hexa to encrypt - * @return the encrypt string - */ - public String encodeHex(String hexa) { - if (!hexa.matches("^[0-9a-fA-F]+$")) { - return Normal.EMPTY; - } - - final List matched = new ArrayList<>(); - final Matcher matcher = PATTERN.matcher(hexa); - - while (matcher.find()) { - matched.add(Long.parseLong(Symbol.ONE + matcher.group(), Normal._16)); - } - - // conversion - final long[] result = new long[matched.size()]; - for (int i = 0; i < matched.size(); i++) { - result[i] = matched.get(i); - } - - return this.encode(result); - } - - /** - * Decrypt string to numbers - * - * @param hash the encrypt string - * @return decryped numbers - */ - public String decodeHex(String hash) { - final StringBuilder result = new StringBuilder(); - final long[] numbers = this.decode(hash); - - for (final long number : numbers) { - result.append(Long.toHexString(number).substring(1)); - } - - return result.toString(); - } - - private String _encode(long... numbers) { - long numberHashInt = 0; - for (int i = 0; i < numbers.length; i++) { - numberHashInt += (numbers[i] % (i + 100)); - } - String alphabet = this.alphabet; - final char ret = alphabet.charAt((int) (numberHashInt % alphabet.length())); - - long num; - long sepsIndex, guardIndex; - String buffer; - final StringBuilder ret_strB = new StringBuilder(this.minHashLength); - ret_strB.append(ret); - char guard; - - for (int i = 0; i < numbers.length; i++) { - num = numbers[i]; - buffer = ret + this.salt + alphabet; - - alphabet = consistentShuffle(alphabet, buffer.substring(0, alphabet.length())); - final String last = hash(num, alphabet); - - ret_strB.append(last); - - if (i + 1 < numbers.length) { - if (last.length() > 0) { - num %= (last.charAt(0) + i); - sepsIndex = (int) (num % this.seps.length()); - } else { - sepsIndex = 0; - } - ret_strB.append(this.seps.charAt((int) sepsIndex)); - } - } - - String ret_str = ret_strB.toString(); - if (ret_str.length() < this.minHashLength) { - guardIndex = (numberHashInt + (ret_str.charAt(0))) % this.guards.length(); - guard = this.guards.charAt((int) guardIndex); - - ret_str = guard + ret_str; - - if (ret_str.length() < this.minHashLength) { - guardIndex = (numberHashInt + (ret_str.charAt(2))) % this.guards.length(); - guard = this.guards.charAt((int) guardIndex); - - ret_str += guard; - } - } - - final int halfLen = alphabet.length() / 2; - while (ret_str.length() < this.minHashLength) { - alphabet = consistentShuffle(alphabet, alphabet); - ret_str = alphabet.substring(halfLen) + ret_str + alphabet.substring(0, halfLen); - final int excess = ret_str.length() - this.minHashLength; - if (excess > 0) { - final int start_pos = excess / 2; - ret_str = ret_str.substring(start_pos, start_pos + this.minHashLength); - } - } - - return ret_str; - } - - private long[] _decode(String hash, String alphabet) { - final List ret = new ArrayList<>(); - String shuffle = alphabet; - int i = 0; - final String regexp = Symbol.BRACKET_LEFT + this.guards + Symbol.BRACKET_RIGHT; - String hashBreakdown = hash.replaceAll(regexp, Symbol.SPACE); - String[] hashArray = hashBreakdown.split(Symbol.SPACE); - - if (hashArray.length == 3 || hashArray.length == 2) { - i = 1; - } - - if (hashArray.length > 0) { - hashBreakdown = hashArray[i]; - if (!hashBreakdown.isEmpty()) { - final char lottery = hashBreakdown.charAt(0); - - hashBreakdown = hashBreakdown.substring(1); - hashBreakdown = hashBreakdown.replaceAll(Symbol.BRACKET_LEFT + this.seps + Symbol.BRACKET_RIGHT, Symbol.SPACE); - hashArray = hashBreakdown.split(Symbol.SPACE); - - String subHash, buffer; - for (final String aHashArray : hashArray) { - subHash = aHashArray; - buffer = lottery + this.salt + shuffle; - shuffle = consistentShuffle(shuffle, buffer.substring(0, shuffle.length())); - ret.add(unhash(subHash, shuffle)); - } - } - } - - // transform from List to long[] - long[] arr = new long[ret.size()]; - for (int k = 0; k < arr.length; k++) { - arr[k] = ret.get(k); - } - - if (!this.encode(arr).equals(hash)) { - arr = Normal.EMPTY_LONG_ARRAY; - } - - return arr; - } - - /** - * Get Hashid algorithm version. - * - * @return id algorithm version implemented. - */ - public String getVersion() { - return "1.0.0"; - } - - public String getSalt() { - return salt; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Http.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Http.java index a4316c5573..3d553c0519 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Http.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Http.java @@ -187,6 +187,10 @@ public class Http { * Supports RFC 5246: TLS version 1.2 ; may support other versions */ public static final String TLS_V_12 = "TLSv1.2"; + /** + * Supports RFC 5246: TLS version 1.3 ; may support other versions + */ + public static final String TLS_V_13 = "TLSv1.3"; /** * Supports SSL version 2 or later; may support other versions */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Normal.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Normal.java index fb598be888..8ac65a3e5e 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Normal.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Normal.java @@ -358,15 +358,25 @@ public class Normal { public static final String LOWER_NUMBER = LOWER + NUMBER; /** - * 字符串: 大小字母 + * 字符串: 大写字母 + 小写字母 */ public static final String UPPER_LOWER = UPPER + LOWER; + /** + * 字符串: 小写字母 + 大写字母 + */ + public static final String LOWER_UPPER = LOWER + UPPER; + /** * 字符串: 大小字母数字 */ public static final String UPPER_LOWER_NUMBER = UPPER_LOWER + NUMBER; + /** + * 字符串: 大小字母数字 + */ + public static final String LOWER_UPPER_NUMBER = LOWER_UPPER + NUMBER; + /** * 七色 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/RegEx.java b/bus-core/src/main/java/org/aoju/bus/core/lang/RegEx.java index 2cc90d0dc7..c510bee186 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/RegEx.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/RegEx.java @@ -116,6 +116,12 @@ public class RegEx { public static final String GROUP_VAR_PATTERN = "\\$(\\d+)"; public static final Pattern GROUP_VAR = Pattern.compile(GROUP_VAR_PATTERN); + /** + * 快速区分IP地址和主机名 + */ + public static final String IP_ADDRESS_PATTERN = "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)"; + public static final Pattern IP_ADDRESS = Pattern.compile(IP_ADDRESS_PATTERN); + /** * IP v4 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/LocalPort.java b/bus-core/src/main/java/org/aoju/bus/core/net/LocalPort.java new file mode 100644 index 0000000000..7eabeb78fd --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/net/LocalPort.java @@ -0,0 +1,73 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.net; + +import org.aoju.bus.core.toolkit.NetKit; + +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 本地端口生成器Percent + * 用于生成本地可用(未被占用)的端口号Percent + * 注意:多线程甚至单线程访问时可能会返回同一端口(例如获取了端口但是没有使用) + * + * @author Kimi Liu + * @since Java 17+ + */ +public class LocalPort implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 备选的本地端口 + */ + private final AtomicInteger alternativePort; + + /** + * 构造 + * + * @param beginPort 起始端口号 + */ + public LocalPort(final int beginPort) { + alternativePort = new AtomicInteger(beginPort); + } + + /** + * 生成一个本地端口,用于远程端口映射 + * + * @return 未被使用的本地端口 + */ + public int generate() { + int validPort = alternativePort.get(); + // 获取可用端口 + while (false == NetKit.isUsableLocalPort(validPort)) { + validPort = alternativePort.incrementAndGet(); + } + return validPort; + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/MaskBit.java b/bus-core/src/main/java/org/aoju/bus/core/net/MaskBit.java new file mode 100644 index 0000000000..b1e11e19b3 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/net/MaskBit.java @@ -0,0 +1,101 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.net; + +import org.aoju.bus.core.map.DuplexingMap; + +import java.util.HashMap; + +/** + * 掩码位和掩码之间的Map对应 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class MaskBit { + + /** + * 掩码位与掩码的点分十进制的双向对应关系 + */ + private static final DuplexingMap MASK_BIT_MAP; + + static { + MASK_BIT_MAP = new DuplexingMap<>(new HashMap<>(32)); + MASK_BIT_MAP.put(1, "128.0.0.0"); + MASK_BIT_MAP.put(2, "192.0.0.0"); + MASK_BIT_MAP.put(3, "224.0.0.0"); + MASK_BIT_MAP.put(4, "240.0.0.0"); + MASK_BIT_MAP.put(5, "248.0.0.0"); + MASK_BIT_MAP.put(6, "252.0.0.0"); + MASK_BIT_MAP.put(7, "254.0.0.0"); + MASK_BIT_MAP.put(8, "255.0.0.0"); + MASK_BIT_MAP.put(9, "255.128.0.0"); + MASK_BIT_MAP.put(10, "255.192.0.0"); + MASK_BIT_MAP.put(11, "255.224.0.0"); + MASK_BIT_MAP.put(12, "255.240.0.0"); + MASK_BIT_MAP.put(13, "255.248.0.0"); + MASK_BIT_MAP.put(14, "255.252.0.0"); + MASK_BIT_MAP.put(15, "255.254.0.0"); + MASK_BIT_MAP.put(16, "255.255.0.0"); + MASK_BIT_MAP.put(17, "255.255.128.0"); + MASK_BIT_MAP.put(18, "255.255.192.0"); + MASK_BIT_MAP.put(19, "255.255.224.0"); + MASK_BIT_MAP.put(20, "255.255.240.0"); + MASK_BIT_MAP.put(21, "255.255.248.0"); + MASK_BIT_MAP.put(22, "255.255.252.0"); + MASK_BIT_MAP.put(23, "255.255.254.0"); + MASK_BIT_MAP.put(24, "255.255.255.0"); + MASK_BIT_MAP.put(25, "255.255.255.128"); + MASK_BIT_MAP.put(26, "255.255.255.192"); + MASK_BIT_MAP.put(27, "255.255.255.224"); + MASK_BIT_MAP.put(28, "255.255.255.240"); + MASK_BIT_MAP.put(29, "255.255.255.248"); + MASK_BIT_MAP.put(30, "255.255.255.252"); + MASK_BIT_MAP.put(31, "255.255.255.254"); + MASK_BIT_MAP.put(32, "255.255.255.255"); + } + + /** + * 根据掩码位获取掩码 + * + * @param maskBit 掩码位 + * @return 掩码 + */ + public static String get(final int maskBit) { + return MASK_BIT_MAP.get(maskBit); + } + + /** + * 根据掩码获取掩码位 + * + * @param mask 掩码的点分十进制表示,如 255.255.255.0 + * @return 掩码位,如 24;如果掩码不合法,则返回null + */ + public static Integer getMaskBit(final String mask) { + return MASK_BIT_MAP.getKey(mask); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/net/package-info.java new file mode 100644 index 0000000000..4fde717922 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/net/package-info.java @@ -0,0 +1,7 @@ +/** + * 网络相关工具 + * + * @author Kimi Liu + * @since Java 17+ + */ +package org.aoju.bus.core.net; \ No newline at end of file diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/DefaultTrustManager.java b/bus-core/src/main/java/org/aoju/bus/core/net/ssl/DefaultTrustManager.java similarity index 98% rename from bus-http/src/main/java/org/aoju/bus/http/secure/DefaultTrustManager.java rename to bus-core/src/main/java/org/aoju/bus/core/net/ssl/DefaultTrustManager.java index 354d15a23d..f1f479235b 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/DefaultTrustManager.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/ssl/DefaultTrustManager.java @@ -23,7 +23,7 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.http.secure; +package org.aoju.bus.core.net.ssl; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/HostnameVerifier.java b/bus-core/src/main/java/org/aoju/bus/core/net/ssl/HostnameVerifier.java similarity index 98% rename from bus-http/src/main/java/org/aoju/bus/http/secure/HostnameVerifier.java rename to bus-core/src/main/java/org/aoju/bus/core/net/ssl/HostnameVerifier.java index 53cc32d4c3..2656228a50 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/HostnameVerifier.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/ssl/HostnameVerifier.java @@ -23,10 +23,10 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.http.secure; +package org.aoju.bus.core.net.ssl; +import org.aoju.bus.core.lang.RegEx; import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.http.Builder; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; @@ -100,7 +100,7 @@ public boolean verify(String host, SSLSession session) { } public boolean verify(String host, X509Certificate certificate) { - return Builder.verifyAsIpAddress(host) + return RegEx.IP_ADDRESS.matcher(host).matches() ? verifyIpAddress(host, certificate) : verifyHostname(host, certificate); } diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/net/ssl/SSLContextBuilder.java similarity index 92% rename from bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java rename to bus-core/src/main/java/org/aoju/bus/core/net/ssl/SSLContextBuilder.java index 41a61dbd99..80de979b78 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/ssl/SSLContextBuilder.java @@ -23,19 +23,15 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.http.secure; +package org.aoju.bus.core.net.ssl; import org.aoju.bus.core.builder.Builder; import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.toolkit.ArrayKit; import org.aoju.bus.core.toolkit.StringKit; -import org.aoju.bus.http.accord.platform.Platform; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.*; import java.security.*; import java.util.Arrays; @@ -114,6 +110,52 @@ public static SSLContext createSSLContext(String protocol, KeyManager[] keyManag .setTrustManagers(trustManagers).build(); } + /** + * 创建SSL证书 + * + * @param x509TrustManager 证书信息 + * @return SSLSocketFactory ssl socket工厂 + */ + public static SSLSocketFactory newSslSocketFactory(X509TrustManager x509TrustManager) { + try { + SSLContext sslContext = getSSLContext(); + sslContext.init(null, new TrustManager[]{x509TrustManager}, new SecureRandom()); + return sslContext.getSocketFactory(); + } catch (GeneralSecurityException ignored) { + throw new AssertionError("No System TLS", ignored); + } + } + + public static SSLContext getSSLContext() { + try { + return SSLContext.getInstance(Http.TLS_V_12); + } catch (NoSuchAlgorithmException e) { + // fallback to TLS + } + + try { + return SSLContext.getInstance(Http.TLS); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("No TLS provider", e); + } + } + + public static X509TrustManager newTrustManager() { + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init((KeyStore) null); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + + Arrays.toString(trustManagers)); + } + return (javax.net.ssl.X509TrustManager) trustManagers[0]; + } catch (GeneralSecurityException e) { + throw new AssertionError("No System TLS", e); + } + } + /** * 设置协议。例如TLS等 * @@ -203,36 +245,4 @@ public SSLContext buildQuietly() throws InternalException { } } - /** - * 创建SSL证书 - * - * @param x509TrustManager 证书信息 - * @return SSLSocketFactory ssl socket工厂 - */ - public static javax.net.ssl.SSLSocketFactory newSslSocketFactory(javax.net.ssl.X509TrustManager x509TrustManager) { - try { - SSLContext sslContext = Platform.get().getSSLContext(); - sslContext.init(null, new TrustManager[]{x509TrustManager}, new SecureRandom()); - return sslContext.getSocketFactory(); - } catch (GeneralSecurityException ignored) { - throw new AssertionError("No System TLS", ignored); - } - } - - public static javax.net.ssl.X509TrustManager newTrustManager() { - try { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof javax.net.ssl.X509TrustManager)) { - throw new IllegalStateException("Unexpected default trust managers:" - + Arrays.toString(trustManagers)); - } - return (javax.net.ssl.X509TrustManager) trustManagers[0]; - } catch (GeneralSecurityException e) { - throw new AssertionError("No System TLS", e); - } - } - } diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/ssl/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/net/ssl/package-info.java new file mode 100644 index 0000000000..caa61be88d --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/net/ssl/package-info.java @@ -0,0 +1,7 @@ +/** + * SSL相关封装 + * + * @author Kimi Liu + * @since Java 17+ + */ +package org.aoju.bus.core.net.ssl; diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java index fe0cf47511..0bc13be22b 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java @@ -4422,6 +4422,25 @@ public static String filter(CharSequence text, Predicate filter) { return sb.toString(); } + /** + * 创建StringBuilder对象 + * + * @return StringBuilder对象 + */ + public static StringBuilder builder() { + return new StringBuilder(); + } + + /** + * 创建StringBuilder对象 + * + * @param capacity 初始大小 + * @return StringBuilder对象 + */ + public static StringBuilder builder(final int capacity) { + return new StringBuilder(capacity); + } + /** * 创建StringBuilder对象 * diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java index 6d3bb5d0d1..9576dfe4ee 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java @@ -35,7 +35,6 @@ import org.aoju.bus.core.io.copier.ChannelCopier; import org.aoju.bus.core.io.copier.FileChannelCopier; import org.aoju.bus.core.io.copier.ReaderWriterCopier; -import org.aoju.bus.core.io.copier.StreamCopier; import org.aoju.bus.core.io.sink.BufferSink; import org.aoju.bus.core.io.sink.RealSink; import org.aoju.bus.core.io.sink.Sink; @@ -250,7 +249,9 @@ public static long copy(final InputStream in, final OutputStream out, final int public static long copy(final InputStream in, final OutputStream out, final int bufferSize, final long count, final Progress progress) { Assert.notNull(in, "InputStream is null !"); Assert.notNull(out, "OutputStream is null !"); - return new StreamCopier(bufferSize, count, progress).copy(in, out); + final long copySize = copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, count, progress); + flush(out); + return copySize; } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/NetKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/NetKit.java index 572ea01a76..26d7b9a069 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/NetKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/NetKit.java @@ -29,6 +29,7 @@ import org.aoju.bus.core.convert.Convert; import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.lang.*; +import org.aoju.bus.core.net.MaskBit; import javax.naming.InitialContext; import javax.naming.NamingException; @@ -37,6 +38,7 @@ import javax.naming.directory.InitialDirContext; import java.io.IOException; import java.io.OutputStream; +import java.math.BigInteger; import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; @@ -53,75 +55,43 @@ public class NetKit { /** - * 默认最小端口,1024 + * 默认最小端口,1024 */ public static final int PORT_RANGE_MIN = Normal._1024; /** - * 默认最大端口,65535 + * 默认最大端口,65535 */ public static final int PORT_RANGE_MAX = 0xFFFF; /** - * 根据long值获取ip v4地址 + * 将IPv6地址字符串转为大整数 * - * @param longIP IP的long表示形式 - * @return IP V4 地址 - */ - public static String longToIpv4(long longIP) { - final StringBuilder sb = new StringBuilder(); - // 直接右移24位 - sb.append((longIP >>> 24)); - sb.append(Symbol.DOT); - // 将高8位置0,然后右移16位 - sb.append(longIP >> Normal._16 & 0xFF); - sb.append(Symbol.DOT); - sb.append(longIP >> 8 & 0xFF); - sb.append(Symbol.DOT); - sb.append(longIP & 0xFF); - return sb.toString(); - } - - /** - * 根据ip地址计算出long型的数据 - * - * @param strIP IP V4 地址 - * @return long值 + * @param ipv6Str 字符串 + * @return 大整数, 如发生异常返回 null */ - public static long ipv4ToLong(String strIP) { - final Matcher matcher = RegEx.IPV4.matcher(strIP); - if (matcher.matches()) { - long addr = 0; - for (int i = 1; i <= 4; ++i) { - addr |= Long.parseLong(matcher.group(i)) << 8 * (4 - i); + public static BigInteger ipv6ToBigInteger(final String ipv6Str) { + try { + final InetAddress address = InetAddress.getByName(ipv6Str); + if (address instanceof Inet6Address) { + return new BigInteger(1, address.getAddress()); } - return addr; + } catch (final UnknownHostException ignore) { } - throw new IllegalArgumentException("Invalid IPv4 address!"); - } - - /** - * 根据ip地址(xxx.xxx.xxx.xxx)计算出long型的数据, 如果格式不正确返回 defaultValue - * - * @param strIP IP V4 地址 - * @param defaultValue 默认值 - * @return long值 - */ - public static long ipv4ToLong(String strIP, long defaultValue) { - return Validator.isIpv4(strIP) ? ipv4ToLong(strIP) : defaultValue; + return null; } /** - * 将匹配到的Ipv4地址的4个分组分别处理 + * 将大整数转换成ipv6字符串 * - * @param matcher 匹配到的Ipv4正则 - * @return ipv4对应long + * @param bigInteger 大整数 + * @return IPv6字符串, 如发生异常返回 null */ - private static long matchAddress(Matcher matcher) { - long addr = 0; - for (int i = 1; i <= 4; ++i) { - addr |= Long.parseLong(matcher.group(i)) << 8 * (4 - i); + public static String bigIntegerToIPv6(final BigInteger bigInteger) { + try { + return InetAddress.getByAddress(bigInteger.toByteArray()).toString().substring(1); + } catch (final UnknownHostException ignore) { + return null; } - return addr; } /** @@ -130,22 +100,22 @@ private static long matchAddress(Matcher matcher) { * @param port 被检测的端口 * @return 是否可用 */ - public static boolean isUsableLocalPort(int port) { + public static boolean isUsableLocalPort(final int port) { if (false == isValidPort(port)) { // 给定的IP未在指定端口范围中 return false; } - // 绑定非127.0.0.1的端口无法被检测到 - try (ServerSocket ss = new ServerSocket(port)) { + // 某些绑定非127.0.0.1的端口无法被检测到 + try (final ServerSocket ss = new ServerSocket(port)) { ss.setReuseAddress(true); - } catch (IOException ignored) { + } catch (final IOException ignored) { return false; } - try (DatagramSocket ds = new DatagramSocket(port)) { + try (final DatagramSocket ds = new DatagramSocket(port)) { ds.setReuseAddress(true); - } catch (IOException ignored) { + } catch (final IOException ignored) { return false; } @@ -159,7 +129,7 @@ public static boolean isUsableLocalPort(int port) { * @param port 端口号 * @return 是否有效 */ - public static boolean isValidPort(int port) { + public static boolean isValidPort(final int port) { // 有效端口是0~65535 return port >= 0 && port <= PORT_RANGE_MAX; } @@ -178,10 +148,10 @@ public static int getUsableLocalPort() { * 查找指定范围内的可用端口,最大值为65535 * 此方法只检测给定范围内的随机一个端口,检测65535-minPort次 * - * @param minPort 端口最小值(包含) + * @param minPort 端口最小值(包含) * @return 可用的端口 */ - public static int getUsableLocalPort(int minPort) { + public static int getUsableLocalPort(final int minPort) { return getUsableLocalPort(minPort, PORT_RANGE_MAX); } @@ -189,11 +159,11 @@ public static int getUsableLocalPort(int minPort) { * 查找指定范围内的可用端口 * 此方法只检测给定范围内的随机一个端口,检测maxPort-minPort次 * - * @param minPort 端口最小值(包含) - * @param maxPort 端口最大值(包含) + * @param minPort 端口最小值(包含) + * @param maxPort 端口最大值(包含) * @return 可用的端口 */ - public static int getUsableLocalPort(int minPort, int maxPort) { + public static int getUsableLocalPort(final int minPort, final int maxPort) { final int maxPortExclude = maxPort + 1; int randomPort; for (int i = minPort; i < maxPortExclude; i++) { @@ -206,47 +176,15 @@ public static int getUsableLocalPort(int minPort, int maxPort) { throw new InternalException("Could not find an available port in the range [{}, {}] after {} attempts", minPort, maxPort, maxPort - minPort); } - /** - * 获得本机物理地址 - * - * @return 本机物理地址 - */ - public static byte[] getLocalHardwareAddress() { - return getHardwareAddress(getLocalhost()); - } - - /** - * 获得指定地址信息中的硬件地址 - * - * @param inetAddress {@link InetAddress} - * @return 硬件地址 - */ - public static byte[] getHardwareAddress(InetAddress inetAddress) { - if (null == inetAddress) { - return null; - } - - try { - final NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress); - if (null != networkInterface) { - return networkInterface.getHardwareAddress(); - } - } catch (SocketException e) { - throw new InternalException(e); - } - return null; - } - - /** * 获取多个本地可用端口 * - * @param numRequested int - * @param minPort 端口最小值(包含) - * @param maxPort 端口最大值(包含) + * @param numRequested 尝试次数 + * @param minPort 端口最小值(包含) + * @param maxPort 端口最大值(包含) * @return 可用的端口 */ - public static TreeSet getUsableLocalPorts(int numRequested, int minPort, int maxPort) { + public static TreeSet getUsableLocalPorts(final int numRequested, final int minPort, final int maxPort) { final TreeSet availablePorts = new TreeSet<>(); int attemptCount = 0; while ((++attemptCount <= numRequested + 100) && availablePorts.size() < numRequested) { @@ -259,43 +197,19 @@ public static TreeSet getUsableLocalPorts(int numRequested, int minPort return availablePorts; } - /** - * 判定是否为内网IP - * 私有IP:A类 10.0.0.0-10.255.255.255 B类 172.16.2.2-172.31.255.255 C类 192.168.0.0-192.168.255.255 当然,还有127这个网段是环回地址 - * - * @param ipAddress IP地址 - * @return 是否为内网IP - */ - public static boolean isInnerIP(String ipAddress) { - boolean isInnerIp; - long ipNum = ipv4ToLong(ipAddress); - - long aBegin = ipv4ToLong("10.0.0.0"); - long aEnd = ipv4ToLong("10.255.255.255"); - - long bBegin = ipv4ToLong("172.16.2.2"); - long bEnd = ipv4ToLong("172.31.255.255"); - - long cBegin = ipv4ToLong("192.168.0.0"); - long cEnd = ipv4ToLong("192.168.255.255"); - - isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd) || ipAddress.equals(Http.HTTP_HOST_IPV4); - return isInnerIp; - } - /** * 相对URL转换为绝对URL * - * @param absoluteBasePath 基准路径,绝对 + * @param absoluteBasePath 基准路径,绝对 * @param relativePath 相对路径 * @return 绝对URL */ - public static String toAbsoluteUrl(String absoluteBasePath, String relativePath) { + public static String toAbsoluteUrl(final String absoluteBasePath, final String relativePath) { try { - URL absoluteUrl = new URL(absoluteBasePath); + final URL absoluteUrl = new URL(absoluteBasePath); return new URL(absoluteUrl, relativePath).toString(); - } catch (Exception e) { - throw new InternalException("To absolute url [{}] base [{}] error!", relativePath, absoluteBasePath); + } catch (final Exception e) { + throw new InternalException(e, "To absolute url [{}] base [{}] error!", relativePath, absoluteBasePath); } } @@ -305,8 +219,8 @@ public static String toAbsoluteUrl(String absoluteBasePath, String relativePath) * @param ip IP地址 * @return 隐藏部分后的IP */ - public static String hideIpPart(String ip) { - return new StringBuffer(ip.length()).append(ip, 0, ip.lastIndexOf(Symbol.DOT) + 1).append(Symbol.STAR).toString(); + public static String hideIpPart(final String ip) { + return StringKit.builder(ip.length()).append(ip, 0, ip.lastIndexOf(".") + 1).append("*").toString(); } /** @@ -315,27 +229,27 @@ public static String hideIpPart(String ip) { * @param ip IP地址 * @return 隐藏部分后的IP */ - public static String hideIpPart(long ip) { + public static String hideIpPart(final long ip) { return hideIpPart(longToIpv4(ip)); } /** * 构建InetSocketAddress - * 当host中包含端口时(用“:”隔开),使用host中的端口,否则使用默认端口 - * 给定host为空时使用本地host(127.0.0.1) + * 当host中包含端口时(用“:”隔开),使用host中的端口,否则使用默认端口 + * 给定host为空时使用本地host(127.0.0.1) * * @param host Host * @param defaultPort 默认端口 * @return InetSocketAddress */ - public static InetSocketAddress buildInetSocketAddress(String host, int defaultPort) { + public static InetSocketAddress buildInetSocketAddress(String host, final int defaultPort) { if (StringKit.isBlank(host)) { host = Http.HTTP_HOST_IPV4; } - String destHost; - int port; - int index = host.indexOf(Symbol.COLON); + final String destHost; + final int port; + final int index = host.indexOf(Symbol.COLON); if (index != -1) { // host:port形式 destHost = host.substring(0, index); @@ -353,10 +267,10 @@ public static InetSocketAddress buildInetSocketAddress(String host, int defaultP * @param hostName HOST * @return ip address or hostName if UnknownHostException */ - public static String getIpByHost(String hostName) { + public static String getIpByHost(final String hostName) { try { return InetAddress.getByName(hostName).getHostAddress(); - } catch (UnknownHostException e) { + } catch (final UnknownHostException e) { return hostName; } } @@ -365,13 +279,13 @@ public static String getIpByHost(String hostName) { * 获取指定名称的网卡信息 * * @param name 网络接口名,例如Linux下默认是eth0 - * @return 网卡,未找到返回null + * @return 网卡,未找到返回{@code null} */ - public static NetworkInterface getNetworkInterface(String name) { - Enumeration networkInterfaces; + public static NetworkInterface getNetworkInterface(final String name) { + final Enumeration networkInterfaces; try { networkInterfaces = NetworkInterface.getNetworkInterfaces(); - } catch (SocketException e) { + } catch (final SocketException e) { return null; } @@ -388,13 +302,13 @@ public static NetworkInterface getNetworkInterface(String name) { /** * 获取本机所有网卡 * - * @return 所有网卡, 异常返回null + * @return 所有网卡,异常返回{@code null} */ public static Collection getNetworkInterfaces() { - Enumeration networkInterfaces; + final Enumeration networkInterfaces; try { networkInterfaces = NetworkInterface.getNetworkInterfaces(); - } catch (SocketException e) { + } catch (final SocketException e) { return null; } @@ -403,7 +317,7 @@ public static Collection getNetworkInterfaces() { /** * 获得本机的IPv4地址列表 - * 返回的IP列表有序,按照系统设备顺序 + * 返回的IP列表有序,按照系统设备顺序 * * @return IP地址列表 {@link LinkedHashSet} */ @@ -413,7 +327,7 @@ public static LinkedHashSet localIpv4s() { /** * 获得本机的IPv6地址列表 - * 返回的IP列表有序,按照系统设备顺序 + * 返回的IP列表有序,按照系统设备顺序 * * @return IP地址列表 {@link LinkedHashSet} */ @@ -427,17 +341,17 @@ public static LinkedHashSet localIpv6s() { * @param addressList 地址{@link Inet4Address} 列表 * @return IP地址字符串列表 */ - public static LinkedHashSet toIpList(Set addressList) { + public static LinkedHashSet toIpList(final Set addressList) { final LinkedHashSet ipSet = new LinkedHashSet<>(); - for (InetAddress address : addressList) { + for (final InetAddress address : addressList) { ipSet.add(address.getHostAddress()); } return ipSet; } /** - * 获得本机的IP地址列表(包括Ipv4和Ipv6) - * 返回的IP列表有序,按照系统设备顺序 + * 获得本机的IP地址列表(包括Ipv4和Ipv6) + * 返回的IP列表有序,按照系统设备顺序 * * @return IP地址列表 {@link LinkedHashSet} */ @@ -448,29 +362,29 @@ public static LinkedHashSet localIps() { /** * 获取所有满足过滤条件的本地IP地址对象 * - * @param addressFilter 过滤器,null表示不过滤,获取所有地址 + * @param addressPredicate 过滤器,{@link Predicate#test(Object)}为{@code true}保留,null表示不过滤,获取所有地址 * @return 过滤后的地址对象列表 */ - public static LinkedHashSet localAddressList(Predicate addressFilter) { - return localAddressList(addressFilter, null); + public static LinkedHashSet localAddressList(final Predicate addressPredicate) { + return localAddressList(null, addressPredicate); } /** * 获取所有满足过滤条件的本地IP地址对象 * - * @param addressFilter 过滤器,null表示不过滤,获取所有地址 * @param networkInterfaceFilter 过滤器,null表示不过滤,获取所有网卡 + * @param addressPredicate 过滤器,{@link Predicate#test(Object)}为{@code true}保留,null表示不过滤,获取所有地址 * @return 过滤后的地址对象列表 */ - public static LinkedHashSet localAddressList(Predicate addressFilter, Predicate networkInterfaceFilter) { - Enumeration networkInterfaces; + public static LinkedHashSet localAddressList(final Predicate networkInterfaceFilter, final Predicate addressPredicate) { + final Enumeration networkInterfaces; try { networkInterfaces = NetworkInterface.getNetworkInterfaces(); - } catch (SocketException e) { + } catch (final SocketException e) { throw new InternalException(e); } - if (null == networkInterfaces) { + if (networkInterfaces == null) { throw new InternalException("Get network interface error!"); } @@ -478,13 +392,13 @@ public static LinkedHashSet localAddressList(Predicate while (networkInterfaces.hasMoreElements()) { final NetworkInterface networkInterface = networkInterfaces.nextElement(); - if (null != networkInterfaceFilter && false == networkInterfaceFilter.test(networkInterface)) { + if (networkInterfaceFilter != null && false == networkInterfaceFilter.test(networkInterface)) { continue; } final Enumeration inetAddresses = networkInterface.getInetAddresses(); while (inetAddresses.hasMoreElements()) { final InetAddress inetAddress = inetAddresses.nextElement(); - if (null != inetAddress && (null == addressFilter || addressFilter.test(inetAddress))) { + if (inetAddress != null && (null == addressPredicate || addressPredicate.test(inetAddress))) { ipSet.add(inetAddress); } } @@ -494,16 +408,17 @@ public static LinkedHashSet localAddressList(Predicate } /** - * 获取本机网卡IP地址,这个地址为所有网卡中非回路地址的第一个 - * 如果获取失败调用 {@link InetAddress#getLocalHost()}方法获取 - * 此方法不会抛出异常,获取失败将返回null + * 获取本机网卡IP地址,这个地址为所有网卡中非回路地址的第一个 + * 如果获取失败调用 {@link InetAddress#getLocalHost()}方法获取。 + * 此方法不会抛出异常,获取失败将返回{@code null} *

    - * 参考:http://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java + * 参考: + * http://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java * - * @return 本机网卡IP地址, 获取失败返回null + * @return 本机网卡IP地址,获取失败返回{@code null} */ public static String getLocalhostString() { - InetAddress localhost = getLocalhost(); + final InetAddress localhost = getLocalhost(); if (null != localhost) { return localhost.getHostAddress(); } @@ -511,11 +426,11 @@ public static String getLocalhostString() { } /** - * 获取本机网卡IP地址,规则如下: + * 获取本机网卡IP地址,规则如下: * *

    -     * 1. 查找所有网卡地址,必须非回路(loopback)地址、非局域网地址(siteLocal)、IPv4地址
    -     * 2. 如果无满足要求的地址,调用 {@link InetAddress#getLocalHost()} 获取地址
    +     * 1. 查找所有网卡地址,必须非回路(loopback)地址、非局域网地址(siteLocal)、IPv4地址
    +     * 2. 如果无满足要求的地址,调用 {@link InetAddress#getLocalHost()} 获取地址
          * 
    *

    * 此方法不会抛出异常,获取失败将返回null @@ -532,7 +447,7 @@ public static InetAddress getLocalhost() { if (CollKit.isNotEmpty(localAddressList)) { InetAddress address2 = null; - for (InetAddress inetAddress : localAddressList) { + for (final InetAddress inetAddress : localAddressList) { if (false == inetAddress.isSiteLocalAddress()) { // 非地区本地地址,指10.0.0.0 ~ 10.255.255.255、172.16.0.0 ~ 172.31.255.255、192.168.0.0 ~ 192.168.255.255 return inetAddress; @@ -548,29 +463,12 @@ public static InetAddress getLocalhost() { try { return InetAddress.getLocalHost(); - } catch (UnknownHostException e) { + } catch (final UnknownHostException e) { // ignore } return null; } - /** - * 获取主机名称,一次获取会缓存名称 - * - * @return 主机名称 - */ - public static String getLocalHostName() { - final InetAddress localhost = getLocalhost(); - if (null != localhost) { - String name = localhost.getHostName(); - if (StringKit.isEmpty(name)) { - return localhost.getHostAddress(); - } - return name; - } - return Normal.EMPTY; - } - /** * 获得本机MAC地址 * @@ -581,12 +479,12 @@ public static String getLocalMacAddress() { } /** - * 获得指定地址信息中的MAC地址,使用分隔符“-” + * 获得指定地址信息中的MAC地址,使用分隔符“-” * * @param inetAddress {@link InetAddress} - * @return MAC地址, 用-分隔 + * @return MAC地址,用-分隔 */ - public static String getMacAddress(InetAddress inetAddress) { + public static String getMacAddress(final InetAddress inetAddress) { return getMacAddress(inetAddress, Symbol.MINUS); } @@ -594,23 +492,15 @@ public static String getMacAddress(InetAddress inetAddress) { * 获得指定地址信息中的MAC地址 * * @param inetAddress {@link InetAddress} - * @param separator 分隔符,推荐使用“-”或者“:” - * @return MAC地址, 用-分隔 + * @param separator 分隔符,推荐使用“-”或者“:” + * @return MAC地址,用-分隔 */ - public static String getMacAddress(InetAddress inetAddress, String separator) { + public static String getMacAddress(final InetAddress inetAddress, final String separator) { if (null == inetAddress) { return null; } - byte[] mac = null; - try { - final NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress); - if (null != networkInterface) { - mac = networkInterface.getHardwareAddress(); - } - } catch (SocketException e) { - throw new InternalException(e); - } + final byte[] mac = getHardwareAddress(inetAddress); if (null != mac) { final StringBuilder sb = new StringBuilder(); String s; @@ -624,17 +514,66 @@ public static String getMacAddress(InetAddress inetAddress, String separator) { } return sb.toString(); } + + return null; + } + + /** + * 获得指定地址信息中的硬件地址 + * + * @param inetAddress {@link InetAddress} + * @return 硬件地址 + */ + public static byte[] getHardwareAddress(final InetAddress inetAddress) { + if (null == inetAddress) { + return null; + } + + try { + final NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress); + if (null != networkInterface) { + return networkInterface.getHardwareAddress(); + } + } catch (final SocketException e) { + throw new InternalException(e); + } + return null; + } + + /** + * 获得本机物理地址 + * + * @return 本机物理地址 + */ + public static byte[] getLocalHardwareAddress() { + return getHardwareAddress(getLocalhost()); + } + + /** + * 获取主机名称,一次获取会缓存名称 + * + * @return 主机名称 + */ + public static String getLocalHostName() { + final InetAddress localhost = getLocalhost(); + if (null != localhost) { + String name = localhost.getHostName(); + if (StringKit.isEmpty(name)) { + name = localhost.getHostAddress(); + } + return name; + } return null; } /** * 创建 {@link InetSocketAddress} * - * @param host 域名或IP地址,空表示任意地址 - * @param port 端口,0表示系统分配临时端口 + * @param host 域名或IP地址,空表示任意地址 + * @param port 端口,0表示系统分配临时端口 * @return {@link InetSocketAddress} */ - public static InetSocketAddress createAddress(String host, int port) { + public static InetSocketAddress createAddress(final String host, final int port) { if (StringKit.isBlank(host)) { return new InetSocketAddress(port); } @@ -650,11 +589,11 @@ public static InetSocketAddress createAddress(String host, int port) { * @param data 需要发送的数据 * @throws InternalException IO异常 */ - public static void netCat(String host, int port, boolean isBlock, ByteBuffer data) throws InternalException { - try (SocketChannel channel = SocketChannel.open(createAddress(host, port))) { + public static void netCat(final String host, final int port, final boolean isBlock, final ByteBuffer data) throws InternalException { + try (final SocketChannel channel = SocketChannel.open(createAddress(host, port))) { channel.configureBlocking(isBlock); channel.write(data); - } catch (IOException e) { + } catch (final IOException e) { throw new InternalException(e); } } @@ -665,15 +604,15 @@ public static void netCat(String host, int port, boolean isBlock, ByteBuffer dat * @param host Server主机 * @param port Server端口 * @param data 数据 - * @throws InternalException 异常 + * @throws InternalException IO异常 */ - public static void netCat(String host, int port, byte[] data) throws InternalException { + public static void netCat(final String host, final int port, final byte[] data) throws InternalException { OutputStream out = null; - try (Socket socket = new Socket(host, port)) { + try (final Socket socket = new Socket(host, port)) { out = socket.getOutputStream(); out.write(data); out.flush(); - } catch (IOException e) { + } catch (final IOException e) { throw new InternalException(e); } finally { IoKit.close(out); @@ -688,14 +627,14 @@ public static void netCat(String host, int port, byte[] data) throws InternalExc * @param cidr CIDR规则 * @return 是否在范围内 */ - public static boolean isInRange(String ip, String cidr) { + public static boolean isInRange(final String ip, final String cidr) { final int maskSplitMarkIndex = cidr.lastIndexOf(Symbol.SLASH); if (maskSplitMarkIndex < 0) { throw new IllegalArgumentException("Invalid cidr: " + cidr); } - final long mask = (-1L << Normal._32 - Integer.parseInt(cidr.substring(maskSplitMarkIndex + 1))); - long cidrIpAddr = ipv4ToLong(cidr.substring(0, maskSplitMarkIndex)); + final long mask = (-1L << 32 - Integer.parseInt(cidr.substring(maskSplitMarkIndex + 1))); + final long cidrIpAddr = ipv4ToLong(cidr.substring(0, maskSplitMarkIndex)); return (ipv4ToLong(ip) & mask) == (cidrIpAddr & mask); } @@ -706,7 +645,7 @@ public static boolean isInRange(String ip, String cidr) { * @param unicode Unicode域名 * @return puny code */ - public static String idnToASCII(String unicode) { + public static String idnToASCII(final String unicode) { return IDN.toASCII(unicode); } @@ -718,9 +657,9 @@ public static String idnToASCII(String unicode) { */ public static String getMultistageReverseProxyIp(String ip) { // 多级反向代理检测 - if (null != ip && ip.indexOf(Symbol.COMMA) > 0) { + if (ip != null && ip.indexOf(Symbol.COMMA) > 0) { final String[] ips = ip.trim().split(Symbol.COMMA); - for (String subIp : ips) { + for (final String subIp : ips) { if (false == isUnknown(subIp)) { ip = subIp; break; @@ -736,7 +675,7 @@ public static String getMultistageReverseProxyIp(String ip) { * @param checkString 被检测的字符串 * @return 是否未知 */ - public static boolean isUnknown(String checkString) { + public static boolean isUnknown(final String checkString) { return StringKit.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); } @@ -744,9 +683,9 @@ public static boolean isUnknown(String checkString) { * 检测IP地址是否能ping通 * * @param ip IP地址 - * @return the true/false 返回是否ping通 + * @return 返回是否ping通 */ - public static boolean ping(String ip) { + public static boolean ping(final String ip) { return ping(ip, 200); } @@ -754,13 +693,14 @@ public static boolean ping(String ip) { * 检测IP地址是否能ping通 * * @param ip IP地址 - * @param timeout 检测超时(毫秒) - * @return the true/false 是否ping通 + * @param timeout 检测超时(毫秒) + * @return 是否ping通 */ - public static boolean ping(String ip, int timeout) { + public static boolean ping(final String ip, final int timeout) { try { + // 当返回值是true时,说明host是可用的,false则不可 return InetAddress.getByName(ip).isReachable(timeout); - } catch (Exception ex) { + } catch (final Exception ex) { return false; } } @@ -771,7 +711,7 @@ public static boolean ping(String ip, int timeout) { * @param cookieStr Cookie字符串 * @return cookie字符串 */ - public static List parseCookies(String cookieStr) { + public static List parseCookies(final String cookieStr) { if (StringKit.isBlank(cookieStr)) { return Collections.emptyList(); } @@ -785,18 +725,26 @@ public static List parseCookies(String cookieStr) { * @param timeout 检测超时 * @return 远程端口是否开启 */ - public static boolean isOpen(InetSocketAddress address, int timeout) { - try (Socket sc = new Socket()) { + public static boolean isOpen(final InetSocketAddress address, final int timeout) { + try (final Socket sc = new Socket()) { sc.connect(address, timeout); return true; - } catch (Exception e) { + } catch (final Exception e) { return false; } } /** - * 获取DNS信息,如TXT信息: + * 设置全局验证 * + * @param authenticator 验证器 + */ + public static void setGlobalAuthenticator(final Authenticator authenticator) { + Authenticator.setDefault(authenticator); + } + + /** + * 获取DNS信息,如TXT信息: *

          *     NetKit.attrNames("aoju.org", "TXT")
          * 
    @@ -805,34 +753,368 @@ public static boolean isOpen(InetSocketAddress address, int timeout) { * @param attrNames 属性 * @return DNS信息 */ - public static List getDnsInfo(String hostName, String... attrNames) { + public static List getDnsInfo(final String hostName, final String... attrNames) { final String uri = StringKit.addPrefixIfNot(hostName, "dns:"); final Attributes attributes = getAttributes(uri, attrNames); final List infos = new ArrayList<>(); - for (Attribute attribute : new EnumerationIterator<>(attributes.getAll())) { + for (final Attribute attribute : new EnumerationIterator<>(attributes.getAll())) { try { infos.add((String) attribute.get()); - } catch (NamingException ignore) { + } catch (final NamingException ignore) { //ignore } } return infos; } + /** + * 格式化IP段 + * + * @param ip IP地址 + * @param mask 掩码 + * @return 返回xxx.xxx.xxx.xxx/mask的格式 + */ + public static String formatIpBlock(final String ip, final String mask) { + return ip + Symbol.SLASH + getMaskBitByMask(mask); + } + + /** + * 智能转换IP地址集合 + * + * @param ipRange IP段,支持X.X.X.X-X.X.X.X或X.X.X.X/X + * @param isAll true:全量地址,false:可用地址;仅在ipRange为X.X.X.X/X时才生效 + * @return IP集 + */ + public static List list(final String ipRange, final boolean isAll) { + if (ipRange.contains(Symbol.MINUS)) { + // X.X.X.X-X.X.X.X + final String[] range = StringKit.splitToArray(ipRange, Symbol.MINUS); + return list(range[0], range[1]); + } else if (ipRange.contains(Symbol.SLASH)) { + // X.X.X.X/X + final String[] param = StringKit.splitToArray(ipRange, Symbol.SLASH); + return list(param[0], Integer.parseInt(param[1]), isAll); + } else { + return CollKit.of(ipRange); + } + } + + /** + * 根据IP地址、子网掩码获取IP地址区间 + * + * @param ip IP地址 + * @param maskBit 掩码位,例如24、32 + * @param isAll true:全量地址,false:可用地址 + * @return 区间地址 + */ + public static List list(final String ip, final int maskBit, final boolean isAll) { + if (maskBit == Normal._32) { + final List list = new ArrayList<>(); + if (isAll) { + list.add(ip); + } + return list; + } + + String startIp = getBeginIpStr(ip, maskBit); + String endIp = getEndIpStr(ip, maskBit); + if (isAll) { + return list(startIp, endIp); + } + + int lastDotIndex = startIp.lastIndexOf(Symbol.C_DOT) + 1; + startIp = StringKit.subPre(startIp, lastDotIndex) + + (Integer.parseInt(Objects.requireNonNull(StringKit.subSuf(startIp, lastDotIndex))) + 1); + lastDotIndex = endIp.lastIndexOf(Symbol.C_DOT) + 1; + endIp = StringKit.subPre(endIp, lastDotIndex) + + (Integer.parseInt(Objects.requireNonNull(StringKit.subSuf(endIp, lastDotIndex))) - 1); + return list(startIp, endIp); + } + + /** + * 得到IP地址区间 + * + * @param ipFrom 开始IP + * @param ipTo 结束IP + * @return 区间地址 + */ + public static List list(final String ipFrom, final String ipTo) { + final int[] ipf = Convert.convert(int[].class, StringKit.splitToArray(ipFrom, Symbol.C_DOT)); + final int[] ipt = Convert.convert(int[].class, StringKit.splitToArray(ipTo, Symbol.C_DOT)); + + final List ips = new ArrayList<>(); + for (int a = ipf[0]; a <= ipt[0]; a++) { + for (int b = (a == ipf[0] ? ipf[1] : 0); b <= (a == ipt[0] ? ipt[1] + : 255); b++) { + for (int c = (b == ipf[1] ? ipf[2] : 0); c <= (b == ipt[1] ? ipt[2] + : 255); c++) { + for (int d = (c == ipf[2] ? ipf[3] : 0); d <= (c == ipt[2] ? ipt[3] + : 255); d++) { + ips.add(a + "." + b + "." + c + "." + d); + } + } + } + } + return ips; + } + + /** + * 根据long值获取ip v4地址:xx.xx.xx.xx + * + * @param longIP IP的long表示形式 + * @return IP V4 地址 + */ + public static String longToIpv4(final long longIP) { + final StringBuilder sb = StringKit.builder(); + // 直接右移24位 + sb.append(longIP >> 24 & 0xFF); + sb.append(Symbol.C_DOT); + // 将高8位置0,然后右移16位 + sb.append(longIP >> 16 & 0xFF); + sb.append(Symbol.C_DOT); + sb.append(longIP >> 8 & 0xFF); + sb.append(Symbol.C_DOT); + sb.append(longIP & 0xFF); + return sb.toString(); + } + + /** + * 根据ip地址(xxx.xxx.xxx.xxx)计算出long型的数据 + * 方法别名:inet_aton + * + * @param strIP IP V4 地址 + * @return long值 + */ + public static long ipv4ToLong(final String strIP) { + final Matcher matcher = RegEx.IPV4.matcher(strIP); + if (matcher.matches()) { + return matchAddress(matcher); + } + throw new IllegalArgumentException("Invalid IPv4 address!"); + } + + /** + * 根据 ip/掩码位 计算IP段的起始IP(字符串型) + * 方法别名:inet_ntoa + * + * @param ip 给定的IP,如218.240.38.69 + * @param maskBit 给定的掩码位,如30 + * @return 起始IP的字符串表示 + */ + public static String getBeginIpStr(final String ip, final int maskBit) { + return longToIpv4(getBeginIpLong(ip, maskBit)); + } + + /** + * 根据 ip/掩码位 计算IP段的起始IP(Long型) + * + * @param ip 给定的IP,如218.240.38.69 + * @param maskBit 给定的掩码位,如30 + * @return 起始IP的长整型表示 + */ + public static Long getBeginIpLong(final String ip, final int maskBit) { + return ipv4ToLong(ip) & ipv4ToLong(getMaskByMaskBit(maskBit)); + } + + /** + * 根据 ip/掩码位 计算IP段的终止IP(字符串型) + * + * @param ip 给定的IP,如218.240.38.69 + * @param maskBit 给定的掩码位,如30 + * @return 终止IP的字符串表示 + */ + public static String getEndIpStr(final String ip, final int maskBit) { + return longToIpv4(getEndIpLong(ip, maskBit)); + } + + /** + * 根据子网掩码转换为掩码位 + * + * @param mask 掩码的点分十进制表示,例如 255.255.255.0 + * @return 掩码位,例如 24 + * @throws IllegalArgumentException 子网掩码非法 + */ + public static int getMaskBitByMask(final String mask) { + final Integer maskBit = MaskBit.getMaskBit(mask); + if (maskBit == null) { + throw new IllegalArgumentException("Invalid netmask " + mask); + } + return maskBit; + } + + /** + * 计算子网大小 + * + * @param maskBit 掩码位 + * @param isAll true:全量地址,false:可用地址 + * @return 地址总数 + */ + public static int countByMaskBit(final int maskBit, final boolean isAll) { + //如果是可用地址的情况,掩码位小于等于0或大于等于32,则可用地址为0 + if ((false == isAll) && (maskBit <= 0 || maskBit >= 32)) { + return 0; + } + + final int count = (int) Math.pow(2, 32 - maskBit); + return isAll ? count : count - 2; + } + + /** + * 根据掩码位获取掩码 + * + * @param maskBit 掩码位 + * @return 掩码 + */ + public static String getMaskByMaskBit(final int maskBit) { + return MaskBit.get(maskBit); + } + + /** + * 根据开始IP与结束IP计算掩码 + * + * @param fromIp 开始IP + * @param toIp 结束IP + * @return 掩码x.x.x.x + */ + public static String getMaskByIpRange(final String fromIp, final String toIp) { + final long toIpLong = ipv4ToLong(toIp); + final long fromIpLong = ipv4ToLong(fromIp); + Assert.isTrue(fromIpLong < toIpLong, "to IP must be greater than from IP!"); + + final String[] fromIpSplit = StringKit.splitToArray(fromIp, Symbol.C_DOT); + final String[] toIpSplit = StringKit.splitToArray(toIp, Symbol.C_DOT); + final StringBuilder mask = new StringBuilder(); + for (int i = 0; i < toIpSplit.length; i++) { + mask.append(255 - Integer.parseInt(toIpSplit[i]) + Integer.parseInt(fromIpSplit[i])).append(Symbol.C_DOT); + } + return mask.substring(0, mask.length() - 1); + } + + /** + * 计算IP区间有多少个IP + * + * @param fromIp 开始IP + * @param toIp 结束IP + * @return IP数量 + */ + public static int countByIpRange(final String fromIp, final String toIp) { + final long toIpLong = ipv4ToLong(toIp); + final long fromIpLong = ipv4ToLong(fromIp); + if (fromIpLong > toIpLong) { + throw new IllegalArgumentException("to IP must be greater than from IP!"); + } + int count = 1; + final int[] fromIpSplit = StringKit.split(fromIp, Symbol.C_DOT).stream().mapToInt(Integer::parseInt).toArray(); + final int[] toIpSplit = StringKit.split(toIp, Symbol.C_DOT).stream().mapToInt(Integer::parseInt).toArray(); + for (int i = fromIpSplit.length - 1; i >= 0; i--) { + count += (toIpSplit[i] - fromIpSplit[i]) * Math.pow(256, fromIpSplit.length - i - 1); + } + return count; + } + + /** + * 判断掩码是否合法 + * + * @param mask 掩码的点分十进制表示,例如 255.255.255.0 + * @return true:掩码合法;false:掩码不合法 + */ + public static boolean isMaskValid(final String mask) { + return MaskBit.getMaskBit(mask) != null; + } + + /** + * 判断掩码位是否合法 + * + * @param maskBit 掩码位,例如 24 + * @return true:掩码位合法;false:掩码位不合法 + */ + public static boolean isMaskBitValid(final int maskBit) { + return MaskBit.get(maskBit) != null; + } + + /** + * 判定是否为内网IPv4 + * 私有IP: + *
    +     * A类 10.0.0.0-10.255.255.255
    +     * B类 172.16.0.0-172.31.255.255
    +     * C类 192.168.0.0-192.168.255.255
    +     * 
    + * 当然,还有127这个网段是环回地址 + * + * @param ipAddress IP地址 + * @return 是否为内网IP + */ + public static boolean isInnerIP(final String ipAddress) { + final boolean isInnerIp; + final long ipNum = ipv4ToLong(ipAddress); + + final long aBegin = ipv4ToLong("10.0.0.0"); + final long aEnd = ipv4ToLong("10.255.255.255"); + + final long bBegin = ipv4ToLong("172.16.0.0"); + final long bEnd = ipv4ToLong("172.31.255.255"); + + final long cBegin = ipv4ToLong("192.168.0.0"); + final long cEnd = ipv4ToLong("192.168.255.255"); + + isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd) || Http.HTTP_HOST_IPV4.equals(ipAddress); + return isInnerIp; + } + + /** + * 根据 ip/掩码位 计算IP段的终止IP(Long型) + * 注:此接口返回负数,请使用转成字符串后再转Long型 + * + * @param ip 给定的IP,如218.240.38.69 + * @param maskBit 给定的掩码位,如30 + * @return 终止IP的长整型表示 + */ + public static Long getEndIpLong(final String ip, final int maskBit) { + return getBeginIpLong(ip, maskBit) + + ~ipv4ToLong(getMaskByMaskBit(maskBit)); + } + + /** + * 将匹配到的Ipv4地址的4个分组分别处理 + * + * @param matcher 匹配到的Ipv4正则 + * @return ipv4对应long + */ + private static long matchAddress(final Matcher matcher) { + long addr = 0; + for (int i = 1; i <= 4; ++i) { + addr |= Long.parseLong(matcher.group(i)) << 8 * (4 - i); + } + return addr; + } + + /** + * 指定IP的long是否在指定范围内 + * + * @param userIp 用户IP + * @param begin 开始IP + * @param end 结束IP + * @return 是否在范围内 + */ + private static boolean isInner(final long userIp, final long begin, final long end) { + return (userIp >= begin) && (userIp <= end); + } + /** * 创建{@link InitialDirContext} * * @param environment 环境参数,{code null}表示无参数 * @return {@link InitialDirContext} */ - public static InitialDirContext createInitialDirContext(Map environment) { + public static InitialDirContext createInitialDirContext(final Map environment) { try { if (MapKit.isEmpty(environment)) { return new InitialDirContext(); } return new InitialDirContext(Convert.convert(Hashtable.class, environment)); - } catch (NamingException e) { + } catch (final NamingException e) { throw new InternalException(e); } } @@ -843,13 +1125,13 @@ public static InitialDirContext createInitialDirContext(Map envi * @param environment 环境参数,{code null}表示无参数 * @return {@link InitialContext} */ - public static InitialContext createInitialContext(Map environment) { + public static InitialContext createInitialContext(final Map environment) { try { if (MapKit.isEmpty(environment)) { return new InitialContext(); } return new InitialContext(Convert.convert(Hashtable.class, environment)); - } catch (NamingException e) { + } catch (final NamingException e) { throw new InternalException(e); } } @@ -862,24 +1144,12 @@ public static InitialContext createInitialContext(Map environmen * @param attrIds 需要获取的属性ID名称 * @return {@link Attributes} */ - public static Attributes getAttributes(String uri, String... attrIds) { + public static Attributes getAttributes(final String uri, final String... attrIds) { try { return createInitialDirContext(null).getAttributes(uri, attrIds); - } catch (NamingException e) { + } catch (final NamingException e) { throw new InternalException(e); } } - /** - * 指定IP的long是否在指定范围内 - * - * @param userIp 用户IP - * @param begin 开始IP - * @param end 结束IP - * @return 是否在范围内 - */ - private static boolean isInner(long userIp, long begin, long end) { - return (userIp >= begin) && (userIp <= end); - } - } diff --git a/bus-http/src/main/java/org/aoju/bus/http/Builder.java b/bus-http/src/main/java/org/aoju/bus/http/Builder.java index 533b035da2..13c0ffcb3d 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/Builder.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Builder.java @@ -31,6 +31,7 @@ import org.aoju.bus.core.io.source.BufferSource; import org.aoju.bus.core.io.source.Source; import org.aoju.bus.core.lang.Normal; +import org.aoju.bus.core.lang.RegEx; import org.aoju.bus.core.lang.Symbol; import org.aoju.bus.http.bodys.ResponseBody; import org.aoju.bus.http.metric.Internal; @@ -49,7 +50,6 @@ import java.util.*; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; /** * 实用方法工具 @@ -111,18 +111,7 @@ public class Builder { public static final DateFormat[] BROWSER_COMPATIBLE_DATE_FORMATS = new DateFormat[BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length]; - /** - * Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation - * of Android's private InetAddress#isNumeric API. - *

    - * This matches IPv6 addresses as a hex string containing at least one colon, and possibly - * including dots after the first colon. It matches IPv4 addresses as strings containing only - * decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP - * addresses nor hostnames; they will be verified as IP addresses (which is a more strict - * verification). - */ - private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile( - "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)"); + /** * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such * cookies are on the fast path. @@ -455,7 +444,7 @@ public static int indexOfControlOrNonAscii(String input) { * Returns true if {@code host} is not a host name and might be an IP address. */ public static boolean verifyAsIpAddress(String host) { - return VERIFY_AS_IP_ADDRESS.matcher(host).matches(); + return RegEx.IP_ADDRESS.matcher(host).matches(); } public static Charset bomAwareCharset(BufferSource source, Charset charset) throws IOException { diff --git a/bus-http/src/main/java/org/aoju/bus/http/Httpd.java b/bus-http/src/main/java/org/aoju/bus/http/Httpd.java index d0494e4902..3f0e568a98 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/Httpd.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Httpd.java @@ -27,6 +27,8 @@ import org.aoju.bus.core.io.sink.Sink; import org.aoju.bus.core.io.source.Source; +import org.aoju.bus.core.net.ssl.HostnameVerifier; +import org.aoju.bus.core.net.ssl.SSLContextBuilder; import org.aoju.bus.http.accord.ConnectionPool; import org.aoju.bus.http.accord.ConnectionSuite; import org.aoju.bus.http.accord.Exchange; @@ -36,7 +38,9 @@ import org.aoju.bus.http.cache.InternalCache; import org.aoju.bus.http.metric.*; import org.aoju.bus.http.metric.proxy.NullProxySelector; -import org.aoju.bus.http.secure.*; +import org.aoju.bus.http.secure.Authenticator; +import org.aoju.bus.http.secure.CertificateChainCleaner; +import org.aoju.bus.http.secure.CertificatePinner; import org.aoju.bus.http.socket.RealWebSocket; import org.aoju.bus.http.socket.WebSocket; import org.aoju.bus.http.socket.WebSocketListener; diff --git a/bus-http/src/main/java/org/aoju/bus/http/Httpx.java b/bus-http/src/main/java/org/aoju/bus/http/Httpx.java index fec8089aef..b6de2698ae 100755 --- a/bus-http/src/main/java/org/aoju/bus/http/Httpx.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Httpx.java @@ -27,6 +27,7 @@ import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.lang.*; +import org.aoju.bus.core.net.ssl.SSLContextBuilder; import org.aoju.bus.core.toolkit.ArrayKit; import org.aoju.bus.core.toolkit.MapKit; import org.aoju.bus.core.toolkit.ObjectKit; @@ -37,7 +38,6 @@ import org.aoju.bus.http.bodys.RequestBody; import org.aoju.bus.http.metric.Dispatcher; import org.aoju.bus.http.plugin.httpx.HttpProxy; -import org.aoju.bus.http.secure.SSLContextBuilder; import org.aoju.bus.logger.Logger; import javax.net.ssl.HostnameVerifier; diff --git a/bus-http/src/main/java/org/aoju/bus/http/Httpz.java b/bus-http/src/main/java/org/aoju/bus/http/Httpz.java index f177e971e3..ebeb4e8e9e 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/Httpz.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Httpz.java @@ -25,11 +25,11 @@ ********************************************************************************/ package org.aoju.bus.http; +import org.aoju.bus.core.net.ssl.SSLContextBuilder; import org.aoju.bus.http.plugin.httpz.GetBuilder; import org.aoju.bus.http.plugin.httpz.HttpBuilder; import org.aoju.bus.http.plugin.httpz.PostBuilder; import org.aoju.bus.http.plugin.httpz.PutBuilder; -import org.aoju.bus.http.secure.SSLContextBuilder; import javax.net.ssl.X509TrustManager; diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnection.java b/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnection.java index 26939ebf77..ad2bf8c1cd 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnection.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnection.java @@ -32,6 +32,7 @@ import org.aoju.bus.core.lang.Header; import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.core.net.ssl.HostnameVerifier; import org.aoju.bus.core.toolkit.IoKit; import org.aoju.bus.http.*; import org.aoju.bus.http.accord.platform.Platform; @@ -40,7 +41,6 @@ import org.aoju.bus.http.metric.Internal; import org.aoju.bus.http.metric.http.*; import org.aoju.bus.http.secure.CertificatePinner; -import org.aoju.bus.http.secure.HostnameVerifier; import org.aoju.bus.http.socket.Handshake; import org.aoju.bus.http.socket.RealWebSocket; diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Android10Platform.java b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Android10Platform.java index c6abc9d235..ad2f0b5e45 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Android10Platform.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Android10Platform.java @@ -38,6 +38,7 @@ * @since Java 17+ */ class Android10Platform extends AndroidPlatform { + Android10Platform(Class sslParametersClass) { super(sslParametersClass, null, null, null, null, null); } diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/AndroidPlatform.java b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/AndroidPlatform.java index 4fcfef12d0..c3f62796ae 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/AndroidPlatform.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/AndroidPlatform.java @@ -28,7 +28,6 @@ import org.aoju.bus.core.Version; import org.aoju.bus.core.lang.Algorithm; import org.aoju.bus.core.lang.Charset; -import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.http.Builder; import org.aoju.bus.http.Protocol; @@ -37,14 +36,16 @@ import org.aoju.bus.http.secure.TrustRootIndex; import org.aoju.bus.logger.Logger; -import javax.net.ssl.*; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.Socket; -import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; @@ -254,21 +255,6 @@ public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) { } } - @Override - public SSLContext getSSLContext() { - try { - return SSLContext.getInstance(Http.TLS_V_12); - } catch (NoSuchAlgorithmException e) { - // fallback to TLS - } - - try { - return SSLContext.getInstance(Http.TLS); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("No TLS provider", e); - } - } - /** * X509TrustManagerExtensions是在API 17 (Android 4.2, 2012年底发布)中添加到Android的。 * 这是在Android上获得干净链的最好方法,因为它使用与TLS握手相同的代码 diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk8WithJettyBootPlatform.java b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk8WithJettyBootPlatform.java index 5be79c0a75..0de84da059 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk8WithJettyBootPlatform.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk8WithJettyBootPlatform.java @@ -172,4 +172,5 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } } } + } diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Platform.java b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Platform.java index ef0242c771..d671ed0175 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Platform.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Platform.java @@ -26,7 +26,6 @@ package org.aoju.bus.http.accord.platform; import org.aoju.bus.core.io.buffer.Buffer; -import org.aoju.bus.core.lang.Http; import org.aoju.bus.http.Protocol; import org.aoju.bus.http.secure.BasicCertificateChainCleaner; import org.aoju.bus.http.secure.BasicTrustRootIndex; @@ -34,7 +33,6 @@ import org.aoju.bus.http.secure.TrustRootIndex; import org.aoju.bus.logger.Logger; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; @@ -42,7 +40,6 @@ import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.Socket; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; @@ -287,14 +284,6 @@ public CertificateChainCleaner buildCertificateChainCleaner(SSLSocketFactory ssl return buildCertificateChainCleaner(trustManager); } - public SSLContext getSSLContext() { - try { - return SSLContext.getInstance(Http.TLS); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("No TLS provider", e); - } - } - public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) { return new BasicTrustRootIndex(trustManager.getAcceptedIssuers()); } diff --git a/bus-http/src/main/java/org/aoju/bus/http/plugin/httpz/HttpBuilder.java b/bus-http/src/main/java/org/aoju/bus/http/plugin/httpz/HttpBuilder.java index 6eacf426fb..2f2fa90809 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/plugin/httpz/HttpBuilder.java +++ b/bus-http/src/main/java/org/aoju/bus/http/plugin/httpz/HttpBuilder.java @@ -25,6 +25,7 @@ ********************************************************************************/ package org.aoju.bus.http.plugin.httpz; +import org.aoju.bus.core.net.ssl.SSLContextBuilder; import org.aoju.bus.http.DnsX; import org.aoju.bus.http.Httpd; import org.aoju.bus.http.Httpz; @@ -37,7 +38,6 @@ import org.aoju.bus.http.metric.Interceptor; import org.aoju.bus.http.secure.Authenticator; import org.aoju.bus.http.secure.CertificatePinner; -import org.aoju.bus.http.secure.SSLContextBuilder; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java b/bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java index 34ea73fa1b..c01547ea1b 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java +++ b/bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java @@ -25,6 +25,8 @@ ********************************************************************************/ package org.aoju.bus.http.secure; +import org.aoju.bus.core.lang.Http; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -41,23 +43,23 @@ public enum TlsVersion { /** * 2016年版本 */ - TLS_1_3("TLSv1.3"), + TLS_1_3(Http.TLS_V_13), /** * 2008年版本 */ - TLS_1_2("TLSv1.2"), + TLS_1_2(Http.TLS_V_12), /** * 2006年版本 */ - TLS_1_1("TLSv1.1"), + TLS_1_1(Http.TLS_V_11), /** * 1999年版本 */ - TLS_1_0("TLSv1"), + TLS_1_0(Http.TLS_V_10), /** * 1996年版本 */ - SSL_3_0("SSLv3"); + SSL_3_0(Http.SSL_V_30); public final String javaName; @@ -67,15 +69,15 @@ public enum TlsVersion { public static TlsVersion forJavaName(String javaName) { switch (javaName) { - case "TLSv1.3": + case Http.TLS_V_13: return TLS_1_3; - case "TLSv1.2": + case Http.TLS_V_12: return TLS_1_2; - case "TLSv1.1": + case Http.TLS_V_11: return TLS_1_1; - case "TLSv1": + case Http.TLS_V_10: return TLS_1_0; - case "SSLv3": + case Http.SSL_V_30: return SSL_3_0; } throw new IllegalArgumentException("Unexpected TLS version: " + javaName); From 1419a61989b3a8b2b7ac3f09b2ff6c1aecbd9fa3 Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Mon, 31 Oct 2022 11:01:09 +0800 Subject: [PATCH 11/19] update stream and date kit --- .../src/main/java/org/aoju/bus/core/net/tls}/TlsVersion.java | 2 +- .../src/main/java/org/aoju/bus/http/accord/ConnectionSuite.java | 2 +- bus-http/src/main/java/org/aoju/bus/http/cache/Cache.java | 2 +- bus-http/src/main/java/org/aoju/bus/http/socket/Handshake.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename {bus-http/src/main/java/org/aoju/bus/http/secure => bus-core/src/main/java/org/aoju/bus/core/net/tls}/TlsVersion.java (99%) diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/TlsVersion.java similarity index 99% rename from bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java rename to bus-core/src/main/java/org/aoju/bus/core/net/tls/TlsVersion.java index c01547ea1b..4021b40b49 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/TlsVersion.java @@ -23,7 +23,7 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.http.secure; +package org.aoju.bus.core.net.tls; import org.aoju.bus.core.lang.Http; diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/ConnectionSuite.java b/bus-http/src/main/java/org/aoju/bus/http/accord/ConnectionSuite.java index 47947ca636..02ded7232a 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/ConnectionSuite.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/ConnectionSuite.java @@ -25,8 +25,8 @@ ********************************************************************************/ package org.aoju.bus.http.accord; +import org.aoju.bus.core.net.tls.TlsVersion; import org.aoju.bus.http.secure.CipherSuite; -import org.aoju.bus.http.secure.TlsVersion; import javax.net.ssl.SSLSocket; import java.util.Arrays; diff --git a/bus-http/src/main/java/org/aoju/bus/http/cache/Cache.java b/bus-http/src/main/java/org/aoju/bus/http/cache/Cache.java index d121b08ca7..de3c80d8f8 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/cache/Cache.java +++ b/bus-http/src/main/java/org/aoju/bus/http/cache/Cache.java @@ -37,13 +37,13 @@ import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.lang.MediaType; import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.core.net.tls.TlsVersion; import org.aoju.bus.core.toolkit.IoKit; import org.aoju.bus.http.*; import org.aoju.bus.http.accord.platform.Platform; import org.aoju.bus.http.bodys.ResponseBody; import org.aoju.bus.http.metric.http.StatusLine; import org.aoju.bus.http.secure.CipherSuite; -import org.aoju.bus.http.secure.TlsVersion; import org.aoju.bus.http.socket.Handshake; import java.io.Closeable; diff --git a/bus-http/src/main/java/org/aoju/bus/http/socket/Handshake.java b/bus-http/src/main/java/org/aoju/bus/http/socket/Handshake.java index ee0f5a2170..2975cb4e16 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/socket/Handshake.java +++ b/bus-http/src/main/java/org/aoju/bus/http/socket/Handshake.java @@ -25,10 +25,10 @@ ********************************************************************************/ package org.aoju.bus.http.socket; +import org.aoju.bus.core.net.tls.TlsVersion; import org.aoju.bus.http.Builder; import org.aoju.bus.http.accord.ConnectionSuite; import org.aoju.bus.http.secure.CipherSuite; -import org.aoju.bus.http.secure.TlsVersion; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; From b727547a40695e7bf4baf3aad07b95e1d2031ac2 Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Tue, 8 Nov 2022 10:52:52 +0800 Subject: [PATCH 12/19] update stream and date kit --- .../aoju/bus/core/lang/tree/TreeBuilder.java | 5 +- .../net/{ssl => tls}/DefaultTrustManager.java | 2 +- .../net/{ssl => tls}/HostnameVerifier.java | 2 +- .../net/{ssl => tls}/SSLContextBuilder.java | 2 +- .../core/net/{ssl => tls}/package-info.java | 2 +- .../org/aoju/bus/core/toolkit/BeanKit.java | 18 +- .../org/aoju/bus/core/toolkit/CharsKit.java | 590 +++++++++--------- 7 files changed, 323 insertions(+), 298 deletions(-) rename bus-core/src/main/java/org/aoju/bus/core/net/{ssl => tls}/DefaultTrustManager.java (98%) rename bus-core/src/main/java/org/aoju/bus/core/net/{ssl => tls}/HostnameVerifier.java (99%) rename bus-core/src/main/java/org/aoju/bus/core/net/{ssl => tls}/SSLContextBuilder.java (99%) rename bus-core/src/main/java/org/aoju/bus/core/net/{ssl => tls}/package-info.java (66%) diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/tree/TreeBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/lang/tree/TreeBuilder.java index 2df953c9cf..225b44f707 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/tree/TreeBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/tree/TreeBuilder.java @@ -32,7 +32,6 @@ import org.aoju.bus.core.toolkit.MapKit; import org.aoju.bus.core.toolkit.ObjectKit; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -58,10 +57,10 @@ public class TreeBuilder implements Builder> { * @param rootId 根节点ID * @param config 配置 */ - public TreeBuilder(E rootId, NodeConfig config) { + public TreeBuilder(final E rootId, final NodeConfig config) { root = new Tree<>(config); root.setId(rootId); - this.idTreeMap = new HashMap<>(); + this.idTreeMap = new LinkedHashMap<>(); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/ssl/DefaultTrustManager.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/DefaultTrustManager.java similarity index 98% rename from bus-core/src/main/java/org/aoju/bus/core/net/ssl/DefaultTrustManager.java rename to bus-core/src/main/java/org/aoju/bus/core/net/tls/DefaultTrustManager.java index f1f479235b..a3471ffe2a 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/net/ssl/DefaultTrustManager.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/DefaultTrustManager.java @@ -23,7 +23,7 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.net.ssl; +package org.aoju.bus.core.net.tls; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/ssl/HostnameVerifier.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/HostnameVerifier.java similarity index 99% rename from bus-core/src/main/java/org/aoju/bus/core/net/ssl/HostnameVerifier.java rename to bus-core/src/main/java/org/aoju/bus/core/net/tls/HostnameVerifier.java index 2656228a50..fba28e9028 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/net/ssl/HostnameVerifier.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/HostnameVerifier.java @@ -23,7 +23,7 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.net.ssl; +package org.aoju.bus.core.net.tls; import org.aoju.bus.core.lang.RegEx; import org.aoju.bus.core.lang.Symbol; diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/ssl/SSLContextBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java similarity index 99% rename from bus-core/src/main/java/org/aoju/bus/core/net/ssl/SSLContextBuilder.java rename to bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java index 80de979b78..fb24f109fa 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/net/ssl/SSLContextBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java @@ -23,7 +23,7 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.net.ssl; +package org.aoju.bus.core.net.tls; import org.aoju.bus.core.builder.Builder; import org.aoju.bus.core.exception.InternalException; diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/ssl/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/package-info.java similarity index 66% rename from bus-core/src/main/java/org/aoju/bus/core/net/ssl/package-info.java rename to bus-core/src/main/java/org/aoju/bus/core/net/tls/package-info.java index caa61be88d..d3be708dc6 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/net/ssl/package-info.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/package-info.java @@ -4,4 +4,4 @@ * @author Kimi Liu * @since Java 17+ */ -package org.aoju.bus.core.net.ssl; +package org.aoju.bus.core.net.tls; diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java index e80f0f9840..b1127c926e 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java @@ -93,16 +93,16 @@ public static boolean isReadable(Class clazz) { * 判断Bean是否为空对象,空对象表示本身为null或者所有属性都为null * * @param bean Bean对象 - * @param ignoreFiledNames 忽略检查的字段名 + * @param ignoreFieldNames 忽略检查的字段名 * @return 是否为空,true - 空 / false - 非空 */ - public static boolean isEmpty(Object bean, String... ignoreFiledNames) { + public static boolean isEmpty(Object bean, String... ignoreFieldNames) { if (null != bean) { for (Field field : ReflectKit.getFields(bean.getClass())) { if (isStatic(field)) { continue; } - if ((false == ArrayKit.contains(ignoreFiledNames, field.getName())) + if ((false == ArrayKit.contains(ignoreFieldNames, field.getName())) && null != ReflectKit.getFieldValue(bean, field)) { return false; } @@ -115,11 +115,11 @@ public static boolean isEmpty(Object bean, String... ignoreFiledNames) { * 判断Bean是否为非空对象,非空对象表示本身不为null或者含有非null属性的对象 * * @param bean Bean对象 - * @param ignoreFiledNames 忽略检查的字段名 + * @param ignoreFieldNames 忽略检查的字段名 * @return 是否为空,true - 空 / false - 非空 */ - public static boolean isNotEmpty(Object bean, String... ignoreFiledNames) { - return false == isEmpty(bean, ignoreFiledNames); + public static boolean isNotEmpty(Object bean, String... ignoreFieldNames) { + return false == isEmpty(bean, ignoreFieldNames); } /** @@ -227,10 +227,10 @@ public static boolean hasNullField(Object bean) { * 对象本身为null也返回true * * @param bean Bean对象 - * @param ignoreFiledNames 忽略检查的字段名 + * @param ignoreFieldNames 忽略检查的字段名 * @return 是否包含值为null的属性,true - 包含 / false - 不包含 */ - public static boolean hasNullField(Object bean, String... ignoreFiledNames) { + public static boolean hasNullField(Object bean, String... ignoreFieldNames) { if (null == bean) { return true; } @@ -238,7 +238,7 @@ public static boolean hasNullField(Object bean, String... ignoreFiledNames) { if (isStatic(field)) { continue; } - if ((false == ArrayKit.contains(ignoreFiledNames, field.getName())) + if ((false == ArrayKit.contains(ignoreFieldNames, field.getName())) && null == ReflectKit.getFieldValue(bean, field)) { return true; } diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java index 0bc13be22b..bce19a2328 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java @@ -293,10 +293,10 @@ public static boolean isBlankChar(final int args) { * * 例: *

      - *
    • {@code StringKit.isBlank(null) // true}
    • - *
    • {@code StringKit.isBlank("") // true}
    • - *
    • {@code StringKit.isBlank(" \t\n") // true}
    • - *
    • {@code StringKit.isBlank("abc") // false}
    • + *
    • {@code isBlank(null) // true}
    • + *
    • {@code isBlank("") // true}
    • + *
    • {@code isBlank(" \t\n") // true}
    • + *
    • {@code isBlank("abc") // false}
    • *
    * 注意:该方法与 {@link #isEmpty(CharSequence)} 的区别是: * 该方法会校验空白字符,且性能相对于 {@link #isEmpty(CharSequence)} 略慢 @@ -336,10 +336,10 @@ public static boolean isBlank(CharSequence text) { * * 例: *
      - *
    • {@code StringKit.isNotBlank(null) // false}
    • - *
    • {@code StringKit.isNotBlank("") // false}
    • - *
    • {@code StringKit.isNotBlank(" \t\n") // false}
    • - *
    • {@code StringKit.isNotBlank("abc") // true}
    • + *
    • {@code isNotBlank(null) // false}
    • + *
    • {@code isNotBlank("") // false}
    • + *
    • {@code isNotBlank(" \t\n") // false}
    • + *
    • {@code isNotBlank("abc") // true}
    • *
    * 注意:该方法与 {@link #isNotEmpty(CharSequence)} 的区别是: * 该方法会校验空白字符,且性能相对于 {@link #isNotEmpty(CharSequence)} 略慢 @@ -357,16 +357,16 @@ public static boolean isNotBlank(CharSequence text) { * 检查是否没有字符序列为空("")、空字符或仅为空格 * *
    -     * StringKit.isNoneBlank(null)             = false
    -     * StringKit.isNoneBlank(null, "foo")      = false
    -     * StringKit.isNoneBlank(null, null)       = false
    -     * StringKit.isNoneBlank("", "bar")        = false
    -     * StringKit.isNoneBlank("bob", "")        = false
    -     * StringKit.isNoneBlank("  bob  ", null)  = false
    -     * StringKit.isNoneBlank(" ", "bar")       = false
    -     * StringKit.isNoneBlank(new String[] {})  = true
    -     * StringKit.isNoneBlank(new String[]{""}) = false
    -     * StringKit.isNoneBlank("foo", "bar")     = true
    +     * isNoneBlank(null)             = false
    +     * isNoneBlank(null, "foo")      = false
    +     * isNoneBlank(null, null)       = false
    +     * isNoneBlank("", "bar")        = false
    +     * isNoneBlank("bob", "")        = false
    +     * isNoneBlank("  bob  ", null)  = false
    +     * isNoneBlank(" ", "bar")       = false
    +     * isNoneBlank(new String[] {})  = true
    +     * isNoneBlank(new String[]{""}) = false
    +     * isNoneBlank("foo", "bar")     = true
          * 
    * * @param args 要检查的字符串可以为null或空 @@ -380,17 +380,17 @@ public static boolean isNoneBlank(final CharSequence... args) { * 检查任何一个字符序列是否为空(""),或为空,或仅为空白 * *
    -     * StringKit.isAnyBlank((String) null)    = true
    -     * StringKit.isAnyBlank((String[]) null)  = false
    -     * StringKit.isAnyBlank(null, "foo")      = true
    -     * StringKit.isAnyBlank(null, null)       = true
    -     * StringKit.isAnyBlank("", "bar")        = true
    -     * StringKit.isAnyBlank("bob", "")        = true
    -     * StringKit.isAnyBlank("  bob  ", null)  = true
    -     * StringKit.isAnyBlank(" ", "bar")       = true
    -     * StringKit.isAnyBlank(new String[] {})  = false
    -     * StringKit.isAnyBlank(new String[]{""}) = true
    -     * StringKit.isAnyBlank("foo", "bar")     = false
    +     * isAnyBlank((String) null)    = true
    +     * isAnyBlank((String[]) null)  = false
    +     * isAnyBlank(null, "foo")      = true
    +     * isAnyBlank(null, null)       = true
    +     * isAnyBlank("", "bar")        = true
    +     * isAnyBlank("bob", "")        = true
    +     * isAnyBlank("  bob  ", null)  = true
    +     * isAnyBlank(" ", "bar")       = true
    +     * isAnyBlank(new String[] {})  = false
    +     * isAnyBlank(new String[]{""}) = true
    +     * isAnyBlank("foo", "bar")     = false
          * 
    * * @param args 要检查的字符序列可以为空或空 @@ -413,10 +413,10 @@ public static boolean isAnyBlank(final CharSequence... args) { * 如果指定的字符串数组的长度为 0,或者所有元素都是空字符串,则返回 true * 例: *
      - *
    • {@code StringKit.isAllBlank() // true}
    • - *
    • {@code StringKit.isAllBlank("", null, " ") // true}
    • - *
    • {@code StringKit.isAllBlank("123", " ") // false}
    • - *
    • {@code StringKit.isAllBlank("123", "abc") // false}
    • + *
    • {@code isAllBlank() // true}
    • + *
    • {@code isAllBlank("", null, " ") // true}
    • + *
    • {@code isAllBlank("123", " ") // false}
    • + *
    • {@code isAllBlank("123", "abc") // false}
    • *
    * 注意:该方法与 {@link #hasBlank(CharSequence...)} 的区别在于: *
      @@ -448,10 +448,10 @@ public static boolean isAllBlank(CharSequence... args) { * * 例: *
        - *
      • {@code StringKit.isEmpty(null) // true}
      • - *
      • {@code StringKit.isEmpty("") // true}
      • - *
      • {@code StringKit.isEmpty(" \t\n") // false}
      • - *
      • {@code StringKit.isEmpty("abc") // false}
      • + *
      • {@code isEmpty(null) // true}
      • + *
      • {@code isEmpty("") // true}
      • + *
      • {@code isEmpty(" \t\n") // false}
      • + *
      • {@code isEmpty("abc") // false}
      • *
      * 注意:该方法与 {@link #isBlank(CharSequence)} 的区别是:该方法不校验空白字符 * 建议: @@ -476,10 +476,10 @@ public static boolean isEmpty(CharSequence text) { * * 例: *
        - *
      • {@code StringKit.isNotEmpty(null) // false}
      • - *
      • {@code StringKit.isNotEmpty("") // false}
      • - *
      • {@code StringKit.isNotEmpty(" \t\n") // true}
      • - *
      • {@code StringKit.isNotEmpty("abc") // true}
      • + *
      • {@code isNotEmpty(null) // false}
      • + *
      • {@code isNotEmpty("") // false}
      • + *
      • {@code isNotEmpty(" \t\n") // true}
      • + *
      • {@code isNotEmpty("abc") // true}
      • *
      * 注意:该方法与 {@link #isNotBlank(CharSequence)} 的区别是:该方法不校验空白字符 * 建议:该方法建议用于工具类或任何可以预期的方法参数的校验中 @@ -497,11 +497,11 @@ public static boolean isNotEmpty(CharSequence text) { * 如果指定的字符串数组的长度为 0,或者所有元素都是空字符串,则返回 true * 例: *
        - *
      • {@code StringKit.isAllEmpty() // true}
      • - *
      • {@code StringKit.isAllEmpty("", null) // true}
      • - *
      • {@code StringKit.isAllEmpty("123", "") // false}
      • - *
      • {@code StringKit.isAllEmpty("123", "abc") // false}
      • - *
      • {@code StringKit.isAllEmpty(" ", "\t", "\n") // false}
      • + *
      • {@code isAllEmpty() // true}
      • + *
      • {@code isAllEmpty("", null) // true}
      • + *
      • {@code isAllEmpty("123", "") // false}
      • + *
      • {@code isAllEmpty("123", "abc") // false}
      • + *
      • {@code isAllEmpty(" ", "\t", "\n") // false}
      • *
      * 注意:该方法与 {@link #hasEmpty(CharSequence...)} 的区别在于: *
        @@ -530,11 +530,11 @@ public static boolean isAllEmpty(CharSequence... args) { * 如果指定的字符串数组的长度不为 0,或者所有元素都不是空字符串,则返回 true * 例: *
          - *
        • {@code StringKit.isAllNotEmpty() // false}
        • - *
        • {@code StringKit.isAllNotEmpty("", null) // false}
        • - *
        • {@code StringKit.isAllNotEmpty("123", "") // false}
        • - *
        • {@code StringKit.isAllNotEmpty("123", "abc") // true}
        • - *
        • {@code StringKit.isAllNotEmpty(" ", "\t", "\n") // true}
        • + *
        • {@code isAllNotEmpty() // false}
        • + *
        • {@code isAllNotEmpty("", null) // false}
        • + *
        • {@code isAllNotEmpty("123", "") // false}
        • + *
        • {@code isAllNotEmpty("123", "abc") // true}
        • + *
        • {@code isAllNotEmpty(" ", "\t", "\n") // true}
        • *
        * 注意:该方法与 {@link #isAllEmpty(CharSequence...)} 的区别在于: *
          @@ -625,10 +625,10 @@ private static boolean isNotNullAndNotUndefinedString(CharSequence text) { * 如果指定的字符串数组的长度为 0,或者其中的任意一个元素是空字符串,则返回 true * 例: *
            - *
          • {@code StringKit.hasBlank() // true}
          • - *
          • {@code StringKit.hasBlank("", null, " ") // true}
          • - *
          • {@code StringKit.hasBlank("123", " ") // true}
          • - *
          • {@code StringKit.hasBlank("123", "abc") // false}
          • + *
          • {@code hasBlank() // true}
          • + *
          • {@code hasBlank("", null, " ") // true}
          • + *
          • {@code hasBlank("123", " ") // true}
          • + *
          • {@code hasBlank("123", "abc") // false}
          • *
          * 注意:该方法与 {@link #isAllBlank(CharSequence...)} 的区别在于: *
            @@ -698,11 +698,11 @@ public static String emptyToNull(CharSequence text) { * 如果指定的字符串数组的长度为 0,或者其中的任意一个元素是空字符串,则返回 true * 例: *
              - *
            • {@code StringKit.hasEmpty() // true}
            • - *
            • {@code StringKit.hasEmpty("", null) // true}
            • - *
            • {@code StringKit.hasEmpty("123", "") // true}
            • - *
            • {@code StringKit.hasEmpty("123", "abc") // false}
            • - *
            • {@code StringKit.hasEmpty(" ", "\t", "\n") // false}
            • + *
            • {@code hasEmpty() // true}
            • + *
            • {@code hasEmpty("", null) // true}
            • + *
            • {@code hasEmpty("123", "") // true}
            • + *
            • {@code hasEmpty("123", "abc") // false}
            • + *
            • {@code hasEmpty(" ", "\t", "\n") // false}
            • *
            * 注意:该方法与 {@link #isAllEmpty(CharSequence...)} 的区别在于: *
              @@ -1076,11 +1076,11 @@ public static String trim(CharSequence text, int mode, Predicate pred * 删除字符串两端的空白字符(char <= 32),如果字符串在修剪后为空("") * 或者如果字符串为{@code null},则返回{@code null} *
              -     * StringKit.trimToNull(null)           = null
              -     * StringKit.trimToNull("")             = null
              -     * StringKit.trimToNull("     ")        = null
              -     * StringKit.trimToNull("abc")          = "abc"
              -     * StringKit.trimToEmpty("    abc    ") = "abc"
              +     * trimToNull(null)           = null
              +     * trimToNull("")             = null
              +     * trimToNull("     ")        = null
              +     * trimToNull("abc")          = "abc"
              +     * trimToEmpty("    abc    ") = "abc"
                    * 
              * * @param text 字符串 @@ -1575,14 +1575,14 @@ public static String subSuf(CharSequence string, int fromIndex) { * 如果分隔字符串为空串"",则返回空串,如果分隔字符串未找到,返回原字符串,举例如下: * *
              -     * StringKit.subBefore(null, *)      = null
              -     * StringKit.subBefore("", *)        = ""
              -     * StringKit.subBefore("abc", "a")   = ""
              -     * StringKit.subBefore("abcba", "b") = "a"
              -     * StringKit.subBefore("abc", "c")   = "ab"
              -     * StringKit.subBefore("abc", "d")   = "abc"
              -     * StringKit.subBefore("abc", "")    = ""
              -     * StringKit.subBefore("abc", null)  = "abc"
              +     * subBefore(null, *)      = null
              +     * subBefore("", *)        = ""
              +     * subBefore("abc", "a")   = ""
              +     * subBefore("abcba", "b") = "a"
              +     * subBefore("abc", "c")   = "ab"
              +     * subBefore("abc", "d")   = "abc"
              +     * subBefore("abc", "")    = ""
              +     * subBefore("abc", null)  = "abc"
                    * 
              * * @param string 被查找的字符串 @@ -1616,12 +1616,12 @@ public static String subBefore(CharSequence string, CharSequence separator, bool * 如果分隔字符串未找到,返回原字符串,举例如下: * *
              -     * StringKit.subBefore(null, *)      = null
              -     * StringKit.subBefore("", *)        = ""
              -     * StringKit.subBefore("abc", 'a')   = ""
              -     * StringKit.subBefore("abcba", 'b') = "a"
              -     * StringKit.subBefore("abc", 'c')   = "ab"
              -     * StringKit.subBefore("abc", 'd')   = "abc"
              +     * subBefore(null, *)      = null
              +     * subBefore("", *)        = ""
              +     * subBefore("abc", 'a')   = ""
              +     * subBefore("abcba", 'b') = "a"
              +     * subBefore("abc", 'c')   = "ab"
              +     * subBefore("abc", 'd')   = "abc"
                    * 
              * * @param string 被查找的字符串 @@ -1651,14 +1651,14 @@ public static String subBefore(CharSequence string, char separator, boolean isLa * 如果分隔字符串为空串(null或""),则返回空串,如果分隔字符串未找到,返回空串,举例如下: * *
              -     * StringKit.subAfter(null, *)      = null
              -     * StringKit.subAfter("", *)        = ""
              -     * StringKit.subAfter(*, null)      = ""
              -     * StringKit.subAfter("abc", "a")   = "bc"
              -     * StringKit.subAfter("abcba", "b") = "cba"
              -     * StringKit.subAfter("abc", "c")   = ""
              -     * StringKit.subAfter("abc", "d")   = ""
              -     * StringKit.subAfter("abc", "")    = "abc"
              +     * subAfter(null, *)      = null
              +     * subAfter("", *)        = ""
              +     * subAfter(*, null)      = ""
              +     * subAfter("abc", "a")   = "bc"
              +     * subAfter("abcba", "b") = "cba"
              +     * subAfter("abc", "c")   = ""
              +     * subAfter("abc", "d")   = ""
              +     * subAfter("abc", "")    = "abc"
                    * 
              * * @param string 被查找的字符串 @@ -1688,12 +1688,12 @@ public static String subAfter(CharSequence string, CharSequence separator, boole * 如果分隔字符串为空串(null或""),则返回空串,如果分隔字符串未找到,返回空串,举例如下: * *
              -     * StringKit.subAfter(null, *)      = null
              -     * StringKit.subAfter("", *)        = ""
              -     * StringKit.subAfter("abc", 'a')   = "bc"
              -     * StringKit.subAfter("abcba", 'b') = "cba"
              -     * StringKit.subAfter("abc", 'c')   = ""
              -     * StringKit.subAfter("abc", 'd')   = ""
              +     * subAfter(null, *)      = null
              +     * subAfter("", *)        = ""
              +     * subAfter("abc", 'a')   = "bc"
              +     * subAfter("abcba", 'b') = "cba"
              +     * subAfter("abc", 'c')   = ""
              +     * subAfter("abc", 'd')   = ""
                    * 
              * * @param string 被查找的字符串 @@ -1729,16 +1729,16 @@ public static String subBetween(CharSequence text, CharSequence before, CharSequ * 截取指定字符串中间部分,不包括标识字符串 * *
              -     * StringKit.subBetween("wx[b]yz", "[", "]")     = "b"
              -     * StringKit.subBetween(null, *, *)              = null
              -     * StringKit.subBetween(*, null, *)              = null
              -     * StringKit.subBetween(*, *, null)              = null
              -     * StringKit.subBetween("", "", "")              = ""
              -     * StringKit.subBetween("", "", "]")             = null
              -     * StringKit.subBetween("", "[", "]")            = null
              -     * StringKit.subBetween("yabcz", "", "")         = ""
              -     * StringKit.subBetween("yabcz", "y", "z")       = "abc"
              -     * StringKit.subBetween("yabczyabcz", "y", "z")  = "abc"
              +     * subBetween("wx[b]yz", "[", "]")     = "b"
              +     * subBetween(null, *, *)              = null
              +     * subBetween(*, null, *)              = null
              +     * subBetween(*, *, null)              = null
              +     * subBetween("", "", "")              = ""
              +     * subBetween("", "", "]")             = null
              +     * subBetween("", "[", "]")            = null
              +     * subBetween("yabcz", "", "")         = ""
              +     * subBetween("yabcz", "y", "z")       = "abc"
              +     * subBetween("yabczyabcz", "y", "z")  = "abc"
                    * 
              * * @param text 被切割的字符串 @@ -1763,12 +1763,12 @@ public static String subBetween(String text, String before, String after) { /** * 截取指定字符串中间部分,不包括标识字符串 *
              -     * StringKit.subBetween(null, *)            = null
              -     * StringKit.subBetween("", "")             = ""
              -     * StringKit.subBetween("", "tag")          = null
              -     * StringKit.subBetween("tagabctag", null)  = null
              -     * StringKit.subBetween("tagabctag", "")    = ""
              -     * StringKit.subBetween("tagabctag", "tag") = "abc"
              +     * subBetween(null, *)            = null
              +     * subBetween("", "")             = ""
              +     * subBetween("", "tag")          = null
              +     * subBetween("tagabctag", null)  = null
              +     * subBetween("tagabctag", "")    = ""
              +     * subBetween("tagabctag", "tag") = "abc"
                    * 
              * * @param text 被切割的字符串 @@ -1782,17 +1782,17 @@ public static String subBetween(CharSequence text, CharSequence beforeAndAfter) /** * 截取指定字符串多段中间部分,不包括标识字符串 *
              -     * StringKit.subBetweenAll("wx[b]y[z]", "[", "]") 		    = ["b","z"]
              -     * StringKit.subBetweenAll(null, *, *)          			= []
              -     * StringKit.subBetweenAll(*, null, *)          			= []
              -     * StringKit.subBetweenAll(*, *, null)          			= []
              -     * StringKit.subBetweenAll("", "", "")          			= []
              -     * StringKit.subBetweenAll("", "", "]")         			= []
              -     * StringKit.subBetweenAll("", "[", "]")        			= []
              -     * StringKit.subBetweenAll("yabcz", "", "")     			= []
              -     * StringKit.subBetweenAll("yabcz", "y", "z")   			= ["abc"]
              -     * StringKit.subBetweenAll("yabczyabcz", "y", "z")   		= ["abc","abc"]
              -     * StringKit.subBetweenAll("[yabc[zy]abcz]", "[", "]");     = ["zy"]
              +     * subBetweenAll("wx[b]y[z]", "[", "]") 		    = ["b","z"]
              +     * subBetweenAll(null, *, *)          			= []
              +     * subBetweenAll(*, null, *)          			= []
              +     * subBetweenAll(*, *, null)          			= []
              +     * subBetweenAll("", "", "")          			= []
              +     * subBetweenAll("", "", "]")         			= []
              +     * subBetweenAll("", "[", "]")        			= []
              +     * subBetweenAll("yabcz", "", "")     			= []
              +     * subBetweenAll("yabcz", "y", "z")   			= ["abc"]
              +     * subBetweenAll("yabczyabcz", "y", "z")   		= ["abc","abc"]
              +     * subBetweenAll("[yabc[zy]abcz]", "[", "]");     = ["zy"]
                    * 
              * * @param text 被切割的字符串 @@ -1835,22 +1835,22 @@ public static String[] subBetweenAll(CharSequence text, CharSequence prefix, Cha * 栗子: * *
              -     * StringKit.subBetweenAll(null, *)          			= []
              -     * StringKit.subBetweenAll(*, null)          			= []
              -     * StringKit.subBetweenAll(*, *)          			    = []
              -     * StringKit.subBetweenAll("", "")          			= []
              -     * StringKit.subBetweenAll("", "#")         			= []
              -     * StringKit.subBetweenAll("hello", "")     		    = []
              -     * StringKit.subBetweenAll("#hello#", "#")   		    = ["hello"]
              -     * StringKit.subBetweenAll("#hello# #world#!", "#")     = ["hello", "world"]
              -     * StringKit.subBetweenAll("#hello# world#!", "#");     = ["hello"]
              +     * subBetweenAll(null, *)          			= []
              +     * subBetweenAll(*, null)          			= []
              +     * subBetweenAll(*, *)          			    = []
              +     * subBetweenAll("", "")          			= []
              +     * subBetweenAll("", "#")         			= []
              +     * subBetweenAll("hello", "")     		    = []
              +     * subBetweenAll("#hello#", "#")   		    = ["hello"]
              +     * subBetweenAll("#hello# #world#!", "#")     = ["hello", "world"]
              +     * subBetweenAll("#hello# world#!", "#");     = ["hello"]
                    * 
              * * @param text 被切割的字符串 * @param prefixAndSuffix 截取开始和结束的字符串标识 * @return 截取后的字符串 */ - public static String[] subBetweenAll(CharSequence text, CharSequence prefixAndSuffix) { + public static String[] subBetweenAll(final CharSequence text, final CharSequence prefixAndSuffix) { return subBetweenAll(text, prefixAndSuffix, prefixAndSuffix); } @@ -1858,20 +1858,20 @@ public static String[] subBetweenAll(CharSequence text, CharSequence prefixAndSu * 切割指定长度的后部分的字符串 * *
              -     * StringKit.subSufByLength("abcde", 3)      =    "cde"
              -     * StringKit.subSufByLength("abcde", 0)      =    ""
              -     * StringKit.subSufByLength("abcde", -5)     =    ""
              -     * StringKit.subSufByLength("abcde", -1)     =    ""
              -     * StringKit.subSufByLength("abcde", 5)      =    "abcde"
              -     * StringKit.subSufByLength("abcde", 10)     =    "abcde"
              -     * StringKit.subSufByLength(null, 3)         =     null
              +     * subSufByLength("abcde", 3)      =    "cde"
              +     * subSufByLength("abcde", 0)      =    ""
              +     * subSufByLength("abcde", -5)     =    ""
              +     * subSufByLength("abcde", -1)     =    ""
              +     * subSufByLength("abcde", 5)      =    "abcde"
              +     * subSufByLength("abcde", 10)     =    "abcde"
              +     * subSufByLength(null, 3)         =     null
                    * 
              * * @param text 字符串 * @param length 切割长度 * @return 切割后后剩余的后半部分字符串 */ - public static String subByLength(CharSequence text, int length) { + public static String subSufByLength(final CharSequence text, int length) { if (isEmpty(text)) { return null; } @@ -1881,6 +1881,31 @@ public static String subByLength(CharSequence text, int length) { return sub(text, -length, text.length()); } + /** + * 截取字符串,从指定位置开始,截取指定长度的字符串 + * + * @param input 原始字符串 + * @param fromIndex 开始的index,包括 + * @param length 要截取的长度 + * @return 截取后的字符串 + */ + public static String subByLength(final CharSequence input, final int fromIndex, final int length) { + if (isEmpty(input)) { + return null; + } + if (length <= 0) { + return Normal.EMPTY; + } + + final int toIndex; + if (fromIndex < 0) { + toIndex = fromIndex - length; + } else { + toIndex = fromIndex + length; + } + return sub(input, fromIndex, toIndex); + } + /** * 比较两个字符串(大小写敏感) * @@ -2042,17 +2067,17 @@ public static int indexOf(final CharSequence text, char word, int start, int end * 指定范围内查找字符串,忽略大小写 * *
              -     * StringKit.indexOfIgnoreCase(null, *, *)          = -1
              -     * StringKit.indexOfIgnoreCase(*, null, *)          = -1
              -     * StringKit.indexOfIgnoreCase("", "", 0)           = 0
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
              -     * StringKit.indexOfIgnoreCase("abc", "", 9)        = -1
              +     * indexOfIgnoreCase(null, *, *)          = -1
              +     * indexOfIgnoreCase(*, null, *)          = -1
              +     * indexOfIgnoreCase("", "", 0)           = 0
              +     * indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
              +     * indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
              +     * indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
              +     * indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
              +     * indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
              +     * indexOfIgnoreCase("aabaabaa", "B", -1) = 2
              +     * indexOfIgnoreCase("aabaabaa", "", 2)   = 2
              +     * indexOfIgnoreCase("abc", "", 9)        = -1
                    * 
              * * @param text 字符串 @@ -2067,17 +2092,17 @@ public static int indexOfIgnoreCase(final CharSequence text, final CharSequence * 指定范围内查找字符串 * *
              -     * StringKit.indexOfIgnoreCase(null, *, *)          = -1
              -     * StringKit.indexOfIgnoreCase(*, null, *)          = -1
              -     * StringKit.indexOfIgnoreCase("", "", 0)           = 0
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
              -     * StringKit.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
              -     * StringKit.indexOfIgnoreCase("abc", "", 9)        = -1
              +     * indexOfIgnoreCase(null, *, *)          = -1
              +     * indexOfIgnoreCase(*, null, *)          = -1
              +     * indexOfIgnoreCase("", "", 0)           = 0
              +     * indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
              +     * indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
              +     * indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
              +     * indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
              +     * indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
              +     * indexOfIgnoreCase("aabaabaa", "B", -1) = 2
              +     * indexOfIgnoreCase("aabaabaa", "", 2)   = 2
              +     * indexOfIgnoreCase("abc", "", 9)        = -1
                    * 
              * * @param text 字符串 @@ -2618,9 +2643,9 @@ public static String repeatByLength(CharSequence text, int padLen) { * 重复某个字符串并通过分界符连接 * *
              -     * StringKit.repeatAndJoin("?", 5, ",")   = "?,?,?,?,?"
              -     * StringKit.repeatAndJoin("?", 0, ",")   = ""
              -     * StringKit.repeatAndJoin("?", 5, null)  = "?????"
              +     * repeatAndJoin("?", 5, ",")   = "?,?,?,?,?"
              +     * repeatAndJoin("?", 0, ",")   = ""
              +     * repeatAndJoin("?", 5, null)  = "?????"
                    * 
              * * @param text 被重复的字符串 @@ -3994,25 +4019,25 @@ private static String appendIfMissing(final String text, * 如果字符串还没有以任何后缀结尾,则将后缀追加到字符串的末尾 * *
              -     * StringKit.appendIfMissing(null, null) = null
              -     * StringKit.appendIfMissing("abc", null) = "abc"
              -     * StringKit.appendIfMissing("", "xyz") = "xyz"
              -     * StringKit.appendIfMissing("abc", "xyz") = "abcxyz"
              -     * StringKit.appendIfMissing("abcxyz", "xyz") = "abcxyz"
              -     * StringKit.appendIfMissing("abcXYZ", "xyz") = "abcXYZxyz"
              +     * appendIfMissing(null, null) = null
              +     * appendIfMissing("abc", null) = "abc"
              +     * appendIfMissing("", "xyz") = "xyz"
              +     * appendIfMissing("abc", "xyz") = "abcxyz"
              +     * appendIfMissing("abcxyz", "xyz") = "abcxyz"
              +     * appendIfMissing("abcXYZ", "xyz") = "abcXYZxyz"
                    * 
              *

              With additional suffixes,

              *
              -     * StringKit.appendIfMissing(null, null, null) = null
              -     * StringKit.appendIfMissing("abc", null, null) = "abc"
              -     * StringKit.appendIfMissing("", "xyz", null) = "xyz"
              -     * StringKit.appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
              -     * StringKit.appendIfMissing("abc", "xyz", "") = "abc"
              -     * StringKit.appendIfMissing("abc", "xyz", "mno") = "abcxyz"
              -     * StringKit.appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz"
              -     * StringKit.appendIfMissing("abcmno", "xyz", "mno") = "abcmno"
              -     * StringKit.appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZxyz"
              -     * StringKit.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNOxyz"
              +     * appendIfMissing(null, null, null) = null
              +     * appendIfMissing("abc", null, null) = "abc"
              +     * appendIfMissing("", "xyz", null) = "xyz"
              +     * appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
              +     * appendIfMissing("abc", "xyz", "") = "abc"
              +     * appendIfMissing("abc", "xyz", "mno") = "abcxyz"
              +     * appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz"
              +     * appendIfMissing("abcmno", "xyz", "mno") = "abcmno"
              +     * appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZxyz"
              +     * appendIfMissing("abcMNO", "xyz", "mno") = "abcMNOxyz"
                    * 
              * * @param text 字符串 @@ -4030,25 +4055,25 @@ public static String appendIfMissing(final String text, * 如果字符串还没有结束,则使用任何后缀将后缀追加到字符串的末尾,不区分大小写. * *
              -     * StringKit.appendIfMissingIgnoreCase(null, null) = null
              -     * StringKit.appendIfMissingIgnoreCase("abc", null) = "abc"
              -     * StringKit.appendIfMissingIgnoreCase("", "xyz") = "xyz"
              -     * StringKit.appendIfMissingIgnoreCase("abc", "xyz") = "abcxyz"
              -     * StringKit.appendIfMissingIgnoreCase("abcxyz", "xyz") = "abcxyz"
              -     * StringKit.appendIfMissingIgnoreCase("abcXYZ", "xyz") = "abcXYZ"
              +     * appendIfMissingIgnoreCase(null, null) = null
              +     * appendIfMissingIgnoreCase("abc", null) = "abc"
              +     * appendIfMissingIgnoreCase("", "xyz") = "xyz"
              +     * appendIfMissingIgnoreCase("abc", "xyz") = "abcxyz"
              +     * appendIfMissingIgnoreCase("abcxyz", "xyz") = "abcxyz"
              +     * appendIfMissingIgnoreCase("abcXYZ", "xyz") = "abcXYZ"
                    * 
              *

              With additional suffixes,

              *
              -     * StringKit.appendIfMissingIgnoreCase(null, null, null) = null
              -     * StringKit.appendIfMissingIgnoreCase("abc", null, null) = "abc"
              -     * StringKit.appendIfMissingIgnoreCase("", "xyz", null) = "xyz"
              -     * StringKit.appendIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
              -     * StringKit.appendIfMissingIgnoreCase("abc", "xyz", "") = "abc"
              -     * StringKit.appendIfMissingIgnoreCase("abc", "xyz", "mno") = "axyz"
              -     * StringKit.appendIfMissingIgnoreCase("abcxyz", "xyz", "mno") = "abcxyz"
              -     * StringKit.appendIfMissingIgnoreCase("abcmno", "xyz", "mno") = "abcmno"
              -     * StringKit.appendIfMissingIgnoreCase("abcXYZ", "xyz", "mno") = "abcXYZ"
              -     * StringKit.appendIfMissingIgnoreCase("abcMNO", "xyz", "mno") = "abcMNO"
              +     * appendIfMissingIgnoreCase(null, null, null) = null
              +     * appendIfMissingIgnoreCase("abc", null, null) = "abc"
              +     * appendIfMissingIgnoreCase("", "xyz", null) = "xyz"
              +     * appendIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
              +     * appendIfMissingIgnoreCase("abc", "xyz", "") = "abc"
              +     * appendIfMissingIgnoreCase("abc", "xyz", "mno") = "axyz"
              +     * appendIfMissingIgnoreCase("abcxyz", "xyz", "mno") = "abcxyz"
              +     * appendIfMissingIgnoreCase("abcmno", "xyz", "mno") = "abcmno"
              +     * appendIfMissingIgnoreCase("abcXYZ", "xyz", "mno") = "abcXYZ"
              +     * appendIfMissingIgnoreCase("abcMNO", "xyz", "mno") = "abcMNO"
                    * 
              * * @param text 字符串 @@ -4092,25 +4117,25 @@ private static String prependIfMissing(final String text, * 如果字符串还没有以任何前缀开始,则将前缀添加到字符串的开头 * *
              -     * StringKit.prependIfMissing(null, null) = null
              -     * StringKit.prependIfMissing("abc", null) = "abc"
              -     * StringKit.prependIfMissing("", "xyz") = "xyz"
              -     * StringKit.prependIfMissing("abc", "xyz") = "xyzabc"
              -     * StringKit.prependIfMissing("xyzabc", "xyz") = "xyzabc"
              -     * StringKit.prependIfMissing("XYZabc", "xyz") = "xyzXYZabc"
              +     * prependIfMissing(null, null) = null
              +     * prependIfMissing("abc", null) = "abc"
              +     * prependIfMissing("", "xyz") = "xyz"
              +     * prependIfMissing("abc", "xyz") = "xyzabc"
              +     * prependIfMissing("xyzabc", "xyz") = "xyzabc"
              +     * prependIfMissing("XYZabc", "xyz") = "xyzXYZabc"
                    * 
              *

              With additional prefixes,

              *
              -     * StringKit.prependIfMissing(null, null, null) = null
              -     * StringKit.prependIfMissing("abc", null, null) = "abc"
              -     * StringKit.prependIfMissing("", "xyz", null) = "xyz"
              -     * StringKit.prependIfMissing("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
              -     * StringKit.prependIfMissing("abc", "xyz", "") = "abc"
              -     * StringKit.prependIfMissing("abc", "xyz", "mno") = "xyzabc"
              -     * StringKit.prependIfMissing("xyzabc", "xyz", "mno") = "xyzabc"
              -     * StringKit.prependIfMissing("mnoabc", "xyz", "mno") = "mnoabc"
              -     * StringKit.prependIfMissing("XYZabc", "xyz", "mno") = "xyzXYZabc"
              -     * StringKit.prependIfMissing("MNOabc", "xyz", "mno") = "xyzMNOabc"
              +     * prependIfMissing(null, null, null) = null
              +     * prependIfMissing("abc", null, null) = "abc"
              +     * prependIfMissing("", "xyz", null) = "xyz"
              +     * prependIfMissing("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
              +     * prependIfMissing("abc", "xyz", "") = "abc"
              +     * prependIfMissing("abc", "xyz", "mno") = "xyzabc"
              +     * prependIfMissing("xyzabc", "xyz", "mno") = "xyzabc"
              +     * prependIfMissing("mnoabc", "xyz", "mno") = "mnoabc"
              +     * prependIfMissing("XYZabc", "xyz", "mno") = "xyzXYZabc"
              +     * prependIfMissing("MNOabc", "xyz", "mno") = "xyzMNOabc"
                    * 
              * * @param text T字符串 @@ -4128,25 +4153,25 @@ public static String prependIfMissing(final String text, * 如果字符串尚未开始,则将前缀添加到字符串的开头,不区分大小写,并使用任何前缀 * *
              -     * StringKit.prependIfMissingIgnoreCase(null, null) = null
              -     * StringKit.prependIfMissingIgnoreCase("abc", null) = "abc"
              -     * StringKit.prependIfMissingIgnoreCase("", "xyz") = "xyz"
              -     * StringKit.prependIfMissingIgnoreCase("abc", "xyz") = "xyzabc"
              -     * StringKit.prependIfMissingIgnoreCase("xyzabc", "xyz") = "xyzabc"
              -     * StringKit.prependIfMissingIgnoreCase("XYZabc", "xyz") = "XYZabc"
              +     * prependIfMissingIgnoreCase(null, null) = null
              +     * prependIfMissingIgnoreCase("abc", null) = "abc"
              +     * prependIfMissingIgnoreCase("", "xyz") = "xyz"
              +     * prependIfMissingIgnoreCase("abc", "xyz") = "xyzabc"
              +     * prependIfMissingIgnoreCase("xyzabc", "xyz") = "xyzabc"
              +     * prependIfMissingIgnoreCase("XYZabc", "xyz") = "XYZabc"
                    * 
              *

              With additional prefixes,

              *
              -     * StringKit.prependIfMissingIgnoreCase(null, null, null) = null
              -     * StringKit.prependIfMissingIgnoreCase("abc", null, null) = "abc"
              -     * StringKit.prependIfMissingIgnoreCase("", "xyz", null) = "xyz"
              -     * StringKit.prependIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
              -     * StringKit.prependIfMissingIgnoreCase("abc", "xyz", "") = "abc"
              -     * StringKit.prependIfMissingIgnoreCase("abc", "xyz", "mno") = "xyzabc"
              -     * StringKit.prependIfMissingIgnoreCase("xyzabc", "xyz", "mno") = "xyzabc"
              -     * StringKit.prependIfMissingIgnoreCase("mnoabc", "xyz", "mno") = "mnoabc"
              -     * StringKit.prependIfMissingIgnoreCase("XYZabc", "xyz", "mno") = "XYZabc"
              -     * StringKit.prependIfMissingIgnoreCase("MNOabc", "xyz", "mno") = "MNOabc"
              +     * prependIfMissingIgnoreCase(null, null, null) = null
              +     * prependIfMissingIgnoreCase("abc", null, null) = "abc"
              +     * prependIfMissingIgnoreCase("", "xyz", null) = "xyz"
              +     * prependIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
              +     * prependIfMissingIgnoreCase("abc", "xyz", "") = "abc"
              +     * prependIfMissingIgnoreCase("abc", "xyz", "mno") = "xyzabc"
              +     * prependIfMissingIgnoreCase("xyzabc", "xyz", "mno") = "xyzabc"
              +     * prependIfMissingIgnoreCase("mnoabc", "xyz", "mno") = "mnoabc"
              +     * prependIfMissingIgnoreCase("XYZabc", "xyz", "mno") = "XYZabc"
              +     * prependIfMissingIgnoreCase("MNOabc", "xyz", "mno") = "MNOabc"
                    * 
              * * @param text 字符串 @@ -4182,12 +4207,12 @@ public static String maxLength(CharSequence string, int length) { * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串 * *
              -     * StringKit.center(null, *)   = null
              -     * StringKit.center("", 4)     = "    "
              -     * StringKit.center("ab", -1)  = "ab"
              -     * StringKit.center("ab", 4)   = " ab "
              -     * StringKit.center("abcd", 2) = "abcd"
              -     * StringKit.center("a", 4)    = " a  "
              +     * center(null, *)   = null
              +     * center("", 4)     = "    "
              +     * center("ab", -1)  = "ab"
              +     * center("ab", 4)   = " ab "
              +     * center("abcd", 2) = "abcd"
              +     * center("a", 4)    = " a  "
                    * 
              * * @param text 字符串 @@ -4202,14 +4227,14 @@ public static String center(CharSequence text, final int size) { * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串 * *
              -     * StringKit.center(null, *, *)     = null
              -     * StringKit.center("", 4, ' ')     = "    "
              -     * StringKit.center("ab", -1, ' ')  = "ab"
              -     * StringKit.center("ab", 4, ' ')   = " ab "
              -     * StringKit.center("abcd", 2, ' ') = "abcd"
              -     * StringKit.center("a", 4, ' ')    = " a  "
              -     * StringKit.center("a", 4, 'y')   = "yayy"
              -     * StringKit.center("abc", 7, ' ')   = "  abc  "
              +     * center(null, *, *)     = null
              +     * center("", 4, ' ')     = "    "
              +     * center("ab", -1, ' ')  = "ab"
              +     * center("ab", 4, ' ')   = " ab "
              +     * center("abcd", 2, ' ') = "abcd"
              +     * center("a", 4, ' ')    = " a  "
              +     * center("a", 4, 'y')   = "yayy"
              +     * center("abc", 7, ' ')   = "  abc  "
                    * 
              * * @param text 字符串 @@ -4235,15 +4260,15 @@ public static String center(CharSequence text, final int size, char padChar) { * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串 * *
              -     * StringKit.center(null, *, *)     = null
              -     * StringKit.center("", 4, " ")     = "    "
              -     * StringKit.center("ab", -1, " ")  = "ab"
              -     * StringKit.center("ab", 4, " ")   = " ab "
              -     * StringKit.center("abcd", 2, " ") = "abcd"
              -     * StringKit.center("a", 4, " ")    = " a  "
              -     * StringKit.center("a", 4, "yz")   = "yayz"
              -     * StringKit.center("abc", 7, null) = "  abc  "
              -     * StringKit.center("abc", 7, "")   = "  abc  "
              +     * center(null, *, *)     = null
              +     * center("", 4, " ")     = "    "
              +     * center("ab", -1, " ")  = "ab"
              +     * center("ab", 4, " ")   = " ab "
              +     * center("abcd", 2, " ") = "abcd"
              +     * center("a", 4, " ")    = " a  "
              +     * center("a", 4, "yz")   = "yayz"
              +     * center("abc", 7, null) = "  abc  "
              +     * center("abc", 7, "")   = "  abc  "
                    * 
              * * @param text 字符串 @@ -4272,9 +4297,9 @@ public static String center(CharSequence text, final int size, CharSequence padT * 补充字符串以满足最小长度 * *
              -     * StringKit.padPre(null, *, *);//null
              -     * StringKit.padPre("1", 3, "ABC");//"AB1"
              -     * StringKit.padPre("123", 2, "ABC");//"12"
              +     * padPre(null, *, *);//null
              +     * padPre("1", 3, "ABC");//"AB1"
              +     * padPre("123", 2, "ABC");//"12"
                    * 
              * * @param text 字符串 @@ -4300,9 +4325,9 @@ public static String padPre(CharSequence text, int minLength, CharSequence padTe * 补充字符串以满足最小长度 * *
              -     * StringKit.padPre(null, *, *);//null
              -     * StringKit.padPre("1", 3, '0');//"001"
              -     * StringKit.padPre("123", 2, '0');//"12"
              +     * padPre(null, *, *);//null
              +     * padPre("1", 3, '0');//"001"
              +     * padPre("123", 2, '0');//"12"
                    * 
              * * @param text 字符串 @@ -4328,9 +4353,9 @@ public static String padPre(CharSequence text, int minLength, char padChar) { * 补充字符串以满足最小长度 * *
              -     * StringKit.padAfter(null, *, *);//null
              -     * StringKit.padAfter("1", 3, '0');//"100"
              -     * StringKit.padAfter("123", 2, '0');//"23"
              +     * padAfter(null, *, *);//null
              +     * padAfter("1", 3, '0');//"100"
              +     * padAfter("123", 2, '0');//"23"
                    * 
              * * @param text 字符串,如果为null,直接返回null @@ -4356,9 +4381,9 @@ public static String padAfter(CharSequence text, int minLength, char padChar) { * 补充字符串以满足最小长度 * *
              -     * StringKit.padAfter(null, *, *);//null
              -     * StringKit.padAfter("1", 3, "ABC");//"1AB"
              -     * StringKit.padAfter("123", 2, "ABC");//"23"
              +     * padAfter(null, *, *);//null
              +     * padAfter("1", 3, "ABC");//"1AB"
              +     * padAfter("123", 2, "ABC");//"23"
                    * 
              * * @param text 字符串,如果为null,直接返回null @@ -4374,7 +4399,8 @@ public static String padAfter(CharSequence text, int minLength, CharSequence pad if (strLen == minLength) { return text.toString(); } else if (strLen > minLength) { - return subByLength(text, minLength); + // 如果提供的字符串大于指定长度,截断之 + return subSufByLength(text, minLength); } return text.toString().concat(repeatByLength(padText, minLength - strLen)); @@ -4470,17 +4496,17 @@ public static int length(final CharSequence text) { * 如果 text=null 或 word=null 或 ordinal小于等于0 则返回-1 * *
              -     * StringKit.ordinalIndexOf(null, *, *)          = -1
              -     * StringKit.ordinalIndexOf(*, null, *)          = -1
              -     * StringKit.ordinalIndexOf("", "", *)           = 0
              -     * StringKit.ordinalIndexOf("aabaabaa", "a", 1)  = 0
              -     * StringKit.ordinalIndexOf("aabaabaa", "a", 2)  = 1
              -     * StringKit.ordinalIndexOf("aabaabaa", "b", 1)  = 2
              -     * StringKit.ordinalIndexOf("aabaabaa", "b", 2)  = 5
              -     * StringKit.ordinalIndexOf("aabaabaa", "ab", 1) = 1
              -     * StringKit.ordinalIndexOf("aabaabaa", "ab", 2) = 4
              -     * StringKit.ordinalIndexOf("aabaabaa", "", 1)   = 0
              -     * StringKit.ordinalIndexOf("aabaabaa", "", 2)   = 0
              +     * ordinalIndexOf(null, *, *)          = -1
              +     * ordinalIndexOf(*, null, *)          = -1
              +     * ordinalIndexOf("", "", *)           = 0
              +     * ordinalIndexOf("aabaabaa", "a", 1)  = 0
              +     * ordinalIndexOf("aabaabaa", "a", 2)  = 1
              +     * ordinalIndexOf("aabaabaa", "b", 1)  = 2
              +     * ordinalIndexOf("aabaabaa", "b", 2)  = 5
              +     * ordinalIndexOf("aabaabaa", "ab", 1) = 1
              +     * ordinalIndexOf("aabaabaa", "ab", 2) = 4
              +     * ordinalIndexOf("aabaabaa", "", 1)   = 0
              +     * ordinalIndexOf("aabaabaa", "", 2)   = 0
                    * 
              * * @param text 被检查的字符串,可以为null @@ -4532,14 +4558,14 @@ public static String toString(char text) { * 检查CharSequence是否以提供的大小写敏感的后缀结尾. * *
              -     * StringKit.endsWithAny(null, null)      = false
              -     * StringKit.endsWithAny(null, new String[] {"abc"})  = false
              -     * StringKit.endsWithAny("abcxyz", null)     = false
              -     * StringKit.endsWithAny("abcxyz", new String[] {""}) = true
              -     * StringKit.endsWithAny("abcxyz", new String[] {"xyz"}) = true
              -     * StringKit.endsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
              -     * StringKit.endsWithAny("abcXYZ", "def", "XYZ") = true
              -     * StringKit.endsWithAny("abcXYZ", "def", "xyz") = false
              +     * endsWithAny(null, null)      = false
              +     * endsWithAny(null, new String[] {"abc"})  = false
              +     * endsWithAny("abcxyz", null)     = false
              +     * endsWithAny("abcxyz", new String[] {""}) = true
              +     * endsWithAny("abcxyz", new String[] {"xyz"}) = true
              +     * endsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
              +     * endsWithAny("abcXYZ", "def", "XYZ") = true
              +     * endsWithAny("abcXYZ", "def", "xyz") = false
                    * 
              * * @param text 要检查的CharSequence可能为空 From baae364764f75a7c4583a1b190cc0aa2a219bf6e Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Mon, 14 Nov 2022 09:26:28 +0800 Subject: [PATCH 13/19] update http and io kit --- .../java/org/aoju/bus/core/io/Blending.java | 0 .../java/org/aoju/bus/core/io/ByteString.java | 150 +++- .../java/org/aoju/bus/core/io/FileSystem.java | 170 +++++ .../java/org/aoju/bus/core/io/LifeCycle.java | 26 +- .../java/org/aoju/bus/core/io/Segment.java | 39 +- .../org/aoju/bus/core/io/buffer/Buffer.java | 250 +++++-- .../aoju/bus/core/io/buffer/ByteBuffer.java | 0 .../org/aoju/bus/core/io/sink/AssignSink.java | 0 .../org/aoju/bus/core/io/sink/BufferSink.java | 273 +++++++ .../aoju/bus/core/io/sink/DeflaterSink.java | 35 +- .../org/aoju/bus/core/io/sink/GzipSink.java | 59 +- .../org/aoju/bus/core/io/sink/RealSink.java | 0 .../java/org/aoju/bus/core/io/sink/Sink.java | 22 +- .../aoju/bus/core/io/source/AssignSource.java | 2 +- .../aoju/bus/core/io/source/BufferSource.java | 298 +++----- .../aoju/bus/core/io/source/GzipSource.java | 88 ++- ...ExtractSource.java => InflaterSource.java} | 53 +- .../aoju/bus/core/io/source/PeekSource.java | 21 +- .../aoju/bus/core/io/source/RealSource.java | 12 +- .../org/aoju/bus/core/io/source/Source.java | 0 .../bus/core/io/timout/AssignTimeout.java | 3 + .../aoju/bus/core/io/timout/AsyncTimeout.java | 174 +++-- .../org/aoju/bus/core/io/timout/Timeout.java | 90 ++- .../bus/core/net/tls/SSLContextBuilder.java | 24 +- .../java/org/aoju/bus/core/toolkit/IoKit.java | 20 +- .../main/java/org/aoju/bus/http/Cookie.java | 5 +- .../main/java/org/aoju/bus/http/Httpd.java | 5 +- .../main/java/org/aoju/bus/http/Httpx.java | 2 +- .../main/java/org/aoju/bus/http/Httpz.java | 2 +- .../main/java/org/aoju/bus/http/RealCall.java | 4 +- .../main/java/org/aoju/bus/http/UnoUrl.java | 20 +- .../org/aoju/bus/http/accord/Exchange.java | 109 ++- .../aoju/bus/http/accord/ExchangeFinder.java | 84 +-- .../aoju/bus/http/accord/RealConnection.java | 16 +- .../bus/http/accord/RealConnectionPool.java | 2 +- .../aoju/bus/http/accord/RouteException.java | 56 +- .../aoju/bus/http/accord/RouteSelector.java | 1 + .../org/aoju/bus/http/accord/Transmitter.java | 28 +- .../accord/platform/Android10Platform.java | 96 --- .../http/accord/platform/AndroidPlatform.java | 408 ----------- .../platform/Jdk8WithJettyBootPlatform.java | 176 ----- .../{Jdk9Platform.java => JdkPlatform.java} | 8 +- .../bus/http/accord/platform/Platform.java | 52 +- .../org/aoju/bus/http/bodys/FormBody.java | 1 + .../org/aoju/bus/http/bodys/RequestBody.java | 19 +- .../org/aoju/bus/http/bodys/ResponseBody.java | 24 +- .../java/org/aoju/bus/http/cache/Cache.java | 13 +- .../org/aoju/bus/http/cache/CacheControl.java | 1 + .../aoju/bus/http/cache/CacheInterceptor.java | 2 +- .../org/aoju/bus/http/cache/DiskLruCache.java | 673 +++++++++--------- .../metric/http/CallServerInterceptor.java | 5 +- .../aoju/bus/http/metric/http/Http1Codec.java | 2 - .../org/aoju/bus/http/metric/http/Http2.java | 4 - .../bus/http/metric/http/Http2Connection.java | 12 +- .../bus/http/metric/http/Http2Stream.java | 3 +- .../bus/http/metric/http/Http2Writer.java | 2 +- .../http/metric/http/RetryAndFollowUp.java | 3 +- .../http/metric/suffix/SuffixDatabase.java | 359 ---------- .../bus/http/plugin/httpz/HttpBuilder.java | 2 +- .../secure/BasicCertificateChainCleaner.java | 1 - .../bus/http/secure/CertificatePinner.java | 3 +- .../org/aoju/bus/http/secure/Credentials.java | 1 + .../aoju/bus/http/socket/RealWebSocket.java | 4 +- .../aoju/bus/http/socket/WebSocketReader.java | 2 +- .../aoju/bus/http/socket/WebSocketWriter.java | 2 +- .../resources/META-INF/suffixes/suffixes.gz | Bin 63744 -> 0 bytes 66 files changed, 1860 insertions(+), 2161 deletions(-) mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/Blending.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/ByteString.java create mode 100644 bus-core/src/main/java/org/aoju/bus/core/io/FileSystem.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/LifeCycle.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/Segment.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/buffer/Buffer.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/sink/AssignSink.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/sink/BufferSink.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/sink/RealSink.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/sink/Sink.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/source/AssignSource.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/source/BufferSource.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/source/GzipSource.java rename bus-core/src/main/java/org/aoju/bus/core/io/source/{ExtractSource.java => InflaterSource.java} (74%) mode change 100755 => 100644 mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/source/PeekSource.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/source/RealSource.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/source/Source.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/timout/AssignTimeout.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/timout/AsyncTimeout.java mode change 100755 => 100644 bus-core/src/main/java/org/aoju/bus/core/io/timout/Timeout.java delete mode 100644 bus-http/src/main/java/org/aoju/bus/http/accord/platform/Android10Platform.java delete mode 100644 bus-http/src/main/java/org/aoju/bus/http/accord/platform/AndroidPlatform.java delete mode 100644 bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk8WithJettyBootPlatform.java rename bus-http/src/main/java/org/aoju/bus/http/accord/platform/{Jdk9Platform.java => JdkPlatform.java} (95%) delete mode 100644 bus-http/src/main/java/org/aoju/bus/http/metric/suffix/SuffixDatabase.java delete mode 100644 bus-http/src/main/resources/META-INF/suffixes/suffixes.gz diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/Blending.java b/bus-core/src/main/java/org/aoju/bus/core/io/Blending.java old mode 100755 new mode 100644 diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/ByteString.java b/bus-core/src/main/java/org/aoju/bus/core/io/ByteString.java old mode 100755 new mode 100644 index b5f74ef706..168855f790 --- a/bus-core/src/main/java/org/aoju/bus/core/io/ByteString.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/ByteString.java @@ -51,21 +51,19 @@ */ public class ByteString implements Serializable, Comparable { + /** + * A singleton empty {@code ByteString}. + */ public static final ByteString EMPTY = ByteString.of(); - public transient int hashCode; - - private byte[] data; - private transient String utf8; - - public ByteString() { - - } + private static final long serialVersionUID = 1L; + public final byte[] data; + public transient int hashCode; // Lazily computed; 0 if unknown. + public transient String utf8; // Lazily computed. public ByteString(byte[] data) { - this.data = data; + this.data = data; // Trusted internal constructor doesn't clone data. } - public static ByteString of(byte... data) { if (null == data) { throw new IllegalArgumentException("data == null"); @@ -73,6 +71,10 @@ public static ByteString of(byte... data) { return new ByteString(data.clone()); } + /** + * Returns a new byte string containing a copy of {@code byteCount} bytes of {@code data} starting + * at {@code offset}. + */ public static ByteString of(byte[] data, int offset, int byteCount) { if (null == data) { throw new IllegalArgumentException("data == null"); @@ -94,6 +96,9 @@ public static ByteString of(ByteBuffer data) { return new ByteString(copy); } + /** + * Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. + */ public static ByteString encodeUtf8(String s) { if (null == s) { throw new IllegalArgumentException("s == null"); @@ -103,6 +108,9 @@ public static ByteString encodeUtf8(String s) { return byteString; } + /** + * Returns a new byte string containing the {@code charset}-encoded bytes of {@code s}. + */ public static ByteString encodeString(String s, java.nio.charset.Charset charset) { if (null == s) { throw new IllegalArgumentException("s == null"); @@ -113,6 +121,10 @@ public static ByteString encodeString(String s, java.nio.charset.Charset charset return new ByteString(s.getBytes(charset)); } + /** + * Decodes the Base64-encoded bytes and returns their value as a byte string. + * Returns null if {@code base64} is not a Base64-encoded sequence of bytes. + */ public static ByteString decodeBase64(String base64) { if (null == base64) { throw new IllegalArgumentException("base64 == null"); @@ -121,6 +133,9 @@ public static ByteString decodeBase64(String base64) { return null != decoded ? new ByteString(decoded) : null; } + /** + * Decodes the hex-encoded bytes and returns their value a byte string. + */ public static ByteString decodeHex(String hex) { if (null == hex) { throw new IllegalArgumentException("hex == null"); @@ -145,6 +160,12 @@ private static int decodeHexDigit(char c) { throw new IllegalArgumentException("Unexpected hex digit: " + c); } + /** + * Reads {@code count} bytes from {@code in} and returns the result. + * + * @throws java.io.EOFException if {@code in} has fewer than {@code count} + * bytes to read. + */ public static ByteString read(InputStream in, int byteCount) throws IOException { if (null == in) { throw new IllegalArgumentException("in == null"); @@ -161,6 +182,24 @@ public static ByteString read(InputStream in, int byteCount) throws IOException return new ByteString(result); } + static int codePointIndexToCharIndex(String s, int codePointCount) { + for (int i = 0, j = 0, length = s.length(), c; i < length; i += Character.charCount(c)) { + if (j == codePointCount) { + return i; + } + c = s.codePointAt(i); + if ((Character.isISOControl(c) && c != '\n' && c != '\r') + || c == Buffer.REPLACEMENT_CHARACTER) { + return -1; + } + j++; + } + return s.length(); + } + + /** + * Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. + */ public String utf8() { String result = utf8; // We don't care if we double-allocate in racy code. @@ -174,22 +213,39 @@ public String string(java.nio.charset.Charset charset) { return new String(data, charset); } + /** + * Returns this byte string encoded as Base64. In violation of the + * RFC, the returned string does not wrap lines at 76 columns. + */ public String base64() { return Base64.encode(data); } + /** + * Returns the 128-bit MD5 hash of this byte string. + */ public ByteString md5() { return digest(Algorithm.MD5.getValue()); } + /** + * Returns the 160-bit SHA-1 hash of this byte string. + */ public ByteString sha1() { return digest(Algorithm.SHA1.getValue()); } + /** + * Returns the 256-bit SHA-256 hash of this byte string. + */ public ByteString sha256() { return digest(Algorithm.SHA256.getValue()); } + /** + * Returns the 512-bit SHA-512 hash of this byte string. + */ public ByteString sha512() { return digest(Algorithm.SHA512.getValue()); } @@ -202,14 +258,23 @@ private ByteString digest(String algorithm) { } } + /** + * Returns the 160-bit SHA-1 HMAC of this byte string. + */ public ByteString hmacSha1(ByteString key) { return hmac(Algorithm.HMACSHA1.getValue(), key); } + /** + * Returns the 256-bit SHA-256 HMAC of this byte string. + */ public ByteString hmacSha256(ByteString key) { return hmac(Algorithm.HMACSHA256.getValue(), key); } + /** + * Returns the 512-bit SHA-512 HMAC of this byte string. + */ public ByteString hmacSha512(ByteString key) { return hmac(Algorithm.HMACSHA512.getValue(), key); } @@ -226,10 +291,17 @@ private ByteString hmac(String algorithm, ByteString key) { } } + /** + * Returns this byte string encoded as URL-safe + * Base64. + */ public String base64Url() { return Base64.encodeUrlSafe(data); } + /** + * Returns this byte string encoded in hexadecimal. + */ public String hex() { char[] result = new char[data.length * 2]; int c = 0; @@ -240,8 +312,13 @@ public String hex() { return new String(result); } + /** + * Returns a byte string equal to this byte string, but with the bytes 'A' + * through 'Z' replaced with the corresponding byte in 'a' through 'z'. + * Returns this byte string if it contains no bytes in 'A' through 'Z'. + */ public ByteString toAsciiLowercase() { - // Search for an uppercase character. If we don't find first, return this. + // Search for an uppercase character. If we don't find one, return this. for (int i = 0; i < data.length; i++) { byte c = data[i]; if (c < 'A' || c > 'Z') continue; @@ -260,8 +337,13 @@ public ByteString toAsciiLowercase() { return this; } + /** + * Returns a byte string equal to this byte string, but with the bytes 'a' + * through 'z' replaced with the corresponding byte in 'A' through 'Z'. + * Returns this byte string if it contains no bytes in 'a' through 'z'. + */ public ByteString toAsciiUppercase() { - // Search for an lowercase character. If we don't find first, return this. + // Search for an lowercase character. If we don't find one, return this. for (int i = 0; i < data.length; i++) { byte c = data[i]; if (c < 'a' || c > 'z') continue; @@ -280,10 +362,19 @@ public ByteString toAsciiUppercase() { return this; } + /** + * Returns a byte string that is a substring of this byte string, beginning at the specified + * index until the end of this string. Returns this byte string if {@code beginIndex} is 0. + */ public ByteString substring(int beginIndex) { return substring(beginIndex, data.length); } + /** + * Returns a byte string that is a substring of this byte string, beginning at the specified + * {@code beginIndex} and ends at the specified {@code endIndex}. Returns this byte string if + * {@code beginIndex} is 0 and {@code endIndex} is the length of this byte string. + */ public ByteString substring(int beginIndex, int endIndex) { if (beginIndex < 0) throw new IllegalArgumentException("beginIndex < 0"); if (endIndex > data.length) { @@ -302,41 +393,70 @@ public ByteString substring(int beginIndex, int endIndex) { return new ByteString(copy); } + /** + * Returns the byte at {@code pos}. + */ public byte getByte(int pos) { return data[pos]; } + /** + * Returns the number of bytes in this ByteString. + */ public int size() { return data.length; } + /** + * Returns a byte array containing a copy of the bytes in this {@code ByteString}. + */ public byte[] toByteArray() { return data.clone(); } + /** + * Returns the bytes of this string without a defensive copy. Do not mutate! + */ public byte[] internalArray() { return data; } + /** + * Returns a {@code ByteBuffer} view of the bytes in this {@code ByteString}. + */ public ByteBuffer asByteBuffer() { return ByteBuffer.wrap(data).asReadOnlyBuffer(); } + /** + * Writes the contents of this byte string to {@code out}. + */ public void write(OutputStream out) throws IOException { - if (null == out) { - throw new IllegalArgumentException("out == null"); - } + if (out == null) throw new IllegalArgumentException("out == null"); out.write(data); } + /** + * Writes the contents of this byte string to {@code buffer}. + */ public void write(Buffer buffer) { buffer.write(data, 0, data.length); } + /** + * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of + * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is + * out of bounds. + */ public boolean rangeEquals(int offset, ByteString other, int otherOffset, int byteCount) { return other.rangeEquals(otherOffset, this.data, offset, byteCount); } + /** + * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of + * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is + * out of bounds. + */ public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) { return offset >= 0 && offset <= data.length - byteCount && otherOffset >= 0 && otherOffset <= other.length - byteCount diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/FileSystem.java b/bus-core/src/main/java/org/aoju/bus/core/io/FileSystem.java new file mode 100644 index 0000000000..9a8b8056c2 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/FileSystem.java @@ -0,0 +1,170 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io; + +import org.aoju.bus.core.io.sink.Sink; +import org.aoju.bus.core.io.source.Source; +import org.aoju.bus.core.toolkit.IoKit; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * Access to read and write files on a hierarchical data store. Most callers should use the {@link + * #SYSTEM} implementation, which uses the host machine's local file system. Alternate + * implementations may be used to inject faults (for testing) or to transform stored data (to add + * encryption, for example). + * + *

              All operations on a file system are racy. For example, guarding a call to {@link #source} with + * {@link #exists} does not guarantee that {@link FileNotFoundException} will not be thrown. The + * file may be moved between the two calls! + * + *

              This interface is less ambitious than {@link java.nio.file.FileSystem} introduced in Java 7. + * It lacks important features like file watching, metadata, permissions, and disk space + * information. In exchange for these limitations, this interface is easier to implement and works + * on all versions of Java and Android. + */ +public interface FileSystem { + + /** + * The host machine's local file system. + */ + FileSystem SYSTEM = new FileSystem() { + @Override + public Source source(File file) throws FileNotFoundException { + return IoKit.source(file); + } + + @Override + public Sink sink(File file) throws FileNotFoundException { + try { + return IoKit.sink(file); + } catch (FileNotFoundException e) { + // Maybe the parent directory doesn't exist? Try creating it first. + file.getParentFile().mkdirs(); + return IoKit.sink(file); + } + } + + @Override + public Sink appendingSink(File file) throws FileNotFoundException { + try { + return IoKit.appendingSink(file); + } catch (FileNotFoundException e) { + // Maybe the parent directory doesn't exist? Try creating it first. + file.getParentFile().mkdirs(); + return IoKit.appendingSink(file); + } + } + + @Override + public void delete(File file) throws IOException { + // If delete() fails, make sure it's because the file didn't exist! + if (!file.delete() && file.exists()) { + throw new IOException("failed to delete " + file); + } + } + + @Override + public boolean exists(File file) { + return file.exists(); + } + + @Override + public long size(File file) { + return file.length(); + } + + @Override + public void rename(File from, File to) throws IOException { + delete(to); + if (!from.renameTo(to)) { + throw new IOException("failed to rename " + from + " to " + to); + } + } + + @Override + public void deleteContents(File directory) throws IOException { + File[] files = directory.listFiles(); + if (files == null) { + throw new IOException("not a readable directory: " + directory); + } + for (File file : files) { + if (file.isDirectory()) { + deleteContents(file); + } + if (!file.delete()) { + throw new IOException("failed to delete " + file); + } + } + } + }; + + /** + * Reads from {@code file}. + */ + Source source(File file) throws FileNotFoundException; + + /** + * Writes to {@code file}, discarding any data already present. Creates parent directories if + * necessary. + */ + Sink sink(File file) throws FileNotFoundException; + + /** + * Writes to {@code file}, appending if data is already present. Creates parent directories if + * necessary. + */ + Sink appendingSink(File file) throws FileNotFoundException; + + /** + * Deletes {@code file} if it exists. Throws if the file exists and cannot be deleted. + */ + void delete(File file) throws IOException; + + /** + * Returns true if {@code file} exists on the file system. + */ + boolean exists(File file); + + /** + * Returns the number of bytes stored in {@code file}, or 0 if it does not exist. + */ + long size(File file); + + /** + * Renames {@code from} to {@code to}. Throws if the file cannot be renamed. + */ + void rename(File from, File to) throws IOException; + + /** + * Recursively delete the contents of {@code directory}. Throws an IOException if any file could + * not be deleted, or if {@code dir} is not a readable directory. + */ + void deleteContents(File directory) throws IOException; + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/LifeCycle.java b/bus-core/src/main/java/org/aoju/bus/core/io/LifeCycle.java old mode 100755 new mode 100644 index 980899c2a5..6fab06d746 --- a/bus-core/src/main/java/org/aoju/bus/core/io/LifeCycle.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/LifeCycle.java @@ -36,13 +36,23 @@ */ public final class LifeCycle { - static final long MAX_SIZE = Normal._64 * Normal._1024; + /** + * The maximum number of bytes to pool + * 64 KiB + */ + public static final long MAX_SIZE = Normal._64 * Normal._1024; - static Segment next; + /** + * Singly-linked list of segments + */ + public static Segment next; - static long byteCount; + /** + * Total bytes in this pool + */ + public static long byteCount; - private LifeCycle() { + public LifeCycle() { } @@ -56,14 +66,14 @@ public static Segment take() { return result; } } - return new Segment(); + return new Segment(); // Pool is empty. Don't zero-fill while holding a lock. } public static void recycle(Segment segment) { - if (null != segment.next || null != segment.prev) throw new IllegalArgumentException(); - if (segment.shared) return; + if (segment.next != null || segment.prev != null) throw new IllegalArgumentException(); + if (segment.shared) return; // This segment cannot be recycled. synchronized (LifeCycle.class) { - if (byteCount + Segment.SIZE > MAX_SIZE) return; + if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full. byteCount += Segment.SIZE; segment.next = next; segment.pos = segment.limit = 0; diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/Segment.java b/bus-core/src/main/java/org/aoju/bus/core/io/Segment.java old mode 100755 new mode 100644 index 75a9f7fd66..5782f62bf2 --- a/bus-core/src/main/java/org/aoju/bus/core/io/Segment.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/Segment.java @@ -99,15 +99,27 @@ public Segment(byte[] data, int pos, int limit, boolean shared, boolean owner) { this.owner = owner; } + /** + * Returns a new segment that shares the underlying byte array with this. Adjusting pos and limit + * are safe but writes are forbidden. This also marks the current segment as shared, which + * prevents it from being pooled. + */ public final Segment sharedCopy() { shared = true; return new Segment(data, pos, limit, true, false); } + /** + * Returns a new segment that its own private copy of the underlying byte array. + */ public final Segment unsharedCopy() { return new Segment(data.clone(), pos, limit, false, true); } + /** + * Removes this segment of a circularly-linked list and returns its successor. + * Returns null if the list is now empty. + */ public final Segment pop() { Segment result = next != this ? next : null; prev.next = next; @@ -117,7 +129,11 @@ public final Segment pop() { return result; } - public Segment push(Segment segment) { + /** + * Appends {@code segment} after this segment in the circularly-linked list. + * Returns the pushed segment. + */ + public final Segment push(Segment segment) { segment.prev = this; segment.next = next; next.prev = segment; @@ -125,10 +141,23 @@ public Segment push(Segment segment) { return segment; } - public Segment split(int byteCount) { + /** + * Splits this head of a circularly-linked list into two segments. The first + * segment contains the data in {@code [pos..pos+byteCount)}. The second + * segment contains the data in {@code [pos+byteCount..limit)}. This can be + * useful when moving partial segments from one buffer to another. + * + *

              Returns the new head of the circularly-linked list. + */ + public final Segment split(int byteCount) { if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException(); Segment prefix; + // We have two competing performance goals: + // - Avoid copying data. We accomplish this by sharing segments. + // - Avoid short shared segments. These are bad for performance because they are readonly and + // may lead to long chains of short segments. + // To balance these goals we only share segments when the copy will be large. if (byteCount >= SHARE_MINIMUM) { prefix = sharedCopy(); } else { @@ -159,9 +188,13 @@ public void compact() { LifeCycle.recycle(this); } - public void writeTo(Segment sink, int byteCount) { + /** + * Moves {@code byteCount} bytes from this segment to {@code sink}. + */ + public final void writeTo(Segment sink, int byteCount) { if (!sink.owner) throw new IllegalArgumentException(); if (sink.limit + byteCount > SIZE) { + // We can't fit byteCount bytes at the sink's current position. Shift sink first. if (sink.shared) throw new IllegalArgumentException(); if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException(); System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/Buffer.java b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/Buffer.java old mode 100755 new mode 100644 index 542d809ea2..5d5e9e3f4e --- a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/Buffer.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/Buffer.java @@ -337,6 +337,11 @@ private void readFrom(InputStream in, long byteCount, boolean forever) throws IO int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit); int bytesRead = in.read(tail.data, tail.limit, maxToCopy); if (bytesRead == -1) { + if (tail.pos == tail.limit) { + // We allocated a tail segment, but didn't end up needing it. Recycle! + head = tail.pop(); + LifeCycle.recycle(tail); + } if (forever) return; throw new EOFException(); } @@ -346,6 +351,11 @@ private void readFrom(InputStream in, long byteCount, boolean forever) throws IO } } + /** + * Returns the number of bytes in segments that are not writable. This is the + * number of bytes that can be flushed immediately to an underlying sink + * without harming throughput. + */ public final long completeSegmentByteCount() { long result = size; if (result == 0) return 0; @@ -537,6 +547,7 @@ public long readDecimalLong() { if (b >= Symbol.C_ZERO && b <= Symbol.C_NINE) { int digit = Symbol.C_ZERO - b; + // Detect when the digit would cause an overflow. if (value < overflowZone || value == overflowZone && digit < overflowDigit) { Buffer buffer = new Buffer().writeDecimalLong(value).writeByte(b); if (!negative) buffer.readByte(); // Skip negative sign. @@ -552,6 +563,7 @@ public long readDecimalLong() { throw new NumberFormatException( "Expected leading [0-9] or '-' character but was 0x" + Integer.toHexString(b)); } + // Set a flag to stop iteration. We still need to run through segment updating below. done = true; break; } @@ -563,7 +575,7 @@ public long readDecimalLong() { } else { segment.pos = pos; } - } while (!done && null != head); + } while (!done && head != null); size -= seen; return negative ? value : -value; @@ -599,10 +611,12 @@ public long readHexadecimalUnsignedLong() { throw new NumberFormatException( "Expected leading [0-9a-fA-F] character but was 0x" + Integer.toHexString(b)); } + // Set a flag to stop iteration. We still need to run through segment updating below. done = true; break; } + // Detect when the shift will overflow. if ((value & 0xf000000000000000L) != 0) { Buffer buffer = new Buffer().writeHexadecimalUnsignedLong(value).writeByte(b); throw new NumberFormatException("Number too large: " + buffer.readUtf8()); @@ -635,11 +649,12 @@ public ByteString readByteString(long byteCount) throws EOFException { } @Override - public int select(Blending options) { - int index = selectPrefix(options, false); + public int select(Blending blending) { + int index = selectPrefix(blending, false); if (index == -1) return -1; - int selectedSize = options.byteStrings[index].size(); + // If the prefix match actually matched a full byte string, consume it and return it. + int selectedSize = blending.byteStrings[index].size(); try { skip(selectedSize); } catch (EOFException e) { @@ -658,11 +673,11 @@ public int select(Blending options) { * 请注意,由于选项是按优先顺序列出的,而且第一个选项可能是另一个选项的前缀, * 这使得情况变得复杂。例如,如果缓冲区包含[ab]而选项是[abc, a],则返回-2 */ - public int selectPrefix(Blending options, boolean selectTruncated) { + public int selectPrefix(Blending blending, boolean selectTruncated) { Segment head = this.head; - if (null == head) { - if (selectTruncated) return -2; - return options.indexOf(ByteString.EMPTY); + if (head == null) { + if (selectTruncated) return -2; // A result is present but truncated. + return blending.indexOf(ByteString.EMPTY); } Segment s = head; @@ -670,7 +685,7 @@ public int selectPrefix(Blending options, boolean selectTruncated) { int pos = head.pos; int limit = head.limit; - int[] trie = options.trie; + int[] trie = blending.trie; int triePos = 0; int prefixIndex = -1; @@ -686,24 +701,26 @@ public int selectPrefix(Blending options, boolean selectTruncated) { int nextStep; - if (null == s) { + if (s == null) { break; } else if (scanOrSelect < 0) { + // Scan: take multiple bytes from the buffer and the trie, looking for any mismatch. int scanByteCount = -1 * scanOrSelect; int trieLimit = triePos + scanByteCount; while (true) { int b = data[pos++] & 0xff; - if (b != trie[triePos++]) return prefixIndex; + if (b != trie[triePos++]) return prefixIndex; // Fail 'cause we found a mismatch. boolean scanComplete = (triePos == trieLimit); + // Advance to the next buffer segment if this one is exhausted. if (pos == limit) { s = s.next; pos = s.pos; data = s.data; limit = s.limit; if (s == head) { - if (!scanComplete) break navigateTrie; - s = null; + if (!scanComplete) break navigateTrie; // We were exhausted before the scan completed. + s = null; // We were exhausted at the end of the scan. } } @@ -713,11 +730,12 @@ public int selectPrefix(Blending options, boolean selectTruncated) { } } } else { + // Select: take one byte from the buffer and find a match in the trie. int selectChoiceCount = scanOrSelect; int b = data[pos++] & 0xff; int selectLimit = triePos + selectChoiceCount; while (true) { - if (triePos == selectLimit) return prefixIndex; + if (triePos == selectLimit) return prefixIndex; // Fail 'cause we didn't find a match. if (b == trie[triePos]) { nextStep = trie[triePos + selectChoiceCount]; @@ -727,30 +745,31 @@ public int selectPrefix(Blending options, boolean selectTruncated) { triePos++; } + // Advance to the next buffer segment if this one is exhausted. if (pos == limit) { s = s.next; pos = s.pos; data = s.data; limit = s.limit; if (s == head) { - s = null; + s = null; // No more segments! The next trie node will be our last. } } } - if (nextStep >= 0) return nextStep; - triePos = -nextStep; + if (nextStep >= 0) return nextStep; // Found a matching option. + triePos = -nextStep; // Found another node to continue the search. } - - if (selectTruncated) return -2; - return prefixIndex; + // We break out of the loop above when we've exhausted the buffer without exhausting the trie. + if (selectTruncated) return -2; // The buffer is a prefix of at least one option. + return prefixIndex; // Return any matches we encountered while searching for a deeper match. } @Override public void readFully(Buffer sink, long byteCount) throws EOFException { if (size < byteCount) { - sink.write(this, size); + sink.write(this, size); // Exhaust ourselves. throw new EOFException(); } sink.write(this, byteCount); @@ -801,6 +820,7 @@ public String readString(long byteCount, java.nio.charset.Charset charset) throw Segment s = head; if (s.pos + byteCount > s.limit) { + // If the string spans multiple segments, delegate to readBytes(). return new String(readByteArray(byteCount), charset); } @@ -1084,42 +1104,54 @@ public Buffer writeUtf8(String string, int beginIndex, int endIndex) { int segmentOffset = tail.limit - i; int runLimit = Math.min(endIndex, Segment.SIZE - segmentOffset); - data[segmentOffset + i++] = (byte) c; + // Emit a 7-bit character with 1 byte. + data[segmentOffset + i++] = (byte) c; // 0xxxxxxx + // Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance + // improvement over independent calls to writeByte(). while (i < runLimit) { c = string.charAt(i); if (c >= 0x80) break; - data[segmentOffset + i++] = (byte) c; + data[segmentOffset + i++] = (byte) c; // 0xxxxxxx } - int runSize = i + segmentOffset - tail.limit; + int runSize = i + segmentOffset - tail.limit; // Equivalent to i - (previous i). tail.limit += runSize; size += runSize; } else if (c < 0x800) { - writeByte(c >> 6 | 0xc0); - writeByte(c & 0x3f | 0x80); + // Emit a 11-bit character with 2 bytes. + writeByte(c >> 6 | 0xc0); // 110xxxxx + writeByte(c & 0x3f | 0x80); // 10xxxxxx i++; } else if (c < 0xd800 || c > 0xdfff) { - writeByte(c >> 12 | 0xe0); - writeByte(c >> 6 & 0x3f | 0x80); - writeByte(c & 0x3f | 0x80); + // Emit a 16-bit character with 3 bytes. + writeByte(c >> 12 | 0xe0); // 1110xxxx + writeByte(c >> 6 & 0x3f | 0x80); // 10xxxxxx + writeByte(c & 0x3f | 0x80); // 10xxxxxx i++; } else { + // c is a surrogate. Make sure it is a high surrogate & that its successor is a low + // surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement character. int low = i + 1 < endIndex ? string.charAt(i + 1) : 0; if (c > 0xdbff || low < 0xdc00 || low > 0xdfff) { - writeByte(Symbol.C_QUESTION_MARK); + writeByte('?'); i++; continue; } + + // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits) + // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits) + // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits) int codePoint = 0x010000 + ((c & ~0xd800) << 10 | low & ~0xdc00); - writeByte(codePoint >> 18 | 0xf0); - writeByte(codePoint >> 12 & 0x3f | 0x80); - writeByte(codePoint >> 6 & 0x3f | 0x80); - writeByte(codePoint & 0x3f | 0x80); + // Emit a 21-bit character with 4 bytes. + writeByte(codePoint >> 18 | 0xf0); // 11110xxx + writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx + writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxyyyy + writeByte(codePoint & 0x3f | 0x80); // 10yyyyyy i += 2; } } @@ -1130,26 +1162,31 @@ public Buffer writeUtf8(String string, int beginIndex, int endIndex) { @Override public Buffer writeUtf8CodePoint(int codePoint) { if (codePoint < 0x80) { + // Emit a 7-bit code point with 1 byte. writeByte(codePoint); } else if (codePoint < 0x800) { - writeByte(codePoint >> 6 | 0xc0); - writeByte(codePoint & 0x3f | 0x80); + // Emit a 11-bit code point with 2 bytes. + writeByte(codePoint >> 6 | 0xc0); // 110xxxxx + writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx } else if (codePoint < 0x10000) { if (codePoint >= 0xd800 && codePoint <= 0xdfff) { - writeByte(Symbol.C_QUESTION_MARK); + // Emit a replacement character for a partial surrogate. + writeByte('?'); } else { - writeByte(codePoint >> 12 | 0xe0); - writeByte(codePoint >> 6 & 0x3f | 0x80); - writeByte(codePoint & 0x3f | 0x80); + // Emit a 16-bit code point with 3 bytes. + writeByte(codePoint >> 12 | 0xe0); // 1110xxxx + writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxxxxx + writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx } } else if (codePoint <= 0x10ffff) { - writeByte(codePoint >> 18 | 0xf0); - writeByte(codePoint >> 12 & 0x3f | 0x80); - writeByte(codePoint >> 6 & 0x3f | 0x80); - writeByte(codePoint & 0x3f | 0x80); + // Emit a 21-bit code point with 4 bytes. + writeByte(codePoint >> 18 | 0xf0); // 11110xxx + writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx + writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxxxxx + writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx } else { throw new IllegalArgumentException( @@ -1434,23 +1471,28 @@ public void write(Buffer source, long byteCount) { IoKit.checkOffsetAndCount(source.size, 0, byteCount); while (byteCount > 0) { + // Is a prefix of the source's head segment all that we need to move? if (byteCount < (source.head.limit - source.head.pos)) { - Segment tail = null != head ? head.prev : null; - if (null != tail && tail.owner + Segment tail = head != null ? head.prev : null; + if (tail != null && tail.owner && (byteCount + tail.limit - (tail.shared ? 0 : tail.pos) <= Segment.SIZE)) { + // Our existing segments are sufficient. Move bytes from source's head to our tail. source.head.writeTo(tail, (int) byteCount); source.size -= byteCount; size += byteCount; return; } else { + // We're going to need another segment. Split the source's head + // segment in two, then move the first of those two to this buffer. source.head = source.head.split((int) byteCount); } } + // Remove the source's head segment and append it to our tail. Segment segmentToMove = source.head; long movedByteCount = segmentToMove.limit - segmentToMove.pos; source.head = segmentToMove.pop(); - if (null == head) { + if (head == null) { head = segmentToMove; head.next = head.prev = head; } else { @@ -1487,6 +1529,10 @@ public long indexOf(byte b) { return indexOf(b, 0, Long.MAX_VALUE); } + /** + * Returns the index of {@code b} in this at or beyond {@code fromIndex}, or + * -1 if this buffer does not contain {@code b} in that range. + */ @Override public long indexOf(byte b, long fromIndex) { return indexOf(b, fromIndex, Long.MAX_VALUE); @@ -1556,7 +1602,6 @@ public long indexOf(ByteString bytes, long fromIndex) throws IOException { Segment s; long offset; - // TODO(jwilson): extract this to a shared helper method when can do so without allocating. findSegmentAndOffset: { s = head; @@ -1589,6 +1634,7 @@ public long indexOf(ByteString bytes, long fromIndex) throws IOException { } } + // Not in this segment. Try the next one. offset += (s.limit - s.pos); fromIndex = offset; s = s.next; @@ -1630,7 +1676,11 @@ public long indexOfElement(ByteString targetBytes, long fromIndex) { } } + // Special case searching for one of two bytes. This is a common case for tools like Moshi, + // which search for pairs of chars like `\r` and `\n` or {@code `"` and `\`. The impact of this + // optimization is a ~5x speedup for this case without a substantial cost to other cases. if (targetBytes.size() == 2) { + // Scan through the segments, searching for either of the two bytes. byte b0 = targetBytes.getByte(0); byte b1 = targetBytes.getByte(1); while (offset < size) { @@ -1642,11 +1692,13 @@ public long indexOfElement(ByteString targetBytes, long fromIndex) { } } + // Not in this segment. Try the next one. offset += (s.limit - s.pos); fromIndex = offset; s = s.next; } } else { + // Scan through the segments, searching for a byte that's also in the array. byte[] targetByteArray = targetBytes.internalArray(); while (offset < size) { byte[] data = s.data; @@ -1657,6 +1709,7 @@ public long indexOfElement(ByteString targetBytes, long fromIndex) { } } + // Not in this segment. Try the next one. offset += (s.limit - s.pos); fromIndex = offset; s = s.next; @@ -1735,6 +1788,9 @@ public Timeout timeout() { return Timeout.NONE; } + /** + * For testing. This returns the sizes of the segments in this buffer. + */ List segmentSizes() { if (null == head) { return Collections.emptyList(); @@ -1750,28 +1806,28 @@ List segmentSizes() { /** * @return the 128-bit MD5 hash of this buffer. */ - public final ByteString md5() { + public ByteString md5() { return digest(Algorithm.MD5.getValue()); } /** * @return the 160-bit SHA-1 hash of this buffer. */ - public final ByteString sha1() { + public ByteString sha1() { return digest(Algorithm.SHA1.getValue()); } /** * @return the 256-bit SHA-256 hash of this buffer. */ - public final ByteString sha256() { + public ByteString sha256() { return digest(Algorithm.SHA256.getValue()); } /** * @return the 512-bit SHA-512 hash of this buffer. */ - public final ByteString sha512() { + public ByteString sha512() { return digest(Algorithm.SHA512.getValue()); } @@ -1794,7 +1850,7 @@ private ByteString digest(String algorithm) { * @param key ByteString * @return the 160-bit SHA-1 HMAC of this buffer. */ - public final ByteString hmacSha1(ByteString key) { + public ByteString hmacSha1(ByteString key) { return hmac(Algorithm.HMACSHA1.getValue(), key); } @@ -1802,7 +1858,7 @@ public final ByteString hmacSha1(ByteString key) { * @param key ByteString * @return the 256-bit SHA-256 HMAC of this buffer. */ - public final ByteString hmacSha256(ByteString key) { + public ByteString hmacSha256(ByteString key) { return hmac(Algorithm.HMACSHA256.getValue(), key); } @@ -1882,11 +1938,18 @@ public int hashCode() { return result; } + /** + * Returns a human-readable string that describes the contents of this buffer. Typically this + * is a string like {@code [text=Hello]} or {@code [hex=0000ffff]}. + */ @Override public String toString() { return snapshot().toString(); } + /** + * Returns a deep copy of this buffer. + */ @Override public Buffer clone() { Buffer result = new Buffer(); @@ -1901,23 +1964,29 @@ public Buffer clone() { return result; } - public final ByteString snapshot() { + /** + * Returns an immutable copy of this buffer as a byte string. + */ + public ByteString snapshot() { if (size > Integer.MAX_VALUE) { throw new IllegalArgumentException("size > Integer.MAX_VALUE: " + size); } return snapshot((int) size); } - public final ByteString snapshot(int byteCount) { + /** + * Returns an immutable copy of the first {@code byteCount} bytes of this buffer as a byte string. + */ + public ByteString snapshot(int byteCount) { if (byteCount == 0) return ByteString.EMPTY; return new ByteBuffer(this, byteCount); } - public final UnsafeCursor readUnsafe() { + public UnsafeCursor readUnsafe() { return readUnsafe(new UnsafeCursor()); } - public final UnsafeCursor readUnsafe(UnsafeCursor unsafeCursor) { + public UnsafeCursor readUnsafe(UnsafeCursor unsafeCursor) { if (null != unsafeCursor.buffer) { throw new IllegalStateException("already attached to a buffer"); } @@ -1927,11 +1996,11 @@ public final UnsafeCursor readUnsafe(UnsafeCursor unsafeCursor) { return unsafeCursor; } - public final UnsafeCursor readAndWriteUnsafe() { + public UnsafeCursor readAndWriteUnsafe() { return readAndWriteUnsafe(new UnsafeCursor()); } - public final UnsafeCursor readAndWriteUnsafe(UnsafeCursor unsafeCursor) { + public UnsafeCursor readAndWriteUnsafe(UnsafeCursor unsafeCursor) { if (null != unsafeCursor.buffer) { throw new IllegalStateException("already attached to a buffer"); } @@ -1954,13 +2023,23 @@ public static final class UnsafeCursor implements Closeable { public int end = -1; private Segment segment; - public final int next() { + /** + * Seeks to the next range of bytes, advancing the offset by {@code end - start}. Returns the + * size of the readable range (at least 1), or -1 if we have reached the end of the buffer and + * there are no more bytes to read. + */ + public int next() { if (offset == buffer.size) throw new IllegalStateException(); if (offset == -1L) return seek(0L); return seek(offset + (end - start)); } - public final int seek(long offset) { + /** + * Reposition the cursor so that the data at {@code offset} is readable at {@code data[start]}. + * Returns the number of bytes readable in {@code data} (at least 1), or -1 if there are no data + * to read. + */ + public int seek(long offset) { if (offset < -1 || offset > buffer.size) { throw new ArrayIndexOutOfBoundsException( String.format("offset=%s > size=%s", offset, buffer.size)); @@ -2013,7 +2092,7 @@ public final int seek(long offset) { } } - // If we're going to write and our segment is shared, swap it for a read-write first. + // If we're going to write and our segment is shared, swap it for a read-write one. if (readWrite && next.shared) { Segment unsharedNext = next.unsharedCopy(); if (buffer.head == next) { @@ -2023,6 +2102,7 @@ public final int seek(long offset) { next.prev.pop(); } + // Update this cursor to the requested offset within the found segment. this.segment = next; this.offset = offset; this.data = next.data; @@ -2031,8 +2111,25 @@ public final int seek(long offset) { return end - start; } - public final long resizeBuffer(long newSize) { - if (null == buffer) { + /** + * Change the size of the buffer so that it equals {@code newSize} by either adding new + * capacity at the end or truncating the buffer at the end. Newly added capacity may span + * multiple segments. + * + *

              As a side-effect this cursor will {@link #seek seek}. If the buffer is being enlarged it + * will move {@link #offset} to the first byte of newly-added capacity. This is the size of the + * buffer prior to the {@code resizeBuffer()} call. If the buffer is being shrunk it will move + * {@link #offset} to the end of the buffer. + * + *

              Warning: it is the caller’s responsibility to write new data to every byte of the + * newly-allocated capacity. Failure to do so may cause serious security problems as the data + * in the returned buffers is not zero filled. Buffers may contain dirty pooled segments that + * hold very sensitive data from other parts of the current process. + * + * @return the previous size of the buffer. + */ + public long resizeBuffer(long newSize) { + if (buffer == null) { throw new IllegalStateException("not attached to a buffer"); } if (!readWrite) { @@ -2089,6 +2186,29 @@ public final long resizeBuffer(long newSize) { return oldSize; } + /** + * Grow the buffer by adding a contiguous range of capacity in a single + * segment. This adds at least {@code minByteCount} bytes but may add up to a full segment of + * additional capacity. + * + *

              As a side-effect this cursor will {@link #seek seek}. It will move {@link #offset} to the + * first byte of newly-added capacity. This is the size of the buffer prior to the {@code + * expandBuffer()} call. + * + *

              If {@code minByteCount} bytes are available in the buffer's current tail segment that will + * be used; otherwise another segment will be allocated and appended. In either case this + * returns the number of bytes of capacity added to this buffer. + * + *

              Warning: it is the caller’s responsibility to either write new data to every byte of the + * newly-allocated capacity, or to {@link #resizeBuffer shrink} the buffer to the data written. + * Failure to do so may cause serious security problems as the data in the returned buffers is + * not zero filled. Buffers may contain dirty pooled segments that hold very sensitive data from + * other parts of the current process. + * + * @param minByteCount the size of the contiguous capacity. Must be positive and not greater + * than the capacity size of a single segment (8 KiB). + * @return the number of bytes expanded by. Not less than {@code minByteCount}. + */ public final long expandBuffer(int minByteCount) { if (minByteCount <= 0) { throw new IllegalArgumentException("minByteCount <= 0: " + minByteCount); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java old mode 100755 new mode 100644 diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/AssignSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/AssignSink.java old mode 100755 new mode 100644 diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/BufferSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/BufferSink.java old mode 100755 new mode 100644 index 9fd0eb3db5..670a911672 --- a/bus-core/src/main/java/org/aoju/bus/core/io/sink/BufferSink.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/sink/BufferSink.java @@ -43,54 +43,327 @@ */ public interface BufferSink extends Sink, WritableByteChannel { + /** + * Returns this sink's internal buffer. + */ Buffer buffer(); BufferSink write(ByteString byteString) throws IOException; + /** + * Like {@link OutputStream#write(byte[])}, this writes a complete byte array to + * this sink. + */ BufferSink write(byte[] source) throws IOException; + /** + * Like {@link OutputStream#write(byte[], int, int)}, this writes {@code byteCount} + * bytes of {@code source}, starting at {@code offset}. + */ BufferSink write(byte[] source, int offset, int byteCount) throws IOException; + /** + * Removes all bytes from {@code source} and appends them to this sink. Returns the + * number of bytes read which will be 0 if {@code source} is exhausted. + */ long writeAll(Source source) throws IOException; + /** + * Removes {@code byteCount} bytes from {@code source} and appends them to this sink. + */ BufferSink write(Source source, long byteCount) throws IOException; + /** + * Encodes {@code string} in UTF-8 and writes it to this sink.

              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeUtf8("Uh uh uh!");
              +     *   buffer.writeByte(' ');
              +     *   buffer.writeUtf8("You didn't say the magic word!");
              +     *
              +     *   assertEquals("Uh uh uh! You didn't say the magic word!", buffer.readUtf8());
              +     * }
              + */ BufferSink writeUtf8(String string) throws IOException; + /** + * Encodes the characters at {@code beginIndex} up to {@code endIndex} from {@code string} in + * UTF-8 and writes it to this sink.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeUtf8("I'm a hacker!\n", 6, 12);
              +     *   buffer.writeByte(' ');
              +     *   buffer.writeUtf8("That's what I said: you're a nerd.\n", 29, 33);
              +     *   buffer.writeByte(' ');
              +     *   buffer.writeUtf8("I prefer to be called a hacker!\n", 24, 31);
              +     *
              +     *   assertEquals("hacker nerd hacker!", buffer.readUtf8());
              +     * }
              + */ BufferSink writeUtf8(String string, int beginIndex, int endIndex) throws IOException; + /** + * Encodes {@code codePoint} in UTF-8 and writes it to this sink. + */ BufferSink writeUtf8CodePoint(int codePoint) throws IOException; + /** + * Encodes {@code string} in {@code charset} and writes it to this sink. + */ BufferSink writeString(String string, Charset charset) throws IOException; + /** + * Encodes the characters at {@code beginIndex} up to {@code endIndex} from {@code string} in + * {@code charset} and writes it to this sink. + */ BufferSink writeString(String string, int beginIndex, int endIndex, Charset charset) throws IOException; + /** + * Writes a byte to this sink. + */ BufferSink writeByte(int b) throws IOException; + /** + * Writes a big-endian short to this sink using two bytes.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeShort(32767);
              +     *   buffer.writeShort(15);
              +     *
              +     *   assertEquals(4, buffer.size());
              +     *   assertEquals((byte) 0x7f, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x0f, buffer.readByte());
              +     *   assertEquals(0, buffer.size());
              +     * }
              + */ BufferSink writeShort(int s) throws IOException; + /** + * Writes a little-endian short to this sink using two bytes.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeShortLe(32767);
              +     *   buffer.writeShortLe(15);
              +     *
              +     *   assertEquals(4, buffer.size());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0x7f, buffer.readByte());
              +     *   assertEquals((byte) 0x0f, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals(0, buffer.size());
              +     * }
              + */ BufferSink writeShortLe(int s) throws IOException; + /** + * Writes a big-endian int to this sink using four bytes.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeInt(2147483647);
              +     *   buffer.writeInt(15);
              +     *
              +     *   assertEquals(8, buffer.size());
              +     *   assertEquals((byte) 0x7f, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x0f, buffer.readByte());
              +     *   assertEquals(0, buffer.size());
              +     * }
              + */ BufferSink writeInt(int i) throws IOException; + /** + * Writes a little-endian int to this sink using four bytes.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeIntLe(2147483647);
              +     *   buffer.writeIntLe(15);
              +     *
              +     *   assertEquals(8, buffer.size());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0x7f, buffer.readByte());
              +     *   assertEquals((byte) 0x0f, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals(0, buffer.size());
              +     * }
              + */ BufferSink writeIntLe(int i) throws IOException; + /** + * Writes a big-endian long to this sink using eight bytes.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeLong(9223372036854775807L);
              +     *   buffer.writeLong(15);
              +     *
              +     *   assertEquals(16, buffer.size());
              +     *   assertEquals((byte) 0x7f, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x0f, buffer.readByte());
              +     *   assertEquals(0, buffer.size());
              +     * }
              + */ BufferSink writeLong(long v) throws IOException; + /** + * Writes a little-endian long to this sink using eight bytes.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeLongLe(9223372036854775807L);
              +     *   buffer.writeLongLe(15);
              +     *
              +     *   assertEquals(16, buffer.size());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0xff, buffer.readByte());
              +     *   assertEquals((byte) 0x7f, buffer.readByte());
              +     *   assertEquals((byte) 0x0f, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals((byte) 0x00, buffer.readByte());
              +     *   assertEquals(0, buffer.size());
              +     * }
              + */ BufferSink writeLongLe(long v) throws IOException; + /** + * Writes a long to this sink in signed decimal form (i.e., as a string in base 10).
              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeDecimalLong(8675309L);
              +     *   buffer.writeByte(' ');
              +     *   buffer.writeDecimalLong(-123L);
              +     *   buffer.writeByte(' ');
              +     *   buffer.writeDecimalLong(1L);
              +     *
              +     *   assertEquals("8675309 -123 1", buffer.readUtf8());
              +     * }
              + */ BufferSink writeDecimalLong(long v) throws IOException; + /** + * Writes a long to this sink in hexadecimal form (i.e., as a string in base 16).
              {@code
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeHexadecimalUnsignedLong(65535L);
              +     *   buffer.writeByte(' ');
              +     *   buffer.writeHexadecimalUnsignedLong(0xcafebabeL);
              +     *   buffer.writeByte(' ');
              +     *   buffer.writeHexadecimalUnsignedLong(0x10L);
              +     *
              +     *   assertEquals("ffff cafebabe 10", buffer.readUtf8());
              +     * }
              + */ BufferSink writeHexadecimalUnsignedLong(long v) throws IOException; + /** + * Writes all buffered data to the underlying sink, if one exists. Then that sink is recursively + * flushed which pushes data as far as possible towards its ultimate destination. Typically that + * destination is a network socket or file.
              {@code
              +     *
              +     *   BufferedSink b0 = new Buffer();
              +     *   BufferedSink b1 = Okio.buffer(b0);
              +     *   BufferedSink b2 = Okio.buffer(b1);
              +     *
              +     *   b2.writeUtf8("hello");
              +     *   assertEquals(5, b2.buffer().size());
              +     *   assertEquals(0, b1.buffer().size());
              +     *   assertEquals(0, b0.buffer().size());
              +     *
              +     *   b2.flush();
              +     *   assertEquals(0, b2.buffer().size());
              +     *   assertEquals(0, b1.buffer().size());
              +     *   assertEquals(5, b0.buffer().size());
              +     * }
              + */ @Override void flush() throws IOException; + /** + * Writes all buffered data to the underlying sink, if one exists. Like {@link #flush}, but + * weaker. Call this before this buffered sink goes out of scope so that its data can reach its + * destination.
              {@code
              +     *
              +     *   BufferedSink b0 = new Buffer();
              +     *   BufferedSink b1 = Okio.buffer(b0);
              +     *   BufferedSink b2 = Okio.buffer(b1);
              +     *
              +     *   b2.writeUtf8("hello");
              +     *   assertEquals(5, b2.buffer().size());
              +     *   assertEquals(0, b1.buffer().size());
              +     *   assertEquals(0, b0.buffer().size());
              +     *
              +     *   b2.emit();
              +     *   assertEquals(0, b2.buffer().size());
              +     *   assertEquals(5, b1.buffer().size());
              +     *   assertEquals(0, b0.buffer().size());
              +     *
              +     *   b1.emit();
              +     *   assertEquals(0, b2.buffer().size());
              +     *   assertEquals(0, b1.buffer().size());
              +     *   assertEquals(5, b0.buffer().size());
              +     * }
              + */ BufferSink emit() throws IOException; + /** + * Writes complete segments to the underlying sink, if one exists. Like {@link #flush}, but + * weaker. Use this to limit the memory held in the buffer to a single segment. Typically + * application code will not need to call this: it is only necessary when application code writes + * directly to this {@linkplain #buffer() sink's buffer}.
              {@code
              +     *
              +     *   BufferedSink b0 = new Buffer();
              +     *   BufferedSink b1 = Okio.buffer(b0);
              +     *   BufferedSink b2 = Okio.buffer(b1);
              +     *
              +     *   b2.buffer().write(new byte[20_000]);
              +     *   assertEquals(20_000, b2.buffer().size());
              +     *   assertEquals(     0, b1.buffer().size());
              +     *   assertEquals(     0, b0.buffer().size());
              +     *
              +     *   b2.emitCompleteSegments();
              +     *   assertEquals( 3_616, b2.buffer().size());
              +     *   assertEquals(     0, b1.buffer().size());
              +     *   assertEquals(16_384, b0.buffer().size()); // This example assumes 8192 byte segments.
              +     * }
              + */ BufferSink emitCompleteSegments() throws IOException; + /** + * Returns an output stream that writes to this sink. + */ OutputStream outputStream(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/DeflaterSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/DeflaterSink.java index 484b37d6d9..38db6fdd96 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/sink/DeflaterSink.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/sink/DeflaterSink.java @@ -51,13 +51,14 @@ public DeflaterSink(Sink sink, Deflater deflater) { this(IoKit.buffer(sink), deflater); } + /** + * This package-private constructor shares a buffer with its trusted caller. + * In general we can't share a BufferedSource because the deflater holds input + * bytes until they are inflated. + */ DeflaterSink(BufferSink sink, Deflater deflater) { - if (null == sink) { - throw new IllegalArgumentException("source == null"); - } - if (null == deflater) { - throw new IllegalArgumentException("inflater == null"); - } + if (sink == null) throw new IllegalArgumentException("source == null"); + if (deflater == null) throw new IllegalArgumentException("inflater == null"); this.sink = sink; this.deflater = deflater; } @@ -66,12 +67,15 @@ public DeflaterSink(Sink sink, Deflater deflater) { public void write(Buffer source, long byteCount) throws IOException { IoKit.checkOffsetAndCount(source.size, 0, byteCount); while (byteCount > 0) { + // Share bytes from the head segment of 'source' with the deflater. Segment head = source.head; int toDeflate = (int) Math.min(byteCount, head.limit - head.pos); deflater.setInput(head.data, head.pos, toDeflate); + // Deflate those bytes into sink. deflate(false); + // Mark those bytes as read. source.size -= toDeflate; head.pos += toDeflate; if (head.pos == head.limit) { @@ -88,6 +92,10 @@ private void deflate(boolean syncFlush) throws IOException { while (true) { Segment s = buffer.writableSegment(1); + // The 4-parameter overload of deflate() doesn't exist in the RI until + // Java 1.7, and is public (although with @hide) on Android since 2.3. + // The @hide tag means that this code won't compile against the Android + // 2.3 SDK, but it will run fine there. int deflated = syncFlush ? deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH) : deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit); @@ -98,6 +106,7 @@ private void deflate(boolean syncFlush) throws IOException { sink.emitCompleteSegments(); } else if (deflater.needsInput()) { if (s.pos == s.limit) { + // We allocated a tail segment, but didn't end up needing it. Recycle! buffer.head = s.pop(); LifeCycle.recycle(s); } @@ -118,9 +127,11 @@ void finishDeflate() throws IOException { } @Override - public void close() { + public void close() throws IOException { if (closed) return; + // Emit deflated data to the underlying sink. If this fails, we still need + // to close the deflater and the sink; otherwise we risk leaking resources. Throwable thrown = null; try { finishDeflate(); @@ -131,21 +142,17 @@ public void close() { try { deflater.end(); } catch (Throwable e) { - if (null == thrown) { - thrown = e; - } + if (thrown == null) thrown = e; } try { sink.close(); } catch (Throwable e) { - if (null == thrown) { - thrown = e; - } + if (thrown == null) thrown = e; } closed = true; - if (null != thrown) IoKit.sneakyRethrow(thrown); + if (thrown != null) IoKit.sneakyRethrow(thrown); } @Override diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/GzipSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/GzipSink.java index 577374b94c..b332f61340 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/sink/GzipSink.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/sink/GzipSink.java @@ -42,14 +42,26 @@ * @author Kimi Liu * @since Java 17+ */ -public final class GzipSink implements Sink { +public class GzipSink implements Sink { + /** + * Sink into which the GZIP format is written. + */ private final BufferSink sink; + /** + * The deflater used to compress the body. + */ private final Deflater deflater; + /** + * The deflater sink takes care of moving data between decompressed source and + * compressed sink buffers. + */ private final DeflaterSink deflaterSink; - + /** + * Checksum calculated for the compressed body. + */ private final CRC32 crc = new CRC32(); private boolean closed; @@ -84,9 +96,14 @@ public Timeout timeout() { } @Override - public void close() { + public void close() throws IOException { if (closed) return; + // This method delegates to the DeflaterSink for finishing the deflate process + // but keeps responsibility for releasing the deflater's resources. This is + // necessary because writeFooter needs to query the processed byte count which + // only works when the deflater is still open. + Throwable thrown = null; try { deflaterSink.finishDeflate(); @@ -98,42 +115,48 @@ public void close() { try { deflater.end(); } catch (Throwable e) { - if (null == thrown) { - thrown = e; - } + if (thrown == null) thrown = e; } try { sink.close(); } catch (Throwable e) { - if (null == thrown) { - thrown = e; - } + if (thrown == null) thrown = e; } closed = true; - if (null != thrown) IoKit.sneakyRethrow(thrown); + if (thrown != null) { + IoKit.sneakyRethrow(thrown); + } } + /** + * Returns the {@link Deflater}. + * Use it to access stats, dictionary, compression level, etc. + */ public final Deflater deflater() { return deflater; } private void writeHeader() { + // Write the Gzip header directly into the buffer for the sink to avoid handling IOException. Buffer buffer = this.sink.buffer(); - buffer.writeShort(0x1f8b); - buffer.writeByte(0x08); - buffer.writeByte(0x00); - buffer.writeInt(0x00); - buffer.writeByte(0x00); - buffer.writeByte(0x00); + buffer.writeShort(0x1f8b); // Two-byte Gzip ID. + buffer.writeByte(0x08); // 8 == Deflate compression method. + buffer.writeByte(0x00); // No flags. + buffer.writeInt(0x00); // No modification time. + buffer.writeByte(0x00); // No extra flags. + buffer.writeByte(0x00); // No OS. } private void writeFooter() throws IOException { - sink.writeIntLe((int) crc.getValue()); - sink.writeIntLe((int) deflater.getBytesRead()); + sink.writeIntLe((int) crc.getValue()); // CRC of original data. + sink.writeIntLe((int) deflater.getBytesRead()); // Length of original data. } + /** + * Updates the CRC with the given bytes. + */ private void updateCrc(Buffer buffer, long byteCount) { for (Segment head = buffer.head; byteCount > 0; head = head.next) { int segmentLength = (int) Math.min(byteCount, head.limit - head.pos); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/RealSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/RealSink.java old mode 100755 new mode 100644 diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/Sink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/Sink.java old mode 100755 new mode 100644 index f5a3e9a703..160f60b31b --- a/bus-core/src/main/java/org/aoju/bus/core/io/sink/Sink.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/sink/Sink.java @@ -43,14 +43,28 @@ */ public interface Sink extends Closeable, Flushable { - @Override - void flush() throws IOException; + /** + * Removes {@code byteCount} bytes from {@code source} and appends them to this. + */ + void write(Buffer source, long byteCount) throws IOException; + /** + * Pushes all buffered bytes to their final destination. + */ @Override - void close() throws IOException; + void flush() throws IOException; + /** + * Returns the timeout for this sink. + */ Timeout timeout(); - void write(Buffer source, long byteCount) throws IOException; + /** + * Pushes all buffered bytes to their final destination and releases the + * resources held by this sink. It is an error to write a closed sink. It is + * safe to close a sink more than once. + */ + @Override + void close() throws IOException; } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/AssignSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/AssignSource.java old mode 100755 new mode 100644 index 0f7f4fb1b8..467d3332ad --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/AssignSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/AssignSource.java @@ -69,7 +69,7 @@ public void close() throws IOException { @Override public String toString() { - return getClass().getSimpleName() + Symbol.PARENTHESE_LEFT + delegate.toString() + Symbol.PARENTHESE_RIGHT; + return getClass().getSimpleName() + Symbol.PARENTHESE_LEFT + delegate + Symbol.PARENTHESE_RIGHT; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/BufferSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/BufferSource.java old mode 100755 new mode 100644 index 85b734ef95..9e6d01ecaa --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/BufferSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/BufferSource.java @@ -45,47 +45,35 @@ public interface BufferSource extends Source, ReadableByteChannel { /** - * @return this source's internal buffer. - * use getBuffer() instead. - */ - Buffer buffer(); - - /** - * @return This source's internal buffer. + * This source's internal buffer. */ Buffer getBuffer(); /** - * @return true if there are no more bytes in this source. This will block until there are bytes + * Returns true if there are no more bytes in this source. This will block until there are bytes * to read or the source is definitely exhausted. - * @throws IOException {@link java.io.IOException} IOException. */ boolean exhausted() throws IOException; /** - * when the buffer contains at least {@code byteCount} bytes. - * - * @param byteCount long - * @throws IOException {@link java.io.IOException} IOException. + * Returns when the buffer contains at least {@code byteCount} bytes. Throws an + * {@link java.io.EOFException} if the source is exhausted before the required bytes can be read. */ void require(long byteCount) throws IOException; /** - * @param byteCount long - * @return true when the buffer contains at least {@code byteCount} bytes, expanding it as + * Returns true when the buffer contains at least {@code byteCount} bytes, expanding it as * necessary. Returns false if the source is exhausted before the requested bytes can be read. - * @throws IOException {@link java.io.IOException} IOException. */ boolean request(long byteCount) throws IOException; /** - * @return Removes a byte from this source and returns it. - * @throws IOException {@link java.io.IOException} IOException. + * Removes a byte from this source and returns it. */ byte readByte() throws IOException; /** - * @return two bytes from this source and returns a big-endian short.
              {@code
              +     * Removes two bytes from this source and returns a big-endian short. 
              {@code
                    *
                    *   Buffer buffer = new Buffer()
                    *       .writeByte(0x7f)
              @@ -100,7 +88,6 @@ public interface BufferSource extends Source, ReadableByteChannel {
                    *   assertEquals(15, buffer.readShort());
                    *   assertEquals(0, buffer.size());
                    * }
              - * @throws IOException {@link java.io.IOException} IOException. */ short readShort() throws IOException; @@ -120,27 +107,83 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(15, buffer.readShortLe()); * assertEquals(0, buffer.size()); * }
              - * - * @return the short - * @throws IOException {@link java.io.IOException} IOException. */ short readShortLe() throws IOException; /** - * @return the int - * @throws IOException {@link java.io.IOException} IOException. + * Removes four bytes from this source and returns a big-endian int.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer()
              +     *       .writeByte(0x7f)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x0f);
              +     *   assertEquals(8, buffer.size());
              +     *
              +     *   assertEquals(2147483647, buffer.readInt());
              +     *   assertEquals(4, buffer.size());
              +     *
              +     *   assertEquals(15, buffer.readInt());
              +     *   assertEquals(0, buffer.size());
              +     * }
              */ int readInt() throws IOException; /** - * @return the int - * @throws IOException {@link java.io.IOException} IOException. + * Removes four bytes from this source and returns a little-endian int.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer()
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0x7f)
              +     *       .writeByte(0x0f)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00);
              +     *   assertEquals(8, buffer.size());
              +     *
              +     *   assertEquals(2147483647, buffer.readIntLe());
              +     *   assertEquals(4, buffer.size());
              +     *
              +     *   assertEquals(15, buffer.readIntLe());
              +     *   assertEquals(0, buffer.size());
              +     * }
              */ int readIntLe() throws IOException; /** - * @return the long - * @throws IOException {@link java.io.IOException} IOException. + * Removes eight bytes from this source and returns a big-endian long.
              {@code
              +     *
              +     *   Buffer buffer = new Buffer()
              +     *       .writeByte(0x7f)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0xff)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x00)
              +     *       .writeByte(0x0f);
              +     *   assertEquals(16, buffer.size());
              +     *
              +     *   assertEquals(9223372036854775807L, buffer.readLong());
              +     *   assertEquals(8, buffer.size());
              +     *
              +     *   assertEquals(15, buffer.readLong());
              +     *   assertEquals(0, buffer.size());
              +     * }
              */ long readLong() throws IOException; @@ -172,9 +215,6 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(15, buffer.readLongLe()); * assertEquals(0, buffer.size()); * } - * - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ long readLongLe() throws IOException; @@ -192,9 +232,8 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(1L, buffer.readDecimalLong()); * } * - * @return the long - * @throws IOException if the found digits do not fit into a {@code long} or a decimal - * number was not present. + * @throws NumberFormatException if the found digits do not fit into a {@code long} or a decimal + * number was not present. */ long readDecimalLong() throws IOException; @@ -212,36 +251,25 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(0x10L, buffer.readHexadecimalUnsignedLong()); * } * - * @return the long - * @throws IOException if the found hexadecimal does not fit into a {@code long} or - * hexadecimal was not found. + * @throws NumberFormatException if the found hexadecimal does not fit into a {@code long} or + * hexadecimal was not found. */ long readHexadecimalUnsignedLong() throws IOException; /** * Reads and discards {@code byteCount} bytes from this source. Throws an - * {@link java.io.IOException} if the source is exhausted before the + * {@link java.io.EOFException} if the source is exhausted before the * requested bytes can be skipped. - * - * @param byteCount long - * @throws IOException {@link java.io.IOException} IOException. */ void skip(long byteCount) throws IOException; /** * Removes all bytes bytes from this and returns them as a byte string. - * - * @return the ByteString - * @throws IOException {@link java.io.IOException} IOException. */ ByteString readByteString() throws IOException; /** * Removes {@code byteCount} bytes from this and returns them as a byte string. - * - * @param byteCount long - * @return the ByteString - * @throws IOException {@link java.io.IOException} IOException. */ ByteString readByteString(long byteCount) throws IOException; @@ -269,78 +297,46 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(480, buffer.readDecimalLong()); * assertEquals('\n', buffer.readByte()); * } - * - * @param options Options - * @return the int - * @throws IOException {@link java.io.IOException} IOException. */ - int select(Blending options) throws IOException; + int select(Blending blending) throws IOException; /** * Removes all bytes from this and returns them as a byte array. - * - * @return the byte[] - * @throws IOException {@link java.io.IOException} IOException. */ byte[] readByteArray() throws IOException; /** * Removes {@code byteCount} bytes from this and returns them as a byte array. - * - * @param byteCount long - * @return the byte[] - * @throws IOException {@link java.io.IOException} IOException. */ byte[] readByteArray(long byteCount) throws IOException; /** * Removes up to {@code sink.length} bytes from this and copies them into {@code sink}. Returns * the number of bytes read, or -1 if this source is exhausted. - * - * @param sink byte[] - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ int read(byte[] sink) throws IOException; /** * Removes exactly {@code sink.length} bytes from this and copies them into {@code sink}. Throws - * an {@link java.io.IOException} if the requested number of bytes cannot be read. - * - * @param sink byte[] - * @throws IOException {@link java.io.IOException} IOException. + * an {@link java.io.EOFException} if the requested number of bytes cannot be read. */ void readFully(byte[] sink) throws IOException; /** * Removes up to {@code byteCount} bytes from this and copies them into {@code sink} at {@code * offset}. Returns the number of bytes read, or -1 if this source is exhausted. - * - * @param sink byte[] - * @param offset int - * @param byteCount int - * @return the int - * @throws IOException {@link java.io.IOException} IOException. */ int read(byte[] sink, int offset, int byteCount) throws IOException; /** * Removes exactly {@code byteCount} bytes from this and appends them to {@code sink}. Throws an - * {@link java.io.IOException} if the requested number of bytes cannot be read. - * - * @param sink Buffer - * @param byteCount long - * @throws IOException {@link java.io.IOException} IOException. + * {@link java.io.EOFException} if the requested number of bytes cannot be read. */ void readFully(Buffer sink, long byteCount) throws IOException; /** * Removes all bytes from this and appends them to {@code sink}. Returns the total number of bytes * written to {@code sink} which will be 0 if this is exhausted. - * - * @param sink Sink - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ long readAll(Sink sink) throws IOException; @@ -359,9 +355,6 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals("", buffer.readUtf8()); * assertEquals(0, buffer.size()); * } - * - * @return the String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8() throws IOException; @@ -384,10 +377,6 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(" magic word!", buffer.readUtf8(12)); * assertEquals(0, buffer.size()); * } - * - * @param byteCount long - * @return the String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8(long byteCount) throws IOException; @@ -419,9 +408,6 @@ public interface BufferSource extends Source, ReadableByteChannel { * java.io.BufferedReader}. If the source doesn't end with a line break then an implicit line * break is assumed. Null is returned once the source is exhausted. Use this for human-generated * data, where a trailing line break is optional. - * - * @return the String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8Line() throws IOException; @@ -430,12 +416,9 @@ public interface BufferSource extends Source, ReadableByteChannel { * either {@code "\n"} or {@code "\r\n"}; these characters are not included in the result. * *

              On the end of the stream this method throws. Every call must consume either - * '\r\n' or '\n'. If these characters are absent in the stream, an {@link java.io.IOException} + * '\r\n' or '\n'. If these characters are absent in the stream, an {@link java.io.EOFException} * is thrown. Use this for machine-generated data where a missing line break implies truncated * input. - * - * @return the String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8LineStrict() throws IOException; @@ -446,7 +429,7 @@ public interface BufferSource extends Source, ReadableByteChannel { * *

              The returned string will have at most {@code limit} UTF-8 bytes, and the maximum number * of bytes scanned is {@code limit + 2}. If {@code limit == 0} this will always throw - * an {@code IOException} because no bytes will be scanned. + * an {@code EOFException} because no bytes will be scanned. * *

              This method is safe. No bytes are discarded if the match fails, and the caller is free * to try another match:

              {@code
              @@ -460,53 +443,38 @@ public interface BufferSource extends Source, ReadableByteChannel {
                    *   // No bytes have been consumed so the caller can retry.
                    *   assertEquals("12345", buffer.readUtf8LineStrict(5));
                    * }
              - * - * @param limit long - * @return String String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8LineStrict(long limit) throws IOException; /** - * @return Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary. + * Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary. * *

              If this source is exhausted before a complete code point can be read, this throws an {@link - * java.io.IOException} and consumes no input. + * java.io.EOFException} and consumes no input. * *

              If this source doesn't start with a properly-encoded UTF-8 code point, this method will * remove 1 or more non-UTF-8 bytes and return the replacement character ({@code U+FFFD}). This * covers encoding problems (the input is not properly-encoded UTF-8), characters out of range * (beyond the 0x10ffff limit of Unicode), code points for UTF-16 surrogates (U+d800..U+dfff) and * overlong encodings (such as {@code 0xc080} for the NUL character in modified UTF-8). - * @throws IOException {@link java.io.IOException} IOException. */ int readUtf8CodePoint() throws IOException; /** - * @param charset Charset Removes all bytes from this, decodes them as {@code charset}, - * @return the string. - * @throws IOException {@link java.io.IOException} IOException. + * Removes all bytes from this, decodes them as {@code charset}, and returns the string. */ String readString(Charset charset) throws IOException; /** - * Removes {@code byteCount} bytes from this, decodes them as {@code charset}, - * - * @param byteCount byteCount - * @param charset Charset - * @return the string. - * @throws IOException {@link java.io.IOException} IOException. + * Removes {@code byteCount} bytes from this, decodes them as {@code charset}, and returns the + * string. */ String readString(long byteCount, Charset charset) throws IOException; /** * Equivalent to {@link #indexOf(byte, long) indexOf(b, 0)}. - * - * @param bytes byte - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ - long indexOf(byte bytes) throws IOException; + long indexOf(byte b) throws IOException; /** * Returns the index of the first {@code b} in the buffer at or after {@code fromIndex}. This @@ -521,13 +489,8 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(6, buffer.indexOf(m)); * assertEquals(40, buffer.indexOf(m, 12)); * } - * - * @param bytes byte - * @param fromIndex long - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ - long indexOf(byte bytes, long fromIndex) throws IOException; + long indexOf(byte b, long fromIndex) throws IOException; /** * Returns the index of {@code b} if it is found in the range of {@code fromIndex} inclusive @@ -536,21 +499,11 @@ public interface BufferSource extends Source, ReadableByteChannel { * *

              The scan terminates at either {@code toIndex} or the end of the buffer, whichever comes * first. The maximum number of bytes scanned is {@code toIndex-fromIndex}. - * - * @param bytes byte - * @param fromIndex long - * @param toIndex long - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ - long indexOf(byte bytes, long fromIndex, long toIndex) throws IOException; + long indexOf(byte b, long fromIndex, long toIndex) throws IOException; /** * Equivalent to {@link #indexOf(ByteString, long) indexOf(bytes, 0)}. - * - * @param bytes ByteString - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ long indexOf(ByteString bytes) throws IOException; @@ -568,68 +521,57 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(6, buffer.indexOf(MOVE)); * assertEquals(40, buffer.indexOf(MOVE, 12)); * } - * - * @param bytes ByteString - * @param fromIndex long - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ long indexOf(ByteString bytes, long fromIndex) throws IOException; /** - * @param targetBytes ByteString - * Equivalent to {@link #indexOfElement(ByteString, long) indexOfElement(targetBytes, 0)}. - * @return the long - * @throws IOException {@link java.io.IOException} IOException. + * Equivalent to {@link #indexOfElement(ByteString, long) indexOfElement(targetBytes, 0)}. */ long indexOfElement(ByteString targetBytes) throws IOException; /** - * @param fromIndex long - * @param bytes ByteString - * @return the long - * @throws IOException {@link java.io.IOException} IOException. + * Returns the first index in this buffer that is at or after {@code fromIndex} and that contains + * any of the bytes in {@code targetBytes}. This expands the buffer as necessary until a target + * byte is found. This reads an unbounded number of bytes into the buffer. Returns -1 if the + * stream is exhausted before the requested byte is found.

              {@code
              +     *
              +     *   ByteString ANY_VOWEL = ByteString.encodeUtf8("AEOIUaeoiu");
              +     *
              +     *   Buffer buffer = new Buffer();
              +     *   buffer.writeUtf8("Dr. Alan Grant");
              +     *
              +     *   assertEquals(4,  buffer.indexOfElement(ANY_VOWEL));    // 'A' in 'Alan'.
              +     *   assertEquals(11, buffer.indexOfElement(ANY_VOWEL, 9)); // 'a' in 'Grant'.
              +     * }
              */ - long indexOfElement(ByteString bytes, long fromIndex) throws IOException; + long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException; /** - * true if the bytes at {@code offset} in this source equal {@code bytes}. This expands + * Returns true if the bytes at {@code offset} in this source equal {@code bytes}. This expands * the buffer as necessary until a byte does not match, all bytes are matched, or if the stream * is exhausted before enough bytes could determine a match.
              {@code
                    *
              -     *                 ByteString simonSays = ByteString.encodeUtf8("Simon says:");
              +     *   ByteString simonSays = ByteString.encodeUtf8("Simon says:");
                    *
              -     *                 Buffer standOnOneLeg = new Buffer().writeUtf8("Simon says: Stand on first leg.");
              -     *                 assertTrue(standOnOneLeg.rangeEquals(0, simonSays));
              +     *   Buffer standOnOneLeg = new Buffer().writeUtf8("Simon says: Stand on one leg.");
              +     *   assertTrue(standOnOneLeg.rangeEquals(0, simonSays));
                    *
              -     *                 Buffer payMeMoney = new Buffer().writeUtf8("Pay me $1,000,000.");
              -     *                 assertFalse(payMeMoney.rangeEquals(0, simonSays));
              -     *               }
              - * - * @param offset long - * @param bytes ByteString - * @return the long - * @throws IOException {@link java.io.IOException} IOException. + * Buffer payMeMoney = new Buffer().writeUtf8("Pay me $1,000,000."); + * assertFalse(payMeMoney.rangeEquals(0, simonSays)); + * } */ boolean rangeEquals(long offset, ByteString bytes) throws IOException; /** - * if {@code byteCount} bytes at {@code offset} in this source equal {@code bytes} - * * at {@code bytesOffset}. This expands the buffer as necessary until a byte does not match, all - * * bytes are matched, or if the stream is exhausted before enough bytes could determine a match. - * - * @param offset long - * @param bytes ByteString - * @param bytesOffset int - * @param byteCount int - * @return the true - * @throws IOException {@link java.io.IOException} IOException. + * Returns true if {@code byteCount} bytes at {@code offset} in this source equal {@code bytes} + * at {@code bytesOffset}. This expands the buffer as necessary until a byte does not match, all + * bytes are matched, or if the stream is exhausted before enough bytes could determine a match. */ boolean rangeEquals(long offset, ByteString bytes, int bytesOffset, int byteCount) throws IOException; /** - * a new {@code BufferSource} that can read data from this {@code BufferSource} + * Returns a new {@code BufferedSource} that can read data from this {@code BufferedSource} * without consuming it. The returned source becomes invalid once this source is next read or * closed. *

              @@ -642,21 +584,17 @@ boolean rangeEquals(long offset, ByteString bytes, int bytesOffset, int byteCoun * * buffer.readUtf8(3) // returns "abc", buffer contains "defghi" * - * BufferSource peek = buffer.peek(); + * BufferedSource peek = buffer.peek(); * peek.readUtf8(3); // returns "def", buffer contains "defghi" * peek.readUtf8(3); // returns "ghi", buffer contains "defghi" * * buffer.readUtf8(3); // returns "def", buffer contains "ghi" * } - * - * @return the long */ BufferSource peek(); /** - * an input stream that reads from this source. - * - * @return the InputStream + * Returns an input stream that reads from this source. */ InputStream inputStream(); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/GzipSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/GzipSource.java old mode 100755 new mode 100644 index 1e441af801..a554c5c1ee --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/GzipSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/GzipSource.java @@ -53,23 +53,35 @@ public class GzipSource implements Source { private static final byte SECTION_TRAILER = 2; private static final byte SECTION_DONE = 3; + /** + * Our source should yield a GZIP header (which we consume directly), followed + * by deflated bytes (which we consume via an InflaterSource), followed by a + * GZIP trailer (which we also consume directly). + */ private final BufferSource source; - + /** + * The inflater used to decompress the deflated body. + */ private final Inflater inflater; - - private final ExtractSource extractSource; - + /** + * The inflater source takes care of moving data between compressed source and + * decompressed sink buffers. + */ + private final InflaterSource inflaterSource; + /** + * Checksum used to check both the GZIP header and decompressed body. + */ private final CRC32 crc = new CRC32(); - + /** + * The current section. Always progresses forward. + */ private int section = SECTION_HEADER; public GzipSource(Source source) { - if (null == source) { - throw new IllegalArgumentException("source == null"); - } + if (source == null) throw new IllegalArgumentException("source == null"); this.inflater = new Inflater(true); this.source = IoKit.buffer(source); - this.extractSource = new ExtractSource(this.source, inflater); + this.inflaterSource = new InflaterSource(this.source, inflater); } @Override @@ -77,6 +89,7 @@ public long read(Buffer sink, long byteCount) throws IOException { if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (byteCount == 0) return 0; + // If we haven't consumed the header, we must consume it before anything else. if (section == SECTION_HEADER) { consumeHeader(); section = SECTION_BODY; @@ -84,7 +97,7 @@ public long read(Buffer sink, long byteCount) throws IOException { if (section == SECTION_BODY) { long offset = sink.size; - long result = extractSource.read(sink, byteCount); + long result = inflaterSource.read(sink, byteCount); if (result != -1) { updateCrc(sink, offset, result); return result; @@ -92,10 +105,17 @@ public long read(Buffer sink, long byteCount) throws IOException { section = SECTION_TRAILER; } + // The body is exhausted; time to read the trailer. We always consume the + // trailer before returning a -1 exhausted result; that way if you read to + // the end of a GzipSource you guarantee that the CRC has been checked. if (section == SECTION_TRAILER) { consumeTrailer(); section = SECTION_DONE; + // Gzip streams self-terminate: they return -1 before their underlying + // source returns -1. Here we attempt to force the underlying stream to + // return -1 which may trigger it to release its resources. If it doesn't + // return -1, then our Gzip data finished prematurely! if (!source.exhausted()) { throw new IOException("gzip finished without exhausting source"); } @@ -105,37 +125,60 @@ public long read(Buffer sink, long byteCount) throws IOException { } private void consumeHeader() throws IOException { + // Read the 10-byte header. We peek at the flags byte first so we know if we + // need to CRC the entire header. Then we read the magic ID1ID2 sequence. + // We can skip everything else in the first 10 bytes. + // +---+---+---+---+---+---+---+---+---+---+ + // |ID1|ID2|CM |FLG| MTIME |XFL|OS | (more-->) + // +---+---+---+---+---+---+---+---+---+---+ source.require(10); - byte flags = source.buffer().getByte(3); + byte flags = source.getBuffer().getByte(3); boolean fhcrc = ((flags >> FHCRC) & 1) == 1; - if (fhcrc) updateCrc(source.buffer(), 0, 10); + if (fhcrc) updateCrc(source.getBuffer(), 0, 10); short id1id2 = source.readShort(); checkEqual("ID1ID2", (short) 0x1f8b, id1id2); source.skip(8); + // Skip optional extra fields. + // +---+---+=================================+ + // | XLEN |...XLEN bytes of "extra field"...| (more-->) + // +---+---+=================================+ if (((flags >> FEXTRA) & 1) == 1) { source.require(2); - if (fhcrc) updateCrc(source.buffer(), 0, 2); - int xlen = source.buffer().readShortLe(); + if (fhcrc) updateCrc(source.getBuffer(), 0, 2); + int xlen = source.getBuffer().readShortLe(); source.require(xlen); - if (fhcrc) updateCrc(source.buffer(), 0, xlen); + if (fhcrc) updateCrc(source.getBuffer(), 0, xlen); source.skip(xlen); } + // Skip an optional 0-terminated name. + // +=========================================+ + // |...original file name, zero-terminated...| (more-->) + // +=========================================+ if (((flags >> FNAME) & 1) == 1) { long index = source.indexOf((byte) 0); if (index == -1) throw new EOFException(); - if (fhcrc) updateCrc(source.buffer(), 0, index + 1); + if (fhcrc) updateCrc(source.getBuffer(), 0, index + 1); source.skip(index + 1); } + + // Skip an optional 0-terminated comment. + // +===================================+ + // |...file comment, zero-terminated...| (more-->) + // +===================================+ if (((flags >> FCOMMENT) & 1) == 1) { long index = source.indexOf((byte) 0); if (index == -1) throw new EOFException(); - if (fhcrc) updateCrc(source.buffer(), 0, index + 1); + if (fhcrc) updateCrc(source.getBuffer(), 0, index + 1); source.skip(index + 1); } + // Confirm the optional header CRC. + // +---+---+ + // | CRC16 | + // +---+---+ if (fhcrc) { checkEqual("FHCRC", source.readShortLe(), (short) crc.getValue()); crc.reset(); @@ -143,6 +186,10 @@ private void consumeHeader() throws IOException { } private void consumeTrailer() throws IOException { + // Read the eight-byte trailer. Confirm the body's CRC and size. + // +---+---+---+---+---+---+---+---+ + // | CRC32 | ISIZE | + // +---+---+---+---+---+---+---+---+ checkEqual("CRC", source.readIntLe(), (int) crc.getValue()); checkEqual("ISIZE", source.readIntLe(), (int) inflater.getBytesWritten()); } @@ -154,15 +201,20 @@ public Timeout timeout() { @Override public void close() throws IOException { - extractSource.close(); + inflaterSource.close(); } + /** + * Updates the CRC with the given bytes. + */ private void updateCrc(Buffer buffer, long offset, long byteCount) { + // Skip segments that we aren't checksumming. Segment s = buffer.head; for (; offset >= (s.limit - s.pos); s = s.next) { offset -= (s.limit - s.pos); } + // Checksum one segment at a time. for (; byteCount > 0; s = s.next) { int pos = (int) (s.pos + offset); int toUpdate = (int) Math.min(s.limit - pos, byteCount); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/ExtractSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/InflaterSource.java old mode 100755 new mode 100644 similarity index 74% rename from bus-core/src/main/java/org/aoju/bus/core/io/source/ExtractSource.java rename to bus-core/src/main/java/org/aoju/bus/core/io/source/InflaterSource.java index 8e5e7bdeae..e209ac2f68 --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/ExtractSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/InflaterSource.java @@ -29,7 +29,6 @@ import org.aoju.bus.core.io.Segment; import org.aoju.bus.core.io.buffer.Buffer; import org.aoju.bus.core.io.timout.Timeout; -import org.aoju.bus.core.lang.Symbol; import org.aoju.bus.core.toolkit.IoKit; import java.io.EOFException; @@ -38,30 +37,34 @@ import java.util.zip.Inflater; /** - * 解压从另一个源读取的数据 - * - * @author Kimi Liu - * @since Java 17+ + * A source that uses DEFLATE + * to decompress data read from another source. */ -public class ExtractSource implements Source { +public final class InflaterSource implements Source { private final BufferSource source; private final Inflater inflater; + /** + * When we call Inflater.setInput(), the inflater keeps our byte array until + * it needs input again. This tracks how many bytes the inflater is currently + * holding on to. + */ private int bufferBytesHeldByInflater; private boolean closed; - public ExtractSource(Source source, Inflater inflater) { + public InflaterSource(Source source, Inflater inflater) { this(IoKit.buffer(source), inflater); } - ExtractSource(BufferSource source, Inflater inflater) { - if (null == source) { - throw new IllegalArgumentException("source == null"); - } - if (null == inflater) { - throw new IllegalArgumentException("inflater == null"); - } + /** + * This package-private constructor shares a buffer with its trusted caller. + * In general we can't share a BufferedSource because the inflater holds input + * bytes until they are inflated. + */ + InflaterSource(BufferSource source, Inflater inflater) { + if (source == null) throw new IllegalArgumentException("source == null"); + if (inflater == null) throw new IllegalArgumentException("inflater == null"); this.source = source; this.inflater = inflater; } @@ -76,6 +79,7 @@ public long read( while (true) { boolean sourceExhausted = refill(); + // Decompress the inflater's compressed data into the sink. try { Segment tail = sink.writableSegment(1); int toRead = (int) Math.min(byteCount, Segment.SIZE - tail.limit); @@ -88,6 +92,7 @@ public long read( if (inflater.finished() || inflater.needsDictionary()) { releaseInflatedBytes(); if (tail.pos == tail.limit) { + // We allocated a tail segment, but didn't end up needing it. Recycle! sink.head = tail.pop(); LifeCycle.recycle(tail); } @@ -100,22 +105,30 @@ public long read( } } - public final boolean refill() throws IOException { + /** + * Refills the inflater with compressed data if it needs input. (And only if + * it needs input). Returns true if the inflater required input but the source + * was exhausted. + */ + public boolean refill() throws IOException { if (!inflater.needsInput()) return false; releaseInflatedBytes(); - if (inflater.getRemaining() != 0) throw new IllegalStateException(Symbol.QUESTION_MARK); + if (inflater.getRemaining() != 0) throw new IllegalStateException("?"); - if (source.exhausted()) { - return true; - } + // If there are compressed bytes in the source, assign them to the inflater. + if (source.exhausted()) return true; - Segment head = source.buffer().head; + // Assign buffer bytes to the inflater. + Segment head = source.getBuffer().head; bufferBytesHeldByInflater = head.limit - head.pos; inflater.setInput(head.data, head.pos, bufferBytesHeldByInflater); return false; } + /** + * When the inflater has processed compressed data, remove it from the buffer. + */ private void releaseInflatedBytes() throws IOException { if (bufferBytesHeldByInflater == 0) return; int toRelease = bufferBytesHeldByInflater - inflater.getRemaining(); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/PeekSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/PeekSource.java old mode 100755 new mode 100644 index 2ccf2915fe..fa33b58e49 --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/PeekSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/PeekSource.java @@ -54,29 +54,34 @@ public class PeekSource implements Source { public PeekSource(BufferSource upstream) { this.upstream = upstream; - this.buffer = upstream.buffer(); + this.buffer = upstream.getBuffer(); this.expectedSegment = buffer.head; - this.expectedPos = null != expectedSegment ? expectedSegment.pos : -1; + this.expectedPos = expectedSegment != null ? expectedSegment.pos : -1; } @Override public long read(Buffer sink, long byteCount) throws IOException { + if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (closed) throw new IllegalStateException("closed"); - if (null != expectedSegment + // Source becomes invalid if there is an expected Segment and it and the expected position + // do not match the current head and head position of the upstream buffer + if (expectedSegment != null && (expectedSegment != buffer.head || expectedPos != buffer.head.pos)) { throw new IllegalStateException("Peek source is invalid because upstream source was used"); } + if (byteCount == 0L) return 0L; + if (!upstream.request(pos + 1)) return -1L; - upstream.request(pos + byteCount); - if (null == expectedSegment && null != buffer.head) { + if (expectedSegment == null && buffer.head != null) { + // Only once the buffer actually holds data should an expected Segment and position be + // recorded. This allows reads from the peek source to repeatedly return -1 and for data to be + // added later. Unit tests depend on this behavior. expectedSegment = buffer.head; expectedPos = buffer.head.pos; } long toCopy = Math.min(byteCount, buffer.size - pos); - if (toCopy <= 0L) return -1L; - buffer.copyTo(sink, pos, toCopy); pos += toCopy; return toCopy; @@ -88,7 +93,7 @@ public Timeout timeout() { } @Override - public void close() { + public void close() throws IOException { closed = true; } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/RealSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/RealSource.java old mode 100755 new mode 100644 index 89e7677da1..7c32146dac --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/RealSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/RealSource.java @@ -58,11 +58,6 @@ public RealSource(Source source) { this.source = source; } - @Override - public Buffer buffer() { - return buffer; - } - @Override public Buffer getBuffer() { return buffer; @@ -133,18 +128,18 @@ public ByteString readByteString(long byteCount) throws IOException { } @Override - public int select(Blending options) throws IOException { + public int select(Blending blending) throws IOException { if (closed) throw new IllegalStateException("closed"); while (true) { - int index = buffer.selectPrefix(options, true); + int index = buffer.selectPrefix(blending, true); if (index == -1) return -1; if (index == -2) { // We need to grow the buffer. Do that, then try it all again. if (source.read(buffer, Segment.SIZE) == -1L) return -1; } else { // We matched a full byte string: consume it and return it. - int selectedSize = options.byteStrings[index].size(); + int selectedSize = blending.byteStrings[index].size(); buffer.skip(selectedSize); return index; } @@ -478,6 +473,7 @@ public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOExce long lastBufferSize = buffer.size; if (source.read(buffer, Segment.SIZE) == -1) return -1L; + // Keep searching, picking up from where we left off. fromIndex = Math.max(fromIndex, lastBufferSize); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/Source.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/Source.java old mode 100755 new mode 100644 diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/timout/AssignTimeout.java b/bus-core/src/main/java/org/aoju/bus/core/io/timout/AssignTimeout.java old mode 100755 new mode 100644 index 0d00d05229..c5f3e89355 --- a/bus-core/src/main/java/org/aoju/bus/core/io/timout/AssignTimeout.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/timout/AssignTimeout.java @@ -45,6 +45,9 @@ public AssignTimeout(Timeout delegate) { this.delegate = delegate; } + /** + * {@link Timeout} instance to which this instance is currently delegating. + */ public final Timeout delegate() { return delegate; } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/timout/AsyncTimeout.java b/bus-core/src/main/java/org/aoju/bus/core/io/timout/AsyncTimeout.java old mode 100755 new mode 100644 index e6a3b01ff5..01fb6a2823 --- a/bus-core/src/main/java/org/aoju/bus/core/io/timout/AsyncTimeout.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/timout/AsyncTimeout.java @@ -29,7 +29,6 @@ import org.aoju.bus.core.io.buffer.Buffer; import org.aoju.bus.core.io.sink.Sink; import org.aoju.bus.core.io.source.Source; -import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.toolkit.IoKit; import java.io.IOException; @@ -44,47 +43,57 @@ * @since Java 17+ */ public class AsyncTimeout extends Timeout { + /** - * 一次不要写超过64 KiB的数据,否则,慢速连接可能会遭受超时 + * Don't write more than 64 KiB of data at a time, give or take a segment. Otherwise slow + * connections may suffer timeouts even when they're making (slow) progress. Without this, writing + * a single 1 MiB buffer may never succeed on a sufficiently slow connection. */ - private static final int TIMEOUT_WRITE_SIZE = Normal._64 * Normal._1024; + private static final int TIMEOUT_WRITE_SIZE = 64 * 1024; + /** - * 任务线程在关闭之前的空闲时间 + * Duration for the watchdog thread to be idle before it shuts itself down. */ private static final long IDLE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60); private static final long IDLE_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(IDLE_TIMEOUT_MILLIS); + /** - * 线程处理一个挂起超时的链表,按要触发的顺序排序。该类在AsyncTimeout.class上同步。这个锁保护队列 + * The watchdog thread processes a linked list of pending timeouts, sorted in the order to be + * triggered. This class synchronizes on AsyncTimeout.class. This lock guards the queue. + * + *

              Head's 'next' points to the first element of the linked list. The first element is the next + * node to time out, or null if the queue is empty. The head is null until the watchdog thread is + * started and also after being idle for {@link #IDLE_TIMEOUT_MILLIS}. */ static AsyncTimeout head; + /** - * 如果此节点当前在队列中,则为True + * True if this node is currently in the queue. */ private boolean inQueue; + /** - * 链表中的下一个节点 + * The next node in the linked list. */ private AsyncTimeout next; /** - * 这就是任务超时时间 + * If scheduled, this is the time that the watchdog should time this out. */ private long timeoutAt; private static synchronized void scheduleTimeout( - AsyncTimeout node, - long timeoutNanos, - boolean hasDeadline) { - // 启动任务线程,并在安排第一次超时时创建head节点 - if (null == head) { + AsyncTimeout node, long timeoutNanos, boolean hasDeadline) { + // Start the watchdog thread and create the head node when the first timeout is scheduled. + if (head == null) { head = new AsyncTimeout(); new Watchdog().start(); } long now = System.nanoTime(); if (timeoutNanos != 0 && hasDeadline) { - // 计算最早的事件;要么超时,要么截止。因为nanoTime可以封装, - // 所以Math.min()对于绝对值没有定义,但是对于相对值有意义 + // Compute the earliest event; either timeout or deadline. Because nanoTime can wrap around, + // Math.min() is undefined for absolute values, but meaningful for relative ones. node.timeoutAt = now + Math.min(timeoutNanos, node.deadlineNanoTime() - now); } else if (timeoutNanos != 0) { node.timeoutAt = now + timeoutNanos; @@ -93,15 +102,15 @@ private static synchronized void scheduleTimeout( } else { throw new AssertionError(); } - // 按排序顺序插入节点 + + // Insert the node in sorted order. long remainingNanos = node.remainingNanos(now); for (AsyncTimeout prev = head; true; prev = prev.next) { - if (null == prev.next || remainingNanos < prev.next.remainingNanos(now)) { + if (prev.next == null || remainingNanos < prev.next.remainingNanos(now)) { node.next = prev.next; prev.next = node; if (prev == head) { - // 在前面插入时,唤醒任务 - AsyncTimeout.class.notify(); + AsyncTimeout.class.notify(); // Wake up the watchdog when inserting at the front. } break; } @@ -109,81 +118,73 @@ private static synchronized void scheduleTimeout( } /** - * 如果超时发生,则返回true - * - * @param node 节点信息 - * @return the true/false + * Returns true if the timeout occurred. */ private static synchronized boolean cancelScheduledTimeout(AsyncTimeout node) { - // 从链表中删除节点 - for (AsyncTimeout prev = head; null != prev; prev = prev.next) { + // Remove the node from the linked list. + for (AsyncTimeout prev = head; prev != null; prev = prev.next) { if (prev.next == node) { prev.next = node.next; node.next = null; return false; } } - // 在链表中没有找到节点:它一定超时了! + + // The node wasn't found in the linked list: it must have timed out! return true; } /** - * 删除并返回列表顶部的节点,如有必要,等待它超时。 - * 如果在开始时列表的头部没有节点,并且在等待{@code IDLE_TIMEOUT_NANOS}之后仍然没有节点, - * 则返回{@link #head}。如果在等待时插入了新节点,则返回null。否则,它将返回被等待的已被删除的节点 - * - * @return 超时信息 {@link AsyncTimeout} - * @throws InterruptedException 异常 + * Removes and returns the node at the head of the list, waiting for it to time out if necessary. + * This returns {@link #head} if there was no node at the head of the list when starting, and + * there continues to be no node after waiting {@code IDLE_TIMEOUT_NANOS}. It returns null if a + * new node was inserted while waiting. Otherwise this returns the node being waited on that has + * been removed. */ static AsyncTimeout awaitTimeout() throws InterruptedException { - // 获取下一个符合条件的节点 + // Get the next eligible node. AsyncTimeout node = head.next; - // 队列为空。等待,直到某物进入队列或空闲超时过期 - if (null == node) { + + // The queue is empty. Wait until either something is enqueued or the idle timeout elapses. + if (node == null) { long startNanos = System.nanoTime(); AsyncTimeout.class.wait(IDLE_TIMEOUT_MILLIS); - return null == head.next && (System.nanoTime() - startNanos) >= IDLE_TIMEOUT_NANOS - ? head // 空闲超时过期 - : null; // 情况发生了变化 + return head.next == null && (System.nanoTime() - startNanos) >= IDLE_TIMEOUT_NANOS + ? head // The idle timeout elapsed. + : null; // The situation has changed. } long waitNanos = node.remainingNanos(System.nanoTime()); - // 队伍的头还没有超时。等待 + // The head of the queue hasn't timed out yet. Await that. if (waitNanos > 0) { - // 由于我们的工作时间是十亿分之一秒,所以等待变得很复杂,但是API需要两个参数(millis, nanos) + // Waiting is made complicated by the fact that we work in nanoseconds, + // but the API wants (millis, nanos) in two arguments. long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); AsyncTimeout.class.wait(waitMillis, (int) waitNanos); return null; } - // 队列的头已经超时了,删除它 + + // The head of the queue has timed out. Remove it. head.next = node.next; node.next = null; return node; } - /** - * 调用者应该在执行超时工作之前调用{@link #enter},然后调用{@link #exit} - * {@link #exit}的返回值指示是否触发超时。注意,对{@link #timedOut}的调用是异步的, - * 可以在{@link #exit}之后调用 - */ public final void enter() { if (inQueue) throw new IllegalStateException("Unbalanced enter/exit"); long timeoutNanos = timeoutNanos(); boolean hasDeadline = hasDeadline(); if (timeoutNanos == 0 && !hasDeadline) { - // 没有暂停和截止日期?别去排队 - return; + return; // No timeout and no deadline? Don't bother with the queue. } inQueue = true; scheduleTimeout(this, timeoutNanos, hasDeadline); } /** - * 如果超时发生,则返回true - * - * @return the true/false + * Returns true if the timeout occurred. */ public final boolean exit() { if (!inQueue) return false; @@ -192,36 +193,32 @@ public final boolean exit() { } /** - * 返回在超时之前剩余的时间量 - * 如果超时已经过去,并且应该立即发生超时,则该值为负 - * - * @param now 当前时间 - * @return + * Returns the amount of time left until the time out. This will be negative if the timeout has + * elapsed and the timeout should occur immediately. */ private long remainingNanos(long now) { return timeoutAt - now; } /** - * 当对{@link #enter()}和{@link #exit()}的调用之间的时间超过超时时,watchdog线程将调用它 + * Invoked by the watchdog thread when the time between calls to {@link #enter()} and {@link + * #exit()} has exceeded the timeout. */ protected void timedOut() { } /** - * 返回一个委托给{@code sink}的新缓冲接收器,使用它来实现超时。 - * 如果{@link #timedOut}被覆盖以中断{@code sink}的当前操作,那么这是最有效的 - * - * @param sink 缓冲接收器 - * @return 新缓冲接收器 + * Returns a new sink that delegates to {@code sink}, using this to implement timeouts. This works + * best if {@link #timedOut} is overridden to interrupt {@code sink}'s current operation. */ public final Sink sink(final Sink sink) { return new Sink() { @Override public void write(Buffer source, long byteCount) throws IOException { IoKit.checkOffsetAndCount(source.size, 0, byteCount); + while (byteCount > 0L) { - // 计算要写入的字节数。这个循环保证我们在一个段边界上分割 + // Count how many bytes to write. This loop guarantees we split on a segment boundary. long toWrite = 0L; for (Segment s = source.head; toWrite < TIMEOUT_WRITE_SIZE; s = s.next) { int segmentSize = s.limit - s.pos; @@ -231,7 +228,8 @@ public void write(Buffer source, long byteCount) throws IOException { break; } } - // 发出一个写。只有这个部分会超时 + + // Emit one write. Only this section is subject to the timeout. boolean throwOnTimeout = false; enter(); try { @@ -281,17 +279,14 @@ public Timeout timeout() { @Override public String toString() { - return "Awaits.sink(" + sink + ")"; + return "AsyncTimeout.sink(" + sink + ")"; } }; } /** - * 返回一个委托给{@code source}的新源,使用它来实现超时。 - * 如果{@link #timedOut}被覆盖以中断{@code sink}的当前操作,那么这是最有效的 - * - * @param source 源 - * @return 新源 + * Returns a new source that delegates to {@code source}, using this to implement timeouts. This + * works best if {@link #timedOut} is overridden to interrupt {@code sink}'s current operation. */ public final Source source(final Source source) { return new Source() { @@ -331,17 +326,14 @@ public Timeout timeout() { @Override public String toString() { - return "Awaits.source(" + source + ")"; + return "AsyncTimeout.source(" + source + ")"; } }; } /** - * 如果{@code throwOnTimeout}为{@code true}且发生超时,则抛出IOException。 - * 有关抛出的异常类型,请参见{@link #newTimeoutException(java.io.IOException)} - * - * @param throwOnTimeout 超时时间 - * @throws IOException 异常 + * Throws an IOException if {@code throwOnTimeout} is {@code true} and a timeout occurred. See + * {@link #newTimeoutException(java.io.IOException)} for the type of exception thrown. */ final void exit(boolean throwOnTimeout) throws IOException { boolean timedOut = exit(); @@ -349,27 +341,23 @@ final void exit(boolean throwOnTimeout) throws IOException { } /** - * 如果超时,则返回{@code cause}或{@code cause}引起的IOException。 - * 有关返回的异常类型,请参见{@link #newTimeoutException(java.io.IOException)} - * - * @param cause 异常 - * @return 异常信息 + * Returns either {@code cause} or an IOException that's caused by {@code cause} if a timeout + * occurred. See {@link #newTimeoutException(java.io.IOException)} for the type of exception + * returned. */ - final IOException exit(IOException cause) { + final IOException exit(IOException cause) throws IOException { if (!exit()) return cause; return newTimeoutException(cause); } /** - * 返回{@link IOException}表示超时。默认情况下,该方法返回{@link java.io.InterruptedIOException}。 - * 如果{@code cause}非空,则将其设置为返回异常的原因 - * - * @param cause 异常 - * @return 异常信息 + * Returns an {@link IOException} to represent a timeout. By default this method returns {@link + * java.io.InterruptedIOException}. If {@code cause} is non-null it is set as the cause of the + * returned exception. */ protected IOException newTimeoutException(IOException cause) { InterruptedIOException e = new InterruptedIOException("timeout"); - if (null != cause) { + if (cause != null) { e.initCause(cause); } return e; @@ -377,7 +365,7 @@ protected IOException newTimeoutException(IOException cause) { private static final class Watchdog extends Thread { Watchdog() { - super("IoKit.Watchdog"); + super("Okio Watchdog"); setDaemon(true); } @@ -388,16 +376,18 @@ public void run() { synchronized (AsyncTimeout.class) { timedOut = awaitTimeout(); - if (null == timedOut) { - continue; - } + // Didn't find a node to interrupt. Try again. + if (timedOut == null) continue; + // The queue is completely empty. Let this thread exit and let another watchdog thread + // get created on the next call to scheduleTimeout(). if (timedOut == head) { head = null; return; } } + // Close the timed out node. timedOut.timedOut(); } catch (InterruptedException ignored) { } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/timout/Timeout.java b/bus-core/src/main/java/org/aoju/bus/core/io/timout/Timeout.java old mode 100755 new mode 100644 index 7a55adaaf5..757e1e5272 --- a/bus-core/src/main/java/org/aoju/bus/core/io/timout/Timeout.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/timout/Timeout.java @@ -73,46 +73,46 @@ public void throwIfReached() { public Timeout() { } + static long minTimeout(long aNanos, long bNanos) { + if (aNanos == 0L) return bNanos; + if (bNanos == 0L) return aNanos; + if (aNanos < bNanos) return aNanos; + return bNanos; + } + /** - * @param timeout long - * @param unit TimeUnit - * @return timeout - *

              * Wait at most {@code timeout} time before aborting an operation. Using a * per-operation timeout means that as long as forward progress is being made, * no sequence of operations will fail. + * *

              If {@code timeout == 0}, operations will run indefinitely. (Operating * system timeouts may still apply.) */ public Timeout timeout(long timeout, TimeUnit unit) { - if (timeout < 0) { - throw new IllegalArgumentException("timeout < 0: " + timeout); - } - if (null == unit) { - throw new IllegalArgumentException("unit == null"); - } + if (timeout < 0) throw new IllegalArgumentException("timeout < 0: " + timeout); + if (unit == null) throw new IllegalArgumentException("unit == null"); this.timeoutNanos = unit.toNanos(timeout); return this; } /** - * @return the timeout in nanoseconds, or {@code 0} for no timeout. + * Returns the timeout in nanoseconds, or {@code 0} for no timeout. */ public long timeoutNanos() { return timeoutNanos; } /** - * @return hasDeadline true if a deadline is enabled. + * Returns true if a deadline is enabled. */ public boolean hasDeadline() { return hasDeadline; } /** - * @return deadlineNanoTime * Returns the {@linkplain System#nanoTime() nano time} when the deadline will * be reached. + * * @throws IllegalStateException if no deadline is set. */ public long deadlineNanoTime() { @@ -121,8 +121,6 @@ public long deadlineNanoTime() { } /** - * @param deadlineNanoTime long - * @return timeout * Sets the {@linkplain System#nanoTime() nano time} when the deadline will be * reached. All operations must complete before this time. Use a deadline to * set a maximum bound on the time spent on a sequence of operations. @@ -134,23 +132,16 @@ public Timeout deadlineNanoTime(long deadlineNanoTime) { } /** - * @param duration long - * @param unit TimeUnit - * @return timeout * Set a deadline of now plus {@code duration} time. */ public final Timeout deadline(long duration, TimeUnit unit) { - if (duration <= 0) { - throw new IllegalArgumentException("duration <= 0: " + duration); - } - if (null == unit) { - throw new IllegalArgumentException("unit == null"); - } + if (duration <= 0) throw new IllegalArgumentException("duration <= 0: " + duration); + if (unit == null) throw new IllegalArgumentException("unit == null"); return deadlineNanoTime(System.nanoTime() + unit.toNanos(duration)); } /** - * @return this Clears the timeout. Operating system timeouts may still apply. + * Clears the timeout. Operating system timeouts may still apply. */ public Timeout clearTimeout() { this.timeoutNanos = 0; @@ -158,7 +149,7 @@ public Timeout clearTimeout() { } /** - * @return this Clears the deadline. + * Clears the deadline. */ public Timeout clearDeadline() { this.hasDeadline = false; @@ -169,12 +160,10 @@ public Timeout clearDeadline() { * Throws an {@link InterruptedIOException} if the deadline has been reached or if the current * thread has been interrupted. This method doesn't detect timeouts; that should be implemented to * asynchronously abort an in-progress operation. - * - * @throws IOException 抛出异常 */ public void throwIfReached() throws IOException { if (Thread.interrupted()) { - Thread.currentThread().interrupt(); + Thread.currentThread().interrupt(); // Retain interrupted status. throw new InterruptedIOException("interrupted"); } @@ -184,10 +173,40 @@ public void throwIfReached() throws IOException { } /** - * @param monitor Waits on {@code monitor} until it is notified. Throws {@link InterruptedIOException} if either - * the thread is interrupted or if this timeout elapses before {@code monitor} is notified. The - * caller must be synchronized on {@code monitor}. - * @throws InterruptedIOException 抛出异常 + * Waits on {@code monitor} until it is notified. Throws {@link InterruptedIOException} if either + * the thread is interrupted or if this timeout elapses before {@code monitor} is notified. The + * caller must be synchronized on {@code monitor}. + * + *

              Here's a sample class that uses {@code waitUntilNotified()} to await a specific state. Note + * that the call is made within a loop to avoid unnecessary waiting and to mitigate spurious + * notifications.

              {@code
              +     *
              +     *   class Dice {
              +     *     Random random = new Random();
              +     *     int latestTotal;
              +     *
              +     *     public synchronized void roll() {
              +     *       latestTotal = 2 + random.nextInt(6) + random.nextInt(6);
              +     *       System.out.println("Rolled " + latestTotal);
              +     *       notifyAll();
              +     *     }
              +     *
              +     *     public void rollAtFixedRate(int period, TimeUnit timeUnit) {
              +     *       Executors.newScheduledThreadPool(0).scheduleAtFixedRate(new Runnable() {
              +     *         public void run() {
              +     *           roll();
              +     *          }
              +     *       }, 0, period, timeUnit);
              +     *     }
              +     *
              +     *     public synchronized void awaitTotal(Timeout timeout, int total)
              +     *         throws InterruptedIOException {
              +     *       while (latestTotal != total) {
              +     *         timeout.waitUntilNotified(this);
              +     *       }
              +     *     }
              +     *   }
              +     * }
              */ public final void waitUntilNotified(Object monitor) throws InterruptedIOException { try { @@ -195,10 +214,11 @@ public final void waitUntilNotified(Object monitor) throws InterruptedIOExceptio long timeoutNanos = timeoutNanos(); if (!hasDeadline && timeoutNanos == 0L) { - monitor.wait(); + monitor.wait(); // There is no timeout: wait forever. return; } + // Compute how long we'll wait. long waitNanos; long start = System.nanoTime(); if (hasDeadline && timeoutNanos != 0) { @@ -210,6 +230,7 @@ public final void waitUntilNotified(Object monitor) throws InterruptedIOExceptio waitNanos = timeoutNanos; } + // Attempt to wait that long. This will break out early if the monitor is notified. long elapsedNanos = 0L; if (waitNanos > 0L) { long waitMillis = waitNanos / 1000000L; @@ -217,6 +238,7 @@ public final void waitUntilNotified(Object monitor) throws InterruptedIOExceptio elapsedNanos = System.nanoTime() - start; } + // Throw if the timeout elapsed before the monitor was notified. if (elapsedNanos >= waitNanos) { throw new InterruptedIOException("timeout"); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java index fb24f109fa..c221c508b4 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java @@ -119,24 +119,22 @@ public static SSLContext createSSLContext(String protocol, KeyManager[] keyManag public static SSLSocketFactory newSslSocketFactory(X509TrustManager x509TrustManager) { try { SSLContext sslContext = getSSLContext(); - sslContext.init(null, new TrustManager[]{x509TrustManager}, new SecureRandom()); + sslContext.init(null, new TrustManager[]{x509TrustManager}, null); return sslContext.getSocketFactory(); - } catch (GeneralSecurityException ignored) { - throw new AssertionError("No System TLS", ignored); + } catch (GeneralSecurityException e) { + throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up. } } public static SSLContext getSSLContext() { try { - return SSLContext.getInstance(Http.TLS_V_12); - } catch (NoSuchAlgorithmException e) { - // fallback to TLS - } - - try { - return SSLContext.getInstance(Http.TLS); + return SSLContext.getInstance(Http.TLS_V_13); } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("No TLS provider", e); + try { + return SSLContext.getInstance(Http.TLS); + } catch (NoSuchAlgorithmException e2) { + throw new IllegalStateException("No TLS provider", e); + } } } @@ -150,9 +148,9 @@ public static X509TrustManager newTrustManager() { throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); } - return (javax.net.ssl.X509TrustManager) trustManagers[0]; + return (X509TrustManager) trustManagers[0]; } catch (GeneralSecurityException e) { - throw new AssertionError("No System TLS", e); + throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up. } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java index 9576dfe4ee..a6338bf7b3 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java @@ -46,6 +46,7 @@ import org.aoju.bus.core.io.timout.Timeout; import org.aoju.bus.core.lang.Assert; import org.aoju.bus.core.lang.Charset; +import org.aoju.bus.core.lang.Console; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.function.XConsumer; @@ -1390,7 +1391,7 @@ private static Sink sink(final OutputStream out, final Timeout timeout) { return new Sink() { @Override public void write(Buffer source, long byteCount) throws IOException { - IoKit.checkOffsetAndCount(source.size, 0, byteCount); + checkOffsetAndCount(source.size, 0, byteCount); while (byteCount > 0) { timeout.throwIfReached(); Segment head = source.head; @@ -1486,7 +1487,14 @@ public long read(Buffer sink, long byteCount) throws IOException { Segment tail = sink.writableSegment(1); int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit); int bytesRead = in.read(tail.data, tail.limit, maxToCopy); - if (bytesRead == -1) return -1; + if (bytesRead == -1) { + if (tail.pos == tail.limit) { + // We allocated a tail segment, but didn't end up needing it. Recycle! + sink.head = tail.pop(); + LifeCycle.recycle(tail); + } + return -1; + } tail.limit += bytesRead; sink.size += bytesRead; return bytesRead; @@ -1648,10 +1656,10 @@ protected void timedOut() { try { socket.close(); } catch (Exception e) { - throw new InternalException(e); + Console.log("Failed to close timed out socket " + socket, e); } catch (AssertionError e) { if (isAndroidGetsocknameError(e)) { - throw new InternalException(e); + Console.log("Failed to close timed out socket " + socket, e); } else { throw e; } @@ -1660,6 +1668,10 @@ protected void timedOut() { }; } + /** + * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2. + * https://code.google.com/p/android/issues/detail?id=54072 + */ static boolean isAndroidGetsocknameError(AssertionError e) { return null != e.getCause() && null != e.getMessage() && e.getMessage().contains("getsockname failed"); diff --git a/bus-http/src/main/java/org/aoju/bus/http/Cookie.java b/bus-http/src/main/java/org/aoju/bus/http/Cookie.java index 5f75e7b05a..4e42883bb1 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/Cookie.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Cookie.java @@ -27,7 +27,6 @@ import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.http.metric.suffix.SuffixDatabase; import java.util.*; import java.util.regex.Matcher; @@ -240,8 +239,7 @@ static Cookie parse(long currentTimeMillis, UnoUrl url, String setCookie) { } // 如果域名是url主机的后缀,则它不能是公共后缀 - if (urlHost.length() != domain.length() - && null == SuffixDatabase.get().getEffectiveTldPlusOne(domain)) { + if (urlHost.length() != domain.length()) { return null; } @@ -521,6 +519,7 @@ public int hashCode() { * 和{@linkplain #domain() domain}. */ public static class Builder { + String name; String value; long expiresAt = org.aoju.bus.http.Builder.MAX_DATE; diff --git a/bus-http/src/main/java/org/aoju/bus/http/Httpd.java b/bus-http/src/main/java/org/aoju/bus/http/Httpd.java index 3f0e568a98..9b6138bc24 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/Httpd.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Httpd.java @@ -27,8 +27,8 @@ import org.aoju.bus.core.io.sink.Sink; import org.aoju.bus.core.io.source.Source; -import org.aoju.bus.core.net.ssl.HostnameVerifier; -import org.aoju.bus.core.net.ssl.SSLContextBuilder; +import org.aoju.bus.core.net.tls.HostnameVerifier; +import org.aoju.bus.core.net.tls.SSLContextBuilder; import org.aoju.bus.http.accord.ConnectionPool; import org.aoju.bus.http.accord.ConnectionSuite; import org.aoju.bus.http.accord.Exchange; @@ -379,6 +379,7 @@ public Builder newBuilder() { } public static class Builder { + final List interceptors = new ArrayList<>(); final List networkInterceptors = new ArrayList<>(); Dispatcher dispatcher; diff --git a/bus-http/src/main/java/org/aoju/bus/http/Httpx.java b/bus-http/src/main/java/org/aoju/bus/http/Httpx.java index b6de2698ae..84a9736cf1 100755 --- a/bus-http/src/main/java/org/aoju/bus/http/Httpx.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Httpx.java @@ -27,7 +27,7 @@ import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.lang.*; -import org.aoju.bus.core.net.ssl.SSLContextBuilder; +import org.aoju.bus.core.net.tls.SSLContextBuilder; import org.aoju.bus.core.toolkit.ArrayKit; import org.aoju.bus.core.toolkit.MapKit; import org.aoju.bus.core.toolkit.ObjectKit; diff --git a/bus-http/src/main/java/org/aoju/bus/http/Httpz.java b/bus-http/src/main/java/org/aoju/bus/http/Httpz.java index ebeb4e8e9e..7628095527 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/Httpz.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Httpz.java @@ -25,7 +25,7 @@ ********************************************************************************/ package org.aoju.bus.http; -import org.aoju.bus.core.net.ssl.SSLContextBuilder; +import org.aoju.bus.core.net.tls.SSLContextBuilder; import org.aoju.bus.http.plugin.httpz.GetBuilder; import org.aoju.bus.http.plugin.httpz.HttpBuilder; import org.aoju.bus.http.plugin.httpz.PostBuilder; diff --git a/bus-http/src/main/java/org/aoju/bus/http/RealCall.java b/bus-http/src/main/java/org/aoju/bus/http/RealCall.java index 51c5a7b454..465a76d735 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/RealCall.java +++ b/bus-http/src/main/java/org/aoju/bus/http/RealCall.java @@ -240,7 +240,7 @@ public void executeOn(ExecutorService executorService) { } finally { if (!success) { // 这个回调不再运行 - client.dispatcher().finished(this.get()); + client.dispatcher().finished(RealCall.this); } } } @@ -269,7 +269,7 @@ protected void execute() { } throw t; } finally { - client.dispatcher().finished(this); + client.dispatcher().finished(RealCall.this); } } } diff --git a/bus-http/src/main/java/org/aoju/bus/http/UnoUrl.java b/bus-http/src/main/java/org/aoju/bus/http/UnoUrl.java index 6ca48ac452..833a230981 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/UnoUrl.java +++ b/bus-http/src/main/java/org/aoju/bus/http/UnoUrl.java @@ -30,7 +30,6 @@ import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.http.metric.suffix.SuffixDatabase; import java.net.MalformedURLException; import java.net.URI; @@ -840,24 +839,6 @@ public String toString() { return url; } - /** - * 通常,这个方法不应该用来测试一个域是否有效或可路由。相反,DNS是推荐的信息来源 - * - *
                - *
              • {@code http://google.com}{@code "google.com"}
              • - *
              • {@code http://adwords.google.co.uk}{@code "google.co.uk"}
              • - *
              • {@code http://co.uk}null
              • - *
              • {@code http://localhost}null
              • - *
              • {@code http://127.0.0.1}null
              • - *
              - * - * @return the string - */ - public String topPrivateDomain() { - if (org.aoju.bus.http.Builder.verifyAsIpAddress(host)) return null; - return SuffixDatabase.get().getEffectiveTldPlusOne(host); - } - private List percentDecode(List list, boolean plusIsSpace) { int size = list.size(); List result = new ArrayList<>(size); @@ -869,6 +850,7 @@ private List percentDecode(List list, boolean plusIsSpace) { } public static class Builder { + static final String INVALID_HOST = "Invalid URL host"; final List encodedPathSegments = new ArrayList<>(); String scheme; diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/Exchange.java b/bus-http/src/main/java/org/aoju/bus/http/accord/Exchange.java index 84307c9d0d..3c11194926 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/Exchange.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/Exchange.java @@ -1,28 +1,18 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ +/* + * Copyright (C) 2019 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.aoju.bus.http.accord; import org.aoju.bus.core.io.buffer.Buffer; @@ -47,35 +37,32 @@ import java.net.SocketException; /** - * 传输单个 HTTP 请求和响应对。这在处理实际 I/O 的 {@link HttpCodec} 上分层连接管理和事件 - * - * @author Kimi Liu - * @since Java 17+ + * Transmits a single HTTP request and a response pair. This layers connection management and events + * on {@link HttpCodec}, which handles the actual I/O. */ -public class Exchange { - +public final class Exchange { final Transmitter transmitter; final NewCall call; final EventListener eventListener; final ExchangeFinder finder; - final HttpCodec httpCodec; + final HttpCodec codec; private boolean duplex; public Exchange(Transmitter transmitter, NewCall call, EventListener eventListener, - ExchangeFinder finder, HttpCodec httpCodec) { + ExchangeFinder finder, HttpCodec codec) { this.transmitter = transmitter; this.call = call; this.eventListener = eventListener; this.finder = finder; - this.httpCodec = httpCodec; + this.codec = codec; } public RealConnection connection() { - return httpCodec.connection(); + return codec.connection(); } /** - * 如果请求正文不需要在响应正文开始之前完成,则返回 true + * Returns true if the request body need not complete before the response body starts. */ public boolean isDuplex() { return duplex; @@ -84,7 +71,7 @@ public boolean isDuplex() { public void writeRequestHeaders(Request request) throws IOException { try { eventListener.requestHeadersStart(call); - httpCodec.writeRequestHeaders(request); + codec.writeRequestHeaders(request); eventListener.requestHeadersEnd(call, request); } catch (IOException e) { eventListener.requestFailed(call, e); @@ -95,15 +82,15 @@ public void writeRequestHeaders(Request request) throws IOException { public Sink createRequestBody(Request request, boolean duplex) throws IOException { this.duplex = duplex; - long length = request.body().length(); + long contentLength = request.body().length(); eventListener.requestBodyStart(call); - Sink rawRequestBody = httpCodec.createRequestBody(request, length); - return new RequestBodySink(rawRequestBody, length); + Sink rawRequestBody = codec.createRequestBody(request, contentLength); + return new RequestBodySink(rawRequestBody, contentLength); } public void flushRequest() throws IOException { try { - httpCodec.flushRequest(); + codec.flushRequest(); } catch (IOException e) { eventListener.requestFailed(call, e); trackFailure(e); @@ -113,7 +100,7 @@ public void flushRequest() throws IOException { public void finishRequest() throws IOException { try { - httpCodec.finishRequest(); + codec.finishRequest(); } catch (IOException e) { eventListener.requestFailed(call, e); trackFailure(e); @@ -127,7 +114,7 @@ public void responseHeadersStart() { public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException { try { - Response.Builder result = httpCodec.readResponseHeaders(expectContinue); + Response.Builder result = codec.readResponseHeaders(expectContinue); if (result != null) { Internal.instance.initExchange(result, this); } @@ -146,11 +133,11 @@ public void responseHeadersEnd(Response response) { public ResponseBody openResponseBody(Response response) throws IOException { try { eventListener.responseBodyStart(call); - String mediaType = response.header("Content-Type"); - long length = httpCodec.reportedContentLength(response); - Source rawSource = httpCodec.openResponseBodySource(response); - ResponseBodySource source = new ResponseBodySource(rawSource, length); - return new RealResponseBody(mediaType, length, IoKit.buffer(source)); + String contentType = response.header("Content-Type"); + long contentLength = codec.reportedContentLength(response); + Source rawSource = codec.openResponseBodySource(response); + ResponseBodySource source = new ResponseBodySource(rawSource, contentLength); + return new RealResponseBody(contentType, contentLength, IoKit.buffer(source)); } catch (IOException e) { eventListener.responseFailed(call, e); trackFailure(e); @@ -159,7 +146,7 @@ public ResponseBody openResponseBody(Response response) throws IOException { } public Headers trailers() throws IOException { - return httpCodec.trailers(); + return codec.trailers(); } public void timeoutEarlyExit() { @@ -168,7 +155,7 @@ public void timeoutEarlyExit() { public RealWebSocket.Streams newWebSocketStreams() throws SocketException { transmitter.timeoutEarlyExit(); - return httpCodec.connection().newWebSocketStreams(this); + return codec.connection().newWebSocketStreams(this); } public void webSocketUpgradeFailed() { @@ -176,24 +163,25 @@ public void webSocketUpgradeFailed() { } public void noNewExchangesOnConnection() { - httpCodec.connection().noNewExchanges(); + codec.connection().noNewExchanges(); } public void cancel() { - httpCodec.cancel(); + codec.cancel(); } /** - * 撤销此交换对流的访问。当需要后续请求但之前的交换尚未完成时,这是必要的 + * Revoke this exchange's access to streams. This is necessary when a follow-up request is + * required but the preceding exchange hasn't completed yet. */ public void detachWithViolence() { - httpCodec.cancel(); + codec.cancel(); transmitter.exchangeMessageDone(this, true, true, null); } void trackFailure(IOException e) { finder.trackFailure(); - httpCodec.connection().trackFailure(e); + codec.connection().trackFailure(e); } IOException bodyComplete( @@ -223,12 +211,12 @@ public void noRequestBody() { } /** - * 完成时触发事件的请求正文 + * A request body that fires events when it completes. */ - private class RequestBodySink extends AssignSink { + private final class RequestBodySink extends AssignSink { private boolean completed; /** - * 要写入的确切字节数,如果未知,则为 -1L + * The exact number of bytes to be written, or -1L if that is unknown. */ private long contentLength; private long bytesReceived; @@ -286,9 +274,9 @@ private IOException complete(IOException e) { } /** - * 完成时触发事件的响应主体 + * A response body that fires events when it completes. */ - class ResponseBodySource extends AssignSource { + final class ResponseBodySource extends AssignSource { private final long contentLength; private long bytesReceived; private boolean completed; @@ -348,5 +336,4 @@ IOException complete(IOException e) { return bodyComplete(bytesReceived, true, false, e); } } - } diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/ExchangeFinder.java b/bus-http/src/main/java/org/aoju/bus/http/accord/ExchangeFinder.java index f9b3d05bfe..00af6c2f26 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/ExchangeFinder.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/ExchangeFinder.java @@ -1,28 +1,18 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ +/* + * Copyright (C) 2015 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.aoju.bus.http.accord; import org.aoju.bus.core.toolkit.IoKit; @@ -36,23 +26,27 @@ import java.util.List; /** - * 尝试找到一系列交换的连接。使用以下策略 + * Attempts to find the connections for a sequence of exchanges. This uses the following strategies: * *
                - *
              1. 如果当前调用已经有一个可以满足使用它的请求的连接。对初始交换及其后续使用相同的连接可能会改善局部性
              2. - *
              3. 如果池中有可以满足请求的连接,则使用它。请注意,共享交换可以向不同的主机名发出请求!有关详细信息,请参阅 {@link RealConnection#isEligible}
              4. - *
              5. 如果没有现有连接,请列出路由(可能需要阻止 DNS 查找)并尝试建立新连接。当发生故障时,重试迭代可用路由列表
              6. + *
              7. If the current call already has a connection that can satisfy the request it is used. + * Using the same connection for an initial exchange and its follow-ups may improve locality. + * + *
              8. If there is a connection in the pool that can satisfy the request it is used. Note that + * it is possible for shared exchanges to make requests to different host names! See {@link + * RealConnection#isEligible} for details. + * + *
              9. If there's no existing connection, make a list of routes (which may require blocking DNS + * lookups) and attempt a new connection them. When failures occur, retries iterate the list + * of available routes. *
              - *

              - * 如果在 DNS、TCP 或 TLS 工作正在进行时池获得了合格的连接,则此查找器将首选池连接 - *

              - * 可以取消查找过程 * - * @author Kimi Liu - * @since Java 17+ + *

              If the pool gains an eligible connection while DNS, TCP, or TLS work is in flight, this finder + * will prefer pooled connections. Only pooled HTTP/2 connections are used for such de-duplication. + * + *

              It is possible to cancel the finding process. */ -class ExchangeFinder { - +final class ExchangeFinder { private final Transmitter transmitter; private final Address address; private final RealConnectionPool connectionPool; @@ -258,12 +252,12 @@ RealConnection connectingConnection() { void trackFailure() { assert (!Thread.holdsLock(connectionPool)); synchronized (connectionPool) { - hasStreamFailure = true; // 允许重试 + hasStreamFailure = true; // Permit retries. } } /** - * 如果重试可能会修复失败,则返回 true + * Returns true if there is a failure that retrying might fix. */ boolean hasStreamFailure() { synchronized (connectionPool) { @@ -272,9 +266,7 @@ boolean hasStreamFailure() { } /** - * 如果当前路线仍然很好,或者如果有我们还没有尝试过的路线,则返回 true - * - * @return the boolean + * Returns true if a current route is still good or if there are routes we haven't tried yet. */ boolean hasRouteToTry() { synchronized (connectionPool) { @@ -282,7 +274,7 @@ boolean hasRouteToTry() { return true; } if (retryCurrentRoute()) { - // 锁定路线,因为 retryCurrentRoute() 是活泼的,我们不想调用它两次 + // Lock in the route because retryCurrentRoute() is racy and we don't want to call it twice. nextRouteToTry = transmitter.connection.route(); return true; } @@ -292,13 +284,13 @@ boolean hasRouteToTry() { } /** - * 如果应该重试用于当前连接的路由,则返回 true,即使连接本身不健康 - * 这里最大的问题是我们不应该重用来自合并连接的路由 + * Return true if the route used for the current connection should be retried, even if the + * connection itself is unhealthy. The biggest gotcha here is that we shouldn't reuse routes from + * coalesced connections. */ private boolean retryCurrentRoute() { return transmitter.connection != null && transmitter.connection.routeFailureCount == 0 && Builder.sameConnection(transmitter.connection.route().address().url(), address.url()); } - } diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnection.java b/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnection.java index ad2bf8c1cd..efa7d8590a 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnection.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnection.java @@ -32,7 +32,7 @@ import org.aoju.bus.core.lang.Header; import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.core.net.ssl.HostnameVerifier; +import org.aoju.bus.core.net.tls.HostnameVerifier; import org.aoju.bus.core.toolkit.IoKit; import org.aoju.bus.http.*; import org.aoju.bus.http.accord.platform.Platform; @@ -356,7 +356,7 @@ private void connectTls(ConnectionSelector connectionSelector) throws IOExceptio throw new SSLPeerUnverifiedException( "Hostname " + address.url().host() + " not verified:" + "\n certificate: " + CertificatePinner.pin(cert) - + "\n DN: " + cert.getSubjectDN().getName() + + "\n DN: " + cert.getSubjectX500Principal().getName() + "\n subjectAltNames: " + HostnameVerifier.allSubjectAltNames(cert)); } else { throw new SSLPeerUnverifiedException( @@ -408,15 +408,15 @@ private Request createTunnel(int readTimeout, int writeTimeout, Request tunnelRe // 在每个SSL +代理连接的第一个消息对上创建SSL隧道 String requestLine = "CONNECT " + Builder.hostHeader(url, true) + " HTTP/1.1"; while (true) { - Http1Codec tunnelConnection = new Http1Codec(null, null, source, sink); + Http1Codec tunnelCodec = new Http1Codec(null, null, source, sink); source.timeout().timeout(readTimeout, TimeUnit.MILLISECONDS); sink.timeout().timeout(writeTimeout, TimeUnit.MILLISECONDS); - tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine); - tunnelConnection.finishRequest(); - Response response = tunnelConnection.readResponseHeaders(false) + tunnelCodec.writeRequest(tunnelRequest.headers(), requestLine); + tunnelCodec.finishRequest(); + Response response = tunnelCodec.readResponseHeaders(false) .request(tunnelRequest) .build(); - tunnelConnection.skipConnectBody(response); + tunnelCodec.skipConnectBody(response); switch (response.code()) { case Http.HTTP_OK: @@ -700,7 +700,7 @@ public Protocol protocol() { @Override public String toString() { - return "Connection{" + return "RealConnection{" + route.address().url().host() + Symbol.COLON + route.address().url().port() + ", proxy=" + route.proxy() diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnectionPool.java b/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnectionPool.java index e04d5ade73..51a103393c 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnectionPool.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/RealConnectionPool.java @@ -163,7 +163,6 @@ public void evictAll() { /** * Performs maintenance on this pool, evicting the connection that has been idle the longest if * either it has exceeded the keep alive limit or the idle connections limit. - *

              * Returns the duration in nanos to sleep until the next scheduled call to this method. Returns * -1 if no further cleanups are required. */ @@ -266,4 +265,5 @@ public void connectFailed(Route failedRoute, IOException failure) { routeDatabase.failed(failedRoute); } + } diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/RouteException.java b/bus-http/src/main/java/org/aoju/bus/http/accord/RouteException.java index d2ec21c2e7..558f4cb698 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/RouteException.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/RouteException.java @@ -1,48 +1,33 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ +/* + * Copyright (C) 2015 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.aoju.bus.http.accord; -import org.aoju.bus.core.exception.HttpUncheckException; import org.aoju.bus.http.Builder; import java.io.IOException; /** - * 抛出异常,以指示通过单一路由连接的问题 - * 可能已经用替代协议进行了多次尝试,但没有一次成功 - * - * @author Kimi Liu - * @since Java 17+ + * An exception thrown to indicate a problem connecting via a single Route. Multiple attempts may + * have been made with alternative protocols, none of which were successful. */ -public class RouteException extends HttpUncheckException { - +public final class RouteException extends RuntimeException { private IOException firstException; private IOException lastException; - public RouteException(IOException cause) { + RouteException(IOException cause) { super(cause); firstException = cause; lastException = cause; @@ -56,9 +41,8 @@ public IOException getLastConnectException() { return lastException; } - public void addConnectException(IOException e) { + void addConnectException(IOException e) { Builder.addSuppressedIfPossible(firstException, e); lastException = e; } - } diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/RouteSelector.java b/bus-http/src/main/java/org/aoju/bus/http/accord/RouteSelector.java index a6dc7764d0..7400f7ceaa 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/RouteSelector.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/RouteSelector.java @@ -235,6 +235,7 @@ private void resetNextInetSocketAddress(Proxy proxy) throws IOException { * 选定的路由 */ public static class Selection { + private final List routes; private int nextRouteIndex = 0; diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/Transmitter.java b/bus-http/src/main/java/org/aoju/bus/http/accord/Transmitter.java index f709b0dd91..03ca464097 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/Transmitter.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/Transmitter.java @@ -138,7 +138,8 @@ public void prepareToConnect(Request request) { } this.request = request; - this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()), call, eventListener); + this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()), + call, eventListener); } private Address createAddress(UnoUrl url) { @@ -151,7 +152,9 @@ private Address createAddress(UnoUrl url) { certificatePinner = client.certificatePinner(); } - return new Address(url.host(), url.port(), client.dns(), client.socketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(), client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector()); + return new Address(url.host(), url.port(), client.dns(), client.socketFactory(), + sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(), + client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector()); } /** @@ -163,12 +166,13 @@ Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) { throw new IllegalStateException("released"); } if (exchange != null) { - throw new IllegalStateException("cannot make a new request because the previous response " + "is still open: please call response.close()"); + throw new IllegalStateException("cannot make a new request because the previous response " + + "is still open: please call response.close()"); } } - HttpCodec httpCodec = exchangeFinder.find(client, chain, doExtensiveHealthChecks); - Exchange result = new Exchange(this, call, eventListener, exchangeFinder, httpCodec); + HttpCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks); + Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec); synchronized (connectionPool) { this.exchange = result; @@ -229,11 +233,11 @@ public void exchangeDoneDueToException() { * Releases resources held with the request or response of {@code exchange}. This should be called * when the request completes normally or when it fails due to an exception, in which case {@code * e} should be non-null. - *

              * If the exchange was canceled or timed out, this will wrap {@code e} in an exception that * provides that additional context. Otherwise {@code e} is returned as-is. */ - IOException exchangeMessageDone(Exchange exchange, boolean requestDone, boolean responseDone, IOException e) { + IOException exchangeMessageDone( + Exchange exchange, boolean requestDone, boolean responseDone, IOException e) { boolean exchangeDone = false; synchronized (connectionPool) { if (exchange != this.exchange) { @@ -270,7 +274,6 @@ public IOException noMoreExchanges(IOException e) { /** * Release the connection if it is no longer needed. This is called after each exchange completes * and after the call signals that no more exchanges are expected. - *

              * If the transmitter was canceled or timed out, this will wrap {@code e} in an exception that * provides that additional context. Otherwise {@code e} is returned as-is. * @@ -285,7 +288,9 @@ private IOException maybeReleaseConnection(IOException e, boolean force) { throw new IllegalStateException("cannot release connection while it is in use"); } releasedConnection = this.connection; - socket = this.connection != null && exchange == null && (force || noMoreExchanges) ? releaseConnectionNoEvents() : null; + socket = this.connection != null && exchange == null && (force || noMoreExchanges) + ? releaseConnectionNoEvents() + : null; if (this.connection != null) releasedConnection = null; callEnd = noMoreExchanges && exchange == null; } @@ -321,7 +326,6 @@ public boolean hasExchange() { * Immediately closes the socket connection if it's currently held. Use this to interrupt an * in-flight request from any thread. It's the caller's responsibility to close the request body * and response body streams; otherwise resources may be leaked. - *

              * This method is safe to be called concurrently, but provides limited guarantees. If a * transport layer connection has been established (such as a HTTP/2 stream) that is terminated. * Otherwise if a socket connection is being established, that is terminated. @@ -332,7 +336,9 @@ public void cancel() { synchronized (connectionPool) { canceled = true; exchangeToCancel = exchange; - connectionToCancel = exchangeFinder != null && exchangeFinder.connectingConnection() != null ? exchangeFinder.connectingConnection() : connection; + connectionToCancel = exchangeFinder != null && exchangeFinder.connectingConnection() != null + ? exchangeFinder.connectingConnection() + : connection; } if (exchangeToCancel != null) { exchangeToCancel.cancel(); diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Android10Platform.java b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Android10Platform.java deleted file mode 100644 index ad2f0b5e45..0000000000 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Android10Platform.java +++ /dev/null @@ -1,96 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.http.accord.platform; - -import org.aoju.bus.http.Protocol; - -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLSocket; -import java.util.List; - -/** - * Android 10+ - * - * @author Kimi Liu - * @since Java 17+ - */ -class Android10Platform extends AndroidPlatform { - - Android10Platform(Class sslParametersClass) { - super(sslParametersClass, null, null, null, null, null); - } - - public static Platform buildIfSupported() { - if (!isAndroid()) { - return null; - } - - try { - Class sslParametersClass = - Class.forName("com.android.org.conscrypt.SSLParametersImpl"); - - return new Android10Platform(sslParametersClass); - } catch (ReflectiveOperationException ignored) { - } - - return null; // Not an Android 10+ runtime. - } - - @Override - public void configureTlsExtensions( - SSLSocket sslSocket, String hostname, List protocols) { - try { - enableSessionTickets(sslSocket); - - SSLParameters sslParameters = sslSocket.getSSLParameters(); - - // Enable ALPN. - String[] protocolsArray = alpnProtocolNames(protocols).toArray(new String[0]); - sslParameters.setApplicationProtocols(protocolsArray); - - sslSocket.setSSLParameters(sslParameters); - } catch (IllegalArgumentException ex) { - // probably java.lang.IllegalArgumentException: Invalid input to toASCII from IDN.toASCII - throw new AssertionError(ex); - } - } - - private void enableSessionTickets(SSLSocket sslSocket) { - - } - - @Override - public String getSelectedProtocol(SSLSocket socket) { - String alpnResult = socket.getApplicationProtocol(); - - if (alpnResult == null || alpnResult.isEmpty()) { - return null; - } - - return alpnResult; - } - -} diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/AndroidPlatform.java b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/AndroidPlatform.java deleted file mode 100644 index c3f62796ae..0000000000 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/AndroidPlatform.java +++ /dev/null @@ -1,408 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.http.accord.platform; - -import org.aoju.bus.core.Version; -import org.aoju.bus.core.lang.Algorithm; -import org.aoju.bus.core.lang.Charset; -import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.http.Builder; -import org.aoju.bus.http.Protocol; -import org.aoju.bus.http.secure.BasicTrustRootIndex; -import org.aoju.bus.http.secure.CertificateChainCleaner; -import org.aoju.bus.http.secure.TrustRootIndex; -import org.aoju.bus.logger.Logger; - -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.X509TrustManager; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.security.cert.Certificate; -import java.security.cert.TrustAnchor; -import java.security.cert.X509Certificate; -import java.util.List; - -/** - * Android 5+ - * - * @author Kimi Liu - * @since Java 17+ - */ -class AndroidPlatform extends Platform { - - private final Class sslParametersClass; - private final Class sslSocketClass; - private final Method setUseSessionTickets; - private final Method setHostname; - private final Method getAlpnSelectedProtocol; - private final Method setAlpnProtocols; - - private final CloseGuard closeGuard = CloseGuard.get(); - - AndroidPlatform(Class sslParametersClass, Class sslSocketClass, Method setUseSessionTickets, - Method setHostname, Method getAlpnSelectedProtocol, Method setAlpnProtocols) { - this.sslParametersClass = sslParametersClass; - this.sslSocketClass = sslSocketClass; - this.setUseSessionTickets = setUseSessionTickets; - this.setHostname = setHostname; - this.getAlpnSelectedProtocol = getAlpnSelectedProtocol; - this.setAlpnProtocols = setAlpnProtocols; - } - - public static Platform buildIfSupported() { - if (!Platform.isAndroid()) { - return null; - } - - // Attempt to find Android 5+ APIs. - Class sslParametersClass; - Class sslSocketClass; - - try { - sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl"); - sslSocketClass = Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl"); - } catch (ClassNotFoundException ignored) { - return null; // Not an Android runtime. - } - - try { - Method setUseSessionTickets = sslSocketClass.getDeclaredMethod( - "setUseSessionTickets", boolean.class); - Method setHostname = sslSocketClass.getMethod("setHostname", String.class); - Method getAlpnSelectedProtocol = sslSocketClass.getMethod("getAlpnSelectedProtocol"); - Method setAlpnProtocols = sslSocketClass.getMethod("setAlpnProtocols", byte[].class); - return new AndroidPlatform(sslParametersClass, sslSocketClass, setUseSessionTickets, - setHostname, getAlpnSelectedProtocol, setAlpnProtocols); - } catch (NoSuchMethodException ignored) { - } - throw new IllegalStateException( - "Expected Android API level 21+ but was " + Version.all()); - } - - - @Override - public void connectSocket(Socket socket, InetSocketAddress address, - int connectTimeout) throws IOException { - try { - socket.connect(address, connectTimeout); - } catch (AssertionError e) { - if (Builder.isAndroidGetsocknameError(e)) throw new IOException(e); - throw e; - } catch (ClassCastException e) { - // On android 8.0, socket.connect throws a ClassCastException due to a bug - throw e; - } - } - - @Override - protected X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) { - Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, "sslParameters"); - if (context == null) { - // If that didn't work, try the Google Play Services SSL provider before giving up. This - // must be loaded by the SSLSocketFactory's class loader. - try { - Class gmsSslParametersClass = Class.forName( - "com.google.android.gms.org.conscrypt.SSLParametersImpl", false, - sslSocketFactory.getClass().getClassLoader()); - context = readFieldOrNull(sslSocketFactory, gmsSslParametersClass, "sslParameters"); - } catch (ClassNotFoundException e) { - return super.trustManager(sslSocketFactory); - } - } - - X509TrustManager x509TrustManager = readFieldOrNull( - context, X509TrustManager.class, "x509TrustManager"); - if (x509TrustManager != null) return x509TrustManager; - - return readFieldOrNull(context, X509TrustManager.class, "trustManager"); - } - - @Override - public void configureTlsExtensions( - SSLSocket sslSocket, String hostname, List protocols) { - if (!sslSocketClass.isInstance(sslSocket)) { - return; // No TLS extensions if the socket class is custom. - } - try { - // 启用SNI和会话票据 - if (hostname != null) { - setUseSessionTickets.invoke(sslSocket, true); - // This is SSLParameters.setServerNames() in API 24+. - setHostname.invoke(sslSocket, hostname); - } - - // 启用 ALPN - setAlpnProtocols.invoke(sslSocket, concatLengthPrefixed(protocols)); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new AssertionError(e); - } - } - - @Override - public String getSelectedProtocol(SSLSocket socket) { - if (!sslSocketClass.isInstance(socket)) { - return null; - } - try { - byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invoke(socket); - return alpnResult != null ? new String(alpnResult, Charset.UTF_8) : null; - } catch (IllegalAccessException | InvocationTargetException e) { - throw new AssertionError(e); - } - } - - @Override - public Object getStackTraceForCloseable(String closer) { - return closeGuard.createAndOpen(closer); - } - - @Override - public void logCloseableLeak(String message, Object stackTrace) { - boolean reported = closeGuard.warnIfOpen(stackTrace); - if (!reported) { - // 无法通过近距离观察报告。作为最后的努力,把它发送到记录器. - Logger.warn(message, null); - } - } - - @Override - public boolean isCleartextTrafficPermitted(String hostname) { - try { - Class networkPolicyClass = Class.forName("android.security.NetworkSecurityPolicy"); - Method getInstanceMethod = networkPolicyClass.getMethod("getInstance"); - Object networkSecurityPolicy = getInstanceMethod.invoke(null); - return api24IsCleartextTrafficPermitted(hostname, networkPolicyClass, networkSecurityPolicy); - } catch (ClassNotFoundException | NoSuchMethodException e) { - return super.isCleartextTrafficPermitted(hostname); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new AssertionError("unable to determine cleartext support", e); - } - } - - private boolean api24IsCleartextTrafficPermitted(String hostname, Class networkPolicyClass, - Object networkSecurityPolicy) throws InvocationTargetException, IllegalAccessException { - try { - Method isCleartextTrafficPermittedMethod = networkPolicyClass - .getMethod("isCleartextTrafficPermitted", String.class); - return (boolean) isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy, hostname); - } catch (NoSuchMethodException e) { - return api23IsCleartextTrafficPermitted(hostname, networkPolicyClass, networkSecurityPolicy); - } - } - - private boolean api23IsCleartextTrafficPermitted(String hostname, Class networkPolicyClass, - Object networkSecurityPolicy) throws InvocationTargetException, IllegalAccessException { - try { - Method isCleartextTrafficPermittedMethod = networkPolicyClass - .getMethod("isCleartextTrafficPermitted"); - return (boolean) isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy); - } catch (NoSuchMethodException e) { - return super.isCleartextTrafficPermitted(hostname); - } - } - - public CertificateChainCleaner buildCertificateChainCleaner(X509TrustManager trustManager) { - try { - Class extensionsClass = Class.forName("android.net.http.X509TrustManagerExtensions"); - Constructor constructor = extensionsClass.getConstructor(X509TrustManager.class); - Object extensions = constructor.newInstance(trustManager); - Method checkServerTrusted = extensionsClass.getMethod( - "checkServerTrusted", X509Certificate[].class, String.class, String.class); - return new AndroidCertificateChainCleaner(extensions, checkServerTrusted); - } catch (Exception e) { - return super.buildCertificateChainCleaner(trustManager); - } - } - - @Override - public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) { - try { - Method method = trustManager.getClass().getDeclaredMethod( - "findTrustAnchorByIssuerAndSignature", X509Certificate.class); - method.setAccessible(true); - return new CustomTrustRootIndex(trustManager, method); - } catch (NoSuchMethodException e) { - return super.buildTrustRootIndex(trustManager); - } - } - - /** - * X509TrustManagerExtensions是在API 17 (Android 4.2, 2012年底发布)中添加到Android的。 - * 这是在Android上获得干净链的最好方法,因为它使用与TLS握手相同的代码 - */ - static class AndroidCertificateChainCleaner extends CertificateChainCleaner { - private final Object x509TrustManagerExtensions; - private final Method checkServerTrusted; - - AndroidCertificateChainCleaner(Object x509TrustManagerExtensions, Method checkServerTrusted) { - this.x509TrustManagerExtensions = x509TrustManagerExtensions; - this.checkServerTrusted = checkServerTrusted; - } - - @Override - public List clean(List chain, String hostname) - throws SSLPeerUnverifiedException { - try { - X509Certificate[] certificates = chain.toArray(new X509Certificate[chain.size()]); - return (List) checkServerTrusted.invoke( - x509TrustManagerExtensions, certificates, Algorithm.RSA, hostname); - } catch (InvocationTargetException e) { - SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage()); - exception.initCause(e); - throw exception; - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } - - @Override - public boolean equals(Object other) { - return other instanceof AndroidCertificateChainCleaner; // All instances are equivalent. - } - - @Override - public int hashCode() { - return 0; - } - } - - /** - * 提供对内部dalvik.system.CloseGuard的访问。CloseGuard类。 - * Android将其与android.os.StrictMode结合使用。 - * 严格模式报告泄漏的java.io.Closeable - */ - static class CloseGuard { - private final Method getMethod; - private final Method openMethod; - private final Method warnIfOpenMethod; - - CloseGuard(Method getMethod, Method openMethod, Method warnIfOpenMethod) { - this.getMethod = getMethod; - this.openMethod = openMethod; - this.warnIfOpenMethod = warnIfOpenMethod; - } - - static CloseGuard get() { - Method getMethod; - Method openMethod; - Method warnIfOpenMethod; - - try { - Class closeGuardClass = Class.forName("dalvik.system.CloseGuard"); - getMethod = closeGuardClass.getMethod(Normal.GET); - openMethod = closeGuardClass.getMethod("open", String.class); - warnIfOpenMethod = closeGuardClass.getMethod("warnIfOpen"); - } catch (Exception ignored) { - getMethod = null; - openMethod = null; - warnIfOpenMethod = null; - } - return new CloseGuard(getMethod, openMethod, warnIfOpenMethod); - } - - Object createAndOpen(String closer) { - if (null != getMethod) { - try { - Object closeGuardInstance = getMethod.invoke(null); - openMethod.invoke(closeGuardInstance, closer); - return closeGuardInstance; - } catch (Exception ignored) { - } - } - return null; - } - - boolean warnIfOpen(Object closeGuardInstance) { - boolean reported = false; - if (null != closeGuardInstance) { - try { - warnIfOpenMethod.invoke(closeGuardInstance); - reported = true; - } catch (Exception ignored) { - } - } - return reported; - } - } - - /** - * 利用Android实现细节的受信任根证书的索引。 - * 这个类的初始化速度可能比{@link BasicTrustRootIndex}快得多, - * 因为它不需要加载和索引受信任的CA证书 - * 这个类使用API 14中添加到Android的API (Android 4.0, 2011年10月发布)。 - * 这个类不应该在Android API 17或更好的版本中使用,因为这些版本由 - * {@link AndroidPlatform.AndroidCertificateChainCleaner}提供更好的服务。 - */ - static class CustomTrustRootIndex implements TrustRootIndex { - private final X509TrustManager trustManager; - private final Method findByIssuerAndSignatureMethod; - - CustomTrustRootIndex(X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) { - this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod; - this.trustManager = trustManager; - } - - @Override - public X509Certificate findByIssuerAndSignature(X509Certificate cert) { - try { - TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke( - trustManager, cert); - return trustAnchor != null - ? trustAnchor.getTrustedCert() - : null; - } catch (IllegalAccessException e) { - throw new AssertionError("unable to get issues and signature", e); - } catch (InvocationTargetException e) { - return null; - } - } - - @Override - public boolean equals(Object object) { - if (object == this) { - return true; - } - if (!(object instanceof CustomTrustRootIndex)) { - return false; - } - CustomTrustRootIndex that = (CustomTrustRootIndex) object; - return trustManager.equals(that.trustManager) - && findByIssuerAndSignatureMethod.equals(that.findByIssuerAndSignatureMethod); - } - - @Override - public int hashCode() { - return trustManager.hashCode() + 31 * findByIssuerAndSignatureMethod.hashCode(); - } - } - -} diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk8WithJettyBootPlatform.java b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk8WithJettyBootPlatform.java deleted file mode 100644 index 0de84da059..0000000000 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk8WithJettyBootPlatform.java +++ /dev/null @@ -1,176 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.http.accord.platform; - -import org.aoju.bus.http.Builder; -import org.aoju.bus.http.Protocol; -import org.aoju.bus.logger.Logger; - -import javax.net.ssl.SSLSocket; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.List; - -/** - * OpenJDK 8 with {@code org.mortbay.jetty.alpn:alpn-boot} in the boot class path. - * - * @author Kimi Liu - * @since Java 17+ - */ -class Jdk8WithJettyBootPlatform extends Platform { - private final Method putMethod; - private final Method getMethod; - private final Method removeMethod; - private final Class clientProviderClass; - private final Class serverProviderClass; - - Jdk8WithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod, - Class clientProviderClass, Class serverProviderClass) { - this.putMethod = putMethod; - this.getMethod = getMethod; - this.removeMethod = removeMethod; - this.clientProviderClass = clientProviderClass; - this.serverProviderClass = serverProviderClass; - } - - public static Platform buildIfSupported() { - // Find Jetty's ALPN extension for OpenJDK. - try { - String alpnClassName = "org.eclipse.jetty.alpn.ALPN"; - Class alpnClass = Class.forName(alpnClassName, true, null); - Class providerClass = Class.forName(alpnClassName + "$Provider", true, null); - Class clientProviderClass = Class.forName(alpnClassName + "$ClientProvider", true, null); - Class serverProviderClass = Class.forName(alpnClassName + "$ServerProvider", true, null); - Method putMethod = alpnClass.getMethod("put", SSLSocket.class, providerClass); - Method getMethod = alpnClass.getMethod("get", SSLSocket.class); - Method removeMethod = alpnClass.getMethod("remove", SSLSocket.class); - return new Jdk8WithJettyBootPlatform( - putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass); - } catch (ClassNotFoundException | NoSuchMethodException ignored) { - } - - return null; - } - - @Override - public void configureTlsExtensions( - SSLSocket sslSocket, String hostname, List protocols) { - List names = alpnProtocolNames(protocols); - - try { - Object alpnProvider = Proxy.newProxyInstance(Platform.class.getClassLoader(), - new Class[]{clientProviderClass, serverProviderClass}, new AlpnProvider(names)); - putMethod.invoke(null, sslSocket, alpnProvider); - } catch (InvocationTargetException | IllegalAccessException e) { - throw new AssertionError("failed to set ALPN", e); - } - } - - @Override - public void afterHandshake(SSLSocket sslSocket) { - try { - removeMethod.invoke(null, sslSocket); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new AssertionError("failed to remove ALPN", e); - } - } - - @Override - public String getSelectedProtocol(SSLSocket socket) { - try { - AlpnProvider provider = - (AlpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket)); - if (!provider.unsupported && provider.selected == null) { - Logger.info("ALPN callback dropped: HTTP/2 is disabled. " - + "Is alpn-boot on the boot class path?", null); - return null; - } - return provider.unsupported ? null : provider.selected; - } catch (InvocationTargetException | IllegalAccessException e) { - throw new AssertionError("failed to get ALPN selected protocol", e); - } - } - - /** - * Handle the methods of ALPN's ClientProvider and ServerProvider without a compile-time - * dependency on those interfaces. - */ - private static class AlpnProvider implements InvocationHandler { - /** - * This peer's supported protocols. - */ - private final List protocols; - /** - * Set when remote peer notifies ALPN is unsupported. - */ - boolean unsupported; - /** - * The protocol the server selected. - */ - String selected; - - AlpnProvider(List protocols) { - this.protocols = protocols; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - String methodName = method.getName(); - Class returnType = method.getReturnType(); - if (args == null) { - args = Builder.EMPTY_STRING_ARRAY; - } - if (methodName.equals("supports") && boolean.class == returnType) { - return true; // ALPN is supported. - } else if (methodName.equals("unsupported") && void.class == returnType) { - this.unsupported = true; // Peer doesn't support ALPN. - return null; - } else if (methodName.equals("protocols") && args.length == 0) { - return protocols; // Client advertises these protocols. - } else if ((methodName.equals("selectProtocol") || methodName.equals("select")) - && String.class == returnType && args.length == 1 && args[0] instanceof List) { - List peerProtocols = (List) args[0]; - // Pick the first known protocol the peer advertises. - for (int i = 0, size = peerProtocols.size(); i < size; i++) { - String protocol = (String) peerProtocols.get(i); - if (protocols.contains(protocol)) { - return selected = protocol; - } - } - return selected = protocols.get(0); // On no intersection, try peer's first protocol. - } else if ((methodName.equals("protocolSelected") || methodName.equals("selected")) - && args.length == 1) { - this.selected = (String) args[0]; // Server selected this protocol. - return null; - } else { - return method.invoke(this, args); - } - } - } - -} diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk9Platform.java b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/JdkPlatform.java similarity index 95% rename from bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk9Platform.java rename to bus-http/src/main/java/org/aoju/bus/http/accord/platform/JdkPlatform.java index 2956747fe2..652605631d 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Jdk9Platform.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/JdkPlatform.java @@ -42,24 +42,24 @@ * @author Kimi Liu * @since Java 17+ */ -public class Jdk9Platform extends Platform { +public class JdkPlatform extends Platform { final Method setProtocolMethod; final Method getProtocolMethod; - Jdk9Platform(Method setProtocolMethod, Method getProtocolMethod) { + JdkPlatform(Method setProtocolMethod, Method getProtocolMethod) { this.setProtocolMethod = setProtocolMethod; this.getProtocolMethod = getProtocolMethod; } - public static Jdk9Platform buildIfSupported() { + public static JdkPlatform buildIfSupported() { // Find JDK 9 new methods try { Method setProtocolMethod = SSLParameters.class.getMethod("setApplicationProtocols", String[].class); Method getProtocolMethod = SSLSocket.class.getMethod("getApplicationProtocol"); - return new Jdk9Platform(setProtocolMethod, getProtocolMethod); + return new JdkPlatform(setProtocolMethod, getProtocolMethod); } catch (NoSuchMethodException ignored) { // pre JDK 9 } diff --git a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Platform.java b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Platform.java index d671ed0175..3b32fb629f 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Platform.java +++ b/bus-http/src/main/java/org/aoju/bus/http/accord/platform/Platform.java @@ -88,57 +88,17 @@ public static List alpnProtocolNames(List protocols) { * @return 平台信息 */ private static Platform findPlatform() { - if (isAndroid()) { - return findAndroidPlatform(); - } else { - return findJvmPlatform(); + Platform jdk = JdkPlatform.buildIfSupported(); + if (jdk != null) { + return jdk; } - } - - public static boolean isAndroid() { - // This explicit check avoids activating in Android Studio with Android specific classes - // available when running plugins inside the IDE. - return "Dalvik".equals(System.getProperty("java.vm.name")); - } - - private static Platform findJvmPlatform() { - Platform jdk9 = Jdk9Platform.buildIfSupported(); - - if (jdk9 != null) { - return jdk9; - } - - Platform jdkWithJettyBoot = Jdk8WithJettyBootPlatform.buildIfSupported(); - - if (jdkWithJettyBoot != null) { - return jdkWithJettyBoot; - } - - // 可能是像OpenJDK和Oracle JDK + // Probably an Oracle JDK like OpenJDK. return new Platform(); } - private static Platform findAndroidPlatform() { - Platform android10 = Android10Platform.buildIfSupported(); - - if (android10 != null) { - return android10; - } - - Platform android = AndroidPlatform.buildIfSupported(); - - if (android == null) { - throw new NullPointerException("No platform found on Android"); - } - - return android; - } - /** - * 返回以8位长度为前缀的协议名的连接 - * - * @param protocols 协议信息 - * @return 8位长度为前缀的协议名的连接 + * Returns the concatenation of 8-bit, length prefixed protocol names. + * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 */ static byte[] concatLengthPrefixed(List protocols) { Buffer result = new Buffer(); diff --git a/bus-http/src/main/java/org/aoju/bus/http/bodys/FormBody.java b/bus-http/src/main/java/org/aoju/bus/http/bodys/FormBody.java index 5b0f2b6667..526a1b38aa 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/bodys/FormBody.java +++ b/bus-http/src/main/java/org/aoju/bus/http/bodys/FormBody.java @@ -121,6 +121,7 @@ private long writeOrCountBytes(BufferSink sink, boolean countBytes) { } public static class Builder { + private final List names = new ArrayList<>(); private final List values = new ArrayList<>(); private final Charset charset; diff --git a/bus-http/src/main/java/org/aoju/bus/http/bodys/RequestBody.java b/bus-http/src/main/java/org/aoju/bus/http/bodys/RequestBody.java index af68e65234..5f456256f0 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/bodys/RequestBody.java +++ b/bus-http/src/main/java/org/aoju/bus/http/bodys/RequestBody.java @@ -54,10 +54,13 @@ public abstract class RequestBody { */ public static RequestBody create(MediaType mediaType, String content) { java.nio.charset.Charset charset = Charset.UTF_8; - if (null != mediaType) { - charset = null != mediaType.charset() ? mediaType.charset() : Charset.UTF_8; + if (mediaType != null) { + charset = mediaType.charset(); + if (charset == null) { + charset = Charset.UTF_8; + mediaType = MediaType.valueOf(mediaType + "; charset=utf-8"); + } } - byte[] bytes = content.getBytes(charset); return create(mediaType, bytes); } @@ -192,27 +195,19 @@ public long length() throws IOException { /** * A duplex request body is special in how it is transmitted on the network and * in the API contract between Http and the application. - *

              * This method returns false unless it is overridden by a subclass. - *

              * Duplex Transmission - *

              * With regular HTTP calls the request always completes sending before the response may begin * receiving. With duplex the request and response may be interleaved! That is, request body bytes * may be sent after response headers or body bytes have been received. - *

              * Though any call may be initiated as a duplex call, only web servers that are specially * designed for this nonstandard interaction will use it. As of 2019-01, the only widely-used * implementation of this pattern is gRPC. - *

              * Because the encoding of interleaved data is not well-defined for HTTP/1, duplex request * bodies may only be used with HTTP/2. Calls to HTTP/1 servers will fail before the HTTP request * is transmitted. If you cannot ensure that your client and server both support HTTP/2, do not * use this feature. - *

              - * Duplex APIs - *

              * With regular request bodies it is not legal to write bytes to the sink passed to {@link * RequestBody#writeTo} after that method returns. For duplex requests bodies that condition is * lifted. Such writes occur on an application-provided thread and may occur concurrently with @@ -228,9 +223,7 @@ public boolean isDuplex() { * Returns true if this body expects at most one call to {@link #writeTo} and can be transmitted * at most once. This is typically used when writing the request body is destructive and it is not * possible to recreate the request body after it has been sent. - *

              * This method returns false unless it is overridden by a subclass. - *

              * By default Http will attempt to retransmit request bodies when the original request fails * due to a stale connection, a client timeout (HTTP 408), a satisfied authorization challenge * (HTTP 401 and 407), or a retryable server failure (HTTP 503 with a {@code Retry-After: 0} diff --git a/bus-http/src/main/java/org/aoju/bus/http/bodys/ResponseBody.java b/bus-http/src/main/java/org/aoju/bus/http/bodys/ResponseBody.java index c108be9158..2c20c5ccc0 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/bodys/ResponseBody.java +++ b/bus-http/src/main/java/org/aoju/bus/http/bodys/ResponseBody.java @@ -78,8 +78,12 @@ public abstract class ResponseBody implements Closeable { */ public static ResponseBody create(MediaType mediaType, String content) { java.nio.charset.Charset charset = Charset.UTF_8; - if (null != mediaType) { - charset = null != mediaType.charset() ? mediaType.charset() : Charset.UTF_8; + if (mediaType != null) { + charset = mediaType.charset(); + if (charset == null) { + charset = Charset.UTF_8; + mediaType = MediaType.valueOf(mediaType + "; charset=utf-8"); + } } Buffer buffer = new Buffer().writeString(content, charset); return create(mediaType, buffer.size(), buffer); @@ -158,7 +162,6 @@ public final InputStream byteStream() { /** * Returns the response as a byte array. - *

              * This method loads entire response body into memory. If the response body is very large this * may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a * possibility for your response. @@ -185,13 +188,10 @@ public final byte[] bytes() throws IOException { /** * Returns the response as a character stream. - *

              - * If the response starts with a Byte - * Order Mark (BOM), it is consumed and used to determine the charset of the response bytes. - *

              + * If the response starts with a ByteOrder Mark (BOM), it is consumed and used to determine + * the charset of the response bytes. * Otherwise if the response has a Content-Type header that specifies a charset, that is used * to determine the charset of the response bytes. - *

              * Otherwise the response bytes are decoded as UTF-8. */ public final Reader charStream() { @@ -201,15 +201,10 @@ public final Reader charStream() { /** * Returns the response as a string. - *

              - * If the response starts with a Byte - * Order Mark (BOM), it is consumed and used to determine the charset of the response bytes. - *

              + * If the response starts with a ByteOrder Mark (BOM), it is consumed and used to determine the charset of the response bytes. * Otherwise if the response has a Content-Type header that specifies a charset, that is used * to determine the charset of the response bytes. - *

              * Otherwise the response bytes are decoded as UTF-8. - *

              * This method loads entire response body into memory. If the response body is very large this * may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a * possibility for your response. @@ -232,6 +227,7 @@ public void close() { } static class BomAwareReader extends Reader { + private final BufferSource source; private final java.nio.charset.Charset charset; diff --git a/bus-http/src/main/java/org/aoju/bus/http/cache/Cache.java b/bus-http/src/main/java/org/aoju/bus/http/cache/Cache.java index de3c80d8f8..f4d257879c 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/cache/Cache.java +++ b/bus-http/src/main/java/org/aoju/bus/http/cache/Cache.java @@ -26,6 +26,7 @@ package org.aoju.bus.http.cache; import org.aoju.bus.core.io.ByteString; +import org.aoju.bus.core.io.FileSystem; import org.aoju.bus.core.io.buffer.Buffer; import org.aoju.bus.core.io.sink.AssignSink; import org.aoju.bus.core.io.sink.BufferSink; @@ -115,7 +116,11 @@ public void trackResponse(CacheStrategy cacheStrategy) { * @param maxSize 缓存的最大大小(以字节为单位) */ public Cache(File directory, long maxSize) { - this.cache = DiskLruCache.create(directory, VERSION, ENTRY_COUNT, maxSize); + this(directory, maxSize, FileSystem.SYSTEM); + } + + public Cache(File directory, long maxSize, FileSystem fileSystem) { + this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize); } public static String key(UnoUrl url) { @@ -127,7 +132,7 @@ static int readInt(BufferSource source) throws IOException { long result = source.readDecimalLong(); String line = source.readUtf8LineStrict(); if (result < 0 || result > Integer.MAX_VALUE || !line.isEmpty()) { - throw new IOException("expected an int but was \"" + result + line + Symbol.DOUBLE_QUOTES); + throw new IOException("expected an int but was \"" + result + line + "\""); } return (int) result; } catch (NumberFormatException e) { @@ -557,7 +562,7 @@ private List readCertificateList(BufferSource source) throws IOExce if (length == -1) return Collections.emptyList(); try { - CertificateFactory certificateFactory = CertificateFactory.getInstance(Builder.X_509); + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); List result = new ArrayList<>(length); for (int i = 0; i < length; i++) { String line = source.readUtf8LineStrict(); @@ -616,6 +621,7 @@ public Response response(DiskLruCache.Snapshot snapshot) { } private static class CacheResponseBody extends ResponseBody { + final DiskLruCache.Snapshot snapshot; private final BufferSource bodySource; private final String mediaType; @@ -658,6 +664,7 @@ public BufferSource source() { } private class CacheRequestImpl implements CacheRequest { + private final DiskLruCache.Editor editor; boolean done; private Sink cacheOut; diff --git a/bus-http/src/main/java/org/aoju/bus/http/cache/CacheControl.java b/bus-http/src/main/java/org/aoju/bus/http/cache/CacheControl.java index b0f06f0a77..61a240f3a6 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/cache/CacheControl.java +++ b/bus-http/src/main/java/org/aoju/bus/http/cache/CacheControl.java @@ -308,6 +308,7 @@ private String headerValue() { * 构建一个{@code Cache-Control}请求头 */ public static class Builder { + boolean noCache; boolean noStore; int maxAgeSeconds = -1; diff --git a/bus-http/src/main/java/org/aoju/bus/http/cache/CacheInterceptor.java b/bus-http/src/main/java/org/aoju/bus/http/cache/CacheInterceptor.java index f1b6378556..152d919b01 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/cache/CacheInterceptor.java +++ b/bus-http/src/main/java/org/aoju/bus/http/cache/CacheInterceptor.java @@ -143,7 +143,7 @@ public Response intercept(Chain chain) throws IOException { } if (null != cacheCandidate && null == cacheResponse) { - // 缓存候选不适用。关闭它 + // 缓存候选不适用关闭它 IoKit.close(cacheCandidate.body()); } diff --git a/bus-http/src/main/java/org/aoju/bus/http/cache/DiskLruCache.java b/bus-http/src/main/java/org/aoju/bus/http/cache/DiskLruCache.java index b3fb61c405..b1f40089b2 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/cache/DiskLruCache.java +++ b/bus-http/src/main/java/org/aoju/bus/http/cache/DiskLruCache.java @@ -25,6 +25,7 @@ ********************************************************************************/ package org.aoju.bus.http.cache; +import org.aoju.bus.core.io.FileSystem; import org.aoju.bus.core.io.sink.BufferSink; import org.aoju.bus.core.io.sink.FaultHideSink; import org.aoju.bus.core.io.sink.Sink; @@ -66,6 +67,7 @@ public class DiskLruCache implements Closeable, Flushable { private static final String DIRTY = "DIRTY"; private static final String REMOVE = "REMOVE"; private static final String READ = "READ"; + final FileSystem fileSystem; /** * 缓存存储其数据的目录 */ @@ -101,8 +103,9 @@ public class DiskLruCache implements Closeable, Flushable { */ private long nextSequenceNumber = 0; - DiskLruCache(File directory, int appVersion, int valueCount, long maxSize, + DiskLruCache(FileSystem fileSystem, File directory, int appVersion, int valueCount, long maxSize, Executor executor) { + this.fileSystem = fileSystem; this.directory = directory; this.appVersion = appVersion; this.journalFile = new File(directory, JOURNAL_FILE); @@ -116,16 +119,15 @@ public class DiskLruCache implements Closeable, Flushable { /** * 创建一个驻留在{@code directory}中的缓存。此缓存在第一次访问时惰性初始化,如果它不存在,将创建它. * + * @param fileSystem 文件系统 * @param directory 一个可写目录 * @param appVersion 版本信息 * @param valueCount 每个缓存条目的值数目. * @param maxSize 此缓存应用于存储的最大字节数 * @return the disk cache */ - public static DiskLruCache create(File directory, - int appVersion, - int valueCount, - long maxSize) { + public static DiskLruCache create(FileSystem fileSystem, File directory, int appVersion, + int valueCount, long maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } @@ -136,7 +138,7 @@ public static DiskLruCache create(File directory, Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), Builder.threadFactory("Httpd DiskLruCache", true)); - return new DiskLruCache(directory, appVersion, valueCount, maxSize, executor); + return new DiskLruCache(fileSystem, directory, appVersion, valueCount, maxSize, executor); } public synchronized void initialize() throws IOException { @@ -147,17 +149,16 @@ public synchronized void initialize() throws IOException { } // 如果存在bkp文件,就使用它 - if (journalFileBackup.exists()) { + if (fileSystem.exists(journalFileBackup)) { // 如果日志文件也存在,删除备份文件 - if (journalFile.exists()) { - journalFileBackup.delete(); + if (fileSystem.exists(journalFile)) { + fileSystem.delete(journalFileBackup); } else { - journalFile.delete(); - journalFileBackup.renameTo(journalFile); + fileSystem.rename(journalFileBackup, journalFile); } } - if (journalFile.exists()) { + if (fileSystem.exists(journalFile)) { try { readJournal(); processJournal(); @@ -183,7 +184,7 @@ public synchronized void initialize() throws IOException { } private void readJournal() throws IOException { - try (BufferSource source = IoKit.buffer(IoKit.source(journalFile))) { + try (BufferSource source = IoKit.buffer(fileSystem.source(journalFile))) { String magic = source.readUtf8LineStrict(); String version = source.readUtf8LineStrict(); String appVersionString = source.readUtf8LineStrict(); @@ -258,7 +259,7 @@ private void readJournalLine(String line) throws IOException { } private BufferSink newJournalWriter() throws FileNotFoundException { - Sink fileSink = IoKit.appendingSink(journalFile); + Sink fileSink = fileSystem.appendingSink(journalFile); Sink faultHidingSink = new FaultHideSink(fileSink) { @Override protected void onException(IOException e) { @@ -275,18 +276,18 @@ protected void onException(IOException e) { * @throws IOException 异常 */ private void processJournal() throws IOException { - journalFileTmp.delete(); + fileSystem.delete(journalFileTmp); for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { Entry entry = i.next(); - if (null == entry.currentEditor) { + if (entry.currentEditor == null) { for (int t = 0; t < valueCount; t++) { size += entry.lengths[t]; } } else { entry.currentEditor = null; for (int t = 0; t < valueCount; t++) { - entry.cleanFiles[t].delete(); - entry.dirtyFiles[t].delete(); + fileSystem.delete(entry.cleanFiles[t]); + fileSystem.delete(entry.dirtyFiles[t]); } i.remove(); } @@ -324,252 +325,17 @@ synchronized void rebuildJournal() throws IOException { } } - if (journalFile.exists()) { - journalFileBackup.delete(); - if (!journalFile.renameTo(journalFileBackup)) { - throw new IOException("failed to rename " + journalFile + " to " + journalFileBackup); - } - } - - journalFile.delete(); - if (!journalFileTmp.renameTo(journalFile)) { - throw new IOException("failed to rename " + journalFileTmp + " to " + journalFile); + if (fileSystem.exists(journalFile)) { + fileSystem.rename(journalFile, journalFileBackup); } - - journalFileBackup.delete(); + fileSystem.rename(journalFileTmp, journalFile); + fileSystem.delete(journalFileBackup); journalWriter = newJournalWriter(); hasJournalErrors = false; mostRecentRebuildFailed = false; } - /** - * 返回缓存当前项的迭代器。这个迭代器不会抛出{@code ConcurrentModificationException}, - * 调用者必须{@link Snapshot#close}每个由{@link Iterator#next}返回的快照 - * 如果做不到这一点,就会泄漏打开的文件,返回的迭代器支持 {@link Iterator#remove}. - * - * @return 返回迭代器 - * @throws IOException 异常 - */ - public synchronized Iterator snapshots() throws IOException { - initialize(); - return new Iterator<>() { - /** - * 迭代条目的副本以防止并发修改错误 - */ - final Iterator delegate = new ArrayList<>(lruEntries.values()).iterator(); - - /** - * 要从{@link #next}返回的快照。如果还没有计算出来,就是Null - */ - Snapshot nextSnapshot; - - /** - * 要使用{@link #remove}删除的快照。如果删除是非法的,则为Null - */ - Snapshot removeSnapshot; - - @Override - public boolean hasNext() { - if (null != nextSnapshot) { - return true; - } - - synchronized (DiskLruCache.this) { - // 如果缓存关闭,则截断迭代器。 - if (closed) return false; - while (delegate.hasNext()) { - Entry entry = delegate.next(); - if (!entry.readable) continue; - Snapshot snapshot = entry.snapshot(); - if (null == snapshot) { - continue; - } - nextSnapshot = snapshot; - return true; - } - } - - return false; - } - - @Override - public Snapshot next() { - if (!hasNext()) throw new NoSuchElementException(); - removeSnapshot = nextSnapshot; - nextSnapshot = null; - return removeSnapshot; - } - - @Override - public void remove() { - if (null == removeSnapshot) { - throw new IllegalStateException("remove() before next()"); - } - try { - DiskLruCache.this.remove(removeSnapshot.key); - } catch (IOException ignored) { - // 这里没什么用。未能从缓存中删除。这很可能是因为无法更新日志,但是缓存的条目仍然没有了 - Logger.error(ignored); - } finally { - removeSnapshot = null; - } - } - }; - } - - /** - * 快照信息 - */ - public class Snapshot implements Closeable { - private final String key; - private final long sequenceNumber; - private final Source[] sources; - private final long[] lengths; - - Snapshot(String key, long sequenceNumber, Source[] sources, long[] lengths) { - this.key = key; - this.sequenceNumber = sequenceNumber; - this.sources = sources; - this.lengths = lengths; - } - - public String key() { - return key; - } - - public Editor edit() throws IOException { - return DiskLruCache.this.edit(key, sequenceNumber); - } - - public Source getSource(int index) { - return sources[index]; - } - - public long getLength(int index) { - return lengths[index]; - } - - public void close() { - for (Source in : sources) { - IoKit.close(in); - } - } - } - - synchronized void completeEdit(Editor editor, boolean success) throws IOException { - Entry entry = editor.entry; - if (entry.currentEditor != editor) { - throw new IllegalStateException(); - } - - // 如果这个编辑是第一次创建条目,那么每个索引必须有一个值 - if (success && !entry.readable) { - for (int i = 0; i < valueCount; i++) { - if (!editor.written[i]) { - editor.abort(); - throw new IllegalStateException("Newly created entry didn't create value for index " + i); - } - if (!entry.dirtyFiles[i].exists()) { - editor.abort(); - return; - } - } - } - - for (int i = 0; i < valueCount; i++) { - File dirty = entry.dirtyFiles[i]; - if (success) { - if (dirty.exists()) { - File clean = entry.cleanFiles[i]; - if (journalFile.exists()) { - clean.delete(); - if (!dirty.renameTo(clean)) { - throw new IOException("failed to rename " + dirty + " to " + clean); - } - } - long oldLength = entry.lengths[i]; - long newLength = clean.length(); - entry.lengths[i] = newLength; - size = size - oldLength + newLength; - } - } else { - dirty.delete(); - } - } - - redundantOpCount++; - entry.currentEditor = null; - if (entry.readable | success) { - entry.readable = true; - journalWriter.writeUtf8(CLEAN).writeByte(Symbol.C_SPACE); - journalWriter.writeUtf8(entry.key); - entry.writeLengths(journalWriter); - journalWriter.writeByte(Symbol.C_LF); - if (success) { - entry.sequenceNumber = nextSequenceNumber++; - } - } else { - lruEntries.remove(entry.key); - journalWriter.writeUtf8(REMOVE).writeByte(Symbol.C_SPACE); - journalWriter.writeUtf8(entry.key); - journalWriter.writeByte(Symbol.C_LF); - } - journalWriter.flush(); - - if (size > maxSize || journalRebuildRequired()) { - executor.execute(cleanupRunnable); - } - } - - boolean removeEntry(Entry entry) throws IOException { - if (null != entry.currentEditor) { - entry.currentEditor.detach(); - } - - for (int i = 0; i < valueCount; i++) { - entry.cleanFiles[i].delete(); - size -= entry.lengths[i]; - entry.lengths[i] = 0; - } - - redundantOpCount++; - journalWriter.writeUtf8(REMOVE).writeByte(Symbol.C_SPACE).writeUtf8(entry.key).writeByte(Symbol.C_LF); - lruEntries.remove(entry.key); - - if (journalRebuildRequired()) { - executor.execute(cleanupRunnable); - } - - return true; - } - - private final Runnable cleanupRunnable = new Runnable() { - public void run() { - synchronized (DiskLruCache.this) { - if (!initialized | closed) { - return; - } - - try { - trimToSize(); - } catch (IOException ignored) { - mostRecentTrimFailed = true; - } - - try { - if (journalRebuildRequired()) { - rebuildJournal(); - redundantOpCount = 0; - } - } catch (IOException e) { - mostRecentRebuildFailed = true; - journalWriter = IoKit.buffer(IoKit.blackhole()); - } - } - } - }; - /** * 返回名为{@code key}的条目的快照,如果条目不存在,则返回null, * 否则当前无法读取。如果返回一个值,它将被移动到LRU队列的头部 @@ -636,10 +402,10 @@ synchronized Editor edit(String key, long expectedSequenceNumber) throws IOExcep journalWriter.flush(); if (hasJournalErrors) { - return null; + return null; // Don't edit; the journal can't be written. } - if (null == entry) { + if (entry == null) { entry = new Entry(key); lruEntries.put(key, entry); } @@ -648,18 +414,23 @@ synchronized Editor edit(String key, long expectedSequenceNumber) throws IOExcep return editor; } + /** + * Returns the directory where this cache stores its data. + */ public File getDirectory() { return directory; } + /** + * Returns the maximum number of bytes that this cache should use to store its data. + */ public synchronized long getMaxSize() { return maxSize; } /** - * 更改缓存可以存储的最大字节数,并在必要时对作业进行排队,以修剪现有存储 - * - * @param maxSize 最大值 + * Changes the maximum number of bytes the cache can store and queues a job to trim the existing + * store, if necessary. */ public synchronized void setMaxSize(long maxSize) { this.maxSize = maxSize; @@ -668,25 +439,78 @@ public synchronized void setMaxSize(long maxSize) { } } + /** + * Returns the number of bytes currently being used to store the values in this cache. This may be + * greater than the max size if a background deletion is pending. + */ public synchronized long size() throws IOException { initialize(); return size; } - /** - * 关闭缓存并删除其所有存储值。这将删除缓存目录中的所有文件,包括没有由缓存创建的文件 - * - * @throws IOException 异常 - */ - public void delete() throws IOException { - close(); - this.deleteContents(directory); + synchronized void completeEdit(Editor editor, boolean success) throws IOException { + Entry entry = editor.entry; + if (entry.currentEditor != editor) { + throw new IllegalStateException(); + } + + // If this edit is creating the entry for the first time, every index must have a value. + if (success && !entry.readable) { + for (int i = 0; i < valueCount; i++) { + if (!editor.written[i]) { + editor.abort(); + throw new IllegalStateException("Newly created entry didn't create value for index " + i); + } + if (!fileSystem.exists(entry.dirtyFiles[i])) { + editor.abort(); + return; + } + } + } + + for (int i = 0; i < valueCount; i++) { + File dirty = entry.dirtyFiles[i]; + if (success) { + if (fileSystem.exists(dirty)) { + File clean = entry.cleanFiles[i]; + fileSystem.rename(dirty, clean); + long oldLength = entry.lengths[i]; + long newLength = fileSystem.size(clean); + entry.lengths[i] = newLength; + size = size - oldLength + newLength; + } + } else { + fileSystem.delete(dirty); + } + } + + redundantOpCount++; + entry.currentEditor = null; + if (entry.readable | success) { + entry.readable = true; + journalWriter.writeUtf8(CLEAN).writeByte(' '); + journalWriter.writeUtf8(entry.key); + entry.writeLengths(journalWriter); + journalWriter.writeByte('\n'); + if (success) { + entry.sequenceNumber = nextSequenceNumber++; + } + } else { + lruEntries.remove(entry.key); + journalWriter.writeUtf8(REMOVE).writeByte(' '); + journalWriter.writeUtf8(entry.key); + journalWriter.writeByte('\n'); + } + journalWriter.flush(); + + if (size > maxSize || journalRebuildRequired()) { + executor.execute(cleanupRunnable); + } } /** - * 只有当日志的大小减半并至少减少2000个ops时,我们才会重建日志 - * - * @return the true/false + * We only rebuild the journal when it will halve the size of the journal and eliminate at least + * 2000 ops. */ boolean journalRebuildRequired() { final int redundantOpCompactThreshold = 2000; @@ -695,12 +519,10 @@ boolean journalRebuildRequired() { } /** - * 如果{@code key}存在并且可以删除,则删除它。如果当前正在编辑 - * {@code key}的条目,那么编辑将正常完成,但是它的值不会被存储 + * Drops the entry for {@code key} if it exists and can be removed. If the entry for {@code key} + * is currently being edited, that edit will complete normally but its value will not be stored. * - * @param key 缓存key - * @return 如果一个条目被删除,则为真 - * @throws IOException 异常 + * @return true if an entry was removed. */ public synchronized boolean remove(String key) throws IOException { initialize(); @@ -708,29 +530,37 @@ public synchronized boolean remove(String key) throws IOException { checkNotClosed(); validateKey(key); Entry entry = lruEntries.get(key); - if (null == entry) { - return false; - } + if (entry == null) return false; boolean removed = removeEntry(entry); if (removed && size <= maxSize) mostRecentTrimFailed = false; return removed; } - public void deleteContents(File directory) throws IOException { - File[] files = directory.listFiles(); - if (null == files) { - throw new IOException("not a readable directory: " + directory); + boolean removeEntry(Entry entry) throws IOException { + if (entry.currentEditor != null) { + entry.currentEditor.detach(); // Prevent the edit from completing normally. } - for (File file : files) { - if (file.isDirectory()) { - deleteContents(file); - } - if (!file.delete()) { - throw new IOException("failed to delete " + file); - } + + for (int i = 0; i < valueCount; i++) { + fileSystem.delete(entry.cleanFiles[i]); + size -= entry.lengths[i]; + entry.lengths[i] = 0; + } + + redundantOpCount++; + journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(entry.key).writeByte('\n'); + lruEntries.remove(entry.key); + + if (journalRebuildRequired()) { + executor.execute(cleanupRunnable); } + + return true; } + /** + * Returns true if this cache has been closed. + */ public synchronized boolean isClosed() { return closed; } @@ -741,6 +571,9 @@ private synchronized void checkNotClosed() { } } + /** + * Force buffered operations to the filesystem. + */ @Override public synchronized void flush() throws IOException { if (!initialized) return; @@ -750,14 +583,18 @@ public synchronized void flush() throws IOException { journalWriter.flush(); } + /** + * Closes this cache. Stored values will remain on the filesystem. + */ @Override public synchronized void close() throws IOException { if (!initialized || closed) { closed = true; return; } + // Copying for safe iteration. for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) { - if (null != entry.currentEditor) { + if (entry.currentEditor != null) { entry.currentEditor.abort(); } } @@ -775,7 +612,160 @@ void trimToSize() throws IOException { mostRecentTrimFailed = false; } - public class Editor { + /** + * Closes the cache and deletes all of its stored values. This will delete all files in the cache + * directory including files that weren't created by the cache. + */ + public void delete() throws IOException { + close(); + fileSystem.deleteContents(directory); + } + + /** + * Deletes all stored values from the cache. In-flight edits will complete normally but their + * values will not be stored. + */ + public synchronized void evictAll() throws IOException { + initialize(); + // Copying for safe iteration. + for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) { + removeEntry(entry); + } + mostRecentTrimFailed = false; + } + + private void validateKey(String key) { + Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); + if (!matcher.matches()) { + throw new IllegalArgumentException( + "keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\""); + } + } + + /** + * 返回缓存当前项的迭代器。这个迭代器不会抛出{@code ConcurrentModificationException}, + * 调用者必须{@link Snapshot#close}每个由{@link Iterator#next}返回的快照 + * 如果做不到这一点,就会泄漏打开的文件,返回的迭代器支持 {@link Iterator#remove}. + * + * @return 返回迭代器 + * @throws IOException 异常 + */ + public synchronized Iterator snapshots() throws IOException { + initialize(); + return new Iterator<>() { + /** + * 迭代条目的副本以防止并发修改错误 + */ + final Iterator delegate = new ArrayList<>(lruEntries.values()).iterator(); + + /** + * 要从{@link #next}返回的快照。如果还没有计算出来,就是Null + */ + Snapshot nextSnapshot; + + /** + * 要使用{@link #remove}删除的快照。如果删除是非法的,则为Null + */ + Snapshot removeSnapshot; + + @Override + public boolean hasNext() { + if (nextSnapshot != null) return true; + + synchronized (DiskLruCache.this) { + // 如果缓存关闭,则截断迭代器 + if (closed) return false; + + while (delegate.hasNext()) { + Entry entry = delegate.next(); + if (!entry.readable) continue; // Entry during edit. + Snapshot snapshot = entry.snapshot(); + if (snapshot == null) continue; // Evicted since we copied the entries. + nextSnapshot = snapshot; + return true; + } + } + + return false; + } + + @Override + public Snapshot next() { + if (!hasNext()) throw new NoSuchElementException(); + removeSnapshot = nextSnapshot; + nextSnapshot = null; + return removeSnapshot; + } + + @Override + public void remove() { + if (removeSnapshot == null) throw new IllegalStateException("remove() before next()"); + try { + DiskLruCache.this.remove(removeSnapshot.key); + } catch (IOException ignored) { + // 这里没什么用。未能从缓存中删除。这很可能是因为无法更新日志,但是缓存的条目仍然没有了 + } finally { + removeSnapshot = null; + } + } + }; + } + + /** + * 快照信息 + */ + public final class Snapshot implements Closeable { + + private final String key; + private final long sequenceNumber; + private final Source[] sources; + private final long[] lengths; + + Snapshot(String key, long sequenceNumber, Source[] sources, long[] lengths) { + this.key = key; + this.sequenceNumber = sequenceNumber; + this.sources = sources; + this.lengths = lengths; + } + + public String key() { + return key; + } + + /** + * Returns an editor for this snapshot's entry, or null if either the entry has changed since + * this snapshot was created or if another edit is in progress. + */ + public Editor edit() throws IOException { + return DiskLruCache.this.edit(key, sequenceNumber); + } + + /** + * Returns the unbuffered stream with the value for {@code index}. + */ + public Source getSource(int index) { + return sources[index]; + } + + /** + * Returns the byte length of the value for {@code index}. + */ + public long getLength(int index) { + return lengths[index]; + } + + public void close() { + for (Source in : sources) { + IoKit.close(in); + } + } + } + + /** + * Edits the values for an entry. + */ + public final class Editor { + final Entry entry; final boolean[] written; private boolean done; @@ -785,15 +775,29 @@ public class Editor { this.written = (entry.readable) ? null : new boolean[valueCount]; } + /** + * Prevents this editor from completing normally. This is necessary either when the edit causes + * an I/O error, or if the target entry is evicted while this editor is active. In either case + * we delete the editor's created files and prevent new files from being created. Note that once + * an editor has been detached it is possible for another editor to edit the entry. + */ void detach() { if (entry.currentEditor == this) { for (int i = 0; i < valueCount; i++) { - entry.dirtyFiles[i].delete(); + try { + fileSystem.delete(entry.dirtyFiles[i]); + } catch (IOException e) { + // This file is potentially leaked. Not much we can do about that. + } } entry.currentEditor = null; } } + /** + * Returns an unbuffered input stream to read the last committed value, or null if no value has + * been committed. + */ public Source newSource(int index) { synchronized (DiskLruCache.this) { if (done) { @@ -803,13 +807,18 @@ public Source newSource(int index) { return null; } try { - return IoKit.source(entry.cleanFiles[index]); + return fileSystem.source(entry.cleanFiles[index]); } catch (FileNotFoundException e) { return null; } } } + /** + * Returns a new unbuffered output stream to write the value at {@code index}. If the underlying + * output stream encounters errors when writing to the filesystem, this edit will be aborted + * when {@link #commit} is called. The returned output stream does not throw IOExceptions. + */ public Sink newSink(int index) { synchronized (DiskLruCache.this) { if (done) { @@ -824,7 +833,7 @@ public Sink newSink(int index) { File dirtyFile = entry.dirtyFiles[index]; Sink sink; try { - sink = IoKit.sink(dirtyFile); + sink = fileSystem.sink(dirtyFile); } catch (FileNotFoundException e) { return IoKit.blackhole(); } @@ -839,6 +848,10 @@ protected void onException(IOException e) { } } + /** + * Commits this edit so it is visible to readers. This releases the edit lock so another edit + * may be started on the same key. + */ public void commit() throws IOException { synchronized (DiskLruCache.this) { if (done) { @@ -881,6 +894,7 @@ public void abortUnlessCommitted() { } private class Entry { + final String key; /** @@ -890,10 +904,19 @@ private class Entry { final File[] cleanFiles; final File[] dirtyFiles; + /** + * True if this entry has ever been published. + */ boolean readable; + /** + * The ongoing edit or null if this entry is not being edited. + */ Editor currentEditor; + /** + * The sequence number of the most recently committed edit to this entry. + */ long sequenceNumber; Entry(String key) { @@ -914,6 +937,9 @@ private class Entry { } } + /** + * Set lengths using decimal numbers like "10123". + */ void setLengths(String[] strings) throws IOException { if (strings.length != valueCount) { throw invalidLengths(strings); @@ -928,6 +954,9 @@ void setLengths(String[] strings) throws IOException { } } + /** + * Append space-prefixed lengths to {@code writer}. + */ void writeLengths(BufferSink writer) throws IOException { for (long length : lengths) { writer.writeByte(Symbol.C_SPACE).writeDecimalLong(length); @@ -938,6 +967,11 @@ private IOException invalidLengths(String[] strings) throws IOException { throw new IOException("unexpected journal line: " + Arrays.toString(strings)); } + /** + * Returns a snapshot of this entry. This opens all streams eagerly to guarantee that we see a + * single published snapshot. If we opened streams lazily then the streams could come from + * different edits. + */ Snapshot snapshot() { if (!Thread.holdsLock(DiskLruCache.this)) throw new AssertionError(); @@ -945,7 +979,7 @@ Snapshot snapshot() { long[] lengths = this.lengths.clone(); try { for (int i = 0; i < valueCount; i++) { - sources[i] = IoKit.source(cleanFiles[i]); + sources[i] = fileSystem.source(cleanFiles[i]); } return new Snapshot(key, sequenceNumber, sources, lengths); } catch (FileNotFoundException e) { @@ -965,26 +999,31 @@ Snapshot snapshot() { } } - /** - * 从缓存中删除所有存储值,飞行中的编辑将正常完成,但不会存储它们的值 - * - * @throws IOException 异常 - */ - public synchronized void evictAll() throws IOException { - initialize(); - // 为了安全迭代而复制 - for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) { - removeEntry(entry); - } - mostRecentTrimFailed = false; - } + private final Runnable cleanupRunnable = new Runnable() { + public void run() { + synchronized (DiskLruCache.this) { + if (!initialized | closed) { + return; + } - private void validateKey(String key) { - Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); - if (!matcher.matches()) { - throw new IllegalArgumentException( - "keys must match regex [a-z0-9_-]{1,120}: \"" + key + Symbol.DOUBLE_QUOTES); + try { + trimToSize(); + } catch (IOException ignored) { + mostRecentTrimFailed = true; + } + + try { + if (journalRebuildRequired()) { + rebuildJournal(); + redundantOpCount = 0; + } + } catch (IOException e) { + mostRecentRebuildFailed = true; + journalWriter = IoKit.buffer(IoKit.blackhole()); + } + } } - } + }; + } diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/CallServerInterceptor.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/CallServerInterceptor.java index 89b87b71f3..b630712844 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/CallServerInterceptor.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/CallServerInterceptor.java @@ -26,6 +26,7 @@ package org.aoju.bus.http.metric.http; import org.aoju.bus.core.io.sink.BufferSink; +import org.aoju.bus.core.lang.Header; import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.toolkit.IoKit; import org.aoju.bus.http.Builder; @@ -145,8 +146,8 @@ public Response intercept(Chain chain) throws IOException { .build(); } - if ("close".equalsIgnoreCase(response.request().header(org.aoju.bus.core.lang.Header.CONNECTION)) - || "close".equalsIgnoreCase(response.header(org.aoju.bus.core.lang.Header.CONNECTION))) { + if ("close".equalsIgnoreCase(response.request().header(Header.CONNECTION)) + || "close".equalsIgnoreCase(response.header(Header.CONNECTION))) { exchange.noNewExchangesOnConnection(); } diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http1Codec.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http1Codec.java index d6d200ff42..14ca153c44 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http1Codec.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http1Codec.java @@ -125,10 +125,8 @@ public void cancel() { /** * Prepares the HTTP headers and sends them to the server. - *

              * For streaming requests with a body, headers must be prepared before the * output stream has been written to. Otherwise the body would need to be buffered! - *

              * For non-streaming requests with a body, headers must be prepared after the * output stream has been written to and closed. This ensures that the {@code Content-Length} * header field receives the proper value. diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2.java index b31dc21843..c5f78a7574 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2.java @@ -133,15 +133,11 @@ static IOException ioException(String message, Object... args) throws IOExceptio /** * Returns human-readable representation of HTTP/2 frame headers. - *

              * The format is: - * *

                    *   direction streamID length type flags
                    * 
              - *

              * Where direction is {@code <<} for inbound and {@code >>} for outbound. - *

              * For example, the following would indicate a HEAD request sent from the client. *

                    * {@code
              diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Connection.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Connection.java
              index ed5379abdd..ff794bab72 100644
              --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Connection.java
              +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Connection.java
              @@ -25,7 +25,6 @@
                ********************************************************************************/
               package org.aoju.bus.http.metric.http;
               
              -import org.aoju.bus.core.exception.RevisedException;
               import org.aoju.bus.core.io.ByteString;
               import org.aoju.bus.core.io.buffer.Buffer;
               import org.aoju.bus.core.io.sink.BufferSink;
              @@ -248,7 +247,7 @@ private Http2Stream newStream(
                                   shutdown(ErrorCode.REFUSED_STREAM);
                               }
                               if (shutdown) {
              -                    throw new RevisedException();
              +                    throw new IOException();
                               }
                               streamId = nextStreamId;
                               nextStreamId += 2;
              @@ -506,7 +505,7 @@ public void setSettings(Settings settings) throws IOException {
                       synchronized (writer) {
                           synchronized (this) {
                               if (shutdown) {
              -                    throw new RevisedException();
              +                    throw new IOException();
                               }
                               settings.merge(settings);
                           }
              @@ -527,15 +526,12 @@ public synchronized boolean isHealthy(long nowNs) {
                    * HTTP/2 can have both stream timeouts (due to a problem with a single stream) and connection
                    * timeouts (due to a problem with the transport). When a stream times out we don't know whether
                    * the problem impacts just one stream or the entire connection.
              -     * 

              * To differentiate the two cases we ping the server when a stream times out. If the overall * connection is fine the ping will receive a pong; otherwise it won't. - *

              * The deadline to respond to this ping attempts to limit the cost of being wrong. If it is too * long, streams created while we await the pong will reuse broken connections and inevitably * fail. If it is too short, slow connections will be marked as failed and extra TCP and TLS * handshakes will be required. - *

              * The deadline is currently hardcoded. We may make this configurable in the future! */ void sendDegradedPingLater() { @@ -740,7 +736,6 @@ public void onStream(Http2Stream stream) throws IOException { /** * Notification that the connection's peer's settings may have changed. Implementations should * take appropriate action to handle the updated settings. - *

              * It is the implementation's responsibility to handle concurrent calls to this method. A * remote peer that sends multiple settings frames will trigger multiple calls to this method, * and those calls are not necessarily serialized. @@ -750,6 +745,7 @@ public void onSettings(Http2Connection connection) { } class PingRunnable extends NamedRunnable { + final boolean reply; final int payload1; final int payload2; @@ -768,6 +764,7 @@ public void execute() { } class IntervalPingRunnable extends NamedRunnable { + IntervalPingRunnable() { super("Http %s ping", connectionName); } @@ -796,6 +793,7 @@ public void execute() { * async task to do so. */ class ReaderRunnable extends NamedRunnable implements Http2Reader.Handler { + final Http2Reader reader; ReaderRunnable(Http2Reader reader) { diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Stream.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Stream.java index 25ac7fc734..2a32e4f103 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Stream.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Stream.java @@ -118,12 +118,10 @@ public int getId() { /** * Returns true if this stream is open. A stream is open until either: - * *

                *
              • A {@code SYN_RESET} frame abnormally terminates the stream. *
              • Both input and output streams have transmitted all data and headers. *
              - *

              * Note that the input stream may continue to yield data even after a stream reports itself as * not open. This is because input data is buffered. */ @@ -587,6 +585,7 @@ public void close() throws IOException { * A sink that writes outgoing data frames of a stream. This class is not thread safe. */ class FramingSink implements Sink { + private static final long EMIT_BUFFER_SIZE = 16384; /** diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Writer.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Writer.java index 57adf73586..43583e140b 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Writer.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Writer.java @@ -43,7 +43,7 @@ * @author Kimi Liu * @since Java 17+ */ -class Http2Writer implements Closeable { +public class Http2Writer implements Closeable { final Hpack.Writer hpackWriter; private final BufferSink sink; diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/RetryAndFollowUp.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/RetryAndFollowUp.java index 622d54befa..f85750fa3a 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/RetryAndFollowUp.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/RetryAndFollowUp.java @@ -25,7 +25,6 @@ ********************************************************************************/ package org.aoju.bus.http.metric.http; -import org.aoju.bus.core.exception.RevisedException; import org.aoju.bus.core.lang.Header; import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.toolkit.IoKit; @@ -96,7 +95,7 @@ public Response intercept(Chain chain) throws IOException { continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. - boolean requestSendStarted = !(e instanceof RevisedException); + boolean requestSendStarted = !(e instanceof IOException); if (!recover(e, transmitter, requestSendStarted, request)) throw e; continue; } finally { diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/suffix/SuffixDatabase.java b/bus-http/src/main/java/org/aoju/bus/http/metric/suffix/SuffixDatabase.java deleted file mode 100644 index c0498b5f66..0000000000 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/suffix/SuffixDatabase.java +++ /dev/null @@ -1,359 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.http.metric.suffix; - -import org.aoju.bus.core.io.source.BufferSource; -import org.aoju.bus.core.io.source.GzipSource; -import org.aoju.bus.core.lang.Charset; -import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.core.toolkit.IoKit; -import org.aoju.bus.logger.Logger; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InterruptedIOException; -import java.net.IDN; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * 提供的公共后缀数据库 - * publicsuffix.org. - * - * @author Kimi Liu - * @since Java 17+ - */ -public class SuffixDatabase { - - public static final String PUBLIC_SUFFIX_RESOURCE = "suffixes.gz"; - - private static final byte[] WILDCARD_LABEL = new byte[]{Symbol.C_STAR}; - private static final String[] EMPTY_RULE = Normal.EMPTY_STRING_ARRAY; - private static final String[] PREVAILING_RULE = new String[]{Symbol.STAR}; - private static final byte EXCEPTION_MARKER = Symbol.C_NOT; - private static final SuffixDatabase instance = new SuffixDatabase(); - - /** - * True after we've attempted to read the list for the first time. - */ - private final AtomicBoolean listRead = new AtomicBoolean(false); - - /** - * Used for concurrent threads reading the list for the first time. - */ - private final CountDownLatch readCompleteLatch = new CountDownLatch(1); - - // The lists are held as a large array of UTF-8 bytes. This is to avoid allocating lots of strings - // that will likely never be used. Each rule is separated by '\n'. Please see the - // PublicSuffixListGenerator class for how these lists are generated. - // Guarded by this. - private byte[] publicSuffixListBytes; - private byte[] publicSuffixExceptionListBytes; - - public static SuffixDatabase get() { - return instance; - } - - private static String binarySearchBytes(byte[] bytesToSearch, byte[][] labels, int labelIndex) { - int low = 0; - int high = bytesToSearch.length; - String match = null; - while (low < high) { - int mid = (low + high) / 2; - // Search for a '\n' that marks the start of a value. Don't go back past the start of the - // array. - while (mid > -1 && bytesToSearch[mid] != Symbol.C_LF) { - mid--; - } - mid++; - - int end = 1; - while (bytesToSearch[mid + end] != Symbol.C_LF) { - end++; - } - int publicSuffixLength = (mid + end) - mid; - - // Compare the bytes. Note that the file stores UTF-8 encoded bytes, so we must compare the - // unsigned bytes. - int compareResult; - int currentLabelIndex = labelIndex; - int currentLabelByteIndex = 0; - int publicSuffixByteIndex = 0; - - boolean expectDot = false; - while (true) { - int byte0; - if (expectDot) { - byte0 = Symbol.C_DOT; - expectDot = false; - } else { - byte0 = labels[currentLabelIndex][currentLabelByteIndex] & 0xff; - } - - int byte1 = bytesToSearch[mid + publicSuffixByteIndex] & 0xff; - - compareResult = byte0 - byte1; - if (compareResult != 0) break; - - publicSuffixByteIndex++; - currentLabelByteIndex++; - if (publicSuffixByteIndex == publicSuffixLength) break; - - if (labels[currentLabelIndex].length == currentLabelByteIndex) { - // We've exhausted our current label. Either there are more labels to compare, in which - // case we expect a dot as the next character. Otherwise, we've checked all our labels. - if (currentLabelIndex == labels.length - 1) { - break; - } else { - currentLabelIndex++; - currentLabelByteIndex = -1; - expectDot = true; - } - } - } - - if (compareResult < 0) { - high = mid - 1; - } else if (compareResult > 0) { - low = mid + end + 1; - } else { - // We found a match, but are the lengths equal? - int publicSuffixBytesLeft = publicSuffixLength - publicSuffixByteIndex; - int labelBytesLeft = labels[currentLabelIndex].length - currentLabelByteIndex; - for (int i = currentLabelIndex + 1; i < labels.length; i++) { - labelBytesLeft += labels[i].length; - } - - if (labelBytesLeft < publicSuffixBytesLeft) { - high = mid - 1; - } else if (labelBytesLeft > publicSuffixBytesLeft) { - low = mid + end + 1; - } else { - // Found a match. - match = new String(bytesToSearch, mid, publicSuffixLength, Charset.UTF_8); - break; - } - } - } - return match; - } - - /** - * Returns the effective top-level domain plus one (eTLD+1) by referencing the public suffix list. - * Returns null if the domain is a public suffix or a private address. - *

              - * Here are some examples:

              {@code
              -     * assertEquals("google.com", getEffectiveTldPlusOne("google.com"));
              -     * assertEquals("google.com", getEffectiveTldPlusOne("www.google.com"));
              -     * assertNull(getEffectiveTldPlusOne("com"));
              -     * assertNull(getEffectiveTldPlusOne("localhost"));
              -     * assertNull(getEffectiveTldPlusOne("mymacbook"));
              -     * }
              - * - * @param domain A canonicalized domain. An International Domain Name (IDN) should be punycode - * encoded. - */ - public String getEffectiveTldPlusOne(String domain) { - if (null == domain) throw new NullPointerException("domain == null"); - - // We use UTF-8 in the list so we need to convert to Unicode. - String unicodeDomain = IDN.toUnicode(domain); - String[] domainLabels = unicodeDomain.split("\\."); - String[] rule = findMatchingRule(domainLabels); - if (domainLabels.length == rule.length && rule[0].charAt(0) != EXCEPTION_MARKER) { - // The domain is a public suffix. - return null; - } - - int firstLabelOffset; - if (rule[0].charAt(0) == EXCEPTION_MARKER) { - // Exception rules hold the effective TLD plus one. - firstLabelOffset = domainLabels.length - rule.length; - } else { - // Otherwise the rule is for a public suffix, so we must take one more label. - firstLabelOffset = domainLabels.length - (rule.length + 1); - } - - StringBuilder effectiveTldPlusOne = new StringBuilder(); - String[] punycodeLabels = domain.split("\\."); - for (int i = firstLabelOffset; i < punycodeLabels.length; i++) { - effectiveTldPlusOne.append(punycodeLabels[i]).append('.'); - } - effectiveTldPlusOne.deleteCharAt(effectiveTldPlusOne.length() - 1); - - return effectiveTldPlusOne.toString(); - } - - private String[] findMatchingRule(String[] domainLabels) { - if (!listRead.get() && listRead.compareAndSet(false, true)) { - readTheListUninterruptibly(); - } else { - try { - readCompleteLatch.await(); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - } - - synchronized (this) { - if (null == publicSuffixListBytes) { - throw new IllegalStateException("Unable to load " + PUBLIC_SUFFIX_RESOURCE + " resource " - + "from the classpath."); - } - } - - // Break apart the domain into UTF-8 labels, i.e. foo.bar.com turns into [foo, bar, com]. - byte[][] domainLabelsUtf8Bytes = new byte[domainLabels.length][]; - for (int i = 0; i < domainLabels.length; i++) { - domainLabelsUtf8Bytes[i] = domainLabels[i].getBytes(Charset.UTF_8); - } - - // Start by looking for exact matches. We start at the leftmost label. For example, foo.bar.com - // will look like: [foo, bar, com], [bar, com], [com]. The longest matching rule wins. - String exactMatch = null; - for (int i = 0; i < domainLabelsUtf8Bytes.length; i++) { - String rule = binarySearchBytes(publicSuffixListBytes, domainLabelsUtf8Bytes, i); - if (null != rule) { - exactMatch = rule; - break; - } - } - - // In theory, wildcard rules are not restricted to having the wildcard in the leftmost position. - // In practice, wildcards are always in the leftmost position. For now, this implementation - // cheats and does not attempt every possible permutation. Instead, it only considers wildcards - // in the leftmost position. We assert this fact when we generate the public suffix file. If - // this assertion ever fails we'll need to refactor this implementation. - String wildcardMatch = null; - if (domainLabelsUtf8Bytes.length > 1) { - byte[][] labelsWithWildcard = domainLabelsUtf8Bytes.clone(); - for (int labelIndex = 0; labelIndex < labelsWithWildcard.length - 1; labelIndex++) { - labelsWithWildcard[labelIndex] = WILDCARD_LABEL; - String rule = binarySearchBytes(publicSuffixListBytes, labelsWithWildcard, labelIndex); - if (null != rule) { - wildcardMatch = rule; - break; - } - } - } - - // Exception rules only apply to wildcard rules, so only try it if we matched a wildcard. - String exception = null; - if (null != wildcardMatch) { - for (int labelIndex = 0; labelIndex < domainLabelsUtf8Bytes.length - 1; labelIndex++) { - String rule = binarySearchBytes( - publicSuffixExceptionListBytes, domainLabelsUtf8Bytes, labelIndex); - if (null != rule) { - exception = rule; - break; - } - } - } - - if (null != exception) { - exception = Symbol.NOT + exception; - return exception.split("\\."); - } else if (null == exactMatch && null == wildcardMatch) { - return PREVAILING_RULE; - } - - String[] exactRuleLabels = null != exactMatch - ? exactMatch.split("\\.") - : EMPTY_RULE; - - String[] wildcardRuleLabels = null != wildcardMatch - ? wildcardMatch.split("\\.") - : EMPTY_RULE; - - return exactRuleLabels.length > wildcardRuleLabels.length - ? exactRuleLabels - : wildcardRuleLabels; - } - - /** - * Reads the public suffix list treating the operation as uninterruptible. We always want to read - * the list otherwise we'll be left in a bad state. If the thread was interrupted prior to this - * operation, it will be re-interrupted after the list is read. - */ - private void readTheListUninterruptibly() { - boolean interrupted = false; - try { - while (true) { - try { - readTheList(); - return; - } catch (InterruptedIOException e) { - Thread.interrupted(); - interrupted = true; - } catch (IOException e) { - Logger.warn("Failed to read public suffix list", e); - return; - } - } - } finally { - if (interrupted) { - Thread.currentThread().interrupt(); - } - } - } - - private void readTheList() throws IOException { - byte[] publicSuffixListBytes; - byte[] publicSuffixExceptionListBytes; - - InputStream resource = SuffixDatabase.class.getResourceAsStream(PUBLIC_SUFFIX_RESOURCE); - if (null == resource) return; - - try (BufferSource BufferSource = IoKit.buffer(new GzipSource(IoKit.source(resource)))) { - int totalBytes = BufferSource.readInt(); - publicSuffixListBytes = new byte[totalBytes]; - BufferSource.readFully(publicSuffixListBytes); - - int totalExceptionBytes = BufferSource.readInt(); - publicSuffixExceptionListBytes = new byte[totalExceptionBytes]; - BufferSource.readFully(publicSuffixExceptionListBytes); - } - - synchronized (this) { - this.publicSuffixListBytes = publicSuffixListBytes; - this.publicSuffixExceptionListBytes = publicSuffixExceptionListBytes; - } - - readCompleteLatch.countDown(); - } - - /** - * Visible for testing. - */ - void setListBytes(byte[] publicSuffixListBytes, byte[] publicSuffixExceptionListBytes) { - this.publicSuffixListBytes = publicSuffixListBytes; - this.publicSuffixExceptionListBytes = publicSuffixExceptionListBytes; - listRead.set(true); - readCompleteLatch.countDown(); - } - -} diff --git a/bus-http/src/main/java/org/aoju/bus/http/plugin/httpz/HttpBuilder.java b/bus-http/src/main/java/org/aoju/bus/http/plugin/httpz/HttpBuilder.java index 2f2fa90809..28c5cf85a5 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/plugin/httpz/HttpBuilder.java +++ b/bus-http/src/main/java/org/aoju/bus/http/plugin/httpz/HttpBuilder.java @@ -25,7 +25,7 @@ ********************************************************************************/ package org.aoju.bus.http.plugin.httpz; -import org.aoju.bus.core.net.ssl.SSLContextBuilder; +import org.aoju.bus.core.net.tls.SSLContextBuilder; import org.aoju.bus.http.DnsX; import org.aoju.bus.http.Httpd; import org.aoju.bus.http.Httpz; diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/BasicCertificateChainCleaner.java b/bus-http/src/main/java/org/aoju/bus/http/secure/BasicCertificateChainCleaner.java index 34b64a5f36..ab962c2e4e 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/BasicCertificateChainCleaner.java +++ b/bus-http/src/main/java/org/aoju/bus/http/secure/BasicCertificateChainCleaner.java @@ -54,7 +54,6 @@ public BasicCertificateChainCleaner(TrustRootIndex trustRootIndex) { /** * Returns a cleaned chain for {@code chain}. - *

              * This method throws if the complete chain to a trusted CA certificate cannot be constructed. * This is unexpected unless the trust root index in this class has a different trust manager than * what was used to establish {@code chain}. diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/CertificatePinner.java b/bus-http/src/main/java/org/aoju/bus/http/secure/CertificatePinner.java index af8cb2f2e8..64d6fe480b 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/CertificatePinner.java +++ b/bus-http/src/main/java/org/aoju/bus/http/secure/CertificatePinner.java @@ -61,8 +61,7 @@ public class CertificatePinner { /** * Returns the SHA-256 of {@code certificate}'s public key. - *

              - * In Http 3.1.2 and earlier, this returned a SHA-1 hash of the public key. Both types are + * In Http and earlier, this returned a SHA-1 hash of the public key. Both types are * supported, but SHA-256 is preferred. */ public static String pin(Certificate certificate) { diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/Credentials.java b/bus-http/src/main/java/org/aoju/bus/http/secure/Credentials.java index c0e8213e5c..16003332c4 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/Credentials.java +++ b/bus-http/src/main/java/org/aoju/bus/http/secure/Credentials.java @@ -38,6 +38,7 @@ public class Credentials { private Credentials() { + } /** diff --git a/bus-http/src/main/java/org/aoju/bus/http/socket/RealWebSocket.java b/bus-http/src/main/java/org/aoju/bus/http/socket/RealWebSocket.java index 2acbe20109..95dc41f064 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/socket/RealWebSocket.java +++ b/bus-http/src/main/java/org/aoju/bus/http/socket/RealWebSocket.java @@ -228,7 +228,7 @@ public void onResponse(NewCall call, Response response) { // Process all web socket messages. try { - String name = "Httpd WebSocket " + request.url().redact(); + String name = "WebSocket " + request.url().redact(); initReaderAndWriter(name, streams); listener.onOpen(RealWebSocket.this, response); loopReader(); @@ -306,7 +306,7 @@ public void loopReader() throws IOException { * For testing: receive a single frame and return true if there are more frames to read. Invoked * only by the reader thread. */ - boolean processNextFrame() throws IOException { + boolean processNextFrame() { try { reader.processNextFrame(); return receivedCloseCode == -1; diff --git a/bus-http/src/main/java/org/aoju/bus/http/socket/WebSocketReader.java b/bus-http/src/main/java/org/aoju/bus/http/socket/WebSocketReader.java index 48243aaa6b..557372eee7 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/socket/WebSocketReader.java +++ b/bus-http/src/main/java/org/aoju/bus/http/socket/WebSocketReader.java @@ -41,7 +41,7 @@ * @author Kimi Liu * @since Java 17+ */ -class WebSocketReader { +public class WebSocketReader { final boolean isClient; final BufferSource source; diff --git a/bus-http/src/main/java/org/aoju/bus/http/socket/WebSocketWriter.java b/bus-http/src/main/java/org/aoju/bus/http/socket/WebSocketWriter.java index e9d41780ef..32bb8767f8 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/socket/WebSocketWriter.java +++ b/bus-http/src/main/java/org/aoju/bus/http/socket/WebSocketWriter.java @@ -41,7 +41,7 @@ * @author Kimi Liu * @since Java 17+ */ -class WebSocketWriter { +public class WebSocketWriter { final boolean isClient; final Random random; diff --git a/bus-http/src/main/resources/META-INF/suffixes/suffixes.gz b/bus-http/src/main/resources/META-INF/suffixes/suffixes.gz deleted file mode 100644 index e1100442439ad4d4c3bdd36b867de0a85c4d590a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63744 zcmcG%2bW#dl`dL2jH**P$5T1yoO3vpb3T=G&Q+>t8;mj07@J@WHZ~@M!6t+4F$h8u z5+aB!iDz>iy6x-xy2twg@B8*#+Na=lzc*X@}zNPb9QRoII*(4xjr|(wYjpsk@_F5X#DTmW!u#H z(%iiQ&ri?4o#);Xn^_i(;eag5xwZ4HH zR>n8S=9cG{aTFY3^^{R|iR(9KvDn7S^ybdk`jmFiv5k$DiMcVXYMfk|*jk!e-W=QH zQ>W(^r#6i7@Pje3Q{54npgn4GYHU%KoLk0{Q^sra#+|v%*_EwL#kr+9^%&l+MPozl5=YtEz>&D|WaHAx z$l%Hi8nui|=8@7?){Prei;G&kwD4f5Ih;j1zh$%9#q=D!hV3ZlZP2nayR!84gTZar z{%A{1Z>=w5Bk&q7pIm`YwSCM_O>EM_Q%$cdF0SnG*hr(vIr?Iw@Y*6@m;YT!)P-$~ zjjwD^Y2K8iUtZaSv$WW2*`S?X5=~8Q%#Oi(<5MbdI3vQ0KiV!ibxu&Hn70-vwSkr@nv9rFSsY!dN z3Qt8hj@6-UysEXWzr$2l-({?CZK|$;v9bnRt*C6OktgD`RZSJ9=44}aMRP?Rx7%TC z#R;@S>j&p8YU{oAHQF?`PytkRnOa+T#;Vqqt_}>7;ew7XeVJZUXHBxPqQlflZ`F3R z;!%l)L?iuW|MiF8Ia$dcs6f96;i)19svyh#WH18}5)3v8N{<1n?f|Qw z0@JbM;{tslI83(;mqJ}X@LcSMbP)4o_PAvrCh#K=_a*r9M;W4IS!{!jFqMG}X<%th za>0kUn?y+vfSSz& zHvweriR@Z{4C6q#kq+b|fHE7A;N>8-JKeUhzXLr~#=i7{z}CTQ_)zWNai;3*^)MSj z*4)^zT%#g`L3xA$+?DZKE^seXE~CrciW2uB1d$M%-u8cd?coqHRB=hcXW!pAcOTTS z1l{Be?ep)-@WLZ?XMPVg7o@f&6Xok7C;TTw`S?1>G0}S<=&Pt=8NdB|%L!p=@7$hh z=rkR6EX(xS8wQ~?#i(74sB^&98h$*%^=H@QO64#a=zOEJ6nf~Je+Xwl3uj~OA(#lj zTDnMv!#x0ur#lwgNxq$TboDVY#C>u%6feUYzovHuQ6k)zouHJ+z)Qs8rbvbvSa#16 zMR*wtC>a!1V6k8Pu9eLCw9rkhDt!!_<0hu~251X(Mm-#=V6fAN$Lie$E6o#|@}UgC zYq>+gafQ8OX111?#_8ip@B1#LUS1|max4r}p1cTZ^ zS*nP89ZR`&CR!+ndJz3O2NKk@cKz|Or?)ZS1uI`X4Q>Z=|K~FgLI9PXU=g6U1TiFR z_cW|qB^_)#+UA6DvbEZn-%z5O-*%l`1zyU9P9g<^V6{r0KA}?J;MDrn+y45{qp(N0 zsMM;$ARbgh=EGx{8<+m_&<~*S2sZ6w&JeR89bt#M8rWSAU8Nx7B{=^F$YTbbT}qIw zcCQ;m>8ugSKmyizU)(mHz4qt(AH`tqfBfM&F$-TQi)PH-yP{c90uw~4KAMN-&+q^x zAYR8~uo8gfy1Giyz*(9ARoC(uHvf}lM7lQW`pvgbCpBIN*6NA44(6! zKg*ovJ^%RL1K_}+t8;d$QFPVH=;i+47_kZ8?1ZG3ySl*WHTB@GJ#&$|&1g9%=EIhG zIHncgUa-Y`+})Y}7Z|(`Z~R&%wDdJg1&)1 zbww=fhF&nPxzpuZ_K)8)WI*Lg8MClnM$2qdycqBk;o2-x_yjNLu1#Y;CV(#8+&ISq zI%+c{rEphX+ZP@*Bu>W$_WNKYIQt6ZY9rYDH=O=@<0~ znwVB}iZv+177SPh)u;512MyH}zkQD_TfI6q;s=ULNg)K$hZ>$9||aM zUU-~^Fr@s+$Ap2ryee!XdOM~Hpfi2~CQu-29GH!46y?2VyP;&Cc>#RzUtf4gtl*9y zK7>8FprA6^09p6ujVxDx5KzR>F?&9E9?YXqolCtJVFG9yDvg#wDFoBUjVB5f8+b(N z5ceQhtgEVSfZELzn`BA@yL^cWJX|2%T-S0TSi(R|8&l#`VUm`$vwc1oxpqdo<$m-d zX!KWgR1dfg!%T?io~&~93SDkhuCY3R(aM-I5+oPViE*H%L9kAjFCJC-tdgD^p`TV# zz!-Srr5}PXsXU}bPzk{u&w{MSH|PU^i!nQ^17*x?vH~%b0yhYHB!TG` zkcRxv+zr7<3tDI6nEECjCjrD=Hs*IPL$IsNmVh}icLqS#X<*f=Fpp@>0nM92!7ct! ze&VtMI|y%w<2e}}s}#uCT4pGLbrI8784JMN#Ip$;A+knV*=`vmV36_1u!_xKa}kt6 zG#dwKVA3Rl*r!#PIpl~!O3fV zl8^LMwR@EL-fI{#4QlJSc6nuUd}brN0>FiVv^FN(W)WC~SXemaAk{gHnq}P_r5;0%=mo^AYV}SvAB349z zvvAIT&yBfQq$RmN!~V+4_dqIFxG4r~s)ZRZC`v702K?)@&wTPrvGm15E}Y5^h3L02 z^2t%zbrBV->s^dn2#81I`hwxHkM4lx3u4^!nYRws-1g47AA$qZASULizdi6hm)rp{ zy0EIdgAOfa!BohYK7AAYD6kHT?Bv4PGEC=_)0oT52&cb3`2xhH--K<%CYgp0hfhUB zs!T~tYsL)EJ$Y)_s@?|5=eN;WwrB3JR9FTKGw;6|^uKPT>jhVs2cA476YoGt2w)pj z-ocei#GrFt-x>-=ch8!dQe@`bLn6Uh2m)_iJE<^MbYD--_y7-)%H8^pxJ7wF0==NW z-SQ*wUF{oIWyaVkl|fBt1@i#aRtfT^rop%WTQ!MsjSo`kxF(Fa!5{e|H&c84(RiVkkg2H_*ahG^~ki}VTlRR{08 z`0!9EgxWbYK9k!G1?l*R>Rw}9Erf|3Cz6GpcHt#(0Sk+nGp0I?SmTEw>p)CjKoy(} z#v|EYHu=CqkY_uXI!bRDbYM75B=`>K&0ox$Sa`EeVrJjsm2=Dx?qFx0J(tU7Ywr#& z*EkR!TMT*WMRx{WFh*wqZL<|TiH`KPaa|W>)&WwzcA)V|fNL%jDiCz~HizEu>ME4gu=1#PpQi%immPMoU)}#WB)~9dxX#D`oR!Va=N!&7c|)zIP8_t$xW~3n z)4K!1T~!b}Rbh>kf_wJ=15Pok0Y0DUe%kVAnE-b^1gqs~0q%~G%~m?Zxu0<-JPFV>4_31v1Ns;6 z4$=OKl3BgT)4%`9m`pI29b99q_y8SDj-_aQXbDP3Y}?*cIVRXHnItA|MDWMw9)(f_ z$`Avu0lccgXlCjIp`aAdhc9mpi3L24k>c)vqqBE_H@$IBkF6N`nBh57mEc!j7E=v` z&DYefr%^dqn|1=?ZCtMp5#S* z%dUZZm`{VAfhr0ZFpmJUh$I%XK)LA6ALy1vXyGz49@au}r;8?-(>jV6sPiw)+N>;b zSIS{@{<$aBZlSxX#i8>03=j|nidFlG3)u_Bko%@rMjOB)*OId!8dS%y_u-LRb$3u(zYCM26r++$o(3FzC+X-CJ(0GLDLkJEB_vcSa(=jZg8d7zdxN0=sx8 z7^`39T|o;&633`+fDTVHU)g#BL(b@sRdn&MWBjb7h`o2b-djdH0k18$U~ikawtsnLoldCg+afn zFE~)w-+mgZC&SB3%xLEg1t8$Ubzp;*_d{Ix$Gq5^#eEXi-WXZ)#-7eAw`tMYoo~dj zl}Vt(Q?%NVAzsR<=?Po0&8|h6F1-SsA=~EZ{`k@Z;6)%ouLuT)WgtjWj@iWU(!*dX zd&gK@`=B@wupq+0OC)$n(ZRKUrNXG0$-{e?6FdwM+#auB_ZD``Rj* zs$fdG4}dX&3&rTl`V9?^YJT??D0_6KNoHmo_7f8>K8Hc2qgaIVFb!wFCxgOOzlMJ0 zI9U1&+4R8Q?*4&Hr*n(mh7#~dPnBnh7^GKe{}iU&9~UfFgD2EwiS7)+lag4_0Aul| z?_4i~s>-fv(ZCaQO_ajO!b4~iEO+H{9vPTTp0V6E8C{TXSk1e7VxW(I+;;NWl)Phd_-js zd(W^sP=LA@Chku;5SroYGc6FnsE7#`2k?)d^Of28#*;e`jVf+N##~^jdJndU`D=g4 z>7-vV-)pBeJ}*AeU(D?@klYxs86mQjY0^0fmdQm6>P}6&@IYJnbPgwhQ>;|p{MOIK zVDG?WiExg#L?IN0$`Y`xWv0e1+p5EcYCzEK*T8^15#Z`x1Md8YolGg}w?hsBl7MN`>byv(2mpfu(?e zdEUnK2l^ThML8t`TzeT;hfYxC9fA4`lo})5T1YL#67*S&w|NTtx^Wf(AGUpL%aqLq zg_h;DwnJWd0gO&*1Ldk8)z8ZBjZc zkya|d<%0i^Gsj{g0>GI!YRl@tU2?}q&qJhn$wrP>-WmzF3M+lShEL84=LEeD`}U%m5wuX)Ne_GH$D@jew?HqFPY( zo6qt=KyPikYP)oXj`4c6VmVAk-F#AE%b1M(@F|sEtzZ1SkQYyGjCbZ+Pk;GaxmLC0 zNf|{$uoY)77FriUu87%YW4tvEY9T0{e&1c=fxE`Q26^inOvsAK+WDvu<{)>RVu&po zbj&Sb{%2$^g0GH(*`|k#KyVS-)gH$mRi{huBz?eYtLbV_<@c<0#!RP zGO~hTo(_YO8CJlHsm+=yuuU@_KyGcQn~WGZtbz4JaWk$Ab46u1_?1WPz=3wU^H2Bw z5IjLF(PIsjz05K*h;;y%BFjwCvD_S&0CKJ0|2AD8%?l&TxybmGU(x^@Zj8!l?iHZV z1-xq1pZlrb)s5`UJu;$$RCPe(%5*ZI{Ts%+H$tlg1#ln0D5x4#Uo2x!h}ulB180wm zPeLw>oh=Grh6%z%#O5;LfaVbHOoz%3+SCe*+)J}upaLO-)UVm9-@Dm2EfA&{o;jZ3 zIVBf>zxO-4zyIn{eHhc1Hiv;%;Z6+C#0Tt0x`J1L`W)B%qt)6N0&bu`|KL0O4d)-= zr8@X}IN-^c5cXmYXo9rXevZ*t}z=7$~uVA00I}l_W$SY zcgql*O_XH?@Ya*?+LsZ2_IIC&4XB5(Pq41aLz1>eJ3NdmIY8x*48YZb^J3s$9Y4M! z8G^~cL#cy7W9~KwMXu5xpWX=uIEer+$j!}#sEPt(xQuyb@XI$^jo3hzdV>kZ67#z^ z*sTDS1kiRsp&+N_KIUgHyA#jGyL;&2*Fw9MQCg}O%q-I%85CRful+-@OL|R z=d~Zg8sxfLONF_qMOKqgmHVIxf{z%DW+u|UH!nEFDpbCM))F`r0CC|4GdRG0;qK>b z$#1R0*;I~XSZ9B8ZCtAToqz4t*U|{)s!QJj7sLd3BrpSjn%yn7mX>z*cOd$`UxOdf z)F0!`@&lMTK=HLNnV^S$VOb7Cp6LA_cu&oZhO5$_r&kX_%KshoN`IfKAx6Olz~_WN~E}w0@SGfyZ#J zF@n>kW8%%9@3DC_d4__kuA|RxK%3A7n0}V$a!Hal4Px?DL7VOfU%xh*H6D4`MejtL0 zvCG^H2=)R8xb4(7_kzy-FQ2Dp>3UbtahUQi_X5zJ744@EAP!cCxMBL~>{HB@@#qfl z;=E9BAndSenU0JM8TR8o0NZYM_O)H!6F4chgUI}3XnY_un%)G|wqyX#h|UZ(hXq2; z7QAyWL|8DF0m2i;xXEs@^x0`;O3$5Bz4L%A|*LkHLgsav7qYjl`7-x&k?NB8B$%Fw+Heh@moO$-d zKji0m&cNh!@lo*ew+xQ3xt_j_8;Rm+`K%xi;30yp1xioNwe)S8@(nENlVIy(Ei%;Z zDt{apx4Kq(1qkrVU9V-80L&TXo{Cg4firRD=MEWuyKQnoOKi`EDl{1Abb1G9Ug54B z=JfaGW(JzSp9!&{3_b}w8 zo9XymXJ)w_Nbv469P4nZ^-hq1pBYklQ^f{`nvk>+4`%lg$S2cnnMdtm2z@|m>(ICj z9hSW}uEQAK*TK9>P)^<%MAmOvhZ-t_}z5Owljjrm7A@L#l)I zjj>sXWA$UIWI{!I%XH!EfCUu?rdk zjd~d);+-QnQO<mH(Ja{9>651+^>i>50^%RAE zYZSYEqf_w_+$zAA>b9un7?I%PAiC@PePYUjD37wH^X8(#YHhN;;-WlryxOBM;+1nC z?N==l_>4b2^5g10ZqziuP6MzCRyyX?s5z2z(I@6u+3GR~K4U~IEL?8TR))g!EZ;Ru zip6PjbhcIB@ZD|Z|0M!c@Pn9pc@@|#uW zCy*3f3qEN`GUr7l&Zkyj@a}J|nHLTqUfa|Rb_fEPN2)ma%!yL4Y7k#|taR;%2mj@! zpK3pny4M4X4to`7Ms{G9Z?4F=bN5;528Kcq-@U*aFR3R&Zyw@Rd!RuOkUm#P-!Aj1 z2t|Mh#gRkOnt4qHUFf1@uj}>#%JHB#;kM zmC|Z*v?`ABoc-$NUnOXNBQIBsTN^~td5tpu6Iht&B_iBKFj1NQyij?QF=ZWH z%U7uWWnhLl=K9f5C_Vq-ci#FrivU=h_~c>Hg!`x*#pqP2o!JhOmdg+ENX2Jv@vekq zApv}r;JAJGU$1{86@z`<*5p;WGq`rpMECdcT)V@);N4eVaO>UAf;J6enuW4E3FwB= zKClLminh!+m2$|33;660ej)mrE94R=b{34S@|~N&&OA$xGt_J~Zu4M*DKjHu1bz8y zFuUvqhl$p<<cmAn=Y5=Wy`ki0g6XJ+b_5Cnl`(|vW-uKShJHI& zx(>MVBfript~z>wSv3XX0GA$-^&a=repZZE6`&b-gu%2^1=-TG80K( z1E=yt@}8)XK^df~$6@r3dmjW>kCa*Pt-s#$U46h*y!a~_a>F`Ustjj+y2aPqm79H~ z!BNX^b5}rq40DDV%6KLxHa1jGo1g(&D`pO7)LHgZ!RCAA*`hR7;Ub8R2lxb28V!OR zw%B11cgHiTVxZHRD%xNC9L1?ghy%+rFw((>nR*X`06IuM=ybOFx6w}ai~vQ=Kl- zQM~HS9rGe!3Y4e%6I5FkKfpVCK`fCfP&)@Y!l4;@S;I8*`AmE|Td*hM+*6SDBWg+e z5sbX@I>F}>c|FijnY5#*oLYokaJtxtpnf;zs4kMDxk( zptDN;pBsMto$zkvqTu39x(bDhsH65K_=n_S_|+Z1-0*}v^sZd3sNROgumMSw*=$%q zQ>d@W8uFA3!5!2_-CHoQ<%@HS`M^Es7>$IguH)U2tUA|*`Yq+3QU~70_L&X>xDnv> z``-GVXo)d4wt38sVit)vhzX~oWSXw=PfhBgym-4l;T!nTgO&Y@n@c@EtIGC zX&pJt-|eku&pmhMG2C~k-+NMte*Z2E{h3Do6LAd-`rL=?6M|FQONG@?Ri_B;XCV9A zJ!F=9W|2>7zXI!$16BLk6lTCK2;iz!5vr=7v!3!)_l6n`(lM;^fLWqe@=31Qn}lOS9~nCpVfPDk#bMn=Q2rYva8-;;=9P6<>s z@QOJ0Gp39{HU8+CAeGEkut#J$$Sl2~oDPi{5t1M?lRm~Nj$@*@o}gbuYoFG)qBzY& zB>_RrV9elm@1uh}RsV!)%iL~x{RQy!jPxCd0Y4_ zYuU_q4DZ!ZZQTa_%u5^eLs={zKko*Xe)(6QKLlCMK1CPWGN}d3lgMS!>seHQY8lYD z3+?-=Stu@VzX%??1_qel`Tjx_-D)+&HtiAg&XY2+u8yx_$>-5AYVDz;c;3XW1xyUZ zJ3>qc$gmKCm7{&w0c1n`N57OAPZg{QdUD*4^#EjqgGGZ5C%VJ}Znk0Y=Vg49$Mu|* zSE0U5r_ae`r3m3%fbIiwT}uxSo4S~UJuC0L=*YbFXBNjeWr81m_5ELKzXJrF4;T0c zow?;5EzeY>lv8(HczbI)7z1(=cI8dZu1T=8$LBDqTxF7-$JL|RriVFRXw9k;lm!-x z5~TeTI&}KM`@J92w<$7p`8u984y1ZQyy}Rs&?md=9pKBMU_b~+`|V^I20Q)jTaV?b zdW2~c6wfr{b->y`pZNj11AP^`JkGZaPytdN<#oJX2hT20UO9j5JF~`uJxrKD_2(Ys z5(29pNOPld1<%3c0Rlx-K{#6P*UBhE-B?#XzSwq^2}loR8+%wHaXTCU5g;~a0qfnk zn!)CkxO&Xv0KV~Hd>RlKum7c>8AJb7r(H&($QsN_Wxw^PT|UQ+X{AcRAIa4=!R9#}qkSg|t^0##8lrogS;rnd2{eF$*JgMVH^gKI z>P6TsL{HZiXL&6|Va&8)5WGBOQs?xk_QPN5#Tv~k1zc98yQVH`zr$lI0vws|#@%7DcNB>f9>*AX@d*{D%4R1Z&90tMLGR8jnH3V=d;v{FK|NV*A z!1>&)C7H8>VCvBWs=#D|+g^{|`{(nw$XNJ!j!HnWPTbN)`<4 zK@s+$9bCl+G_&~*qo2+O2sj_*)zB~W9^fWoqTHg3<*j=;#djV(>B`kIK$-g-0y(kV zx^)xSKFF>FOvff`)&1k=l|yGSigV+a8bEzJ80Xj7s!n2@P4>MXEmyiQ0RiUdC%+J# zow##I^lxxx3!SUu7#h7N;I#JpN=(E&vAMa;hytEuW{7G+q6Xt3&D|{AUADURE3Zs* z21MuanA`>M@$Di*vmr^*Z+@pC&TWucqmBuUHx{IVm1_}zqT20W?tcn{uYPq`Z7Chm za{4>q+#PFVtt^#d4is|)CeVJ58tR2RgR;u%#?#n@xv4%7`xkR{9W>L@dG%$LV5}X$ zlERIPOfSGWUt|kJtf=6TbfS|CRSq)bpZyGsO^BJd%+q?M@P#2rpAey21igG2J_W&b0Ef^J{<@Hf)0noO$qa5KndaaqYVp`;u`C>Oh~e&OUoi zfkG;?#gX+Zbxr) z_P}uoG=?l=d#Pg$67cJu@%9xp>!WW$8KnyolnE+*p2=`t`8l5ka4UefyA`-2i01{G zxUa3V%m%@c3*@fo%ny1rU<0hghqVFhGP?zl5Er-s!SuGxGNWi0`q8V4;Z8hadAygA zSFvdOXD&SnAviT`L(iCH6v8JgO!l~>c|-a1F|m5HZh_B#DDoSec~Mw>tXmn9{db>d zp2nSjgp>LdZ2~502`C4omRA)9LwPcbFw-+CC~4Gg7HU1n1_Vr&v}fBv`fESWUj&VN z{@^;F6ecR&%=2?aqCjDEnP8lSYIN7fClx+etzQkbGL1>qtlAIry2T6zbZ>CKj3?c363oBR znCu;h!qSpzhMxP20Rdm!zI^SIdBgx-HJ{V<<>N;TirOXHcFWbr!GngufOz(cM&<)M zo?0v&DqRQgF-%TDx<6#k=PGB+Vhp_YtJkSq9&mX34pEZgI~hF{YPyGq2edDfYJv}; zmVf>z_!+Var}v(P0QiF3>lB?SufxwtSD|oW?e{x}!kWdNTVxdvZ5EU|4#v2tLK}B! zW|L{EVZP0kf&uhR3259B#&VfycmMmhe?otJ@_Zcla;nMFU&>$@q0hbnKDK_@8@zCX zK4j6mv7LQ+Js2=GQ!mOVPe9I$Th3NmZ4XO|zq`uqp(QiMs$4pqDxY@Qn~i)LOoFlTvgbmT2-vyzukD(ljg207%%;@rv!qVR9h>ZfeuX3Pe8(zJHe|4 zWMu2>r^;N$mhDfoA?)XXIXbGBR;64sT*jIL5;jD#VF=nFVnD?~mPF&lJa25s-(P#Y zHw@Y;{is8&%68D23r5eYl#pwZORhev;$XH{al}lDYAaQ$!eAO0NZ=`z_Q`*Nmmu-{Q zatQ_<4QV@5^dnR3Aif@a2&6;xfC?B=EqTE1lNsZzU!^NaBf5I-7isEfYq|{z_s6ZV z3bhY@T+HMN1RwwCP?T=9QH)YQegNE5n6Y;H#bzEHuom&&^$dX^ccK+aygr5!0OISt zSBy>&+D9wOP`I0cJz+f4bF4=U;UWqbLPV*4`(aUp??W*7Y~3^L=yY+~mAj}-@9sj- zg)+t)QE^(apYI?+cNSCP7y^p z1A{DjT1`(`v6)R~U%f-FV#Cb^wO#oxI5!ETxCgcHjlzA8WQQFxNd`e~JSK4YSun$> zpTkfXb1(x$cZD(j4j#Mg1AZWYA)ReCc+%gh(4aBD$3cQq23>{1aFl>tF5gAfDaz6; z+Kn*8ax-DPI|3x&dO9N1yHGc*v^H#dPUaMRau;i4)lQ#)GSvju6$?gZDwnxJb!GDN zQ8pmp!u2B6CCu?1tNdfmPFXxH*MNI)s{q}?MzQH&igh+|*IXCqpw*k59bm*xzY4}I z%be}-sDMl?2yuh3pZxQ$9*68}2h(v#zdN%#_lQm9CkCpG(|LjnW(Qj`FxL~nhcU3g ziL@!kFrBVm-ktMk+*;=<%oHHMX3GO&737-;#&#;OX`D}kwxt_HSu$LDY!2%9*%$iu z524KZvAhL*#-gaD>Fj#bAR1NJc7ujw7W|FJm$~oxB9?vog>RQa6mrui5!9PU`+tax z>BP71xz;9GY$`4Fix;$y=yLB-UexVSqCm}{I={rCY^Jnh_4mh$(7t}<*t8~RDaHn)YyIPdcF3;Y%WG5Yw9gsG9>&_%IkmMI@{PMa zctYUxlOn-cTM2IGpJTHDyu;PA(=mwu+QwL3I{yC3O|2MkL)o@}f9CEK1M4z48H{%V zmV8$a6|--e$mw3jh|XTB7fYY2Se~yO6e(um)j7UuhifpX@GhOa!XyKFqMy0PYcAda z#ytrvHKv=OlyT>R+V>uU!Y2Uj`=jbP7>s&bfA}+I4#1No)_^UvOqW2+)G>;lpgs`m z4X7JV0B3yyMIt_X8PZnkQ^Si;lk>#h*--w~%X~86J_MEnDj)+`Kvkd}zToIDJ{e%( z1Wge6d)+c{?!Iyhn>#>PuoJnR{ptMsH$&lTQysQ~`RBwn%FDo*OAF!QD#UvsTv9-v zIYb|UYd>7YAarnxf_7co6io#Q1ADGZ2Qm{`7CqbH;N5v1;LMu?8i5Qx)e02)`FD09 zyq&>(Hpacd^&mJ4VHCtC1KeI0 z7Xz{iQh41W{kX_RdExh906TC=<}@7x^oamZWz4o@$Tq(Q%3J~Cf&~r{PBpWLhxI}?Ld?ZE*@O_4Wf;4@SU#A?@Guu<%eFfZH!rJQT1@i&{R`Y{A6Dz>j z-=}@{b@y6jPj{6}^H$(g4bute;t91+o`>uoV)DKI8kmlhRwf-C3aIF@==*nS;~^G6 z6QGZO3C7F^)qYH%IN1q#5lf8QG+p}+Z6K3fCrfRL?d#kv2_ao7<`_nU0zl1- zZ!R?_5Z!YUr7{?0sWIJ|gdU7n2~Wo;3Fs>~CwiXS21Y< zIXDhKlnGD3hGWUq?)VD&;Xth*b%XIks6 z?gXPp&FqQ5I}eH+)=K>I?=!y3pn!g`WSQIN3EAI(>0(X*?acJ{;yeTW55%jIWU-Cs z@s1eKO3C=d93+r5#hhwuANcBtSH;p^ms&R~DuXKc^2PU`%4O{UQ*4526Jn`Xh9){5 zXc7U=RLS*|A>RT#gcWF}n9t7#2^ypO!^?(4971Kt1ys1$DQ;wbQ2X_A8UO}9*+=w7 z`V3fe0vq7{(INlig$L>GluVChubdpi3_U2|NqNiz8EPMR12ss7!Z!^Am+n4kfPRWM zvJh0sFfCns*mQwL5EHj6m`9DQ)jnl^lx^VC-vz{UuC77@lB3zgw14C2gyPMv*;@eOLSFQ`)$B?dRGOml&qOb2q1NE!)qXe%U#;Htg&@FbQT_ z2CZ#G^=I+WWjZ#wG%)$@?JX&+CSak&g=Gr3Sut>-EiwY%9pZkyL5KhPo1Z|WgH*|| z8SvCJ1*lLc6WyYN-3>6LMv9Wtk<2r4Yf)n9;@w_{V7{Qkt}RQ^{`EJHFL}RtE6i#c zXgZRhr5+_NSx(5>u9W!_WAihd_~vFp(Q%5FuVYBiqTScaCgAadWk4m+_E5@ z&gv3<1ERBJ7<>;D!`h%rUcX6MAOJHc>b>W|ZI{+myXB>UARwK)iv#sDOGpBDh}q&k z-YsBL$}*dojLjGB2B)77K^-h6ft{{(Lu0bq+s>#oR7-Qgm- z0z5!8f7U#Muu{vCw^pgN65h*4W`U00{wX*R!VzHcn2Z?;mF8m5xqYwW00!mOVqr{q zzCKXRFmLhN0gDI$V{XkVxm={SX@;SJ+tNO>d0+c6T8x%Kf*KXlmHOyUAGKcIs;dap z{`a)TQnt90VENNuI(Px=07wKG5)IwXBMh}Gom_bNNBrs#ph%#z7>oDl3RaQYLaz4X z;}`=>L?DV!>J(k)g?z;EN4FIZdU}dcJ|1we&jCX}&?1Oo5U|27KSN*b(L*}otCQ=p zxTD2`3huR?du1v|b9XTyfExhJQKw&nC<$Tc7dW54YCij%3NVH=v8e#-jxOMQo}hSF zSD|P>>@Nlv#!2Pphj&9C2m(t@0h@IX@is&5a8Cm5`)sIeS?os4OwM}PyD|rWB@i3! zzS;=XFs6YZd3ym$WH?yf0>9`J9E)JXG4ZgQ#bETXfTaMOe-Z{{h>`fuu=mGr-+G8ie+HTrTp(9EctEOl#e<%N+Ozr+2s8YeNA=|ysFsLh zkfpYiN9)#6a`yUE=%A5_!6Ye+4_|=*c<#>f4}T&f;oW@ketz#EpG4CB=Nw4pN)vsX z4FcY0SKOV?3Ed16gRKZ~RUHGmK@`=?n!jOo^z&j~0iQl7GTDg_1}w?X=mNnaRLC)? z?C2{J8@Tr6cpH=w&<8(KuamZ(Ro4`Hnb!P7)o_h=Z$GfNU=I^8$9}oW3}H=;hq;0? zyG~+H` zzx;t=`|PO%OLE7<5Taiq3de`J1!K8fWE#Y(ZUq5>`;*|4|G4+r5LS*EG-Q`L0|88c zrmj99gN-3(Q6uPIesKTVD18O+aKQQB7mLDR2u4QeVWvWKD9hyZ9c+#Nyy;eoFpxX9 z<5FUxX9Ek_!@M~KaAMdec-ODZZ7)+y;4{%+9WwNHrgwo^16Z{7`}JZ_Nm*N&U%~xq zFM8*{-0*Fg1&!V7Veo_D2|D-g0M7;nQ;t(Yu`>}^CiZE7rp*O zx*2kZk8}Y6;m%--Y%K!T!4W7Q#KU^A&b0s9mfjI;?WFU4B;fsm;Chs0k6ypAuz6grPdqmb>PXz@>-Scv*@SAUFD^fQA6DDQlH!&>2$t4^S@XT8Ewsa!>bE zo8{hs91$mZM?V+zRtX+{YF4FZl4t)ieLWoNDwpqd*D)J_%fDn1rSI~or^K{c>m`cE z5aiZ=fuqVuv4UUww>zloUU}^S>1_vGp5>VYQlXE9dg4GE7%p^!HO|-?syA|5=}W*c zX1fES90zUeuxCo9W=G6SW*rP;4pSC6!?MbY0JC_{FB3z+E`~PNpCE9knx$!L*enZB z9!b|Hp|DgsQ^9IL%Y*5c9)+9+3Cx}0cF?M-f{x4jpm4YL*-16b%w;2(j!4nkOU9Nb z`+59B?zpwvdaNKLm#TmX9IO0KzcZ5aEXVcQvf|X7KR@ymbOAko@m784wg#2)*Zv)9 zMYlsKpnnD04}-Yyd5lI4Ptph)2EBVXn3+vaha)t9(C4bmsS8v-%vP&NiIoKf^fs!V zR6qH_4ToZ5ov4lijj2w9>fR+X1izMrP`A^-dOmmtoQ;G<0XP}FnU&tm`(u!yl@CY< z1)H*+GvD}*I%`BKcb4Rp!pw^u@yvr5@>y=Igfy43PIIC|-@l!Q2ZHS49X3Ys!AERG zj#h(lqkO6B;JI1)K)DD$RFJwAM=!Pp>K4X*EF>XHvQT>)@A^`e5i zpRxhpR}rJngY>zi&D}+H?kp}<5>rf{AA(BdqAy-#Ltsq-@$Eg-=9`!uWv<)~&MBGY54N# zL(#yspGsyC1B@r&&V%5$2W8~4o5hr%&+W9I)R<)M+R9|Q2RCaK!*UcvN2r#L;i%sZ zgKrh7&sH6<77F(4teh=dNH>hWd6Rz0`5S$czI@Wpcr+j;I)}9bXrEdvYMP0gWh?Wy z)xLMAzIwCFco;ohwU1HWb-*X@!d4Yh95VvRxDVQ>AVP1L80chH_cDb`8kf>q+2F%L z0=_Vun5KKoXa|*|T}-Prp7-OYz*ZPvfc;_Kvt%72AC#S#?MeTwyGCX9t4A^sfmP*Y zsH4i#p?$g#%MVcHqOuqUHn@L(@RgqgsDkZz{Z>crZ8G3!5`sr)1_`oe=5$^Ag=>t4 z-N2kds|Qz~FLX{g(61;`dr5@FK2G};|41|Rl;jy69LSsFGrB>1AI&YNfHN>mKHjB+ zPE4m4-o9P8#qz;G5oj5t4v-3UVvCMu-Z8ljLC1@!t^Isyoy41uRX6dQMIc-WK3 zjZ(>b(%5SW!6d^8?9$uIbnytteN+o`15_Qht)klTJiDMzen86#sJXoue6|b>nCJ&% zHGlErkc?{|7qt1&)37Wav{cfc0_}g8hRR2~_R@(CCMUJXcBovVJYJu$71bq{B_a^qCWg zj#qir0X4CRy{3mMureF}K5sIv*;HNmf!M+`*F`x>|MWW1=wS8w=NwaYEv1C64Yi~! zMo}!8j$SZ;>B8Qg)jE}92$|b~I-wTvg8s$*vmHw;QQ%laEZY_E-pgy?TqcqZsRuEl z+JB4;*)aun>6T3Jlkmr2bfA2rqFJmeNC8d@XRptPY1NI1RUnh#HV*X#Ju*_yZ%tyz zPoj()jtUND5k~WY2|Sg+S^JFaHq{5kR#}z#;cNO8%rL+|bvmtacQOa+K`@UPo27OU z%G!!p>{^NV>W6n$jbQ*}^Fn}P0<;a%eq}*z7DK>|DLhZs7t90aTkRe~0P3krv(Wc* z9Ni!oG!MU>I_-0z6U$kcL)l=0xD3|w5-1eZBu1UHEOZ=k^U9r@#c3C+jBWhc}3kH4T)9i9GiLB)unS z*;ZR`?1sVPaHEm#x%g9=E#+oB80uXfJRQX4R{0ub%>ZfYF?rC>-zzq^eAciaAvNv9 z$0NX|{jLBXTESO@4`}AVnb8)ML4-QZH}&ZjQ99`I{#@0zn$)gkjs<4Vn1zO1V?miM zQ0d@pb#SPF8N{8>$Z#8CJ4_Oi8Sord^%QK6VJu4rpnCzfQh?=C+8(g+$#0+xuw5;J zzWCJ^Gh7Gz+2=oJXFCL`H&Ka9^Q-&wuZkYUO8n>+_HZ4SnG>rPH^{`9+eMB~7D3@T z+SAT`n>_o>LQGr+s=82KgYsB*F=T z`wyeP0p((r8{nzBqKp{+9M(ZK$ZG4u2N}c}?gW_on#%_0kFV~(oo8`*=T1hIn*)Ya zicT%Cc=+gH?tv-7L;}3D5AIl&0r+yQIc82qAzP5I|FHSe{cL^APN0RWH*KlhYG5V4 z{s35qiZWEX-4Mt9M}dHL5ju}^jfdV0%Fu1P;(jFR+t+cs4m(Vd{ZcSiNk$bIz~fnO ziZ0ytYYE6~jb@AFn=zOrIl|!4doC+r{UAZkLVR#g59`xkw?WxeqgBo{R}O=#U_lF} zFH|zLumcJzxH5PD&Uf9#LO-)JTBq>~2<9YPg&(N2WSu$5Gntj=_;%fO2Wm(+fWP|_FyL#Uz&N9pjiTe)M_j7D^2N@?psF2So>@iqnkq# z2L>-Nz-u)#Ks5PioEj5T0vPzu`ipcF2_Z?*elT6-aYZkif~c<0`s!DFX3B*6L1jm=S> z+3Ina2w><1Ie)tO4%QRkqZ^?xp8&4~c5^DuNj>9yr1oD1o%n@V(@?Hl@wJW-%aYW7 z&D0J`Baai)FdUy&KqU@LJuLo|Gv}R#J0O78v?B-6z(cg+icYNv;UXO#I~A3h8(5?4 zF7zkelCXBFVIC4lQ%S^xJ&CvO&SRD`X<4HJ?bqMtox{4BZ*Id(YvvroHZ-vS{^fOG z6Cgj(Noc>I6;OL9wr*F3y{cv}m%FlFop|d-nb=?=0oOkK**|{gn42yIX+Je6)57T^ zK|gmfEBd;KkfR|{bZ+(8_0Y|rkb1D$BfimQ-hY_u*Hp0oI4-YhzxM$Zux3HfOQ~5r zS{;Zd101a8llVlz9K=`8aBV?Pr>=@pz_m}pC!@KjObl0U(kFQI81BsW^j%`q+N7vH z89ZJZh?yT12WGpwU-izzP*mT8225p&EXsDSvz~-L%>BK73)988CUcP)yoj~TE>WZX zTH6#f$N%dOY-m6{=*S7IFyWZ@3kF4ua;H3`)9Vk4B~rEAV+={GIvv~4dQ$Wul;AUW zfcG(dGSA)(&c&{Em2a2L^83AxqA0~=|1@>}2)djaqN2C{F^@x-SP zEvhmMwQ9uND5wXdT(tvLpkDBf7;WaIXkX>Z1lJ$r8q6|t+wzHzJ7zKht99e$5IZGc z@yrmxrQ43Pplwd1d~#i43a_~M6xXp_F&=i|7uM{S6Gp}$nul`q1)IrZR?eGQn0lHWPcwV}{iUaGO*4lkl>uH}=2HC7nXrKJ3kG`QC%b2MKr1^}4 z)0umrVo~qyF^p6;L1S2QSL6cWr6qA}CoK&!yz+DyzdQo2or!MVImLz!Frepd5MB7; zC}gVW)er-WMz9YbD_GRE5oiqGfNp_8w*~AhXYyWvSqwCGC8JwxEHUo_{1F(i! zGzQ_I8*7b`fo4t((ku-U#S_19NIt>UiT`s7nU0^=L4<-ER{NDBOJ5%`VKR ze{7Wik#-1{@zEpKe#Vx&Vs}Z+U?hRQc#*;v^+ELdr?2v>0s4Bi(%kL{8J)3_msf3; zYj~;MXTM-l0@+*Ze3GEda+q|nq&sFXMthSojO;1$)BZ)xhRX|v)ukBm{guJxLT6OA zc+rK7M2l4yIH3J(%}h!%)XcHZehlHY>hA$Dr+T#+dIadg&%qcfn`zGE*??KCf$dwf zNipoZOy439z;p&U#ccrk_eYix44K#*qV;c;H~_J9nPp2LpoK*fefFoEn)7D13O1Y% zhq)m@<(pS)3I-};{E}qO3ZjYI3*$P1F^G8W8>Sj)X&_av$^UO(-{DY~1Ovtf*EN}$Bm{os z_b1Hv-F8BjWC0H8kj`qN9Q zEX2@zQPcVq+Ua0Olw6cq1ElnTF%~AJFWicoY%BufZla5L^i2?;e?t+p^-gYdG7$F7 zcYf#0WyAjTUD1TM7w{03Aa$kag>RJ5aH`xvVh#Z-8{Dkmi$^Q}@r%27J8KJJ=Fx{3e*cV&<1q}+ zSQ)7{u>1-lTeNSSM29;9dSdMM*3hb`hUAnFHpfdC{H&z{OI&KdQA87M(EVMshk1y8 z`s0fx`_MF&J|^F1uW`QuZdMhNBlOVa--tfmjpf0tHUqKp{NvzlS6>6a_A0I7@bSyw z`IW`I13-hk^LsuA+(dDh({xEGxdmv-j51)og0>s9Ff?@K6wd?%OT$_-<-`7zkt56^ zz#TL!;uIQZ5y3k|3g*G)dB$-@G3V&mhGdQzV%!NB7eS!oBfJ}XTPus;s>n0+fP|?T z@ay+5u(%xOMI+5sgf<{4I zJ7E4-?*s1v3F!B%YIN zDcArZ!)jKo5*8llR6&)>uL+0%4Y>kZ|N4!el-HY##qYd&@&3s?Xn0Rcw!U{C_jc)- zd2kY-nY&43+J&V6Xalvd+GIxNvoh>$flV{6n81lD-XSuzIOd{#Q)x~i!@GA(?F;e% zgKrSTi0xHAaDZH!45q#CPo2#heLE2T_MZy^j?&n=%r-!(p$k~TX!;J&z$%z08C@W# zkjA*u=4)gc3reie8e`Yz-g_W6$w~e@i}A%w6VRTE%OTQH@h+VNEhr&}gWOcWQLX~f zyjD#KN00K@tL}Z^n@o2V0xgVnfKRv-8qSrT;zhvZQkeFWKZxqSv;ZI1lRPPB(HYes zfRXAI)_wW@<8()*I_Fn+zJ*=~RiAyIweI>Uu&Z~00sWv&Qw-xJCjyM?8;vy#XduE2 zmItZ?g<550pL=L&Ic9zoJ-Pv)w;FkT9mSgvnflCa?{Y5c0TP&2w$_~?&djBI)*gJB zbPmV&^*Mk1nN2PuLck3%3m6adM;8z6K%;T}4K60Q`hc|6w6HHod!^Glgbm+8ds|_uXX-xbR>Coz=wqvxewkOhmB2 z*t_(C59j%sRDX##9Ey zf4_24rnpRb(V$oF2cx_8;zbXhbp(utT?I8ey3V1w@Fdt6Xw4N2u#%<@O+Bd7y!t0~ z^A#BZ&!?f|=(`!wG2MfBP9Xl;6W~_tsc>~<+E={)aQt81{7t`Sr}np$&@>Oa*1mf> ze59#?$JKs3q`h5|I|Oo$YX1c(9pS2S$5~bTh@zqkEpP53eJ5@VVDfpy#bAn+BjY^G!_mee46#`-m3(_yYEW+nM<~>6?OLD8Oe3x4RSYMj< zvs)Z&A*9#s@v{QJI05|WAC@6N?OQq@KHm`_Qe+BE(p(=hf@WTH;C`TNy?0pH)!x@u zttt+WujW}pY~nVwqgXAm9PiQu%3EunFuo%|$wr1j1xV0{hUVo8%S?4Z`*R<(+!hcx zDs31Vt90#G?c)FVeVQ3yA*Ct%Rs-Uj(E8+SFL#9mSYLeDaT;we?|1WQx?vUssE9Is z`h$3PnjNT4>^Ap0=Xq}rbTLTZf7ny>;8UQ&!I2(|BHp|d+%n$XtI7lh#zCo|3lFb> zd+dX$assJn7z{Wr4S1qRo;)|eNMJ-d(}{FIXj!bX-C$NN&j1+m%fVx$<7}VMO*6M$ ze6bQu#(%tdQU>zE7Tn9@A-gCO`S9r)E`#b~&tru}q5x+p%j6n1Bdj0FyEPE)%D`5f zk{PnKAbWo>4=%_3w#3u;o+?@yl?sVA2?}7n+F`u)! z4U7bp*H!q{x7lMYPRF1Mp^E{}zdrxNcvkl~P-oX)&pc-@1Hm8sgqw&j2K(E|-;46v z_vairIjEgS&6!itr+lZrlmg`tG&~iTeeeomjRkvt;0*}Z#<&n6rG-|!j!t|F-Qy# zpsr0?Mw+0S6+w_eFXri8E^HjT{zheQO!++I?S_$0f52OpT77sL&F9a)_u2xz*}))O zk8CuR(z1uayCwM#c#4h=wk`&X0yRtzRu_mKygYh~$EOE7Y(`j(96^9z+-@lvw<4xW zRkrrFIHo?ZB~3d$(+Nt9R(^o&$*clC`Z-vall3%*x@NhscQ6=t6jY%mvE~SvthAmR zC@zwV`X?Xy2L%I(rtaR4<03kIAvsB}>7(aeF5M3~c@r$DlQ*htDwaWT-r+X%WHS09 zLA1Dz=#WOp(Z`R1GqCDF#mc~S0YT^qhNH9}Sh#Xe_{IQCje2U1DkI<;oLiXe^+;H5PpF`uC&@e((iGlZKTk z&d;M4RqU7{I^B90Tt`RbCv~P|>t7A*eABRRo%=KSE4%25WCPKV+Uv;Ac3--2~*@&iC zhIw}ZQzwuk&FP{z<}R&zw3I441VF1Qgp8~EyZ+}JPiBYGJNetkWrQ{W_JgUtLa|Q; zNZ_ZZzP$Sx88-51$QuLiJ}>Qn%3BxZNL6WCY_^s$K?UOmHyAG$q@i&JZAZDUzx#MD zXE93zmOwSJoHz`DEpc<)dY{V1wncT&{KPMw9b?h!106***FB)D0Fzjfm4{4!L+K=x}2&JnT zKi1kK@;LWUllAc(Xn<5!qwH~8=}lO97|dtiM2Eo-XrBNW3M?$V^1@?aI!bungUFji6uGsOzIMrxF(o zdN zQ2RJ+#+0MA2Bq79O;F3jGH>(zUFX_3GywvxoQzd=@aQNN6?pS@BbQhN*+zlovW6Y= z0t1>s=NMj#UqA6ZG};#v9#4T(M8>xM+E385hJ($B>fpVh=rMg5e!-xvm2*EV+m1|D zIYB4nfNVej58VRNe$D>I$s&l?PlK&ig6RnGW8jv7TuO8$1cn1Zo%Y-c$b|Iy{qFQD z>C(Mm@h)K8Cty_^7nE4p9TO;BaK(q%4(757}a){)H;zo0#F5F`WX`F z&A&g&yrVU}K@3hnx%K>`kT*mXT1wNiCJ0zI))=8)t=ez()#)Q#OAzhFY5~w%f^Z&@ zVa4mMLAE^UZ7?0~EBETE&aYm5G6B66i&bx5B0!xSv$8-fyw@PevPX0lKtK(bxqhc~ z*3r~=i44xjJo~B#_y%Kcrk?EhocsA7mq%5Wc}6Zrnp*6Syh8&DESE+^2h#m~(#L^3 zeZx|m%#37@b$4(;IT6wYHy}fp#(Ls9p8NIS!M46 zQSsT($(wrBf~7NOkAV8S=*HFKV3rN@tUA1mh8T2|Csq2$sEPni&kgZ#J%M(fac{Rv z;~mDAZ=K}56Wo_VuRa0c{YN^tv_IyQNyTdS7CY=z7)>d3i-9L!q{lXuYg1i~Bj`@> z*|x%`DRBO`y^I4LdD?4^a~*o@lXYEH1H}HdVQv$!3>36C#IOpLwtVpbnvg3eQFUHu(6P=EbC+Bd2x7@Xb{Om9-F47JClxtbl7f+?2t7GA_NseJDX zbJb2l_puDoSZV2eNFIhmz*>{`9lY@>7K%48{H;tuKc}@tF5)IGJ_*JEc;}2L!_v^U z#X73AyZk60SlSQd(5@(Li1z<|a)IzRMs?THtp~!??NouH#dwjCpN)X6S(Y;<-$*&w zvVBGek2((mruoPZGhcm;e%KM+?C3TGD^Wlhi^9 zX<3KI4&EZ`V;nHZ$ZVx6v zK_?i{A&v0JeJ8UM?-23MziCg#D$h0xkVm_ ztJO65hU+-5ZUy!~lngny`!5=7k9LW!dhF0^G327|(EFa+wbV=M!V4Mu- z+~0?{&EmN>Ks-{9UG}%)er~NzIRb-ozT8rVGdVD}3O28d$NUlabN4+Py=s;y{aCsM z>)Z{Hl^p7T#u>zP0rp(L=n4x&>kuq`)Qz+_r5pog?0c0RBgA~(y5c?8gLSmT8B5JF6~DP#r#O1 z&J1ZY+o6Td%%Oc4RRBY&o;#Yz``|4!p4+NoSfXYkD6Y+9lHaysJP3GNMxAFWk5T7m z$4G3f>;x~j3ACAV_i=85TnZ`xjR$1cnpGAS~+ zea|{LV^GDRO(tY@q4vtbMDzg{7wtc=nA(S-y_Z2(Fh70Oi9AGWy@obQPgSL&@*Kn+D+oH2+!ziO$%GA#2a^5o!R6}bqZS6rk?@k+y3-ncjY40ql zy!<_g^r(9@8~Wwp+RxO`EoWUw&H!tuN|NDdW_u+w z`m;ZX=E93C3E?alg^80K4hE@{-Tb+0ZWp>hK`e8{1Y+>&NPGLIkP8|+rWljfw1FT( zmmqM1fQmPUbv^GCs7}rz2yg|upj^6Jj4*f5XLoV}T~uw&3p>z-~scM_p1iq@237>2t3_2&n>Nth93~J38z{a$Z-D6o!A- zIGFa||HBZ}!L1H{yyZS=d141+JQM`drKw*F;gJ_pD z6P@bDFbwTs1XBkZ5mDy}IpwNQU3`78#lt5?AE9f5eP}9x<^{OJ+)=Kh!)G#*>1J@@ z%wkV7L8k@PTE!w)3YU47&941l;ep0)!FH>A;k#O}rZeh3Dcu>One~`heYf z6_@L!Z%zKLvp)3a1_O!gH49dgU^S9B``E~q`R$@XV01F-L{O6 zp7ya6iFTfWxd)VixD7$HG`ns76-E)6uZ$Fao(Rgm{K(K?X5t=XZkrUT651}crhC=| zg&=3K#fEoi)-`Djv5@&76%5#IulVpJfW5k~-}~z8Hu?;n*H`aV70qpQXEO9?9)3*S zW_gU51-lJv(x*w5qv@1jP(V>owy};rm;?cCOps-ek1~a*6ME39{e1o+*`x({ znXxDg9H`arisK+19oL^`^rwKhvj)&Q-2!a%g9RToNblgw4f*&37~Ln1>}I10&DQ=v z6jC3T2g2RiF>@VCDZVkQW69dT>2}ba0~vZIcUnoKhJJz(JH=dkUQznlFSNh;jz| z`JFf>OqQ4)9_B(ko7kf(Pp-kFlo zHlY1j+q0uKV!0h7EFlaxLG@Gv1ELDFWb$KWQCGVeYS2kzdI}f}+83;#T^Z!g6J+~s50MdT9 z-BiK&`G2RL6g}@SA);`tER;j`QyYiaQ+e%-jGF)Q+)p5lZ#!$>WaTtCI_now*#OmX znZT3qNI(tEbmhljp)K^XKsms}t{D9$%hAwd^a#AAahHa11YEn*fzKykcbo?7i~*y= z%uy~I@f&t3x*`@-NGF>x1~7~TjNFU@e=bX630ZS3S9_qfQb7Wng*XVapbsAp-$e87 zNoFbV`Q1D!?Z!MYJAw^agKdDWoD7J5|3?sj%<-}@zvIJ^oYe#}SrQ&S17n4F@IIEC z7)Rc|XDga9p>i&p!+1o1qO{lDLGT69g4%BmWMyYJsbq8uffAV6>yD3F7{2%rWY!1m zMF6~k(VAYqaVJ>a2v~r6GHZXCe)e@}-hUI!TG@=j*knr+2zZ+-&l|w&r`EX%9j0&Y zdV&T|W;p<^KMF=WI)U=*qm}{!5B=>tmDSD+-a&dG@rVkHfjoSj4N=HG*Kn%E?QDgIhwGX7+MzHosGo3%GeX>g9DLg?y4=ub3^6zjJP5NlM zDOSXdDYPmuA$k4zw@>+_Wp*0P+yM`|dz44{@Klp4)3q}vm|@Os2zZ2VUU|I|O(P>t zd#_E|AS4FiKv+%C{(IknbnXtLC6&(Rd$q}Kj+{KC~J-f z?gN=YKv*!?ipaE(j!|9W*@qZ)eCW{3G)@TU+=J=GXnN+r05{MS#~ADYmz$60iGW~G zM4C^F^uU>ILtj7r3sKbj4RFVCw$0K94*)f(${@-y+hSKbWyq=xTrHK9a+F@}rmI=9 z16*n%x#QcwA%GuDqIl&xs50${&q(7DNU0?(8fHcp%SI2U(R(oqR$88WoZG6=|^^Nz-LR52!idTGEtOK6#D zb3mc`!MoGk^RjKUsBv1m%6j5V*FA5$>jf{t;;Zj?jBOy3_&* zslKcN+FeB_>&d7(scN8E-T?DY20Nj>J;a@1s-sCH%bw7uKY>1zTc$`??#+N;1l3)7 zU?%&6d$#zTNnrNbFu0P{D%x+1O{9K&rU6R8t6kbdZ_$Vq%@taFE1i`%2E-$4|BHc^ zagj3)b}R+#Fl^{BpgyuCHn58U2e5#Lf%vEZW)Hr1ED;^;tvF^s837;S1eYps6)Q5b z-YYFQR|5DnUFhFrz|v#QT8b*4@3un2oB(zQ7>6oRHW_utly#Xaf}AMUM(f%WOszEU zbEowYJ4ij_-L&nMAMh51pdfA!=rL*UUR;qLI8CC1B`AfOBo@@tw@(++4{LVmi~*Y> zFdd=Y4BBGp^E5!WG>mX{k{;TTqg&~$gB@xK#&Eax%hof?bkb6_jYKuGLG$UI zu04*7mk<{)K>??mLB4K`w4K?}8X5T8sh1&48?AWdd=NKTA|p{9t_)VgAQ-cBiV0e? z;?7|neCAuWn-~r2Z-$o3v^9bWs+MT&+pu=x{il4O8wJrRZVVz|(%JEdQ$r_#IkqdQVh=Jnp<#9XM+lU;~;2@!r}S2hj=&y`}Y%##3ETUAZp`F7>Tru#?zd zX2>X_)pdlcx}hr=55Tb#+UEx8*rJ*7GiTN@jb=D1`dZco`e_}MJ^|)GH5=8>0|3!u zVBCGyaII*Hr2%(gzNoma5bZ=XZ{q~YSIa|8Pf@#VK*uyb9gn6Dw5QZ6SE0!Q325Li zfAKUpjdP6taATB(GQEJ9tR<^s3C!zp?hZ&b7t^sGOj00tin|b4(e*o^7>f?Q4G(oc z?R#lyv&66O{y7@nZIg>>zm-e-&@2EC2k=oCEusx8{fx>8QGwHOtnC5@z$z%#kVy;Z zSTD=|;~nTlwAYlO@$UcdH>@OyGU2Z;pER~XWMKldUok;je-OS5`h1W}ChrFP|L4AY z5{>_IN@N7umP@DTX~B_?ACIFai>lqhP1B6+nwk!^_Q4nXtd%mh2dQ-hRe*rx+;@H> z5;&`i4!U^qn^TW`@E8+-n+cV%FMcJh&O3C8ZrK0RcW6mkTo6O0-g8Zd=(hznQ68ks z7+?vf-jvz%clIxHhud;ZYJ-Ya`f~|Hf4E1QlJjbJ{NuaIv^Upi-$@L;HAwqbFlPt? znsSKO>FD4&&%VGTc}4142vJ!s+7~!>igGO2pRom$O9OuFZI%Rp6(Oc?%&>@-j%V03 z)@Wbfl{^iZ!8p>$Br=w5u(Tz*+B3WjJlc4iTCoeA&t9{QcF5Y-Uf$t|K48mp01igK zxF)V4Fc#G_bpSOUVCeLY2sPY%7AjUCdV&e(q>_)JqH4@W)_5U$0G}lc@F~z72%r_5 z$2>Ne7Qh!TORMvJ+MYg)*#zrYq=R!=)&UP`zWm+yL`XGp;#>)%keS8(P*LRHNmUtW z4aBVhB}_4({_4F=HoeE0?sZGrZ(Bm5bp$F4(8&?SG8*Pnd-i&h3)&439V(!Cj^xx! zCk4$|h_Yh-B=ese(0;RMS^Ms3+lq}`nVZiiOou@_;Oz2N#;MKO{(%hMQF+eMkshBv z_)k^HJyV?B4FXD~0rwvRn;mApIrM-rd<6+UW=C4bU%E7HCc`Nk#2pIMg3?j`nnlmJ z%G&{>OeTPq<&iT$fu{Og2L1vFr~z@`v)nWDv8KiJ;b|2l8O%tACO|&}4O@(esitxo zQn0H_b3yakcj!!e%qNwp%(chp?4KWhZp%ppXbD$}l51=kDVLtN9K=TS-e{O$zHAw$ z+aQ1$qJx7~?b4n5+dZ#|RDecp-veSCh-L;#E5Td>0;I<~Gn-YkF-jPdKsaa~G|Dts zr%eDy?JsVwJ>*KemDSQ(GmMM?-%Br480x}SRP-HLhf@2E4n{=ZVp-4m7a-_p8W`KF zrhB-@Fm>=%eRySNPCW+-xCJ0-l9_KTsG3hkYnyuYd1qHYmQZFjklZslP(H{=ym?3G z^(W9WnWLD8f|<)tI_8P!Fpjn<9_-dzJFSd6dPjTpbbCOK#S0^E-wnNrkTIGsZ>LTj za3LFARWBfQfewBfc;PH>&tL`8`;CmAN^X@F1?XJU#KFsLtkCoN_%871z@AIrb%PMX z4hS#}ee&;U7SFy;4+8EWPeUVp!cY$dF^JxMunrvHJ$ND$sB@Q9eJVgjnQ~!66_nMC z{p!(APBUo6#_ZJQeEhIzJQyfBMh0)XUCSbxJ{bUKBm=Z{+^1(Mmx2b* z{%9B-S}uEu_Ef>v4DrJ2V8FI=tTKuy!L?U;Mh%7g=o$$8@U@%Bzy+Ab0K)5 zw*_$1b|QkWfws9U;MUawJ{;s+fo4*4hV=R7zUYK6ULRbTm^`710fy?s>$v(m_ZG1r zACbum&C)WLsOAhdb6!y*3BU0eT5cgy`{5r17H6hKAxH-|L!~dsbyn0gE5LLUtSbX_ zIYvXl+9cRlpC%SccWLG??x8EF3oe!k!aF1M-pyY_Y&{WSEYVd(ySn)4&*mX}ftpW% zX(QaN_UZ$)3{8L=ICPa)m8e$>)qS{&6Gf^fIWW7Goq5wt<~D=SHU&%_43e780Z^w$ zbW3Y-AtPWL)EZi{?SNKCK34&1_jQ#Bx9;21H7XYL{z$6(>c>Ap_sWahvZ+`fxq6?7 zJT+i34my@u-eEGVy_siRWZ`X#Jf&c$d~bt$aAf)c^sImW~ZCaEJ{BT4G(u*x;kfI z0l7}Pra+gn%X4)(DRnV5btGnT#uB0!qZevKt2$2r@Hp zuh2yR6KQYaTg%EoK{#)pv^YE{{e$JDm-_yrB5;zW8la=1M@IW2c11{*W{ppjt(8q> zhgtM?)yFTDpU_-?QzkP^xK)6*J0haadBhdz9cntD+2Nu6-hWIPU*>@C&+jo4ibV1L zkM{R?ZkBoAzg~I>{MH)>MFSmrM>!Odj)QN&gd*3wh*i&GzC|asoXoZiG^Xa}T4Gsk z7?VDHopnG5qce`9D&I0g$I5o~Q4!qKV?=tDd2vj2WzkfK?x1}c1lRs!b)QoNp=f&D z_8I1M6%A~0S?|X$p8FXz0#`3a|Er4u6 zo!+6<4x@9);;cGVb$Av*DP1(aMLRJP~>EA;!dx zG6T1eGHjzyG^@vjC{jbXy1G(SEc06KY*}v5Ysv6S|Hd@RmAQV>fS)L#C-e1wqb~8` z=CAi4@Nj@~n=&U9TtIEQFlc8vxqvtN-=MbaT7n7F|2&E7q#nFUa5st7Mf)RCZiElI zijHcB-csJ+*dSOf=hG{>A3eOj!yO+6<21k Date: Mon, 14 Nov 2022 13:01:07 +0800 Subject: [PATCH 14/19] bug fix and add method --- .../org/aoju/bus/core/beans/BeanPath.java | 155 ++++++-------- .../java/org/aoju/bus/core/date/Almanac.java | 25 ++- .../org/aoju/bus/core/date/Formatter.java | 20 -- .../java/org/aoju/bus/core/date/Lunar.java | 14 +- .../aoju/bus/core/io/reader/LineReader.java | 138 +++++++++++++ .../bus/core/io/reader/ReaderWrapper.java | 84 ++++++++ .../aoju/bus/core/io/stream/BOMReader.java | 37 ++-- .../java/org/aoju/bus/core/lang/FileType.java | 5 + .../org/aoju/bus/core/loader/JarLoaders.java | 1 - .../java/org/aoju/bus/core/map/ForestMap.java | 64 +++--- .../aoju/bus/core/scanner/ClassScaner.java | 172 ++++++++++------ .../org/aoju/bus/core/text/Placeholder.java | 190 ++++++++++++++++++ .../org/aoju/bus/core/toolkit/ClassKit.java | 94 +++++---- .../org/aoju/bus/core/toolkit/CollKit.java | 20 +- .../org/aoju/bus/core/toolkit/FileKit.java | 21 +- .../java/org/aoju/bus/core/toolkit/IoKit.java | 139 +++++++++++-- .../org/aoju/bus/core/toolkit/XmlKit.java | 3 + .../aoju/bus/http/metric/anget/Browser.java | 2 + .../java/org/aoju/bus/setting/Readers.java | 7 +- .../aoju/bus/setting/magic/Properties.java | 42 +++- 20 files changed, 912 insertions(+), 321 deletions(-) create mode 100755 bus-core/src/main/java/org/aoju/bus/core/io/reader/LineReader.java create mode 100755 bus-core/src/main/java/org/aoju/bus/core/io/reader/ReaderWrapper.java create mode 100644 bus-core/src/main/java/org/aoju/bus/core/text/Placeholder.java diff --git a/bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java index dd5f4ce81c..ca08675057 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java +++ b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java @@ -94,9 +94,9 @@ private static Object getFieldValue(final Object bean, final String expression) return null; } - if (StringKit.contains(expression, Symbol.C_COLON)) { + if (StringKit.contains(expression, ':')) { // [start:end:step] 模式 - final List parts = StringKit.splitTrim(expression, Symbol.C_COLON); + final List parts = StringKit.splitTrim(expression, ':'); final int start = Integer.parseInt(parts.get(0)); final int end = Integer.parseInt(parts.get(1)); int step = 1; @@ -108,8 +108,9 @@ private static Object getFieldValue(final Object bean, final String expression) } else if (ArrayKit.isArray(bean)) { return ArrayKit.sub(bean, start, end, step); } - } else if (StringKit.contains(expression, Symbol.C_COMMA)) { - final List keys = StringKit.splitTrim(expression, Symbol.C_COMMA); + } else if (StringKit.contains(expression, ',')) { + // [num0,num1,num2...]模式或者['key0','key1']模式 + final List keys = StringKit.splitTrim(expression, ','); if (bean instanceof Collection) { return CollKit.getAny((Collection) bean, Convert.convert(int[].class, keys)); } else if (ArrayKit.isArray(bean)) { @@ -117,14 +118,14 @@ private static Object getFieldValue(final Object bean, final String expression) } else { final String[] unWrappedKeys = new String[keys.size()]; for (int i = 0; i < unWrappedKeys.length; i++) { - unWrappedKeys[i] = StringKit.unWrap(keys.get(i), Symbol.C_SINGLE_QUOTE); + unWrappedKeys[i] = StringKit.unWrap(keys.get(i), '\''); } if (bean instanceof Map) { // 只支持String为key的Map - MapKit.getAny((Map) bean, unWrappedKeys); + return MapKit.getAny((Map) bean, unWrappedKeys); } else { final Map map = BeanKit.beanToMap(bean); - MapKit.getAny(map, unWrappedKeys); + return MapKit.getAny(map, unWrappedKeys); } } } else { @@ -135,39 +136,6 @@ private static Object getFieldValue(final Object bean, final String expression) return null; } - /** - * 对于非表达式去除单引号 - * - * @param expression 表达式 - * @return 表达式 - */ - private static String unWrapIfPossible(CharSequence expression) { - if (StringKit.containsAny(expression, " = ", " > ", " < ", " like ", Symbol.COMMA)) { - return expression.toString(); - } - return StringKit.unWrap(expression, Symbol.C_SINGLE_QUOTE); - } - - /** - * 判断path列表中末尾的标记是否为数字 - * - * @param patternParts path列表 - * @return 是否为数字 - */ - private static boolean lastIsNumber(List patternParts) { - return MathKit.isInteger(patternParts.get(patternParts.size() - 1)); - } - - /** - * 获取父级路径列表 - * - * @param patternParts 路径列表 - * @return 父级路径列表 - */ - private static List getParentParts(List patternParts) { - return patternParts.subList(0, patternParts.size() - 1); - } - /** * 获取表达式解析后的分段列表 * @@ -184,7 +152,53 @@ public List getPatternParts() { * @return 值, 如果对应值不存在, 则返回null */ public Object get(final Object bean) { - return get(this.patternParts, bean, false); + return get(this.patternParts, bean); + } + + /** + * 设置表达式指定位置(或filed对应)的值
              + * 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值
              + * 注意: + * + *

              +     * 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
              +     * 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
              +     * 
              + * + * @param bean Bean、Map或List + * @param value 值 + */ + public void set(final Object bean, final Object value) { + Objects.requireNonNull(bean); + + Object subBean = bean, previousBean; + boolean isFirst = true; + String patternPart; + // 尝试找到倒数第二个子对象, 最终需要设置它的字段值 + final int length = patternParts.size() - 1; + for (int i = 0; i < length; i++) { + patternPart = patternParts.get(i); + // 保存当前操作的bean, 以便subBean不存在时, 可以用来填充缺失的子对象 + previousBean = subBean; + // 获取当前对象的子对象 + subBean = getFieldValue(subBean, patternPart); + if (null == subBean) { + // 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作) + if (isFirst && false == this.isStartWith && BeanKit.isMatchName(bean, patternPart, true)) { + subBean = bean; + isFirst = false; + } else { + // 填充缺失的子对象, 根据下一个表达式决定填充的值, 如果是整数(下标)则使用列表, 否则当做Map对象 + subBean = MathKit.isInteger(patternParts.get(i + 1)) ? new ArrayList<>() : new HashMap<>(); + BeanKit.setFieldValue(previousBean, patternPart, subBean); + // 上面setFieldValue中有可能发生对象转换, 因此此处重新获取子对象 + // 欲知详情请自行阅读FieldUtil.setFieldValue(Object, Field, Object) + subBean = BeanKit.getFieldValue(previousBean, patternPart); + } + } + } + // 设置最终的字段值 + BeanKit.setFieldValue(subBean, patternParts.get(length), value); } @Override @@ -195,24 +209,17 @@ public String toString() { /** * 获取Bean中对应表达式的值 * - * @param patternParts 表达式分段列表 - * @param bean Bean对象或Map或List等 - * @param ignoreLast 是否忽略最后一个值,忽略最后一个值则用于set,否则用于read + * @param list 表达式分段列表 + * @param bean Bean对象或Map或List等 * @return 值, 如果对应值不存在, 则返回null */ - private Object get(final List patternParts, final Object bean, final boolean ignoreLast) { - int length = patternParts.size(); - if (ignoreLast) { - length--; - } + private Object get(final List list, final Object bean) { Object subBean = bean; boolean isFirst = true; - String patternPart; - for (int i = 0; i < length; i++) { - patternPart = patternParts.get(i); + for (String patternPart : list) { subBean = getFieldValue(subBean, patternPart); if (null == subBean) { - // 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作) + // 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作) if (isFirst && false == this.isStartWith && BeanKit.isMatchName(bean, patternPart, true)) { subBean = bean; isFirst = false; @@ -293,46 +300,4 @@ private void init(final String expression) { this.patternParts = CollKit.unmodifiable(localPatternParts); } - /** - * 设置表达式指定位置(或filed对应)的值 - * 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值 - * 注意: - * - *
              -     * 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
              -     * 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
              -     * 
              - * - * @param bean Bean、Map或List - * @param value 值 - */ - public void set(final Object bean, final Object value) { - set(bean, this.patternParts, lastIsNumber(this.patternParts), value); - } - - /** - * 设置表达式指定位置(或filed对应)的值 - * 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值 - * 注意: - * - *
              -     * 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
              -     * 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
              -     * 
              - * - * @param bean Bean、Map或List - * @param patternParts 表达式块列表 - * @param value 值 - */ - private void set(Object bean, List patternParts, boolean nextNumberPart, Object value) { - Object subBean = this.get(patternParts, bean, true); - if (null == subBean) { - final List parentParts = getParentParts(patternParts); - this.set(bean, parentParts, lastIsNumber(parentParts), nextNumberPart ? new ArrayList<>() : new HashMap<>()); - //set中有可能做过转换,因此此处重新获取bean - subBean = this.get(patternParts, bean, true); - } - BeanKit.setFieldValue(subBean, patternParts.get(patternParts.size() - 1), value); - } - } diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java b/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java index b5f78c89d1..7fbae3f0f9 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java @@ -4846,22 +4846,27 @@ public static boolean isShortDate(String date) { /** * 检查两个时间段是否有时间重叠 - * 重叠指两个时间段是否有交集 - * - *
                - *
              1. x > b || a > y 无交集
              2. - *
              3. 则有交集的逻辑为 !(x > b || a > y) 根据德摩根公式,可化简为 x <= b && a <= y
              4. - *
              + * 重叠指两个时间段是否有交集,注意此方法时间段重合时如: + *
                + *
              • 此方法未纠正开始时间小于结束时间
              • + *
              • 当realStartTime和realEndTime或startTime和endTime相等时,退化为判断区间是否包含点
              • + *
              • 当realStartTime和realEndTime和startTime和endTime相等时,退化为判断点与点是否相等
              • + *
              + * See 准确的区间关系参考:艾伦区间代数 * * @param realStartTime 第一个时间段的开始时间 * @param realEndTime 第一个时间段的结束时间 * @param startTime 第二个时间段的开始时间 * @param endTime 第二个时间段的结束时间 - * @return true 表示时间有重合 + * @return true 表示时间有重合或包含或相等 */ - public static boolean isOverlap(ChronoLocalDateTime realStartTime, ChronoLocalDateTime realEndTime, - ChronoLocalDateTime startTime, ChronoLocalDateTime endTime) { - return startTime.isBefore(realEndTime) && endTime.isAfter(realStartTime); + public static boolean isOverlap(final Date realStartTime, final Date realEndTime, + final Date startTime, final Date endTime) { + + // x>b||a>y 无交集 + // 则有交集的逻辑为 !(x>b||a>y) + // 根据德摩根公式,可化简为 x<=b && a<=y 即 realStartTime<=endTime && startTime<=realEndTime + return realStartTime.compareTo(endTime) <= 0 && startTime.compareTo(realEndTime) <= 0; } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/Formatter.java b/bus-core/src/main/java/org/aoju/bus/core/date/Formatter.java index c938a2543a..1e7b992581 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/date/Formatter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/Formatter.java @@ -774,26 +774,6 @@ public static String getShotName(TimeUnit unit) { } } - /** - * 检查两个时间段是否有时间重叠 - * 重叠指两个时间段是否有交集 - * - *
                - *
              1. x > b || a > y 无交集
              2. - *
              3. 则有交集的逻辑为 !(x > b || a > y) 根据德摩根公式,可化简为 x <= b && a <= y
              4. - *
              - * - * @param realStartTime 第一个时间段的开始时间 - * @param realEndTime 第一个时间段的结束时间 - * @param startTime 第二个时间段的开始时间 - * @param endTime 第二个时间段的结束时间 - * @return true 表示时间有重合 - */ - public static boolean isOverlap(Date realStartTime, Date realEndTime, - Date startTime, Date endTime) { - return startTime.before(realEndTime) && endTime.after(realStartTime); - } - /** * 标准化日期,默认处理以空格区分的日期时间格式,空格前为日期 * 将以下字符替换为"-" diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java b/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java index eb08e9a3da..dbb406d842 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java @@ -4928,7 +4928,7 @@ public String getLiuYao() { * @return 物候 */ public String getWuHou() { - SolarTerm jieQi = getPrevJieQi(); + SolarTerm jieQi = getPrevJieQi(true); String name = jieQi.getName(); int offset = 0; for (int i = 0, j = Fields.CN_SOLARTERM.length; i < j; i++) { @@ -4937,13 +4937,13 @@ public String getWuHou() { break; } } - Calendar current = Kalendar.calendar(solar.getYear(), solar.getMonth(), solar.getDay()); - Solar startSolar = jieQi.getSolar(); - Calendar start = Kalendar.calendar(startSolar.getYear(), startSolar.getMonth(), startSolar.getDay()); - - int days = Solar.getDays(start, current); - return WU_HOU[offset * 3 + days / 5 % WU_HOU.length]; + int days = Solar.getDays(startSolar.getYear(), startSolar.getMonth(), startSolar.getDay(), solar.getYear(), solar.getMonth(), solar.getDay()); + int index = days / 5; + if (index > 2) { + index = 2; + } + return WU_HOU[(offset * 3 + index) % WU_HOU.length]; } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/reader/LineReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/reader/LineReader.java new file mode 100755 index 0000000000..3aec9cf140 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/reader/LineReader.java @@ -0,0 +1,138 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.reader; + +import org.aoju.bus.core.collection.ComputeIterator; +import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.core.toolkit.CharsKit; +import org.aoju.bus.core.toolkit.IoKit; +import org.aoju.bus.core.toolkit.StringKit; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.Iterator; + +/** + * 行读取器,类似于BufferedInputStream,支持多行转义,规则如下: + *
                + *
              • 支持'\n'和'\r\n'两种换行符,不支持'\r'换行符
              • + *
              • 如果想读取转义符,必须定义为'\\'
              • + *
              • 多行转义后的换行符和空格都会被忽略
              • + *
              + * 读出后就是{@code a=12} + * + * @author Kimi Liu + * @since Java 17+ + */ +public class LineReader extends ReaderWrapper implements Iterable { + + /** + * 构造 + * + * @param in {@link InputStream} + * @param charset 编码 + */ + public LineReader(final InputStream in, final Charset charset) { + this(IoKit.getReader(in, charset)); + } + + /** + * 构造 + * + * @param reader {@link Reader} + */ + public LineReader(final Reader reader) { + super(IoKit.toBuffered(reader)); + } + + /** + * 读取一行 + * + * @return 内容 + * @throws IOException IO异常 + */ + public String readLine() throws IOException { + StringBuilder str = null; + // 换行符前是否为转义符 + boolean precedingBackslash = false; + int c; + while ((c = read()) > 0) { + if (null == str) { + // 只有有字符的情况下才初始化行,否则为行结束 + str = StringKit.builder(1024); + } + if (Symbol.C_BACKSLASH == c) { + // 转义符转义,行尾需要使用'\'时,使用转义符转义,即`\\` + if (false == precedingBackslash) { + // 转义符,添加标识,但是不加入字符 + precedingBackslash = true; + continue; + } else { + precedingBackslash = false; + } + } else { + if (precedingBackslash) { + // 转义模式下,跳过转义符后的所有空白符 + if (CharsKit.isBlankChar(c)) { + continue; + } + // 遇到普通字符,关闭转义 + precedingBackslash = false; + } else if (Symbol.C_LF == c) { + // 非转义状态下,表示行的结束 + // 如果换行符是`\r\n`,删除末尾的`\r` + final int lastIndex = str.length() - 1; + if (lastIndex >= 0 && Symbol.C_CR == str.charAt(lastIndex)) { + str.deleteCharAt(lastIndex); + } + break; + } + } + + str.append((char) c); + } + + return StringKit.toStringOrNull(str); + } + + @Override + public Iterator iterator() { + return new ComputeIterator<>() { + @Override + protected String computeNext() { + try { + return readLine(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + }; + } + +} + diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/reader/ReaderWrapper.java b/bus-core/src/main/java/org/aoju/bus/core/io/reader/ReaderWrapper.java new file mode 100755 index 0000000000..6902e6a387 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/reader/ReaderWrapper.java @@ -0,0 +1,84 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.reader; + +import org.aoju.bus.core.lang.Assert; +import org.aoju.bus.core.lang.function.XWrapper; + +import java.io.IOException; +import java.io.Reader; +import java.nio.CharBuffer; + +/** + * {@link Reader} 包装 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class ReaderWrapper extends Reader implements XWrapper { + + protected final Reader raw; + + /** + * 构造 + * + * @param reader {@link Reader} + */ + public ReaderWrapper(final Reader reader) { + this.raw = Assert.notNull(reader); + } + + @Override + public Reader getRaw() { + return this.raw; + } + + @Override + public int read() throws IOException { + return raw.read(); + } + + @Override + public int read(final CharBuffer target) throws IOException { + return raw.read(target); + } + + @Override + public int read(final char[] cbuf) throws IOException { + return raw.read(cbuf); + } + + @Override + public int read(final char[] buffer, final int off, final int len) throws IOException { + return raw.read(buffer, off, len); + } + + @Override + public void close() throws IOException { + raw.close(); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java index ace4154714..9eb2fe3aee 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java @@ -25,9 +25,13 @@ ********************************************************************************/ package org.aoju.bus.core.io.stream; +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.io.reader.ReaderWrapper; import org.aoju.bus.core.lang.Assert; -import java.io.*; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; /** * 读取带BOM头的流内容的Reader,如果非bom的流或无法识别的编码,则默认UTF-8 @@ -49,9 +53,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class BOMReader extends Reader { - - private InputStreamReader reader; +public class BOMReader extends ReaderWrapper { /** * 构造 @@ -59,22 +61,23 @@ public class BOMReader extends Reader { * @param in 流 */ public BOMReader(final InputStream in) { + super(initReader(in)); + } + + /** + * 初始化为{@link InputStreamReader},将给定流转换为{@link BOMInputStream} + * + * @param in {@link InputStream} + * @return {@link InputStreamReader} + */ + private static InputStreamReader initReader(final InputStream in) { Assert.notNull(in, "InputStream must be not null!"); final BOMInputStream bin = (in instanceof BOMInputStream) ? (BOMInputStream) in : new BOMInputStream(in); try { - this.reader = new InputStreamReader(bin, bin.getCharset()); - } catch (final UnsupportedEncodingException ignore) { + return new InputStreamReader(bin, bin.getCharset()); + } catch (final UnsupportedEncodingException e) { + throw new InternalException(e); } } - @Override - public int read(final char[] buffer, final int off, final int len) throws IOException { - return reader.read(buffer, off, len); - } - - @Override - public void close() throws IOException { - reader.close(); - } - -} +} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java b/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java index 4ec9396cde..c842b69f40 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java @@ -75,6 +75,11 @@ public class FileType { public static final String TYPE_PPTX = ".pptx"; public static final String TYPE_PPS = ".pps"; public static final String TYPE_PPSX = ".ppsx"; + /** + * XML格式 + */ + public static final String TYPE_XML = ".xml"; + /** * psd格式,Photoshop的专用格式Photoshop */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/loader/JarLoaders.java b/bus-core/src/main/java/org/aoju/bus/core/loader/JarLoaders.java index 2eef054598..58964fb722 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/loader/JarLoaders.java +++ b/bus-core/src/main/java/org/aoju/bus/core/loader/JarLoaders.java @@ -110,7 +110,6 @@ public static void loadJar(URLClassLoader loader, File jarFile) throws InternalE try { final Method method = ClassKit.getDeclaredMethod(URLClassLoader.class, "addURL", URL.class); if (null != method) { - method.setAccessible(true); final List jars = loopJar(jarFile); for (File jar : jars) { ReflectKit.invoke(loader, method, jar.toURI().toURL()); diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java index 79bd66bf6a..2d74dc001a 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java @@ -25,10 +25,14 @@ ********************************************************************************/ package org.aoju.bus.core.map; +import org.aoju.bus.core.lang.Optional; import org.aoju.bus.core.toolkit.CollKit; import org.aoju.bus.core.toolkit.ObjectKit; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; @@ -55,7 +59,7 @@ public interface ForestMap extends Map> { * @see #putNode(Object, Object) */ @Override - default TreeEntry put(K key, TreeEntry node) { + default TreeEntry put(final K key, final TreeEntry node) { return putNode(key, node.getValue()); } @@ -65,7 +69,7 @@ default TreeEntry put(K key, TreeEntry node) { * @param treeEntryMap 节点集合 */ @Override - default void putAll(Map> treeEntryMap) { + default void putAll(final Map> treeEntryMap) { if (CollKit.isEmpty(treeEntryMap)) { return; } @@ -89,7 +93,7 @@ default void putAll(Map> treeEntryMap) { * @param ignoreNullNode 是否获取到的key为null的子节点/父节点 */ default > void putAllNode( - C values, Function keyGenerator, Function parentKeyGenerator, boolean ignoreNullNode) { + final C values, final Function keyGenerator, final Function parentKeyGenerator, final boolean ignoreNullNode) { if (CollKit.isEmpty(values)) { return; } @@ -153,7 +157,7 @@ default > void putAllNode( * @param childKey 子节点的key * @param childValue 子节点的值 */ - default void putLinkedNodes(K parentKey, V parentValue, K childKey, V childValue) { + default void putLinkedNodes(final K parentKey, final V parentValue, final K childKey, final V childValue) { putNode(parentKey, parentValue); putNode(childKey, childValue); linkNodes(parentKey, childKey); @@ -178,7 +182,7 @@ default void putLinkedNodes(K parentKey, V parentValue, K childKey, V childValue * @param parentKey 父节点的key * @param childKey 子节点的key */ - default void linkNodes(K parentKey, K childKey) { + default void linkNodes(final K parentKey, final K childKey) { linkNodes(parentKey, childKey, null); } @@ -200,13 +204,13 @@ default void linkNodes(K parentKey, K childKey) { void unlinkNode(K parentKey, K childKey); /** - * 获取指定节点所在树结构的全部树节点
              + * 获取指定节点所在树结构的全部树节点 * 比如:存在 a -> b -> c 的关系,则输入 a/b/c 都将返回 a, b, c * * @param key 指定节点的key * @return 节点 */ - default Set> getTreeNodes(K key) { + default Set> getTreeNodes(final K key) { final TreeEntry target = get(key); if (ObjectKit.isNull(target)) { return Collections.emptySet(); @@ -217,26 +221,26 @@ default Set> getTreeNodes(K key) { } /** - * 获取以指定节点作为叶子节点的树结构,然后获取该树结构的根节点
              + * 获取以指定节点作为叶子节点的树结构,然后获取该树结构的根节点 * 比如:存在 a -> b -> c 的关系,则输入 a/b/c 都将返回 a * * @param key 指定节点的key * @return 节点 */ - default TreeEntry getRootNode(K key) { + default TreeEntry getRootNode(final K key) { return Optional.ofNullable(get(key)) .map(TreeEntry::getRoot) .orElse(null); } /** - * 获取指定节点的直接父节点
              + * 获取指定节点的直接父节点 * 比如:若存在 a -> b -> c 的关系,此时输入 a 将返回 null,输入 b 将返回 a,输入 c 将返回 b * * @param key 指定节点的key * @return 节点 */ - default TreeEntry getDeclaredParentNode(K key) { + default TreeEntry getDeclaredParentNode(final K key) { return Optional.ofNullable(get(key)) .map(TreeEntry::getDeclaredParent) .orElse(null); @@ -249,7 +253,7 @@ default TreeEntry getDeclaredParentNode(K key) { * @param parentKey 指定父节点key * @return 节点 */ - default TreeEntry getParentNode(K key, K parentKey) { + default TreeEntry getParentNode(final K key, final K parentKey) { return Optional.ofNullable(get(key)) .map(t -> t.getParent(parentKey)) .orElse(null); @@ -262,12 +266,24 @@ default TreeEntry getParentNode(K key, K parentKey) { * @param parentKey 指定父节点的key * @return 是否 */ - default boolean containsParentNode(K key, K parentKey) { + default boolean containsParentNode(final K key, final K parentKey) { return Optional.ofNullable(get(key)) .map(m -> m.containsParent(parentKey)) .orElse(false); } + /** + * 获取指定节点的值 + * + * @param key 节点的key + * @return 节点值,若节点不存在,或节点值为null都将返回null + */ + default V getNodeValue(final K key) { + return Optional.ofNullable(get(key)) + .map(TreeEntry::getValue) + .get(); + } + /** * 判断以该父节点作为根节点的树结构中是否具有指定子节点 * @@ -275,24 +291,12 @@ default boolean containsParentNode(K key, K parentKey) { * @param childKey 子节点 * @return 是否 */ - default boolean containsChildNode(K parentKey, K childKey) { + default boolean containsChildNode(final K parentKey, final K childKey) { return Optional.ofNullable(get(parentKey)) .map(m -> m.containsChild(childKey)) .orElse(false); } - /** - * 获取指定节点的值 - * - * @param key 节点的key - * @return 节点值,若节点不存在,或节点值为null都将返回null - */ - default V getNodeValue(K key) { - return Optional.ofNullable(get(key)) - .map(TreeEntry::getValue) - .get(); - } - /** * 获取指定父节点直接关联的子节点 * 比如:若存在 a -> b -> c 的关系,此时输入 b 将返回 c,输入 a 将返回 b @@ -300,7 +304,7 @@ default V getNodeValue(K key) { * @param key key * @return 节点 */ - default Collection> getDeclaredChildNodes(K key) { + default Collection> getDeclaredChildNodes(final K key) { return Optional.ofNullable(get(key)) .map(TreeEntry::getDeclaredChildren) .map(Map::values) @@ -308,13 +312,13 @@ default Collection> getDeclaredChildNodes(K key) { } /** - * 获取指定父节点的全部子节点
              + * 获取指定父节点的全部子节点 * 比如:若存在 a -> b -> c 的关系,此时输入 b 将返回 c,输入 a 将返回 b,c * * @param key key * @return 该节点的全部子节点 */ - default Collection> getChildNodes(K key) { + default Collection> getChildNodes(final K key) { return Optional.ofNullable(get(key)) .map(TreeEntry::getChildren) .map(Map::values) diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/ClassScaner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/ClassScaner.java index d9b1d8ab78..4f8618869b 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/ClassScaner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/ClassScaner.java @@ -76,11 +76,26 @@ public class ClassScaner { * 编码 */ private final java.nio.charset.Charset charset; - private final Set> classes = new HashSet<>(); /** * 是否初始化类 */ private boolean initialize; + /** + * 扫描结果集 + */ + private final Set> classes = new HashSet<>(); + /** + * 获取加载错误的类名列表 + */ + private final Set classesOfLoadError = new HashSet<>(); + /** + * 忽略loadClass时的错误 + */ + private boolean ignoreLoadError = false; + /** + * 类加载器 + */ + private ClassLoader classLoader; /** * 构造,默认UTF-8编码 @@ -125,7 +140,6 @@ public ClassScaner(String packageName, Predicate> predicate, java.nio.c this.charset = charset; } - /** * 扫描该包路径下所有class文件,包括其他加载的jar或者类 * @@ -140,16 +154,16 @@ public static Set> scanAllPackage() { * 如果包路径为 com.abs + A.class 但是输入 abs会产生classNotFoundException * 因为className 应该为 com.abs.A 现在却成为abs.A,此工具类对该异常进行忽略处理 * - * @param packageName 包路径 com | com.xxx + * @param packageName 包路径 com | com. | com.abs | com.abs. * @param classFilter class过滤器,过滤掉不需要的class * @return 类集合 */ - public static Set> scanAllPackage(String packageName, Predicate> classFilter) { + public static Set> scanAllPackage(final String packageName, final Predicate> classFilter) { return new ClassScaner(packageName, classFilter).scan(true); } /** - * 扫描该包路径下所有class文件 + * 扫描classpath下所有class文件,如果classpath下已经有类,不再扫描其他加载的jar或者类 * * @return 类集合 */ @@ -160,24 +174,24 @@ public static Set> scanPackage() { /** * 扫描该包路径下所有class文件 * - * @param packageName 包路径 com | com.xxx + * @param packageName 包路径 com | com. | com.abs | com.abs. * @return 类集合 */ - public static Set> scanPackage(String packageName) { + public static Set> scanPackage(final String packageName) { return scanPackage(packageName, null); } /** - * 扫面包路径下满足class过滤器条件的所有class文件, - * 如果包路径为 com.xxx + A.class 但是输入 abs会产生classNotFoundException - * 因为className 应该为 com.xxx.A 现在却成为xxx.A,此工具类对该异常进行忽略处理 + * 扫描包路径下满足class过滤器条件的所有class文件, + * 如果包路径为 com.abs + A.class 但是输入 abs会产生classNotFoundException + * 因为className 应该为 com.abs.A 现在却成为abs.A,此工具类对该异常进行忽略处理 * - * @param packageName 包路径 com | com.xxx - * @param predicate class过滤器,过滤掉不需要的class + * @param packageName 包路径 com | com. | com.abs | com.abs. + * @param classFilter class过滤器,过滤掉不需要的class * @return 类集合 */ - public static Set> scanPackage(String packageName, Predicate> predicate) { - return new ClassScaner(packageName, predicate).scan(); + public static Set> scanPackage(final String packageName, final Predicate> classFilter) { + return new ClassScaner(packageName, classFilter).scan(); } /** @@ -187,18 +201,19 @@ public static Set> scanPackage(String packageName, Predicate> * @param annotationClass 注解类 * @return 类集合 */ - public static Set> scanAllPackageByAnnotation(String packageName, Class annotationClass) { + public static Set> scanAllPackageByAnnotation(final String packageName, final Class annotationClass) { return scanAllPackage(packageName, clazz -> clazz.isAnnotationPresent(annotationClass)); } /** * 扫描指定包路径下所有包含指定注解的类 + * 如果classpath下已经有类,不再扫描其他加载的jar或者类 * * @param packageName 包路径 * @param annotationClass 注解类 * @return 类集合 */ - public static Set> scanPackageByAnnotation(String packageName, final Class annotationClass) { + public static Set> scanPackageByAnnotation(final String packageName, final Class annotationClass) { return scanPackage(packageName, clazz -> clazz.isAnnotationPresent(annotationClass)); } @@ -209,18 +224,19 @@ public static Set> scanPackageByAnnotation(String packageName, final Cl * @param superClass 父类或接口(不包括) * @return 类集合 */ - public static Set> scanAllPackageBySuper(String packageName, Class superClass) { + public static Set> scanAllPackageBySuper(final String packageName, final Class superClass) { return scanAllPackage(packageName, clazz -> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz)); } /** - * 扫描指定包路径下所有指定类或接口的子类或实现类 + * 扫描指定包路径下所有指定类或接口的子类或实现类,不包括指定父类本身 + * 如果classpath下已经有类,不再扫描其他加载的jar或者类 * * @param packageName 包路径 - * @param superClass 父类或接口 + * @param superClass 父类或接口(不包括) * @return 类集合 */ - public static Set> scanPackageBySuper(String packageName, final Class superClass) { + public static Set> scanPackageBySuper(final String packageName, final Class superClass) { return scanPackage(packageName, clazz -> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz)); } @@ -240,11 +256,15 @@ public Set> scan() { * @param forceScanJavaClassPaths 是否强制扫描其他位于classpath关联jar中的类 * @return 类集合 */ - public Set> scan(boolean forceScanJavaClassPaths) { - for (URL url : FileKit.getUrls(this.packagePath)) { + public Set> scan(final boolean forceScanJavaClassPaths) { + // 多次扫描时,清理上次扫描历史 + this.classes.clear(); + this.classesOfLoadError.clear(); + + for (final URL url : FileKit.getUrls(this.packagePath)) { switch (url.getProtocol()) { case "file": - scanFile(new File(UriKit.decode(url.getFile(), this.charset.name())), null); + scanFile(new File(UriKit.decode(url.getFile(), this.charset)), null); break; case "jar": scanJar(UriKit.getJarFile(url)); @@ -252,6 +272,7 @@ public Set> scan(boolean forceScanJavaClassPaths) { } } + // classpath下未找到,则扫描其他jar包下的类 if (forceScanJavaClassPaths || CollKit.isEmpty(this.classes)) { scanJavaClassPaths(); } @@ -260,9 +281,43 @@ public Set> scan(boolean forceScanJavaClassPaths) { } /** - * 扫描Java指定的ClassPath路径 + * 忽略加载错误扫描后,可以获得之前扫描时加载错误的类名字集合 + * + * @return 加载错误的类名字集合 + */ + public Set getClassesOfLoadError() { + return Collections.unmodifiableSet(this.classesOfLoadError); + } + + /** + * 设置是否忽略所有错误 + * + * @param ignoreLoadError 是否忽略错误 + */ + public void setIgnoreLoadError(final boolean ignoreLoadError) { + this.ignoreLoadError = ignoreLoadError; + } + + /** + * 设置是否在扫描到类时初始化类 + * + * @param initialize 是否初始化类 + */ + public void setInitialize(final boolean initialize) { + this.initialize = initialize; + } + + /** + * 设置自定义的类加载器 * - * @return 扫描到的类 + * @param classLoader 类加载器 + */ + public void setClassLoader(final ClassLoader classLoader) { + this.classLoader = classLoader; + } + + /** + * 扫描Java指定的ClassPath路径 */ private void scanJavaClassPaths() { final String[] javaClassPaths = ClassKit.getJavaClassPaths(); @@ -280,7 +335,7 @@ private void scanJavaClassPaths() { * @param file 文件或目录 * @param rootDir 包名对应classpath绝对路径 */ - private void scanFile(File file, String rootDir) { + private void scanFile(final File file, final String rootDir) { if (file.isFile()) { final String fileName = file.getAbsolutePath(); if (fileName.endsWith(FileType.CLASS)) { @@ -300,7 +355,7 @@ private void scanFile(File file, String rootDir) { } else if (file.isDirectory()) { final File[] files = file.listFiles(); if (null != files) { - for (File subFile : files) { + for (final File subFile : files) { scanFile(subFile, (null == rootDir) ? subPathBeforePackage(file) : rootDir); } } @@ -312,13 +367,13 @@ private void scanFile(File file, String rootDir) { * * @param jar jar包 */ - private void scanJar(JarFile jar) { + private void scanJar(final JarFile jar) { String name; - for (JarEntry entry : new EnumerationIterator<>(jar.entries())) { + for (final JarEntry entry : new EnumerationIterator<>(jar.entries())) { name = StringKit.removePrefix(entry.getName(), Symbol.SLASH); if (StringKit.isEmpty(packagePath) || name.startsWith(this.packagePath)) { if (name.endsWith(FileType.CLASS) && false == entry.isDirectory()) { - final String className = name + final String className = name// .substring(0, name.length() - 6) .replace(Symbol.C_SLASH, Symbol.C_DOT); addIfAccept(loadClass(className)); @@ -327,55 +382,57 @@ private void scanJar(JarFile jar) { } } - /** - * 设置是否在扫描到类时初始化类 - * - * @param initialize 是否初始化类 - */ - public void setInitialize(boolean initialize) { - this.initialize = initialize; - } - /** * 加载类 * * @param className 类名 * @return 加载的类 */ - private Class loadClass(String className) { + protected Class loadClass(final String className) { + ClassLoader loader = this.classLoader; + if (null == loader) { + loader = ClassKit.getClassLoader(); + this.classLoader = loader; + } + Class clazz = null; try { - clazz = Class.forName(className, this.initialize, ClassKit.getClassLoader()); - } catch (NoClassDefFoundError e) { - // 由于依赖库导致的类无法加载,直接跳过此类 - } catch (UnsupportedClassVersionError | ClassNotFoundException ee) { - // 版本导致的不兼容的类,跳过 - } catch (Exception e) { - throw new RuntimeException(e); + clazz = Class.forName(className, this.initialize, loader); + } catch (final NoClassDefFoundError | ClassNotFoundException e) { + // 由于依赖库导致的类无法加载,直接跳过此类 + } catch (final UnsupportedClassVersionError e) { + // 版本导致的不兼容的类,跳过 + } catch (final Exception e) { + classesOfLoadError.add(className); + } catch (final Throwable e) { + if (false == this.ignoreLoadError) { + throw new RuntimeException(e); + } else { + classesOfLoadError.add(className); + } } return clazz; } /** - * 通过过滤器,是否满足接受此类的条件 + * 通过过滤器,是否满足接受此类的条件 * - * @param className 类 - * @return 是否接受 + * @param className 类名 */ - private void addIfAccept(String className) { + private void addIfAccept(final String className) { if (StringKit.isBlank(className)) { return; } - int classLen = className.length(); - int packageLen = this.packageName.length(); + final int classLen = className.length(); + final int packageLen = this.packageName.length(); if (classLen == packageLen) { //类名和包名长度一致,用户可能传入的包名是类名 if (className.equals(this.packageName)) { addIfAccept(loadClass(className)); } } else if (classLen > packageLen) { - //检查类名是否以指定包名为前缀,包名后加. - if (className.startsWith(this.packageNameWithDot)) { + //检查类名是否以指定包名为前缀,包名后加.(org.aoju.c和org.aoju.b这类类名引起的歧义) + if (Symbol.DOT.equals(this.packageNameWithDot) || className.startsWith(this.packageNameWithDot)) { addIfAccept(loadClass(className)); } } @@ -385,9 +442,8 @@ private void addIfAccept(String className) { * 通过过滤器,是否满足接受此类的条件 * * @param clazz 类 - * @return 是否接受 */ - private void addIfAccept(Class clazz) { + private void addIfAccept(final Class clazz) { if (null != clazz) { Predicate> classFilter = this.predicate; if (null == classFilter || classFilter.test(clazz)) { @@ -402,7 +458,7 @@ private void addIfAccept(Class clazz) { * @param file 文件 * @return 包名之前的部分 */ - private String subPathBeforePackage(File file) { + private String subPathBeforePackage(final File file) { String filePath = file.getAbsolutePath(); if (StringKit.isNotEmpty(this.packageDirName)) { filePath = StringKit.subBefore(filePath, this.packageDirName, true); diff --git a/bus-core/src/main/java/org/aoju/bus/core/text/Placeholder.java b/bus-core/src/main/java/org/aoju/bus/core/text/Placeholder.java new file mode 100644 index 0000000000..145f71b091 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/text/Placeholder.java @@ -0,0 +1,190 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.text; + +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.lang.Assert; +import org.aoju.bus.core.lang.Normal; +import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.core.toolkit.StringKit; + +import java.util.Objects; +import java.util.function.UnaryOperator; + +/** + * 简单的占位符解析器给定占位符的左右边界符号以及转义符, + * 将允许把一段字符串中的占位符解析并替换为指定内容,支持指定转义符对边界符号进行转义 + * 比如: + *
              + *   {@code
              + *     String text = "select * from #[tableName] where id = #[id]";
              + *     PlaceholderParser parser = new PlaceholderParser(str -> "?", "#[", "]");
              + *     parser.apply(text); // = select * from ? where id = ?
              + *   }
              + * 
              + * + * @author Kimi Liu + * @since Java 17+ + */ +public class Placeholder implements UnaryOperator { + + /** + * processor + */ + private final UnaryOperator processor; + + /** + * 占位符开始符号 + */ + private final String open; + + /** + * 结束符号长度 + */ + private final int openLength; + + /** + * 占位符结束符号 + */ + private final String close; + + /** + * 结束符号长度 + */ + private final int closeLength; + + /** + * 转义符 + */ + private final char escape; + + /** + * 创建一个占位符解析器,默认转义符为{@code "\"} + * + * @param processor 占位符处理器 + * @param prefix 占位符开始符号,不允许为空 + * @param suffix 占位符结束符号,不允许为空 + */ + public Placeholder( + final UnaryOperator processor, final String prefix, final String suffix) { + this(processor, prefix, suffix, Symbol.C_BACKSLASH); + } + + /** + * 创建一个占位符解析器 + * + * @param processor 占位符处理器 + * @param prefix 占位符开始符号,不允许为空 + * @param suffix 占位符结束符号,不允许为空 + * @param escape 转义符 + */ + public Placeholder( + final UnaryOperator processor, final String prefix, final String suffix, final char escape) { + Assert.isFalse(StringKit.isEmpty(prefix), "开始符号不能为空"); + Assert.isFalse(StringKit.isEmpty(suffix), "结束符号不能为空"); + this.processor = Objects.requireNonNull(processor); + this.open = prefix; + this.openLength = prefix.length(); + this.close = suffix; + this.closeLength = suffix.length(); + this.escape = escape; + } + + /** + * 解析并替换字符串中的占位符 + * + * @param text 待解析的字符串 + * @return 处理后的字符串 + */ + @Override + public String apply(final String text) { + if (StringKit.isEmpty(text)) { + return Normal.EMPTY; + } + + // 寻找第一个开始符号 + int closeCursor = 0; + int openCursor = text.indexOf(open, closeCursor); + if (openCursor == -1) { + return text; + } + + // 开始匹配 + final char[] src = text.toCharArray(); + final StringBuilder result = new StringBuilder(src.length); + final StringBuilder expression = new StringBuilder(); + while (openCursor > -1) { + + // 开始符号是否被转义,若是则跳过并寻找下一个开始符号 + if (openCursor > 0 && src[openCursor - 1] == escape) { + result.append(src, closeCursor, openCursor - closeCursor - 1).append(open); + closeCursor = openCursor + openLength; + openCursor = text.indexOf(open, closeCursor); + continue; + } + + // 记录当前位符的开始符号与上一占位符的结束符号间的字符串 + result.append(src, closeCursor, openCursor - closeCursor); + // 重置结束游标至当前占位符的开始处 + closeCursor = openCursor + openLength; + // 寻找结束符号下标 + int end = text.indexOf(close, closeCursor); + while (end > -1) { + // 结束符号被转义,寻找下一个结束符号 + if (end > closeCursor && src[end - 1] == escape) { + expression.append(src, closeCursor, end - closeCursor - 1).append(close); + closeCursor = end + closeLength; + end = text.indexOf(close, closeCursor); + } + // 找到结束符号 + else { + expression.append(src, closeCursor, end - closeCursor); + break; + } + } + + // 未能找到结束符号,说明匹配异常 + if (end == -1) { + throw new InternalException("\"{}\" 中字符下标 {} 处的开始符没有找到对应的结束符", text, openCursor); + } + // 找到结束符号,将开始到结束符号之间的字符串替换为指定表达式 + else { + result.append(processor.apply(expression.toString())); + expression.setLength(0); + // 完成当前占位符的处理匹配,寻找下一个 + closeCursor = end + close.length(); + } + // 寻找下一个开始符号 + openCursor = text.indexOf(open, closeCursor); + } + // 若匹配结束后仍有未处理的字符串,则直接将其拼接到表达式上 + if (closeCursor < src.length) { + result.append(src, closeCursor, src.length - closeCursor); + } + return result.toString(); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java index 7a42a6921e..9c887d49e3 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/ClassKit.java @@ -38,9 +38,7 @@ import org.aoju.bus.core.lang.Symbol; import org.aoju.bus.core.lang.System; import org.aoju.bus.core.lang.mutable.MutableObject; -import org.aoju.bus.core.lang.tuple.Pair; import org.aoju.bus.core.loader.JarLoaders; -import org.aoju.bus.core.map.WeakMap; import javax.tools.*; import java.beans.IntrospectionException; @@ -61,7 +59,8 @@ import java.util.jar.Manifest; /** - * Class工具类 + * Class工具类 {@link ClassLoader} + * 此工具类加载的类,不提供缓存,缓存应由实现的ClassLoader完成 * * @author Kimi Liu * @since Java 17+ @@ -92,10 +91,6 @@ public class ClassKit { Byte.TYPE, Short.TYPE, Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE }; - /** - * 类缓存信息 - */ - private static final WeakMap, Class> CLASS_CACHE = new WeakMap<>(); static { List> primitiveTypes = new ArrayList<>(Normal._32); @@ -773,7 +768,7 @@ public static boolean isStatic(Method method) { * @return 方法 */ public static Method setAccessible(Method method) { - if (null != method && false == method.isAccessible()) { + if (null != method && false == method.canAccess(method)) { method.setAccessible(true); } return method; @@ -1082,7 +1077,7 @@ public static Class loadClass(String name) throws InternalException { * @throws InternalException 没有类名对应的类时抛出此异常 */ public static Class loadClass(String name, boolean isInitialized) throws InternalException { - return (Class) loadClass(name, null, isInitialized); + return (Class) loadClass(name, isInitialized, null); } /** @@ -1098,12 +1093,12 @@ public static Class loadClass(String name, boolean isInitialized) throws *
              * * @param name 类名 - * @param classLoader {@link ClassLoader},{@code null} 则使用系统默认ClassLoader * @param isInitialized 是否初始化类(调用static模块内容和初始化static属性) + * @param classLoader {@link ClassLoader},{@code null} 则使用系统默认ClassLoader * @return 类名对应的类 * @throws InternalException 没有类名对应的类时抛出此异常 */ - public static Class loadClass(String name, ClassLoader classLoader, boolean isInitialized) throws InternalException { + public static Class loadClass(String name, boolean isInitialized, ClassLoader classLoader) throws InternalException { Assert.notNull(name, "Name must not be null"); // 自动将包名中的"/"替换为"." @@ -1112,12 +1107,9 @@ public static Class loadClass(String name, ClassLoader classLoader, boolean i classLoader = getClassLoader(); } - // 加载原始类型和缓存中的类 Class clazz = loadPrimitiveClass(name); if (clazz == null) { - final String finalName = name; - final ClassLoader finalClassLoader = classLoader; - clazz = CLASS_CACHE.computeIfAbsent(Pair.of(name, classLoader), (key) -> doLoadClass(finalName, finalClassLoader, isInitialized)); + clazz = doLoadClass(name, isInitialized, classLoader); } return clazz; } @@ -1166,7 +1158,7 @@ public static Class loadClass(File jarOrDir, String name) { /** * 指定类是否被提供,使用默认ClassLoader - * 通过调用{@link #loadClass(String, ClassLoader, boolean)}方法尝试加载指定类名的类,如果加载失败返回false + * 通过调用{@link #loadClass(String, boolean, ClassLoader)}方法尝试加载指定类名的类,如果加载失败返回false * 加载失败的原因可能是此类不存在或其关联引用类不存在 * * @param className 类名 @@ -1178,7 +1170,7 @@ public static boolean isPresent(String className) { /** * 指定类是否被提供 - * 通过调用{@link #loadClass(String, ClassLoader, boolean)}方法尝试加载指定类名的类,如果加载失败返回false + * 通过调用{@link #loadClass(String, boolean, ClassLoader)}方法尝试加载指定类名的类,如果加载失败返回false * 加载失败的原因可能是此类不存在或其关联引用类不存在 * * @param className 类名 @@ -1187,7 +1179,7 @@ public static boolean isPresent(String className) { */ public static boolean isPresent(String className, ClassLoader classLoader) { try { - loadClass(className, classLoader, false); + loadClass(className, false, classLoader); return true; } catch (Throwable ex) { return false; @@ -1276,6 +1268,24 @@ public static Class forName(String name, ClassLoader classLoader) } } + + /** + * 加载指定名称的类 + * + * @param name 类名 + * @param initialize 是否初始化 + * @param loader {@link ClassLoader} + * @return 指定名称对应的类,如果不存在类,返回{@code null} + */ + private static Class forName(final String name, final boolean initialize, final ClassLoader loader) { + try { + return Class.forName(name, initialize, loader); + } catch (final ClassNotFoundException ex2) { + // 尝试获取内部类失败时,忽略之。 + return null; + } + } + public static Class resolvePrimitiveClassName(String name) { Class result = null; // 大多数类名都很长,因为它们应该放在包中,所以长度检查是值得的. @@ -1871,7 +1881,7 @@ public static Object invokeMethod(final Object object, messagePrefix = "No such method: "; method = getMatchingMethod(object.getClass(), methodName, parameterTypes); - if (null != method && !method.isAccessible()) { + if (null != method && !method.canAccess(method)) { method.setAccessible(true); } } else { @@ -3619,37 +3629,35 @@ public static JavaSourceCompiler getCompiler(ClassLoader parent) { * 加载非原始类类,无缓存 * * @param name 类名 - * @param classLoader {@link ClassLoader} * @param isInitialized 是否初始化 + * @param classLoader {@link ClassLoader},必须非空 * @return 类 */ - private static Class doLoadClass(String name, ClassLoader classLoader, boolean isInitialized) { + private static Class doLoadClass(String name, boolean isInitialized, ClassLoader classLoader) { + // 去除尾部多余的"." + name = StringKit.trim(name, 1, (c) -> Symbol.C_DOT == c); Class clazz; if (name.endsWith(Symbol.BRACKET)) { // 对象数组"java.lang.String[]"风格 final String elementClassName = name.substring(0, name.length() - Symbol.BRACKET.length()); - final Class elementClass = loadClass(elementClassName, classLoader, isInitialized); + final Class elementClass = loadClass(elementClassName, isInitialized, classLoader); clazz = Array.newInstance(elementClass, 0).getClass(); } else if (name.startsWith(Symbol.NON_PREFIX) && name.endsWith(Symbol.SEMICOLON)) { // "[Ljava.lang.String;" 风格 final String elementName = name.substring(Symbol.NON_PREFIX.length(), name.length() - 1); - final Class elementClass = loadClass(elementName, classLoader, isInitialized); + final Class elementClass = loadClass(elementName, isInitialized, classLoader); clazz = Array.newInstance(elementClass, 0).getClass(); } else if (name.startsWith(Symbol.BRACKET_LEFT)) { // "[[I" 或 "[[Ljava.lang.String;" 风格 final String elementName = name.substring(Symbol.BRACKET_LEFT.length()); - final Class elementClass = loadClass(elementName, classLoader, isInitialized); + final Class elementClass = loadClass(elementName, isInitialized, classLoader); clazz = Array.newInstance(elementClass, 0).getClass(); } else { - // 加载普通类 - if (null == classLoader) { - classLoader = getClassLoader(); - } try { clazz = Class.forName(name, isInitialized, classLoader); } catch (ClassNotFoundException ex) { // 尝试获取内部类,例如java.lang.Thread.State = java.lang.Thread$State - clazz = tryLoadInnerClass(name, classLoader, isInitialized); + clazz = tryLoadInnerClass(name, isInitialized, classLoader); if (null == clazz) { throw new InternalException(ex); } @@ -3662,22 +3670,28 @@ private static Class doLoadClass(String name, ClassLoader classLoader, boolea * 尝试转换并加载内部类,例如java.lang.Thread.State - java.lang.Thread$State * * @param name 类名 - * @param classLoader {@link ClassLoader},{@code null} 则使用系统默认ClassLoader * @param isInitialized 是否初始化类(调用static模块内容和初始化static属性) + * @param classLoader {@link ClassLoader},{@code null} 则使用系统默认ClassLoader * @return 类名对应的类 */ - private static Class tryLoadInnerClass(String name, ClassLoader classLoader, boolean isInitialized) { - // 尝试获取内部类,例如java.lang.Thread.State = java.lang.Thread$State - final int lastDotIndex = name.lastIndexOf(Symbol.C_DOT); - if (lastDotIndex > 0) {// 类与内部类的分隔符不能在第一位,因此>0 - final String innerClassName = name.substring(0, lastDotIndex) + Symbol.C_DOLLAR + name.substring(lastDotIndex + 1); - try { - return Class.forName(innerClassName, isInitialized, classLoader); - } catch (ClassNotFoundException ex2) { - // 尝试获取内部类失败时,忽略之。 + private static Class tryLoadInnerClass(String name, boolean isInitialized, ClassLoader classLoader) { + // 尝试获取内部类,例如java.lang.Thread.State =》java.lang.Thread$State + int lastDotIndex = name.lastIndexOf(Symbol.C_DOT); + Class clazz = null; + while (lastDotIndex > 0) {// 类与内部类的分隔符不能在第一位,因此>0 + if (false == Character.isUpperCase(name.charAt(lastDotIndex + 1))) { + // 类名必须大写,非大写的类名跳过 + break; + } + name = name.substring(0, lastDotIndex) + Symbol.C_DOLLAR + name.substring(lastDotIndex + 1); + clazz = forName(name, isInitialized, classLoader); + if (null != clazz) { + break; } + + lastDotIndex = name.lastIndexOf(Symbol.C_DOT); } - return null; + return clazz; } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java index 56a195ee3e..d816ee81ba 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CollKit.java @@ -1340,13 +1340,25 @@ public static List unmodifiable(List list) { } /** - * 创建新的集合对象 + * 创建新的集合对象,返回具体的泛型集合 * - * @param 对象 + * @param 集合元素类型,rawtype 如 ArrayList.class, EnumSet.class ... * @param collectionType 集合类型 * @return 集合类型对应的实例 */ - public static Collection create(Class collectionType) { + public static Collection create(final Class collectionType) { + return create(collectionType, null); + } + + /** + * 创建新的集合对象,返回具体的泛型集合 + * + * @param 集合元素类型,rawtype 如 ArrayList.class, EnumSet.class ... + * @param collectionType 集合类型 + * @param elementType 集合元素类,只用于EnumSet创建,如果创建EnumSet,则此参数必须非空 + * @return 集合类型对应的实例 + */ + public static Collection create(final Class collectionType, final Class elementType) { Collection list; if (collectionType.isAssignableFrom(AbstractCollection.class)) { // 抽象集合默认使用ArrayList @@ -1366,7 +1378,7 @@ else if (collectionType.isAssignableFrom(HashSet.class)) { return ObjectKit.compare(o1.toString(), o2.toString()); }); } else if (collectionType.isAssignableFrom(EnumSet.class)) { - list = (Collection) EnumSet.noneOf((Class) ClassKit.getTypeArgument(collectionType)); + list = (Collection) EnumSet.noneOf((Class) Assert.notNull(elementType)); } // List diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/FileKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/FileKit.java index 5ed7a26720..53939fe5f7 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/FileKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/FileKit.java @@ -3622,6 +3622,21 @@ public static File getWebRoot() { return null; } + /** + * 获取指定文件的父路径 + * + *
              +     * getParent(file("d:/aaa/bbb/cc/ddd")) -》 "d:/aaa/bbb/cc"
              +     * 
              + * + * @param file 目录或文件 + * @return 路径File,如果不存在返回null + * @since 6.0.0 + */ + public static File getParent(final File file) { + return getParent(file, 1); + } + /** * 获取指定层级的父路径 * @@ -3992,7 +4007,7 @@ public static InputStream getStreamSafe(String resurce) { } /** - * 获得资源的URL
              + * 获得资源的URL * 路径用/分隔,例如: * *
              @@ -4020,7 +4035,7 @@ public static URL getUrl(String resource, final Class baseClass) {
                   }
               
                   /**
              -     * 获取指定路径下的资源列表
              + * 获取指定路径下的资源列表 * 路径格式必须为目录格式,用/分隔,例如: * *
              @@ -4036,7 +4051,7 @@ public static List getUrls(final String resource) {
                   }
               
                   /**
              -     * 获取指定路径下的资源列表
              + * 获取指定路径下的资源列表 * 路径格式必须为目录格式,用/分隔,例如: * *
              diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java
              index a6338bf7b3..33a2315edc 100755
              --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java
              +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/IoKit.java
              @@ -932,13 +932,79 @@ public static ByteArrayInputStream toStream(FastByteOutputStream out) {
                       return new ByteArrayInputStream(out.toByteArray());
                   }
               
              +
              +    /**
              +     * 将{@link InputStream}转换为支持mark标记的流
              +     * 若原流支持mark标记,则返回原流,否则使用{@link BufferedInputStream} 包装之
              +     *
              +     * @param in 流
              +     * @return {@link InputStream}
              +     */
              +    public static InputStream toMarkSupportStream(final InputStream in) {
              +        if (null == in) {
              +            return null;
              +        }
              +        if (false == in.markSupported()) {
              +            return new BufferedInputStream(in);
              +        }
              +        return in;
              +    }
              +
              +    /**
              +     * 转换为{@link PushbackInputStream}
              +     * 如果传入的输入流已经是{@link PushbackInputStream},强转返回,否则新建一个
              +     *
              +     * @param in           {@link InputStream}
              +     * @param pushBackSize 推后的byte数
              +     * @return {@link PushbackInputStream}
              +     */
              +    public static PushbackInputStream toPushbackStream(final InputStream in, final int pushBackSize) {
              +        return (in instanceof PushbackInputStream) ? (PushbackInputStream) in : new PushbackInputStream(in, pushBackSize);
              +    }
              +
              +    /**
              +     * 将指定{@link InputStream} 转换为{@link InputStream#available()}方法可用的流。
              +     * 在Socket通信流中,服务端未返回数据情况下{@link InputStream#available()}方法始终为{@code 0}
              +     * 因此,在读取前需要调用{@link InputStream#read()}读取一个字节(未返回会阻塞),一旦读取到了,{@link InputStream#available()}方法就正常了。
              +     * 需要注意的是,在网络流中,是按照块来传输的,所以 {@link InputStream#available()} 读取到的并非最终长度,而是此次块的长度。
              +     * 此方法返回对象的规则为:
              +     *
              +     * 
                + *
              • FileInputStream 返回原对象,因为文件流的available方法本身可用
              • + *
              • 其它InputStream 返回PushbackInputStream
              • + *
              + * + * @param in 被转换的流 + * @return 转换后的流,可能为{@link PushbackInputStream} + */ + public static InputStream toAvailableStream(final InputStream in) { + if (in instanceof FileInputStream) { + // FileInputStream本身支持available方法。 + return in; + } + + final PushbackInputStream pushbackInputStream = toPushbackStream(in, 1); + try { + final int available = pushbackInputStream.available(); + if (available <= 0) { + //此操作会阻塞,直到有数据被读到 + final int b = pushbackInputStream.read(); + pushbackInputStream.unread(b); + } + } catch (final IOException e) { + throw new InternalException(e); + } + + return pushbackInputStream; + } + /** * 转换为{@link BufferedInputStream} * * @param in {@link InputStream} * @return {@link BufferedInputStream} */ - public static BufferedInputStream toBuffered(InputStream in) { + public static BufferedInputStream toBuffered(final InputStream in) { Assert.notNull(in, "InputStream must be not null!"); return (in instanceof BufferedInputStream) ? (BufferedInputStream) in : new BufferedInputStream(in); } @@ -950,7 +1016,7 @@ public static BufferedInputStream toBuffered(InputStream in) { * @param bufferSize buffer size * @return {@link BufferedInputStream} */ - public static BufferedInputStream toBuffered(InputStream in, int bufferSize) { + public static BufferedInputStream toBuffered(final InputStream in, final int bufferSize) { Assert.notNull(in, "InputStream must be not null!"); return (in instanceof BufferedInputStream) ? (BufferedInputStream) in : new BufferedInputStream(in, bufferSize); } @@ -961,7 +1027,7 @@ public static BufferedInputStream toBuffered(InputStream in, int bufferSize) { * @param out {@link OutputStream} * @return {@link BufferedOutputStream} */ - public static BufferedOutputStream toBuffered(OutputStream out) { + public static BufferedOutputStream toBuffered(final OutputStream out) { Assert.notNull(out, "OutputStream must be not null!"); return (out instanceof BufferedOutputStream) ? (BufferedOutputStream) out : new BufferedOutputStream(out); } @@ -973,38 +1039,67 @@ public static BufferedOutputStream toBuffered(OutputStream out) { * @param bufferSize buffer size * @return {@link BufferedOutputStream} */ - public static BufferedOutputStream toBuffered(OutputStream out, int bufferSize) { + public static BufferedOutputStream toBuffered(final OutputStream out, final int bufferSize) { Assert.notNull(out, "OutputStream must be not null!"); return (out instanceof BufferedOutputStream) ? (BufferedOutputStream) out : new BufferedOutputStream(out, bufferSize); } /** - * 将{@link InputStream}转换为支持mark标记的流 - * 若原流支持mark标记,则返回原流,否则使用{@link BufferedInputStream} 包装之 + * 转换为{@link BufferedReader} * - * @param in 流 - * @return {@link InputStream} + * @param reader {@link Reader} + * @return {@link BufferedReader} */ - public static InputStream toMarkSupportStream(InputStream in) { - if (null == in) { - return null; - } - if (false == in.markSupported()) { - return new BufferedInputStream(in); - } - return in; + public static BufferedReader toBuffered(final Reader reader) { + Assert.notNull(reader, "Reader must be not null!"); + return (reader instanceof BufferedReader) ? (BufferedReader) reader : new BufferedReader(reader); } /** - * 转换为{@link PushbackInputStream} - * 如果传入的输入流已经是{@link PushbackInputStream},强转返回,否则新建一个 + * 转换为{@link BufferedReader} * - * @param in {@link InputStream} + * @param reader {@link Reader} + * @param bufferSize buffer size + * @return {@link BufferedReader} + */ + public static BufferedReader toBuffered(final Reader reader, final int bufferSize) { + Assert.notNull(reader, "Reader must be not null!"); + return (reader instanceof BufferedReader) ? (BufferedReader) reader : new BufferedReader(reader, bufferSize); + } + + /** + * 转换为{@link BufferedWriter} + * + * @param writer {@link Writer} + * @return {@link BufferedWriter} + */ + public static BufferedWriter toBuffered(final Writer writer) { + Assert.notNull(writer, "Writer must be not null!"); + return (writer instanceof BufferedWriter) ? (BufferedWriter) writer : new BufferedWriter(writer); + } + + /** + * 转换为{@link BufferedWriter} + * + * @param writer {@link Writer} + * @param bufferSize buffer size + * @return {@link BufferedWriter} + */ + public static BufferedWriter toBuffered(final Writer writer, final int bufferSize) { + Assert.notNull(writer, "Writer must be not null!"); + return (writer instanceof BufferedWriter) ? (BufferedWriter) writer : new BufferedWriter(writer, bufferSize); + } + + /** + * 获得{@link PushbackReader} + * 如果是{@link PushbackReader}强转返回,否则新建 + * + * @param reader 普通Reader * @param pushBackSize 推后的byte数 - * @return {@link PushbackInputStream} + * @return {@link PushbackReader} */ - public static PushbackInputStream toPushbackStream(InputStream in, int pushBackSize) { - return (in instanceof PushbackInputStream) ? (PushbackInputStream) in : new PushbackInputStream(in, pushBackSize); + public static PushbackReader toPushBackReader(final Reader reader, final int pushBackSize) { + return (reader instanceof PushbackReader) ? (PushbackReader) reader : new PushbackReader(reader, pushBackSize); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/XmlKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/XmlKit.java index 14c63264ee..6667ca1e55 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/XmlKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/XmlKit.java @@ -223,6 +223,9 @@ public static void readBySax(InputSource source, ContentHandler contentHandler) // 3.得到解读器 reader = parse.getXMLReader(); + // 防止XEE攻击,见:https://www.jianshu.com/p/1a857905b22c + reader.setFeature("http://xml.org/sax/features/external-general-entities", false); + reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); reader.setContentHandler(contentHandler); reader.parse(source); } catch (IOException | ParserConfigurationException | SAXException e) { diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/anget/Browser.java b/bus-http/src/main/java/org/aoju/bus/http/metric/anget/Browser.java index 967e2c72bb..ba4058ffa1 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/anget/Browser.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/anget/Browser.java @@ -62,6 +62,8 @@ public class Browser extends UserAgent { new Browser("miniProgram", "miniProgram", "miniProgram\\/([\\d\\w\\.\\-]+)"), // QQ浏览器 new Browser("QQBrowser", "MQQBrowser", "MQQBrowser\\/([\\d\\w\\.\\-]+)"), + // 钉钉PC端浏览器 + new Browser("DingTalk-win", "dingtalk-win", "DingTalk\\(([\\d\\w\\.\\-]+)\\)"), // 钉钉内置浏览器 new Browser("DingTalk", "DingTalk", "AliApp\\(DingTalk\\/([\\d\\w\\.\\-]+)\\)"), // 支付宝内置浏览器 diff --git a/bus-setting/src/main/java/org/aoju/bus/setting/Readers.java b/bus-setting/src/main/java/org/aoju/bus/setting/Readers.java index e33a214816..4a86084840 100755 --- a/bus-setting/src/main/java/org/aoju/bus/setting/Readers.java +++ b/bus-setting/src/main/java/org/aoju/bus/setting/Readers.java @@ -25,6 +25,7 @@ ********************************************************************************/ package org.aoju.bus.setting; +import org.aoju.bus.core.io.reader.LineReader; import org.aoju.bus.core.io.resource.Resource; import org.aoju.bus.core.lang.Assert; import org.aoju.bus.core.lang.Charset; @@ -148,11 +149,11 @@ public boolean load(Resource resource) { * @return 加载成功与否 * @throws IOException IO异常 */ - public boolean load(InputStream inputStream) throws IOException { + public boolean load(final InputStream inputStream) throws IOException { this.groupMap.clear(); - BufferedReader reader = null; + LineReader reader = null; try { - reader = IoKit.getReader(inputStream, this.charset); + reader = new LineReader(inputStream, this.charset); // 分组 String group = null; diff --git a/bus-setting/src/main/java/org/aoju/bus/setting/magic/Properties.java b/bus-setting/src/main/java/org/aoju/bus/setting/magic/Properties.java index 251633f7c4..ebfabd2c01 100755 --- a/bus-setting/src/main/java/org/aoju/bus/setting/magic/Properties.java +++ b/bus-setting/src/main/java/org/aoju/bus/setting/magic/Properties.java @@ -33,16 +33,14 @@ import org.aoju.bus.core.io.watcher.WatchMonitor; import org.aoju.bus.core.lang.Assert; import org.aoju.bus.core.lang.Charset; +import org.aoju.bus.core.lang.FileType; import org.aoju.bus.core.lang.Symbol; import org.aoju.bus.core.lang.function.XFunction; import org.aoju.bus.core.lang.function.XSupplier; import org.aoju.bus.core.toolkit.*; import org.aoju.bus.logger.Logger; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.Writer; +import java.io.*; import java.net.URL; import java.nio.file.Path; import java.nio.file.WatchEvent; @@ -64,7 +62,7 @@ public final class Properties extends java.util.Properties implements TypeGetter private Resource resource; private WatchMonitor watchMonitor; /** - * properties文件编码
              + * properties文件编码 * issue#1701,此属性不能被序列化,故忽略序列化 */ private transient java.nio.charset.Charset charset = Charset.ISO_8859_1; @@ -186,6 +184,33 @@ public void load(final URL url) { load(new UriResource(url)); } + /** + * 加载配置文件内容到{@link java.util.Properties}中 + * 需要注意的是,如果资源文件的扩展名是.xml,会调用{@link java.util.Properties#loadFromXML(InputStream)} 读取。 + * + * @param properties {@link java.util.Properties}文件 + * @param resource 资源 + * @param charset 编码,对XML无效 + */ + public static void load(final java.util.Properties properties, final Resource resource, final java.nio.charset.Charset charset) { + final String filename = resource.getName(); + if (filename != null && filename.endsWith(FileType.TYPE_XML)) { + // XML + try (final InputStream in = resource.getStream()) { + properties.loadFromXML(in); + } catch (final IOException e) { + throw new InternalException(e); + } + } else { + // .properties + try (final BufferedReader reader = resource.getReader(charset)) { + properties.load(reader); + } catch (final IOException e) { + throw new InternalException(e); + } + } + } + /** * 初始化配置文件 * @@ -194,12 +219,7 @@ public void load(final URL url) { public void load(final Resource resource) { Assert.notNull(resource, "Props resource must be not null!"); this.resource = resource; - - try (final BufferedReader reader = resource.getReader(charset)) { - super.load(reader); - } catch (final IOException e) { - throw new InternalException(e); - } + load(this, resource, this.charset); } /** From 92c1e161029feb80cefe94aa1d3a4ef036f4a999 Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Tue, 15 Nov 2022 16:14:05 +0800 Subject: [PATCH 15/19] bug fix tls 1.2 --- bus-http/src/main/java/org/aoju/bus/http/Builder.java | 6 ++---- bus-http/src/main/java/org/aoju/bus/http/Headers.java | 7 +++---- .../src/main/java/org/aoju/bus/http/Protocol.java | 4 ++-- .../java/org/aoju/bus/http/metric/NamedRunnable.java | 4 +--- .../java/org/aoju/bus/http/metric/http/Http2.java | 11 +++++------ .../org/aoju/bus/http/metric/http/Http2Codec.java | 8 ++++---- .../aoju/bus/http/metric/http/Http2Connection.java | 7 +++---- .../org/aoju/bus/http/metric/http/Http2Reader.java | 3 +-- .../org/aoju/bus/http/metric/http/Http2Writer.java | 3 +-- 9 files changed, 22 insertions(+), 31 deletions(-) diff --git a/bus-http/src/main/java/org/aoju/bus/http/Builder.java b/bus-http/src/main/java/org/aoju/bus/http/Builder.java index 13c0ffcb3d..695693beae 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/Builder.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Builder.java @@ -63,11 +63,9 @@ public class Builder { * 最后一个四位数的年份:"Fri, 31 Dec 9999 23:59:59 GMT" */ public static final long MAX_DATE = 253402300799999L; - public static final String X_509 = "X.509"; - public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - public static final String[] EMPTY_STRING_ARRAY = new String[0]; + public static final Headers EMPTY_HEADERS = Headers.of(); - public static final ResponseBody EMPTY_RESPONSE = ResponseBody.create(null, EMPTY_BYTE_ARRAY); + public static final ResponseBody EMPTY_RESPONSE = ResponseBody.create(null, Normal.EMPTY_BYTE_ARRAY); /** * GMT and UTC are equivalent for our purposes */ diff --git a/bus-http/src/main/java/org/aoju/bus/http/Headers.java b/bus-http/src/main/java/org/aoju/bus/http/Headers.java index e9c35e4c0f..b87f83be5a 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/Headers.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Headers.java @@ -30,7 +30,6 @@ import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.http.metric.CookieJar; import org.aoju.bus.http.secure.Challenge; @@ -125,7 +124,7 @@ static void checkName(String name) { for (int i = 0, length = name.length(); i < length; i++) { char c = name.charAt(i); if (c <= '\u0020' || c >= '\u007f') { - throw new IllegalArgumentException(StringKit.format( + throw new IllegalArgumentException(String.format( "Unexpected char %#04x at %d in header name: %s", (int) c, i, name)); } } @@ -136,7 +135,7 @@ static void checkValue(String value, String name) { for (int i = 0, length = value.length(); i < length; i++) { char c = value.charAt(i); if ((c <= '\u001f' && c != Symbol.C_HT) || c >= '\u007f') { - throw new IllegalArgumentException(StringKit.format( + throw new IllegalArgumentException(String.format( "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value)); } } @@ -847,7 +846,7 @@ public int hashCode() { @Override public String toString() { - return StringKit.format("%s: %s", name.utf8(), value.utf8()); + return String.format("%s: %s", name.utf8(), value.utf8()); } } diff --git a/bus-http/src/main/java/org/aoju/bus/http/Protocol.java b/bus-http/src/main/java/org/aoju/bus/http/Protocol.java index a87ceef8c2..0fef691fad 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/Protocol.java +++ b/bus-http/src/main/java/org/aoju/bus/http/Protocol.java @@ -53,9 +53,9 @@ public enum Protocol { /** * IETF的二进制框架协议,包括头压缩、在同一个套接字上多路复用多个请求和服务器推送 - * HTTP/1.1语义是在HTTP/2上分层的. + * HTTP/1.1语义是在HTTP/2上分层的 */ - HTTP_2(Http.HTTP_2_0), + HTTP_2("h2"), /** * Chromium的二进制框架协议,包括标头压缩、在同一个套接字上多路复用多个请求和服务器推送 diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/NamedRunnable.java b/bus-http/src/main/java/org/aoju/bus/http/metric/NamedRunnable.java index 0ab33b4e9c..136f89494c 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/NamedRunnable.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/NamedRunnable.java @@ -25,8 +25,6 @@ ********************************************************************************/ package org.aoju.bus.http.metric; -import org.aoju.bus.core.toolkit.StringKit; - /** * 可运行的实现,它总是设置它的线程名 * @@ -38,7 +36,7 @@ public abstract class NamedRunnable implements Runnable { protected final String name; public NamedRunnable(String format, Object... args) { - this.name = StringKit.format(format, args); + this.name = String.format(format, args); } @Override diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2.java index c5f78a7574..f3fc3a6daa 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2.java @@ -28,7 +28,6 @@ import org.aoju.bus.core.io.ByteString; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.core.toolkit.StringKit; import java.io.IOException; @@ -87,7 +86,7 @@ public class Http2 { static { for (int i = 0; i < BINARY.length; i++) { - BINARY[i] = StringKit.format("%8s", Integer.toBinaryString(i)).replace(Symbol.C_SPACE, Symbol.C_ZERO); + BINARY[i] = String.format("%8s", Integer.toBinaryString(i)).replace(Symbol.C_SPACE, Symbol.C_ZERO); } FLAGS[FLAG_NONE] = Normal.EMPTY; @@ -124,11 +123,11 @@ private Http2() { } static IllegalArgumentException illegalArgument(String message, Object... args) { - throw new IllegalArgumentException(StringKit.format(message, args)); + throw new IllegalArgumentException(String.format(message, args)); } static IOException ioException(String message, Object... args) throws IOException { - throw new IOException(StringKit.format(message, args)); + throw new IOException(String.format(message, args)); } /** @@ -146,9 +145,9 @@ static IOException ioException(String message, Object... args) throws IOExceptio *
              */ static String frameLog(boolean inbound, int streamId, int length, byte type, byte flags) { - String formattedType = type < FRAME_NAMES.length ? FRAME_NAMES[type] : StringKit.format("0x%02x", type); + String formattedType = type < FRAME_NAMES.length ? FRAME_NAMES[type] : String.format("0x%02x", type); String formattedFlags = formatFlags(type, flags); - return StringKit.format("%s 0x%08x %5d %-13s %s", inbound ? "<<" : ">>", streamId, length, + return String.format("%s 0x%08x %5d %-13s %s", inbound ? "<<" : ">>", streamId, length, formattedType, formattedFlags); } diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Codec.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Codec.java index 9af8b566fa..093cee8dbd 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Codec.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Codec.java @@ -29,6 +29,7 @@ import org.aoju.bus.core.io.source.Source; import org.aoju.bus.core.lang.Header; import org.aoju.bus.core.lang.Http; +import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.http.*; import org.aoju.bus.http.accord.RealConnection; import org.aoju.bus.http.metric.Interceptor; @@ -38,7 +39,6 @@ import java.net.ProtocolException; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.concurrent.TimeUnit; /** @@ -105,9 +105,9 @@ public static List http2HeadersList(Request request) { for (int i = 0, size = headers.size(); i < size; i++) { // header names must be lowercase. - String name = headers.name(i).toLowerCase(Locale.US); - if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name) - || name.equals(Header.TE) && headers.value(i).equals("trailers")) { + String name = StringKit.upperFirst(headers.name(i)); + if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name) || name.equals(Header.TE) + && "trailers".equals(headers.value(i))) { result.add(new Headers.Header(name, headers.value(i))); } } diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Connection.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Connection.java index ff794bab72..89c9a10c25 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Connection.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Connection.java @@ -32,7 +32,6 @@ import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.toolkit.IoKit; -import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.http.Headers; import org.aoju.bus.http.Settings; import org.aoju.bus.http.metric.NamedRunnable; @@ -161,7 +160,7 @@ public class Http2Connection implements Closeable { connectionName = builder.connectionName; writerExecutor = new ScheduledThreadPoolExecutor(1, - org.aoju.bus.http.Builder.threadFactory(StringKit.format("Http %s Writer", connectionName), false)); + org.aoju.bus.http.Builder.threadFactory(String.format("Http %s Writer", connectionName), false)); if (builder.pingIntervalMillis != 0) { writerExecutor.scheduleAtFixedRate(new IntervalPingRunnable(), builder.pingIntervalMillis, builder.pingIntervalMillis, TimeUnit.MILLISECONDS); @@ -169,7 +168,7 @@ public class Http2Connection implements Closeable { // Like newSingleThreadExecutor, except lazy creates the thread. pushExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), - org.aoju.bus.http.Builder.threadFactory(StringKit.format("Http %s Push Observer", connectionName), true)); + org.aoju.bus.http.Builder.threadFactory(String.format("Http %s Push Observer", connectionName), true)); peerSettings.set(Http.INITIAL_WINDOW_SIZE, Http.DEFAULT_INITIAL_WINDOW_SIZE); peerSettings.set(Http.MAX_FRAME_SIZE, Http2.INITIAL_MAX_FRAME_SIZE); bytesLeftInWriteWindow = peerSettings.getInitialWindowSize(); @@ -416,7 +415,7 @@ public void shutdown(ErrorCode statusCode) throws IOException { shutdown = true; lastGoodStreamId = this.lastGoodStreamId; } - writer.goAway(lastGoodStreamId, statusCode, org.aoju.bus.http.Builder.EMPTY_BYTE_ARRAY); + writer.goAway(lastGoodStreamId, statusCode, Normal.EMPTY_BYTE_ARRAY); } } diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Reader.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Reader.java index 78f0fd8a9d..49143f1aea 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Reader.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Reader.java @@ -30,7 +30,6 @@ import org.aoju.bus.core.io.source.BufferSource; import org.aoju.bus.core.io.source.Source; import org.aoju.bus.core.io.timout.Timeout; -import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.http.Headers; import org.aoju.bus.http.Settings; import org.aoju.bus.logger.Logger; @@ -88,7 +87,7 @@ public void readConnectionPreface(Handler handler) throws IOException { } else { ByteString connectionPreface = source.readByteString(Http2.CONNECTION_PREFACE.size()); if (Logger.isDebug()) { - Logger.warn(StringKit.format("<< CONNECTION %s", connectionPreface.hex())); + Logger.debug(String.format("<< CONNECTION %s" + connectionPreface.hex())); } if (!Http2.CONNECTION_PREFACE.equals(connectionPreface)) { throw Http2.ioException("Expected a connection header but was %s", connectionPreface.utf8()); diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Writer.java b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Writer.java index 43583e140b..2364b6c4cd 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Writer.java +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/http/Http2Writer.java @@ -28,7 +28,6 @@ import org.aoju.bus.core.io.buffer.Buffer; import org.aoju.bus.core.io.sink.BufferSink; import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.http.Headers; import org.aoju.bus.http.Settings; import org.aoju.bus.logger.Logger; @@ -73,7 +72,7 @@ public synchronized void connectionPreface() throws IOException { if (closed) throw new IOException("closed"); if (!client) return; // Nothing to write; servers don't send connection headers! if (Logger.isDebug()) { - Logger.warn(StringKit.format(">> CONNECTION %s", Http2.CONNECTION_PREFACE.hex())); + Logger.warn(String.format(">> CONNECTION %s", Http2.CONNECTION_PREFACE.hex())); } sink.write(Http2.CONNECTION_PREFACE.toByteArray()); sink.flush(); From 496dd01d540cbfc3b2735e7ee41bf7d032b4af56 Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Wed, 16 Nov 2022 13:42:38 +0800 Subject: [PATCH 16/19] bug fix and add method --- bus-extra/pom.xml | 2 +- bus-gitlab/pom.xml | 1 - .../builtin/hardware/CentralProcessor.java | 9 ++-- .../health/builtin/software/FileSystem.java | 8 +++ .../health/builtin/software/OSProcess.java | 24 +++++++++ .../builtin/software/OperatingSystem.java | 8 +-- .../org/aoju/bus/health/linux/ProcPath.java | 1 + .../linux/software/LinuxFileSystem.java | 9 ++++ .../health/linux/software/LinuxOSProcess.java | 51 ++++++++++++++++-- .../linux/software/LinuxOperatingSystem.java | 2 +- .../health/mac/software/MacFileSystem.java | 5 ++ .../bus/health/mac/software/MacOSProcess.java | 28 +++++++++- .../mac/software/MacOperatingSystem.java | 2 +- .../aix/hardware/AixCentralProcessor.java | 7 ++- .../unix/aix/software/AixFileSystem.java | 11 ++++ .../unix/aix/software/AixOSProcess.java | 28 +++++++++- .../unix/aix/software/AixOperatingSystem.java | 2 +- .../freebsd/software/FreeBsdFileSystem.java | 6 +++ .../freebsd/software/FreeBsdOSProcess.java | 53 +++++++++++++++++-- .../software/FreeBsdOperatingSystem.java | 2 +- .../openbsd/software/OpenBsdFileSystem.java | 5 ++ .../openbsd/software/OpenBsdOSProcess.java | 27 +++++++++- .../software/OpenBsdOperatingSystem.java | 2 +- .../solaris/software/SolarisFileSystem.java | 11 ++++ .../solaris/software/SolarisOSProcess.java | 49 +++++++++++++++-- .../software/SolarisOperatingSystem.java | 2 +- .../windows/software/WindowsFileSystem.java | 8 ++- .../windows/software/WindowsOSProcess.java | 10 ++++ .../software/WindowsOperatingSystem.java | 12 +++-- 29 files changed, 342 insertions(+), 43 deletions(-) diff --git a/bus-extra/pom.xml b/bus-extra/pom.xml index 6fabc7b346..0e980a2e36 100644 --- a/bus-extra/pom.xml +++ b/bus-extra/pom.xml @@ -45,7 +45,7 @@ 1.18.24 1.2.83 2.9.1 - 2.14.0-rc2 + 2.14.0 1.1.8 2.5.1 2.0.3.RELEASE diff --git a/bus-gitlab/pom.xml b/bus-gitlab/pom.xml index 1afa0fa956..f82046d789 100755 --- a/bus-gitlab/pom.xml +++ b/bus-gitlab/pom.xml @@ -42,7 +42,6 @@ UTF-8 UTF-8 17 - 2.12.4 2.36 4.0.4 diff --git a/bus-health/src/main/java/org/aoju/bus/health/builtin/hardware/CentralProcessor.java b/bus-health/src/main/java/org/aoju/bus/health/builtin/hardware/CentralProcessor.java index 35593d66f9..7d23065c70 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/builtin/hardware/CentralProcessor.java +++ b/bus-health/src/main/java/org/aoju/bus/health/builtin/hardware/CentralProcessor.java @@ -93,10 +93,11 @@ public interface CentralProcessor { long[] getCurrentFreq(); /** - * Returns an {@code UnmodifiableList} of the CPU's logical processors. The list - * will be sorted in order of increasing NUMA node number, and then processor - * number. This order is consistent with other methods providing per-processor - * results. + * Returns an {@code UnmodifiableList} of the CPU's logical processors. The list will be sorted in order of + * increasing NUMA node number, and then processor number. This order is (usually) consistent with other methods + * providing per-processor results. + * On some operating systems with variable numbers of logical processors, the size of this array could change and + * may not align with other per-processor methods. * * @return An {@code UnmodifiabeList} of logical processors. */ diff --git a/bus-health/src/main/java/org/aoju/bus/health/builtin/software/FileSystem.java b/bus-health/src/main/java/org/aoju/bus/health/builtin/software/FileSystem.java index 99479b654b..66800e0ec6 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/builtin/software/FileSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/builtin/software/FileSystem.java @@ -94,4 +94,12 @@ public interface FileSystem { */ long getMaxFileDescriptors(); + /** + * The maximum number of open file descriptors per process. This returns the upper limit which applies to each + * process. The actual limit of a process may be lower if configured. + * + * @return The maximum number of file descriptors of each process if available, 0 otherwise. + */ + long getMaxFileDescriptorsPerProcess(); + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/builtin/software/OSProcess.java b/bus-health/src/main/java/org/aoju/bus/health/builtin/software/OSProcess.java index 35f775f187..c7fc5d9758 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/builtin/software/OSProcess.java +++ b/bus-health/src/main/java/org/aoju/bus/health/builtin/software/OSProcess.java @@ -297,6 +297,30 @@ public interface OSProcess { */ long getOpenFiles(); + /** + * Gets the soft limit for open file handles (or network connections) of the given process. + *

              + * Retrieving the soft limit for processes other than the calling process is only supported on Linux, FreeBsd and + * Solaris. + * + * @return the soft open file limit for the process if available. Returns -1 if the calling process is not the same + * as this OSProcess instance and the underlying operating system does not support retrieving the soft limit + * for other processes. + */ + long getSoftOpenFileLimit(); + + /** + * Gets the hard limit for open file handles (or network connections) that belong to the given process. + *

              + * Retrieving the hard limit for processes other than the calling process is only supported on Linux, FreeBsd and + * Solaris. + * + * @return the hard open file limit for the process if available. Returns -1 if the calling process is not the same + * as this OSProcess instance and the underlying operating system does not support retrieving the hard limit + * for other processes. + */ + long getHardOpenFileLimit(); + /** * Gets cumulative CPU usage of this process. *

              diff --git a/bus-health/src/main/java/org/aoju/bus/health/builtin/software/OperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/builtin/software/OperatingSystem.java index b8bc20e63e..626e29df25 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/builtin/software/OperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/builtin/software/OperatingSystem.java @@ -397,13 +397,7 @@ class OSVersionInfo { private final String versionStr; public OSVersionInfo(String version, String codeName, String buildNumber) { - // Insider Preview Windows 11 is marked as Windows 10 build 22000 - // Temporary code until JDK os.name matches up - if ("10".equals(version) && buildNumber.compareTo("22000") >= 0) { - this.version = "11"; - } else { - this.version = version; - } + this.version = version; this.codeName = codeName; this.buildNumber = buildNumber; diff --git a/bus-health/src/main/java/org/aoju/bus/health/linux/ProcPath.java b/bus-health/src/main/java/org/aoju/bus/health/linux/ProcPath.java index 8a4286a86f..c07234686c 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/linux/ProcPath.java +++ b/bus-health/src/main/java/org/aoju/bus/health/linux/ProcPath.java @@ -68,6 +68,7 @@ public final class ProcPath { public static final String SELF_STAT = PROC + "/self/stat"; public static final String STAT = PROC + "/stat"; public static final String SYS_FS_FILE_NR = PROC + "/sys/fs/file-nr"; + public static final String SYS_FS_FILE_MAX = PROC + "/sys/fs/file-max"; public static final String TASK_PATH = PROC + "/%d/task"; public static final String TASK_COMM = TASK_PATH + "/%d/comm"; public static final String TASK_STATUS = TASK_PATH + "/%d/status"; diff --git a/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxFileSystem.java b/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxFileSystem.java index 6fe6d708df..8504869ec3 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxFileSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxFileSystem.java @@ -272,4 +272,13 @@ public long getMaxFileDescriptors() { return getFileDescriptors(2); } + private static long getFileDescriptorsPerProcess() { + return Builder.getLongFromFile(ProcPath.SYS_FS_FILE_MAX); + } + + @Override + public long getMaxFileDescriptorsPerProcess() { + return getFileDescriptorsPerProcess(); + } + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOSProcess.java b/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOSProcess.java index 969e972ffc..189c3ec3f6 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOSProcess.java +++ b/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOSProcess.java @@ -25,12 +25,14 @@ ********************************************************************************/ package org.aoju.bus.health.linux.software; +import com.sun.jna.platform.unix.Resource; import org.aoju.bus.core.annotation.ThreadSafe; import org.aoju.bus.core.lang.RegEx; import org.aoju.bus.core.toolkit.StringKit; import org.aoju.bus.health.*; import org.aoju.bus.health.builtin.software.AbstractOSProcess; import org.aoju.bus.health.builtin.software.OSThread; +import org.aoju.bus.health.linux.LinuxLibc; import org.aoju.bus.health.linux.ProcPath; import org.aoju.bus.health.linux.drivers.proc.ProcessStat; import org.aoju.bus.logger.Logger; @@ -44,10 +46,7 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -99,8 +98,11 @@ public class LinuxOSProcess extends AbstractOSProcess { private long majorFaults; private long contextSwitches; - public LinuxOSProcess(int pid) { + private final LinuxOperatingSystem os; + + public LinuxOSProcess(int pid, LinuxOperatingSystem os) { super(pid); + this.os = os; updateAttributes(); } @@ -292,6 +294,28 @@ public long getOpenFiles() { return ProcessStat.getFileDescriptorFiles(getProcessID()).length; } + @Override + public long getSoftOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + LinuxLibc.INSTANCE.getrlimit(LinuxLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_cur; + } else { + return getProcessOpenFileLimit(getProcessID(), 1); + } + } + + @Override + public long getHardOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + LinuxLibc.INSTANCE.getrlimit(LinuxLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_max; + } else { + return getProcessOpenFileLimit(getProcessID(), 2); + } + } + @Override public int getBitness() { return this.bitness.get(); @@ -427,4 +451,21 @@ public int getOrder() { } } + private long getProcessOpenFileLimit(long processId, int index) { + final String limitsPath = String.format("/proc/%d/limits", processId); + if (!Files.exists(Paths.get(limitsPath))) { + return -1; // not supported + } + final List lines = Builder.readFile(limitsPath); + final Optional maxOpenFilesLine = lines.stream().filter(line -> line.startsWith("Max open files")) + .findFirst(); + if (!maxOpenFilesLine.isPresent()) { + return -1; + } + + // Split all non-Digits away -> ["", "{soft-limit}, "{hard-limit}"] + final String[] split = maxOpenFilesLine.get().split("\\D+"); + return Long.parseLong(split[index]); + } + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOperatingSystem.java index 8f509bb45b..165c5ead7d 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOperatingSystem.java @@ -517,7 +517,7 @@ public List getSessions() { @Override public OSProcess getProcess(int pid) { - OSProcess proc = new LinuxOSProcess(pid); + OSProcess proc = new LinuxOSProcess(pid, this); if (!proc.getState().equals(OSProcess.State.INVALID)) { return proc; } diff --git a/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacFileSystem.java b/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacFileSystem.java index f290561fbe..6433ee246c 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacFileSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacFileSystem.java @@ -275,4 +275,9 @@ public long getMaxFileDescriptors() { return SysctlKit.sysctl("kern.maxfiles", 0); } + @Override + public long getMaxFileDescriptorsPerProcess() { + return SysctlKit.sysctl("kern.maxfilesperproc", 0); + } + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOSProcess.java b/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOSProcess.java index 648acdb536..4608bf8e1e 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOSProcess.java +++ b/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOSProcess.java @@ -31,6 +31,7 @@ import com.sun.jna.platform.mac.SystemB.Group; import com.sun.jna.platform.mac.SystemB.Passwd; import com.sun.jna.platform.unix.LibCAPI.size_t; +import com.sun.jna.platform.unix.Resource; import org.aoju.bus.core.annotation.ThreadSafe; import org.aoju.bus.core.lang.Charset; import org.aoju.bus.core.lang.Normal; @@ -74,6 +75,7 @@ public class MacOSProcess extends AbstractOSProcess { private static final int SIDL = 4; // intermediate state in process creation private static final int SZOMB = 5; // intermediate state in process termination private static final int SSTOP = 6; // process being traced + private static final int MAC_RLIMIT_NOFILE = 8; private int majorVersion; private int minorVersion; @@ -103,11 +105,13 @@ public class MacOSProcess extends AbstractOSProcess { private long minorFaults; private long majorFaults; private long contextSwitches; + private final MacOperatingSystem os; - public MacOSProcess(int pid, int major, int minor) { + public MacOSProcess(int pid, int major, int minor, MacOperatingSystem os) { super(pid); this.majorVersion = major; this.minorVersion = minor; + this.os = os; updateAttributes(); } @@ -310,6 +314,28 @@ public long getOpenFiles() { return this.openFiles; } + @Override + public long getSoftOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + SystemB.INSTANCE.getrlimit(MAC_RLIMIT_NOFILE, rlimit); + return rlimit.rlim_cur; + } else { + return -1L; // not supported + } + } + + @Override + public long getHardOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + SystemB.INSTANCE.getrlimit(MAC_RLIMIT_NOFILE, rlimit); + return rlimit.rlim_max; + } else { + return -1L; // not supported + } + } + @Override public int getBitness() { return this.bitness; diff --git a/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOperatingSystem.java index b554830c28..7f68f42b91 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/mac/software/MacOperatingSystem.java @@ -172,7 +172,7 @@ public List queryAllProcesses() { @Override public OSProcess getProcess(int pid) { - OSProcess proc = new MacOSProcess(pid, this.major, this.minor); + OSProcess proc = new MacOSProcess(pid, this.major, this.minor, this); return proc.getState().equals(OSProcess.State.INVALID) ? null : proc; } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/aix/hardware/AixCentralProcessor.java b/bus-health/src/main/java/org/aoju/bus/health/unix/aix/hardware/AixCentralProcessor.java index 95efe17dfb..244bb1f6fb 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/aix/hardware/AixCentralProcessor.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/aix/hardware/AixCentralProcessor.java @@ -123,14 +123,19 @@ protected ProcessorIdentifier queryProcessorId() { protected Triple, List, List> initProcessorCounts() { this.config = PerfstatConfig.queryConfig(); + // Reporting "online" or "active" values can lead to nonsense so we go with max int physProcs = (int) config.numProcessors.max; if (physProcs < 1) { physProcs = 1; } - int lcpus = config.lcpus; + int lcpus = (int) config.vcpus.max; if (lcpus < 1) { lcpus = 1; } + // Sanity check to ensure lp/pp ratio >= 1 + if (physProcs > lcpus) { + physProcs = lcpus; + } int lpPerPp = lcpus / physProcs; // Get node and package mapping Map> nodePkgMap = Lssrad.queryNodesPackages(); diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixFileSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixFileSystem.java index a335bbd7ef..fa9c654d27 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixFileSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixFileSystem.java @@ -196,4 +196,15 @@ public long getMaxFileDescriptors() { return Builder.parseLongOrDefault(Executor.getFirstAnswer("ulimit -n"), 0L); } + @Override + public long getMaxFileDescriptorsPerProcess() { + final List lines = Builder.readFile("/etc/security/limits"); + for (final String line : lines) { + if (line.trim().startsWith("nofiles")) { + return Builder.parseLastLong(line, Long.MAX_VALUE); + } + } + return Long.MAX_VALUE; + } + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixOSProcess.java b/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixOSProcess.java index b627a4d031..93747e6a2b 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixOSProcess.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixOSProcess.java @@ -26,6 +26,7 @@ package org.aoju.bus.health.unix.aix.software; import com.sun.jna.Native; +import com.sun.jna.platform.unix.Resource; import com.sun.jna.platform.unix.aix.Perfstat.perfstat_process_t; import org.aoju.bus.core.annotation.ThreadSafe; import org.aoju.bus.core.lang.Normal; @@ -92,10 +93,13 @@ public class AixOSProcess extends AbstractOSProcess { private long upTime; private long bytesRead; private long bytesWritten; + private final AixOperatingSystem os; - public AixOSProcess(int pid, Pair userSysCpuTime, Supplier procCpu) { + public AixOSProcess(int pid, Pair userSysCpuTime, Supplier procCpu, + AixOperatingSystem os) { super(pid); this.procCpu = procCpu; + this.os = os; updateAttributes(userSysCpuTime); } @@ -278,6 +282,28 @@ public long getOpenFiles() { } } + @Override + public long getSoftOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + AixLibc.INSTANCE.getrlimit(AixLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_cur; + } else { + return -1L; // not supported + } + } + + @Override + public long getHardOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + AixLibc.INSTANCE.getrlimit(AixLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_max; + } else { + return -1L; // not supported + } + } + @Override public int getBitness() { return this.bitness.get(); diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixOperatingSystem.java index fe9d228b50..ec37086475 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/aix/software/AixOperatingSystem.java @@ -161,7 +161,7 @@ private List getProcessListFromProcfs(int pid) { // Keys of this map are pids for (Entry> entry : cpuMap.entrySet()) { - OSProcess proc = new AixOSProcess(entry.getKey(), entry.getValue(), procCpu); + OSProcess proc = new AixOSProcess(entry.getKey(), entry.getValue(), procCpu, this); if (proc.getState() != OSProcess.State.INVALID) { procs.add(proc); } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdFileSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdFileSystem.java index 83aed5cea0..ead1c7e333 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdFileSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdFileSystem.java @@ -173,4 +173,10 @@ public long getMaxFileDescriptors() { return BsdSysctlKit.sysctl("kern.maxfiles", 0); } + @Override + public long getMaxFileDescriptorsPerProcess() { + // On FreeBsd there is no process specific system-wide limit, so the general limit is returned + return getMaxFileDescriptors(); + } + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOSProcess.java b/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOSProcess.java index 00e825116b..7016876dbe 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOSProcess.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOSProcess.java @@ -28,6 +28,7 @@ import com.sun.jna.Memory; import com.sun.jna.Native; import com.sun.jna.platform.unix.LibCAPI.size_t; +import com.sun.jna.platform.unix.Resource; import org.aoju.bus.core.annotation.ThreadSafe; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.health.Builder; @@ -42,10 +43,9 @@ import org.aoju.bus.health.unix.freebsd.ProcstatKit; import org.aoju.bus.logger.Logger; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -89,11 +89,15 @@ public class FreeBsdOSProcess extends AbstractOSProcess { private String commandLineBackup; private final Supplier commandLine = Memoize.memoize(this::queryCommandLine); - public FreeBsdOSProcess(int pid, Map psMap) { + private final FreeBsdOperatingSystem os; + + public FreeBsdOSProcess(int pid, Map psMap, FreeBsdOperatingSystem os) { super(pid); + this.os = os; updateAttributes(psMap); } + @Override public String getName() { return this.name; @@ -264,6 +268,28 @@ public long getOpenFiles() { return ProcstatKit.getOpenFiles(getProcessID()); } + @Override + public long getSoftOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + FreeBsdLibc.INSTANCE.getrlimit(FreeBsdLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_cur; + } else { + return getProcessOpenFileLimit(getProcessID(), 1); + } + } + + @Override + public long getHardOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + FreeBsdLibc.INSTANCE.getrlimit(FreeBsdLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_max; + } else { + return getProcessOpenFileLimit(getProcessID(), 2); + } + } + @Override public int getBitness() { return this.bitness.get(); @@ -409,6 +435,23 @@ private boolean updateAttributes(Map return true; } + private long getProcessOpenFileLimit(long processId, int index) { + final String limitsPath = String.format("/proc/%d/limits", processId); + if (!Files.exists(Paths.get(limitsPath))) { + return -1; // not supported + } + final List lines = Builder.readFile(limitsPath); + final Optional maxOpenFilesLine = lines.stream().filter(line -> line.startsWith("Max open files")) + .findFirst(); + if (!maxOpenFilesLine.isPresent()) { + return -1; + } + + // Split all non-Digits away -> ["", "{soft-limit}, "{hard-limit}"] + final String[] split = maxOpenFilesLine.get().split("\\D+"); + return Long.parseLong(split[index]); + } + enum PsThreadColumns { TDNAME, LWP, STATE, ETIMES, SYSTIME, TIME, TDADDR, NIVCSW, NVCSW, MAJFLT, MINFLT, PRI } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOperatingSystem.java index aa975895c6..9231ea6677 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOperatingSystem.java @@ -69,7 +69,7 @@ private static List getProcessListFromPS(int pid) { return Executor.runNative(psCommand).stream().skip(1).parallel() .map(proc -> Builder.stringToEnumMap(PsKeywords.class, proc.trim(), ' ')).filter(hasKeywordArgs) .map(psMap -> new FreeBsdOSProcess( - pid < 0 ? Builder.parseIntOrDefault(psMap.get(PsKeywords.PID), 0) : pid, psMap)) + pid < 0 ? Builder.parseIntOrDefault(psMap.get(PsKeywords.PID), 0) : pid, psMap, this)) .filter(OperatingSystem.ProcessFiltering.VALID_PROCESS).collect(Collectors.toList()); } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdFileSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdFileSystem.java index f1fd6cf8f5..07151c4d93 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdFileSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdFileSystem.java @@ -174,4 +174,9 @@ public long getMaxFileDescriptors() { return OpenBsdSysctlKit.sysctl("kern.maxfiles", 0); } + @Override + public long getMaxFileDescriptorsPerProcess() { + return OpenBsdSysctlKit.sysctl("kern.maxfilesperproc", 0); + } + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOSProcess.java b/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOSProcess.java index 87f57c2a39..2e9a191e87 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOSProcess.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOSProcess.java @@ -29,6 +29,7 @@ import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.platform.unix.LibCAPI.size_t; +import com.sun.jna.platform.unix.Resource; import org.aoju.bus.core.annotation.ThreadSafe; import org.aoju.bus.health.Builder; import org.aoju.bus.health.Executor; @@ -101,9 +102,11 @@ public class OpenBsdOSProcess extends AbstractOSProcess { private long contextSwitches; private String commandLineBackup; private final Supplier commandLine = Memoize.memoize(this::queryCommandLine); + private final OpenBsdOperatingSystem os; - public OpenBsdOSProcess(int pid, Map psMap) { + public OpenBsdOSProcess(int pid, Map psMap, OpenBsdOperatingSystem os) { super(pid); + this.os = os; // OpenBSD does not maintain a compatibility layer. // Process bitness is OS bitness this.bitness = Native.LONG_SIZE * 8; @@ -300,6 +303,28 @@ public long getOpenFiles() { return FstatKit.getOpenFiles(getProcessID()); } + @Override + public long getSoftOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + OpenBsdLibc.INSTANCE.getrlimit(OpenBsdLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_cur; + } else { + return -1L; // not supported + } + } + + @Override + public long getHardOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + OpenBsdLibc.INSTANCE.getrlimit(OpenBsdLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_max; + } else { + return -1L; // not supported + } + } + @Override public int getBitness() { return this.bitness; diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOperatingSystem.java index c78bcd4cf5..8d6de6124d 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOperatingSystem.java @@ -72,7 +72,7 @@ private static List getProcessListFromPS(int pid) { // Check if last (thus all) value populated if (psMap.containsKey(PsKeywords.ARGS)) { procs.add(new OpenBsdOSProcess( - pid < 0 ? Builder.parseIntOrDefault(psMap.get(PsKeywords.PID), 0) : pid, psMap)); + pid < 0 ? Builder.parseIntOrDefault(psMap.get(PsKeywords.PID), 0) : pid, psMap, this)); } } return procs; diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisFileSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisFileSystem.java index 90c180a8e9..0494b6881e 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisFileSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisFileSystem.java @@ -207,4 +207,15 @@ public long getMaxFileDescriptors() { return 0L; } + @Override + public long getMaxFileDescriptorsPerProcess() { + final List lines = Builder.readFile("/etc/system"); + for (final String line : lines) { + if (line.startsWith("set rlim_fd_max")) { + return Builder.parseLastLong(line, 65536L); + } + } + return 65536L; // 65536 is the default value for the process open file limit in Solaris + } + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOSProcess.java b/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOSProcess.java index 8dadccf6bc..4971b6f0c8 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOSProcess.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOSProcess.java @@ -26,6 +26,7 @@ package org.aoju.bus.health.unix.solaris.software; import com.sun.jna.Native; +import com.sun.jna.platform.unix.Resource; import org.aoju.bus.core.annotation.ThreadSafe; import org.aoju.bus.core.lang.RegEx; import org.aoju.bus.core.lang.tuple.Pair; @@ -44,10 +45,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -88,9 +86,11 @@ public class SolarisOSProcess extends AbstractOSProcess { private long minorFaults; private long majorFaults; private long contextSwitches = 0; // default + private final SolarisOperatingSystem os; - public SolarisOSProcess(int pid) { + public SolarisOSProcess(int pid, SolarisOperatingSystem os) { super(pid); + this.os = os; updateAttributes(); } @@ -288,6 +288,28 @@ public long getOpenFiles() { } } + @Override + public long getSoftOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + SolarisLibc.INSTANCE.getrlimit(SolarisLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_cur; + } else { + return getProcessOpenFileLimit(getProcessID(), 1); + } + } + + @Override + public long getHardOpenFileLimit() { + if (getProcessID() == this.os.getProcessId()) { + final Resource.Rlimit rlimit = new Resource.Rlimit(); + SolarisLibc.INSTANCE.getrlimit(SolarisLibc.RLIMIT_NOFILE, rlimit); + return rlimit.rlim_max; + } else { + return getProcessOpenFileLimit(getProcessID(), 2); + } + } + @Override public int getBitness() { return this.bitness.get(); @@ -396,4 +418,21 @@ public boolean updateAttributes() { return true; } + private long getProcessOpenFileLimit(final long processId, final int index) { + final List output = Executor.runNative("plimit " + processId); + if (output.isEmpty()) { + return -1; // not supported + } + + final Optional nofilesLine = output.stream().filter(line -> line.trim().startsWith("nofiles")) + .findFirst(); + if (!nofilesLine.isPresent()) { + return -1; + } + + // Split all non-Digits away -> ["", "{soft-limit}, "{hard-limit}"] + final String[] split = nofilesLine.get().split("\\D+"); + return Long.parseLong(split[index]); + } + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOperatingSystem.java index f552ae3246..e478f3290e 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOperatingSystem.java @@ -109,7 +109,7 @@ private static List getProcessListFromProcfs(int pid) { // Iterate files for (File pidFile : numericFiles) { int pidNum = Builder.parseIntOrDefault(pidFile.getName(), 0); - OSProcess proc = new SolarisOSProcess(pidNum); + OSProcess proc = new SolarisOSProcess(pidNum, this); if (proc.getState() != OSProcess.State.INVALID) { procs.add(proc); } diff --git a/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsFileSystem.java b/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsFileSystem.java index e0c3d8bee8..abd2fb50a3 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsFileSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsFileSystem.java @@ -57,6 +57,8 @@ @ThreadSafe public class WindowsFileSystem extends AbstractFileSystem { + public static final long MAX_WINDOWS_HANDLES; + private static final int BUFSIZE = 255; private static final int SEM_FAILCRITICALERRORS = 0x0001; @@ -80,7 +82,6 @@ public class WindowsFileSystem extends AbstractFileSystem { private static final int FILE_VOLUME_QUOTAS = 0x00000020; private static final Map OPTIONS_MAP = new HashMap<>(); - private static final long MAX_WINDOWS_HANDLES; static { OPTIONS_MAP.put(FILE_CASE_PRESERVED_NAMES, "casepn"); @@ -307,4 +308,9 @@ public long getMaxFileDescriptors() { return MAX_WINDOWS_HANDLES; } + @Override + public long getMaxFileDescriptorsPerProcess() { + return MAX_WINDOWS_HANDLES; + } + } diff --git a/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsOSProcess.java b/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsOSProcess.java index d7b139a4ed..d0ec7233f6 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsOSProcess.java +++ b/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsOSProcess.java @@ -251,6 +251,16 @@ public long getOpenFiles() { return this.openFiles; } + @Override + public long getSoftOpenFileLimit() { + return WindowsFileSystem.MAX_WINDOWS_HANDLES; + } + + @Override + public long getHardOpenFileLimit() { + return WindowsFileSystem.MAX_WINDOWS_HANDLES; + } + @Override public int getBitness() { return this.bitness; diff --git a/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsOperatingSystem.java index 1bfe8b45d6..36ab4f60d6 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/windows/software/WindowsOperatingSystem.java @@ -344,13 +344,11 @@ public Pair queryFamilyVersionInfo() { if (version.startsWith("Windows ")) { version = version.substring(8); } - - String sp = null; int suiteMask = 0; - String buildNumber = null; + String buildNumber = Normal.EMPTY; WmiResult versionInfo = Win32OperatingSystem.queryOsVersion(); if (versionInfo.getResultCount() > 0) { - sp = WmiKit.getString(versionInfo, OSVersionProperty.CSDVERSION, 0); + String sp = WmiKit.getString(versionInfo, OSVersionProperty.CSDVERSION, 0); if (!sp.isEmpty() && !Normal.UNKNOWN.equals(sp)) { version = version + " " + sp.replace("Service Pack ", "SP"); } @@ -358,6 +356,12 @@ public Pair queryFamilyVersionInfo() { buildNumber = WmiKit.getString(versionInfo, OSVersionProperty.BUILDNUMBER, 0); } String codeName = parseCodeName(suiteMask); + // Older JDKs don't recognize Win11 and Server2022 + if ("10".equals(version) && buildNumber.compareTo("22000") >= 0) { + version = "11"; + } else if ("Server 2019".equals(version) && buildNumber.compareTo("20347") > 0) { + version = "Server 2022"; + } return Pair.of("Windows", new OSVersionInfo(version, codeName, buildNumber)); } From 1300fb16576ab61099c85d6bdc461bf85b2e0208 Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Wed, 16 Nov 2022 13:44:56 +0800 Subject: [PATCH 17/19] v6.6.0 --- README.md | 4 ++-- bus-all/pom.xml | 2 +- bus-base/pom.xml | 4 ++-- bus-bom/pom.xml | 2 +- bus-cache/pom.xml | 4 ++-- bus-core/README.md | 2 +- bus-core/pom.xml | 2 +- bus-core/src/main/java/org/aoju/bus/core/Version.java | 2 +- bus-cron/pom.xml | 2 +- bus-crypto/pom.xml | 2 +- bus-extra/pom.xml | 2 +- bus-gitlab/README.md | 2 +- bus-gitlab/pom.xml | 2 +- bus-goalie/pom.xml | 4 ++-- bus-health/pom.xml | 2 +- bus-http/pom.xml | 2 +- bus-image/pom.xml | 2 +- bus-limiter/pom.xml | 4 ++-- bus-logger/pom.xml | 2 +- bus-mapper/pom.xml | 2 +- bus-notify/README.md | 2 +- bus-notify/pom.xml | 2 +- bus-oauth/README.md | 2 +- bus-oauth/pom.xml | 2 +- bus-office/pom.xml | 2 +- bus-opencv/pom.xml | 2 +- bus-pager/README.md | 2 +- bus-pager/pom.xml | 2 +- bus-pay/pom.xml | 4 ++-- bus-proxy/pom.xml | 2 +- bus-sensitive/pom.xml | 2 +- bus-setting/pom.xml | 2 +- bus-shade/pom.xml | 4 ++-- bus-socket/pom.xml | 2 +- bus-spring/pom.xml | 4 ++-- bus-starter/pom.xml | 4 ++-- bus-storage/pom.xml | 2 +- bus-tracer/pom.xml | 4 ++-- bus-validate/pom.xml | 2 +- 39 files changed, 49 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index ae77129438..88a8672440 100755 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

              - + @@ -97,7 +97,7 @@ Bus (应用/服务总线) 是一个基础框架、服务套件,它基于Java17 org.aoju bus-all - 6.5.9 + 6.6.0 ``` diff --git a/bus-all/pom.xml b/bus-all/pom.xml index 6ef61c6166..8cb601d35c 100755 --- a/bus-all/pom.xml +++ b/bus-all/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-all - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-base/pom.xml b/bus-base/pom.xml index b2ff006745..6bff159a42 100755 --- a/bus-base/pom.xml +++ b/bus-base/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-base - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 1.18.24 2.2 diff --git a/bus-bom/pom.xml b/bus-bom/pom.xml index 74e4ddb936..d99f6737de 100755 --- a/bus-bom/pom.xml +++ b/bus-bom/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-bom - 6.5.9 + 6.6.0 pom ${project.artifactId} diff --git a/bus-cache/pom.xml b/bus-cache/pom.xml index 85d77dc852..8b7c0a42e5 100755 --- a/bus-cache/pom.xml +++ b/bus-cache/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-cache - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 1.18.24 5.1.0 4.2.3 diff --git a/bus-core/README.md b/bus-core/README.md index 65d26c6ae3..75544d78ca 100755 --- a/bus-core/README.md +++ b/bus-core/README.md @@ -14,7 +14,7 @@ org.aoju bus-core - 6.5.9 + 6.6.0 ``` diff --git a/bus-core/pom.xml b/bus-core/pom.xml index f342d93a74..8934d89200 100755 --- a/bus-core/pom.xml +++ b/bus-core/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-core - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-core/src/main/java/org/aoju/bus/core/Version.java b/bus-core/src/main/java/org/aoju/bus/core/Version.java index c5d9b08e79..cee07c66cf 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/Version.java +++ b/bus-core/src/main/java/org/aoju/bus/core/Version.java @@ -60,7 +60,7 @@ public class Version { * @return 项目的版本号 */ public static String get() { - return "6.5.9.RELEASE"; + return "6.6.0.RELEASE"; } /** diff --git a/bus-cron/pom.xml b/bus-cron/pom.xml index 5ef9326c63..89deaeda7f 100755 --- a/bus-cron/pom.xml +++ b/bus-cron/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-cron - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-crypto/pom.xml b/bus-crypto/pom.xml index e1b9faac45..988e7b0ec8 100755 --- a/bus-crypto/pom.xml +++ b/bus-crypto/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-crypto - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-extra/pom.xml b/bus-extra/pom.xml index 0e980a2e36..42303a325b 100644 --- a/bus-extra/pom.xml +++ b/bus-extra/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-extra - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-gitlab/README.md b/bus-gitlab/README.md index 05c1bf9bbc..2da0c4980e 100755 --- a/bus-gitlab/README.md +++ b/bus-gitlab/README.md @@ -70,7 +70,7 @@ dependencies { org.aoju bus-gitlab - 6.5.9 + 6.6.0 ``` diff --git a/bus-gitlab/pom.xml b/bus-gitlab/pom.xml index f82046d789..aaa60f71df 100755 --- a/bus-gitlab/pom.xml +++ b/bus-gitlab/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-gitlab - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-goalie/pom.xml b/bus-goalie/pom.xml index 5eccfd349c..f2c69a69bd 100644 --- a/bus-goalie/pom.xml +++ b/bus-goalie/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-goalie - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 1.18.24 31.1-jre diff --git a/bus-health/pom.xml b/bus-health/pom.xml index 666d2bb1c2..aaf0608f1f 100755 --- a/bus-health/pom.xml +++ b/bus-health/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-health - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-http/pom.xml b/bus-http/pom.xml index b7b82b6ed2..2e1e831146 100755 --- a/bus-http/pom.xml +++ b/bus-http/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-http - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-image/pom.xml b/bus-image/pom.xml index 68fdfe8b21..066cc25f2d 100755 --- a/bus-image/pom.xml +++ b/bus-image/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-image - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-limiter/pom.xml b/bus-limiter/pom.xml index 83ae78ada1..b640ab0566 100755 --- a/bus-limiter/pom.xml +++ b/bus-limiter/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-limiter - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 5.3.0 3.17.4 31.1-jre diff --git a/bus-logger/pom.xml b/bus-logger/pom.xml index 71ea504304..7067ed1c5f 100644 --- a/bus-logger/pom.xml +++ b/bus-logger/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-logger - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-mapper/pom.xml b/bus-mapper/pom.xml index 395757ab28..594c98bdef 100755 --- a/bus-mapper/pom.xml +++ b/bus-mapper/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-mapper - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-notify/README.md b/bus-notify/README.md index 4f62022ad9..385f515bf2 100755 --- a/bus-notify/README.md +++ b/bus-notify/README.md @@ -11,7 +11,7 @@ org.aoju bus-notify - 6.5.9 + 6.6.0 ``` diff --git a/bus-notify/pom.xml b/bus-notify/pom.xml index 401ec34942..2c57a9ec1a 100755 --- a/bus-notify/pom.xml +++ b/bus-notify/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-notify - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-oauth/README.md b/bus-oauth/README.md index 0f3d77c5e9..07ef6a4769 100755 --- a/bus-oauth/README.md +++ b/bus-oauth/README.md @@ -16,7 +16,7 @@ org.aoju bus-oauth - 6.5.9 + 6.6.0 ``` diff --git a/bus-oauth/pom.xml b/bus-oauth/pom.xml index 3a3b2b8a82..7ded0d5929 100755 --- a/bus-oauth/pom.xml +++ b/bus-oauth/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-oauth - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-office/pom.xml b/bus-office/pom.xml index 1ea9e45369..3e5a5774ca 100755 --- a/bus-office/pom.xml +++ b/bus-office/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-office - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-opencv/pom.xml b/bus-opencv/pom.xml index f316699a02..1f8070dc94 100755 --- a/bus-opencv/pom.xml +++ b/bus-opencv/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-opencv - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-pager/README.md b/bus-pager/README.md index 525dbbfab7..41adb301f4 100755 --- a/bus-pager/README.md +++ b/bus-pager/README.md @@ -42,7 +42,7 @@ org.aoju bus-pager - 6.5.9 + 6.6.0 ``` diff --git a/bus-pager/pom.xml b/bus-pager/pom.xml index 7c959e8f13..7cf066ec16 100755 --- a/bus-pager/pom.xml +++ b/bus-pager/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-pager - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-pay/pom.xml b/bus-pay/pom.xml index c9b9c7902b..d9c60245d4 100644 --- a/bus-pay/pom.xml +++ b/bus-pay/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-pay - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -49,7 +49,7 @@ 4.5.13 3.5.0 1.70 - 2.7.3 + 2.7.5 4.3.9.B 4.33.1.ALL 2.0.0 diff --git a/bus-proxy/pom.xml b/bus-proxy/pom.xml index 8e89518d2e..fbd954e7b5 100755 --- a/bus-proxy/pom.xml +++ b/bus-proxy/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-proxy - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-sensitive/pom.xml b/bus-sensitive/pom.xml index 456e21b589..65a1162043 100755 --- a/bus-sensitive/pom.xml +++ b/bus-sensitive/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-sensitive - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-setting/pom.xml b/bus-setting/pom.xml index b477fc6db7..dd1abaa246 100755 --- a/bus-setting/pom.xml +++ b/bus-setting/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-setting - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-shade/pom.xml b/bus-shade/pom.xml index a68e38e625..cf2fd4dd54 100755 --- a/bus-shade/pom.xml +++ b/bus-shade/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-shade - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 1.21 2.3.31 1.18.24 diff --git a/bus-socket/pom.xml b/bus-socket/pom.xml index f456a5d2ea..527a3cc82b 100755 --- a/bus-socket/pom.xml +++ b/bus-socket/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-socket - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-spring/pom.xml b/bus-spring/pom.xml index b3a863a8df..7fb0b3f759 100755 --- a/bus-spring/pom.xml +++ b/bus-spring/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-spring - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 1.18.24 diff --git a/bus-starter/pom.xml b/bus-starter/pom.xml index 57283a43de..173d50c96a 100755 --- a/bus-starter/pom.xml +++ b/bus-starter/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-starter - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 1.18.24 1.9.9.1 2.2 diff --git a/bus-storage/pom.xml b/bus-storage/pom.xml index afbaf075cc..359de50633 100755 --- a/bus-storage/pom.xml +++ b/bus-storage/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-storage - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-tracer/pom.xml b/bus-tracer/pom.xml index 2ea7f91252..9fdaca7bdc 100755 --- a/bus-tracer/pom.xml +++ b/bus-tracer/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-tracer - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 1.18.24 3.0.10 4.0.4 diff --git a/bus-validate/pom.xml b/bus-validate/pom.xml index 7d332d7b49..5f8ab83442 100755 --- a/bus-validate/pom.xml +++ b/bus-validate/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-validate - 6.5.9 + 6.6.0 jar ${project.artifactId} From 6a43dc4f3edc8d60583b089e0d4aaf3e17fb2337 Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Wed, 16 Nov 2022 14:06:34 +0800 Subject: [PATCH 18/19] v6.6.0 --- .../aoju/bus/health/linux/software/LinuxOperatingSystem.java | 4 ++-- .../health/unix/freebsd/software/FreeBsdOperatingSystem.java | 2 +- .../health/unix/openbsd/software/OpenBsdOperatingSystem.java | 2 +- .../health/unix/solaris/software/SolarisOperatingSystem.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOperatingSystem.java index 165c5ead7d..6cc45dfd06 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/linux/software/LinuxOperatingSystem.java @@ -130,10 +130,10 @@ public LinuxOperatingSystem() { super.getVersionInfo(); } - private static List queryProcessList(Set descendantPids) { + private List queryProcessList(Set descendantPids) { List procs = new ArrayList<>(); for (int pid : descendantPids) { - OSProcess proc = new LinuxOSProcess(pid); + OSProcess proc = new LinuxOSProcess(pid, this); if (!proc.getState().equals(OSProcess.State.INVALID)) { procs.add(proc); } diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOperatingSystem.java index 9231ea6677..1132ffa711 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/freebsd/software/FreeBsdOperatingSystem.java @@ -59,7 +59,7 @@ public class FreeBsdOperatingSystem extends AbstractOperatingSystem { .collect(Collectors.joining(",")); private static final long BOOTTIME = querySystemBootTime(); - private static List getProcessListFromPS(int pid) { + private List getProcessListFromPS(int pid) { String psCommand = "ps -awwxo " + PS_COMMAND_ARGS; if (pid >= 0) { psCommand += " -p " + pid; diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOperatingSystem.java index 8d6de6124d..7d031961f2 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/openbsd/software/OpenBsdOperatingSystem.java @@ -52,7 +52,7 @@ public class OpenBsdOperatingSystem extends AbstractOperatingSystem { .collect(Collectors.joining(",")); private static final long BOOTTIME = querySystemBootTime(); - private static List getProcessListFromPS(int pid) { + private List getProcessListFromPS(int pid) { List procs = new ArrayList<>(); // https://man.openbsd.org/ps#KEYWORDS // missing are threadCount and kernelTime which is included in cputime diff --git a/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOperatingSystem.java b/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOperatingSystem.java index e478f3290e..117dce1b21 100644 --- a/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOperatingSystem.java +++ b/bus-health/src/main/java/org/aoju/bus/health/unix/solaris/software/SolarisOperatingSystem.java @@ -82,11 +82,11 @@ public class SolarisOperatingSystem extends AbstractOperatingSystem { HAS_KSTAT2 = lib != null; } - private static List queryAllProcessesFromPrStat() { + private List queryAllProcessesFromPrStat() { return getProcessListFromProcfs(-1); } - private static List getProcessListFromProcfs(int pid) { + private List getProcessListFromProcfs(int pid) { List procs = new ArrayList<>(); File[] numericFiles = null; From 8b734b1da16a076cb3f97feaaa58c6546e14b7ac Mon Sep 17 00:00:00 2001 From: Kimi Liu <839536@qq.com> Date: Wed, 16 Nov 2022 14:49:10 +0800 Subject: [PATCH 19/19] v6.6.0 --- .../aoju/bus/http/metric/suffix/Suffixes.java | 348 ++++++++++++++++++ .../aoju/bus/http/metric/suffix/suffixes.gz | Bin 0 -> 36752 bytes 2 files changed, 348 insertions(+) create mode 100644 bus-http/src/main/java/org/aoju/bus/http/metric/suffix/Suffixes.java create mode 100644 bus-http/src/main/java/org/aoju/bus/http/metric/suffix/suffixes.gz diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/suffix/Suffixes.java b/bus-http/src/main/java/org/aoju/bus/http/metric/suffix/Suffixes.java new file mode 100644 index 0000000000..7bb1717376 --- /dev/null +++ b/bus-http/src/main/java/org/aoju/bus/http/metric/suffix/Suffixes.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2017 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.aoju.bus.http.metric.suffix; + +import org.aoju.bus.core.io.source.BufferSource; +import org.aoju.bus.core.io.source.GzipSource; +import org.aoju.bus.core.toolkit.IoKit; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.net.IDN; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * A database of public suffixes provided by + * publicsuffix.org. + */ +public final class Suffixes { + + public static final String PUBLIC_SUFFIX_RESOURCE = "suffixes.gz"; + + private static final byte[] WILDCARD_LABEL = new byte[]{'*'}; + private static final String[] EMPTY_RULE = new String[0]; + private static final String[] PREVAILING_RULE = new String[]{"*"}; + + private static final byte EXCEPTION_MARKER = '!'; + + private static final Suffixes instance = new Suffixes(); + + /** + * True after we've attempted to read the list for the first time. + */ + private final AtomicBoolean listRead = new AtomicBoolean(false); + + /** + * Used for concurrent threads reading the list for the first time. + */ + private final CountDownLatch readCompleteLatch = new CountDownLatch(1); + + // The lists are held as a large array of UTF-8 bytes. This is to avoid allocating lots of strings + // that will likely never be used. Each rule is separated by '\n'. Please see the + // PublicSuffixListGenerator class for how these lists are generated. + // Guarded by this. + private byte[] publicSuffixListBytes; + private byte[] publicSuffixExceptionListBytes; + + public static Suffixes get() { + return instance; + } + + private static String binarySearchBytes(byte[] bytesToSearch, byte[][] labels, int labelIndex) { + int low = 0; + int high = bytesToSearch.length; + String match = null; + while (low < high) { + int mid = (low + high) / 2; + // Search for a '\n' that marks the start of a value. Don't go back past the start of the + // array. + while (mid > -1 && bytesToSearch[mid] != '\n') { + mid--; + } + mid++; + + // Now look for the ending '\n'. + int end = 1; + while (bytesToSearch[mid + end] != '\n') { + end++; + } + int publicSuffixLength = (mid + end) - mid; + + // Compare the bytes. Note that the file stores UTF-8 encoded bytes, so we must compare the + // unsigned bytes. + int compareResult; + int currentLabelIndex = labelIndex; + int currentLabelByteIndex = 0; + int publicSuffixByteIndex = 0; + + boolean expectDot = false; + while (true) { + int byte0; + if (expectDot) { + byte0 = '.'; + expectDot = false; + } else { + byte0 = labels[currentLabelIndex][currentLabelByteIndex] & 0xff; + } + + int byte1 = bytesToSearch[mid + publicSuffixByteIndex] & 0xff; + + compareResult = byte0 - byte1; + if (compareResult != 0) break; + + publicSuffixByteIndex++; + currentLabelByteIndex++; + if (publicSuffixByteIndex == publicSuffixLength) break; + + if (labels[currentLabelIndex].length == currentLabelByteIndex) { + // We've exhausted our current label. Either there are more labels to compare, in which + // case we expect a dot as the next character. Otherwise, we've checked all our labels. + if (currentLabelIndex == labels.length - 1) { + break; + } else { + currentLabelIndex++; + currentLabelByteIndex = -1; + expectDot = true; + } + } + } + + if (compareResult < 0) { + high = mid - 1; + } else if (compareResult > 0) { + low = mid + end + 1; + } else { + // We found a match, but are the lengths equal? + int publicSuffixBytesLeft = publicSuffixLength - publicSuffixByteIndex; + int labelBytesLeft = labels[currentLabelIndex].length - currentLabelByteIndex; + for (int i = currentLabelIndex + 1; i < labels.length; i++) { + labelBytesLeft += labels[i].length; + } + + if (labelBytesLeft < publicSuffixBytesLeft) { + high = mid - 1; + } else if (labelBytesLeft > publicSuffixBytesLeft) { + low = mid + end + 1; + } else { + // Found a match. + match = new String(bytesToSearch, mid, publicSuffixLength, UTF_8); + break; + } + } + } + return match; + } + + /** + * Returns the effective top-level domain plus one (eTLD+1) by referencing the public suffix list. + * Returns null if the domain is a public suffix or a private address. + * + *

              Here are some examples:

              {@code
              +     * assertEquals("google.com", getEffectiveTldPlusOne("google.com"));
              +     * assertEquals("google.com", getEffectiveTldPlusOne("www.google.com"));
              +     * assertNull(getEffectiveTldPlusOne("com"));
              +     * assertNull(getEffectiveTldPlusOne("localhost"));
              +     * assertNull(getEffectiveTldPlusOne("mymacbook"));
              +     * }
              + * + * @param domain A canonicalized domain. An International Domain Name (IDN) should be punycode + * encoded. + */ + public String getEffectiveTldPlusOne(String domain) { + if (domain == null) throw new NullPointerException("domain == null"); + + // We use UTF-8 in the list so we need to convert to Unicode. + String unicodeDomain = IDN.toUnicode(domain); + String[] domainLabels = unicodeDomain.split("\\."); + String[] rule = findMatchingRule(domainLabels); + if (domainLabels.length == rule.length && rule[0].charAt(0) != EXCEPTION_MARKER) { + // The domain is a public suffix. + return null; + } + + int firstLabelOffset; + if (rule[0].charAt(0) == EXCEPTION_MARKER) { + // Exception rules hold the effective TLD plus one. + firstLabelOffset = domainLabels.length - rule.length; + } else { + // Otherwise the rule is for a public suffix, so we must take one more label. + firstLabelOffset = domainLabels.length - (rule.length + 1); + } + + StringBuilder effectiveTldPlusOne = new StringBuilder(); + String[] punycodeLabels = domain.split("\\."); + for (int i = firstLabelOffset; i < punycodeLabels.length; i++) { + effectiveTldPlusOne.append(punycodeLabels[i]).append('.'); + } + effectiveTldPlusOne.deleteCharAt(effectiveTldPlusOne.length() - 1); + + return effectiveTldPlusOne.toString(); + } + + private String[] findMatchingRule(String[] domainLabels) { + if (!listRead.get() && listRead.compareAndSet(false, true)) { + readTheListUninterruptibly(); + } else { + try { + readCompleteLatch.await(); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); // Retain interrupted status. + } + } + + synchronized (this) { + if (publicSuffixListBytes == null) { + throw new IllegalStateException("Unable to load " + PUBLIC_SUFFIX_RESOURCE + " resource " + + "from the classpath."); + } + } + + // Break apart the domain into UTF-8 labels, i.e. foo.bar.com turns into [foo, bar, com]. + byte[][] domainLabelsUtf8Bytes = new byte[domainLabels.length][]; + for (int i = 0; i < domainLabels.length; i++) { + domainLabelsUtf8Bytes[i] = domainLabels[i].getBytes(UTF_8); + } + + // Start by looking for exact matches. We start at the leftmost label. For example, foo.bar.com + // will look like: [foo, bar, com], [bar, com], [com]. The longest matching rule wins. + String exactMatch = null; + for (int i = 0; i < domainLabelsUtf8Bytes.length; i++) { + String rule = binarySearchBytes(publicSuffixListBytes, domainLabelsUtf8Bytes, i); + if (rule != null) { + exactMatch = rule; + break; + } + } + + // In theory, wildcard rules are not restricted to having the wildcard in the leftmost position. + // In practice, wildcards are always in the leftmost position. For now, this implementation + // cheats and does not attempt every possible permutation. Instead, it only considers wildcards + // in the leftmost position. We assert this fact when we generate the public suffix file. If + // this assertion ever fails we'll need to refactor this implementation. + String wildcardMatch = null; + if (domainLabelsUtf8Bytes.length > 1) { + byte[][] labelsWithWildcard = domainLabelsUtf8Bytes.clone(); + for (int labelIndex = 0; labelIndex < labelsWithWildcard.length - 1; labelIndex++) { + labelsWithWildcard[labelIndex] = WILDCARD_LABEL; + String rule = binarySearchBytes(publicSuffixListBytes, labelsWithWildcard, labelIndex); + if (rule != null) { + wildcardMatch = rule; + break; + } + } + } + + // Exception rules only apply to wildcard rules, so only try it if we matched a wildcard. + String exception = null; + if (wildcardMatch != null) { + for (int labelIndex = 0; labelIndex < domainLabelsUtf8Bytes.length - 1; labelIndex++) { + String rule = binarySearchBytes( + publicSuffixExceptionListBytes, domainLabelsUtf8Bytes, labelIndex); + if (rule != null) { + exception = rule; + break; + } + } + } + + if (exception != null) { + // Signal we've identified an exception rule. + exception = "!" + exception; + return exception.split("\\."); + } else if (exactMatch == null && wildcardMatch == null) { + return PREVAILING_RULE; + } + + String[] exactRuleLabels = exactMatch != null + ? exactMatch.split("\\.") + : EMPTY_RULE; + + String[] wildcardRuleLabels = wildcardMatch != null + ? wildcardMatch.split("\\.") + : EMPTY_RULE; + + return exactRuleLabels.length > wildcardRuleLabels.length + ? exactRuleLabels + : wildcardRuleLabels; + } + + /** + * Reads the public suffix list treating the operation as uninterruptible. We always want to read + * the list otherwise we'll be left in a bad state. If the thread was interrupted prior to this + * operation, it will be re-interrupted after the list is read. + */ + private void readTheListUninterruptibly() { + boolean interrupted = false; + try { + while (true) { + try { + readTheList(); + return; + } catch (InterruptedIOException e) { + Thread.interrupted(); // Temporarily clear the interrupted state. + interrupted = true; + } catch (IOException e) { + + return; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); // Retain interrupted status. + } + } + } + + private void readTheList() throws IOException { + byte[] publicSuffixListBytes; + byte[] publicSuffixExceptionListBytes; + + InputStream resource = Suffixes.class.getResourceAsStream(PUBLIC_SUFFIX_RESOURCE); + if (resource == null) return; + + try (BufferSource BufferSource = IoKit.buffer(new GzipSource(IoKit.source(resource)))) { + int totalBytes = BufferSource.readInt(); + publicSuffixListBytes = new byte[totalBytes]; + BufferSource.readFully(publicSuffixListBytes); + + int totalExceptionBytes = BufferSource.readInt(); + publicSuffixExceptionListBytes = new byte[totalExceptionBytes]; + BufferSource.readFully(publicSuffixExceptionListBytes); + } + + synchronized (this) { + this.publicSuffixListBytes = publicSuffixListBytes; + this.publicSuffixExceptionListBytes = publicSuffixExceptionListBytes; + } + + readCompleteLatch.countDown(); + } + + /** + * Visible for testing. + */ + void setListBytes(byte[] publicSuffixListBytes, byte[] publicSuffixExceptionListBytes) { + this.publicSuffixListBytes = publicSuffixListBytes; + this.publicSuffixExceptionListBytes = publicSuffixExceptionListBytes; + listRead.set(true); + readCompleteLatch.countDown(); + } + +} diff --git a/bus-http/src/main/java/org/aoju/bus/http/metric/suffix/suffixes.gz b/bus-http/src/main/java/org/aoju/bus/http/metric/suffix/suffixes.gz new file mode 100644 index 0000000000000000000000000000000000000000..795e941ae6bbcfb41b54917773b97341f25383f3 GIT binary patch literal 36752 zcmV)5K*_%!iwFP!000000F1p$ll-a@CuVQ^qfA6~+}GS0e;hq1iIM;vKvk{UdF}8w z@a9DLT!$lO>xi@Pu07w&`O5?Z>i?aY=q}|$;tfdx3H(q0^Z)*T{D;rKC}TbcXFmSJ zXE#1|aQ^cZK&*obpH{_}^9$(LsGgrgM`%Cd@qT}f-L{2K)%iFRXTWEJ4WAEpC>xWf z^xXESZh!skO!s!SdxiGR{}`V=WpzYlW^PZTlR|(tgsO%*cYiGx6V5XZW-(Ap@L&NHJ6q7-4wwv71iEorwpFNUl zLAls%lm&!s6CP_9@6MagFjrKVKj9~Fe%tk?R%IxdoSWf49-kT>1j=~lorDsG`Ey4e z1!c!BrIYZW+vtT&4bktWNe2p758?bMy43{PZ0ONeD>4FU;gsf*XzZ}{wwIe{%zh+ z7z~G5Ao=&#zyJL|eQtU{v4@Yp{`1yf@zwl$)yJ1I_=s&`^3(J4z;j~G z{?6?DeDU**r{%a<#y9?QV;?-b_58i(?*sb~_)8Q8JsO8|L49+Nsc&fH z^)&ap9&UrP(Yv+0D5=!G-vhuvU&ivD`qqK{B@s+zc7DB5K9sMx#4|zN9|H+7vjg?8 zRdU1Zr~{`G6!N|Iot+dZA`7V(+7!0oDu3mLj*8>Qw=WXDQuytwhjbmwCla{)LKZLG zq(Aw4s}e&~rn{v(Qs+t>dSB)DGE&i!aU}Yan5%z9I2RZ#>Cc_E!Z`2*ts@gLD8kb*a^_)GhWM z;MP&YT-Q75_8XWe&4hrz&E3r$zu9hMHA{p8xYX`Qz#g~zzOIQII@D9RpSyjMU`Q#$ ztH>>TO%+M1Gqreqzon^Lifs4Eh6*P=w9 zrDv#=0yXX3bjHQZqk7(!n<4eDWUuM>x;+=NyjKvO_DXj@7}R0?wv(j1@~Qb=Sn7*< zxDX00A{^uwJ?@+(`jZOMr!fMN5aVXmoP(4$fgvhC^5+RVLm~(@J*CQW8Q5+;pjiVm z)Tlqvik2(`Ap$GO4zd*w#L-+TmL#22Mth1a((}VkiaN)PMlAiFFyc@`#D~!xiA&Y_ zMhRS1j2$E>c`Y8|1=HpA`66>J-!pmr+ClZQr5(JZZna!I{R zPO4RR@^m>H97p{I;XFv~fm%o<9P$Q65Z3#cOK?nj%F!+mo8OIwXLQG6YD=mu9)$EcGuWghHetx+2L#6{J@*bc)@& zNc*q9`P=`%H-G=P-vqbE`M(hkzyI&V<^1N~#-#`n!O;0{lG-9zwvNW+dh9Y0c}z&{-&Ud7QK4>5-9f7|LNw@1E>4v=7!?&yT691gLe=J#&mS07?|c^c^->^!}YFF=XheEuAD(CXRE0alSlWp z<{79PL|6DZYhURHVQ{OyZF<1G1L_eoYI>xy4$7wr7v_n&rjhFe`SOdg2=}uS+6zTt z(wTQiBy&3xPL-Q8Ce&JwG(ujeHgLd;HVH``YgnFcO|e>X-xo-8uj^c}JRl~pJ^|A3 zE?k;!2%ihS4wxQkV-e_|a;~XnA*hkYBp961%b=?lmS-~p1<*ePV*UgWjRG)h(ZIzA z_c?6>(P984GF0&gfi^)*G7ua{5m2F^=j=hnC{c={J_H)DSyxS4gq;M&sVC4Uk@OHd z-iFR>qOisi*~orGRG>y)MjD-d0gysM#T7}RjHkXB4Yzp8WXK1qDwA#pqr@_oDjuWr zR*|%!V%tmj`+!6d95p|9u8W|<(<+jOGLYG)oz(Rg2fHs`E&F%;1 z`_Gd_9ZraVQI)#3W9qx4kSJ;zx@#D-N_CaGZ_L0jNF}sKc}NUtWZ^JZb2^WX%Js>) z{rrsN_(|QHPL1(NUPwJBELZubCM~sN*cANJk#OW1CKyj2JsmVOFkkfFJ!+NgMIlpJ zevw~a`V@fFP7O8IcNT0%*VE|m$W7;v(p=6$JxE@(#Y{E-%`)_@eBbE@>Dv$>7=vR9 z=9G(Xo<_s-W?ZjVCG`*tOgVJgDp3Z&d8r;V>_NWy{=d_d5w*7-^SeDNU5|;t{yLFM zqec?jNpzW?7VFJoo;jfkj8FRx-_`c=s*%R@at8XBo>?@0p$ zIr4+lucqtjc@R}~qr_O~fl~2Yg9#35r9s(=ox%EH?T&$zri=$5<1SJ0>h2uk?kB29 z{7Oq=>ap4FoPcP?U=7*1vzT$qgwL@^ISZ^rhbr8bQLKq2bjR9P_AbXa zsclfQnU-Mc6crACu`92otzkusOD9Oy289Y+kh5%qs)>2gC=I_?1UHVLd5Dn;iqwag z{EKrq-A7mJCq&n^nIcy;%6%r8PVgY^M^Bm4Q~m7&WOLtB82yu~;(%43exU3e^yg^N z6bB`_I*_Z8r4aseheqQ#>NlRbai{ndn{mo?cmAsmMg6@ioD1 zqx7T+$X@wLyr%|c(uaPcEIj@5St=>NC=AihHxzQ0-4p)(Mcu0a=6~LEb1gJ?ky}EYa5_thBm*M@92WK2jM-ZPqHhhj*9KswumqA0X`e z_k@x(?Ji#Z(5!pCAQEfexic13Q`32PN!Y{>p!}1hdv15yQ#10{h|_@eRKJN_@{$VS zai&gmsFEI2=OhK8-}qtek%3TXaRi2g!MHF`@(E|%`*YC@*1*6#1-uwod1pO?O`LP~ zy|Hu%5FY{(C0_3xgQsQi=Geh9;T%yx`aiHV(?4k3ENU5}NiXXkRKZd(=qtqS+Is^a+S8(+EgRT_O2&rp3Isxk~!8bcNTP~}^!(imSSzE}P{)AvVI zxl%hU_3$TOu)q@TPVpdUMy|=m(dfDxW-)4FnP4aZ5S8SgDE7!DyKSTzllDZY|6C~_ zD9O^FW6i^ld4xmF@q{gGlWWwbnBZ2lDB3xE-?i)_^l~M7=k!^ z>Jbi1xA22IKFZQnR_=NiAiH4owlHuufzvgJaHHgEA_G zeo^9b*_fb6_i8qnRAfdJEb(E)q%mczUnJHTPGGpNr z(b|7bFYLdVUf6#zy|9<*g`IRx2TQm-v;ZS^OE-2k4=XZdU8djtG`ss5r%<8fFZ!B2 z%vFut1J%-itmc8v~iY72Yo6-^z@RM7YDYu5I^{}}0ie0=|r$a)z2{jWT< zX31L#ZK4~g{rzW>i32n`8p@9LKm8{bzXOCn+tLmH{3n_!X?8<*!)Tv|tN(80a_I8l zY2bEDPYZXL0UWwcTGi0?_^p`e8ChoG-#t6L_S6OH?xrUaGFs+fGV{V4QFxx+V>Uqm^Cp*z)O+&y zX0T|IT!GQh`dr;3lHErw{zYo?)!>nYb;l1B4B8~{k!qZAnDDPYQ~b~~1D{JbFs-?w z06;}R2e4Kp8=|kP8=S?nPzil^K9vX!#Gn;qBquq}4l|lV_oY)wvgz?aW2T{>pcD?I zTn#;Pj%4vbpAMg5Lg%5Jv2G;kO}n6NwC>Bn|-r$Mjjb48F^+T zN6+PtUCvvVvvkW5-ZIDMc!nH$$f3s^^Z1pJo#VOZXg{-W&+N%Fzk20Yul(wjNY&r zsO)?iixI~!B#sU)RUry)HL|`Cv#y9|B;7Mpj;QP^kf`kz)py4kMzRSYWPr`U$;Yd` zUcV=XlJ!|kMvIMKy>dp}xjQ=8jbi1$Yz!d&1Gc3$8wLt**(X;kEzFvK#ae39X68M9 zerCGsM*&wnPNu^TMmsDbwpn1@N2=v8x2uJqW2XVPZU1SC zfVSc`4{P|L^3Ri=s41Wu4De|rp~8(uM6iLQ#>VyY7oX%8$9^uE!(69pNk=2GOB4<_ zR_1n@QM6XrNbN+%m<-#7+0odAdYLoI?!MO!9PAaVTINtH4L!H?(ONqUGX_waR5qy0 zXE5MWUk%Jb*PHzjRtS|j`p@OBf6J18CS{y7ekjz8TWVi|yE@qYQYm&<*WcaMNg@jK zs+P^8g~R#{6|S@nDvdz~{sX4L4_@;aG|$itn%-HMXq2>&UVoY%4|*fj(84c2tct-o zI2bgBul&4wYRV#9HJeLd&S;=Q9u-5)j{(nvAFNUkKlNHx;YCP+1vB$%U0!(a zpS)Lh3hsra3k53L?b>M*04#M~lshZw+pLVN>PZSI%;adqIglc<;As&@08A19sZ~SF zD_2CE#Ut!L6QjJJrhLMu5m`o!iX9IEXoNAmg-HX1R}fV8!SF7@hQY#|>1{iSw3i_7 z8LN3~nv7tcLWUPtcPgG(k-KFJktz_v*@h?+SYOCE)Ub?V0X7qAFDOO@1--jMlnfyp z3w#BC6?9MeaFE}az)x}FaPo#1#^Q@14G}&DN+pG8#kdqht6yy0%&%Wc>_&h2iR73q zJur``>4_R;MU}AI@i~q7m@W5E|DnV4bK<9PBA$KEPtDIfWCs4i6C*;lal&)ur{Sj) z;Q9H+b6oH&Mte*=*%p;Bw%w;D&R$F$iAnGSPc$@M7Le5dJlTKE-e?X*b95h^49E~( zD8SRplO3-EKRHapaX0*t;UEnMV>o4oBW?Mcyg3*sC)IKsEN7GD4<)_ZDo<=90K(DR z*gwx!sX=eX`1ot=H%`R$X#d~yWe{N04%Fthv7QN~3(!_DJ;3cca7RTy-uYUuX7&t^_*A*?e|`fV+T6fEKWRq~)Kq z>cikA;LQL7IDlTjEI^$15&{Nx0PZ8L6)^|`;zwFAVjzAF;(QS2gE$|=`5=)PBoc$T z8fnn&gv7(lALRCdq z2eW)R%a>Xrs3n5>HvtlthcFk3;3A#7b0eXfF9G72mC&u!9xGm3iM*B2Q$5?{%S|$Q69=2* z^(JxIBrcn{+axZV#ATE4eiL83_~IqjUgGH`fv%E1Pa16F3G7tw*Vjaa@l(_5?mtDf!#g|QN|c_GXHW6-DJgnM z7O;3Sn1%r~Z0rSOx4Jd#ZkXZ1jmL>quTqB~n?)H)=HgfdUy#ZH(=*loW?C6jZdlwl(kgKG6 zMS+t_;)X{1W+Ne4NBHE`lW9<>ctddbXf|&Wfj;>BP8PosEJJeV__6Ur!Y0VApCGqp zg5010Ujn&q6O8g5sj5*wMT;kj2If;W(sS7Vc`0@8m3A~fyJ5om-xY!nnza!BgelL- z56Zq}CXW=+-BfS(VW!@#i=^@6kOAqCNQ2ZRQ+CRtBSSfZqjGNRinecDtN_T{-3JKa zP4(U<9|c8Bz^TP1CF-v(@ELI1I1H81-gV9fCR+;U_yPIltt`8_565*oL_jX=O=K8O{%h6*i?t?5KiOGP-K2{xWAo^`CkZsz66k z!;g;D+N0W#IUEKO9?z3SMNK2NMKE4fB8xiw--fd#-xDp(zo zkP3;R7?Ykd?>OJSRG)|^$-@V1grVLi^85Fi4jQ|wm^$e9?yyy|n(L5h`Eej+WgKr9 z5mMzC0ZoD&3qtN*$!so&NvWj}kiLaju|*&g#WxXL<5=p`I8vzeE`19?VtXt`r?N^g zs&3^6^@0G*iI&%UM@5RY7^7k170YW?8=Q`6JS-XkjT_J?dH6u3MJfZK+M;;mt>EI* zM9KUZ*{1Pq??jX5BxMO%`Ehet>Vxqtsy8@ShM`V_QpA4$+OQwzs6#=-ODyA>SJ}~_=(?- zedv&9atIKga|D`_wDEpskBY3G8mCiv@*GR76RHI=_@37Z2umvj<20nj)p4eQN0b#_ z87Yo4rBh?+40S}Sb)?p}Q~%25#>AlH>xtJ2M{TKj?`N&jBwJ$w4&{ZpGU@{bd7>I6 zgsj z^bzCfn>)5%D_}E@HyIh#Xy40`-AZGhf$m%OWZxjU zCflfHT=*gFcPul$ql4q8?9>`b3t1hyu8m{jF&v{O4Mj%Z86!62gxX0Z0~;caB5DaA zOSm91Uv&nvlFH|II45VsxipJ-BAb#KqJH@AKl7^~jTWO~N@- z63$77@RG_GNhIujNAC7v;YZ1zosz!+&}UFA$&cOB@Ccw<&KjQ`ef;$JvH1cfDhV(C z9I)~viG54<^T-6E^!B%ZWZd1jv2&b>d~W$GVg;RZj>xm`Ap&*zh7DNKoeQ^WHJRY# zXF{TcU8uvY%&Q6`U@7f(hK6KVW5{$;xg8enQ zvL&{NS9Y}LjD7yIV7c7W4nsncN!duzfl(RD0tNk2LBCXg{Tqh(>aYL&$0zRySO0*% zEXNV@p#=-}49;1z1S_4+lxiFZ!Brg)+B57SE!&k}h zRZ_l6WLAmqD&ekA0pi?9s*MD1#DS4i8wuV>42?K95^W>C7>SGd5|I3qC|U8-N|LNZ z!b!?DiS8z$Z&IN)$@)zqu}LI0iNq$(H;Ke1k=P`>-^4R7@4S?UmzV{47bIVTDaJnUi`>Ms3|S)^;nEA2@iQU z9Pq^zH18u_t&1>e6gy%^ z>iL3tDKQXL==SK@bL%jqk$l1A5QS-eA#P2N1BWVQUm4{|fdSw%@=~rE*mvJN>6pn* zUpNherxFQxjIAKe3{R7ZIl7{+@+m~{mo%-7&t1sj+R@Q4le9S$ANLduhYJy$NV(fG z6=`*>))DF7AbhRPic(fz&hSt+CBDoj!3qYI z4^iAaPq#6SZHni?R@i%Knqy;>eQNLIlb@_^#S}7i`fX#_^s2US`mqJH8Juo=;0!I^m!L_5tI@TN;3+1q)OPr&oo|Du*z<{4BhnrI~Amg1QP!t zsIL-l(l5H(3S(0p(gwnc7j(s2)ott8oFj8+7|fdDu9D`wrD`gowdDV6I%0Svywpsd-wLCT@uHw;!Cp|KR-N z2j{wk0BRDS($j=WNP1{&1}onut6BiS;aE5mMCm)L2*tpkf|D0P$$ z9HhcI^2{4XlWhRN_wWCe{_=az>qP4LP&Yop!`tlQYvA*X3X&@Em#Su+)z_?h-L#1> z2nMLAUmLZ$1i}qVLJ=nFZ2d)C7u5KXPqa%g2SFLn$1H!`p&{R)A>X?R50b6)@Gp(2 z7s5zAzIVJdm=41ekYgj%D0+J#Al(XqoV?Rukbdxy+Il!v)lqeZVavVaQ%MOJew-eA zb2jgq=|hG!n}*5fITpmooz)$U)FJc`Kjcu28|AN;CEv;DoHIBIudg$DOU+?z+PR6QuC~LQb`UE`G<%T`3byC0^OD-!%*+s>5iAK^jcw zZyoEZg&b8wPJKh__U&V9$S~Dp!Z|1d(a*31Z!wGjpzaL@KI?~=`KXW6+=nn#EmS75 zN|depQzFw|EMRGbdZ&uT4=fyHi38fcrPdHC+sJ^ux^=xxlvA%X|NnzI{^CO}ZtdAz zgWB+UGPrf}z&#fR+cA&d>H?!2e`{=oD+^B+nSMzQR_ zubmJbCQrwN+p%uDD>g2q?R585EsH9ODUn4ny~AA4159)3?gd9E8ZTs{`5jg`;=swO zHX=^AJ~v@7xpnz-%%%gn>`XR)682(Yv&PRfZHB{+2WlV?gX>{!zFB~?n;E-Ip4?a2 z<#Z>M7r*bkTj$Oi?A`JP*E?Zunq>RN-K~qlQ49P5Y2B&Iy*vJm{K$4)I2eY{3AsCe zlM^9$9tI?zY?BE`3Ior=fC!jO%5O-?%=l?eMa0MtkxS&$7oHQgP@{l4w4X%lQ0P_P z#Kv9{LUc=h|Cig9(a?b_8(@-3j+Dt&vdBlHNu}G@u8JgbA-UX5O# z%>Zjmc=V3D5Uw;Q$>B5AzB^xyc~-3uXm7w(Sfbm?J5n#5J%>MF@wp0Y?+Plep+0Sj zSUSZ}*JCr#XVb%`E~`06-Mi*9y;FsF-X=gP;>1$F;{AeU)f-1z{hJp*`B%=*uzwNs z&yfFJ!2c%PCRl2U-c@T;!CLTXojNuOc7ii4^AH?G2~uuPR+ zTcy`l{@W_;cCPS8g_{b0R`_wo-&Jm1RbH+tqpeEgR`Gq6u297@RPl^edgIrL?^PO~ zmH*F*|El<}Dy&zPme*M>Fn?qV^wGJ5VG|=E*l8%XG$3M;HgFW)iq625Tx`jN%`d}-=)QWK-vFeMy%d*NY@{`eUI zS08l^1|Rufj9G+3ajY^eaq9I1W}+l&!x%70?PvH!8T!T8fzld(Bs!grSkG*Ld30?$ zr-&SocX&t0e0j^H|Vmr?!G(H4%mc+&=3~UJTYDAybjWw_fVa_PE_dX77OF1t6#BqD?qw#B$Nsk z!K!iyvCRrHMu^P7>&JTHxlM?Tp>vT_TqTYSu^$Y&oX|J zxr+%3{ltg+O(XqdXf};(TtWZ`aWDqvfnDRED4{I=X?Xm>7G3tukg})yk{*Oe96fOE z!)#AJh6UJ2!c^xh1pw;-@|r$ivqbrK8fo*9z|#-3Aq06CFH zMXzo$^#zTXSf0~D;G9v&B+0!=MRTO(bHa-$%5?vjC;&<$J=jKUnm;B@|7wmn;G)a* zN@GU%EFgW4{0JXB>7Qr*PpIFbh)g(zgvJ6=W zm+SN6`_GJ09a=j?p|YF=9^`ql@QG46Q&v3|V{sy0FkC==TwUK~@Z-j_#J(Jl3obmN z9^jSy*fz1bnbv(m`-M%}M4|H1(PVlmVw^9|UmN<&e0@w*Tdh`a%e$f`cYAVeAqIOiKt8Z?2kn+tkF{rY7DhHSs2?$@N%a z2S9oV3@d|CC^ZNO45r9nwhWK}0#-BFIsr<+!XP{uJeld5bM+^O1A-4gcUDKI`D_V} zx8@7iHfN5VpQ# zi!Z0C^^3{DlgQ>;bHO<9cvg&vb;(9knV=2T{UtFr+)JX;LJ!f@eOA}#Om2VToI-|N z1qnjb0nSn2Vg#C1pnnMrs1_*jCoDM_C;@i?s{k(`2-pR@8T6fiUVykm%K_=$< zPZ)GChyo4);@L0?kYERKK8SN}WKURmF!&N6&PVxjlut(SOi7BAq)1HyB-lxqlf)B( z96$n^B%YH5G)V~a!a$2VEfLh>K>sE{+-XUlmVh1-rA0ztB%np2yNL5;6Yvrs;VqJe zRdRQg++8I_tHfoM@KyL>aRwyS)+$4iH338JlHwki+FE!%GB$ybF>i zLGmO>%?T2TAi)L+FNoJcs!Wg&SgI0`PohLOO3b2UMoL%|=TSo6CG=fV^pwz_lEF`L z{*qX~<;%Bt{buLNSBu|7N7i<2&iV zaQu=dfj6E#sqbWA76^6Y3EgJgA4NzkXQ_kG%zK}X6?9FHlZGKnyf&q$_Zl6B*ROmx z&*a^}YP5Gt;2C3{RL!Igg|;~E3)w>GB%V1OT_ruD$4u)?30coBpLm-*3*&7Mt`LGj zXY(633C}iT1SHv%qVOq~n*x~=BFDlJ0@GaJwNE#{5gBvPF$eX`@qZ?_yzLU5I;ES>K$`ZB_q6gI zgwz!i(!HgaF~q{@i5#}V5%P7`&X>z)u*nc- zbxNC@Fov>u zo_xi^Ol7V0nHxvu z90lBPq#^DI@v@d7+!7z5NC!#SGdt%~5oblhUYB7HZUE{crEZ_AL$7AF@chIXS58IX0zJvJaU7MG%KGfq2efebt?

              VTDkbGRPF}8u4t5<(&y7WDzar!K1U>S!BNd{ME-|%Tawj z#pefRi0y}*T!_=uepwGvuA}|iW2{S~e&jl+vs-#{dI61fnP@Sh# zy{ga`6uW4)y1pB-2)%WSH&jQ^$7P2=nU(1)5PLih<~zW8TS|bc;$*s+N{w@ zU_Vz;TpzeKG<7QvY#A>-3U&Jn_a|a}K!AJ2g?f~g`QnJ)h(1Rg%Y4`O$Bk`#*xgC8 zd)LcYmXPm16E-aB_?Ts|%jY+?jkm57gcm)CI#wJpd3lc^-7i?JW|ZT=2eSttDlzyz z8G@#|B96$|r2#DP?>`E0-PkGHmd{yw&^Q1-P0fJhee!};AKhabmn1y%F%caY)8_{` z^WqaGnu*xIQeJ1O`jDvXSK6VO=9$0|pM&C~Lk`mo#h=XEYL*QW}4CL}ya zp0e*}{^jThGdFmW9vMU%Bm8*bhy%iKgKX2_9beX6x;rN8{*o9+^T8JR#5zGFR^FBy zn{e0D!UD35ly8>%KwHqndwKIV!LQM4qyhQ7CuCg`65oPw zsYG0HK!DebSQ5$ejMj^ON3mTcoK0J01W5z(GTArtPLetFq|sC06s+t%n9HWbS+=;& z`!pl$U5B8Rzddk!ryTP-lV)ah)Rmyy^PZo1PYZYRGuz?L2Mu88iQ5?RP_Fc&$3C@} zWD*CL8-C1h-r+NU|6XQGzJE97`}caJ{QY|wX^M^O{Qa+H&f@#esye}LsN@h4XBKHp z-Du1%Ys@BVOycOj{rw-PFLLI$rfNkaj$}wC^B&beDht?3(Eb7~eX;IKR_I=a?peLk zJ$XOmX-_>+K~c7UXc^i^SM{2j&p7tgupb;OW>=Fq!g~Gq=v57}GslRLPa_?5o_;Ew zvv9b#DGFW1Yr?TEsCuqK5BrB6=^qO`^wb{D^~ulULylF~V|7ca9`9HQ(W6&U%4+0= z5`g&n(3CZ0-=S8D$UCOb^_{XY9m9Xi=kj9)I5QjGtd2$xl2dAmZUd{WMvc~*_cGOA z#2*LkXwh%$kKRzDD!l|8+k8SI$l>YHF0x9iS(7W`Q%bK$Hod^mte7qdS*jCLwX_mW z9VFl4wUh%J-0OT1pI%qK?6;n>(W`r^bh4$4pfxes zFrBiG?-xDOYcMs8*{W*vPE6P|w@KqUI{<$gbZEo3w`eo=^pGQ?OS3h`@uSx(sbqw1 zD&2*liL$p2X9V#ceO;df0mAng+haOxgou#__a_EHno!OciE-iZ&0H(-*3v0ycCO~8= zU--;u+U|mzkj{O`LUk8%BegHIJe&rkV$GYdW1wdbQMGkLC=^01!t7n*d&j~ufLR!D znc=vnh~h*Wpa&K<1nvpG1@?V{>ANArFM(=EFu9+S`#I5bq8F8*1d8QffkB{o3bcuD z?+K>wtrxg039bnyzEANAqd+bW!1O+*_{5a1XNu1=efLHdqsmX8-0mj68(pH0Agj3r zKG?~|bSRW%TE{)Y$GrEnV4`$02`*w0VRgXZ&49C(@4;puRB3{IF&_ZuScI+y!1)!i zat&a`?Izy)U4Xoc4*~Ly(;X$*q~OVOMb$cohP?S2$M8Q}mQCC8Kl1MEd;kp5tK@jx zS&GR-Qq`g`htAj75&rHfJ5tiN^!Cpr?KXXP$pW2l92Z2)-#*Y^`|h_A7aH&;2Ob-= zFpNLn?d=Q}GB4b^8?Lj)ETou&F$+72r?jI{7>>lcQd`6GI~86&23c79x`rEA<*R{& ztLCMq@)ch^Q~M+rfV5tbbh%ewfa1g#>in{74o^qR2{+l_3$gclSk*18UHwfyR;}jX zz50}N^pheDa^#bA>R8Nsxs{sf#qxfe z;v-b&fV+~aZ>YOJ8k)3RpLUd-jM(8uftpE!(i@BfE8kB=8zfaIJv5Fi>u*iDTbv$x zl3S$pfRoZZK9_-FeH>9Wxe6U<+TV`FAnC6Yw73W7=B%Y5gqa4{# zv(hOGC%5N&dI(8Z=A;Va zkkvp5sPr+R4E*@iSQ?Mj&Ae&lO(Ulk%M)jI&vEbC$zw3Mc)B^y+ZNS6oGM^YJfN&+ zdWnaF=|A`|(~F|I%}(L8m&G>bd3Vt={!x-8?jnqalKU+{}pG~Pr2=kkdr zam!jrSe3h*O3@2!>Q7MA)3Y}W^aeL0;STe|WvPhtP*bAb1&D%06y-E}S9G0e2!I8U zs`Df_5tb?XR2X8~sc~x1v0Iv*FkoO|x`}Mk++s&d=#^94=e0 za1kvt3zZ2e@l0#gts@_;sv{{ePh7*t8%T9J(~((E2LmXL;ToQKPFO!DHr1iAJ&lf= zVZf`q?^ZRl5U=Y31tH62JrFGHYHlWcnOUzL5yP}2986riugh=5y-wzVwKofHh)rw1 zBJrzZc_KWFsTGZ8kB$e;j}8`J1WbRds09zq5uG~T12;L!hm{%IqkDGr zAe|yvus?>Le&qG2p63CskCBH(4@LiqUaI?@yX4}z61%Pk$sgRbIAJqAe4}AtGUo!C z47eW3@7TO;PUqCx(!>uyGjDYg=_QQF3=eN>=A1*Y~=;09h?s41~kChTZ;Nln9U;}vg+}`4$Sj_Ln4o<^Y!@^k6n%J z@)~id{SeUilVvq2b_6{iNA=kGX2}N|13te0Jagvz&nhPt)OkK&1ebNm9g~c$1@i?9 z1$$>qE}CS_6)a{<4z*@XWsC;v0QVBWRJ;(AOJKV2WMY)DHG#sg8Cy!s9Tr1RxBTL@(`URM#nonK2a)b!1 zNgA_7Fif?1O=$hYP)7KW`)4P6nx-_p$Vpt-?OleTm;DkMX4TW&SW0LX1Bd}DtVSV8 zyjlTRI@y$0X^{)M%~m`voy><y6%uBCS}Lcm~0Twwey$6fw|t< z-7k<_eCj`8=2)nG`TfKLVGf=E?bm>-8eXz7}m!xa?V59Sv_wZ@l9bt4ma14R$rGX8vAx zcf!e&-j`1!dd^FwX8RIVEHxJFu&qnk)x9-nb=5$@Yz=kWn$sBCf@HO}ZKYPFp<7iA zttu8TRr5;RHVdgZ&ZcFlMi#|-!9@3>tMbEni)CoSq|ujlXq|oN&srFv;I6{Q`qrb} zT*}+B_&jTc?*_L@GxS)z7`sdaQhvnVK2b}-_hcAX`Rl9G2$_OG_ZbQw`6V7>jqNIs2`;-{fI7_$;fqOSj*Z1t52d?Hf_2UN{7yis4HP!lp}FUex^5+207+ zR`ceCr+?lL)q*Qv;K_inBGoy2}gz z>d3{8XzAY6J1YOajc&E6;=V7v8^-KaH6BOOWog*L#~wE^Ri@_>ZbDESYshMofjJF^ z{gKRz7{}2Gu=7bIU-98#=|gn%>GI%3!8nX0t@CE?tm&QW#M#)jmbW zp~Gt>p_6`4nulh+_f&(h{i>;Kum!$$BGoqG!PAuW>iKUKFG6e>dr*I+F%A?j+ zab&q(TZ=5%Oi(p-BG2~Q@>8=(c^qp6S;xl9SPM&{x@?SMPZoc5HUr!=E!S(+xfP`) zmsXe=r88s3tJ>dCIBf-pBp5I5s_h0%fklyZr^sZ^54o1P*Ko|Wo<*r&wG6!|0t_=(u{c`nS3XCF#RugZ?~!VA$@LJ)N9y-WMPZ~OsruFS`-@XOu4#Oh z=}h9RR-Pv-nKz#UYzz2Du?6}<9y?EkzA!0M69BkASw_0RS*2+@$Qf#FcJ{V!)i$@pVb#|KN zydxs0^0*U~LwX5c}-pBJtqy*W72G!B{!Ur_p@WzFWXX=yQ%mv2^#yGb+=Q) zq>9vDtB5{Dj=I2(;_@&AiR-rR(^icGHX4%u&hi$|i&$6Cp*9O^j2hRr@Z_oDaX)@6FunJmL=10E!o8 zjx)|V66yvJagD;oVN8Rr{l!+NxqiO)c0d0jLzHK=aPaKbA{m~tWuBldr>iW~ot3lE zK-g0ZJatqd?XCxrC#`_yxvJ;)zpB7+(rWSfMgOOieAOcJ_(7&`1da^cTh`=Sj{B&|+S*;nCSwnocPSkXDXHJTd)$0+&J}+DQ4+Y`+-NS@m#WUw$e~Th3BqNTyTsdJ93i(u~*c9`+o?IJqc_D$Md#narSWQ zd2aZM@i8=smGXzW8WPSLICb!AXM5dCgzUgp(&}^uPL!_(-7#>6KOOmT`^OE7;1Y8h zEQZSI==sLc6=nPECW#jC?>{n8fBuCt_1A9Wd5E?9ZP#ncm%P`9uu!en6PXmCm3o+U zMYQVdg()?kB$RJ1&(m~ulOXXX;7k<2w3;0HgVPr_)E#AB}pX;;sAL~H>j1PNX z4#-3ikCYBRSaMa}xOIFPlmk(pgh?dwoSx+b3?iBXURF}+Lw_U`cMl?GR3Ej;=K!y2 z7*bHf;1aSbKLEbvl)qKoNm2Mn=kO8ez?*PCyS$#@KX!s74V4Ig)?OlVHY;3SK4IdA zgU+#BLiu2oAnZOtMy5RF8#f-^XI%6thN-?;X0H)%Ftfd2! zlMsOE@>W*W87ppOIUU$iA}3-F*jn706Dz)$5+zeYGsQnsl4FX`rXE zI%AK5nSwnFR$}Uk3s;K3m5kp??6<=6CC;J5C6u_tk`7s02ey~kKZ}3Q;>)wVeU-Pb z^7fTtI#Ny2sn%(jvI}-s{5Z|{L4|lgs_02-84d8UQGk#=ZQ5D&qWFR9M#ZR+MuK@h()S6% zf0EX#&9JmA>_HXThxt8u=DQ+5eE*Trg&(9ix;1>trAc5MFyT&yRdeCUMIj*HXHZ)} z3ZHQIl*|J---EhrTPLRTR)m@U(v&EQ2Ba-vtj=K=fvJ)@Q%lsmXV{^~FD zV61yyWWU0S)dQe%xKZSZ!^Z5?CAd2n6HI``L7K2mBHQu#pj^@}9~HVfinBA~l@VV| z&nKBB9*861Zz(uuM~E;6XN;*6MGpp{>b6bfCa!kL_Y|%)%AlyORmV%i`08%%)y13; zT)vJc@cmi!@(#s=rKY#U)yjuo5#R}%e=}8JNbkST{0LZbjS#| z`kcD+kX%?|Sg}JTG@R@u2auXA!Cnn`V{K~I`=+Vz_Ou0zkfiD6z1P5E_WMim!!&n? z5NG$48T*@}SC~)wFG*&lcYTWT3(0ZVDfg;Jsj%O_7bWof|Ex5u?|+ppn8xjA1;-gb zXMOtqGu0OZMb$trG#<<9-QCiiC7XhLlh-$s|l^moYxqgR+|!ZpLbgg%}5%6+1gZ zy&e-dnM!P#%diUL_jkG8(VR!YP3g71fACRKR8odFW7u;OmRvmu@Wx)0qU+N@NbL;r zfhhG1nHbyfkgb_HGPon~xx3A9HWQhZGwj)R{xmBj+HE8|<(7M*f}&CsH%T)xIB!ji zH0q^566yF+)8oq?fr@D%VX#T8uE{f9Ho7zz*zofRGwJ-$!LW7y)vU`KK2YtPQzJT5 zJ7p6YDmtcv$qD^B*%(jTHzDw1t52mq$#kxu%Im1l zBER*n>DGJ7wK(_V&pqG3+1dt{ z*%$ZL)p?A$endabY~;y*UZAL^$9{SAmB~gC_59>{5LO$C7z;I$A;#|Z?EK>)S_l{l zE&RA^)C*qkDHNo)>E7-Y-bw@ki6I{68jL^iM5*+I4fLM{zTx4e85UT4A0LKU&s-gr{yB_jkw8X^6-5)e>>0L5UA45&W=pgtU6>jbOWjaX*We5fd=j#xi9)F=Gv zC?uom`xc49*QB>s%L~|9bHkpZCzLj(o-l4q)T=Ul5i-}aV#ZAv`f!Wy^J$5M&3i~# zYO-og>VQ#PYLGA8T97lACZy|;N$BH;jPrR50pHzhIP-l_keBlQ`;YPgcADYJkm6wo zX#%9ZYfcRg~PDME% z#?eFsyo<3+VzOR-F;q{lV8d5eR4{w&460fhct7lGgpIw)?__#bMgAAI>hhx6ZJHPt z!#Lq4OXM~ghHs_$!FCh&u>H(g8j5?p?6sD`{Mgxx<)H9)$A-z3$*S;CHnI@Z9b}c8(^YU7a-B9$~&$`Rf^-Kmx%7Nt`64G zjPzgw*dwwZX>UPZ*JKjQ`gL8r?leK}j&1Rs*?l0gr*{W-9ybDoIc3pNXzMPvkLEOF zsT|hb?2(#AQ?dt}{9c{ap1!jYZNsdm0@CTSRsS!HOaCp*HguPEUGL3zvCXRYJ~z!X zEyFf(#_3!}S7rX*+e;6P?66G3$;bDf6-C z1jeiFG$6$osEicc6F;r^N#YO-5>VQS!Wl4xyfx~Ec4@-Feg9F~DY@Uc+(`MRrwkW8 zsU&jcj-d#IGXCSEKnx$;NzXhM@Xr*QYYv&oZdXE>ec8paih7`4OCzJW2f9^?0&K|eB_UZ-WYOZ<5E#Di8>6_!` zN$`q21`+4H&T+#vlZ=&0d+-SLkYmqA@^#>4L)L*w-Pl7u)yN#fNUjKu9jT&;Cpb&b zgD<0(1zI9BZq1DO74>iFEF>a4?jcM#pNL`(oJTR_uP=$admZ2TRotZGKJrn`=HRoS zPZJbe_sJ`r62b$O0!{W<+}g?dkvXthBC0+QWDlN}+lCa1Tbk{q=GfHs$xFgOW59)A zgyHNez6hIe%`*hY7QT`nzghCHoWEfX-#wKb`$XkKo(I?=b-!kcKNqGXIm~YBv3{HL zH0mIUj63d0Ox$O>E8QF~ST?a+%DeC1+cg|&;+WC>{{5WN{r=-zuKE5W+pd}a?|)4T z9~LWQ7As`-AAEQp)lAVpY=^U=9_lpXvR#i~=Zng3J3a(A%|h2>5ZjIF>jT=)-1Lgz zwEM+Q4Jlz)hV1IhlLFwmR)i+jQlF>+UaizIEn9mVUY<2C_z&jZ43hcQXx9!=gx9L` zX~5B>r5PTA4wu9>GX)H39ao&QjElPQF%;naB z!n^waBW;P*balVpZo}NRq;O(FJw)SqpdS(8DcE+X&g{LOiUh}Mo%Y6`dA(dK*3#8o zjL^7k?W|_tV02pT%>Xm}%EVTB#k1A8$LM%^; zoq1socc~}m`6VSD8w_e#kmGb-!F+mG?Oz8JjjIxjxRGS?D$*ch zz%6mAivQkRj|W4Gd@G6Vo^{SW(Qm?3g6A8Ec3K3-d0!*pXzs_4ipdMTwx{3lA@|FQ z1*TYET1ttuLtetg)8o0l@QiOf_vu3nG_C65ZTE80sm0}VFfhrnROG)JaYMXL>w;Ec zP|-$5`H`yjr+d!8n@vy{>SE0?q;*Wt{Z3es+ZI@BC$xtKnb`Dm;`76s{k)x7w~3)JO$_l%mT#g-U@I6#CabCL;>QCviJT(36BY zF9O8DET7Qeg@JgcC0bfO(Z2~0KeZ(MA(41U%omBvBI#em-6Ba{B-lj~w@OB@lF_Rq zb(JWs62dAWtWN>r+=x3P!5VR3B)Uc-VZ@!06d6gh5nqhNg+_l2lAjVKE0x7c8my$j zN?fdbk}BCIk=P^>o79p`;<8CxHc9&?&NqqDCQ;fX*x$q#FA?+-vmneM`4S{wf|N>- zm<0(hNI*dn9mMM(UPt)^S^$8=I!ctHIEWInDDI+!zDwx4BxM)Rc1inF;_{S4KP7vf z;^$M+@RDS{#g{h+%l9r4ysw%webx6Pw^p3oOmMGctJ--2$|^+YMT3^D*+l{#HltYN z$m;>)g`;|B)R!Jy#h^|-R%GA@j7R`CJ_GuEd%*IpkYAA>GKL4r((>rqi`-CY?ktY) zD}H%$+}=YOG^0jMrR7czKd$QpPJRQ8~6Yi^93Sna>= z10<$thh18~TWwSGF>hs#*1{UzvK>O>&(fN*5yP#W9E<@RIbk2b+l_}d-|*55aI}{( zW~)Zg&>zK7j%@XDd&3B_A0~*ofU8}$k(D;K1A8p0Y%cjGCE$8$(!Utny0IQPfI1rkFJeIeFai-Ew{O?A-L(r5Xikr2qQX&gMX%#n&Y?rb zmW2#C8*uau)sD|@6rcp$1xy08fR(|JfMcKngS%%ii7%7*GRc=vZ3sY%!7d5IV3AK2 zp;igONO({gu#sXAAn)wXV3X)>;(U`3pf(fuP2PEV=Ouz(A{fM%AaQ{l29VHq2^M`R zfu{uf6y}@#?5TU}*+0b^0(Mq6Lt;lc%GW72Oh4Yba&;M#cido+30J+TP{`(tlU|># z4~5in#kft^`tHxu;oYrg-${ z%%818*}wRH5^14=OOq5lI{5Wst<1e>2tIaFb6y^blY2$dJRQ~~aP@|*XTK;d7~4Yg z^JO&=Hw6$g@B+FG`^NWyh^PoJ9lpmR@7WDgZOaSpN_e8s%6ew9|bm@-J4=hQS zj+BT{AGPBKMHpX1a<1Uj%-?o(vg^}`9Pia~TGiNlW0 z`117;c~03)7kfJ#hAs&z6D86C*B-o@8CrLl>vz}bT4wlq$=Frr3J~6&{o$q+R@Nft z+?3A*N==y@YFh z8fyKxV>%s4R@%eT-c?-Ppr%FX6_m#t|lLKbxG)j^;4Dqh1Twt zydl_8IbUygaHvG>fCu*+H`WMp$+63KG7@r-f-gXhg>h@IaoLdB@Q!jqBq`obY>%o;PsT{OHJMKR|BZcHliW6v?mpLlX~iDf5&L^s zRqQG@Sp-FZRn2;`Bzq*^#&eTBzKyLJwJgh)+`8CmNtXQ3XJ0qu6R|PU?n`Y*-IC7E z`41<*OaKH}C3z#NfXpO_8*xbhiOjWoJVc!Ool`<|H~fRM>k5OTmVuhZ@ki1hYL>;C zmK9|CM_g$g2cu^4NyAZsS7PC}W|5-af>O=;mz9VTv;Q;WgoE_29ZASh%idU07)y|`yq-#lQYld?MM)*S zRMMNsvxz*LNUkT6>_kdCk<=%Zc#`XheO>Z0kxWc;@#KZ61er>ZOd`u9pP2;7PKqaS zW|FfV$mcv3JU?-_Z|NUn{%ZlvlO$xhYN3yEWvZX-2y;SUH z(VhlKi$_9AO3wJB9aq+M*)1w`p(K9Q))w+R6%4v{qQ)IX+0P`g5=CX!l#D9Oyzx7A zys4>RJrd`1SaXC^xluiJ-5$55!yjrlal5UB!fUQ!UM2ofs zF}O2$cE_2Zs#bI2XGA7d?eY+{yA6F^?pwD!oU_9BZmUD}%~dc#)LS@t8P(dX!5Ws` zhf~;C@9LoCCajBWT=SlYTJki%RQ>&jWuW97NVxK>uwPPP$fTjW^4di#MD<(oBDeC| zMnr*Bm5hk5^P3YDt?}!j>aKba!K$htsIkB#Wd}R0#$AABoi*B3{jk3WKd4 zRAYWf8BDQSA=ka1$^$UJOtGgb?8)yUAQpMknTe)tv;WWwsJ!+0~? zqOAvfolnQ~&k88vCBOR?0WGQ1m~PUd4G9_XH`P?7WwzUqvCd<$?LlXFxDDYTs8NWZ z)o8Do{{$=$N3N^1ltHtkYdbaC&h zBD1G^RkkD!i8x9;@TZ9c5ffHn=zvQtfVycX%LbB>o)S`YfVJ#s2?coBeb{1T)nx%H zTJ{>`K!JU&ie@(C;uj56g^fhm58bdpZ>QJfb{{dml?7-pQoeZ7v=!g#s?>Oz$cmM~ zBGH+Hp=eH;wuQOeYKn(hX`6oVj22<=&@&ZZ zs)w}Yh!vLrGWN!Zz$yIPTf`y6By-+6t}uh@++~)Kr3K3jHgpn;R1oTL(aekUUcEL0W5c!5o#j)LKh9N4uUZyD&OVEyw{ zSQF}9s=&TkE9Dg!M%%-v1`;DBT0%kuP=zHnxwJ~DWLB-XVk+sN`1#(;I|<~v-pjiK zRc*yO0U2o%b+Vh0JLXc7hv5o524#o3vsk1L+BDH<@=_tL#kKueOo6u+m?aJf!gJOE zq*&Nnh?8e1pE-!unKbQU-A>+&-Ex%Cgn-JevpM!s*r&BM`x1sPbFg1=1SrBXou)}a z4cMuEly=TJR$Q;Ol8RE2dxc#CcvB9Bm2AaU0@yp^GY`O9z>zKDaVz+U39l5yT(C_1qXkk2lwW{5u) z)39le<+s?j_oV=)rsl>iSrd4hz2fST!>9HaHj&2>NSpi6+(BPzI!R(V%3~ET?xv~*PE%;;u%R;TO_U2F0`n^WV#H5bPfB6?~ zpf&o6a3jP>Vg?{DCw^XbAw9xj6S>$-er8?dpvF}e)DVEy2Cxg&8uw3Z?9-eE?_|Ey z$b%`3yaLf+ifzJLb=v%Z;}TwnihM$yP$W)rzfKCTf6(1Cuc5`xnaqGqaMiz_TjR=d z87@fcgl~M+39He-YyN&222^VGjxtL&TH(Z_#N3b7NTW~TA~(uv(g5W637ef5Eg3MX7N2sN4x9KEXq|FjgEvy_ z5jzX77Hp2FM^nS9xd_U(QyZQJK@Fdx?nV}vfT*FlYYva4BWJd0q+^Z206+t=7Fw+m zJUKDW;Z0Zxms}W^Dq&Izl{EojJxCa0nhXaJR7u4x;ennP$qWNDC5o{W9#;~*2o1b- zii41aETwCHt#Nr5Vcr*onn_2W5qxD41*HG&YyxbdIr|KfmTZvTh++qf^6yO(f3KJWChW)9hn^Y>0Pv zX>gtnY983?X}6b9GuJITlOa5o)roZCY&2zu*6T$ENC8ic4Zj`gqp6{GD>1k&N^q8A z0)@(&by+k}m#8`xLiBqnbw(cudU0&*PV*#|!0vOfrJGDHp7YY!DKM)u{!6aa5RwiAvSwlJ8q!7Z8usXIFArEN|1z`lB6mZsi$ckbEAR=(VVoC=evyRu}Uet3U8>n-0&q{M|Ugh zngx<;>Xr-Y6z$O3)2Z9ZM4B=TkHt2P>$Y*Wal7+eBIa!aPfde{woLU>_^Nt2@vF=F z@B+`ShZo%RdU(Ojn1_3I);`?xQ|{rVVCFnL@U!*dLHE6=rQ2_Hcwj#k1>sv{+#Ug-dqRrO-I)R-*;x`wVe*TSrzm{)+bOg1o%(Z6Lh5CSFwouP!;Qpd{x0Gc2v>@wg#g&d}QC#ILm3 zmxbJr*(l^ov%Mbbf0Xj8`aux#)s@nPdu4-&!@Z=zZwU0toV~o|1c6@OOM^f!@249A zZOGHB%l?YOyrKv%O)eR9WlD<#CLe}rQc@<1+=jq{UMCJ2)o|^SUng$Gul#5i^sVS*J7tHKhvEsJ<<2G950O}IRXJ}bKCMc+bEtqJa$t`X>-f`aS~fIm)rjB9sYL#0TF~y88Tb^kvQAuA zvnGt7HuG58S_ocSj%QKM|Wa8YusZ33XzFQ>t(_GDRSDN!d?%3BokIrZ^lkVWf`chc z7DTcPZ_jI|l_C-oAZqD1*a;Df5d`Q_(F_@A$_e~t&A6`ytVVqei!h=STwnt_N(}G6 z0H(FKy%B?@j4u^GfW?cAPv$gAY^!4IDc%Yeuq^GQDS%r6Sqo~XI?m%xe;f$1JOf0$ zxAR8|Yp~KVZaH}Zx3MsW+R-4fi3&b8)Mi9OMTv8Ouqt|sKP?(tf>u?k?To!#h9+9* zQ}be75?!J+AlJ<@B5bD-3Pm{fQWYTn_^J%oU&Su?3>hTXS{K* z6NId#LDu7RQYH@0tQi-AkzHQ9hD&}TReGpo_mGfgyXo&}=tSe6z<5(U09p~FpaOL| zRJ}1!(?~!o@Uc$9h@$lPk`jzg@3^^jV~km_tSz_Po9 zYggn1vaxtIdpnA~A{Vb^590!P zClmIxc*_~QB;DSoF$`dVO}9|v2O-c151%^bkPq>?M=Do&n#&C_L2;@cg@CoeDNfLV zsKR<-2#B~R9$GqR$+V{65-yUOA1uq(;wqNY~4iSudkI0_HcEV+kLAM2@6=^kX| zxY1_qNvyXoBI+J>bJYD%T9mF7Mem7$)hOXoMmuU+!sHGOWB5MGuoE7Q>JiKlr1ciB zKIB7y0{NE_+FT92Z_=cxm5jLwva%FCEnfOVZ73rQ5Y?cXQQ)8WwN(jt;8i%5omq*e+{FdXjqfFsp#e=)qV?E|H|FuOYm zC{W25AwCMHtdS8mI^@GK=AY3}z76Xf-SeplW1TiLSb_C;R*yOa^(fo2KH*vQdiT6~ zEr_Y=nlfQ0nO#FV6@}^acl0Wok^p zo5o%L1SJ@r2#2}jt|82$K08loY)BXM-^Cw?O7OT3?NaaM`-6I$8V=Pl^?N?4)l*t< zvZ9yV0ivZadTl4x zA;={3uPEas03_ zaZRboT92eQQ}?P|wMp+I*2$XDBc|r-8vvf{nHQ$0s6p3QH?B3ydc)vQvm)DgG=TQ) zJn4U!I^*`)e_v&B%6z3bt^!N*n{3=nlGy475i#U6{mqFfZX<_ZST}Kk2JI{Stf7?$ z9uFu|9bRMT4H5#!L{SFWYt9O@@>G>c@2fK+2XNcd3k9ie7=6v{O%07$X z642~q@D}V?)?ljEn}mvIF4b3#2@(uI5>V6E#U$E*AB;yGN-Og#09lRGT#3_LH}z&C zp^ltL3WKdc5O|T((9Bwo(3!KUs4kZaB3N(y;uU6meIUHyhA^h0@9Q;jtJnof!|jB)D|UPX0S$hxrL##%I$3%_JCB|BrDa+cKeRc5eELe(c%c=YV>z(Q0RY>t}N z!q8K|q(s|iqiu4sqUg2{OXVLP?)euE_bd@VUoo;Ht2Xd^YQ%dZYBa*)i1$QwGvJFD z1$#IqaJ8q#w62J^msL8B_x{zk==uY&NU-3wfURydaRNR7CD#h0fZB;v80h|Jtn%(f zTVvQzfy)v3Ogf{=fJE|DtNtZ3qLem{GE{)XXlAPmUsJI?WZRC3ZRuulRZ?40Ju{=4 z77#|Nybkm-0ds1Z0IPrwEkk}En>8%ioYZPWEG9Q0ReP;W+KGb;Y}=#Kl4!)vDQY9` z#-FCu=9u@0L&LmGr6n0F?&5RBQ36Yf-9;TH;;5@5^bQUbRYT=>Nh+?o%I}r1fp>Pd zjM}Q5QE#UBD1qd5iBLhDl0CG;3c8iXsRdF;;qj{4NsByjf+s9*$1cw)6}E>eNKsOw z=R5Vf>Kby@j#DQXs%eqhX_gf1UtomZfk@thJgicwYTvVDYZ*JpNWPXzU`5Yb7boP3 zs|Xlv$p(<*ogT|zV`#m=ZdbvqD!HV-2GOjH!xYq|bP%ARkFd2+a=}Hjid-98#WMtS zXTnJW`EcN@Ccwf|oRW|qseA%KX|w2t4wJZynluPyQt9P@wv*b@)b==qdX72@ie;?J zAI(Ku2@N*dZkdF$Q+%UE<1U8p7zSoYrT>@_{-W?hn>6W8pG-=#3*Q6|DDKpXz!Gzb zAlOXoLhUHuQ!NZ@&f2}Xb9fLu7bFUxM)_0~vkvN_roq;ho%(uO%zZqn#x+9tV@C*{ z$&(bC4FnkH7AUtZP}`7v&cVQ3-s=2|sIX;aHO2 zj^2c1;z@{`vwWZn`OI`^iE~VWXXF7M|Tttt8@oX$NzXx(>gU8Ax{YFc+jE%ez+#1me zqnCDva+$kHfvwSSeh9MGru5-~W4R6w90LS{e<24%!&}I?0QIa35EVX&m=_{=?uFov zWg*1!DThCGUD`la)Ie6$yk5Ls{c|lF%!U^>Cb8ZS|IQYNU&#>_y5^#Vo57|IYsCwO z5hGS{!aX2Nx_Iix0s~nQ+eiU}P414GvHiRgS^_mJ3KQ#{sx*UFho(6Q=OYU@>{vV~Dr?`3M?J$5H9`Gkksc?L z>|}urNuYHUWuAly=6J0e6hN4Q6pYM5n??0wDPH16D0LIzM<#zTaqM7vJY?CdQ6}s+pli zqGrjhEr)bS)b?jd+k~6@L>Wd+LxyG}EzY0&T09;#N=ToqWwjHHBG|LThV6HSOo~a= zFhQU?32LNf@s9AL)NJ&I2jqH^ipMMf77%J!FVl&|nsA(;VIKz+(GuAt8d8k#tY{-Z zE89rmGzYt>0UPd-wPvs$$f|>_^@i0>^CR~3qb6y3#>G4(?36Ga6}2QGMDZAf+JR=t zne4l}4z-Ja#tznev?wtnLPwtUH>2teOyp)vKWbup8^-X>AX>CQu4Jo)woq`!SjCVt zvi2B_T*o7C%8qaa0qE06M@$ec873|8$E|m41D>D;YOmD;9nu0DmsK2`@eAoTREZ=E z=5og|kCtJ05N{d~TaxG$C7M+{pGgUzWO0iXE=oax2WUBt4>*XDX1!*cmU*ZKn(#71 zZIt0qma@DTtz{BXokGwoU2=n=6=G&drK++*>YCZnNpBQ^@Ge^>=h+zh8+9YT*&%<9 z7k3lgm3h|$rnk-Zz)-DDAlQOtPoHT@pLxonY7owbNnY#wcYxBm4w?j2omX>RwlMO=Bg;#3@5MmSsqYs`-fE;#A(cUN~N^yoe?1uHT z8@_yLSm!#qvk|)!xQK#p1{>D0OfDEdR(@_~v}7Td&B+?d72kw2toqteP=CwK4nh8Y zn^8l*wqV^){?OKJ*y}WEXpO3OOh+(lTd+@x1Z@l-t#&I;;8b>%W?`I@XqbT#V73b! zj4MzD9LORQ4rA%jsh(lgyvFc;ticc!rr!4JQWbQdR>^uOBJn&LQP}Kg&`4S|lQ@KmpQ~uOLIiHZ!51gKG_hVB zlUeJPU}0zdp4Rd@I~tQ9)3;VzsQNXn^+eLt)G*!fnFE-f+MQ*ZT1ecyaABo_hnXI3 zEt{EuaWG(gPoyttCMhepkT;+JzADn)6MS_Zl&lOQ$x0iG<=P1lD|LV}sN|`$WQ)2) zks7!?c*5ej0ka3S7h*C}+jrFjh8rExJ zVvat`H;>%V~nP&VjQURgCZX>K)GWzO$7y%WY37vU1^syKyO=>A z{b=MMUV#h9iSF|HvmH%kjo;dH57U-b?8&s+AGH!}FYggFPihApUJ{VwB!h;J+UWztTon=O6Cz;I4D|qH-f|6^I%s(y%iORD z$Z_0NnF#KzTN5-RMKk}%3>sFN8Y(Pq#=PG}o@hI4#;WvZCrvsI*QHGwc12*E!Kh0# z+aqTzv%${^9#HG|nvt7n-5v2kHILsCB_xaFfQeuY!E|A$?&N}XwqPR~^n zi_sF=RY!^QgK+iNlw;UE^@pQS_o&!&D!K@qLp3dY@NA|H!0xpj$c1o5%bmju9xlbm zh#MRlCeqqBDOlBQDK%OytuZqf?hg8WY& zzcc~oxlX73ch(rM8{4sT*M@a>I@I6-Fbst0;Aw3FjBilYZ5qiY5xFTs1MUqi6Gy5R zL-W!5)RS2|HRTT<%c>{$k0FE|4W_zxmk~2ok_DMBSgv#}R4og~6WCmbsy<@MWcq6$ z!X0*aIihnFU@!sEx zT8b|5X5Q(}Y`VU>NZ2MqH=)pwV&^F;*Ag4<&2lIZkY*Ido4j@VSNDz=Q~d(iStVfSbcr-BzITv=Si_O-b$1*xhl4Q~VANMIebR6TpB?3GArWYRw z<26zr52Gyc;bh)}-%z&!0ErEsQ0c{C=82NmDrNhDz|ti$1i%&kQULdLp$}1Bt0xnY zVmI(zInN+X@Zzcpd^%><~oFDE@>>klPMe=NM zbq=9Hj5swbO%Bl<`Ix7LTR6`7M&z6_G?6T1^y8B7ndUr8c2Hs2P;H?*K{>EI9jX`~ z;UE~EJst*VkB4zH)GDopxe6sNhQ4`52@AhP9ya8}^HrARve=zx&{~*wbV8A>68~}H z8G6E4aEwMVfUSXEKSnD2!;2a7te)`u@VfbcaM--v;)!OQI=uHddYY!iSj|#w5fqjZ zgx5$NC$R%o?g<6es0Ras6;dS_Q?ofTzsp~*z;0m*MW-F;<13OetpVnFvc%bEh-!<@ zvf4ia%yh*^Z*!Pv#Yu3D$sRX&=afjvHKY>PWz{$bUWjNZ0eM>H4QouXnIMv0z!C&N zH5v2jkH9$xZ*7EHS`_)|-0Pv4Q(K0^9~;I8=EPr4h*Xu%QZIq5C>jrWOL)$^8*^h_ zv`b2kYFQD)OUXG{;E5HOL#MEvkRYc7mb=QHh59pvP@pE!&lR18*99iN1(tma*7daT z>YWx|z0-mxLOyEfR#KRE^WKdxI~Rvbxdn?5b=wOT4^BHm;Smq_h-tV3Ksa_vVwM9| z?4$FmMN$mwC9Rb))i0npL|pfs2`N;Wr2)>jVaUfwxS)^9&P1UCO1aPN4%BEl*S%LK z!|2ZL0!A)M_Oe7$ul3TJh+HtgG{HgHsEa~RX%I{nR@hR?XJ!kX$joz!x*YmsJL85uHM z6hj&fMU90}wvGQ{5*At87?%Q|sx;A(*^l_?xH zv$M#Q?p`qC047FUO|0!spsi}Kk7seu)i0=_79L0zvURt>7np4qiB)K^fV?#EKk^Z1 zu%3w=GJ&{QkQdDYZAhsxTgHTp3p-S?d{;yEB$xuVO73_R1TAhCg0FzOLaVwM!_~yT zP>+D=(S=$J78Si79<(m1aXRm`TQ6$de}okg*1I7j#h0PryMj>Kyck_{YV{U6_ZNH? zW<-B#B8^&l8rv~nEEW)CS+F7V#d4N)%cNZl^Sc=4ci(v9b(qlH9je{IE;YKUp3Fqi z24TcNDD3Ohsio8iwM|TfX$QL*tu9OQ=+DAB-JamCegi`OviG;UOYgMH!R``)S|r^f z={6Y#m(_vgJ$b8EY{{BHPL4^fS|5g$!{t4G{(RGQvUHl7c;|mSz)X((qP%FVRwh%rV|=Ny(;qv|RT4>~13R zgs3*U&3F%Y6SpU=g>&1uEz6afSVJ9%oQ-bIkzLaqq65(e>PwJyvKppqxv(>BoL>>A z^m7wPM)jKx)B0KN?KcQ1r_LT}fDkg|hQ~dkc8b)Zgm#;f{iRC7;A#AQ%QX>v(9p7a z5fb~kOI{%DKnRYiy3efRYb`8c6J0f8O0B~F6&ef#HV&j&fKRfxscOz0|-lF;(jRyZI;GA6(>8F{+#$EpM5md$OK(YQxr_z(w{J=!Ev z1j-a_0LG!xHNh4>zl`!xXIYQn*~*qD%dFe&B!cQ1vVs;Y6Fk&xBm5HnCED!W)sC3Wp2~H8`M-)2p+of`*o^xiq>FRk*&3^JZ=&>E1R?d}7x& z%igzcmOW)_OU&O(%-fJR354*xU#02rb{qS%11`qci-aYZlsbAiFczy8j`oo)W zet1aqTMvKu<@CdY0eQb2JUn=K_u+$wKe^99*gidd@bDg<4CLY9;ZFqIg7@IzefEAi zMU)#4_Xk9|{>z&_p$89d1@L`}X8++Gyz_u)H?8MG{M^0y%bUNP`Q;S6w+0kBz4goK zfqQo5;oV=}gcrQyN&F{2M{Odvt>cmmXag^yZ^G z6w&vO-+lB=kMQ_xMEKA5dm;Yw{r@~8ggcMlqW8bHDLj7r(OpXLvLNqLMwtG##=nMZHZWAmsF;+mt4~@%5u`DGikC%m6j~ z`0W9`@B<#c&DC&|lOp6CGE3oz$SB2eXYlweg+_vB2ahS&6yZ(!c$^-g|M87S|Km+t zrq#vstG#zt7q1a?d9`i7aj7oAeXPMEQL2 ze0O#A-GkM4`>XE=xpsDS?egmT4_4PMuCD#Oy7v9*+5@59SY7*sADmx(e~w6>@^eSJ zc7*|QPp_`+6FgY$UtR4#Sncnx_Ajok6Mp0B>c)fBjs4XPO6S(q)vX7sTl=e97gx7V z4^}_kU;TJ`_2Z|j+aC^A2XCzouC5L)5cKit;M>)~jn%>5POR>{#rR)|{O8rd0nt_m zr&b5IRtM*zheW=5C_)FA_w72Tu>)d2#jw z%rQ?7K6-Zkee$k6y?gh>G$-il)1NQEJNoQCd!N4t@A#v$&+h+z@bvC?FV5WOH-9?y z^x)IM)BC@Be)CNx-2d$8`t4-zkBxCyX5_FboX1NcJ%(o$3NVDaq5qZd~oLZ@84nQ!~Nqs z58ypJ^|!&%AKpK{`ab1~AD#cr(U-qv@9xL&j=ws~-riNnQ5^l5>g0Xv9bNqT#oK@2 zaGzg(cK7GO(U(6x`>mvK@$08QeSCcV{`32nfE=CsjU^wy_cOc~f4(AwZ=N39J3jx( z;ArpM^LM^y+U4s{@BD6XbnP1Dgb4)Qve1t&KD#$K`rc>v`t_p^|K_0kf3?u@UoSIs z<99Fq{JB6E+#5H(IezCohHn0N{M8+XZtp$6K&86#^V5UR2S<0$&?BZlxOVixr{vwV zgyXk9JG%0jg`R!6$I#p7jy`&8aD49f&(2+cdjG2dMSuMHU3kwgTpk?1_u2DPe;yqF z>F1+&FR*v_bL-&)TF8FDR1$ez<`CV4;rY!V-4xb4!1I`+JV^DcB{gu3X&o96G?6Y&vzW)8-#TS1#{@XQv z`o$-xh5aa~l&^lGetZ1hI|ExoFTVY= zZN@Kd?j3#j5kq%=K6?9i_FMh;d$<4lt=sVaaqrgPAE$r%$6MD2|L^?g|M!QhgMa#` ze_D(nk%lFw6C)D>t+w(OVm2KKyI2mv8oNzAs