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

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

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

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

@@ -77,13 +76,13 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 | -------------------|---------------------------------------------------------------------------------- | | hutool-aop | JDK动态代理封装,提供非IOC下的切面支持 | | hutool-bloomFilter | 布隆过滤,提供一些Hash算法的布隆过滤 | -| hutool-cache | 简单缓存实现 | +| hutool-cache | 简单缓存实现 | | hutool-core | 核心,包括Bean操作、日期、各种Util等 | | hutool-cron | 定时任务模块,提供类Crontab表达式的定时任务 | -| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 | +| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 | | hutool-db | JDBC封装后的数据操作,基于ActiveRecord思想 | | hutool-dfa | 基于DFA模型的多关键字查找 | -| hutool-extra | 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等) | +| hutool-extra | 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等) | | hutool-http | 基于HttpUrlConnection的Http客户端封装 | | hutool-log | 自动识别日志实现的日志门面 | | hutool-script | 脚本执行封装,例如Javascript | @@ -91,7 +90,7 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 | hutool-system | 系统参数调用封装(JVM信息等) | | hutool-json | JSON实现 | | hutool-captcha | 图片验证码实现 | -| hutool-poi | 针对POI中Excel的封装 | +| hutool-poi | 针对POI中Excel和Word的封装 | | hutool-socket | 基于Java的NIO和AIO的Socket封装 | 可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。 @@ -101,10 +100,11 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 ## 文档 [中文文档](https://www.hutool.cn/docs/) -[中文文档(备用)](https://www.hutool.club/docs/) [参考API](https://apidoc.gitee.com/loolly/hutool/) +[视频介绍](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2) + ------------------------------------------------------------------------------- ## 安装 @@ -116,24 +116,24 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.2.5 + 5.3.6 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.2.5' +compile 'cn.hutool:hutool-all:5.3.6' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.2.5/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.2.5/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.6/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.6/) > 注意 -> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类获工具方法可用。 +> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 > 如果你的项目使用JDK7,请使用Hutool 4.x版本 ### 编译安装 diff --git a/bin/cobertura.sh b/bin/cobertura.sh new file mode 100644 index 000000000..1611b93f0 --- /dev/null +++ b/bin/cobertura.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +exec mvn -T 1 cobertura:cobertura diff --git a/bin/simple_install.sh b/bin/simple_install.sh new file mode 100644 index 000000000..d3b9281e2 --- /dev/null +++ b/bin/simple_install.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +exec mvn -T 1C clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true diff --git a/bin/version.txt b/bin/version.txt index 462faf748..9a97057db 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.2.5 +5.3.6 diff --git a/docs/js/version.js b/docs/js/version.js index 3c62295c9..71491d0e4 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.2.5' \ No newline at end of file +var version = '5.3.6' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 7610de4f3..5521bd7e1 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,12 +9,12 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-all ${project.artifactId} - 提供丰富的Java工具方法,此模块为Hutool所有模块的打包汇总,最终形式为一个jar包 + Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool diff --git a/hutool-all/src/main/java/cn/hutool/Hutool.java b/hutool-all/src/main/java/cn/hutool/Hutool.java index 94cd56878..18fe66962 100644 --- a/hutool-all/src/main/java/cn/hutool/Hutool.java +++ b/hutool-all/src/main/java/cn/hutool/Hutool.java @@ -18,13 +18,14 @@ package cn.hutool; /** *

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

* *

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

+ * + *

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

* * @author Looly * diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index dcba385ba..b13f5e5eb 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-aop @@ -19,6 +19,7 @@ 3.3.0 + 5.2.5.RELEASE @@ -34,5 +35,12 @@ compile true + + org.springframework + spring-core + ${spring.version} + compile + true + diff --git a/hutool-aop/src/main/java/cn/hutool/aop/aspects/TimeIntervalAspect.java b/hutool-aop/src/main/java/cn/hutool/aop/aspects/TimeIntervalAspect.java index 719ac3fb1..fd8da9b48 100644 --- a/hutool-aop/src/main/java/cn/hutool/aop/aspects/TimeIntervalAspect.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/aspects/TimeIntervalAspect.java @@ -13,7 +13,7 @@ import java.lang.reflect.Method; public class TimeIntervalAspect extends SimpleAspect { private static final long serialVersionUID = 1L; - private TimeInterval interval = new TimeInterval(); + private final TimeInterval interval = new TimeInterval(); @Override public boolean before(Object target, Method method, Object[] args) { diff --git a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/CglibInterceptor.java b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/CglibInterceptor.java index a157dd9b6..a4b7ddf02 100644 --- a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/CglibInterceptor.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/CglibInterceptor.java @@ -1,6 +1,7 @@ package cn.hutool.aop.interceptor; import cn.hutool.aop.aspects.Aspect; +import cn.hutool.core.lang.Console; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; @@ -42,6 +43,7 @@ public class CglibInterceptor implements MethodInterceptor, Serializable { if (aspect.before(target, method, args)) { try { // result = proxy.invokeSuper(obj, args); + Console.log(target); result = proxy.invoke(target, args); } catch (InvocationTargetException e) { // 异常回调(只捕获业务代码导致的异常,而非反射导致的异常) diff --git a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/JdkInterceptor.java b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/JdkInterceptor.java index 82a3a1eab..baa6d4b05 100644 --- a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/JdkInterceptor.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/JdkInterceptor.java @@ -18,8 +18,8 @@ import java.lang.reflect.Method; public class JdkInterceptor implements InvocationHandler, Serializable { private static final long serialVersionUID = 1L; - private Object target; - private Aspect aspect; + private final Object target; + private final Aspect aspect; /** * 构造 diff --git a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/SpringCglibInterceptor.java b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/SpringCglibInterceptor.java new file mode 100644 index 000000000..8f3afa390 --- /dev/null +++ b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/SpringCglibInterceptor.java @@ -0,0 +1,65 @@ +package cn.hutool.aop.interceptor; + +import cn.hutool.aop.aspects.Aspect; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Spring-cglib实现的动态代理切面 + * + * @author looly + */ +public class SpringCglibInterceptor implements MethodInterceptor, Serializable { + private static final long serialVersionUID = 1L; + + private final Object target; + private final Aspect aspect; + + /** + * 构造 + * + * @param target 被代理对象 + * @param aspect 切面实现 + */ + public SpringCglibInterceptor(Object target, Aspect aspect) { + this.target = target; + this.aspect = aspect; + } + + /** + * 获得目标对象 + * + * @return 目标对象 + */ + public Object getTarget() { + return this.target; + } + + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + final Object target = this.target; + Object result = null; + // 开始前回调 + if (aspect.before(target, method, args)) { + try { +// result = proxy.invokeSuper(obj, args); + result = proxy.invoke(target, args); + } catch (InvocationTargetException e) { + // 异常回调(只捕获业务代码导致的异常,而非反射导致的异常) + if (aspect.afterException(target, method, args, e.getTargetException())) { + throw e; + } + } + } + + // 结束执行回调 + if (aspect.after(target, method, args, result)) { + return result; + } + return null; + } +} diff --git a/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java b/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java index 24ac30871..8186ea668 100644 --- a/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java @@ -1,46 +1,59 @@ package cn.hutool.aop.proxy; -import java.io.Serializable; - import cn.hutool.aop.aspects.Aspect; import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.ServiceLoaderUtil; + +import java.io.Serializable; /** * 代理工厂
* 根据用户引入代理库的不同,产生不同的代理对象 - * - * @author looly * + * @author looly */ -public abstract class ProxyFactory implements Serializable{ +public abstract class ProxyFactory implements Serializable { private static final long serialVersionUID = 1L; /** * 创建代理 * - * @param 代理对象类型 + * @param 代理对象类型 + * @param target 被代理对象 + * @param aspectClass 切面实现类,自动实例化 + * @return 代理对象 + * @since 5.3.1 + */ + public T proxy(T target, Class aspectClass) { + return proxy(target, ReflectUtil.newInstanceIfPossible(aspectClass)); + } + + /** + * 创建代理 + * + * @param 代理对象类型 * @param target 被代理对象 * @param aspect 切面实现 * @return 代理对象 */ public abstract T proxy(T target, Aspect aspect); - + /** * 根据用户引入Cglib与否自动创建代理对象 - * - * @param 切面对象类型 - * @param target 目标对象 + * + * @param 切面对象类型 + * @param target 目标对象 * @param aspectClass 切面对象类 * @return 代理对象 */ - public static T createProxy(T target, Class aspectClass){ + public static T createProxy(T target, Class aspectClass) { return createProxy(target, ReflectUtil.newInstance(aspectClass)); } /** * 根据用户引入Cglib与否自动创建代理对象 - * - * @param 切面对象类型 + * + * @param 切面对象类型 * @param target 被代理对象 * @param aspect 切面实现 * @return 代理对象 @@ -51,15 +64,10 @@ public abstract class ProxyFactory implements Serializable{ /** * 根据用户引入Cglib与否创建代理工厂 - * + * * @return 代理工厂 */ public static ProxyFactory create() { - try { - return new CglibProxyFactory(); - } catch (NoClassDefFoundError e) { - // ignore - } - return new JdkProxyFactory(); + return ServiceLoaderUtil.loadFirstAvailable(ProxyFactory.class); } } diff --git a/hutool-aop/src/main/java/cn/hutool/aop/proxy/SpringCglibProxyFactory.java b/hutool-aop/src/main/java/cn/hutool/aop/proxy/SpringCglibProxyFactory.java new file mode 100644 index 000000000..46db19996 --- /dev/null +++ b/hutool-aop/src/main/java/cn/hutool/aop/proxy/SpringCglibProxyFactory.java @@ -0,0 +1,25 @@ +package cn.hutool.aop.proxy; + +import cn.hutool.aop.aspects.Aspect; +import cn.hutool.aop.interceptor.SpringCglibInterceptor; +import org.springframework.cglib.proxy.Enhancer; + +/** + * 基于Spring-cglib的切面代理工厂 + * + * @author looly + * + */ +public class SpringCglibProxyFactory extends ProxyFactory{ + private static final long serialVersionUID = 1L; + + @Override + @SuppressWarnings("unchecked") + public T proxy(T target, Aspect aspect) { + final Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(target.getClass()); + enhancer.setCallback(new SpringCglibInterceptor(target, aspect)); + return (T) enhancer.create(); + } + +} diff --git a/hutool-aop/src/main/resources/META-INF/services/cn.hutool.aop.proxy.ProxyFactory b/hutool-aop/src/main/resources/META-INF/services/cn.hutool.aop.proxy.ProxyFactory new file mode 100644 index 000000000..2bad43826 --- /dev/null +++ b/hutool-aop/src/main/resources/META-INF/services/cn.hutool.aop.proxy.ProxyFactory @@ -0,0 +1,3 @@ +cn.hutool.aop.proxy.CglibProxyFactory +cn.hutool.aop.proxy.SpringCglibProxyFactory +cn.hutool.aop.proxy.JdkProxyFactory \ No newline at end of file diff --git a/hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java b/hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java index eb036d1d4..55da3d3e7 100644 --- a/hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java +++ b/hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java @@ -23,7 +23,7 @@ public class AopTest { } @Test - public void aopByCglibTest() { + public void aopByAutoCglibTest() { Dog dog = ProxyUtil.proxy(new Dog(), TimeIntervalAspect.class); String result = dog.eat(); Assert.assertEquals("狗吃肉", result); diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 2a6d645d9..731b60d5d 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java index 2a1096abb..b1da492d6 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java @@ -13,35 +13,36 @@ import cn.hutool.core.util.NumberUtil; * 2.散列hash映射到数组的bit位置
* 3.验证
* 此实现方式可以指定Hash算法 - * + * * @author Ansj */ -public class BitMapBloomFilter implements BloomFilter{ +public class BitMapBloomFilter implements BloomFilter { private static final long serialVersionUID = 1L; private BloomFilter[] filters; /** * 构造,使用默认的5个过滤器 + * * @param m M值决定BitMap的大小 */ public BitMapBloomFilter(int m) { - int mNum =NumberUtil.div(String.valueOf(m), String.valueOf(5)).intValue(); + long mNum = NumberUtil.div(String.valueOf(m), String.valueOf(5)).longValue(); long size = mNum * 1024 * 1024 * 8; - + filters = new BloomFilter[]{ - new DefaultFilter(size), - new ELFFilter(size), - new JSFilter(size), - new PJWFilter(size), - new SDBMFilter(size) + new DefaultFilter(size), + new ELFFilter(size), + new JSFilter(size), + new PJWFilter(size), + new SDBMFilter(size) }; } /** * 使用自定的多个过滤器建立BloomFilter - * - * @param m M值决定BitMap的大小 + * + * @param m M值决定BitMap的大小 * @param filters Bloom过滤器列表 */ public BitMapBloomFilter(int m, BloomFilter... filters) { @@ -51,11 +52,12 @@ public class BitMapBloomFilter implements BloomFilter{ /** * 增加字符串到Filter映射中 + * * @param str 字符串 */ @Override public boolean add(String str) { - boolean flag = true; + boolean flag = false; for (BloomFilter filter : filters) { flag |= filter.add(str); } @@ -64,6 +66,7 @@ public class BitMapBloomFilter implements BloomFilter{ /** * 是否可能包含此字符串,此处存在误判 + * * @param str 字符串 * @return 是否存在 */ diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitSetBloomFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitSetBloomFilter.java index 044edd16b..05846af9a 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitSetBloomFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitSetBloomFilter.java @@ -1,13 +1,13 @@ package cn.hutool.bloomfilter; -import java.io.BufferedReader; -import java.io.IOException; -import java.util.BitSet; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.HashUtil; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.BitSet; + /** * BloomFilter实现方式2,此方式使用BitSet存储。
* Hash算法的使用使用固定顺序,只需指定个数即可 @@ -17,10 +17,10 @@ import cn.hutool.core.util.HashUtil; public class BitSetBloomFilter implements BloomFilter{ private static final long serialVersionUID = 1L; - private BitSet bitSet; - private int bitSetSize; - private int addedElements; - private int hashFunctionNumber; + private final BitSet bitSet; + private final int bitSetSize; + private final int addedElements; + private final int hashFunctionNumber; /** * 构造一个布隆过滤器,过滤器的容量为c * n 个bit. diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java index e5d310ca4..e6f83d540 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java @@ -11,7 +11,7 @@ import java.io.Serializable; public class IntMap implements BitMap, Serializable { private static final long serialVersionUID = 1L; - private int[] ints; + private final int[] ints; /** * 构造 diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java index 75d021db6..7ee584663 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java @@ -11,7 +11,7 @@ import java.io.Serializable; public class LongMap implements BitMap, Serializable { private static final long serialVersionUID = 1L; - private long[] longs; + private final long[] longs; /** * 构造 diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 3662d2a7b..8da905e90 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 780da5d5f..58066ac06 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-cache diff --git a/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java b/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java index a7f95fcda..dea22235e 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java @@ -24,7 +24,7 @@ public enum GlobalPruneTimer { /** * 缓存任务计数 */ - private AtomicInteger cacheTaskNumber = new AtomicInteger(1); + private final AtomicInteger cacheTaskNumber = new AtomicInteger(1); /** * 定时器 @@ -56,7 +56,7 @@ public enum GlobalPruneTimer { if (null != pruneTimer) { shutdownNow(); } - this.pruneTimer = new ScheduledThreadPoolExecutor(16, r -> ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement()))); + this.pruneTimer = new ScheduledThreadPoolExecutor(1, r -> ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement()))); } /** diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java index bb67d4e91..464ab44b7 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java @@ -6,9 +6,7 @@ import cn.hutool.core.lang.func.Func0; import java.util.Iterator; import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import java.util.concurrent.locks.StampedLock; /** * 超时和限制大小的缓存的默认实现
@@ -17,32 +15,39 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; *
  • 创建一个新的Map
  • *
  • 实现 prune 策略
  • * - * - * @author Looly,jodd * * @param 键类型 * @param 值类型 + * @author Looly, jodd */ public abstract class AbstractCache implements Cache { private static final long serialVersionUID = 1L; protected Map> cacheMap; - private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); - private final ReadLock readLock = cacheLock.readLock(); - private final WriteLock writeLock = cacheLock.writeLock(); + private final StampedLock lock = new StampedLock(); - /** 返回缓存容量,0表示无大小限制 */ + /** + * 返回缓存容量,0表示无大小限制 + */ protected int capacity; - /** 缓存失效时长, 0 表示无限制,单位毫秒 */ + /** + * 缓存失效时长, 0 表示无限制,单位毫秒 + */ protected long timeout; - /** 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 */ + /** + * 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 + */ protected boolean existCustomTimeout; - /** 命中数 */ + /** + * 命中数 + */ protected int hitCount; - /** 丢失数 */ + /** + * 丢失数 + */ protected int missCount; // ---------------------------------------------------------------- put start @@ -53,20 +58,19 @@ public abstract class AbstractCache implements Cache { @Override public void put(K key, V object, long timeout) { - writeLock.lock(); - + final long stamp = lock.writeLock(); try { putWithoutLock(key, object, timeout); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } /** * 加入元素,无锁 - * - * @param key 键 - * @param object 值 + * + * @param key 键 + * @param object 值 * @param timeout 超时时长 * @since 4.5.16 */ @@ -85,8 +89,7 @@ public abstract class AbstractCache implements Cache { // ---------------------------------------------------------------- get start @Override public boolean containsKey(K key) { - readLock.lock(); - + final long stamp = lock.readLock(); try { // 不存在或已移除 final CacheObj co = cacheMap.get(key); @@ -99,7 +102,7 @@ public abstract class AbstractCache implements Cache { return true; } } finally { - readLock.unlock(); + lock.unlockRead(stamp); } // 过期 @@ -111,24 +114,14 @@ public abstract class AbstractCache implements Cache { * @return 命中数 */ public int getHitCount() { - this.readLock.lock(); - try { - return hitCount; - } finally { - this.readLock.unlock(); - } + return hitCount; } /** * @return 丢失数 */ public int getMissCount() { - this.readLock.lock(); - try { - return missCount; - } finally { - this.readLock.unlock(); - } + return missCount; } @Override @@ -140,11 +133,11 @@ public abstract class AbstractCache implements Cache { public V get(K key, Func0 supplier) { V v = get(key); if (null == v && null != supplier) { - writeLock.lock(); + final long stamp = lock.writeLock(); try { // 双重检查锁 final CacheObj co = cacheMap.get(key); - if(null == co || co.isExpired() || null == co.getValue()) { + if (null == co || co.isExpired()) { try { v = supplier.call(); } catch (Exception e) { @@ -155,7 +148,7 @@ public abstract class AbstractCache implements Cache { v = co.get(true); } } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } return v; @@ -163,23 +156,25 @@ public abstract class AbstractCache implements Cache { @Override public V get(K key, boolean isUpdateLastAccess) { - readLock.lock(); - + // 尝试读取缓存,使用乐观读锁 + long stamp = lock.readLock(); try { // 不存在或已移除 final CacheObj co = cacheMap.get(key); - if (co == null) { + if (null == co) { missCount++; return null; } - if (false == co.isExpired()) { + if (co.isExpired()) { + missCount++; + } else{ // 命中 hitCount++; return co.get(isUpdateLastAccess); } } finally { - readLock.unlock(); + lock.unlock(stamp); } // 过期 @@ -189,7 +184,6 @@ public abstract class AbstractCache implements Cache { // ---------------------------------------------------------------- get end - @SuppressWarnings("NullableProblems") @Override public Iterator iterator() { CacheObjIterator copiedIterator = (CacheObjIterator) this.cacheObjIterator(); @@ -199,30 +193,32 @@ public abstract class AbstractCache implements Cache { @Override public Iterator> cacheObjIterator() { CopiedIter> copiedIterator; - readLock.lock(); + final long stamp = lock.readLock(); try { copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator()); } finally { - readLock.unlock(); + lock.unlockRead(stamp); } return new CacheObjIterator<>(copiedIterator); } // ---------------------------------------------------------------- prune start + /** - * 清理实现 - * + * 清理实现
    + * 子类实现此方法时无需加锁 + * * @return 清理数 */ protected abstract int pruneCache(); @Override public final int prune() { - writeLock.lock(); + final long stamp = lock.writeLock(); try { return pruneCache(); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } // ---------------------------------------------------------------- prune end @@ -235,7 +231,7 @@ public abstract class AbstractCache implements Cache { /** * @return 默认缓存失效时长。
    - * 每个对象可以单独设置失效时长 + * 每个对象可以单独设置失效时长 */ @Override public long timeout() { @@ -244,26 +240,16 @@ public abstract class AbstractCache implements Cache { /** * 只有设置公共缓存失效时长或每个对象单独的失效时长时清理可用 - * + * * @return 过期对象清理是否可用,内部使用 */ protected boolean isPruneExpiredActive() { - this.readLock.lock(); - try { - return (timeout != 0) || existCustomTimeout; - } finally { - this.readLock.unlock(); - } + return (timeout != 0) || existCustomTimeout; } @Override public boolean isFull() { - this.readLock.lock(); - try { - return (capacity > 0) && (cacheMap.size() >= capacity); - } finally { - this.readLock.unlock(); - } + return (capacity > 0) && (cacheMap.size() >= capacity); } @Override @@ -273,49 +259,34 @@ public abstract class AbstractCache implements Cache { @Override public void clear() { - writeLock.lock(); + final long stamp = lock.writeLock(); try { cacheMap.clear(); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } @Override public int size() { - this.readLock.lock(); - try { - return cacheMap.size(); - } finally { - this.readLock.unlock(); - } + return cacheMap.size(); } @Override public boolean isEmpty() { - this.readLock.lock(); - try { - return cacheMap.isEmpty(); - } finally { - this.readLock.unlock(); - } + return cacheMap.isEmpty(); } @Override public String toString() { - this.readLock.lock(); - try { - return this.cacheMap.toString(); - } finally { - this.readLock.unlock(); - } + return this.cacheMap.toString(); } // ---------------------------------------------------------------- common end /** * 对象移除回调。默认无动作 - * - * @param key 键 + * + * @param key 键 * @param cachedObject 被缓存的对象 */ protected void onRemove(K key, V cachedObject) { @@ -324,27 +295,27 @@ public abstract class AbstractCache implements Cache { /** * 移除key对应的对象 - * - * @param key 键 + * + * @param key 键 * @param withMissCount 是否计数丢失数 */ private void remove(K key, boolean withMissCount) { - writeLock.lock(); + final long stamp = lock.writeLock(); CacheObj co; try { co = removeWithoutLock(key, withMissCount); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } if (null != co) { onRemove(co.key, co.obj); } } - + /** * 移除key对应的对象,不加锁 - * - * @param key 键 + * + * @param key 键 * @param withMissCount 是否计数丢失数 * @return 移除的对象,无返回null */ diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java index 0964109cd..55d6ec896 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java @@ -1,10 +1,10 @@ package cn.hutool.cache.impl; -import java.util.Iterator; - import cn.hutool.cache.Cache; import cn.hutool.core.lang.func.Func0; +import java.util.Iterator; + /** * 无缓存实现,用于快速关闭缓存 * @@ -61,7 +61,17 @@ public class NoCache implements Cache { @Override public Iterator iterator() { - return null; + return new Iterator() { + @Override + public boolean hasNext() { + return false; + } + + @Override + public V next() { + return null; + } + }; } @Override diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java index 03add0533..292ea326f 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java @@ -28,7 +28,7 @@ public class TimedCache extends AbstractCache { * @param timeout 超时(过期)时长,单位毫秒 */ public TimedCache(long timeout) { - this(timeout, new HashMap>()); + this(timeout, new HashMap<>()); } /** diff --git a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java index 166c57a00..0fd8b80d9 100644 --- a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java +++ b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java @@ -1,15 +1,12 @@ package cn.hutool.cache.test; -import java.util.Iterator; - -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.cache.Cache; import cn.hutool.cache.impl.FIFOCache; import cn.hutool.cache.impl.LRUCache; import cn.hutool.core.lang.Console; import cn.hutool.core.thread.ThreadUtil; +import org.junit.Ignore; +import org.junit.Test; /** * 缓存单元测试 @@ -28,30 +25,22 @@ public class CacheConcurrentTest { // 由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除 for (int i = 0; i < threadCount; i++) { - ThreadUtil.execute(new Runnable() { - @Override - public void run() { - cache.put("key1", "value1", System.currentTimeMillis() * 3); - cache.put("key2", "value2", System.currentTimeMillis() * 3); - cache.put("key3", "value3", System.currentTimeMillis() * 3); - cache.put("key4", "value4", System.currentTimeMillis() * 3); - ThreadUtil.sleep(1000); - cache.put("key5", "value5", System.currentTimeMillis() * 3); - cache.put("key6", "value6", System.currentTimeMillis() * 3); - cache.put("key7", "value7", System.currentTimeMillis() * 3); - cache.put("key8", "value8", System.currentTimeMillis() * 3); - Console.log("put all"); - } + ThreadUtil.execute(() -> { + cache.put("key1", "value1", System.currentTimeMillis() * 3); + cache.put("key2", "value2", System.currentTimeMillis() * 3); + cache.put("key3", "value3", System.currentTimeMillis() * 3); + cache.put("key4", "value4", System.currentTimeMillis() * 3); + ThreadUtil.sleep(1000); + cache.put("key5", "value5", System.currentTimeMillis() * 3); + cache.put("key6", "value6", System.currentTimeMillis() * 3); + cache.put("key7", "value7", System.currentTimeMillis() * 3); + cache.put("key8", "value8", System.currentTimeMillis() * 3); + Console.log("put all"); }); } for (int i = 0; i < threadCount; i++) { - ThreadUtil.execute(new Runnable() { - @Override - public void run() { - show(cache); - } - }); + ThreadUtil.execute(() -> show(cache)); } System.out.println("=============================="); @@ -66,23 +55,20 @@ public class CacheConcurrentTest { for (int i = 0; i < threadCount; i++) { final int index = i; - ThreadUtil.execute(new Runnable() { - @Override - public void run() { - cache.put("key1"+ index, "value1"); - cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3); - - int size = cache.size(); - int capacity = cache.capacity(); - if(size > capacity) { - Console.log("{} {}", size, capacity); - } - ThreadUtil.sleep(1000); - size = cache.size(); - capacity = cache.capacity(); - if(size > capacity) { - Console.log("## {} {}", size, capacity); - } + ThreadUtil.execute(() -> { + cache.put("key1"+ index, "value1"); + cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3); + + int size = cache.size(); + int capacity = cache.capacity(); + if(size > capacity) { + Console.log("{} {}", size, capacity); + } + ThreadUtil.sleep(1000); + size = cache.size(); + capacity = cache.capacity(); + if(size > capacity) { + Console.log("## {} {}", size, capacity); } }); } @@ -91,10 +77,8 @@ public class CacheConcurrentTest { } private void show(Cache cache) { - Iterator its = cache.iterator(); - while (its.hasNext()) { - Object tt = its.next(); + for (Object tt : cache) { Console.log(tt); } } diff --git a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java index 12ccbe74c..c1832ce28 100644 --- a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java +++ b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java @@ -58,7 +58,7 @@ public class CacheTest { //使用时间推近 lruCache.get("key1"); lruCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3); - + String value1 = lruCache.get("key1"); Assert.assertNotNull(value1); //由于缓存容量只有3,当加入第四个元素的时候,根据LRU规则,最少使用的将被移除(2被移除) diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index dd94659d2..ac2b49d38 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-captcha diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java index b8df607f0..193b2ae95 100644 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java @@ -1,17 +1,16 @@ package cn.hutool.captcha; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.image.BufferedImage; -import java.util.concurrent.ThreadLocalRandom; - import cn.hutool.core.img.GraphicsUtil; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.util.concurrent.ThreadLocalRandom; + /** * 圆圈干扰验证码 * @@ -73,7 +72,7 @@ public class CircleCaptcha extends AbstractCaptcha { /** * 绘制字符串 * - * @param g {@link Graphics}画笔 + * @param g {@link Graphics2D}画笔 * @param code 验证码 */ private void drawString(Graphics2D g, String code) { diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java index fa22ff72c..f99f577fc 100644 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java @@ -1,16 +1,16 @@ package cn.hutool.captcha; +import cn.hutool.core.img.GraphicsUtil; +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; + import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; -import cn.hutool.core.img.GraphicsUtil; -import cn.hutool.core.img.ImgUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.RandomUtil; - /** * 扭曲干扰验证码 * @@ -110,18 +110,15 @@ public class ShearCaptcha extends AbstractCaptcha { int period = RandomUtil.randomInt(this.width); - boolean borderGap = true; int frames = 1; int phase = RandomUtil.randomInt(2); for (int i = 0; i < h1; i++) { double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); g.copyArea(0, i, w1, 1, (int) d, 0); - if (borderGap) { - g.setColor(color); - g.drawLine((int) d, i, 0, i); - g.drawLine((int) d + w1, i, w1, i); - } + g.setColor(color); + g.drawLine((int) d, i, 0, i); + g.drawLine((int) d + w1, i, w1, i); } } @@ -162,6 +159,7 @@ public class ShearCaptcha extends AbstractCaptcha { * @param thickness 粗细 * @param c 颜色 */ + @SuppressWarnings("SameParameterValue") private void drawInterfere(Graphics g, int x1, int y1, int x2, int y2, int thickness, Color c) { // The thick line is in fact a filled polygon diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java b/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java index 2beb52194..748706d75 100644 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java @@ -16,7 +16,7 @@ public class MathGenerator implements CodeGenerator { private static final String operators = "+-*"; /** 参与计算数字最大长度 */ - private int numberLength; + private final int numberLength; /** * 构造 diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 9434f0194..53810980b 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java index 3e485b450..fb41e24ff 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java @@ -1,14 +1,5 @@ package cn.hutool.core.bean; -import java.io.Serializable; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - -import cn.hutool.core.annotation.Alias; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.util.BooleanUtil; @@ -18,6 +9,14 @@ import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.TypeUtil; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + /** * Bean信息描述做为BeanInfo替代方案,此对象持有JavaBean中的setters和getters等相关信息描述
    * 查找Getter和Setter方法时会: @@ -142,7 +141,7 @@ public class BeanDesc implements Serializable{ for (Field field : ReflectUtil.getFields(this.beanClass)) { if(false == ModifierUtil.isStatic(field)) { //只针对非static属性 - this.propMap.put(field.getName(), createProp(field)); + this.propMap.put(ReflectUtil.getFieldName(field), createProp(field)); } } return this; @@ -322,7 +321,7 @@ public class BeanDesc implements Serializable{ } /** - * 获取字段名,如果存在{@link Alias}注解,读取注解的值作为名称 + * 获取字段名,如果存在Alias注解,读取注解的值作为名称 * * @return 字段名 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java index ba7bead3e..47928ddcf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java @@ -1,13 +1,5 @@ package cn.hutool.core.bean; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.MapUtil; @@ -16,6 +8,14 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Bean路径表达式,用于获取多层嵌套Bean中的字段值或Bean对象
    * 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种: @@ -242,10 +242,6 @@ public class BeanPath implements Serializable{ } isNumStart = false; // 中括号结束加入下标 - if (builder.length() > 0) { - localPatternParts.add(unWrapIfPossible(builder)); - } - builder.reset(); } else { if (isNumStart) { // 非结束中括号情况下发现起始中括号报错(中括号未关闭) @@ -255,11 +251,11 @@ public class BeanPath implements Serializable{ isNumStart = true; } // 每一个边界符之前的表达式是一个完整的KEY,开始处理KEY - if (builder.length() > 0) { - localPatternParts.add(unWrapIfPossible(builder)); - } - builder.reset(); } + if (builder.length() > 0) { + localPatternParts.add(unWrapIfPossible(builder)); + } + builder.reset(); } else { // 非边界符号,追加字符 builder.append(c); diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index 4d007271d..17608fc02 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -42,6 +42,23 @@ import java.util.Map; */ public class BeanUtil { + /** + * 判断是否为可读的Bean对象,判定方法是: + * + *
    +	 *     1、是否存在只有无参数的getXXX方法或者isXXX方法
    +	 *     2、是否存在public类型的字段
    +	 * 
    + * + * @param clazz 待测试类 + * @return 是否为可读的Bean对象 + * @see #hasGetter(Class) + * @see #hasPublicField(Class) + */ + public static boolean isReadableBean(Class clazz) { + return hasGetter(clazz) || hasPublicField(clazz); + } + /** * 判断是否为Bean对象,判定方法是: * @@ -53,6 +70,7 @@ public class BeanUtil { * @param clazz 待测试类 * @return 是否为Bean对象 * @see #hasSetter(Class) + * @see #hasPublicField(Class) */ public static boolean isBean(Class clazz) { return hasSetter(clazz) || hasPublicField(clazz); @@ -458,7 +476,7 @@ public class BeanUtil { * @since 5.2.4 */ public static T toBean(Object source, Class clazz, CopyOptions options) { - final T target = ReflectUtil.newInstance(clazz); + final T target = ReflectUtil.newInstanceIfPossible(clazz); copyProperties(source, target, options); return target; } @@ -596,7 +614,7 @@ public class BeanUtil { * @return 目标对象 */ public static T copyProperties(Object source, Class tClass) { - T target = ReflectUtil.newInstance(tClass); + T target = ReflectUtil.newInstanceIfPossible(tClass); copyProperties(source, target, CopyOptions.create()); return target; } @@ -676,7 +694,7 @@ public class BeanUtil { */ public static T trimStrFields(T bean, String... ignoreFields) { if (bean == null) { - return bean; + return null; } final Field[] fields = ReflectUtil.getFields(bean.getClass()); diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java b/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java index 915e60e05..8df3da986 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java @@ -1,14 +1,14 @@ package cn.hutool.core.bean; -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.Map; - import cn.hutool.core.clone.CloneSupport; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReflectUtil; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Map; + /** * 动态Bean,通过反射对Bean的相关方法做操作
    * 支持Map和普通Bean @@ -19,8 +19,8 @@ import cn.hutool.core.util.ReflectUtil; public class DynaBean extends CloneSupport implements Serializable{ private static final long serialVersionUID = 1L; - private Class beanClass; - private Object bean; + private final Class beanClass; + private final Object bean; /** * 创建一个{@link DynaBean} diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java index 80fea3fd4..10fb83b65 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java @@ -130,7 +130,10 @@ public class BeanCopier implements Copier, Serializable { * @param bean Bean */ private void mapToBean(Map map, Object bean) { - valueProviderToBean(new MapValueProvider(map, this.copyOptions.ignoreCase), bean); + valueProviderToBean( + new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError), + bean + ); } /** @@ -264,7 +267,7 @@ public class BeanCopier implements Copier, Serializable { if (null == value && copyOptions.ignoreNullValue) { continue;// 当允许跳过空时,跳过 } - if (bean.equals(value)) { + if (bean == value) { continue;// 值不能为bean本身,防止循环引用 } diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java index e2598bfa8..d8663a6ce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java @@ -2,8 +2,6 @@ package cn.hutool.core.bean.copier; import java.lang.reflect.Type; -import cn.hutool.core.convert.Convert; - /** * 值提供者,用于提供Bean注入时参数对应值得抽象接口
    * 继承或匿名实例化此接口
    @@ -17,10 +15,10 @@ public interface ValueProvider{ /** * 获取值
    - * 返回值一般需要匹配被注入类型,如果不匹配会调用默认转换 {@link Convert#convert(Type, Object)}实现转换 + * 返回值一般需要匹配被注入类型,如果不匹配会调用默认转换 Convert#convert(Type, Object)实现转换 * * @param key Bean对象中参数名 - * @param valueType 被注入的值得类型 + * @param valueType 被注入的值的类型 * @return 对应参数名的值 */ Object value(T key, Type valueType); diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java index fbc410b2d..38bab46ed 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java @@ -3,6 +3,7 @@ package cn.hutool.core.bean.copier.provider; import cn.hutool.core.bean.BeanDesc.PropDesc; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.ValueProvider; +import cn.hutool.core.convert.Convert; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.util.StrUtil; @@ -42,20 +43,27 @@ public class BeanValueProvider implements ValueProvider { //boolean类型字段字段名支持两种方式 sourcePd = sourcePdMap.get(StrUtil.upperFirstAndAddPre(key, "is")); } - + + Object result = null; if (null != sourcePd) { final Method getter = sourcePd.getGetter(); if (null != getter) { try { - return getter.invoke(source); + result = getter.invoke(source); } catch (Exception e) { if (false == ignoreError) { throw new UtilException(e, "Inject [{}] error!", key); } } + + // 尝试将结果转换为目标类型,如果转换失败,返回原类型。 + final Object convertValue = Convert.convertWithCheck(valueType,result, null, ignoreError); + if(null != convertValue){ + result = convertValue; + } } } - return null; + return result; } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java index 2ab2a7e58..2007776b6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java @@ -16,15 +16,28 @@ import java.util.Map; */ public class MapValueProvider implements ValueProvider { - private Map map; + private final Map map; + private final boolean ignoreError; + + /** + * 构造 + * + * @param map Map + * @param ignoreCase 是否忽略key的大小写 + */ + public MapValueProvider(Map map, boolean ignoreCase) { + this(map, ignoreCase, false); + } /** * 构造 * * @param map Map * @param ignoreCase 是否忽略key的大小写 + * @param ignoreError 是否忽略错误 + * @since 5.3.2 */ - public MapValueProvider(Map map, boolean ignoreCase) { + public MapValueProvider(Map map, boolean ignoreCase, boolean ignoreError) { if(false == ignoreCase || map instanceof CaseInsensitiveMap) { //不忽略大小写或者提供的Map本身为CaseInsensitiveMap则无需转换 this.map = map; @@ -32,6 +45,7 @@ public class MapValueProvider implements ValueProvider { //转换为大小写不敏感的Map this.map = new CaseInsensitiveMap<>(map); } + this.ignoreError = ignoreError; } @Override @@ -42,15 +56,15 @@ public class MapValueProvider implements ValueProvider { value = map.get(StrUtil.toUnderlineCase(key)); } - return Convert.convert(valueType, value); + return Convert.convertWithCheck(valueType, value, null, this.ignoreError); } @Override public boolean containsKey(String key) { - //检查下划线模式 if(map.containsKey(key)) { return true; }else { + //检查下划线模式 return map.containsKey(StrUtil.toUnderlineCase(key)); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/CompareToBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/CompareToBuilder.java index 77cf72479..de0c6705b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/CompareToBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/CompareToBuilder.java @@ -1,13 +1,13 @@ package cn.hutool.core.builder; +import cn.hutool.core.util.ArrayUtil; + import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Comparator; -import cn.hutool.core.util.ArrayUtil; - /** * 用于构建 {@link java.lang.Comparable#compareTo(Object)} 方法的辅助工具 * @@ -418,7 +418,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Long.compare(lhs, rhs)); return this; } @@ -434,7 +434,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Integer.compare(lhs, rhs)); return this; } @@ -450,7 +450,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Short.compare(lhs, rhs)); return this; } @@ -466,7 +466,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Character.compare(lhs, rhs)); return this; } @@ -482,7 +482,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Byte.compare(lhs, rhs)); return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java index 07ad8c3d7..19a5299b1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java @@ -1,5 +1,8 @@ package cn.hutool.core.builder; +import cn.hutool.core.lang.Pair; +import cn.hutool.core.util.ArrayUtil; + import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -7,9 +10,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; -import cn.hutool.core.lang.Pair; -import cn.hutool.core.util.ArrayUtil; - /** *

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

    * @@ -39,830 +39,828 @@ import cn.hutool.core.util.ArrayUtil; * return EqualsBuilder.reflectionEquals(this, obj); * } * - * */ public class EqualsBuilder implements Builder { private static final long serialVersionUID = 1L; - /** - *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    - * Unregisters the given object pair. - *

    - * - *

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

    + * Unregisters the given object pair. + *

    + * + *

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

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

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

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

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

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

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

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

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

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

    - * - *

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

    - * - *

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

    - * - *

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

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

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

    + * + *

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

    + * + *

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

    + * + *

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

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

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

    - * - *

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

    - * - *

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

    - * - *

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

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

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

    + * + *

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

    + * + *

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

    + * + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Test if two ints are equal.

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

    Test if two ints are equal.

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

    Test if two shorts are equal.

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

    Test if two shorts are equal.

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

    Test if two chars are equal.

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

    Test if two chars are equal.

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

    Test if two bytes are equal.

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

    Test if two bytes are equal.

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

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

    - * - *

    This handles NaNs, Infinities, and -0.0.

    - * - *

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

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

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

    + * + *

    This handles NaNs, Infinities, and -0.0.

    + * + *

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

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

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

    - * - *

    This handles NaNs, Infinities, and -0.0.

    - * - *

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

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

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

    + * + *

    This handles NaNs, Infinities, and -0.0.

    + * + *

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

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

    Test if two booleanss are equal.

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

    Test if two booleanss are equal.

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

    Performs a deep comparison of two Object arrays.

    - * - *

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

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

    Performs a deep comparison of two Object arrays.

    + * + *

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

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

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

    - * - *

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

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

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

    + * + *

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

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

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

    - * - *

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

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

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

    + * + *

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

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

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

    - * - *

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

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

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

    + * + *

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

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

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

    - * - *

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

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

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

    + * + *

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

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

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

    - * - *

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

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

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

    + * + *

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

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

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

    - * - *

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

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

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

    + * + *

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

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

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

    - * - *

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

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

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

    + * + *

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

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

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

    - * - *

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

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

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

    + * + *

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

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

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

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

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

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

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

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

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

    + * + * @return true if all of the fields that have been checked + * are equal, false otherwise. + * @since 3.0 + */ + @Override + public Boolean build() { + return isEquals(); + } - /** - * Sets the isEquals value. - * - * @param isEquals The value to set. - * @since 2.1 - */ - protected void setEquals(final boolean isEquals) { - this.isEquals = isEquals; - } + /** + * Sets the isEquals value. + * + * @param isEquals The value to set. + * @since 2.1 + */ + protected void setEquals(boolean isEquals) { + this.isEquals = isEquals; + } - /** - * Reset the EqualsBuilder so you can use the same object again - * @since 2.5 - */ - public void reset() { - this.isEquals = true; - } + /** + * Reset the EqualsBuilder so you can use the same object again + * + * @since 2.5 + */ + public void reset() { + this.isEquals = true; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java index d796f882b..979b568c8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java @@ -1,14 +1,14 @@ package cn.hutool.core.codec; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; + import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; - /** * Base64工具类,提供Base64的编码和解码方案
    * base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符,
    @@ -72,7 +72,7 @@ public class Base64 { * @return 被加密后的字符串 */ public static String encode(CharSequence source, String charset) { - return Base64Encoder.encode(source, CharsetUtil.charset(charset)); + return encode(source, CharsetUtil.charset(charset)); } /** @@ -84,7 +84,7 @@ public class Base64 { * @since 3.0.6 */ public static String encodeUrlSafe(CharSequence source, String charset) { - return Base64Encoder.encodeUrlSafe(source, CharsetUtil.charset(charset)); + return encodeUrlSafe(source, CharsetUtil.charset(charset)); } /** @@ -272,7 +272,7 @@ public class Base64 { * @return 被加密后的字符串 */ public static String decodeStr(CharSequence source, String charset) { - return Base64Decoder.decodeStr(source, CharsetUtil.charset(charset)); + return decodeStr(source, CharsetUtil.charset(charset)); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/BoundedPriorityQueue.java b/hutool-core/src/main/java/cn/hutool/core/collection/BoundedPriorityQueue.java index c62c93709..d83d1135c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/BoundedPriorityQueue.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/BoundedPriorityQueue.java @@ -2,7 +2,6 @@ package cn.hutool.core.collection; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.PriorityQueue; @@ -80,7 +79,7 @@ public class BoundedPriorityQueue extends PriorityQueue{ */ public ArrayList toList() { final ArrayList list = new ArrayList<>(this); - Collections.sort(list, comparator); + list.sort(comparator); return list; } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 0f116f6a2..de399b88b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -9,6 +9,7 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Editor; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.Matcher; +import cn.hutool.core.lang.func.Func1; import cn.hutool.core.lang.hash.Hash32; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; @@ -21,7 +22,6 @@ import cn.hutool.core.util.TypeUtil; import java.lang.reflect.Type; import java.util.AbstractCollection; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -38,8 +38,10 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.NavigableSet; import java.util.Objects; import java.util.Set; +import java.util.SortedSet; import java.util.Stack; import java.util.TreeMap; import java.util.TreeSet; @@ -47,6 +49,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingDeque; +import java.util.function.Function; /** * 集合相关工具类 @@ -141,6 +144,74 @@ public class CollUtil { return union; } + /** + * 多个集合的非重复并集,类似于SQL中的“UNION DISTINCT”
    + * 针对一个集合中存在多个相同元素的情况,只保留一个
    + * 例如:集合1:[a, b, c, c, c],集合2:[a, b, c, c]
    + * 结果:[a, b, c],此结果中只保留了一个c + * + * @param 集合元素类型 + * @param coll1 集合1 + * @param coll2 集合2 + * @param otherColls 其它集合 + * @return 并集的集合,返回 {@link LinkedHashSet} + */ + @SafeVarargs + public static Set unionDistinct(Collection coll1, Collection coll2, Collection... otherColls) { + final Set result; + if (isEmpty(coll1)) { + result = new LinkedHashSet<>(); + } else { + result = new LinkedHashSet<>(coll1); + } + + if (isNotEmpty(coll2)) { + result.addAll(coll2); + } + + if (ArrayUtil.isNotEmpty(otherColls)) { + for (Collection otherColl : otherColls) { + result.addAll(otherColl); + } + } + + return result; + } + + /** + * 多个集合的完全并集,类似于SQL中的“UNION ALL”
    + * 针对一个集合中存在多个相同元素的情况,保留全部元素
    + * 例如:集合1:[a, b, c, c, c],集合2:[a, b, c, c]
    + * 结果:[a, b, c, c, c, a, b, c, c] + * + * @param 集合元素类型 + * @param coll1 集合1 + * @param coll2 集合2 + * @param otherColls 其它集合 + * @return 并集的集合,返回 {@link ArrayList} + */ + @SafeVarargs + public static List unionAll(Collection coll1, Collection coll2, Collection... otherColls) { + final List result; + if (isEmpty(coll1)) { + result = new ArrayList<>(); + } else { + result = new ArrayList<>(coll1); + } + + if (isNotEmpty(coll2)) { + result.addAll(coll2); + } + + if (ArrayUtil.isNotEmpty(otherColls)) { + for (Collection otherColl : otherColls) { + result.addAll(otherColl); + } + } + + return result; + } + /** * 两个集合的交集
    * 针对一个集合中存在多个相同元素的情况,计算两个集合中此元素的个数,保留最少的个数
    @@ -222,7 +293,7 @@ public class CollUtil { return coll1; } - final ArrayList result = new ArrayList<>(); + final List result = new ArrayList<>(); final Map map1 = countMap(coll1); final Map map2 = countMap(coll2); final Set elts = newHashSet(coll2); @@ -246,7 +317,7 @@ public class CollUtil { * * @param coll1 集合1 * @param coll2 集合2 - * @param 元素类型 + * @param 元素类型 * @return 单差集 */ public static Collection subtract(Collection coll1, Collection coll2) { @@ -255,6 +326,39 @@ public class CollUtil { return result; } + /** + * 计算集合的单差集,即只返回【集合1】中有,但是【集合2】中没有的元素,例如: + * + *
    +	 *     subtractToList([1,2,3,4],[2,3,4,5]) -》 [1]
    +	 * 
    + * + * @param coll1 集合1 + * @param coll2 集合2 + * @param 元素类型 + * @return 单差集 + * @since 5.3.5 + */ + public static List subtractToList(Collection coll1, Collection coll2) { + + if (isEmpty(coll1)) { + return ListUtil.empty(); + } + if (isEmpty(coll2)) { + return ListUtil.list(true, coll2); + } + + //将被交数用链表储存,防止因为频繁扩容影响性能 + final List result = new LinkedList<>(); + Set set = new HashSet<>(coll2); + for (T t : coll1) { + if (false == set.contains(t)) { + result.add(t); + } + } + return result; + } + /** * 判断指定集合是否包含指定值,如果集合为空(null或者空),返回{@code false},否则找到元素返回{@code true} * @@ -305,7 +409,15 @@ public class CollUtil { * @since 4.5.12 */ public static boolean containsAll(Collection coll1, Collection coll2) { - if (isEmpty(coll1) || isEmpty(coll2) || coll1.size() < coll2.size()) { + if (isEmpty(coll1)) { + return isEmpty(coll2); + } + + if (isEmpty(coll2)) { + return true; + } + + if (coll1.size() < coll2.size()) { return false; } @@ -328,10 +440,10 @@ public class CollUtil { * @param 集合元素类型 * @param collection 集合 * @return {@link Map} - * @see IterUtil#countMap(Iterable) + * @see IterUtil#countMap(Iterator) */ public static Map countMap(Iterable collection) { - return IterUtil.countMap(collection); + return IterUtil.countMap(null == collection ? null : collection.iterator()); } /** @@ -342,10 +454,31 @@ public class CollUtil { * @param iterable {@link Iterable} * @param conjunction 分隔符 * @return 连接后的字符串 - * @see IterUtil#join(Iterable, CharSequence) + * @see IterUtil#join(Iterator, CharSequence) */ public static String join(Iterable iterable, CharSequence conjunction) { - return IterUtil.join(iterable, conjunction); + if (null == iterable) { + return null; + } + return IterUtil.join(iterable.iterator(), conjunction); + } + + /** + * 以 conjunction 为分隔符将集合转换为字符串 + * + * @param 集合元素类型 + * @param iterable {@link Iterable} + * @param conjunction 分隔符 + * @param prefix 每个元素添加的前缀,null表示不添加 + * @param suffix 每个元素添加的后缀,null表示不添加 + * @return 连接后的字符串 + * @since 5.3.0 + */ + public static String join(Iterable iterable, CharSequence conjunction, String prefix, String suffix) { + if (null == iterable) { + return null; + } + return IterUtil.join(iterable.iterator(), conjunction, prefix, suffix); } /** @@ -356,8 +489,9 @@ public class CollUtil { * @param iterator 集合 * @param conjunction 分隔符 * @return 连接后的字符串 - * @see IterUtil#join(Iterator, CharSequence) + * @deprecated 请使用IterUtil#join(Iterator, CharSequence) */ + @Deprecated public static String join(Iterator iterator, CharSequence conjunction) { return IterUtil.join(iterator, conjunction); } @@ -486,7 +620,7 @@ public class CollUtil { */ @SafeVarargs public static LinkedHashSet newLinkedHashSet(T... ts) { - return (LinkedHashSet) newHashSet(true, ts); + return (LinkedHashSet) set(true, ts); } /** @@ -644,8 +778,8 @@ public class CollUtil { * 新建一个List
    * 提供的参数为null时返回空{@link ArrayList} * - * @param 集合元素类型 - * @param isLinked 是否新建LinkedList + * @param 集合元素类型 + * @param isLinked 是否新建LinkedList * @param enumeration {@link Enumeration} * @return ArrayList对象 * @since 3.0.8 @@ -708,7 +842,7 @@ public class CollUtil { * 新建一个ArrayList
    * 提供的参数为null时返回空{@link ArrayList} * - * @param 集合元素类型 + * @param 集合元素类型 * @param iterator {@link Iterator} * @return ArrayList对象 * @since 3.0.8 @@ -721,7 +855,7 @@ public class CollUtil { * 新建一个ArrayList
    * 提供的参数为null时返回空{@link ArrayList} * - * @param 集合元素类型 + * @param 集合元素类型 * @param enumeration {@link Enumeration} * @return ArrayList对象 * @since 3.0.8 @@ -824,7 +958,7 @@ public class CollUtil { /** * 创建Map
    - * 传入抽象Map{@link AbstractMap}和{@link Map}类将默认创建{@link HashMap} + * 传入AbstractMap和{@link Map}类将默认创建{@link HashMap} * * @param map键类型 * @param map值类型 @@ -923,6 +1057,9 @@ public class CollUtil { */ public static List> split(Collection collection, int size) { final List> result = new ArrayList<>(); + if (CollUtil.isEmpty(collection)) { + return result; + } ArrayList subList = new ArrayList<>(size); for (T t : collection) { @@ -1134,17 +1271,34 @@ public class CollUtil { * @param editor 编辑器 * @param ignoreNull 是否忽略空值 * @return 抽取后的新列表 + * @see #map(Iterable, Function, boolean) * @since 4.5.7 */ public static List extract(Iterable collection, Editor editor, boolean ignoreNull) { - final List fieldValueList = new ArrayList<>(); + return map(collection, editor::edit, ignoreNull); + } + + /** + * 通过func自定义一个规则,此规则将原集合中的元素转换成新的元素,生成新的列表返回
    + * 例如提供的是一个Bean列表,通过Function接口实现获取某个字段值,返回这个字段值组成的新列表 + * + * @param 集合元素类型 + * @param 返回集合元素类型 + * @param collection 原集合 + * @param func 编辑函数 + * @param ignoreNull 是否忽略空值 + * @return 抽取后的新列表 + * @since 5.3.5 + */ + public static List map(Iterable collection, Function func, boolean ignoreNull) { + final List fieldValueList = new ArrayList<>(); if (null == collection) { return fieldValueList; } - Object value; - for (Object bean : collection) { - value = editor.edit(bean); + R value; + for (T bean : collection) { + value = func.apply(bean); if (null == value && ignoreNull) { continue; } @@ -1177,7 +1331,7 @@ public class CollUtil { * @since 4.5.7 */ public static List getFieldValues(Iterable collection, final String fieldName, boolean ignoreNull) { - return extract(collection, bean -> { + return map(collection, bean -> { if (bean instanceof Map) { return ((Map) bean).get(fieldName); } else { @@ -1202,6 +1356,36 @@ public class CollUtil { return Convert.toList(elementType, fieldValues); } + /** + * 字段值与列表值对应的Map,常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况
    + * 例如:车牌号 =》车 + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 对象类型 + * @param iterable 对象列表 + * @param fieldName 字段名(会通过反射获取其值) + * @return 某个字段值与对象对应Map + * @since 5.0.6 + */ + public static Map fieldValueMap(Iterable iterable, String fieldName) { + return IterUtil.fieldValueMap(null == iterable ? null : iterable.iterator(), fieldName); + } + + /** + * 两个字段值组成新的Map + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 值类型,不确定使用Object + * @param iterable 对象列表 + * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) + * @param fieldNameForValue 做为值的字段名(会通过反射获取其值) + * @return 某个字段值与对象对应Map + * @since 5.0.6 + */ + public static Map fieldValueAsMap(Iterable iterable, String fieldNameForKey, String fieldNameForValue) { + return IterUtil.fieldValueAsMap(null == iterable ? null : iterable.iterator(), fieldNameForKey, fieldNameForValue); + } + /** * 查找第一个匹配元素对象 * @@ -1310,6 +1494,30 @@ public class CollUtil { return count; } + /** + * 获取匹配规则定义中匹配到元素的所有位置
    + * 此方法对于某些无序集合的位置信息,以转换为数组后的位置为准。 + * + * @param 元素类型 + * @param collection 集合 + * @param matcher 匹配器,为空则全部匹配 + * @return 位置数组 + * @since 5.2.5 + */ + public static int[] indexOfAll(Collection collection, Matcher matcher) { + final List indexList = new ArrayList<>(); + if (null != collection) { + int index = 0; + for (T t : collection) { + if (null == matcher || matcher.match(t)) { + indexList.add(index); + } + index++; + } + } + return Convert.convert(int[].class, indexList); + } + // ---------------------------------------------------------------------- isEmpty /** @@ -1692,6 +1900,40 @@ public class CollUtil { return MapUtil.toMapList(listMap); } + /** + * 集合转换为Map,转换规则为:
    + * 按照keyFunc函数规则根据元素对象生成Key,元素作为值 + * + * @param Map键类型 + * @param Map值类型 + * @param values 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 + * @param keyFunc 生成key的函数 + * @return 生成的map + * @since 5.2.6 + */ + public static Map toMap(Iterable values, Map map, Func1 keyFunc) { + return IterUtil.toMap(null == values ? null : values.iterator(), map, keyFunc); + } + + /** + * 集合转换为Map,转换规则为:
    + * 按照keyFunc函数规则根据元素对象生成Key,按照valueFunc函数规则根据元素对象生成value组成新的Map + * + * @param Map键类型 + * @param Map值类型 + * @param 元素类型 + * @param values 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 + * @param keyFunc 生成key的函数 + * @param valueFunc 生成值的策略函数 + * @return 生成的map + * @since 5.2.6 + */ + public static Map toMap(Iterable values, Map map, Func1 keyFunc, Func1 valueFunc) { + return IterUtil.toMap(null == values ? null : values.iterator(), map, keyFunc, valueFunc); + } + /** * 将指定对象全部加入到集合中
    * 提供的对象如果为集合类型,会自动转换为目标元素类型
    @@ -2249,7 +2491,8 @@ public class CollUtil { } /** - * 循环遍历Map,使用{@link KVConsumer} 接受遍历的每条数据,并针对每条数据做处理 + * 循环遍历Map,使用{@link KVConsumer} 接受遍历的每条数据,并针对每条数据做处理
    + * 和JDK8中的map.forEach不同的是,此方法支持index * * @param Key类型 * @param Value类型 @@ -2434,6 +2677,70 @@ public class CollUtil { return Collections.min(coll); } + /** + * 转为只读集合 + * + * @param 元素类型 + * @param c 集合 + * @return 只读集合 + * @since 5.2.6 + */ + public static Collection unmodifiable(Collection c) { + return Collections.unmodifiableCollection(c); + } + + /** + * 根据给定的集合类型,返回对应的空集合,支持类型包括: + * * + *
    +	 *     1. NavigableSet
    +	 *     2. SortedSet
    +	 *     3. Set
    +	 *     4. List
    +	 * 
    + * + * @param 元素类型 + * @param 集合类型 + * @param collectionClass 集合类型 + * @return 空集合 + * @since 5.3.1 + */ + @SuppressWarnings("unchecked") + public static > T empty(Class collectionClass) { + if (null == collectionClass) { + return (T) Collections.emptyList(); + } + + if (Set.class.isAssignableFrom(collectionClass)) { + if (NavigableSet.class == collectionClass) { + return (T) Collections.emptyNavigableSet(); + } else if (SortedSet.class == collectionClass) { + return (T) Collections.emptySortedSet(); + } else { + return (T) Collections.emptySet(); + } + } else if (List.class.isAssignableFrom(collectionClass)) { + return (T) Collections.emptyList(); + } + + // 不支持空集合的集合类型 + throw new IllegalArgumentException(StrUtil.format("[{}] is not support to get empty!", collectionClass)); + } + + /** + * 清除一个或多个集合内的元素,每个集合调用clear()方法 + * + * @param collections 一个或多个集合 + * @since 5.3.6 + */ + public static void clear(Collection... collections) { + for (Collection collection : collections) { + if (isNotEmpty(collection)) { + collection.clear(); + } + } + } + // ---------------------------------------------------------------------------------------------- Interface start /** diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CopiedIter.java b/hutool-core/src/main/java/cn/hutool/core/collection/CopiedIter.java index a9c836423..9b77361fe 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CopiedIter.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CopiedIter.java @@ -24,7 +24,7 @@ import java.util.List; public class CopiedIter implements Iterator, Iterable, Serializable { private static final long serialVersionUID = 1L; - private Iterator listIterator; + private final Iterator listIterator; public static CopiedIter copyOf(Iterator iterator){ return new CopiedIter<>(iterator); diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index 9de7ce377..53b15b249 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -1,17 +1,27 @@ package cn.hutool.core.collection; +import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.func.Func1; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.function.Function; /** * {@link Iterable} 和 {@link Iterator} 相关工具类 - * + * * @author Looly * @since 3.1.0 */ @@ -19,7 +29,7 @@ public class IterUtil { /** * Iterable是否为空 - * + * * @param iterable Iterable对象 * @return 是否为空 */ @@ -29,7 +39,7 @@ public class IterUtil { /** * Iterator是否为空 - * + * * @param Iterator Iterator对象 * @return 是否为空 */ @@ -39,7 +49,7 @@ public class IterUtil { /** * Iterable是否为空 - * + * * @param iterable Iterable对象 * @return 是否为空 */ @@ -49,7 +59,7 @@ public class IterUtil { /** * Iterator是否为空 - * + * * @param Iterator Iterator对象 * @return 是否为空 */ @@ -59,7 +69,7 @@ public class IterUtil { /** * 是否包含{@code null}元素 - * + * * @param iter 被检查的{@link Iterable}对象,如果为{@code null} 返回true * @return 是否包含{@code null}元素 */ @@ -69,7 +79,7 @@ public class IterUtil { /** * 是否包含{@code null}元素 - * + * * @param iter 被检查的{@link Iterator}对象,如果为{@code null} 返回true * @return 是否包含{@code null}元素 */ @@ -88,7 +98,7 @@ public class IterUtil { /** * 是否全部元素为null - * + * * @param iter iter 被检查的{@link Iterable}对象,如果为{@code null} 返回true * @return 是否全部元素为null * @since 3.3.0 @@ -99,7 +109,7 @@ public class IterUtil { /** * 是否全部元素为null - * + * * @param iter iter 被检查的{@link Iterator}对象,如果为{@code null} 返回true * @return 是否全部元素为null * @since 3.3.0 @@ -124,11 +134,13 @@ public class IterUtil { * a: 1
    * b: 1
    * c: 3
    - * - * @param 集合元素类型 + * + * @param 集合元素类型 * @param iter {@link Iterable},如果为null返回一个空的Map * @return {@link Map} + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.countMap */ + @Deprecated public static Map countMap(Iterable iter) { return countMap(null == iter ? null : iter.iterator()); } @@ -140,8 +152,8 @@ public class IterUtil { * a: 1
    * b: 1
    * c: 3
    - * - * @param 集合元素类型 + * + * @param 集合元素类型 * @param iter {@link Iterator},如果为null返回一个空的Map * @return {@link Map} */ @@ -166,14 +178,16 @@ public class IterUtil { /** * 字段值与列表值对应的Map,常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况
    * 例如:车牌号 =》车 - * - * @param 字段名对应值得类型,不确定请使用Object - * @param 对象类型 - * @param iter 对象列表 + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 对象类型 + * @param iter 对象列表 * @param fieldName 字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.0.4 + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.fieldValueMap */ + @Deprecated public static Map fieldValueMap(Iterable iter, String fieldName) { return fieldValueMap(null == iter ? null : iter.iterator(), fieldName); } @@ -181,71 +195,60 @@ public class IterUtil { /** * 字段值与列表值对应的Map,常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况
    * 例如:车牌号 =》车 - * - * @param 字段名对应值得类型,不确定请使用Object - * @param 对象类型 - * @param iter 对象列表 + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 对象类型 + * @param iter 对象列表 * @param fieldName 字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.0.4 */ @SuppressWarnings("unchecked") public static Map fieldValueMap(Iterator iter, String fieldName) { - final Map result = new HashMap<>(); - if (null != iter) { - V value; - while (iter.hasNext()) { - value = iter.next(); - result.put((K) ReflectUtil.getFieldValue(value, fieldName), value); - } - } - return result; + return toMap(iter, new HashMap<>(), (value) -> (K) ReflectUtil.getFieldValue(value, fieldName)); } - + /** * 两个字段值组成新的Map - * - * @param 字段名对应值得类型,不确定请使用Object - * @param 值类型,不确定使用Object - * @param iterable 对象列表 - * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 值类型,不确定使用Object + * @param iterable 对象列表 + * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) * @param fieldNameForValue 做为值的字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.6.2 + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.fieldValueMap */ + @Deprecated public static Map fieldValueAsMap(Iterable iterable, String fieldNameForKey, String fieldNameForValue) { return fieldValueAsMap(null == iterable ? null : iterable.iterator(), fieldNameForKey, fieldNameForValue); } /** * 两个字段值组成新的Map - * - * @param 字段名对应值得类型,不确定请使用Object - * @param 值类型,不确定使用Object - * @param iter 对象列表 - * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 值类型,不确定使用Object + * @param iter 对象列表 + * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) * @param fieldNameForValue 做为值的字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.0.10 */ @SuppressWarnings("unchecked") public static Map fieldValueAsMap(Iterator iter, String fieldNameForKey, String fieldNameForValue) { - final Map result = new HashMap<>(); - if (null != iter) { - Object value; - while (iter.hasNext()) { - value = iter.next(); - result.put((K) ReflectUtil.getFieldValue(value, fieldNameForKey), (V) ReflectUtil.getFieldValue(value, fieldNameForValue)); - } - } - return result; + return toMap(iter, new HashMap<>(), + (value) -> (K) ReflectUtil.getFieldValue(value, fieldNameForKey), + (value) -> (V) ReflectUtil.getFieldValue(value, fieldNameForValue) + ); } - + /** * 获取指定Bean列表中某个字段,生成新的列表 - * - * @param 对象类型 - * @param iterable 对象列表 + * + * @param 对象类型 + * @param iterable 对象列表 * @param fieldName 字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.6.2 @@ -256,9 +259,9 @@ public class IterUtil { /** * 获取指定Bean列表中某个字段,生成新的列表 - * - * @param 对象类型 - * @param iter 对象列表 + * + * @param 对象类型 + * @param iter 对象列表 * @param fieldName 字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.0.10 @@ -277,43 +280,47 @@ public class IterUtil { /** * 以 conjunction 为分隔符将集合转换为字符串 - * - * @param 集合元素类型 - * @param iterable {@link Iterable} + * + * @param 集合元素类型 + * @param iterable {@link Iterable} * @param conjunction 分隔符 * @return 连接后的字符串 + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.join */ + @Deprecated public static String join(Iterable iterable, CharSequence conjunction) { if (null == iterable) { return null; } return join(iterable.iterator(), conjunction); } - + /** * 以 conjunction 为分隔符将集合转换为字符串 - * - * @param 集合元素类型 - * @param iterable {@link Iterable} + * + * @param 集合元素类型 + * @param iterable {@link Iterable} * @param conjunction 分隔符 - * @param prefix 每个元素添加的前缀,null表示不添加 - * @param suffix 每个元素添加的后缀,null表示不添加 + * @param prefix 每个元素添加的前缀,null表示不添加 + * @param suffix 每个元素添加的后缀,null表示不添加 * @return 连接后的字符串 * @since 4.0.10 + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.join */ + @Deprecated public static String join(Iterable iterable, CharSequence conjunction, String prefix, String suffix) { if (null == iterable) { return null; } return join(iterable.iterator(), conjunction, prefix, suffix); } - + /** * 以 conjunction 为分隔符将集合转换为字符串
    * 如果集合元素为数组、{@link Iterable}或{@link Iterator},则递归组合其为字符串 - * - * @param 集合元素类型 - * @param iterator 集合 + * + * @param 集合元素类型 + * @param iterator 集合 * @param conjunction 分隔符 * @return 连接后的字符串 */ @@ -324,12 +331,12 @@ public class IterUtil { /** * 以 conjunction 为分隔符将集合转换为字符串
    * 如果集合元素为数组、{@link Iterable}或{@link Iterator},则递归组合其为字符串 - * - * @param 集合元素类型 - * @param iterator 集合 + * + * @param 集合元素类型 + * @param iterator 集合 * @param conjunction 分隔符 - * @param prefix 每个元素添加的前缀,null表示不添加 - * @param suffix 每个元素添加的后缀,null表示不添加 + * @param prefix 每个元素添加的前缀,null表示不添加 + * @param suffix 每个元素添加的后缀,null表示不添加 * @return 连接后的字符串 * @since 4.0.10 */ @@ -364,9 +371,9 @@ public class IterUtil { /** * 将Entry集合转换为HashMap - * - * @param 键类型 - * @param 值类型 + * + * @param 键类型 + * @param 值类型 * @param entryIter entry集合 * @return Map */ @@ -379,15 +386,15 @@ public class IterUtil { } return map; } - + /** * 将键列表和值列表转换为Map
    * 以键为准,值与键位置需对应。如果键元素数多于值元素,多余部分值用null代替。
    * 如果值多于键,忽略多余的值。 - * - * @param 键类型 - * @param 值类型 - * @param keys 键列表 + * + * @param 键类型 + * @param 值类型 + * @param keys 键列表 * @param values 值列表 * @return 标题内容Map * @since 3.1.0 @@ -400,11 +407,11 @@ public class IterUtil { * 将键列表和值列表转换为Map
    * 以键为准,值与键位置需对应。如果键元素数多于值元素,多余部分值用null代替。
    * 如果值多于键,忽略多余的值。 - * - * @param 键类型 - * @param 值类型 - * @param keys 键列表 - * @param values 值列表 + * + * @param 键类型 + * @param 值类型 + * @param keys 键列表 + * @param values 值列表 * @param isOrder 是否有序 * @return 标题内容Map * @since 4.1.12 @@ -412,15 +419,15 @@ public class IterUtil { public static Map toMap(Iterable keys, Iterable values, boolean isOrder) { return toMap(null == keys ? null : keys.iterator(), null == values ? null : values.iterator(), isOrder); } - + /** * 将键列表和值列表转换为Map
    * 以键为准,值与键位置需对应。如果键元素数多于值元素,多余部分值用null代替。
    * 如果值多于键,忽略多余的值。 - * - * @param 键类型 - * @param 值类型 - * @param keys 键列表 + * + * @param 键类型 + * @param 值类型 + * @param keys 键列表 * @param values 值列表 * @return 标题内容Map * @since 3.1.0 @@ -433,11 +440,11 @@ public class IterUtil { * 将键列表和值列表转换为Map
    * 以键为准,值与键位置需对应。如果键元素数多于值元素,多余部分值用null代替。
    * 如果值多于键,忽略多余的值。 - * - * @param 键类型 - * @param 值类型 - * @param keys 键列表 - * @param values 值列表 + * + * @param 键类型 + * @param 值类型 + * @param keys 键列表 + * @param values 值列表 * @param isOrder 是否有序 * @return 标题内容Map * @since 4.1.12 @@ -452,17 +459,133 @@ public class IterUtil { return resultMap; } + /** + * 将列表转成值为List的HashMap + * + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param 键类型 + * @param 值类型 + * @return HashMap + * @since 5.3.6 + */ + public static Map> toListMap(Iterable iterable, Function keyMapper) { + return toListMap(iterable, keyMapper, v -> v); + } + + /** + * 将列表转成值为List的HashMap + * + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map中List的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + * @since 5.3.6 + */ + public static Map> toListMap(Iterable iterable, Function keyMapper, Function valueMapper) { + return toListMap(MapUtil.newHashMap(), iterable, keyMapper, valueMapper); + } + + /** + * 将列表转成值为List的HashMap + * + * @param resultMap 结果Map,可自定义结果Map类型 + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map中List的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + * @since 5.3.6 + */ + public static Map> toListMap(Map> resultMap, Iterable iterable, Function keyMapper, Function valueMapper) { + if (null == resultMap) { + resultMap = MapUtil.newHashMap(); + } + if (ObjectUtil.isNull(iterable)) { + return resultMap; + } + + for (T value : iterable) { + resultMap.computeIfAbsent(keyMapper.apply(value), k -> new ArrayList<>()).add(valueMapper.apply(value)); + } + + return resultMap; + } + + /** + * 将列表转成HashMap + * + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param 键类型 + * @param 值类型 + * @return HashMap + * @since 5.3.6 + */ + public static Map toMap(Iterable iterable, Function keyMapper) { + return toMap(iterable, keyMapper, v -> v); + } + + /** + * 将列表转成HashMap + * + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + * @since 5.3.6 + */ + public static Map toMap(Iterable iterable, Function keyMapper, Function valueMapper) { + return toMap(MapUtil.newHashMap(), iterable, keyMapper, valueMapper); + } + + /** + * 将列表转成Map + * + * @param resultMap 结果Map,通过传入map对象决定结果的Map类型 + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + * @since 5.3.6 + */ + public static Map toMap(Map resultMap, Iterable iterable, Function keyMapper, Function valueMapper) { + if (null == resultMap) { + resultMap = MapUtil.newHashMap(); + } + if (ObjectUtil.isNull(iterable)) { + return resultMap; + } + + for (T value : iterable) { + resultMap.put(keyMapper.apply(value), valueMapper.apply(value)); + } + + return resultMap; + } + /** * Iterator转List
    * 不判断,直接生成新的List - * - * @param 元素类型 + * + * @param 元素类型 * @param iter {@link Iterator} * @return List * @since 4.0.6 */ public static List toList(Iterable iter) { - if(null == iter) { + if (null == iter) { return null; } return toList(iter.iterator()); @@ -471,8 +594,8 @@ public class IterUtil { /** * Iterator转List
    * 不判断,直接生成新的List - * - * @param 元素类型 + * + * @param 元素类型 * @param iter {@link Iterator} * @return List * @since 4.0.6 @@ -489,9 +612,9 @@ public class IterUtil { * Enumeration转换为Iterator *

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

     	 * 1、过滤出需要的对象,{@link Filter#accept(Object)}方法返回false的对象将被使用{@link Iterator#remove()}方法移除
     	 * 
    - * - * @param 集合类型 - * @param 集合元素类型 - * @param iter 集合 + * + * @param 集合类型 + * @param 集合元素类型 + * @param iter 集合 * @param filter 过滤器接口 * @return 编辑后的集合 * @since 4.6.5 */ public static , E> T filter(T iter, Filter filter) { - if(null == iter) { + if (null == iter) { return null; } - + filter(iter.iterator(), filter); - + return iter; } - + /** * 过滤集合,此方法在原集合上直接修改
    * 通过实现Filter接口,完成元素的过滤,这个Filter实现可以实现以下功能: - * + * *
     	 * 1、过滤出需要的对象,{@link Filter#accept(Object)}方法返回false的对象将被使用{@link Iterator#remove()}方法移除
     	 * 
    - * - * @param 集合元素类型 - * @param iter 集合 + * + * @param 集合元素类型 + * @param iter 集合 * @param filter 过滤器接口 * @return 编辑后的集合 * @since 4.6.5 @@ -614,11 +737,74 @@ public class IterUtil { return iter; } - while(iter.hasNext()) { - if(false == filter.accept(iter.next())) { + while (iter.hasNext()) { + if (false == filter.accept(iter.next())) { iter.remove(); } } return iter; } + + /** + * Iterator转换为Map,转换规则为:
    + * 按照keyFunc函数规则根据元素对象生成Key,元素作为值 + * + * @param Map键类型 + * @param Map值类型 + * @param iterator 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 + * @param keyFunc 生成key的函数 + * @return 生成的map + * @since 5.2.6 + */ + public static Map toMap(Iterator iterator, Map map, Func1 keyFunc) { + return toMap(iterator, map, keyFunc, (value) -> value); + } + + /** + * 集合转换为Map,转换规则为:
    + * 按照keyFunc函数规则根据元素对象生成Key,按照valueFunc函数规则根据元素对象生成value组成新的Map + * + * @param Map键类型 + * @param Map值类型 + * @param 元素类型 + * @param iterator 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 + * @param keyFunc 生成key的函数 + * @param valueFunc 生成值的策略函数 + * @return 生成的map + * @since 5.2.6 + */ + public static Map toMap(Iterator iterator, Map map, Func1 keyFunc, Func1 valueFunc) { + if (null == iterator) { + return map; + } + + if (null == map) { + map = MapUtil.newHashMap(true); + } + + E element; + while (iterator.hasNext()) { + element = iterator.next(); + try { + map.put(keyFunc.call(element), valueFunc.call(element)); + } catch (Exception e) { + throw new UtilException(e); + } + } + return map; + } + + /** + * 返回一个空Iterator + * + * @param 元素类型 + * @return 空Iterator + * @see Collections#emptyIterator() + * @since 5.3.1 + */ + public static Iterator empty() { + return Collections.emptyIterator(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java index d45d2c60d..babe5f555 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java @@ -2,7 +2,9 @@ package cn.hutool.core.collection; import cn.hutool.core.comparator.PinyinComparator; import cn.hutool.core.comparator.PropertyComparator; +import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Editor; +import cn.hutool.core.lang.Matcher; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.PageUtil; @@ -419,4 +421,50 @@ public class ListUtil { } return list2; } + + /** + * 获取匹配规则定义中匹配到元素的所有位置 + * + * @param 元素类型 + * @param list 列表 + * @param matcher 匹配器,为空则全部匹配 + * @return 位置数组 + * @since 5.2.5 + */ + public static int[] indexOfAll(List list, Matcher matcher) { + final List indexList = new ArrayList<>(); + if (null != list) { + int index = 0; + for (T t : list) { + if (null == matcher || matcher.match(t)) { + indexList.add(index); + } + index++; + } + } + return Convert.convert(int[].class, indexList); + } + + /** + * 将对应List转换为不可修改的List + * + * @param list List + * @param 元素类型 + * @return 不可修改List + * @since 5.2.6 + */ + public static List unmodifiable(List list) { + return Collections.unmodifiableList(list); + } + + /** + * 获取一个空List + * + * @param 元素类型 + * @return 空的List + * @since 5.2.6 + */ + public static List empty() { + return Collections.emptyList(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java b/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java index 8dec1aa80..44fee28f0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java @@ -1,5 +1,7 @@ package cn.hutool.core.comparator; +import cn.hutool.core.lang.Chain; + import java.io.Serializable; import java.util.ArrayList; import java.util.BitSet; @@ -8,8 +10,6 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import cn.hutool.core.lang.Chain; - /** * 比较器链。此链包装了多个比较器,最终比较结果按照比较器顺序综合多个比较器结果。
    * 按照比较器链的顺序分别比较,如果比较出相等则转向下一个比较器,否则直接返回
    @@ -24,7 +24,7 @@ public class ComparatorChain implements Chain, ComparatorChain< /** 比较器链. */ private final List> chain; /** 对应比较器位置是否反序. */ - private BitSet orderingBits; + private final BitSet orderingBits; /** 比较器是否被锁定。锁定的比较器链不能再添加新的比较器。比较器会在开始比较时开始加锁。 */ private boolean lock = false; @@ -249,8 +249,9 @@ public class ComparatorChain implements Chain, ComparatorChain< } if (object.getClass().equals(this.getClass())) { final ComparatorChain otherChain = (ComparatorChain) object; - return (Objects.equals(this.orderingBits, otherChain.orderingBits)) // - && (null == otherChain ? null == otherChain.chain : this.chain.equals(otherChain.chain)); + // + return Objects.equals(this.orderingBits, otherChain.orderingBits) + && this.chain.equals(otherChain.chain); } return false; } diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java index 3795eec52..c592e7a30 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java @@ -2,6 +2,11 @@ package cn.hutool.core.comparator; import java.util.Comparator; +/** + * 比较工具类 + * + * @author looly + */ public class CompareUtil { /** @@ -22,7 +27,7 @@ public class CompareUtil { @SuppressWarnings({"rawtypes", "unchecked"}) public static int compare(T c1, T c2, Comparator comparator) { if (null == comparator) { - return compare((Comparable)c1, (Comparable)c2); + return compare((Comparable) c1, (Comparable) c2); } return comparator.compare(c1, c2); } diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java index 0b62eb2d5..adba14c20 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java @@ -1,9 +1,9 @@ package cn.hutool.core.comparator; -import java.util.Comparator; - import cn.hutool.core.util.ArrayUtil; +import java.util.Comparator; + /** * 按照数组的顺序正序排列,数组的元素位置决定了对象的排序先后
    * 如果参与排序的元素并不在数组中,则排序在前 @@ -15,7 +15,7 @@ import cn.hutool.core.util.ArrayUtil; */ public class IndexedComparator implements Comparator { - private T[] array; + private final T[] array; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/AbstractConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/AbstractConverter.java index 11aa91d66..73aa9435e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/AbstractConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/AbstractConverter.java @@ -58,7 +58,8 @@ public abstract class AbstractConverter implements Converter, Serializable T result = convertInternal(value); return ((null == result) ? defaultValue : result); } else { - throw new IllegalArgumentException(StrUtil.format("Default value [{}] is not the instance of [{}]", defaultValue, targetType)); + throw new IllegalArgumentException( + StrUtil.format("Default value [{}]({}) is not the instance of [{}]", defaultValue, defaultValue.getClass(), targetType)); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java b/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java index d4770dda7..d06fed1e8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java @@ -1,14 +1,5 @@ package cn.hutool.core.convert; -import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.time.Instant; -import java.time.LocalDateTime; -import java.util.*; -import java.util.concurrent.TimeUnit; - import cn.hutool.core.convert.impl.CollectionConverter; import cn.hutool.core.convert.impl.EnumConverter; import cn.hutool.core.convert.impl.MapConverter; @@ -20,6 +11,21 @@ import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + /** * 类型转换器 * @@ -674,7 +680,7 @@ public class Convert { * @throws ConvertException 转换器不存在 */ public static T convert(Type type, Object value, T defaultValue) throws ConvertException { - return ConverterRegistry.getInstance().convert(type, value, defaultValue); + return convertWithCheck(type, value, defaultValue, false); } /** @@ -703,10 +709,30 @@ public class Convert { * @since 4.5.10 */ public static T convertQuietly(Type type, Object value, T defaultValue) { + return convertWithCheck(type, value, defaultValue, true); + } + + /** + * 转换值为指定类型,可选是否不抛异常转换
    + * 当转换失败时返回默认值 + * + * @param 目标类型 + * @param type 目标类型 + * @param value 值 + * @param defaultValue 默认值 + * @param quietly 是否静默转换,true不抛异常 + * @return 转换后的值 + * @since 5.3.2 + */ + public static T convertWithCheck(Type type, Object value, T defaultValue, boolean quietly) { + final ConverterRegistry registry = ConverterRegistry.getInstance(); try { - return convert(type, value, defaultValue); + return registry.convert(type, value, defaultValue); } catch (Exception e) { - return defaultValue; + if(quietly){ + return defaultValue; + } + throw e; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java index 7869a3741..5b6d2c700 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java @@ -93,7 +93,7 @@ public class ConverterRegistry implements Serializable{ /** 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载 */ private static class SingletonHolder { /** 静态初始化器,由JVM来保证线程安全 */ - private static ConverterRegistry instance = new ConverterRegistry(); + private static final ConverterRegistry INSTANCE = new ConverterRegistry(); } /** @@ -102,7 +102,7 @@ public class ConverterRegistry implements Serializable{ * @return {@link ConverterRegistry} */ public static ConverterRegistry getInstance() { - return SingletonHolder.instance; + return SingletonHolder.INSTANCE; } public ConverterRegistry() { diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java index cb78ec6e4..72513ef59 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java @@ -65,7 +65,9 @@ public class BeanConverter extends AbstractConverter { @Override protected T convertInternal(Object value) { - if(value instanceof Map || value instanceof ValueProvider || BeanUtil.isBean(value.getClass())) { + if(value instanceof Map || + value instanceof ValueProvider || + BeanUtil.isBean(value.getClass())) { if(value instanceof Map && this.beanClass.isInterface()) { // 将Map动态代理为Bean return MapProxy.create((Map)value).toProxyBean(this.beanClass); diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BooleanConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BooleanConverter.java index a205369cd..44368e78f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BooleanConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BooleanConverter.java @@ -13,11 +13,8 @@ public class BooleanConverter extends AbstractConverter{ @Override protected Boolean convertInternal(Object value) { - if(boolean.class == value.getClass()){ - return Boolean.valueOf((boolean)value); - } - String valueStr = convertToStr(value); - return Boolean.valueOf(BooleanUtil.toBoolean(valueStr)); + //Object不可能出现Primitive类型,故忽略 + return BooleanUtil.toBoolean(convertToStr(value)); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/CharacterConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/CharacterConverter.java index a641aa7f1..8b3707cc9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/CharacterConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/CharacterConverter.java @@ -15,16 +15,12 @@ public class CharacterConverter extends AbstractConverter { @Override protected Character convertInternal(Object value) { - if (char.class == value.getClass()) { - return Character.valueOf((char) value); - } else if (value instanceof Boolean) { + if (value instanceof Boolean) { return BooleanUtil.toCharacter((Boolean) value); - } else if (boolean.class == value.getClass()) { - return BooleanUtil.toCharacter((boolean) value); } else { final String valueStr = convertToStr(value); if (StrUtil.isNotBlank(valueStr)) { - return Character.valueOf(valueStr.charAt(0)); + return valueStr.charAt(0); } } return null; diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/DateConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/DateConverter.java index 1ee9350c4..f9f0132ff 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/DateConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/DateConverter.java @@ -18,7 +18,7 @@ import java.util.Date; public class DateConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; /** 日期格式化 */ private String format; diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java index 1b4d5618b..292dc04c4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java @@ -25,7 +25,7 @@ public class EnumConverter extends AbstractConverter { private static final Map, Map, Method>> VALUE_OF_METHOD_CACHE = new ConcurrentHashMap<>(); - private Class enumClass; + private final Class enumClass; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java index 179717041..7291f55e0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java @@ -14,7 +14,7 @@ import cn.hutool.core.convert.AbstractConverter; public class GenericEnumConverter> extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class enumClass; + private final Class enumClass; /** * 构造 @@ -25,9 +25,9 @@ public class GenericEnumConverter> extends AbstractConverter * 支持类型为:
    @@ -17,20 +21,21 @@ import cn.hutool.core.util.StrUtil; *
  • java.lang.Byte
  • *
  • java.lang.Short
  • *
  • java.lang.Integer
  • + *
  • java.util.concurrent.atomic.AtomicInteger
  • *
  • java.lang.Long
  • + *
  • java.util.concurrent.atomic.AtomicLong
  • *
  • java.lang.Float
  • *
  • java.lang.Double
  • *
  • java.math.BigDecimal
  • *
  • java.math.BigInteger
  • * - * - * @author Looly * + * @author Looly */ public class NumberConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; public NumberConverter() { this.targetType = Number.class; @@ -38,7 +43,7 @@ public class NumberConverter extends AbstractConverter { /** * 构造
    - * + * * @param clazz 需要转换的数字类型,默认 {@link Number} */ public NumberConverter(Class clazz) { @@ -47,84 +52,88 @@ public class NumberConverter extends AbstractConverter { @Override protected Number convertInternal(Object value) { - final Class targetType = this.targetType; + return convertInternal(value, this.targetType); + } + + private Number convertInternal(Object value, Class targetType) { if (Byte.class == targetType) { if (value instanceof Number) { - return Byte.valueOf(((Number) value).byteValue()); - } else if(value instanceof Boolean) { - return BooleanUtil.toByteObj((Boolean)value); + return ((Number) value).byteValue(); + } else if (value instanceof Boolean) { + return BooleanUtil.toByteObj((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : Byte.valueOf(valueStr); - + } else if (Short.class == targetType) { if (value instanceof Number) { - return Short.valueOf(((Number) value).shortValue()); - } else if(value instanceof Boolean) { - return BooleanUtil.toShortObj((Boolean)value); + return ((Number) value).shortValue(); + } else if (value instanceof Boolean) { + return BooleanUtil.toShortObj((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : Short.valueOf(valueStr); } else if (Integer.class == targetType) { if (value instanceof Number) { - return Integer.valueOf(((Number) value).intValue()); - } else if(value instanceof Boolean) { - return BooleanUtil.toInteger((Boolean)value); + return ((Number) value).intValue(); + } else if (value instanceof Boolean) { + return BooleanUtil.toInteger((Boolean) value); + } else if (value instanceof Date) { + return (int)((Date) value).getTime(); + } else if (value instanceof Calendar) { + return (int)((Calendar) value).getTimeInMillis(); + } else if (value instanceof TemporalAccessor) { + return (int)DateUtil.toInstant((TemporalAccessor) value).toEpochMilli(); } final String valueStr = convertToStr(value); - return StrUtil.isBlank(valueStr) ? null : Integer.valueOf(NumberUtil.parseInt(valueStr)); - + return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseInt(valueStr); + } else if (AtomicInteger.class == targetType) { - final AtomicInteger intValue = new AtomicInteger(); - if (value instanceof Number) { - intValue.set(((Number) value).intValue()); - } else if(value instanceof Boolean) { - intValue.set(BooleanUtil.toInt((Boolean) value)); + final Number number = convertInternal(value, Integer.class); + if (null != number) { + final AtomicInteger intValue = new AtomicInteger(); + intValue.set(number.intValue()); + return intValue; } - final String valueStr = convertToStr(value); - if (StrUtil.isBlank(valueStr)) { - return null; - } - intValue.set(NumberUtil.parseInt(valueStr)); - return intValue; + return null; } else if (Long.class == targetType) { if (value instanceof Number) { - return Long.valueOf(((Number) value).longValue()); - } else if(value instanceof Boolean) { - return BooleanUtil.toLongObj((Boolean)value); + return ((Number) value).longValue(); + } else if (value instanceof Boolean) { + return BooleanUtil.toLongObj((Boolean) value); + } else if (value instanceof Date) { + return ((Date) value).getTime(); + } else if (value instanceof Calendar) { + return ((Calendar) value).getTimeInMillis(); + } else if (value instanceof TemporalAccessor) { + return DateUtil.toInstant((TemporalAccessor) value).toEpochMilli(); } final String valueStr = convertToStr(value); - return StrUtil.isBlank(valueStr) ? null : Long.valueOf(NumberUtil.parseLong(valueStr)); + return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseLong(valueStr); } else if (AtomicLong.class == targetType) { - final AtomicLong longValue = new AtomicLong(); - if (value instanceof Number) { - longValue.set(((Number) value).longValue()); - } else if(value instanceof Boolean) { - longValue.set(BooleanUtil.toLong((Boolean) value)); + final Number number = convertInternal(value, Long.class); + if (null != number) { + final AtomicLong longValue = new AtomicLong(); + longValue.set(number.longValue()); + return longValue; } - final String valueStr = convertToStr(value); - if (StrUtil.isBlank(valueStr)) { - return null; - } - longValue.set(NumberUtil.parseLong(valueStr)); - return longValue; - + return null; } else if (Float.class == targetType) { if (value instanceof Number) { - return Float.valueOf(((Number) value).floatValue()); - } else if(value instanceof Boolean) { - return BooleanUtil.toFloatObj((Boolean)value); + return ((Number) value).floatValue(); + } else if (value instanceof Boolean) { + return BooleanUtil.toFloatObj((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : Float.valueOf(valueStr); } else if (Double.class == targetType) { if (value instanceof Number) { - return Double.valueOf(((Number) value).doubleValue()); - } else if(value instanceof Boolean) { - return BooleanUtil.toDoubleObj((Boolean)value); + return ((Number) value).doubleValue(); + } else if (value instanceof Boolean) { + return BooleanUtil.toDoubleObj((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : Double.valueOf(valueStr); @@ -134,12 +143,12 @@ public class NumberConverter extends AbstractConverter { } else if (BigInteger.class == targetType) { return toBigInteger(value); - - }else if(Number.class == targetType){ + + } else if (Number.class == targetType) { if (value instanceof Number) { - return (Number)value; - } else if(value instanceof Boolean) { - return BooleanUtil.toInteger((Boolean)value); + return (Number) value; + } else if (value instanceof Boolean) { + return BooleanUtil.toInteger((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseNumber(valueStr); @@ -152,7 +161,7 @@ public class NumberConverter extends AbstractConverter { * 转换为BigDecimal
    * 如果给定的值为空,或者转换失败,返回默认值
    * 转换失败不会报错 - * + * * @param value 被转换的值 * @return 结果 */ @@ -163,8 +172,8 @@ public class NumberConverter extends AbstractConverter { return new BigDecimal((Integer) value); } else if (value instanceof BigInteger) { return new BigDecimal((BigInteger) value); - } else if(value instanceof Boolean) { - return new BigDecimal((boolean)value ? 1 : 0); + } else if (value instanceof Boolean) { + return new BigDecimal((boolean) value ? 1 : 0); } //对于Double类型,先要转换为String,避免精度问题 @@ -179,15 +188,15 @@ public class NumberConverter extends AbstractConverter { * 转换为BigInteger
    * 如果给定的值为空,或者转换失败,返回默认值
    * 转换失败不会报错 - * + * * @param value 被转换的值 * @return 结果 */ private BigInteger toBigInteger(Object value) { if (value instanceof Long) { return BigInteger.valueOf((Long) value); - } else if(value instanceof Boolean) { - return BigInteger.valueOf((boolean)value ? 1 : 0); + } else if (value instanceof Boolean) { + return BigInteger.valueOf((boolean) value ? 1 : 0); } final String valueStr = convertToStr(value); if (StrUtil.isBlank(valueStr)) { diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java index ee3a95f31..a8dd0b987 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java @@ -29,7 +29,7 @@ import java.util.Date; public class PrimitiveConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; /** * 构造
    diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/ReferenceConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/ReferenceConverter.java index 8dd269a9f..b9bbd42ce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/ReferenceConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/ReferenceConverter.java @@ -1,15 +1,15 @@ package cn.hutool.core.convert.impl; -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; -import java.lang.ref.WeakReference; -import java.lang.reflect.Type; - import cn.hutool.core.convert.AbstractConverter; import cn.hutool.core.convert.ConverterRegistry; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.TypeUtil; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Type; + /** * {@link Reference}转换器 * @@ -20,7 +20,7 @@ import cn.hutool.core.util.TypeUtil; public class ReferenceConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java index 8943fb888..02bfe6a22 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java @@ -4,6 +4,7 @@ import cn.hutool.core.convert.AbstractConverter; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import java.time.Instant; import java.time.LocalDate; @@ -39,7 +40,7 @@ import java.util.Objects; public class TemporalAccessorConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; /** * 日期格式化 */ @@ -107,6 +108,10 @@ public class TemporalAccessorConverter extends AbstractConverter + * + * @param startDate 起始日期(包含) + * @param endDate 结束日期(包含) + * @return 季度列表 ,元素类似于 20132 + * @since 4.1.15 + */ + public static LinkedHashSet yearAndQuarter(long startDate, long endDate) { + LinkedHashSet quarters = new LinkedHashSet<>(); + final Calendar cal = calendar(startDate); + while (startDate <= endDate) { + // 如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环 + quarters.add(yearAndQuarter(cal)); + + cal.add(Calendar.MONTH, 3); + startDate = cal.getTimeInMillis(); + } + + return quarters; + } + + /** + * 获得指定日期年份和季度
    + * 格式:[20131]表示2013年第一季度 + * + * @param cal 日期 + * @return 年和季度,格式类似于20131 + */ + public static String yearAndQuarter(Calendar cal) { + return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString(); + } + + /** + * 获取指定日期字段的最小值,例如分钟的最小值是0 + * + * @param calendar {@link Calendar} + * @param dateField {@link DateField} + * @return 字段最小值 + * @see Calendar#getActualMinimum(int) + * @since 4.5.7 + */ + public static int getBeginValue(Calendar calendar, int dateField) { + if (Calendar.DAY_OF_WEEK == dateField) { + return calendar.getFirstDayOfWeek(); + } + return calendar.getActualMinimum(dateField); + } + + /** + * 获取指定日期字段的最大值,例如分钟的最大值是59 + * + * @param calendar {@link Calendar} + * @param dateField {@link DateField} + * @return 字段最大值 + * @see Calendar#getActualMaximum(int) + * @since 4.5.7 + */ + public static int getEndValue(Calendar calendar, int dateField) { + if (Calendar.DAY_OF_WEEK == dateField) { + return (calendar.getFirstDayOfWeek() + 6) % 7; + } + return calendar.getActualMaximum(dateField); + } + + /** + * Calendar{@link Instant}对象 + * + * @param calendar Date对象 + * @return {@link Instant}对象 + * @since 5.0.5 + */ + public static Instant toInstant(Calendar calendar) { + return null == calendar ? null : calendar.toInstant(); + } + + /** + * {@link Calendar} 转换为 {@link LocalDateTime},使用系统默认时区 + * + * @param calendar {@link Calendar} + * @return {@link LocalDateTime} + * @since 5.0.5 + */ + public static LocalDateTime toLocalDateTime(Calendar calendar) { + return LocalDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId()); + } + + /** + * {@code null}安全的{@link Calendar}比较,{@code null}小于任何日期 + * + * @param calendar1 日期1 + * @param calendar2 日期2 + * @return 比较结果,如果calendar1 < calendar2,返回数小于0,calendar1==calendar2返回0,calendar1 > calendar2 大于0 + * @since 4.6.2 + */ + public static int compare(Calendar calendar1, Calendar calendar2) { + return CompareUtil.compare(calendar1, calendar2); + } + + /** + * 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄 + * + * @param birthday 生日 + * @param dateToCompare 需要对比的日期 + * @return 年龄 + */ + public static int age(Calendar birthday, Calendar dateToCompare) { + return age(birthday.getTimeInMillis(), dateToCompare.getTimeInMillis()); + } + + /** + * 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄 + * + * @param birthday 生日 + * @param dateToCompare 需要对比的日期 + * @return 年龄 + */ + protected static int age(long birthday, long dateToCompare) { + if (birthday > dateToCompare) { + throw new IllegalArgumentException("Birthday is after dateToCompare!"); + } + + final Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(dateToCompare); + + final int year = cal.get(Calendar.YEAR); + final int month = cal.get(Calendar.MONTH); + final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); + final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); + + cal.setTimeInMillis(birthday); + int age = year - cal.get(Calendar.YEAR); + + final int monthBirth = cal.get(Calendar.MONTH); + if (month == monthBirth) { + + final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH); + final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); + if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth < dayOfMonthBirth) { + // 如果生日在当月,但是未达到生日当天的日期,年龄减一 + age--; + } + } else if (month < monthBirth) { + // 如果当前月份未达到生日的月份,年龄计算减一 + age--; + } + + return age; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java index 09655aa72..09622ddd3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java @@ -19,16 +19,16 @@ public class ChineseDate { private static final Date baseDate = DateUtil.parseDate("1900-01-31"); //农历年 - private int year; + private final int year; //农历月 - private int month; + private final int month; //农历日 - private int day; + private final int day; //是否闰年 private boolean leap; - private String[] chineseNumber = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"}; - private String[] chineseNumberName = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"}; - private long[] lunarInfo = new long[]{0x04bd8, 0x04ae0, 0x0a570, + private final String[] chineseNumber = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"}; + private final String[] chineseNumberName = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"}; + private final long[] lunarInfo = new long[]{0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, @@ -51,7 +51,7 @@ public class ChineseDate { 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0}; //农历节日 *表示放假日 - private String[] lFtv = new String[]{ + private final String[] lFtv = new String[]{ "0101 春节", "0102 大年初二", "0103 大年初三", "0104 大年初四", "0105 大年初五", "0106 大年初六", "0107 大年初七", "0105 路神生日", "0115 元宵节", "0202 龙抬头", "0219 观世音圣诞", "0404 寒食节", diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java b/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java index ff2a5ed84..47c01fe3f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java @@ -16,9 +16,9 @@ public class DateBetween implements Serializable{ private static final long serialVersionUID = 1L; /** 开始日期 */ - private Date begin; + private final Date begin; /** 结束日期 */ - private Date end; + private final Date end; /** * 创建
    diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateField.java b/hutool-core/src/main/java/cn/hutool/core/date/DateField.java index d5b2b07f3..c8c572837 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateField.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateField.java @@ -103,7 +103,7 @@ public enum DateField { MILLISECOND(Calendar.MILLISECOND); // --------------------------------------------------------------- - private int value; + private final int value; DateField(int value) { this.value = value; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java index 1a0758155..d0626f382 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java @@ -1,5 +1,12 @@ package cn.hutool.core.date; +import cn.hutool.core.date.format.DateParser; +import cn.hutool.core.date.format.DatePrinter; +import cn.hutool.core.date.format.FastDateFormat; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; + import java.sql.Timestamp; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -13,13 +20,6 @@ import java.util.Date; import java.util.Locale; import java.util.TimeZone; -import cn.hutool.core.date.format.DateParser; -import cn.hutool.core.date.format.DatePrinter; -import cn.hutool.core.date.format.FastDateFormat; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; - /** * 包装java.util.Date * @@ -833,10 +833,10 @@ public class DateTime extends Date { // -------------------------------------------------------------------- toString start /** - * 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
    + * 转为"yyyy-MM-dd HH:mm:ss" 格式字符串
    * 如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区 * - * @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串 + * @return "yyyy-MM-dd HH:mm:ss" 格式字符串 */ @Override public String toString() { @@ -844,10 +844,10 @@ public class DateTime extends Date { } /** - * 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
    + * 转为"yyyy-MM-dd HH:mm:ss" 格式字符串
    * 时区使用当前地区的默认时区 * - * @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串 + * @return "yyyy-MM-dd HH:mm:ss" 格式字符串 * @since 4.1.14 */ public String toStringDefaultTimeZone() { @@ -855,11 +855,11 @@ public class DateTime extends Date { } /** - * 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
    + * 转为"yyyy-MM-dd HH:mm:ss" 格式字符串
    * 如果时区不为{@code null},会转换为其时区对应的时间,否则转换为当前时间对应的时区 * * @param timeZone 时区 - * @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串 + * @return "yyyy-MM-dd HH:mm:ss" 格式字符串 * @since 4.1.14 */ public String toString(TimeZone timeZone) { @@ -872,9 +872,9 @@ public class DateTime extends Date { } /** - * 转为"yyyy-MM-dd " 格式字符串 + * 转为"yyyy-MM-dd" 格式字符串 * - * @return "yyyy-MM-dd " 格式字符串 + * @return "yyyy-MM-dd" 格式字符串 * @since 4.0.0 */ public String toDateStr() { diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUnit.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUnit.java index 13a36c71d..2f2a58544 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUnit.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUnit.java @@ -19,7 +19,7 @@ public enum DateUnit { /**一周的毫秒数 */ WEEK(DAY.getMillis() * 7); - private long millis; + private final long millis; DateUnit(long millis){ this.millis = millis; } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 9160cdf78..bd4d160b6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -3,10 +3,10 @@ package cn.hutool.core.date; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.DateModifier.ModifyType; import cn.hutool.core.date.format.DateParser; import cn.hutool.core.date.format.DatePrinter; import cn.hutool.core.date.format.FastDateFormat; +import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.PatternPool; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ReUtil; @@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit; * * @author xiaoleilu */ -public class DateUtil { +public class DateUtil extends CalendarUtil { /** * java.util.Date EEE MMM zzz 缩写数组 @@ -129,42 +129,6 @@ public class DateUtil { return new DateTime(temporalAccessor); } - /** - * 创建Calendar对象,时间为默认时区的当前时间 - * - * @return Calendar对象 - * @since 4.6.6 - */ - public static Calendar calendar() { - return Calendar.getInstance(); - } - - /** - * 转换为Calendar对象 - * - * @param date 日期对象 - * @return Calendar对象 - */ - public static Calendar calendar(Date date) { - if (date instanceof DateTime) { - return ((DateTime) date).toCalendar(); - } else { - return calendar(date.getTime()); - } - } - - /** - * 转换为Calendar对象 - * - * @param millis 时间戳 - * @return Calendar对象 - */ - public static Calendar calendar(long millis) { - final Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(millis); - return cal; - } - /** * 当前时间的时间戳 * @@ -372,17 +336,6 @@ public class DateUtil { return DateTime.of(date).isAM(); } - /** - * 是否为上午 - * - * @param calendar {@link Calendar} - * @return 是否为上午 - * @since 4.5.7 - */ - public static boolean isAM(Calendar calendar) { - return Calendar.AM == calendar.get(Calendar.AM_PM); - } - /** * 是否为下午 * @@ -503,29 +456,6 @@ public class DateUtil { } return yearAndQuarter(startDate.getTime(), endDate.getTime()); } - - /** - * 获得指定日期区间内的年份和季节
    - * - * @param startDate 起始日期(包含) - * @param endDate 结束日期(包含) - * @return 季度列表 ,元素类似于 20132 - * @since 4.1.15 - */ - public static LinkedHashSet yearAndQuarter(long startDate, long endDate) { - LinkedHashSet quarters = new LinkedHashSet<>(); - final Calendar cal = calendar(startDate); - while (startDate <= endDate) { - // 如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环 - quarters.add(yearAndQuarter(cal)); - - cal.add(Calendar.MONTH, 3); - startDate = cal.getTimeInMillis(); - } - - return quarters; - } - // ------------------------------------ Format start ---------------------------------------------- /** @@ -797,7 +727,14 @@ public class DateUtil { } /** - * 格式yyyy-MM-dd HH:mm:ss + * 解析日期时间字符串,格式支持: + * + *
    +	 * yyyy-MM-dd HH:mm:ss
    +	 * yyyy/MM/dd HH:mm:ss
    +	 * yyyy.MM.dd HH:mm:ss
    +	 * yyyy年MM月dd日 HH:mm:ss
    +	 * 
    * * @param dateString 标准形式的时间字符串 * @return 日期对象 @@ -808,7 +745,13 @@ public class DateUtil { } /** - * 解析格式为yyyy-MM-dd的日期,忽略时分秒 + * 解析日期字符串,忽略时分秒,支持的格式包括: + *
    +	 * yyyy-MM-dd
    +	 * yyyy/MM/dd
    +	 * yyyy.MM.dd
    +	 * yyyy年MM月dd日
    +	 * 
    * * @param dateString 标准形式的日期字符串 * @return 日期对象 @@ -999,18 +942,6 @@ public class DateUtil { return new DateTime(truncate(calendar(date), dateField)); } - /** - * 修改日期为某个时间字段起始时间 - * - * @param calendar {@link Calendar} - * @param dateField 时间字段 - * @return 原{@link Calendar} - * @since 4.5.7 - */ - public static Calendar truncate(Calendar calendar, DateField dateField) { - return DateModifier.modify(calendar, dateField.getValue(), ModifyType.TRUNCATE); - } - /** * 修改日期为某个时间字段四舍五入时间 * @@ -1023,18 +954,6 @@ public class DateUtil { return new DateTime(round(calendar(date), dateField)); } - /** - * 修改日期为某个时间字段四舍五入时间 - * - * @param calendar {@link Calendar} - * @param dateField 时间字段 - * @return 原{@link Calendar} - * @since 4.5.7 - */ - public static Calendar round(Calendar calendar, DateField dateField) { - return DateModifier.modify(calendar, dateField.getValue(), ModifyType.ROUND); - } - /** * 修改日期为某个时间字段结束时间 * @@ -1047,18 +966,6 @@ public class DateUtil { return new DateTime(ceiling(calendar(date), dateField)); } - /** - * 修改日期为某个时间字段结束时间 - * - * @param calendar {@link Calendar} - * @param dateField 时间字段 - * @return 原{@link Calendar} - * @since 4.5.7 - */ - public static Calendar ceiling(Calendar calendar, DateField dateField) { - return DateModifier.modify(calendar, dateField.getValue(), ModifyType.CEILING); - } - /** * 获取秒级别的开始时间,即忽略毫秒部分 * @@ -1081,28 +988,6 @@ public class DateUtil { return new DateTime(endOfSecond(calendar(date))); } - /** - * 获取秒级别的开始时间,即忽略毫秒部分 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.6.2 - */ - public static Calendar beginOfSecond(Calendar calendar) { - return truncate(calendar, DateField.SECOND); - } - - /** - * 获取秒级别的结束时间,即毫秒设置为999 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.6.2 - */ - public static Calendar endOfSecond(Calendar calendar) { - return ceiling(calendar, DateField.SECOND); - } - /** * 获取某天的开始时间 * @@ -1123,26 +1008,6 @@ public class DateUtil { return new DateTime(endOfDay(calendar(date))); } - /** - * 获取某天的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfDay(Calendar calendar) { - return truncate(calendar, DateField.DAY_OF_MONTH); - } - - /** - * 获取某天的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfDay(Calendar calendar) { - return ceiling(calendar, DateField.DAY_OF_MONTH); - } - /** * 获取某周的开始时间,周一定为一周的开始时间 * @@ -1163,54 +1028,6 @@ public class DateUtil { return new DateTime(endOfWeek(calendar(date))); } - /** - * 获取给定日期当前周的开始时间,周一定为一周的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfWeek(Calendar calendar) { - return beginOfWeek(calendar, true); - } - - /** - * 获取给定日期当前周的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @param isMondayAsFirstDay 是否周一做为一周的第一天(false表示周日做为第一天) - * @return {@link Calendar} - * @since 3.1.2 - */ - public static Calendar beginOfWeek(Calendar calendar, boolean isMondayAsFirstDay) { - calendar.setFirstDayOfWeek(isMondayAsFirstDay ? Calendar.MONDAY : Calendar.SUNDAY); - // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH - return truncate(calendar, DateField.WEEK_OF_MONTH); - } - - /** - * 获取某周的结束时间,周日定为一周的结束 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfWeek(Calendar calendar) { - return endOfWeek(calendar, true); - } - - /** - * 获取某周的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @param isSundayAsLastDay 是否周日做为一周的最后一天(false表示周六做为最后一天) - * @return {@link Calendar} - * @since 3.1.2 - */ - public static Calendar endOfWeek(Calendar calendar, boolean isSundayAsLastDay) { - calendar.setFirstDayOfWeek(isSundayAsLastDay ? Calendar.MONDAY : Calendar.SUNDAY); - // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH - return ceiling(calendar, DateField.WEEK_OF_MONTH); - } - /** * 获取某月的开始时间 * @@ -1231,26 +1048,6 @@ public class DateUtil { return new DateTime(endOfMonth(calendar(date))); } - /** - * 获取某月的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfMonth(Calendar calendar) { - return truncate(calendar, DateField.MONTH); - } - - /** - * 获取某月的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfMonth(Calendar calendar) { - return ceiling(calendar, DateField.MONTH); - } - /** * 获取某季度的开始时间 * @@ -1271,34 +1068,6 @@ public class DateUtil { return new DateTime(endOfQuarter(calendar(date))); } - /** - * 获取某季度的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.1.0 - */ - public static Calendar beginOfQuarter(Calendar calendar) { - //noinspection MagicConstant - calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3); - calendar.set(Calendar.DAY_OF_MONTH, 1); - return beginOfDay(calendar); - } - - /** - * 获取某季度的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.1.0 - */ - public static Calendar endOfQuarter(Calendar calendar) { - //noinspection MagicConstant - calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3 + 2); - calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); - return endOfDay(calendar); - } - /** * 获取某年的开始时间 * @@ -1318,27 +1087,6 @@ public class DateUtil { public static DateTime endOfYear(Date date) { return new DateTime(endOfYear(calendar(date))); } - - /** - * 获取某年的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfYear(Calendar calendar) { - return truncate(calendar, DateField.YEAR); - } - - /** - * 获取某年的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfYear(Calendar calendar) { - return ceiling(calendar, DateField.YEAR); - } - // --------------------------------------------------- Offset for now /** @@ -1567,9 +1315,9 @@ public class DateUtil { /** * 计算指定指定时间区间内的周数 * - * @param beginDate 开始时间 - * @param endDate 结束时间 - * @param isReset 是否重置时间为起始时间 + * @param beginDate 开始时间 + * @param endDate 结束时间 + * @param isReset 是否重置时间为起始时间 * @return 周数 */ public static long betweenWeek(Date beginDate, Date endDate, boolean isReset) { @@ -1699,23 +1447,6 @@ public class DateUtil { return isSameDay(calendar(date1), calendar(date2)); } - /** - * 比较两个日期是否为同一天 - * - * @param cal1 日期1 - * @param cal2 日期2 - * @return 是否为同一天 - * @since 4.1.13 - */ - public static boolean isSameDay(Calendar cal1, Calendar cal2) { - if (cal1 == null || cal2 == null) { - throw new IllegalArgumentException("The date must not be null"); - } - return cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) && // - cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && // - cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA); - } - /** * 计时,常用于记录某段代码的执行时间,单位:纳秒 * @@ -1876,41 +1607,16 @@ public class DateUtil { /** * 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄 * - * @param birthDay 生日 + * @param birthday 生日 * @param dateToCompare 需要对比的日期 * @return 年龄 */ - public static int age(Date birthDay, Date dateToCompare) { - Calendar cal = Calendar.getInstance(); - cal.setTime(dateToCompare); - - if (cal.before(birthDay)) { - throw new IllegalArgumentException(StrUtil.format("Birthday is after date {}!", formatDate(dateToCompare))); + public static int age(Date birthday, Date dateToCompare) { + Assert.notNull(birthday, "Birthday can not be null !"); + if (null == dateToCompare) { + dateToCompare = date(); } - - final int year = cal.get(Calendar.YEAR); - final int month = cal.get(Calendar.MONTH); - final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); - final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); - - cal.setTime(birthDay); - int age = year - cal.get(Calendar.YEAR); - - final int monthBirth = cal.get(Calendar.MONTH); - if (month == monthBirth) { - - final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH); - final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); - if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth < dayOfMonthBirth) { - // 如果生日在当月,但是未达到生日当天的日期,年龄减一 - age--; - } - } else if (month < monthBirth) { - // 如果当前月份未达到生日的月份,年龄计算减一 - age--; - } - - return age; + return age(birthday.getTime(), dateToCompare.getTime()); } /** @@ -2055,38 +1761,6 @@ public class DateUtil { return Zodiac.getChineseZodiac(year); } - /** - * 获取指定日期字段的最小值,例如分钟的最小值是0 - * - * @param calendar {@link Calendar} - * @param dateField {@link DateField} - * @return 字段最小值 - * @see Calendar#getActualMinimum(int) - * @since 4.5.7 - */ - public static int getBeginValue(Calendar calendar, int dateField) { - if (Calendar.DAY_OF_WEEK == dateField) { - return calendar.getFirstDayOfWeek(); - } - return calendar.getActualMinimum(dateField); - } - - /** - * 获取指定日期字段的最大值,例如分钟的最大值是59 - * - * @param calendar {@link Calendar} - * @param dateField {@link DateField} - * @return 字段最大值 - * @see Calendar#getActualMaximum(int) - * @since 4.5.7 - */ - public static int getEndValue(Calendar calendar, int dateField) { - if (Calendar.DAY_OF_WEEK == dateField) { - return (calendar.getFirstDayOfWeek() + 6) % 7; - } - return calendar.getActualMaximum(dateField); - } - /** * {@code null}安全的日期比较,{@code null}对象排在末尾 * @@ -2099,18 +1773,6 @@ public class DateUtil { return CompareUtil.compare(date1, date2); } - /** - * {@code null}安全的{@link Calendar}比较,{@code null}小于任何日期 - * - * @param calendar1 日期1 - * @param calendar2 日期2 - * @return 比较结果,如果calendar1 < calendar2,返回数小于0,calendar1==calendar2返回0,calendar1 > calendar2 大于0 - * @since 4.6.2 - */ - public static int compare(Calendar calendar1, Calendar calendar2) { - return CompareUtil.compare(calendar1, calendar2); - } - /** * 纳秒转毫秒 * @@ -2144,17 +1806,6 @@ public class DateUtil { return null == date ? null : date.toInstant(); } - /** - * Calendar{@link Instant}对象 - * - * @param calendar Date对象 - * @return {@link Instant}对象 - * @since 5.0.5 - */ - public static Instant toInstant(Calendar calendar) { - return null == calendar ? null : calendar.toInstant(); - } - /** * Date对象转换为{@link Instant}对象 * @@ -2202,21 +1853,10 @@ public class DateUtil { return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); } - /** - * {@link Calendar} 转换为 {@link LocalDateTime},使用系统默认时区 - * - * @param calendar {@link Calendar} - * @return {@link LocalDateTime} - * @since 5.0.5 - */ - public static LocalDateTime toLocalDateTime(Calendar calendar) { - return LocalDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId()); - } - /** * {@link Date} 转换为 {@link LocalDateTime},使用系统默认时区 * - * @param date {@link Calendar} + * @param date {@link Date} * @return {@link LocalDateTime} * @since 5.0.5 */ @@ -2227,16 +1867,6 @@ public class DateUtil { // ------------------------------------------------------------------------ Private method start - /** - * 获得指定日期年份和季节
    - * 格式:[20131]表示2013年第一季度 - * - * @param cal 日期 - */ - private static String yearAndQuarter(Calendar cal) { - return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString(); - } - /** * 标准化日期,默认处理以空格区分的日期时间格式,空格前为日期,空格后为时间:
    * 将以下字符替换为"-" diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Month.java b/hutool-core/src/main/java/cn/hutool/core/date/Month.java index 9d64b9e15..3c35608d0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/Month.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/Month.java @@ -53,7 +53,7 @@ public enum Month { UNDECIMBER(Calendar.UNDECIMBER); // --------------------------------------------------------------- - private int value; + private final int value; Month(int value) { this.value = value; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Quarter.java b/hutool-core/src/main/java/cn/hutool/core/date/Quarter.java index 0382e6160..d735011c5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/Quarter.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/Quarter.java @@ -23,7 +23,7 @@ public enum Quarter { Q4(4); // --------------------------------------------------------------- - private int value; + private final int value; Quarter(int value) { this.value = value; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/SystemClock.java b/hutool-core/src/main/java/cn/hutool/core/date/SystemClock.java index d70842140..502acc67a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/SystemClock.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/SystemClock.java @@ -26,7 +26,7 @@ public class SystemClock { * 构造 * @param period 时钟更新间隔,单位毫秒 */ - private SystemClock(long period) { + public SystemClock(long period) { this.period = period; this.now = System.currentTimeMillis(); scheduleClockUpdating(); diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TimeInterval.java b/hutool-core/src/main/java/cn/hutool/core/date/TimeInterval.java index 896c47de1..8edd5cfe8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/TimeInterval.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/TimeInterval.java @@ -12,7 +12,7 @@ public class TimeInterval implements Serializable { private static final long serialVersionUID = 1L; private long time; - private boolean isNano; + private final boolean isNano; /** * 构造,默认使用毫秒计数 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Week.java b/hutool-core/src/main/java/cn/hutool/core/date/Week.java index af1c8add1..e4282f43a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/Week.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/Week.java @@ -36,7 +36,7 @@ public enum Week { // --------------------------------------------------------------- /** 星期对应{@link Calendar} 中的Week值 */ - private int value; + private final int value; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/format/FastDatePrinter.java b/hutool-core/src/main/java/cn/hutool/core/date/format/FastDatePrinter.java index 9f6dd33d9..00db5b766 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/format/FastDatePrinter.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/format/FastDatePrinter.java @@ -1,5 +1,7 @@ package cn.hutool.core.date.format; +import cn.hutool.core.date.DateException; + import java.io.IOException; import java.io.ObjectInputStream; import java.text.DateFormatSymbols; @@ -12,8 +14,6 @@ import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import cn.hutool.core.date.DateException; - /** * {@link java.text.SimpleDateFormat} 的线程安全版本,用于将 {@link Date} 格式化输出
    * Thanks to Apache Commons Lang 3.5 @@ -48,7 +48,7 @@ class FastDatePrinter extends AbstractDateBasic implements DatePrinter { */ private void init() { final List rulesList = parsePattern(); - rules = rulesList.toArray(new Rule[rulesList.size()]); + rules = rulesList.toArray(new Rule[0]); int len = 0; for (int i = rules.length; --i >= 0;) { diff --git a/hutool-core/src/main/java/cn/hutool/core/img/BackgroundRemoval.java b/hutool-core/src/main/java/cn/hutool/core/img/BackgroundRemoval.java new file mode 100644 index 000000000..0988d32f0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/img/BackgroundRemoval.java @@ -0,0 +1,354 @@ +package cn.hutool.core.img; + +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; + +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

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

    + *

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

    + *

    2020-05-21 16:36

    + * + * @author Dai Yuanchuan + **/ +public class BackgroundRemoval { + + /** + * 目前暂时支持的图片类型数组 + * 其他格式的不保证结果 + */ + public static String[] IMAGES_TYPE = {"jpg", "png"}; + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param inputPath 要处理图片的路径 + * @param outputPath 输出图片的路径 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(String inputPath, String outputPath, int tolerance) { + return backgroundRemoval(new File(inputPath), new File(outputPath), tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param input 需要进行操作的图片 + * @param output 最后输出的文件 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(File input, File output, int tolerance) { + return backgroundRemoval(input, output, null, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param input 需要进行操作的图片 + * @param output 最后输出的文件 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(File input, File output, Color override, int tolerance) { + if (fileTypeValidation(input, IMAGES_TYPE)) { + return false; + } + try { + // 获取图片左上、中上、右上、右中、右下、下中、左下、左中、8个像素点rgb的16进制值 + BufferedImage bufferedImage = ImageIO.read(input); + // 图片输出的格式为 png + return ImageIO.write(backgroundRemoval(bufferedImage, override, tolerance), "png", output); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param bufferedImage 需要进行处理的图片流 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理好的图片流 + */ + public static BufferedImage backgroundRemoval(BufferedImage bufferedImage, Color override, int tolerance) { + // 容差值 最大255 最小0 + tolerance = Math.min(255, Math.max(tolerance, 0)); + // 绘制icon + ImageIcon imageIcon = new ImageIcon(bufferedImage); + BufferedImage image = new BufferedImage(imageIcon.getIconWidth(), imageIcon.getIconHeight(), + BufferedImage.TYPE_4BYTE_ABGR); + // 绘图工具 + Graphics graphics = image.getGraphics(); + graphics.drawImage(imageIcon.getImage(), 0, 0, imageIcon.getImageObserver()); + // 需要删除的RGB元素 + String[] removeRgb = getRemoveRgb(bufferedImage); + // 获取图片的大概主色调 + String mainColor = getMainColor(bufferedImage); + int alpha = 0; + for (int y = image.getMinY(); y < image.getHeight(); y++) { + for (int x = image.getMinX(); x < image.getWidth(); x++) { + // 获取像素的16进制 + int rgb = image.getRGB(x, y); + String hex = ImgUtil.toHex((rgb & 0xff0000) >> 16, (rgb & 0xff00) >> 8, (rgb & 0xff)); + boolean isTrue = ArrayUtil.contains(removeRgb, hex) || + areColorsWithinTolerance(hexToRgb(mainColor), new Color(Integer.parseInt(hex.substring(1), 16)), tolerance); + if (isTrue) { + rgb = override == null ? ((alpha + 1) << 24) | (rgb & 0x00ffffff) : override.getRGB(); + } + image.setRGB(x, y, rgb); + } + } + graphics.drawImage(image, 0, 0, imageIcon.getImageObserver()); + return image; + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param outputStream 需要进行处理的图片字节数组流 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理好的图片流 + */ + public static BufferedImage backgroundRemoval(ByteArrayOutputStream outputStream, Color override, int tolerance) { + try { + return backgroundRemoval(ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray())), override, tolerance); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取要删除的 RGB 元素 + * 分别获取图片左上、中上、右上、右中、右下、下中、左下、左中、8个像素点rgb的16进制值 + * + * @param image 图片流 + * @return String数组 包含 各个位置的rgb数值 + */ + private static String[] getRemoveRgb(BufferedImage image) { + // 获取图片流的宽和高 + int width = image.getWidth() - 1; + int height = image.getHeight() - 1; + // 左上 + int leftUpPixel = image.getRGB(1, 1); + String leftUp = ImgUtil.toHex((leftUpPixel & 0xff0000) >> 16, (leftUpPixel & 0xff00) >> 8, (leftUpPixel & 0xff)); + // 上中 + int upMiddlePixel = image.getRGB(width / 2, 1); + String upMiddle = ImgUtil.toHex((upMiddlePixel & 0xff0000) >> 16, (upMiddlePixel & 0xff00) >> 8, (upMiddlePixel & 0xff)); + // 右上 + int rightUpPixel = image.getRGB(width, 1); + String rightUp = ImgUtil.toHex((rightUpPixel & 0xff0000) >> 16, (rightUpPixel & 0xff00) >> 8, (rightUpPixel & 0xff)); + // 右中 + int rightMiddlePixel = image.getRGB(width, height / 2); + String rightMiddle = ImgUtil.toHex((rightMiddlePixel & 0xff0000) >> 16, (rightMiddlePixel & 0xff00) >> 8, (rightMiddlePixel & 0xff)); + // 右下 + int lowerRightPixel = image.getRGB(width, height); + String lowerRight = ImgUtil.toHex((lowerRightPixel & 0xff0000) >> 16, (lowerRightPixel & 0xff00) >> 8, (lowerRightPixel & 0xff)); + // 下中 + int lowerMiddlePixel = image.getRGB(width / 2, height); + String lowerMiddle = ImgUtil.toHex((lowerMiddlePixel & 0xff0000) >> 16, (lowerMiddlePixel & 0xff00) >> 8, (lowerMiddlePixel & 0xff)); + // 左下 + int leftLowerPixel = image.getRGB(1, height); + String leftLower = ImgUtil.toHex((leftLowerPixel & 0xff0000) >> 16, (leftLowerPixel & 0xff00) >> 8, (leftLowerPixel & 0xff)); + // 左中 + int leftMiddlePixel = image.getRGB(1, height / 2); + String leftMiddle = ImgUtil.toHex((leftMiddlePixel & 0xff0000) >> 16, (leftMiddlePixel & 0xff00) >> 8, (leftMiddlePixel & 0xff)); + // 需要删除的RGB元素 + return new String[]{leftUp, upMiddle, rightUp, rightMiddle, lowerRight, lowerMiddle, leftLower, leftMiddle}; + } + + /** + * 十六进制颜色码转RGB颜色值 + * + * @param hex 十六进制颜色码 + * @return 返回 RGB颜色值 + */ + public static Color hexToRgb(String hex) { + return new Color(Integer.parseInt(hex.substring(1), 16)); + } + + + /** + * 判断颜色是否在容差范围内 + * 对比两个颜色的相似度,判断这个相似度是否小于 tolerance 容差值 + * + * @param color1 颜色1 + * @param color2 颜色2 + * @param tolerance 容差值 + * @return 返回true:两个颜色在容差值之内 false: 不在 + */ + public static boolean areColorsWithinTolerance(Color color1, Color color2, int tolerance) { + return areColorsWithinTolerance(color1, color2, new Color(tolerance, tolerance, tolerance)); + } + + /** + * 判断颜色是否在容差范围内 + * 对比两个颜色的相似度,判断这个相似度是否小于 tolerance 容差值 + * + * @param color1 颜色1 + * @param color2 颜色2 + * @param tolerance 容差色值 + * @return 返回true:两个颜色在容差值之内 false: 不在 + */ + public static boolean areColorsWithinTolerance(Color color1, Color color2, Color tolerance) { + return (color1.getRed() - color2.getRed() < tolerance.getRed() && color1 + .getRed() - color2.getRed() > -tolerance.getRed()) + && (color1.getBlue() - color2.getBlue() < tolerance + .getBlue() && color1.getBlue() - color2.getBlue() > -tolerance + .getBlue()) + && (color1.getGreen() - color2.getGreen() < tolerance + .getGreen() && color1.getGreen() + - color2.getGreen() > -tolerance.getGreen()); + } + + /** + * 获取图片大概的主题色 + * 循环所有的像素点,取出出现次数最多的一个像素点的RGB值 + * + * @param input 图片文件路径 + * @return 返回一个图片的大概的色值 一个16进制的颜色码 + */ + public static String getMainColor(String input) { + return getMainColor(new File(input)); + } + + /** + * 获取图片大概的主题色 + * 循环所有的像素点,取出出现次数最多的一个像素点的RGB值 + * + * @param input 图片文件 + * @return 返回一个图片的大概的色值 一个16进制的颜色码 + */ + public static String getMainColor(File input) { + try { + return getMainColor(ImageIO.read(input)); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } + + /** + * 获取图片大概的主题色 + * 循环所有的像素点,取出出现次数最多的一个像素点的RGB值 + * + * @param bufferedImage 图片流 + * @return 返回一个图片的大概的色值 一个16进制的颜色码 + */ + public static String getMainColor(BufferedImage bufferedImage) { + if (bufferedImage == null) { + throw new IllegalArgumentException("图片流是空的"); + } + + // 存储图片的所有RGB元素 + List list = new ArrayList<>(); + for (int y = bufferedImage.getMinY(); y < bufferedImage.getHeight(); y++) { + for (int x = bufferedImage.getMinX(); x < bufferedImage.getWidth(); x++) { + int pixel = bufferedImage.getRGB(x, y); + list.add(((pixel & 0xff0000) >> 16) + "-" + ((pixel & 0xff00) >> 8) + "-" + (pixel & 0xff)); + } + } + + Map map = new HashMap<>(list.size()); + for (String string : list) { + Integer integer = map.get(string); + if (integer == null) { + integer = 1; + } else { + integer++; + } + map.put(string, integer); + } + String max = ""; + long num = 0; + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Integer temp = entry.getValue(); + if (StrUtil.isBlank(max) || temp > num) { + max = key; + num = temp; + } + } + String[] strings = max.split("-"); + // rgb 的数量只有3个 + int rgbLength = 3; + if (strings.length == rgbLength) { + return ImgUtil.toHex(Integer.parseInt(strings[0]), Integer.parseInt(strings[1]), + Integer.parseInt(strings[2])); + } + return ""; + } + + // -------------------------------------------------------------------------- private + + /** + * 文件类型验证 + * 根据给定文件类型数据,验证给定文件类型. + * + * @param input 需要进行验证的文件 + * @param imagesType 文件包含的类型数组 + * @return 返回布尔值 false:给定文件的文件类型在文件数组中 true:给定文件的文件类型 不在给定数组中。 + */ + private static boolean fileTypeValidation(File input, String[] imagesType) { + if (!input.exists()) { + throw new IllegalArgumentException("给定文件为空"); + } + // 获取图片类型 + String type = FileTypeUtil.getType(input); + // 类型对比 + if (!ArrayUtil.contains(imagesType, type)) { + throw new IllegalArgumentException(StrUtil.format("文件类型{}不支持", type)); + } + return false; + } +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java new file mode 100644 index 000000000..c6b2502d7 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java @@ -0,0 +1,110 @@ +package cn.hutool.core.img; + +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.IORuntimeException; + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.FontMetrics; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * AWT中字体相关工具类 + * + * @author looly + * @since 5.3.6 + */ +public class FontUtil { + + /** + * 创建默认字体 + * + * @return 默认字体 + */ + public static Font createFont() { + return new Font(null); + } + + /** + * 创建SansSerif字体 + * + * @param size 字体大小 + * @return 字体 + */ + public static Font createSansSerifFont(int size) { + return createFont(Font.SANS_SERIF, size); + } + + /** + * 创建指定名称的字体 + * + * @param name 字体名称 + * @param size 字体大小 + * @return 字体 + */ + public static Font createFont(String name, int size) { + return new Font(name, Font.PLAIN, size); + } + + /** + * 根据文件创建字体
    + * 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT} + * + * @param fontFile 字体文件 + * @return {@link Font} + */ + public static Font createFont(File fontFile) { + try { + return Font.createFont(Font.TRUETYPE_FONT, fontFile); + } catch (FontFormatException e) { + // True Type字体无效时使用Type1字体 + try { + return Font.createFont(Font.TYPE1_FONT, fontFile); + } catch (Exception e1) { + throw new UtilException(e); + } + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 根据文件创建字体
    + * 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT} + * + * @param fontStream 字体流 + * @return {@link Font} + */ + public static Font createFont(InputStream fontStream) { + try { + return Font.createFont(Font.TRUETYPE_FONT, fontStream); + } catch (FontFormatException e) { + // True Type字体无效时使用Type1字体 + try { + return Font.createFont(Font.TYPE1_FONT, fontStream); + } catch (Exception e1) { + throw new UtilException(e1); + } + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 获得字体对应字符串的长宽信息 + * + * @param metrics {@link FontMetrics} + * @param str 字符串 + * @return 长宽信息 + */ + public static Dimension getDimension(FontMetrics metrics, String str) { + final int width = metrics.stringWidth(str); + final int height = metrics.getAscent() - metrics.getLeading() - metrics.getDescent(); + + return new Dimension(width, height); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java index f1ce6a002..107c87e45 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java @@ -1,16 +1,23 @@ package cn.hutool.core.img; +import cn.hutool.core.util.ObjectUtil; + +import java.awt.AlphaComposite; import java.awt.Color; +import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.image.BufferedImage; /** * {@link Graphics}相关工具类 - * + * * @author looly * @since 4.5.2 */ @@ -18,7 +25,7 @@ public class GraphicsUtil { /** * 创建{@link Graphics2D} - * + * * @param image {@link BufferedImage} * @param color {@link Color}背景颜色以及当前画笔颜色,{@code null}表示不设置背景色 * @return {@link Graphics2D} @@ -26,8 +33,8 @@ public class GraphicsUtil { */ public static Graphics2D createGraphics(BufferedImage image, Color color) { final Graphics2D g = image.createGraphics(); - - if(null != color) { + + if (null != color) { // 填充背景 g.setColor(color); g.fillRect(0, 0, image.getWidth(), image.getHeight()); @@ -39,8 +46,8 @@ public class GraphicsUtil { /** * 获取文字居中高度的Y坐标(距离上边距距离)
    * 此方法依赖FontMetrics,如果获取失败,默认为背景高度的1/3 - * - * @param g {@link Graphics2D}画笔 + * + * @param g {@link Graphics2D}画笔 * @param backgroundHeight 背景高度 * @return 最小高度,-1表示无法获取 * @since 4.5.17 @@ -64,11 +71,11 @@ public class GraphicsUtil { /** * 绘制字符串,使用随机颜色,默认抗锯齿 - * - * @param g {@link Graphics}画笔 - * @param str 字符串 - * @param font 字体 - * @param width 字符串总宽度 + * + * @param g {@link Graphics}画笔 + * @param str 字符串 + * @param font 字体 + * @param width 字符串总宽度 * @param height 字符串背景高度 * @return 画笔对象 * @since 4.5.10 @@ -79,12 +86,12 @@ public class GraphicsUtil { /** * 绘制字符串,默认抗锯齿 - * - * @param g {@link Graphics}画笔 - * @param str 字符串 - * @param font 字体 - * @param color 字体颜色,{@code null} 表示使用随机颜色(每个字符单独随机) - * @param width 字符串背景的宽度 + * + * @param g {@link Graphics}画笔 + * @param str 字符串 + * @param font 字体 + * @param color 字体颜色,{@code null} 表示使用随机颜色(每个字符单独随机) + * @param width 字符串背景的宽度 * @param height 字符串背景的高度 * @return 画笔对象 * @since 4.5.10 @@ -98,7 +105,7 @@ public class GraphicsUtil { g.setFont(font); // 文字高度(必须在设置字体后调用) - int midY = GraphicsUtil.getCenterY(g, height); + int midY = getCenterY(g, height); if (null != color) { g.setColor(color); } @@ -115,4 +122,97 @@ public class GraphicsUtil { return g; } + /** + * 绘制字符串,默认抗锯齿。
    + * 此方法定义一个矩形区域和坐标,文字基于这个区域中间偏移x,y绘制。 + * + * @param g {@link Graphics}画笔 + * @param str 字符串 + * @param font 字体,字体大小决定了在背景中绘制的大小 + * @param color 字体颜色,{@code null} 表示使用黑色 + * @param rectangle 字符串绘制坐标和大小,此对象定义了绘制字符串的区域大小和偏移位置 + * @return 画笔对象 + * @since 4.5.10 + */ + public static Graphics drawString(Graphics g, String str, Font font, Color color, Rectangle rectangle) { + // 背景长宽 + final int backgroundWidth = rectangle.width; + final int backgroundHeight = rectangle.height; + + //获取字符串本身的长宽 + Dimension dimension; + try { + dimension = FontUtil.getDimension(g.getFontMetrics(font), str); + } catch (Exception e) { + // 此处报告bug某些情况下会抛出IndexOutOfBoundsException,在此做容错处理 + dimension = new Dimension(backgroundWidth / 3, backgroundHeight / 3); + } + + rectangle.setSize(dimension.width, dimension.height); + final Point point = ImgUtil.getPointBaseCentre(rectangle, backgroundWidth, backgroundHeight); + + return drawString(g, str, font, color, point); + } + + /** + * 绘制字符串,默认抗锯齿 + * + * @param g {@link Graphics}画笔 + * @param str 字符串 + * @param font 字体,字体大小决定了在背景中绘制的大小 + * @param color 字体颜色,{@code null} 表示使用黑色 + * @param point 绘制字符串的位置坐标 + * @return 画笔对象 + * @since 5.3.6 + */ + public static Graphics drawString(Graphics g, String str, Font font, Color color, Point point) { + // 抗锯齿 + if (g instanceof Graphics2D) { + ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + + g.setFont(font); + g.setColor(ObjectUtil.defaultIfNull(color, Color.BLACK)); + g.drawString(str, point.x, point.y); + + return g; + } + + /** + * 绘制图片 + * + * @param g 画笔 + * @param img 要绘制的图片 + * @param point 绘制的位置,基于左上角 + * @return 画笔对象 + */ + public static Graphics drawImg(Graphics g, Image img, Point point) { + return drawImg(g, img, + new Rectangle(point.x, point.y, img.getWidth(null), img.getHeight(null))); + } + + /** + * 绘制图片 + * + * @param g 画笔 + * @param img 要绘制的图片 + * @param rectangle 矩形对象,表示矩形区域的x,y,width,height,,基于左上角 + * @return 画笔对象 + */ + public static Graphics drawImg(Graphics g, Image img, Rectangle rectangle) { + g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图 + return g; + } + + /** + * 设置画笔透明度 + * + * @param g 画笔 + * @param alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字 + * @return 画笔 + */ + public static Graphics2D setAlpha(Graphics2D g, float alpha){ + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); + return g; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/img/Img.java b/hutool-core/src/main/java/cn/hutool/core/img/Img.java index 3233751c8..9f6c6112d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/Img.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/Img.java @@ -15,9 +15,9 @@ import javax.imageio.stream.ImageOutputStream; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Image; +import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Toolkit; @@ -47,7 +47,7 @@ import java.nio.file.Path; public class Img implements Serializable { private static final long serialVersionUID = 1L; - private BufferedImage srcImage; + private final BufferedImage srcImage; private Image targetImage; /** * 目标图片文件格式,用于写出 @@ -145,13 +145,13 @@ public class Img implements Serializable { /** * 构造 * - * @param srcImage 来源图片 + * @param srcImage 来源图片 * @param targetImageType 目标图片类型 * @since 5.0.7 */ public Img(BufferedImage srcImage, String targetImageType) { this.srcImage = srcImage; - if(null == targetImageType){ + if (null == targetImageType) { targetImageType = ImgUtil.IMAGE_TYPE_JPG; } this.targetImageType = targetImageType; @@ -312,7 +312,7 @@ public class Img implements Serializable { Graphics2D g = image.createGraphics(); // 设置背景 - if(null != fixedColor){ + if (null != fixedColor) { g.setBackground(fixedColor); g.clearRect(0, 0, width, height); } @@ -450,20 +450,22 @@ public class Img implements Serializable { if (null == font) { // 默认字体 - font = new Font("Courier", Font.PLAIN, (int) (targetImage.getHeight() * 0.75)); + font = FontUtil.createSansSerifFont((int) (targetImage.getHeight() * 0.75)); } - - // 抗锯齿 - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setColor(color); - g.setFont(font); // 透明度 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); - // 在指定坐标绘制水印文字 - final FontMetrics metrics = g.getFontMetrics(font); - final int textLength = metrics.stringWidth(pressText); - final int textHeight = metrics.getAscent() - metrics.getLeading() - metrics.getDescent(); - g.drawString(pressText, Math.abs(targetImage.getWidth() - textLength) / 2 + x, Math.abs(targetImage.getHeight() + textHeight) / 2 + y); + + // 绘制 + if (positionBaseCentre) { + // 基于中心绘制 + GraphicsUtil.drawString(g, pressText, font, color, + new Rectangle(x, y, targetImage.getWidth(), targetImage.getHeight())); + } else { + // 基于左上角绘制 + GraphicsUtil.drawString(g, pressText, font, color, + new Point(x, y)); + } + g.dispose(); this.targetImage = targetImage; @@ -482,7 +484,6 @@ public class Img implements Serializable { public Img pressImage(Image pressImg, int x, int y, float alpha) { final int pressImgWidth = pressImg.getWidth(null); final int pressImgHeight = pressImg.getHeight(null); - return pressImage(pressImg, new Rectangle(x, y, pressImgWidth, pressImgHeight), alpha); } @@ -498,7 +499,6 @@ public class Img implements Serializable { public Img pressImage(Image pressImg, Rectangle rectangle, float alpha) { final Image targetImg = getValidSrcImg(); - fixRectangle(rectangle, targetImg.getWidth(null), targetImg.getHeight(null)); this.targetImage = draw(ImgUtil.toBufferedImage(targetImg), pressImg, rectangle, alpha); return this; } @@ -554,7 +554,7 @@ public class Img implements Serializable { * @return 处理过的图片 */ public Image getImg() { - return this.targetImage; + return null == this.targetImage ? this.srcImage : this.targetImage; } /** @@ -620,12 +620,21 @@ public class Img implements Serializable { * @param backgroundImg 背景图片 * @param img 要绘制的图片 * @param rectangle 矩形对象,表示矩形区域的x,y,width,height,x,y从背景图片中心计算 + * @param alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字 * @return 绘制后的背景 */ - private static BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) { + private BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) { final Graphics2D g = backgroundImg.createGraphics(); - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); - g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图 + GraphicsUtil.setAlpha(g, alpha); + + Point point; + if (positionBaseCentre) { + point = ImgUtil.getPointBaseCentre(rectangle, backgroundImg.getWidth(), backgroundImg.getHeight()); + } else { + point = new Point(rectangle.x, rectangle.y); + } + GraphicsUtil.drawImg(g, img, point); + g.dispose(); return backgroundImg; } diff --git a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java index 5c44801d9..8caa47738 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java @@ -2,7 +2,6 @@ package cn.hutool.core.img; import cn.hutool.core.codec.Base64; import cn.hutool.core.convert.Convert; -import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; @@ -10,8 +9,10 @@ import cn.hutool.core.io.resource.Resource; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; import javax.imageio.IIOImage; import javax.imageio.ImageIO; @@ -23,10 +24,10 @@ import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import java.awt.Color; import java.awt.Font; -import java.awt.FontFormatException; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; +import java.awt.Point; import java.awt.Rectangle; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; @@ -1260,6 +1261,20 @@ public class ImgUtil { return IoUtil.toStream(toBytes(image, imageType)); } + /** + * 将图片对象转换为Base64的Data URI形式,格式为:data:image/[imageType];base64,[data] + * + * @param image 图片对象 + * @param imageType 图片类型 + * @return Base64的字符串表现形式 + * @since 5.3.6 + */ + public static String toBase64DateUri(Image image, String imageType) { + return URLUtil.getDataUri( + "image/" + imageType, "base64", + toBase64(image, imageType)); + } + /** * 将图片对象转换为Base64形式 * @@ -1291,28 +1306,65 @@ public class ImgUtil { * * @param str 文字 * @param font 字体{@link Font} - * @param backgroundColor 背景颜色 - * @param fontColor 字体颜色 + * @param backgroundColor 背景颜色,默认透明 + * @param fontColor 字体颜色,默认黑色 * @param out 图片输出地 * @throws IORuntimeException IO异常 */ public static void createImage(String str, Font font, Color backgroundColor, Color fontColor, ImageOutputStream out) throws IORuntimeException { + writePng(createImage(str, font, backgroundColor, fontColor, BufferedImage.TYPE_INT_ARGB), out); + } + + /** + * 根据文字创建图片 + * + * @param str 文字 + * @param font 字体{@link Font} + * @param backgroundColor 背景颜色,默认透明 + * @param fontColor 字体颜色,默认黑色 + * @param imageType 图片类型,见:{@link BufferedImage} + * @return 图片 + * @throws IORuntimeException IO异常 + */ + public static BufferedImage createImage(String str, Font font, Color backgroundColor, Color fontColor, int imageType) throws IORuntimeException { // 获取font的样式应用在str上的整个矩形 - Rectangle2D r = font.getStringBounds(str, new FontRenderContext(AffineTransform.getScaleInstance(1, 1), false, false)); - int unitHeight = (int) Math.floor(r.getHeight());// 获取单个字符的高度 + final Rectangle2D r = getRectangle(str, font); + // 获取单个字符的高度 + int unitHeight = (int) Math.floor(r.getHeight()); // 获取整个str用了font样式的宽度这里用四舍五入后+1保证宽度绝对能容纳这个字符串作为图片的宽度 int width = (int) Math.round(r.getWidth()) + 1; - int height = unitHeight + 3;// 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度 + // 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度 + int height = unitHeight + 3; + // 创建图片 - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); - Graphics g = image.getGraphics(); - g.setColor(backgroundColor); - g.fillRect(0, 0, width, height);// 先用背景色填充整张图片,也就是背景 - g.setColor(fontColor); + final BufferedImage image = new BufferedImage(width, height, imageType); + final Graphics g = image.getGraphics(); + if (null != backgroundColor) { + // 先用背景色填充整张图片,也就是背景 + g.setColor(backgroundColor); + g.fillRect(0, 0, width, height); + } + g.setColor(ObjectUtil.defaultIfNull(fontColor, Color.BLACK)); g.setFont(font);// 设置画笔字体 g.drawString(str, 0, font.getSize());// 画出字符串 g.dispose(); - writePng(image, out); + + return image; + } + + /** + * 获取font的样式应用在str上的整个矩形 + * + * @param str 字符串,必须非空 + * @param font 字体,必须非空 + * @return {@link Rectangle2D} + * @since 5.3.3 + */ + public static Rectangle2D getRectangle(String str, Font font) { + return font.getStringBounds(str, + new FontRenderContext(AffineTransform.getScaleInstance(1, 1), + false, + false)); } /** @@ -1324,18 +1376,7 @@ public class ImgUtil { * @since 3.0.9 */ public static Font createFont(File fontFile) { - try { - return Font.createFont(Font.TRUETYPE_FONT, fontFile); - } catch (FontFormatException e) { - // True Type字体无效时使用Type1字体 - try { - return Font.createFont(Font.TYPE1_FONT, fontFile); - } catch (Exception e1) { - throw new UtilException(e); - } - } catch (IOException e) { - throw new IORuntimeException(e); - } + return FontUtil.createFont(fontFile); } /** @@ -1347,18 +1388,7 @@ public class ImgUtil { * @since 3.0.9 */ public static Font createFont(InputStream fontStream) { - try { - return Font.createFont(Font.TRUETYPE_FONT, fontStream); - } catch (FontFormatException e) { - // True Type字体无效时使用Type1字体 - try { - return Font.createFont(Font.TYPE1_FONT, fontStream); - } catch (Exception e1) { - throw new UtilException(e1); - } - } catch (IOException e) { - throw new IORuntimeException(e); - } + return FontUtil.createFont(fontStream); } /** @@ -1735,13 +1765,23 @@ public class ImgUtil { * @since 4.1.14 */ public static String toHex(Color color) { - String R = Integer.toHexString(color.getRed()); - R = R.length() < 2 ? ('0' + R) : R; - String G = Integer.toHexString(color.getGreen()); - G = G.length() < 2 ? ('0' + G) : G; - String B = Integer.toHexString(color.getBlue()); - B = B.length() < 2 ? ('0' + B) : B; - return '#' + R + G + B; + return toHex(color.getRed(), color.getGreen(), color.getBlue()); + } + + /** + * RGB颜色值转换成十六进制颜色码 + * + * @param r 红(R) + * @param g 绿(G) + * @param b 蓝(B) + * @return 返回字符串形式的 十六进制颜色码 如 + */ + public static String toHex(int r, int g, int b) { + // rgb 小于 255 + if(r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255){ + throw new IllegalArgumentException("RGB must be 0~255!"); + } + return String.format("#%02X%02X%02X", r, g, b); } /** @@ -1864,4 +1904,113 @@ public class ImgUtil { } return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)); } + + /** + * 获得修正后的矩形坐标位置,变为以背景中心为基准坐标(即x,y == 0,0时,处于背景正中) + * + * @param rectangle 矩形 + * @param backgroundWidth 参考宽(背景宽) + * @param backgroundHeight 参考高(背景高) + * @return 修正后的{@link Point} + * @since 5.3.6 + */ + public static Point getPointBaseCentre(Rectangle rectangle, int backgroundWidth, int backgroundHeight) { + return new Point( + rectangle.x + (Math.abs(backgroundWidth - rectangle.width) / 2), // + rectangle.y + (Math.abs(backgroundHeight - rectangle.height) / 2)// + ); + } + + // ------------------------------------------------------------------------------------------------------ 背景图换算 + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param inputPath 要处理图片的路径 + * @param outputPath 输出图片的路径 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(String inputPath, String outputPath, int tolerance) { + return BackgroundRemoval.backgroundRemoval(inputPath, outputPath, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param input 需要进行操作的图片 + * @param output 最后输出的文件 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(File input, File output, int tolerance) { + return BackgroundRemoval.backgroundRemoval(input, output, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param input 需要进行操作的图片 + * @param output 最后输出的文件 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(File input, File output, Color override, int tolerance) { + return BackgroundRemoval.backgroundRemoval(input, output, override, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param bufferedImage 需要进行处理的图片流 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理好的图片流 + */ + public static BufferedImage backgroundRemoval(BufferedImage bufferedImage, Color override, int tolerance) { + return BackgroundRemoval.backgroundRemoval(bufferedImage, override, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param outputStream 需要进行处理的图片字节数组流 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理好的图片流 + */ + public static BufferedImage backgroundRemoval(ByteArrayOutputStream outputStream, Color override, int tolerance) { + return BackgroundRemoval.backgroundRemoval(outputStream, override, tolerance); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/img/ScaleType.java b/hutool-core/src/main/java/cn/hutool/core/img/ScaleType.java index ed8eb8724..d96048879 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/ScaleType.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/ScaleType.java @@ -35,7 +35,7 @@ public enum ScaleType { this.value = value; } - private int value; + private final int value; public int getValue() { return this.value; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java index f1b498508..a27cace9d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java @@ -1,11 +1,11 @@ package cn.hutool.core.io; +import cn.hutool.core.util.CharsetUtil; + import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; -import cn.hutool.core.util.CharsetUtil; - /** * 基于快速缓冲FastByteBuffer的OutputStream,随着数据的增长自动扩充缓冲区 *

    @@ -34,7 +34,6 @@ public class FastByteArrayOutputStream extends OutputStream { buffer = new FastByteBuffer(size); } - @SuppressWarnings("NullableProblems") @Override public void write(byte[] b, int off, int len) { buffer.append(b, off, len); diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java b/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java new file mode 100644 index 000000000..b5b225c63 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java @@ -0,0 +1,91 @@ +package cn.hutool.core.io; + +import cn.hutool.core.text.StrBuilder; + +import java.io.Writer; + +/** + * 借助{@link StrBuilder} 提供快读的字符串写出,相比jdk的StringWriter非线程安全,速度更快。 + * + * @author looly + * @since 5.3.3 + */ +public final class FastStringWriter extends Writer { + + private final StrBuilder builder; + + /** + * 构造 + */ + public FastStringWriter() { + this(StrBuilder.DEFAULT_CAPACITY); + } + + /** + * 构造 + * + * @param initialSize 初始容量 + */ + public FastStringWriter(int initialSize) { + super(); + if (initialSize < 0) { + initialSize = StrBuilder.DEFAULT_CAPACITY; + } + this.builder = new StrBuilder(initialSize); + } + + + @Override + public void write(final int c) { + this.builder.append((char) c); + } + + + @Override + public void write(final String str) { + this.builder.append(str); + } + + + @Override + public void write(final String str, final int off, final int len) { + this.builder.append(str, off, off + len); + } + + + @Override + public void write(final char[] cbuf) { + this.builder.append(cbuf, 0, cbuf.length); + } + + + @Override + public void write(final char[] cbuf, final int off, final int len) { + if ((off < 0) || (off > cbuf.length) || (len < 0) || + ((off + len) > cbuf.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + this.builder.append(cbuf, off, len); + } + + + @Override + public void flush() { + // Nothing to be flushed + } + + + @Override + public void close() { + // Nothing to be closed + } + + + @Override + public String toString() { + return this.builder.toString(); + } + +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 852ee8aa2..2b30df3a2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -59,7 +59,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.jar.JarFile; import java.util.regex.Pattern; import java.util.zip.CRC32; @@ -83,7 +82,7 @@ public class FileUtil { /** * Windows下文件名中的无效字符 */ - private static Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]"); + private static final Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]"); /** * Class文件扩展名 @@ -273,7 +272,7 @@ public class FileUtil { * @param start 起始路径,必须为目录 * @param maxDepth 最大遍历深度,-1表示不限制深度 * @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作 - * @see Files#walkFileTree(Path, Set, int, FileVisitor) + * @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor) * @since 4.6.3 */ public static void walkFiles(Path start, int maxDepth, FileVisitor visitor) { @@ -568,7 +567,7 @@ public class FileUtil { * @return 最后修改时间 */ public static Date lastModifiedTime(File file) { - if (!exist(file)) { + if (false == exist(file)) { return null; } @@ -590,13 +589,12 @@ public class FileUtil { * 当给定对象为文件时,直接调用 {@link File#length()}
    * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回 * - * @param file 目录或文件 + * @param file 目录或文件,null或者文件不存在返回0 * @return 总大小,bytes长度 */ public static long size(File file) { - Assert.notNull(file, "file argument is null !"); - if (false == file.exists()) { - throw new IllegalArgumentException(StrUtil.format("File [{}] not exist !", file.getAbsolutePath())); + if (null == file || false == file.exists()) { + return 0; } if (file.isDirectory()) { @@ -1177,7 +1175,10 @@ public class FileUtil { */ public static File rename(File file, String newName, boolean isRetainExt, boolean isOverride) { if (isRetainExt) { - newName = newName.concat(".").concat(FileUtil.extName(file)); + final String extName = FileUtil.extName(file); + if(StrUtil.isNotBlank(extName)){ + newName = newName.concat(".").concat(extName); + } } final Path path = file.toPath(); final CopyOption[] options = isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{}; @@ -3254,10 +3255,10 @@ public class FileUtil { * * @param file 文件 * @param out 流 - * @return 目标文件 + * @return 写出的流byte数 * @throws IORuntimeException IO异常 */ - public static File writeToStream(File file, OutputStream out) throws IORuntimeException { + public static long writeToStream(File file, OutputStream out) throws IORuntimeException { return FileReader.create(file).writeToStream(out); } @@ -3266,10 +3267,11 @@ public class FileUtil { * * @param fullFilePath 文件绝对路径 * @param out 输出流 + * @return 写出的流byte数 * @throws IORuntimeException IO异常 */ - public static void writeToStream(String fullFilePath, OutputStream out) throws IORuntimeException { - writeToStream(touch(fullFilePath), out); + public static long writeToStream(String fullFilePath, OutputStream out) throws IORuntimeException { + return writeToStream(touch(fullFilePath), out); } /** @@ -3576,6 +3578,6 @@ public class FileUtil { * @param charset 编码 */ public static void tail(File file, Charset charset) { - FileUtil.tail(file, charset, Tailer.CONSOLE_HANDLER); + tail(file, charset, Tailer.CONSOLE_HANDLER); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 72a073ca6..9d368316c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -1,5 +1,12 @@ package cn.hutool.core.io; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -13,7 +20,6 @@ import java.io.Flushable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -36,36 +42,37 @@ import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; import java.util.zip.Checksum; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.HexUtil; -import cn.hutool.core.util.StrUtil; - /** * IO工具类
    * IO工具类只是辅助流的读写,并不负责关闭流。原因是流可能被多次读写,读写关闭后容易造成问题。 - * - * @author xiaoleilu * + * @author xiaoleilu */ public class IoUtil { - /** 默认缓存大小 8192*/ + /** + * 默认缓存大小 8192 + */ public static final int DEFAULT_BUFFER_SIZE = 2 << 12; - /** 默认中等缓存大小 16384*/ + /** + * 默认中等缓存大小 16384 + */ public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13; - /** 默认大缓存大小 32768*/ + /** + * 默认大缓存大小 32768 + */ public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14; - /** 数据流末尾 */ + /** + * 数据流末尾 + */ public static final int EOF = -1; // -------------------------------------------------------------------------------------- Copy start + /** * 将Reader中的内容复制到Writer中 使用默认缓存大小,拷贝后不关闭Reader - * + * * @param reader Reader * @param writer Writer * @return 拷贝的字节数 @@ -77,9 +84,9 @@ public class IoUtil { /** * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader - * - * @param reader Reader - * @param writer Writer + * + * @param reader Reader + * @param writer Writer * @param bufferSize 缓存大小 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -90,10 +97,10 @@ public class IoUtil { /** * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader - * - * @param reader Reader - * @param writer Writer - * @param bufferSize 缓存大小 + * + * @param reader Reader + * @param writer Writer + * @param bufferSize 缓存大小 * @param streamProgress 进度处理器 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -125,8 +132,8 @@ public class IoUtil { /** * 拷贝流,使用默认Buffer大小,拷贝后不关闭流 - * - * @param in 输入流 + * + * @param in 输入流 * @param out 输出流 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -137,9 +144,9 @@ public class IoUtil { /** * 拷贝流,拷贝后不关闭流 - * - * @param in 输入流 - * @param out 输出流 + * + * @param in 输入流 + * @param out 输出流 * @param bufferSize 缓存大小 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -150,10 +157,10 @@ public class IoUtil { /** * 拷贝流,拷贝后不关闭流 - * - * @param in 输入流 - * @param out 输出流 - * @param bufferSize 缓存大小 + * + * @param in 输入流 + * @param out 输出流 + * @param bufferSize 缓存大小 * @param streamProgress 进度条 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -171,7 +178,7 @@ public class IoUtil { } long size = 0; try { - for (int readSize; (readSize = in.read(buffer)) != EOF;) { + for (int readSize; (readSize = in.read(buffer)) != EOF; ) { out.write(buffer, 0, readSize); size += readSize; out.flush(); @@ -191,10 +198,10 @@ public class IoUtil { /** * 拷贝流 thanks to: https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java
    * 本方法不会关闭流 - * - * @param in 输入流 - * @param out 输出流 - * @param bufferSize 缓存大小 + * + * @param in 输入流 + * @param out 输出流 + * @param bufferSize 缓存大小 * @param streamProgress 进度条 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -205,8 +212,8 @@ public class IoUtil { /** * 拷贝文件流,使用NIO - * - * @param in 输入 + * + * @param in 输入 * @param out 输出 * @return 拷贝的字节数 * @throws IORuntimeException IO异常 @@ -231,8 +238,8 @@ public class IoUtil { /** * 拷贝流,使用NIO,不会关闭流 - * - * @param in {@link ReadableByteChannel} + * + * @param in {@link ReadableByteChannel} * @param out {@link WritableByteChannel} * @return 拷贝的字节数 * @throws IORuntimeException IO异常 @@ -244,9 +251,9 @@ public class IoUtil { /** * 拷贝流,使用NIO,不会关闭流 - * - * @param in {@link ReadableByteChannel} - * @param out {@link WritableByteChannel} + * + * @param in {@link ReadableByteChannel} + * @param out {@link WritableByteChannel} * @param bufferSize 缓冲大小,如果小于等于0,使用默认 * @return 拷贝的字节数 * @throws IORuntimeException IO异常 @@ -258,10 +265,10 @@ public class IoUtil { /** * 拷贝流,使用NIO,不会关闭流 - * - * @param in {@link ReadableByteChannel} - * @param out {@link WritableByteChannel} - * @param bufferSize 缓冲大小,如果小于等于0,使用默认 + * + * @param in {@link ReadableByteChannel} + * @param out {@link WritableByteChannel} + * @param bufferSize 缓冲大小,如果小于等于0,使用默认 * @param streamProgress {@link StreamProgress}进度处理器 * @return 拷贝的字节数 * @throws IORuntimeException IO异常 @@ -296,6 +303,7 @@ public class IoUtil { // -------------------------------------------------------------------------------------- Copy end // -------------------------------------------------------------------------------------- getReader and getWriter start + /** * 获得一个文件读取器,默认使用UTF-8编码 * @@ -309,8 +317,8 @@ public class IoUtil { /** * 获得一个文件读取器 - * - * @param in 输入流 + * + * @param in 输入流 * @param charsetName 字符集名称 * @return BufferedReader对象 */ @@ -320,8 +328,8 @@ public class IoUtil { /** * 获得一个Reader - * - * @param in 输入流 + * + * @param in 输入流 * @param charset 字符集 * @return BufferedReader对象 */ @@ -343,7 +351,7 @@ public class IoUtil { /** * 获得{@link BufferedReader}
    * 如果是{@link BufferedReader}强转返回,否则新建。如果提供的Reader为null返回null - * + * * @param reader 普通Reader,如果为null返回null * @return {@link BufferedReader} or null * @since 3.0.9 @@ -359,8 +367,8 @@ public class IoUtil { /** * 获得{@link PushbackReader}
    * 如果是{@link PushbackReader}强转返回,否则新建 - * - * @param reader 普通Reader + * + * @param reader 普通Reader * @param pushBackSize 推后的byte数 * @return {@link PushbackReader} * @since 3.1.0 @@ -382,8 +390,8 @@ public class IoUtil { /** * 获得一个Writer - * - * @param out 输入流 + * + * @param out 输入流 * @param charsetName 字符集 * @return OutputStreamWriter对象 */ @@ -393,8 +401,8 @@ public class IoUtil { /** * 获得一个Writer - * - * @param out 输入流 + * + * @param out 输入流 * @param charset 字符集 * @return OutputStreamWriter对象 */ @@ -412,10 +420,11 @@ public class IoUtil { // -------------------------------------------------------------------------------------- getReader and getWriter end // -------------------------------------------------------------------------------------- read start + /** * 从流中读取内容 - * - * @param in 输入流 + * + * @param in 输入流 * @param charsetName 字符集 * @return 内容 * @throws IORuntimeException IO异常 @@ -427,8 +436,8 @@ public class IoUtil { /** * 从流中读取内容,读取完毕后并不关闭流 - * - * @param in 输入流,读取完毕后并不关闭流 + * + * @param in 输入流,读取完毕后并不关闭流 * @param charset 字符集 * @return 内容 * @throws IORuntimeException IO异常 @@ -440,7 +449,7 @@ public class IoUtil { /** * 从流中读取内容,读取完毕后并不关闭流 - * + * * @param channel 可读通道,读取完毕后并不关闭通道 * @param charset 字符集 * @return 内容 @@ -453,8 +462,8 @@ public class IoUtil { } /** - * 从流中读取内容,读到输出流中 - * + * 从流中读取内容,读到输出流中,读取完毕后并不关闭流 + * * @param in 输入流 * @return 输出流 * @throws IORuntimeException IO异常 @@ -467,7 +476,7 @@ public class IoUtil { /** * 从流中读取内容,读到输出流中 - * + * * @param channel 可读通道,读取完毕后并不关闭通道 * @return 输出流 * @throws IORuntimeException IO异常 @@ -480,7 +489,7 @@ public class IoUtil { /** * 从Reader中读取String,读取完毕后并不关闭Reader - * + * * @param reader Reader * @return String * @throws IORuntimeException IO异常 @@ -500,7 +509,7 @@ public class IoUtil { /** * 从FileChannel中读取UTF-8编码内容 - * + * * @param fileChannel 文件管道 * @return 内容 * @throws IORuntimeException IO异常 @@ -511,7 +520,7 @@ public class IoUtil { /** * 从FileChannel中读取内容,读取完毕后并不关闭Channel - * + * * @param fileChannel 文件管道 * @param charsetName 字符集 * @return 内容 @@ -523,9 +532,9 @@ public class IoUtil { /** * 从FileChannel中读取内容 - * + * * @param fileChannel 文件管道 - * @param charset 字符集 + * @param charset 字符集 * @return 内容 * @throws IORuntimeException IO异常 */ @@ -541,7 +550,7 @@ public class IoUtil { /** * 从流中读取bytes,读取完毕后关闭流 - * + * * @param in {@link InputStream} * @return bytes * @throws IORuntimeException IO异常 @@ -553,7 +562,7 @@ public class IoUtil { /** * 从流中读取bytes * - * @param in {@link InputStream} + * @param in {@link InputStream} * @param isCloseStream 是否关闭输入流 * @return bytes * @throws IORuntimeException IO异常 @@ -562,7 +571,7 @@ public class IoUtil { public static byte[] readBytes(InputStream in, boolean isCloseStream) throws IORuntimeException { final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); copy(in, out); - if(isCloseStream){ + if (isCloseStream) { close(in); } return out.toByteArray(); @@ -570,8 +579,8 @@ public class IoUtil { /** * 读取指定长度的byte数组,不关闭流 - * - * @param in {@link InputStream},为null返回null + * + * @param in {@link InputStream},为null返回null * @param length 长度,小于等于0返回空byte数组 * @return bytes * @throws IORuntimeException IO异常 @@ -602,9 +611,9 @@ public class IoUtil { /** * 读取16进制字符串 - * - * @param in {@link InputStream} - * @param length 长度 + * + * @param in {@link InputStream} + * @param length 长度 * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 * @return 16进制字符串 * @throws IORuntimeException IO异常 @@ -615,7 +624,7 @@ public class IoUtil { /** * 从流中读取前28个byte并转换为16进制,字母部分使用大写 - * + * * @param in {@link InputStream} * @return 16进制字符串 * @throws IORuntimeException IO异常 @@ -626,7 +635,7 @@ public class IoUtil { /** * 从流中读取前28个byte并转换为16进制,字母部分使用小写 - * + * * @param in {@link InputStream} * @return 16进制字符串 * @throws IORuntimeException IO异常 @@ -636,24 +645,69 @@ public class IoUtil { } /** - * 从流中读取内容,读到输出流中 - * + * 从流中读取对象,即对象的反序列化 + * + *

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

    + * * @param 读取对象的类型 - * @param in 输入流 + * @param in 输入流 * @return 输出流 * @throws IORuntimeException IO异常 - * @throws UtilException ClassNotFoundException包装 + * @throws UtilException ClassNotFoundException包装 */ public static T readObj(InputStream in) throws IORuntimeException, UtilException { + return readObj(in, null); + } + + /** + * 从流中读取对象,即对象的反序列化,读取后不关闭流 + * + *

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

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

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

    + * + * @param 读取对象的类型 + * @param in 输入流,使用{@link ValidateObjectInputStream}中的黑白名单方式过滤类,用于避免反序列化漏洞 + * @param clazz 读取对象类型 + * @return 输出流 + * @throws IORuntimeException IO异常 + * @throws UtilException ClassNotFoundException包装 + */ + public static T readObj(ValidateObjectInputStream in, Class clazz) throws IORuntimeException, UtilException { if (in == null) { throw new IllegalArgumentException("The InputStream must not be null"); } - ObjectInputStream ois; try { - ois = new ObjectInputStream(in); - @SuppressWarnings("unchecked") // may fail with CCE if serialised form is incorrect - final T obj = (T) ois.readObject(); - return obj; + //noinspection unchecked + return (T) in.readObject(); } catch (IOException e) { throw new IORuntimeException(e); } catch (ClassNotFoundException e) { @@ -663,9 +717,9 @@ public class IoUtil { /** * 从流中读取内容,使用UTF-8编码 - * - * @param 集合类型 - * @param in 输入流 + * + * @param 集合类型 + * @param in 输入流 * @param collection 返回集合 * @return 内容 * @throws IORuntimeException IO异常 @@ -676,11 +730,11 @@ public class IoUtil { /** * 从流中读取内容 - * - * @param 集合类型 - * @param in 输入流 + * + * @param 集合类型 + * @param in 输入流 * @param charsetName 字符集 - * @param collection 返回集合 + * @param collection 返回集合 * @return 内容 * @throws IORuntimeException IO异常 */ @@ -690,10 +744,10 @@ public class IoUtil { /** * 从流中读取内容 - * - * @param 集合类型 - * @param in 输入流 - * @param charset 字符集 + * + * @param 集合类型 + * @param in 输入流 + * @param charset 字符集 * @param collection 返回集合 * @return 内容 * @throws IORuntimeException IO异常 @@ -704,9 +758,9 @@ public class IoUtil { /** * 从Reader中读取内容 - * - * @param 集合类型 - * @param reader {@link Reader} + * + * @param 集合类型 + * @param reader {@link Reader} * @param collection 返回集合 * @return 内容 * @throws IORuntimeException IO异常 @@ -718,8 +772,8 @@ public class IoUtil { /** * 按行读取UTF-8编码数据,针对每行的数据做处理 - * - * @param in {@link InputStream} + * + * @param in {@link InputStream} * @param lineHandler 行处理接口,实现handle方法用于编辑一行的数据后入到指定地方 * @throws IORuntimeException IO异常 * @since 3.1.1 @@ -730,9 +784,9 @@ public class IoUtil { /** * 按行读取数据,针对每行的数据做处理 - * - * @param in {@link InputStream} - * @param charset {@link Charset}编码 + * + * @param in {@link InputStream} + * @param charset {@link Charset}编码 * @param lineHandler 行处理接口,实现handle方法用于编辑一行的数据后入到指定地方 * @throws IORuntimeException IO异常 * @since 3.0.9 @@ -744,8 +798,8 @@ public class IoUtil { /** * 按行读取数据,针对每行的数据做处理
    * {@link Reader}自带编码定义,因此读取数据的编码跟随其编码。 - * - * @param reader {@link Reader} + * + * @param reader {@link Reader} * @param lineHandler 行处理接口,实现handle方法用于编辑一行的数据后入到指定地方 * @throws IORuntimeException IO异常 */ @@ -769,8 +823,8 @@ public class IoUtil { /** * String 转为流 - * - * @param content 内容 + * + * @param content 内容 * @param charsetName 编码 * @return 字节流 */ @@ -780,7 +834,7 @@ public class IoUtil { /** * String 转为流 - * + * * @param content 内容 * @param charset 编码 * @return 字节流 @@ -791,10 +845,10 @@ public class IoUtil { } return toStream(StrUtil.bytes(content, charset)); } - + /** * String 转为UTF-8编码的字节流流 - * + * * @param content 内容 * @return 字节流 * @since 4.5.1 @@ -833,7 +887,7 @@ public class IoUtil { /** * 转换为{@link BufferedInputStream} - * + * * @param in {@link InputStream} * @return {@link BufferedInputStream} * @since 4.0.10 @@ -844,7 +898,7 @@ public class IoUtil { /** * 转换为{@link BufferedOutputStream} - * + * * @param out {@link OutputStream} * @return {@link BufferedOutputStream} * @since 4.0.10 @@ -856,7 +910,7 @@ public class IoUtil { /** * 将{@link InputStream}转换为支持mark标记的流
    * 若原流支持mark标记,则返回原流,否则使用{@link BufferedInputStream} 包装之 - * + * * @param in 流 * @return {@link InputStream} * @since 4.0.9 @@ -874,8 +928,8 @@ public class IoUtil { /** * 转换为{@link PushbackInputStream}
    * 如果传入的输入流已经是{@link PushbackInputStream},强转返回,否则新建一个 - * - * @param in {@link InputStream} + * + * @param in {@link InputStream} * @param pushBackSize 推后的byte数 * @return {@link PushbackInputStream} * @since 3.1.0 @@ -886,10 +940,10 @@ public class IoUtil { /** * 将byte[]写到流中 - * - * @param out 输出流 + * + * @param out 输出流 * @param isCloseOut 写入完毕是否关闭输出流 - * @param content 写入的内容 + * @param content 写入的内容 * @throws IORuntimeException IO异常 */ public static void write(OutputStream out, boolean isCloseOut, byte[] content) throws IORuntimeException { @@ -906,10 +960,10 @@ public class IoUtil { /** * 将多部分内容写到流中,自动转换为UTF-8字符串 - * - * @param out 输出流 + * + * @param out 输出流 * @param isCloseOut 写入完毕是否关闭输出流 - * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 + * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 * @throws IORuntimeException IO异常 * @since 3.1.1 */ @@ -919,11 +973,11 @@ public class IoUtil { /** * 将多部分内容写到流中,自动转换为字符串 - * - * @param out 输出流 + * + * @param out 输出流 * @param charsetName 写出的内容的字符集 - * @param isCloseOut 写入完毕是否关闭输出流 - * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 + * @param isCloseOut 写入完毕是否关闭输出流 + * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 * @throws IORuntimeException IO异常 */ public static void write(OutputStream out, String charsetName, boolean isCloseOut, Object... contents) throws IORuntimeException { @@ -932,11 +986,11 @@ public class IoUtil { /** * 将多部分内容写到流中,自动转换为字符串 - * - * @param out 输出流 - * @param charset 写出的内容的字符集 + * + * @param out 输出流 + * @param charset 写出的内容的字符集 * @param isCloseOut 写入完毕是否关闭输出流 - * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 + * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 * @throws IORuntimeException IO异常 * @since 3.0.9 */ @@ -947,9 +1001,9 @@ public class IoUtil { for (Object content : contents) { if (content != null) { osw.write(Convert.toStr(content, StrUtil.EMPTY)); - osw.flush(); } } + osw.flush(); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -961,10 +1015,23 @@ public class IoUtil { /** * 将多部分内容写到流中 - * - * @param out 输出流 + * + * @param out 输出流 * @param isCloseOut 写入完毕是否关闭输出流 - * @param contents 写入的内容 + * @param obj 写入的对象内容 + * @throws IORuntimeException IO异常 + * @since 5.3.3 + */ + public static void writeObj(OutputStream out, boolean isCloseOut, Serializable obj) throws IORuntimeException { + writeObjects(out, isCloseOut, obj); + } + + /** + * 将多部分内容写到流中 + * + * @param out 输出流 + * @param isCloseOut 写入完毕是否关闭输出流 + * @param contents 写入的内容 * @throws IORuntimeException IO异常 */ public static void writeObjects(OutputStream out, boolean isCloseOut, Serializable... contents) throws IORuntimeException { @@ -988,7 +1055,7 @@ public class IoUtil { /** * 从缓存中刷出数据 - * + * * @param flushable {@link Flushable} * @since 4.2.2 */ @@ -1005,7 +1072,7 @@ public class IoUtil { /** * 关闭
    * 关闭失败不会抛出异常 - * + * * @param closeable 被关闭的对象 */ public static void close(Closeable closeable) { @@ -1021,7 +1088,7 @@ public class IoUtil { /** * 关闭
    * 关闭失败不会抛出异常 - * + * * @param closeable 被关闭的对象 */ public static void close(AutoCloseable closeable) { @@ -1037,7 +1104,7 @@ public class IoUtil { /** * 尝试关闭指定对象
    * 判断对象如果实现了{@link AutoCloseable},则调用之 - * + * * @param obj 可关闭对象 * @since 4.3.2 */ @@ -1142,7 +1209,7 @@ public class IoUtil { /** * 计算流CRC32校验码,计算后关闭流 - * + * * @param in 文件,不能为目录 * @return CRC32值 * @throws IORuntimeException IO异常 @@ -1154,8 +1221,8 @@ public class IoUtil { /** * 计算流的校验码,计算后关闭流 - * - * @param in 流 + * + * @param in 流 * @param checksum {@link Checksum} * @return Checksum * @throws IORuntimeException IO异常 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java new file mode 100644 index 000000000..91c1a130e --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java @@ -0,0 +1,101 @@ +package cn.hutool.core.io; + +import cn.hutool.core.collection.CollUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.util.HashSet; +import java.util.Set; + +/** + * 带有类验证的对象流,用于避免反序列化漏洞
    + * 详细见:https://xz.aliyun.com/t/41/ + * + * @author looly + * @since 5.2.6 + */ +public class ValidateObjectInputStream extends ObjectInputStream { + + private Set whiteClassSet; + private Set blackClassSet; + + /** + * 构造 + * + * @param inputStream 流 + * @param acceptClasses 白名单的类 + * @throws IOException IO异常 + */ + public ValidateObjectInputStream(InputStream inputStream, Class... acceptClasses) throws IOException { + super(inputStream); + accept(acceptClasses); + } + + /** + * 禁止反序列化的类,用于反序列化验证 + * + * @param refuseClasses 禁止反序列化的类 + * @since 5.3.5 + */ + public void refuse(Class... refuseClasses) { + if(null == this.blackClassSet){ + this.blackClassSet = new HashSet<>(); + } + for (Class acceptClass : refuseClasses) { + this.blackClassSet.add(acceptClass.getName()); + } + } + + /** + * 接受反序列化的类,用于反序列化验证 + * + * @param acceptClasses 接受反序列化的类 + */ + public void accept(Class... acceptClasses) { + if(null == this.whiteClassSet){ + this.whiteClassSet = new HashSet<>(); + } + for (Class acceptClass : acceptClasses) { + this.whiteClassSet.add(acceptClass.getName()); + } + } + + /** + * 只允许反序列化SerialObject class + */ + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + validateClassName(desc.getName()); + return super.resolveClass(desc); + } + + /** + * 验证反序列化的类是否合法 + * @param className 类名 + * @throws InvalidClassException 非法类 + */ + private void validateClassName(String className) throws InvalidClassException { + // 黑名单 + if(CollUtil.isNotEmpty(this.blackClassSet)){ + if(this.blackClassSet.contains(className)){ + throw new InvalidClassException("Unauthorized deserialization attempt by black list", className); + } + } + + if(CollUtil.isEmpty(this.whiteClassSet)){ + return; + } + if(className.startsWith("java.")){ + // java中的类默认在白名单中 + return; + } + if(this.whiteClassSet.contains(className)){ + return; + } + + throw new InvalidClassException("Unauthorized deserialization attempt", className); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java b/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java index 3fc31c1ad..2017418c5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java @@ -66,7 +66,7 @@ public class CRC16 implements Checksum, Serializable { @Override public void update(byte[] b, int off, int len) { for (int i = off; i < off + len; i++) - update((int) b[i]); + update(b[i]); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileAppender.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileAppender.java index 51a61a00f..1c0b087af 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileAppender.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileAppender.java @@ -1,5 +1,7 @@ package cn.hutool.core.io.file; +import cn.hutool.core.util.CharsetUtil; + import java.io.File; import java.io.PrintWriter; import java.io.Serializable; @@ -7,8 +9,6 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; -import cn.hutool.core.util.CharsetUtil; - /** * 文件追加器
    * 持有一个文件,在内存中积累一定量的数据后统一追加到文件
    @@ -21,12 +21,12 @@ import cn.hutool.core.util.CharsetUtil; public class FileAppender implements Serializable{ private static final long serialVersionUID = 1L; - private FileWriter writer; + private final FileWriter writer; /** 内存中持有的字符串数 */ - private int capacity; + private final int capacity; /** 追加内容是否为新行 */ - private boolean isNewLineMode; - private List list = new ArrayList<>(100); + private final boolean isNewLineMode; + private final List list = new ArrayList<>(100); /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java index 6fb9ed780..25abe5ce1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java @@ -1,5 +1,12 @@ package cn.hutool.core.io.file; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.copier.SrcToDestCopier; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; + import java.io.File; import java.io.IOException; import java.nio.file.CopyOption; @@ -7,12 +14,6 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.copier.SrcToDestCopier; -import cn.hutool.core.util.StrUtil; - /** * 文件拷贝器
    * 支持以下几种情况: @@ -209,25 +210,28 @@ public class FileCopier extends SrcToDestCopier{ //被过滤的目录跳过 return; } - + if (false == dest.exists()) { //目标为不存在路径,创建为目录 + //noinspection ResultOfMethodCallIgnored dest.mkdirs(); } else if (false == dest.isDirectory()) { throw new IORuntimeException(StrUtil.format("Src [{}] is a directory but dest [{}] is a file!", src.getPath(), dest.getPath())); } final String[] files = src.list(); - File srcFile; - File destFile; - for (String file : files) { - srcFile = new File(src, file); - destFile = this.isOnlyCopyFile ? dest : new File(dest, file); - // 递归复制 - if (srcFile.isDirectory()) { - internalCopyDirContent(srcFile, destFile); - } else { - internalCopyFile(srcFile, destFile); + if(ArrayUtil.isNotEmpty(files)){ + File srcFile; + File destFile; + for (String file : files) { + srcFile = new File(src, file); + destFile = this.isOnlyCopyFile ? dest : new File(dest, file); + // 递归复制 + if (srcFile.isDirectory()) { + internalCopyDirContent(srcFile, destFile); + } else { + internalCopyFile(srcFile, destFile); + } } } } @@ -263,6 +267,7 @@ public class FileCopier extends SrcToDestCopier{ } }else { //路径不存在则创建父目录 + //noinspection ResultOfMethodCallIgnored dest.getParentFile().mkdirs(); } @@ -275,7 +280,7 @@ public class FileCopier extends SrcToDestCopier{ } try { - Files.copy(src.toPath(), dest.toPath(), optionList.toArray(new CopyOption[optionList.size()])); + Files.copy(src.toPath(), dest.toPath(), optionList.toArray(new CopyOption[0])); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java index b0a684241..93808ce89 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java @@ -1,5 +1,12 @@ package cn.hutool.core.io.file; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; @@ -11,13 +18,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; - /** * 文件读取器 * @@ -249,20 +249,15 @@ public class FileReader extends FileWrapper { * 将文件写入流中 * * @param out 流 - * @return File + * @return 写出的流byte数 * @throws IORuntimeException IO异常 */ - public File writeToStream(OutputStream out) throws IORuntimeException { - FileInputStream in = null; - try { - in = new FileInputStream(file); - IoUtil.copy(in, out); + public long writeToStream(OutputStream out) throws IORuntimeException { + try (FileInputStream in = new FileInputStream(this.file)){ + return IoUtil.copy(in, out); }catch (IOException e) { throw new IORuntimeException(e); - } finally { - IoUtil.close(in); } - return this.file; } // -------------------------------------------------------------------------- Interface start diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/LineReadWatcher.java b/hutool-core/src/main/java/cn/hutool/core/io/file/LineReadWatcher.java index 18cc739b7..f0f83d05a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/LineReadWatcher.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/LineReadWatcher.java @@ -1,16 +1,16 @@ package cn.hutool.core.io.file; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.io.watch.SimpleWatcher; + import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.WatchEvent; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.io.watch.SimpleWatcher; - /** * 行处理的Watcher实现 * @@ -19,9 +19,9 @@ import cn.hutool.core.io.watch.SimpleWatcher; */ public class LineReadWatcher extends SimpleWatcher implements Runnable { - private RandomAccessFile randomAccessFile; - private Charset charset; - private LineHandler lineHandler; + private final RandomAccessFile randomAccessFile; + private final Charset charset; + private final LineHandler lineHandler; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/LineSeparator.java b/hutool-core/src/main/java/cn/hutool/core/io/file/LineSeparator.java index 23441aea3..514a5db0f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/LineSeparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/LineSeparator.java @@ -23,7 +23,7 @@ public enum LineSeparator { /** Windows系统换行符:"\r\n" */ WINDOWS("\r\n"); - private String value; + private final String value; LineSeparator(String lineSeparator) { this.value = lineSeparator; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java b/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java index f9403eac6..172a65439 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java @@ -1,5 +1,14 @@ package cn.hutool.core.io.file; +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.CharsetUtil; + import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; @@ -12,15 +21,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import cn.hutool.core.date.DateUnit; -import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.CharsetUtil; - /** * 文件内容跟随器,实现类似Linux下"tail -f"命令功能 * @@ -33,16 +33,16 @@ public class Tailer implements Serializable { public static final LineHandler CONSOLE_HANDLER = new ConsoleLineHandler(); /** 编码 */ - private Charset charset; + private final Charset charset; /** 行处理器 */ - private LineHandler lineHandler; + private final LineHandler lineHandler; /** 初始读取的行数 */ - private int initReadLine; + private final int initReadLine; /** 定时任务检查间隔时长 */ - private long period; + private final long period; - private RandomAccessFile randomAccessFile; - private ScheduledExecutorService executorService; + private final RandomAccessFile randomAccessFile; + private final ScheduledExecutorService executorService; /** * 构造,默认UTF-8编码 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java index c350d9943..f3607ba4b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java @@ -1,5 +1,8 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -8,10 +11,6 @@ import java.io.StringReader; import java.net.URL; import java.nio.charset.Charset; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; - /** * 基于byte[]的资源获取器
    * 注意:此对象中getUrl方法始终返回null @@ -70,11 +69,6 @@ public class BytesResource implements Resource, Serializable { return StrUtil.str(this.bytes, charset); } - @Override - public String readUtf8Str() throws IORuntimeException { - return readStr(CharsetUtil.CHARSET_UTF_8); - } - @Override public byte[] readBytes() throws IORuntimeException { return this.bytes; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java index 0955db2ce..e861329f2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java @@ -1,15 +1,14 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; + import java.io.BufferedReader; import java.io.InputStream; import java.io.Serializable; import java.net.URL; import java.nio.charset.Charset; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; - /** * 基于{@link InputStream}的资源获取器
    * 注意:此对象中getUrl方法始终返回null @@ -20,8 +19,8 @@ import cn.hutool.core.util.CharsetUtil; public class InputStreamResource implements Resource, Serializable { private static final long serialVersionUID = 1L; - private InputStream in; - private String name; + private final InputStream in; + private final String name; /** * 构造 @@ -74,11 +73,6 @@ public class InputStreamResource implements Resource, Serializable { } } - @Override - public String readUtf8Str() throws IORuntimeException { - return readStr(CharsetUtil.CHARSET_UTF_8); - } - @Override public byte[] readBytes() throws IORuntimeException { InputStream in = null; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index efa737bd7..3e6f98c75 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -1,5 +1,8 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IORuntimeException; + import java.io.BufferedReader; import java.io.InputStream; import java.io.Serializable; @@ -10,9 +13,6 @@ import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.IORuntimeException; - /** * 多资源组合资源
    * 此资源为一个利用游标自循环资源,只有调用{@link #next()} 方法才会获取下一个资源,使用完毕后调用{@link #reset()}方法重置游标 @@ -23,7 +23,7 @@ import cn.hutool.core.io.IORuntimeException; public class MultiResource implements Resource, Iterable, Iterator, Serializable { private static final long serialVersionUID = 1L; - private List resources; + private final List resources; private int cursor; /** @@ -94,7 +94,7 @@ public class MultiResource implements Resource, Iterable, Iterator= resources.size()) { throw new ConcurrentModificationException(); } @@ -110,7 +110,7 @@ public class MultiResource implements Resource, Iterable, Iterator * 资源可以是文件、URL、ClassPath中的文件亦或者jar包中的文件 @@ -35,6 +38,20 @@ public interface Resource { * @return {@link InputStream} */ InputStream getStream(); + + /** + * 将资源内容写出到流,不关闭输出流,但是关闭资源流 + * @param out 输出流 + * @throws IORuntimeException IO异常 + * @since 5.3.5 + */ + default void writeTo(OutputStream out) throws IORuntimeException{ + try (InputStream in = getStream()) { + IoUtil.copy(in, out); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } /** * 获得Reader @@ -60,7 +77,9 @@ public interface Resource { * @return 读取资源内容 * @throws IORuntimeException 包装IOException */ - String readUtf8Str() throws IORuntimeException; + default String readUtf8Str() throws IORuntimeException{ + return readStr(CharsetUtil.CHARSET_UTF_8); + } /** * 读取资源内容,读取完毕后会关闭流
    diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index e2e793622..b8b9844d1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -18,7 +18,7 @@ import java.util.Enumeration; import java.util.List; /** - * ClassPath资源工具类 + * Resource资源工具类 * * @author Looly * @@ -33,7 +33,7 @@ public class ResourceUtil { * @since 3.1.1 */ public static String readUtf8Str(String resource) { - return readStr(resource, CharsetUtil.CHARSET_UTF_8); + return getResourceObj(resource).readUtf8Str(); } /** @@ -62,41 +62,52 @@ public class ResourceUtil { /** * 从ClassPath资源中获取{@link InputStream} * - * @param resurce ClassPath资源 + * @param resource ClassPath资源 * @return {@link InputStream} * @throws NoResourceException 资源不存在异常 * @since 3.1.2 */ - public static InputStream getStream(String resurce) throws NoResourceException { - return getResourceObj(resurce).getStream(); + public static InputStream getStream(String resource) throws NoResourceException { + return getResourceObj(resource).getStream(); } /** * 从ClassPath资源中获取{@link InputStream},当资源不存在时返回null * - * @param resurce ClassPath资源 + * @param resource ClassPath资源 * @return {@link InputStream} * @since 4.0.3 */ - public static InputStream getStreamSafe(String resurce) { + public static InputStream getStreamSafe(String resource) { try { - return getResourceObj(resurce).getStream(); + return getResourceObj(resource).getStream(); } catch (NoResourceException e) { // ignore } return null; } + /** + * 从ClassPath资源中获取{@link BufferedReader} + * + * @param resource ClassPath资源 + * @return {@link InputStream} + * @since 5.3.6 + */ + public static BufferedReader getUtf8Reader(String resource) { + return getReader(resource, CharsetUtil.CHARSET_UTF_8); + } + /** * 从ClassPath资源中获取{@link BufferedReader} * - * @param resurce ClassPath资源 + * @param resource ClassPath资源 * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ - public static BufferedReader getReader(String resurce, Charset charset) { - return getResourceObj(resurce).getReader(charset); + public static BufferedReader getReader(String resource, Charset charset) { + return getResourceObj(resource).getReader(charset); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java index 53a6d108a..0f45a4972 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java @@ -1,5 +1,9 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; + import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -8,10 +12,6 @@ import java.io.StringReader; import java.net.URL; import java.nio.charset.Charset; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; - /** * 字符串资源,字符串做为资源 * @@ -21,9 +21,9 @@ import cn.hutool.core.util.CharsetUtil; public class StringResource implements Resource, Serializable { private static final long serialVersionUID = 1L; - private String data; - private String name; - private Charset charset; + private final String data; + private final String name; + private final Charset charset; /** * 构造,使用UTF8编码 @@ -82,11 +82,6 @@ public class StringResource implements Resource, Serializable { return this.data; } - @Override - public String readUtf8Str() throws IORuntimeException { - return this.data; - } - @Override public byte[] readBytes() throws IORuntimeException { return this.data.getBytes(this.charset); diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index 0707cd294..995a5923f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -1,5 +1,11 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.URLUtil; + import java.io.BufferedReader; import java.io.File; import java.io.InputStream; @@ -7,13 +13,6 @@ import java.io.Serializable; import java.net.URL; import java.nio.charset.Charset; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.URLUtil; - /** * URL资源访问类 * @author Looly @@ -68,7 +67,7 @@ public class UrlResource implements Resource, Serializable{ @Override public InputStream getStream() throws NoResourceException{ if(null == this.url){ - throw new NoResourceException("Resource [{}] not exist!", this.url); + throw new NoResourceException("Resource URL is null!"); } return URLUtil.getStream(url); } @@ -96,11 +95,6 @@ public class UrlResource implements Resource, Serializable{ } } - @Override - public String readUtf8Str() throws IORuntimeException{ - return readStr(CharsetUtil.CHARSET_UTF_8); - } - @Override public byte[] readBytes() throws IORuntimeException{ InputStream in = null; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java index e49dd7f0b..a5b639291 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java @@ -35,7 +35,17 @@ public enum WatchKind { */ DELETE(StandardWatchEventKinds.ENTRY_DELETE); - private WatchEvent.Kind value; + /** + * 全部事件 + */ + public static final WatchEvent.Kind[] ALL = {// + OVERFLOW.getValue(), //事件丢失 + MODIFY.getValue(), //修改 + CREATE.getValue(), //创建 + DELETE.getValue() //删除 + }; + + private final WatchEvent.Kind value; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java index 6cca21fcf..daaefe156 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java @@ -15,7 +15,6 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchService; @@ -35,28 +34,23 @@ public class WatchMonitor extends WatchServer { /** * 事件丢失 */ - public static final WatchEvent.Kind OVERFLOW = StandardWatchEventKinds.OVERFLOW; + public static final WatchEvent.Kind OVERFLOW = WatchKind.OVERFLOW.getValue(); /** * 修改事件 */ - public static final WatchEvent.Kind ENTRY_MODIFY = StandardWatchEventKinds.ENTRY_MODIFY; + public static final WatchEvent.Kind ENTRY_MODIFY = WatchKind.MODIFY.getValue(); /** * 创建事件 */ - public static final WatchEvent.Kind ENTRY_CREATE = StandardWatchEventKinds.ENTRY_CREATE; + public static final WatchEvent.Kind ENTRY_CREATE = WatchKind.CREATE.getValue(); /** * 删除事件 */ - public static final WatchEvent.Kind ENTRY_DELETE = StandardWatchEventKinds.ENTRY_DELETE; + public static final WatchEvent.Kind ENTRY_DELETE = WatchKind.DELETE.getValue(); /** * 全部事件 */ - public static final WatchEvent.Kind[] EVENTS_ALL = {// - OVERFLOW, //事件丢失 - ENTRY_MODIFY, //修改 - ENTRY_CREATE, //创建 - ENTRY_DELETE //删除 - }; + public static final WatchEvent.Kind[] EVENTS_ALL = WatchKind.ALL; /** * 监听路径,必须为目录 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java index 57ee1f4af..342ca0cb8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java @@ -51,7 +51,7 @@ public class WatchServer extends Thread implements Closeable, Serializable { /** * WatchKey 和 Path的对应表 */ - private Map watchKeyPathMap = new HashMap<>(); + private final Map watchKeyPathMap = new HashMap<>(); /** * 初始化
    @@ -95,12 +95,14 @@ public class WatchServer extends Thread implements Closeable, Serializable { * @param maxDepth 递归下层目录的最大深度 */ public void registerPath(Path path, int maxDepth) { + final WatchEvent.Kind[] kinds = ArrayUtil.defaultIfEmpty(this.events, WatchKind.ALL); + try { final WatchKey key; if (ArrayUtil.isEmpty(this.modifiers)) { - key = path.register(this.watchService, this.events); + key = path.register(this.watchService, kinds); } else { - key = path.register(this.watchService, this.events, this.modifiers); + key = path.register(this.watchService, kinds, this.modifiers); } watchKeyPathMap.put(key, path); diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ClassScanner.java b/hutool-core/src/main/java/cn/hutool/core/lang/ClassScanner.java index b998f9c3c..4e099050c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/ClassScanner.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ClassScanner.java @@ -2,7 +2,6 @@ package cn.hutool.core.lang; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; -import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.resource.ResourceUtil; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Console.java b/hutool-core/src/main/java/cn/hutool/core/lang/Console.java index 0f1dfd8b5..a50f4edb2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Console.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Console.java @@ -1,13 +1,12 @@ package cn.hutool.core.lang; -import static java.lang.System.out; - -import java.util.Scanner; - import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; +import java.util.Scanner; + import static java.lang.System.err; +import static java.lang.System.out; /** * 命令行(控制台)工具方法类
    @@ -189,7 +188,7 @@ public class Console { * @since 5.2.5 */ public static String where() { - StackTraceElement stackTraceElement = new Throwable().getStackTrace()[1]; + final StackTraceElement stackTraceElement = new Throwable().getStackTrace()[1]; final String className = stackTraceElement.getClassName(); final String methodName = stackTraceElement.getMethodName(); final String fileName = stackTraceElement.getFileName(); diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java index 2b6d28ca8..fa9510594 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java @@ -1,10 +1,10 @@ package cn.hutool.core.lang; +import cn.hutool.core.clone.CloneSupport; + import java.io.Serializable; import java.util.Objects; -import cn.hutool.core.clone.CloneSupport; - /** * 键值对对象,只能在构造时传入键值 * @@ -17,8 +17,8 @@ import cn.hutool.core.clone.CloneSupport; public class Pair extends CloneSupport> implements Serializable{ private static final long serialVersionUID = 1L; - private K key; - private V value; + private final K key; + private final V value; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java index 9778c61d3..c56a2e3df 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java @@ -183,8 +183,8 @@ public class PatternPool { * @author Looly */ private static class RegexWithFlag { - private String regex; - private int flag; + private final String regex; + private final int flag; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Range.java b/hutool-core/src/main/java/cn/hutool/core/lang/Range.java index af6d82bab..6057d5bb9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Range.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Range.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang; +import cn.hutool.core.thread.lock.NoLock; + import java.io.Serializable; import java.util.Iterator; import java.util.NoSuchElementException; @@ -7,8 +9,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import cn.hutool.core.thread.lock.NoLock; - /** * 范围生成器。根据给定的初始值、结束值和步进生成一个步进列表生成器
    * 由于用户自行实现{@link Steper}来定义步进,因此Range本身无法判定边界(是否达到end),需在step实现边界判定逻辑。 @@ -27,19 +27,19 @@ public class Range implements Iterable, Iterator, Serializable { /** 锁保证线程安全 */ private Lock lock = new ReentrantLock(); /** 起始对象 */ - private T start; + private final T start; /** 结束对象 */ - private T end; + private final T end; /** 当前对象 */ private T current; /** 下一个对象 */ private T next; /** 步进 */ - private Steper steper; + private final Steper steper; /** 索引 */ private int index = 0; /** 是否包含第一个元素 */ - private boolean includeStart; + private final boolean includeStart; /** 是否包含最后一个元素 */ private boolean includeEnd; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java index cdcdc0efc..c8149f759 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java @@ -1,64 +1,82 @@ package cn.hutool.core.lang; -import java.io.Serializable; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - import cn.hutool.core.lang.func.Func0; +import java.io.Serializable; +import java.util.Iterator; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; + /** - * 简单缓存,无超时实现,使用{@link WeakHashMap}实现缓存自动清理 - * @author Looly + * 简单缓存,无超时实现,默认使用{@link WeakHashMap}实现缓存自动清理 * * @param 键类型 * @param 值类型 + * @author Looly */ -public class SimpleCache implements Serializable{ +public class SimpleCache implements Iterable>, Serializable { private static final long serialVersionUID = 1L; - - /** 池 */ - private final Map cache = new WeakHashMap<>(); - - private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); - private final ReadLock readLock = cacheLock.readLock(); - private final WriteLock writeLock = cacheLock.writeLock(); + + /** + * 池 + */ + private final Map cache; + // 乐观读写锁 + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + /** + * 构造,默认使用{@link WeakHashMap}实现缓存自动清理 + */ + public SimpleCache() { + this(new WeakHashMap<>()); + } + + /** + * 构造 + *

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

    + * + * @param initMap 初始Map,用于定义Map类型 + */ + public SimpleCache(Map initMap) { + this.cache = initMap; + } /** * 从缓存池中查找值 - * + * * @param key 键 * @return 值 */ public V get(K key) { - // 尝试读取缓存 - readLock.lock(); - V value; + lock.readLock().lock(); try { - value = cache.get(key); + return cache.get(key); } finally { - readLock.unlock(); + lock.readLock().unlock(); } - return value; } - + /** * 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象 - * - * @param key 键 + * + * @param key 键 * @param supplier 如果不存在回调方法,用于生产值对象 * @return 值对象 */ public V get(K key, Func0 supplier) { V v = get(key); - if (null == v && null != supplier) { - writeLock.lock(); - try { - // 双重检查锁 + + if(null == v && null != supplier){ + lock.writeLock().lock(); + try{ v = cache.get(key); - if(null == v) { + // 双重检查,防止在竞争锁的过程中已经有其它线程写入 + if (null == v) { try { v = supplier.call(); } catch (Exception e) { @@ -66,41 +84,45 @@ public class SimpleCache implements Serializable{ } cache.put(key, v); } - } finally { - writeLock.unlock(); + } finally{ + lock.writeLock().unlock(); } } + return v; } - + /** * 放入缓存 - * @param key 键 + * + * @param key 键 * @param value 值 * @return 值 */ - public V put(K key, V value){ - writeLock.lock(); + public V put(K key, V value) { + // 独占写锁 + lock.writeLock().lock(); try { cache.put(key, value); } finally { - writeLock.unlock(); + lock.writeLock().unlock(); } return value; } /** * 移除缓存 - * + * * @param key 键 * @return 移除的值 */ public V remove(K key) { - writeLock.lock(); + // 独占写锁 + lock.writeLock().lock(); try { return cache.remove(key); } finally { - writeLock.unlock(); + lock.writeLock().unlock(); } } @@ -108,11 +130,17 @@ public class SimpleCache implements Serializable{ * 清空缓存池 */ public void clear() { - writeLock.lock(); + // 独占写锁 + lock.writeLock().lock(); try { this.cache.clear(); } finally { - writeLock.unlock(); + lock.writeLock().unlock(); } } -} + + @Override + public Iterator> iterator() { + return this.cache.entrySet().iterator(); + } +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java b/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java index ef9f23b86..96ab8616f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java @@ -1,22 +1,22 @@ package cn.hutool.core.lang; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - +import cn.hutool.core.lang.func.Func0; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; +import java.util.HashMap; + /** * 单例类
    * 提供单例对象的统一管理,当调用get方法时,如果对象池中存在此对象,返回此对象,否则创建新对象返回
    - * - * @author loolly * + * @author loolly */ public final class Singleton { - private static Map pool = new ConcurrentHashMap<>(); + + private static final SimpleCache POOL = new SimpleCache<>(new HashMap<>()); private Singleton() { } @@ -25,38 +25,41 @@ public final class Singleton { * 获得指定类的单例对象
    * 对象存在于池中返回,否则创建,每次调用此方法获得的对象为同一个对象
    * 注意:单例针对的是类和对象,因此get方法第一次调用时创建的对象始终唯一,也就是说就算参数变更,返回的依旧是第一次创建的对象 - * - * @param 单例对象类型 - * @param clazz 类 + * + * @param 单例对象类型 + * @param clazz 类 * @param params 构造方法参数 * @return 单例对象 */ - @SuppressWarnings("unchecked") public static T get(Class clazz, Object... params) { Assert.notNull(clazz, "Class must be not null !"); final String key = buildKey(clazz.getName(), params); - T obj = (T) pool.get(key); - - if (null == obj) { - synchronized (Singleton.class) { - obj = (T) pool.get(key); - if (null == obj) { - obj = (T) ReflectUtil.newInstance(clazz, params); - pool.put(key, obj); - } - } - } - - return obj; + return get(key, () -> ReflectUtil.newInstance(clazz, params)); } /** * 获得指定类的单例对象
    * 对象存在于池中返回,否则创建,每次调用此方法获得的对象为同一个对象
    - * - * @param 单例对象类型 + * 注意:单例针对的是类和对象,因此get方法第一次调用时创建的对象始终唯一,也就是说就算参数变更,返回的依旧是第一次创建的对象 + * + * @param 单例对象类型 + * @param key 自定义键 + * @param supplier 单例对象的创建函数 + * @return 单例对象 + * @since 5.3.3 + */ + @SuppressWarnings("unchecked") + public static T get(String key, Func0 supplier) { + return (T) POOL.get(key, supplier::call); + } + + /** + * 获得指定类的单例对象
    + * 对象存在于池中返回,否则创建,每次调用此方法获得的对象为同一个对象
    + * + * @param 单例对象类型 * @param className 类名 - * @param params 构造参数 + * @param params 构造参数 * @return 单例对象 */ public static T get(String className, Object... params) { @@ -67,39 +70,60 @@ public final class Singleton { /** * 将已有对象放入单例中,其Class做为键 - * + * * @param obj 对象 * @since 4.0.7 */ public static void put(Object obj) { Assert.notNull(obj, "Bean object must be not null !"); - pool.put(obj.getClass().getName(), obj); + put(obj.getClass().getName(), obj); + } + + /** + * 将已有对象放入单例中,其Class做为键 + * + * @param key 键 + * @param obj 对象 + * @since 5.3.3 + */ + public static void put(String key, Object obj) { + POOL.put(key, obj); } /** * 移除指定Singleton对象 - * + * * @param clazz 类 */ public static void remove(Class clazz) { if (null != clazz) { - pool.remove(clazz.getName()); + remove(clazz.getName()); } } + /** + * 移除指定Singleton对象 + * + * @param key 键 + */ + public static void remove(String key) { + POOL.remove(key); + } + /** * 清除所有Singleton对象 */ public static void destroy() { - pool.clear(); + POOL.clear(); } // ------------------------------------------------------------------------------------------- Private method start + /** * 构建key - * + * * @param className 类名 - * @param params 参数列表 + * @param params 参数列表 * @return key */ private static String buildKey(String className, Object... params) { diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java index 6b1baa8d5..ba031fec5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java @@ -51,11 +51,11 @@ public class Snowflake implements Serializable { @SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"}) private final long sequenceMask = -1L ^ (-1L << sequenceBits);// 4095 - private long workerId; - private long dataCenterId; + private final long workerId; + private final long dataCenterId; + private final boolean useSystemClock; private long sequence = 0L; private long lastTimestamp = -1L; - private boolean useSystemClock; /** * 构造 @@ -141,10 +141,16 @@ public class Snowflake implements Serializable { public synchronized long nextId() { long timestamp = genTime(); if (timestamp < lastTimestamp) { - // 如果服务器时间有问题(时钟后退) 报错。 - throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp)); + if(lastTimestamp - timestamp < 2000){ + // 容忍2秒内的回拨,避免NTP校时造成的异常 + timestamp = lastTimestamp; + } else{ + // 如果服务器时间有问题(时钟后退) 报错。 + throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp)); + } } - if (lastTimestamp == timestamp) { + + if (timestamp == lastTimestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); @@ -177,9 +183,15 @@ public class Snowflake implements Serializable { */ private long tilNextMillis(long lastTimestamp) { long timestamp = genTime(); - while (timestamp <= lastTimestamp) { + // 循环直到操作系统时间戳变化 + while (timestamp == lastTimestamp) { timestamp = genTime(); } + if (timestamp < lastTimestamp) { + // 如果发现新的时间戳比上次记录的时间戳数值小,说明操作系统时间发生了倒退,报错 + throw new IllegalStateException( + StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp)); + } return timestamp; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java b/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java index 8c8805d07..40e993d87 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java @@ -1,13 +1,13 @@ package cn.hutool.core.lang; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Random; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; - /** * 提供通用唯一识别码(universally unique identifier)(UUID)实现,UUID表示一个128位的值。
    * 此类拷贝自java.util.UUID,用于生成不带-的UUID字符串 @@ -165,15 +165,15 @@ public final class UUID implements java.io.Serializable, Comparable { components[i] = "0x" + components[i]; } - long mostSigBits = Long.decode(components[0]).longValue(); + long mostSigBits = Long.decode(components[0]); mostSigBits <<= 16; - mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits |= Long.decode(components[1]); mostSigBits <<= 16; - mostSigBits |= Long.decode(components[2]).longValue(); + mostSigBits |= Long.decode(components[2]); - long leastSigBits = Long.decode(components[3]).longValue(); + long leastSigBits = Long.decode(components[3]); leastSigBits <<= 48; - leastSigBits |= Long.decode(components[4]).longValue(); + leastSigBits |= Long.decode(components[4]); return new UUID(mostSigBits, leastSigBits); } @@ -412,11 +412,11 @@ public final class UUID implements java.io.Serializable, Comparable { public int compareTo(UUID val) { // The ordering is intentionally set up so that the UUIDs // can simply be numerically compared as two numbers - return (this.mostSigBits < val.mostSigBits ? -1 : // - (this.mostSigBits > val.mostSigBits ? 1 : // - (this.leastSigBits < val.leastSigBits ? -1 : // - (this.leastSigBits > val.leastSigBits ? 1 : // - 0)))); + int compare = Long.compare(this.mostSigBits, val.mostSigBits); + if(0 == compare){ + compare = Long.compare(this.leastSigBits, val.leastSigBits); + } + return compare; } // ------------------------------------------------------------------------------------------------------------------- Private method start diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/WeightRandom.java b/hutool-core/src/main/java/cn/hutool/core/lang/WeightRandom.java index 630c84a03..fd5f4dd0e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/WeightRandom.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/WeightRandom.java @@ -1,14 +1,14 @@ package cn.hutool.core.lang; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.RandomUtil; + import java.io.Serializable; import java.util.Random; import java.util.SortedMap; import java.util.TreeMap; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.RandomUtil; - /** * 权重随机算法实现
    *

    @@ -31,8 +31,8 @@ import cn.hutool.core.util.RandomUtil; public class WeightRandom implements Serializable { private static final long serialVersionUID = -8244697995702786499L; - private TreeMap weightMap; - private Random random; + private final TreeMap weightMap; + private final Random random; /** * 创建权重随机获取器 @@ -157,7 +157,7 @@ public class WeightRandom implements Serializable { /** 对象 */ private T obj; /** 权重 */ - private double weight; + private final double weight; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func.java index 8a91da6f1..a22f28ba6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func.java @@ -23,4 +23,19 @@ public interface Func { */ @SuppressWarnings("unchecked") R call(P... parameters) throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @param parameters 参数列表 + * @return 函数执行结果 + */ + @SuppressWarnings("unchecked") + default R callWithRuntimeException(P... parameters){ + try { + return call(parameters); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func0.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func0.java index c0cbe71d8..bdc8db679 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func0.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func0.java @@ -20,4 +20,18 @@ public interface Func0 { * @throws Exception 自定义异常 */ R call() throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @return 函数执行结果 + * @since 5.3.6 + */ + default R callWithRuntimeException(){ + try { + return call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func1.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func1.java index 3306e82fd..1967f08d1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func1.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func1.java @@ -23,4 +23,19 @@ public interface Func1 { * @throws Exception 自定义异常 */ R call(P parameter) throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @param parameter 参数 + * @return 函数执行结果 + * @since 5.3.6 + */ + default R callWithRuntimeException(P parameter){ + try { + return call(parameter); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc.java index 4034b79ff..931599f20 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc.java @@ -22,4 +22,18 @@ public interface VoidFunc

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

    { * @throws Exception 自定义异常 */ void call(P parameter) throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @param parameter 参数 + * @since 5.3.6 + */ + default void callWithRuntimeException(P parameter){ + try { + call(parameter); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java index 38ab7d796..f5ab41fe4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java @@ -16,7 +16,7 @@ import java.util.List; public class Tree extends LinkedHashMap implements Node { private static final long serialVersionUID = 1L; - private TreeNodeConfig treeNodeConfig; + private final TreeNodeConfig treeNodeConfig; private Tree parent; public Tree() { diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java index 3ec88e31b..6432a7360 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java @@ -134,9 +134,14 @@ public class TreeUtil { return node; } + final List> children = node.getChildren(); + if(null == children) { + return null; + } + // 查找子节点 Tree childNode; - for (Tree child : node.getChildren()) { + for (Tree child : children) { childNode = child.getNode(id); if (null != childNode) { return childNode; diff --git a/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java b/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java new file mode 100644 index 000000000..9680675a6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java @@ -0,0 +1,71 @@ +package cn.hutool.core.map; + +import java.util.Map; + +/** + * 双向Map
    + * 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素
    + * 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值
    + * 它与TableMap的区别是,BiMap维护两个Map实现高效的正向和反向查找 + * + * @param 键类型 + * @param 值类型 + * @since 5.2.6 + */ +public class BiMap extends MapWrapper { + + private Map inverse; + + /** + * 构造 + * + * @param raw 被包装的Map + */ + public BiMap(Map raw) { + super(raw); + } + + @Override + public V put(K key, V value) { + if (null != this.inverse) { + this.inverse.put(value, key); + } + return super.put(key, value); + } + + @Override + public void putAll(Map m) { + super.putAll(m); + if (null != this.inverse) { + m.forEach((key, value) -> this.inverse.put(value, key)); + } + } + + @Override + public void clear() { + super.clear(); + this.inverse = null; + } + + /** + * 获取反向Map + * + * @return 反向Map + */ + public Map getInverse() { + if (null == this.inverse) { + inverse = MapUtil.inverse(getRaw()); + } + return this.inverse; + } + + /** + * 根据值获得键 + * + * @param value 值 + * @return 键 + */ + public K getKey(V value) { + return getInverse().get(value); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a278e5521..c2922e78e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -13,7 +13,32 @@ import java.util.Map; public class MapBuilder implements Serializable{ private static final long serialVersionUID = 1L; - private Map map; + private final Map map; + + /** + * 创建Builder,默认HashMap实现 + * + * @param Key类型 + * @param Value类型 + * @return MapBuilder + * @since 5.3.0 + */ + public static MapBuilder create() { + return create(false); + } + + /** + * 创建Builder + * + * @param Key类型 + * @param Value类型 + * @param isLinked true创建LinkedHashMap,false创建HashMap + * @return MapBuilder + * @since 5.3.0 + */ + public static MapBuilder create(boolean isLinked) { + return create(MapUtil.newHashMap(isLinked)); + } /** * 创建Builder diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapProxy.java b/hutool-core/src/main/java/cn/hutool/core/map/MapProxy.java index 2d9875605..44604ee15 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapProxy.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapProxy.java @@ -1,5 +1,12 @@ package cn.hutool.core.map; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.getter.OptNullBasicTypeFromObjectGetter; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.StrUtil; + import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @@ -8,13 +15,6 @@ import java.util.Collection; import java.util.Map; import java.util.Set; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.getter.OptNullBasicTypeFromObjectGetter; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.ClassLoaderUtil; -import cn.hutool.core.util.StrUtil; - /** * Map代理,提供各种getXXX方法,并提供默认值支持 * @@ -123,7 +123,7 @@ public class MapProxy implements Map, OptNullBasicTypeFromObject final Class[] parameterTypes = method.getParameterTypes(); if (ArrayUtil.isEmpty(parameterTypes)) { final Class returnType = method.getReturnType(); - if (null != returnType && void.class != returnType) { + if (void.class != returnType) { // 匹配Getter final String methodName = method.getName(); String fieldName = null; diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java index 80a3523a6..07d432faa 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java @@ -22,7 +22,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.NavigableMap; import java.util.Set; +import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; @@ -560,7 +562,7 @@ public class MapUtil { public static String join(Map map, String separator, String keyValueSeparator, boolean isIgnoreNull, String... otherParams) { final StringBuilder strBuilder = StrUtil.builder(); boolean isFirst = true; - if(isNotEmpty(map)){ + if (isNotEmpty(map)) { for (Entry entry : map.entrySet()) { if (false == isIgnoreNull || entry.getKey() != null && entry.getValue() != null) { if (isFirst) { @@ -681,10 +683,13 @@ public class MapUtil { /** * Map的键和值互换 + * 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素
    + * 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值 * * @param 键和值类型 * @param map Map对象,键值类型必须一致 * @return 互换后的Map + * @see #inverse(Map) * @since 3.2.2 */ public static Map reverse(Map map) { @@ -707,6 +712,23 @@ public class MapUtil { }); } + /** + * Map的键和值互换
    + * 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素
    + * 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值 + * + * @param 键和值类型 + * @param 键和值类型 + * @param map Map对象,键值类型必须一致 + * @return 互换后的Map + * @since 5.2.6 + */ + public static Map inverse(Map map) { + final Map result = createMap(map.getClass()); + map.forEach((key, value) -> result.put(value, key)); + return result; + } + /** * 排序已有Map,Key有序的Map,使用默认Key排序方式(字母顺序) * @@ -733,7 +755,7 @@ public class MapUtil { * @since 4.0.1 */ public static TreeMap sort(Map map, Comparator comparator) { - if(null == map){ + if (null == map) { return null; } @@ -777,6 +799,19 @@ public class MapUtil { return new MapWrapper<>(map); } + /** + * 将对应Map转换为不可修改的Map + * + * @param map Map + * @param 键类型 + * @param 值类型 + * @return 不修改Map + * @since 5.2.6 + */ + public static Map unmodifiable(Map map) { + return Collections.unmodifiableMap(map); + } + // ----------------------------------------------------------------------------------------------- builder /** @@ -1034,4 +1069,63 @@ public class MapUtil { return map; } + + /** + * 返回一个空Map + * + * @param 键类型 + * @param 值类型 + * @return 空Map + * @see Collections#emptyMap() + * @since 5.3.1 + */ + public static Map empty() { + return Collections.emptyMap(); + } + + /** + * 根据传入的Map类型不同,返回对应类型的空Map,支持类型包括: + * + *

    +	 *     1. NavigableMap
    +	 *     2. SortedMap
    +	 *     3. Map
    +	 * 
    + * + * @param 键类型 + * @param 值类型 + * @param Map类型 + * @param mapClass Map类型,null返回默认的Map + * @return 空Map + * @since 5.3.1 + */ + @SuppressWarnings("unchecked") + public static > T empty(Class mapClass) { + if (null == mapClass) { + return (T) Collections.emptyMap(); + } + if (NavigableMap.class == mapClass) { + return (T) Collections.emptyNavigableMap(); + } else if (SortedMap.class == mapClass) { + return (T) Collections.emptySortedMap(); + } else if (Map.class == mapClass) { + return (T) Collections.emptyMap(); + } + + // 不支持空集合的集合类型 + throw new IllegalArgumentException(StrUtil.format("[{}] is not support to get empty!", mapClass)); + } + + /** + * 清除一个或多个Map集合内的元素,每个Map调用clear()方法 + * + * @param maps 一个或多个Map + */ + public static void clear(Map... maps) { + for (Map map : maps) { + if (isNotEmpty(map)) { + map.clear(); + } + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java index 79cdd94f4..dfb58c0f6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java @@ -4,42 +4,50 @@ import java.io.Serializable; import java.util.Collection; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; /** * Map包装类,通过包装一个已有Map实现特定功能。例如自定义Key的规则或Value规则 - * - * @author looly * * @param 键类型 * @param 值类型 * @author looly + * @author looly * @since 4.3.3 */ public class MapWrapper implements Map, Iterable>, Serializable, Cloneable { private static final long serialVersionUID = -7524578042008586382L; - - /** 默认增长因子 */ + + /** + * 默认增长因子 + */ protected static final float DEFAULT_LOAD_FACTOR = 0.75f; - /** 默认初始大小 */ + /** + * 默认初始大小 + */ protected static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 - private Map raw; + private final Map raw; /** * 构造 - * + * * @param raw 被包装的Map */ public MapWrapper(Map raw) { this.raw = raw; } - + /** * 获取原始的Map + * * @return Map */ - public Map getRaw(){ + public Map getRaw() { return this.raw; } @@ -113,8 +121,83 @@ public class MapWrapper implements Map, Iterable>, S return this.entrySet().iterator(); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapWrapper that = (MapWrapper) o; + return Objects.equals(raw, that.raw); + } + + @Override + public int hashCode() { + return Objects.hash(raw); + } + @Override public String toString() { return raw.toString(); } + + + @Override + public void forEach(BiConsumer action) { + raw.forEach(action); + } + + @Override + public void replaceAll(BiFunction function) { + raw.replaceAll(function); + } + + @Override + public V putIfAbsent(K key, V value) { + return raw.putIfAbsent(key, value); + } + + @Override + public boolean remove(Object key, Object value) { + return raw.remove(key, value); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return raw.replace(key, oldValue, newValue); + } + + @Override + public V replace(K key, V value) { + return raw.replace(key, value); + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return raw.computeIfAbsent(key, mappingFunction); + } + + //---------------------------------------------------------------------------- Override default methods start + @Override + public V getOrDefault(Object key, V defaultValue) { + return raw.getOrDefault(key, defaultValue); + } + + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + return raw.computeIfPresent(key, remappingFunction); + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + return raw.compute(key, remappingFunction); + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + return raw.merge(key, value, remappingFunction); + } + //---------------------------------------------------------------------------- Override default methods end } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java index 7b4be3645..76dac40fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java @@ -1,28 +1,39 @@ package cn.hutool.core.map; -import java.io.Serializable; -import java.util.*; - import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; /** - * 无重复键的Map - * - * @author looly + * 可重复键和值的Map
    + * 通过键值单独建立List方式,使键值对一一对应,实现正向和反向两种查找
    + * 无论是正向还是反向,都是遍历列表查找过程,相比标准的HashMap要慢,数据越多越慢 * * @param 键类型 * @param 值类型 + * @author looly */ -public class TableMap implements Map, Serializable { +public class TableMap implements Map, Iterable>, Serializable { private static final long serialVersionUID = 1L; - private List keys; - private List values; + private final List keys; + private final List values; /** * 构造 - * + * * @param size 初始容量 */ public TableMap(int size) { @@ -32,8 +43,8 @@ public class TableMap implements Map, Serializable { /** * 构造 - * - * @param keys 键列表 + * + * @param keys 键列表 * @param values 值列表 */ public TableMap(K[] keys, V[] values) { @@ -48,7 +59,7 @@ public class TableMap implements Map, Serializable { @Override public boolean isEmpty() { - return ArrayUtil.isEmpty(keys); + return CollUtil.isEmpty(keys); } @Override @@ -73,6 +84,48 @@ public class TableMap implements Map, Serializable { return null; } + /** + * 根据value获得对应的key,只返回找到的第一个value对应的key值 + * @param value 值 + * @return 键 + * @since 5.3.3 + */ + public K getKey(V value){ + final int index = values.indexOf(value); + if (index > -1 && index < keys.size()) { + return keys.get(index); + } + return null; + } + + /** + * 获取指定key对应的所有值 + * + * @param key 键 + * @return 值列表 + * @since 5.2.5 + */ + public List getValues(K key) { + return CollUtil.getAny( + this.values, + ListUtil.indexOfAll(this.keys, (ele) -> ObjectUtil.equal(ele, key)) + ); + } + + /** + * 获取指定value对应的所有key + * + * @param value 值 + * @return 值列表 + * @since 5.2.5 + */ + public List getKeys(V value) { + return CollUtil.getAny( + this.keys, + ListUtil.indexOfAll(this.values, (ele) -> ObjectUtil.equal(ele, value)) + ); + } + @Override public V put(K key, V value) { keys.add(key); @@ -113,22 +166,54 @@ public class TableMap implements Map, Serializable { @Override public Collection values() { - return new HashSet<>(values); + return Collections.unmodifiableList(this.values); } @Override public Set> entrySet() { - HashSet> hashSet = new HashSet<>(); + final Set> hashSet = new LinkedHashSet<>(); for (int i = 0; i < size(); i++) { hashSet.add(new Entry<>(keys.get(i), values.get(i))); } return hashSet; } + @Override + public Iterator> iterator() { + return new Iterator>() { + private final Iterator keysIter = keys.iterator(); + private final Iterator valuesIter = values.iterator(); + + @Override + public boolean hasNext() { + return keysIter.hasNext() && valuesIter.hasNext(); + } + + @Override + public Map.Entry next() { + return new Entry<>(keysIter.next(), valuesIter.next()); + } + + @Override + public void remove() { + keysIter.remove(); + valuesIter.remove(); + } + }; + } + + @Override + public String toString() { + return "TableMap{" + + "keys=" + keys + + ", values=" + values + + '}'; + } + private static class Entry implements Map.Entry { - private K key; - private V value; + private final K key; + private final V value; public Entry(K key, V value) { this.key = key; @@ -149,12 +234,13 @@ public class TableMap implements Map, Serializable { public V setValue(V value) { throw new UnsupportedOperationException("setValue not supported."); } + @Override public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { - Map.Entry e = (Map.Entry)o; + Map.Entry e = (Map.Entry) o; return Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue()); } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java index 580f39599..9f190c52e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java @@ -1,203 +1,103 @@ package cn.hutool.core.map; -import cn.hutool.core.util.ObjectUtil; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; /** * 一个可以提供默认值的Map * - * @author pantao - * @since 2020/1/3 + * @param 键类型 + * @param 值类型 + * @author pantao, looly */ -public class TolerantMap extends AbstractMap implements Map, Cloneable, Serializable { +public class TolerantMap extends MapWrapper { + private static final long serialVersionUID = -4158133823263496197L; - private static final long serialVersionUID = -4158133823263496197L; + private final V defaultValue; - private transient Map map; + /** + * 构造 + * + * @param defaultValue 默认值 + */ + public TolerantMap(V defaultValue) { + this(new HashMap<>(), defaultValue); + } - private transient V defaultValue; + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param loadFactor 增长因子 + * @param defaultValue 默认值 + */ + public TolerantMap(int initialCapacity, float loadFactor, V defaultValue) { + this(new HashMap<>(initialCapacity, loadFactor), defaultValue); + } - public TolerantMap(V defaultValue) { - this(new HashMap<>(), defaultValue); - } + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param defaultValue 默认值 + */ + public TolerantMap(int initialCapacity, V defaultValue) { + this(new HashMap<>(initialCapacity), defaultValue); + } - public TolerantMap(int initialCapacity, float loadFactor, V defaultValue) { - this(new HashMap<>(initialCapacity, loadFactor), defaultValue); - } + /** + * 构造 + * + * @param map Map实现 + * @param defaultValue 默认值 + */ + public TolerantMap(Map map, V defaultValue) { + super(map); + this.defaultValue = defaultValue; + } - public TolerantMap(int initialCapacity, V defaultValue) { - this(new HashMap<>(initialCapacity), defaultValue); - } + /** + * 构建TolerantMap + * + * @param map map实现 + * @param defaultValue 默认值 + * @param 键类型 + * @param 值类型 + * @return TolerantMap + */ + public static TolerantMap of(Map map, V defaultValue) { + return new TolerantMap<>(map, defaultValue); + } - public TolerantMap(Map map, V defaultValue) { - this.map = map; - this.defaultValue = defaultValue; - } + @Override + public V get(Object key) { + return getOrDefault(key, defaultValue); + } - public static TolerantMap of(Map map, V defaultValue) { - return new TolerantMap<>(map, defaultValue); - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + 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); + } - @Override - public int size() { - return map.size(); - } + @Override + public int hashCode() { + return Objects.hash(getRaw(), defaultValue); + } - @Override - public boolean isEmpty() { - return map.isEmpty(); - } - - @Override - public boolean containsValue(Object value) { - return map.containsValue(value); - } - - @Override - public boolean containsKey(Object key) { - return map.containsKey(key); - } - - @Override - public V get(Object key) { - return getOrDefault(key, defaultValue); - } - - @Override - public V put(K key, V value) { - return map.put(key, value); - } - - @Override - public V remove(Object key) { - return map.remove(key); - } - - @Override - public void putAll(Map m) { - map.putAll(m); - } - - @Override - public void clear() { - map.clear(); - } - - @Override - public Set keySet() { - return map.keySet(); - } - - @Override - public Collection values() { - return map.values(); - } - - @Override - public Set> entrySet() { - return map.entrySet(); - } - - @Override - public V getOrDefault(Object key, V defaultValue) { - return map.getOrDefault(key, defaultValue); - } - - @Override - public void forEach(BiConsumer action) { - map.forEach(action); - } - - @Override - public void replaceAll(BiFunction function) { - map.replaceAll(function); - } - - @Override - public V putIfAbsent(K key, V value) { - return map.putIfAbsent(key, value); - } - - @Override - public boolean remove(Object key, Object value) { - return map.remove(key, value); - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { - return map.replace(key, oldValue, newValue); - } - - @Override - public V replace(K key, V value) { - return map.replace(key, value); - } - - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - return map.computeIfAbsent(key, mappingFunction); - } - - @Override - public V computeIfPresent(K key, BiFunction remappingFunction) { - return map.computeIfPresent(key, remappingFunction); - } - - @Override - public V compute(K key, BiFunction remappingFunction) { - return map.compute(key, remappingFunction); - } - - @Override - public V merge(K key, V value, BiFunction remappingFunction) { - return map.merge(key, value, remappingFunction); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } - TolerantMap that = (TolerantMap) o; - return map.equals(that.map) && Objects.equals(defaultValue, that.defaultValue); - } - - @Override - public int hashCode() { - return Objects.hash(map, defaultValue); - } - - @Override - public String toString() { - return "TolerantMap{" + "map=" + map + ", defaultValue=" + defaultValue + '}'; - } - - private void writeObject(ObjectOutputStream s) throws IOException { - s.writeObject(ObjectUtil.serialize(map)); - s.writeObject(ObjectUtil.serialize(defaultValue)); - } - - private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { - map = ObjectUtil.deserialize((byte[]) s.readObject()); - defaultValue = ObjectUtil.deserialize((byte[]) s.readObject()); - } - - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } + @Override + public String toString() { + return "TolerantMap{" + "map=" + getRaw() + ", defaultValue=" + defaultValue + '}'; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/CollectionValueMap.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/CollectionValueMap.java index dab043fd2..64d1a47fe 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/CollectionValueMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/CollectionValueMap.java @@ -1,12 +1,12 @@ package cn.hutool.core.map.multi; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapWrapper; + import java.util.Collection; import java.util.HashMap; import java.util.Map; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapWrapper; - /** * 值作为集合的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 * @@ -67,7 +67,7 @@ public abstract class CollectionValueMap extends MapWrapper>(initialCapacity, loadFactor)); + super(new HashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end @@ -81,7 +81,7 @@ public abstract class CollectionValueMap extends MapWrapper collection = this.get(key); if (null == collection) { - collection = createCollction(); + collection = createCollection(); this.put(key, collection); } collection.add(value); @@ -105,5 +105,5 @@ public abstract class CollectionValueMap extends MapWrapper createCollction(); + protected abstract Collection createCollection(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/ListValueMap.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/ListValueMap.java index 72b705bbd..b7e908353 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/ListValueMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/ListValueMap.java @@ -62,7 +62,7 @@ public class ListValueMap extends CollectionValueMap { * @param loadFactor 加载因子 */ public ListValueMap(int initialCapacity, float loadFactor) { - super(new HashMap>(initialCapacity, loadFactor)); + super(new HashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end @@ -72,7 +72,7 @@ public class ListValueMap extends CollectionValueMap { } @Override - protected Collection createCollction() { + protected Collection createCollection() { return new ArrayList<>(DEFAULT_COLLCTION_INITIAL_CAPACITY); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/SetValueMap.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/SetValueMap.java index 52efdc4dc..a1e814bb4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/SetValueMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/SetValueMap.java @@ -62,7 +62,7 @@ public class SetValueMap extends CollectionValueMap { * @param loadFactor 加载因子 */ public SetValueMap(int initialCapacity, float loadFactor) { - super(new HashMap>(initialCapacity, loadFactor)); + super(new HashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end @@ -72,7 +72,7 @@ public class SetValueMap extends CollectionValueMap { } @Override - protected Collection createCollction() { + protected Collection createCollection() { return new LinkedHashSet<>(DEFAULT_COLLCTION_INITIAL_CAPACITY); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/math/Money.java b/hutool-core/src/main/java/cn/hutool/core/math/Money.java index 69d209e29..7845181c9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/math/Money.java +++ b/hutool-core/src/main/java/cn/hutool/core/math/Money.java @@ -77,7 +77,7 @@ public class Money implements Serializable, Comparable { /** * 币种。 */ - private Currency currency; + private final Currency currency; // 构造器 ==================================================== diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java index 36e503915..1c218677f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java @@ -12,6 +12,7 @@ import cn.hutool.core.util.StrUtil; import java.io.IOException; import java.io.OutputStream; import java.net.DatagramSocket; +import java.net.HttpCookie; import java.net.IDN; import java.net.Inet4Address; import java.net.Inet6Address; @@ -27,8 +28,10 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import java.util.TreeSet; @@ -647,7 +650,7 @@ public class NetUtil { if (ip != null && ip.indexOf(",") > 0) { final String[] ips = ip.trim().split(","); for (String subIp : ips) { - if (false == isUnknow(subIp)) { + if (false == isUnknown(subIp)) { ip = subIp; break; } @@ -662,8 +665,20 @@ public class NetUtil { * @param checkString 被检测的字符串 * @return 是否未知 * @since 4.4.1 + * @deprecated 拼写错误,请使用{@link #isUnknown(String)} */ public static boolean isUnknow(String checkString) { + return isUnknown(checkString); + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关
    + * + * @param checkString 被检测的字符串 + * @return 是否未知 + * @since 5.2.6 + */ + public static boolean isUnknown(String checkString) { return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); } @@ -692,6 +707,36 @@ public class NetUtil { } } + /** + * 解析Cookie信息 + * + * @param cookieStr Cookie字符串 + * @return cookie字符串 + * @since 5.2.6 + */ + public static List parseCookies(String cookieStr) { + if (StrUtil.isBlank(cookieStr)) { + return Collections.emptyList(); + } + return HttpCookie.parse(cookieStr); + } + + /** + * 检查远程端口是否开启 + * + * @param address 远程地址 + * @param timeout 检测超时 + * @return 远程端口是否开启 + * @since 5.3.2 + */ + public static boolean isOpen(InetSocketAddress address, int timeout) { + try (Socket sc = new Socket()){ + sc.connect(address, timeout); + return true; + } catch (Exception e) { + return false; + } + } // ----------------------------------------------------------------------------------------- Private method start /** diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java new file mode 100644 index 000000000..9509fa72c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java @@ -0,0 +1,73 @@ +package cn.hutool.core.net; + +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.ByteArrayOutputStream; +import java.io.Serializable; +import java.nio.charset.Charset; + +/** + * URL解码,数据内容的类型是 application/x-www-form-urlencoded。 + * + *
    + * 1. 将%20转换为空格 ;
    + * 2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
    + * 3. 跳过不符合规范的%形式,直接输出
    + * 
    + * + * @author looly + */ +public class URLDecoder implements Serializable { + private static final long serialVersionUID = 1L; + + private static final byte ESCAPE_CHAR = '%'; + + /** + * 解码 + * + * @param str 包含URL编码后的字符串 + * @param charset 编码 + * @return 解码后的字符串 + */ + public static String decode(String str, Charset charset) { + return StrUtil.str(decode(StrUtil.bytes(str, charset)), charset); + } + + /** + * 解码 + * + * @param bytes url编码的bytes + * @return 解码后的bytes + */ + public static byte[] decode(byte[] bytes) { + if (bytes == null) { + return null; + } + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length); + int b; + for (int i = 0; i < bytes.length; i++) { + b = bytes[i]; + if (b == '+') { + buffer.write(CharUtil.SPACE); + } else if (b == ESCAPE_CHAR) { + if (i + 1 < bytes.length) { + final int u = CharUtil.digit16(bytes[i + 1]); + if (u >= 0 && i + 2 < bytes.length) { + final int l = CharUtil.digit16(bytes[i + 2]); + if (l >= 0) { + buffer.write((char) ((u << 4) + l)); + i += 2; + continue; + } + } + } + // 跳过不符合规范的%形式 + buffer.write(b); + } else { + buffer.write(b); + } + } + return buffer.toByteArray(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java index 096b0025d..c01365f00 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java @@ -1,5 +1,8 @@ package cn.hutool.core.net; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.HexUtil; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; @@ -7,9 +10,6 @@ import java.io.Serializable; import java.nio.charset.Charset; import java.util.BitSet; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.HexUtil; - /** * URL编码,数据内容的类型是 application/x-www-form-urlencoded。 * @@ -17,7 +17,6 @@ import cn.hutool.core.util.HexUtil; * 1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不会被编码; * 2.将空格转换为%20 ; * 3.将非文本内容转换成"%xy"的形式,xy是两位16进制的数值; - * 4.在每个 name=value 对之间放置 & 符号。 * * * @author looly, @@ -196,10 +195,8 @@ public class URLEncoder implements Serializable{ * @return 编码后的字符串 */ public String encode(String path, Charset charset) { - - int maxBytesPerChar = 10; final StringBuilder rewrittenPath = new StringBuilder(path.length()); - ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(buf, charset); int c; @@ -221,9 +218,8 @@ public class URLEncoder implements Serializable{ } byte[] ba = buf.toByteArray(); - for (int j = 0; j < ba.length; j++) { + for (byte toEncode : ba) { // Converting each byte in the buffer - byte toEncode = ba[j]; rewrittenPath.append('%'); HexUtil.appendHex(rewrittenPath, toEncode, false); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartFormData.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java similarity index 69% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartFormData.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java index a7e32fe57..b6162dc98 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartFormData.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java @@ -1,17 +1,17 @@ -package cn.hutool.extra.servlet.multipart; +package cn.hutool.core.net.multipart; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.multi.ListValueMap; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; +import java.nio.charset.Charset; +import java.util.List; import java.util.Map; import java.util.Set; -import javax.servlet.ServletRequest; - -import cn.hutool.core.util.ArrayUtil; - /** * HttpRequest解析器
    * 来自Jodd @@ -21,14 +21,14 @@ import cn.hutool.core.util.ArrayUtil; public class MultipartFormData { /** 请求参数 */ - private Map requestParameters = new HashMap(); + private final ListValueMap requestParameters = new ListValueMap<>(); /** 请求文件 */ - private Map requestFiles = new HashMap(); + private final ListValueMap requestFiles = new ListValueMap<>(); + /** 上传选项 */ + private final UploadSetting setting; /** 是否解析完毕 */ private boolean loaded; - /** 上传选项 */ - private UploadSetting setting; // --------------------------------------------------------------------- Constructor start /** @@ -48,16 +48,6 @@ public class MultipartFormData { } // --------------------------------------------------------------------- Constructor end - /** - * 解析上传文件和表单数据 - * - * @param request Http请求 - * @throws IOException IO异常 - */ - public void parseRequest(ServletRequest request) throws IOException { - parseRequestStream(request.getInputStream(), request.getCharacterEncoding()); - } - /** * 提取上传的文件和表单数据 * @@ -65,7 +55,7 @@ public class MultipartFormData { * @param charset 编码 * @throws IOException IO异常 */ - public void parseRequestStream(InputStream inputStream, String charset) throws IOException { + public void parseRequestStream(InputStream inputStream, Charset charset) throws IOException { setLoaded(); MultipartRequestInputStream input = new MultipartRequestInputStream(inputStream); @@ -115,12 +105,9 @@ public class MultipartFormData { * @return null未找到,否则返回值 */ public String getParam(String paramName) { - if (requestParameters == null) { - return null; - } - String[] values = requestParameters.get(paramName); - if (ArrayUtil.isNotEmpty(values)) { - return values[0]; + final List values = getListParam(paramName); + if (CollUtil.isNotEmpty(values)) { + return values.get(0); } return null; } @@ -129,9 +116,6 @@ public class MultipartFormData { * @return 获得参数名集合 */ public Set getParamNames() { - if (requestParameters == null) { - return Collections.emptySet(); - } return requestParameters.keySet(); } @@ -142,9 +126,21 @@ public class MultipartFormData { * @return 数组表单值 */ public String[] getArrayParam(String paramName) { - if (requestParameters == null) { - return null; + final List listParam = getListParam(paramName); + if(null != listParam){ + return listParam.toArray(new String[0]); } + return null; + } + + /** + * 获得集合表单值 + * + * @param paramName 参数名 + * @return 数组表单值 + * @since 5.3.0 + */ + public List getListParam(String paramName) { return requestParameters.get(paramName); } @@ -154,7 +150,16 @@ public class MultipartFormData { * @return 所有属性的集合 */ public Map getParamMap() { - return requestParameters; + return Convert.toMap(String.class, String[].class, getParamListMap()); + } + + /** + * 获取所有属性的集合 + * + * @return 所有属性的集合 + */ + public ListValueMap getParamListMap() { + return this.requestParameters; } // --------------------------------------------------------------------------- Files parameters @@ -180,9 +185,22 @@ public class MultipartFormData { * @return 上传的文件列表 */ public UploadFile[] getFiles(String paramName) { - if (requestFiles == null) { - return null; + final List fileList = getFileList(paramName); + if(null != fileList){ + return fileList.toArray(new UploadFile[0]); } + return null; + } + + /** + * 获得某个属性名的所有文件
    + * 当表单中两个文件使用同一个name的时候 + * + * @param paramName 属性名 + * @return 上传的文件列表 + * @since 5.3.0 + */ + public List getFileList(String paramName) { return requestFiles.get(paramName); } @@ -192,9 +210,6 @@ public class MultipartFormData { * @return 上传的文件属性名集合 */ public Set getFileParamNames() { - if (requestFiles == null) { - return Collections.emptySet(); - } return requestFiles.keySet(); } @@ -204,6 +219,15 @@ public class MultipartFormData { * @return 文件映射 */ public Map getFileMap() { + return Convert.toMap(String.class, UploadFile[].class, getFileListValueMap()); + } + + /** + * 获取文件映射 + * + * @return 文件映射 + */ + public ListValueMap getFileListValueMap() { return this.requestFiles; } @@ -225,9 +249,7 @@ public class MultipartFormData { * @param uploadFile 文件 */ private void putFile(String name, UploadFile uploadFile) { - UploadFile[] fileUploads = requestFiles.get(name); - fileUploads = fileUploads == null ? new UploadFile[] { uploadFile } : ArrayUtil.append(fileUploads, uploadFile); - requestFiles.put(name, fileUploads); + this.requestFiles.putValue(name, uploadFile); } /** @@ -237,9 +259,7 @@ public class MultipartFormData { * @param value 参数值 */ private void putParameter(String name, String value) { - String[] params = requestParameters.get(name); - params = params == null ? new String[] { value } : ArrayUtil.append(params, value); - requestParameters.put(name, params); + this.requestParameters.putValue(name, value); } /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartRequestInputStream.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java similarity index 84% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartRequestInputStream.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java index 65b35b648..8dc5df6b0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartRequestInputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java @@ -1,15 +1,16 @@ -package cn.hutool.extra.servlet.multipart; +package cn.hutool.core.net.multipart; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; /** * Http请求解析流,提供了专门针对带文件的form表单的解析
    * 来自Jodd - * + * * @author jodd.org */ public class MultipartRequestInputStream extends BufferedInputStream { @@ -20,7 +21,7 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 读取byte字节流,在末尾抛出异常 - * + * * @return byte * @throws IOException 读取异常 */ @@ -34,7 +35,7 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 跳过指定位数的 bytes. - * + * * @param i 跳过的byte数 * @throws IOException IO异常 */ @@ -47,12 +48,14 @@ public class MultipartRequestInputStream extends BufferedInputStream { // ---------------------------------------------------------------- boundary - /** part部分边界 */ + /** + * part部分边界 + */ protected byte[] boundary; /** * 输入流中读取边界 - * + * * @return 边界 * @throws IOException 读取异常 */ @@ -60,6 +63,7 @@ public class MultipartRequestInputStream extends BufferedInputStream { ByteArrayOutputStream boundaryOutput = new ByteArrayOutputStream(1024); byte b; // skip optional whitespaces + //noinspection StatementWithEmptyBody while ((b = readByte()) <= ' ') { } boundaryOutput.write(b); @@ -89,12 +93,12 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 从流中读取文件头部信息, 如果达到末尾则返回null - * + * * @param encoding 字符集 * @return 头部信息, 如果达到末尾则返回null * @throws IOException 读取异常 */ - public UploadFileHeader readDataHeader(String encoding) throws IOException { + public UploadFileHeader readDataHeader(Charset encoding) throws IOException { String dataHeader = readDataHeaderString(encoding); if (dataHeader != null) { lastHeader = new UploadFileHeader(dataHeader); @@ -104,7 +108,14 @@ public class MultipartRequestInputStream extends BufferedInputStream { return lastHeader; } - protected String readDataHeaderString(String encoding) throws IOException { + /** + * 读取数据头信息字符串 + * + * @param charset 编码 + * @return 数据头信息字符串 + * @throws IOException IO异常 + */ + protected String readDataHeaderString(Charset charset) throws IOException { ByteArrayOutputStream data = new ByteArrayOutputStream(); byte b; while (true) { @@ -128,13 +139,13 @@ public class MultipartRequestInputStream extends BufferedInputStream { data.write(b); } skipBytes(3); - return encoding == null ? data.toString() : data.toString(encoding); + return charset == null ? data.toString() : data.toString(charset.name()); } // ---------------------------------------------------------------- copy /** * 全部字节流复制到out - * + * * @param out 输出流 * @return 复制的字节数 * @throws IOException 读取异常 @@ -154,8 +165,8 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 复制字节流到out, 大于maxBytes或者文件末尾停止 - * - * @param out 输出流 + * + * @param out 输出流 * @param limit 最大字节数 * @return 复制的字节数 * @throws IOException 读取异常 @@ -178,7 +189,7 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 跳过边界表示 - * + * * @return 跳过的字节数 * @throws IOException 读取异常 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFile.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java similarity index 83% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFile.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java index 43f4f3884..4cc456934 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFile.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java @@ -1,36 +1,29 @@ -package cn.hutool.extra.servlet.multipart; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; +package cn.hutool.core.net.multipart; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.log.Log; -import cn.hutool.log.LogFactory; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; /** * 上传的文件对象 - * + * * @author xiaoleilu - * */ public class UploadFile { - private static Log log = LogFactory.get(); private static final String TMP_FILE_PREFIX = "hutool-"; private static final String TMP_FILE_SUFFIX = ".upload.tmp"; - private UploadFileHeader header; - private UploadSetting setting; - + private final UploadFileHeader header; + private final UploadSetting setting; + private int size = -1; // 文件流(小文件位于内存中) @@ -40,8 +33,8 @@ public class UploadFile { /** * 构造 - * - * @param header 头部信息 + * + * @param header 头部信息 * @param setting 上传设置 */ public UploadFile(UploadFileHeader header, UploadSetting setting) { @@ -56,6 +49,7 @@ public class UploadFile { */ public void delete() { if (tempFile != null) { + //noinspection ResultOfMethodCallIgnored tempFile.delete(); } if (data != null) { @@ -66,17 +60,18 @@ public class UploadFile { /** * 将上传的文件写入指定的目标文件路径,自动创建文件
    * 写入后原临时文件会被删除 + * * @param destPath 目标文件路径 * @return 目标文件 * @throws IOException IO异常 */ public File write(String destPath) throws IOException { - if(data != null || tempFile != null) { - return write(FileUtil.touch(destPath)); + if (data != null || tempFile != null) { + return write(FileUtil.file(destPath)); } return null; } - + /** * 将上传的文件写入目标文件
    * 写入后原临时文件会被删除 @@ -87,7 +82,7 @@ public class UploadFile { */ public File write(File destination) throws IOException { assertValid(); - + if (destination.isDirectory() == true) { destination = new File(destination, this.header.getFileName()); } @@ -101,14 +96,14 @@ public class UploadFile { } return destination; } - + /** * @return 获得文件字节流 * @throws IOException IO异常 */ public byte[] getFileContent() throws IOException { assertValid(); - + if (data != null) { return data; } @@ -124,12 +119,12 @@ public class UploadFile { */ public InputStream getFileInputStream() throws IOException { assertValid(); - + if (data != null) { - return new BufferedInputStream(new ByteArrayInputStream(data)); + return IoUtil.toBuffered(IoUtil.toStream(this.data)); } if (tempFile != null) { - return new BufferedInputStream(new FileInputStream(tempFile)); + return IoUtil.toBuffered(IoUtil.toStream(this.tempFile)); } return null; } @@ -174,9 +169,10 @@ public class UploadFile { } // ---------------------------------------------------------------- process + /** * 处理上传表单流,提取出文件 - * + * * @param input 上传表单的流 * @return 是否成功 * @throws IOException IO异常 @@ -184,7 +180,6 @@ public class UploadFile { protected boolean processStream(MultipartRequestInputStream input) throws IOException { if (!isAllowedExtension()) { // 非允许的扩展名 - log.debug("Forbidden uploaded file [{}]", this.getFileName()); size = input.skipToBoundary(); return false; } @@ -193,7 +188,7 @@ public class UploadFile { // 处理内存文件 int memoryThreshold = setting.memoryThreshold; if (memoryThreshold > 0) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(memoryThreshold); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(memoryThreshold); int written = input.copy(baos, memoryThreshold); data = baos.toByteArray(); if (written <= memoryThreshold) { @@ -220,9 +215,9 @@ public class UploadFile { size += input.copy(out, maxFileSize - size + 1); // one more byte to detect larger files if (size > maxFileSize) { // 超出上传大小限制 + //noinspection ResultOfMethodCallIgnored tempFile.delete(); tempFile = null; - log.debug("Upload file [{}] too big, file size > [{}]", this.getFileName(), maxFileSize); input.skipToBoundary(); return false; } @@ -236,6 +231,7 @@ public class UploadFile { } // ---------------------------------------------------------------------------- Private method start + /** * @return 是否为允许的扩展名 */ @@ -257,13 +253,14 @@ public class UploadFile { // 未匹配到扩展名,如果为允许列表,返回false, 否则true return !isAllow; } - + /** * 断言是否文件流可用 + * * @throws IOException IO异常 */ private void assertValid() throws IOException { - if(! isUploaded()) { + if (false == isUploaded()) { throw new IOException(StrUtil.format("File [{}] upload fail", getFileName())); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFileHeader.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFileHeader.java similarity index 94% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFileHeader.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFileHeader.java index b8b1a86ef..5c1770d9b 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFileHeader.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFileHeader.java @@ -1,4 +1,4 @@ -package cn.hutool.extra.servlet.multipart; +package cn.hutool.core.net.multipart; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadSetting.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java similarity index 66% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadSetting.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java index c75116c65..872bb7010 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadSetting.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java @@ -1,22 +1,12 @@ -package cn.hutool.extra.servlet.multipart; - -import cn.hutool.core.util.URLUtil; -import cn.hutool.log.Log; -import cn.hutool.setting.Setting; - -import java.net.URL; +package cn.hutool.core.net.multipart; /** * 上传文件设定文件 - * + * * @author xiaoleilu * */ public class UploadSetting { - private static final Log log = Log.get(); - - /** 默认的配置文件路径(相对ClassPath) */ - public final static String DEFAULT_SETTING_PATH = "config/upload.setting"; /** 最大文件大小,默认无限制 */ protected int maxFileSize = -1; @@ -42,7 +32,7 @@ public class UploadSetting { /** * 设定最大文件大小,-1表示无限制 - * + * * @param maxFileSize 最大文件大小 */ public void setMaxFileSize(int maxFileSize) { @@ -59,7 +49,7 @@ public class UploadSetting { /** * 设定文件保存到内存的边界
    * 如果文件大小小于这个边界,将保存于内存中,否则保存至临时目录中 - * + * * @param memoryThreshold 文件保存到内存的边界 */ public void setMemoryThreshold(int memoryThreshold) { @@ -75,7 +65,7 @@ public class UploadSetting { /** * 设定上传文件的临时目录,null表示使用系统临时目录 - * + * * @param tmpUploadPath 临时目录,绝对路径 */ public void setTmpUploadPath(String tmpUploadPath) { @@ -92,7 +82,7 @@ public class UploadSetting { /** * 设定文件扩展名限定里列表
    * 禁止列表还是允许列表取决于isAllowFileExts - * + * * @param fileExts 文件扩展名列表 */ public void setFileExts(String[] fileExts) { @@ -101,7 +91,7 @@ public class UploadSetting { /** * 是否允许文件扩展名
    - * + * * @return 若true表示只允许列表里的扩展名,否则是禁止列表里的扩展名 */ public boolean isAllowFileExts() { @@ -110,39 +100,11 @@ public class UploadSetting { /** * 设定是否允许扩展名 - * + * * @param isAllowFileExts 若true表示只允许列表里的扩展名,否则是禁止列表里的扩展名 */ public void setAllowFileExts(boolean isAllowFileExts) { this.isAllowFileExts = isAllowFileExts; } // ---------------------------------------------------------------------- Setters and Getters end - - /** - * 加载全局设定
    - * 使用默认的配置文件classpath/config/upload.setting - */ - public void load() { - load(DEFAULT_SETTING_PATH); - } - - /** - * 加载全局设定 - * - * @param settingPath 设定文件路径,相对Classpath - */ - synchronized public void load(String settingPath) { - URL url = URLUtil.getURL(settingPath); - if (url == null) { - log.info("Can not find Upload setting file [{}], use default setting.", settingPath); - return; - } - Setting setting = new Setting(url, Setting.DEFAULT_CHARSET, true); - - maxFileSize = setting.getInt("file.size.max"); - memoryThreshold = setting.getInt("memory.threshold"); - tmpUploadPath = setting.getStr("tmp.upload.path"); - fileExts = setting.getStrings("file.exts"); - isAllowFileExts = setting.getBool("file.exts.allow"); - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/multipart/package-info.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/package-info.java new file mode 100644 index 000000000..1056d3767 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/package-info.java @@ -0,0 +1,7 @@ +/** + * 文件上传封装 + * + * @author looly + * + */ +package cn.hutool.core.net.multipart; \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java new file mode 100644 index 000000000..95549f74f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -0,0 +1,495 @@ +package cn.hutool.core.net.url; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLStreamHandler; +import java.nio.charset.Charset; + +/** + * URL 生成器,格式形如: + *
    + * [scheme:]scheme-specific-part[#fragment]
    + * [scheme:][//authority][path][?query][#fragment]
    + * [scheme:][//host:port][path][?query][#fragment]
    + * 
    + * + * @author looly + * @see Uniform Resource Identifier + * @since 5.3.1 + */ +public final class UrlBuilder implements Serializable { + private static final long serialVersionUID = 1L; + private static final String DEFAULT_SCHEME = "http"; + + /** + * 协议,例如http + */ + private String scheme; + /** + * 主机,例如127.0.0.1 + */ + private String host; + /** + * 端口,默认-1 + */ + private int port = -1; + /** + * 路径,例如/aa/bb/cc + */ + private UrlPath path; + /** + * 查询语句,例如a=1&b=2 + */ + private UrlQuery query; + /** + * 标识符,例如#后边的部分 + */ + private String fragment; + + /** + * 编码,用于URLEncode和URLDecode + */ + private Charset charset; + + /** + * 使用URI构建UrlBuilder + * + * @param uri URI + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(URI uri, Charset charset) { + return of(uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getRawQuery(), uri.getFragment(), charset); + } + + /** + * 使用URL字符串构建UrlBuilder,当传入的URL没有协议时,按照http协议对待。 + * + * @param httpUrl URL字符串 + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder ofHttp(String httpUrl, Charset charset) { + Assert.notBlank(httpUrl, "Http url must be not blank!"); + + final int sepIndex = httpUrl.indexOf("://"); + if (sepIndex < 0) { + httpUrl = "http://" + httpUrl.trim(); + } + return of(httpUrl, charset); + } + + /** + * 使用URL字符串构建UrlBuilder + * + * @param url URL字符串 + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(String url, Charset charset) { + Assert.notBlank(url, "Url must be not blank!"); + return of(URLUtil.url(url.trim()), charset); + } + + /** + * 使用URL构建UrlBuilder + * + * @param url URL + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(URL url, Charset charset) { + return of(url.getProtocol(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef(), charset); + } + + /** + * 构建UrlBuilder + * + * @param scheme 协议,默认http + * @param host 主机,例如127.0.0.1 + * @param port 端口,-1表示默认端口 + * @param path 路径,例如/aa/bb/cc + * @param query 查询,例如a=1&b=2 + * @param fragment 标识符例如#后边的部分 + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(String scheme, String host, int port, String path, String query, String fragment, Charset charset) { + return of(scheme, host, port, UrlPath.of(path, charset), UrlQuery.of(query, charset), fragment, charset); + } + + /** + * 构建UrlBuilder + * + * @param scheme 协议,默认http + * @param host 主机,例如127.0.0.1 + * @param port 端口,-1表示默认端口 + * @param path 路径,例如/aa/bb/cc + * @param query 查询,例如a=1&b=2 + * @param fragment 标识符例如#后边的部分 + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(String scheme, String host, int port, UrlPath path, UrlQuery query, String fragment, Charset charset) { + return new UrlBuilder(scheme, host, port, path, query, fragment, charset); + } + + /** + * 创建空的UrlBuilder + * + * @return UrlBuilder + */ + public static UrlBuilder create() { + return new UrlBuilder(); + } + + /** + * 构造 + */ + public UrlBuilder() { + this.charset = CharsetUtil.CHARSET_UTF_8; + } + + /** + * 构造 + * + * @param scheme 协议,默认http + * @param host 主机,例如127.0.0.1 + * @param port 端口,-1表示默认端口 + * @param path 路径,例如/aa/bb/cc + * @param query 查询,例如a=1&b=2 + * @param fragment 标识符例如#后边的部分 + * @param charset 编码,用于URLEncode和URLDecode + */ + public UrlBuilder(String scheme, String host, int port, UrlPath path, UrlQuery query, String fragment, Charset charset) { + this.charset = charset; + this.scheme = scheme; + this.host = host; + this.port = port; + this.path = path; + this.query = query; + this.setFragment(fragment); + } + + /** + * 获取协议,例如http + * + * @return 协议,例如http + */ + public String getScheme() { + return scheme; + } + + /** + * 获取协议,例如http,如果用户未定义协议,使用默认的http协议 + * + * @return 协议,例如http + */ + public String getSchemeWithDefault() { + return StrUtil.emptyToDefault(this.scheme, DEFAULT_SCHEME); + } + + /** + * 设置协议,例如http + * + * @param scheme 协议,例如http + * @return this + */ + public UrlBuilder setScheme(String scheme) { + this.scheme = scheme; + return this; + } + + /** + * 获取 主机,例如127.0.0.1 + * + * @return 主机,例如127.0.0.1 + */ + public String getHost() { + return host; + } + + /** + * 设置主机,例如127.0.0.1 + * + * @param host 主机,例如127.0.0.1 + * @return this + */ + public UrlBuilder setHost(String host) { + this.host = host; + return this; + } + + /** + * 获取端口,默认-1 + * + * @return 端口,默认-1 + */ + public int getPort() { + return port; + } + + /** + * 设置端口,默认-1 + * + * @param port 端口,默认-1 + * @return this + */ + public UrlBuilder setPort(int port) { + this.port = port; + return this; + } + + /** + * 获得authority部分 + * + * @return authority部分 + */ + public String getAuthority() { + return (port < 0) ? host : host + ":" + port; + } + + /** + * 获取路径,例如/aa/bb/cc + * + * @return 路径,例如/aa/bb/cc + */ + public UrlPath getPath() { + return path; + } + + /** + * 获得路径,例如/aa/bb/cc + * + * @return 路径,例如/aa/bb/cc + */ + public String getPathStr() { + return null == this.path ? StrUtil.SLASH : this.path.build(charset); + } + + /** + * 设置路径,例如/aa/bb/cc,将覆盖之前所有的path相关设置 + * + * @param path 路径,例如/aa/bb/cc + * @return this + */ + public UrlBuilder setPath(UrlPath path) { + this.path = path; + return this; + } + + /** + * 增加路径节点 + * + * @param segment 路径节点 + * @return this + */ + public UrlBuilder addPath(String segment) { + if (StrUtil.isBlank(segment)) { + return this; + } + if (null == this.path) { + this.path = new UrlPath(); + } + this.path.add(segment); + return this; + } + + /** + * 追加path节点 + * + * @param segment path节点 + * @return this + */ + public UrlBuilder appendPath(CharSequence segment) { + if (StrUtil.isEmpty(segment)) { + return this; + } + + if (this.path == null) { + this.path = new UrlPath(); + } + this.path.add(segment); + return this; + } + + /** + * 获取查询语句,例如a=1&b=2 + * + * @return 查询语句,例如a=1&b=2 + */ + public UrlQuery getQuery() { + return query; + } + + /** + * 获取查询语句,例如a=1&b=2 + * + * @return 查询语句,例如a=1&b=2 + */ + public String getQueryStr() { + return null == this.query ? null : this.query.build(this.charset); + } + + /** + * 设置查询语句,例如a=1&b=2,将覆盖之前所有的query相关设置 + * + * @param query 查询语句,例如a=1&b=2 + * @return this + */ + public UrlBuilder setQuery(UrlQuery query) { + this.query = query; + return this; + } + + /** + * 添加查询项,支持重复键 + * + * @param key 键 + * @param value 值 + * @return this + */ + public UrlBuilder addQuery(String key, String value) { + if (StrUtil.isEmpty(key)) { + return this; + } + + if (this.query == null) { + this.query = new UrlQuery(); + } + this.query.add(key, value); + return this; + } + + /** + * 获取标识符,#后边的部分 + * + * @return 标识符,例如#后边的部分 + */ + public String getFragment() { + return fragment; + } + + /** + * 获取标识符,#后边的部分 + * + * @return 标识符,例如#后边的部分 + */ + public String getFragmentEncoded() { + return URLUtil.encodeAll(this.fragment, this.charset); + } + + /** + * 设置标识符,例如#后边的部分 + * + * @param fragment 标识符,例如#后边的部分 + * @return this + */ + public UrlBuilder setFragment(String fragment) { + if (StrUtil.isEmpty(fragment)) { + this.fragment = null; + } + this.fragment = StrUtil.removePrefix(fragment, "#"); + return this; + } + + /** + * 获取编码,用于URLEncode和URLDecode + * + * @return 编码 + */ + public Charset getCharset() { + return charset; + } + + /** + * 设置编码,用于URLEncode和URLDecode + * + * @param charset 编码 + * @return this + */ + public UrlBuilder setCharset(Charset charset) { + this.charset = charset; + return this; + } + + /** + * 创建URL字符串 + * + * @return URL字符串 + */ + public String build() { + return toURL().toString(); + } + + /** + * 转换为{@link URL} 对象 + * + * @return {@link URL} + */ + public URL toURL() { + return toURL(null); + } + + /** + * 转换为{@link URL} 对象 + * + * @param handler {@link URLStreamHandler},null表示默认 + * @return {@link URL} + */ + public URL toURL(URLStreamHandler handler) { + final StringBuilder fileBuilder = new StringBuilder(); + + // path + fileBuilder.append(StrUtil.blankToDefault(getPathStr(), StrUtil.SLASH)); + + // query + final String query = getQueryStr(); + if (StrUtil.isNotBlank(query)) { + fileBuilder.append('?').append(query); + } + + // fragment + if (StrUtil.isNotBlank(this.fragment)) { + fileBuilder.append('#').append(getFragmentEncoded()); + } + + try { + return new URL(getSchemeWithDefault(), host, port, fileBuilder.toString(), handler); + } catch (MalformedURLException e) { + return null; + } + } + + /** + * 转换为URI + * + * @return URI + */ + public URI toURI() { + try { + return new URI( + getSchemeWithDefault(), + getAuthority(), + getPathStr(), + getQueryStr(), + getFragmentEncoded()); + } catch (URISyntaxException e) { + return null; + } + } + + @Override + public String toString() { + return build(); + } + +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java new file mode 100644 index 000000000..31a1b715f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java @@ -0,0 +1,185 @@ +package cn.hutool.core.net.url; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + +import java.nio.charset.Charset; +import java.util.LinkedList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * URL中Path部分的封装 + * + * @author looly + * @since 5.3.1 + */ +public class UrlPath { + private List segments; + private boolean withEngTag; + + /** + * 构建UrlPath + * + * @param pathStr 初始化的路径字符串 + * @param charset decode用的编码,null表示不做decode + * @return {@link UrlPath} + */ + public static UrlPath of(String pathStr, Charset charset) { + final UrlPath urlPath = new UrlPath(); + urlPath.parse(pathStr, charset); + return urlPath; + } + + /** + * 是否path的末尾加 / + * + * @param withEngTag 是否path的末尾加 / + * @return this + */ + public UrlPath setWithEndTag(boolean withEngTag) { + this.withEngTag = withEngTag; + return this; + } + + /** + * 获取path的节点列表 + * + * @return 节点列表 + */ + public List getSegments() { + return this.segments; + } + + /** + * 获得指定节点 + * + * @param index 节点位置 + * @return 节点,无节点或者越界返回null + */ + public String getSegment(int index) { + if (null == this.segments || index >= this.segments.size()) { + return null; + } + return this.segments.get(index); + } + + /** + * 添加到path最后面 + * + * @param segment Path节点 + * @return this + */ + public UrlPath add(CharSequence segment) { + add(segment, false); + return this; + } + + /** + * 添加到path最前面 + * + * @param segment Path节点 + * @return this + */ + public UrlPath addBefore(CharSequence segment) { + add(segment, true); + return this; + } + + /** + * 解析path + * + * @param path 路径,类似于aaa/bb/ccc + * @param charset decode编码,null表示不解码 + * @return this + */ + public UrlPath parse(String path, Charset charset) { + UrlPath urlPath = new UrlPath(); + + if (StrUtil.isNotEmpty(path)) { + path = path.trim(); + + // 原URL中以/结尾,则这个规则需保留,issue#I1G44J@Gitee + if(StrUtil.endWith(path, CharUtil.SLASH)){ + this.withEngTag = true; + } + + final StringTokenizer tokenizer = new StringTokenizer(path, "/"); + while (tokenizer.hasMoreTokens()) { + add(URLUtil.decode(tokenizer.nextToken(), charset)); + } + } + + return urlPath; + } + + /** + * 构建path,前面带'/' + * + * @param charset encode编码,null表示不做encode + * @return 如果没有任何内容,则返回空字符串"" + */ + public String build(Charset charset) { + if (CollUtil.isEmpty(this.segments)) { + return StrUtil.EMPTY; + } + + final StringBuilder builder = new StringBuilder(); + for (String segment : segments) { + builder.append(CharUtil.SLASH).append(URLUtil.encodeAll(segment, charset)); + } + if (withEngTag || StrUtil.isEmpty(builder)) { + builder.append(CharUtil.SLASH); + } + return builder.toString(); + } + + @Override + public String toString() { + return build(null); + } + + /** + * 增加节点 + * + * @param segment 节点 + * @param before 是否在前面添加 + */ + private void add(CharSequence segment, boolean before) { + final String seg = fixSegment(segment); + if (null == seg) { + return; + } + + + if (this.segments == null) { + this.segments = new LinkedList<>(); + } + if (before) { + this.segments.add(0, seg); + } else { + this.segments.add(seg); + } + } + + /** + * 修正节点,包括去掉前后的/,去掉空白符 + * + * @param segment 节点 + * @return 修正后的节点 + */ + private static String fixSegment(CharSequence segment) { + if (StrUtil.isEmpty(segment) || "/".contentEquals(segment)) { + return null; + } + + String segmentStr = StrUtil.str(segment); + segmentStr = StrUtil.trim(segmentStr); + segmentStr = StrUtil.removePrefix(segmentStr, "/"); + segmentStr = StrUtil.removeSuffix(segmentStr, "/"); + segmentStr = StrUtil.trim(segmentStr); + return segmentStr; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java new file mode 100644 index 000000000..74e4119b0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -0,0 +1,253 @@ +package cn.hutool.core.net.url; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.TableMap; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.Map; + +/** + * URL中查询字符串部分的封装,类似于: + *
    + *   key1=v1&key2=&key3=v3
    + * 
    + * + * @author looly + * @since 5.3.1 + */ +public class UrlQuery { + + private final TableMap query; + + /** + * 构建UrlQuery + * + * @param queryMap 初始化的查询键值对 + * @return {@link UrlQuery} + */ + public static UrlQuery of(Map queryMap) { + return new UrlQuery(queryMap); + } + + /** + * 构建UrlQuery + * + * @param queryStr 初始化的查询字符串 + * @param charset decode用的编码,null表示不做decode + * @return {@link UrlQuery} + */ + public static UrlQuery of(String queryStr, Charset charset) { + final UrlQuery urlQuery = new UrlQuery(); + urlQuery.parse(queryStr, charset); + return urlQuery; + } + + /** + * 构造 + */ + public UrlQuery() { + this(null); + } + + /** + * 构造 + * + * @param queryMap 初始化的查询键值对 + */ + public UrlQuery(Map queryMap) { + if (MapUtil.isNotEmpty(queryMap)) { + query = new TableMap<>(queryMap.size()); + addAll(queryMap); + } else { + query = new TableMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY); + } + } + + /** + * 增加键值对 + * + * @param key 键 + * @param value 值,集合和数组转换为逗号分隔形式 + * @return this + */ + public UrlQuery add(CharSequence key, Object value) { + this.query.put(key, toStr(value)); + return this; + } + + /** + * 批量增加键值对 + * + * @param queryMap query中的键值对 + * @return this + */ + public UrlQuery addAll(Map queryMap) { + if (MapUtil.isNotEmpty(queryMap)) { + queryMap.forEach(this::add); + } + return this; + } + + /** + * 解析URL中的查询字符串 + * + * @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3 + * @param charset decode编码,null表示不做decode + * @return this + */ + public UrlQuery parse(String queryStr, Charset charset) { + if (StrUtil.isBlank(queryStr)) { + return this; + } + + // 去掉Path部分 + int pathEndPos = queryStr.indexOf('?'); + if (pathEndPos > -1) { + queryStr = StrUtil.subSuf(queryStr, pathEndPos + 1); + if (StrUtil.isBlank(queryStr)) { + return this; + } + } + + final int len = queryStr.length(); + String name = null; + int pos = 0; // 未处理字符开始位置 + int i; // 未处理字符结束位置 + char c; // 当前字符 + for (i = 0; i < len; i++) { + c = queryStr.charAt(i); + switch (c) { + case '='://键和值的分界符 + if (null == name) { + // name可以是"" + name = queryStr.substring(pos, i); + // 开始位置从分节符后开始 + pos = i + 1; + } + // 当=不作为分界符时,按照普通字符对待 + break; + case '&'://键值对之间的分界符 + addParam(name, queryStr.substring(pos, i), charset); + name = null; + if (i+4 < len && "amp;".equals(queryStr.substring(i + 1, i + 5))) { + // issue#850@Github,"&"转义为"&" + i+=4; + } + // 开始位置从分节符后开始 + pos = i + 1; + break; + } + } + + // 处理结尾 + addParam(name, queryStr.substring(pos, i), charset); + return this; + } + + /** + * 获得查询的Map + * + * @return 查询的Map,只读 + */ + public Map getQueryMap() { + return MapUtil.unmodifiable(this.query); + } + + /** + * 获取查询值 + * + * @param key 键 + * @return 值 + */ + public CharSequence get(CharSequence key) { + if (MapUtil.isEmpty(this.query)) { + return null; + } + return this.query.get(key); + } + + /** + * 构建URL查询字符串,即将key-value键值对转换为key1=v1&key2=&key3=v3形式 + * + * @param charset encode编码,null表示不做encode编码 + * @return URL查询字符串 + */ + public String build(Charset charset) { + if (MapUtil.isEmpty(this.query)) { + return StrUtil.EMPTY; + } + + final StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + CharSequence key; + CharSequence value; + for (Map.Entry entry : this.query) { + if (isFirst) { + isFirst = false; + } else { + sb.append("&"); + } + key = entry.getKey(); + if (StrUtil.isNotEmpty(key)) { + sb.append(URLUtil.encodeAll(StrUtil.str(key), charset)).append("="); + value = entry.getValue(); + if (StrUtil.isNotEmpty(value)) { + sb.append(URLUtil.encodeAll(StrUtil.str(value), charset)); + } + } + } + return sb.toString(); + } + + @Override + public String toString() { + return build(null); + } + + /** + * 对象转换为字符串,用于URL的Query中 + * + * @param value 值 + * @return 字符串 + */ + private static String toStr(Object value) { + String result; + if (value instanceof Iterable) { + result = CollUtil.join((Iterable) value, ","); + } else if (value instanceof Iterator) { + result = IterUtil.join((Iterator) value, ","); + } else { + result = Convert.toStr(value); + } + return result; + } + + /** + * 将键值对加入到值为List类型的Map中,,情况如下: + *
    +	 *     1、key和value都不为null,类似于 "a=1"或者"=1",直接put
    +	 *     2、key不为null,value为null,类似于 "a=",值传""
    +	 *     3、key为null,value不为null,类似于 "1"
    +	 *     4、key和value都为null,忽略之,比如&&
    +	 * 
    + * + * @param key key,为null则value作为key + * @param value value,为null且key不为null时传入"" + * @param charset 编码 + */ + private void addParam(String key, String value, Charset charset) { + if (null != key) { + final String actualKey = URLUtil.decode(key, charset); + this.query.put(actualKey, StrUtil.nullToEmpty(URLUtil.decode(value, charset))); + } else if (null != value) { + // name为空,value作为name,value赋值"" + this.query.put(URLUtil.decode(value, charset), StrUtil.EMPTY); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/package-info.java b/hutool-core/src/main/java/cn/hutool/core/net/url/package-info.java new file mode 100644 index 000000000..1df7ac259 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/package-info.java @@ -0,0 +1,7 @@ +/** + * URL相关工具 + * + * @author looly + * @since 5.3.1 + */ +package cn.hutool.core.net.url; \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ClipboardMonitor.java b/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ClipboardMonitor.java index b24d899b6..ee1f4d942 100644 --- a/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ClipboardMonitor.java +++ b/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ClipboardMonitor.java @@ -1,5 +1,8 @@ package cn.hutool.core.swing.clipboard; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.ObjectUtil; + import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.Transferable; @@ -7,9 +10,6 @@ import java.io.Closeable; import java.util.LinkedHashSet; import java.util.Set; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.ObjectUtil; - /** * 剪贴板监听 * @@ -189,6 +189,7 @@ public enum ClipboardMonitor implements ClipboardOwner, Runnable, Closeable { for (int i = 0; i < this.tryCount; i++) { if (this.delay > 0 && i > 0) { // 第一次获取不等待,只有从第二次获取时才开始等待 + //noinspection BusyWait Thread.sleep(this.delay); } @@ -201,7 +202,7 @@ public enum ClipboardMonitor implements ClipboardOwner, Runnable, Closeable { return newContents; } } - return newContents; + return null; } // ------------------------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ImageSelection.java b/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ImageSelection.java index 21380df85..ea656307a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ImageSelection.java +++ b/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ImageSelection.java @@ -4,7 +4,6 @@ import java.awt.Image; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.IOException; import java.io.Serializable; /** @@ -17,7 +16,7 @@ import java.io.Serializable; public class ImageSelection implements Transferable, Serializable { private static final long serialVersionUID = 1L; - private Image image; + private final Image image; /** * 构造 @@ -56,7 +55,7 @@ public class ImageSelection implements Transferable, Serializable { * @return 转换后的对象 */ @Override - public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { if (false == DataFlavor.imageFlavor.equals(flavor)) { throw new UnsupportedFlavorException(flavor); } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/Simhash.java b/hutool-core/src/main/java/cn/hutool/core/text/Simhash.java index 74aa9fed4..a1fc9f191 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/Simhash.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/Simhash.java @@ -1,16 +1,14 @@ package cn.hutool.core.text; +import cn.hutool.core.lang.hash.MurmurHash; + import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - -import cn.hutool.core.lang.hash.MurmurHash; +import java.util.concurrent.locks.StampedLock; /** *

    @@ -35,8 +33,8 @@ public class Simhash { private final int hammingThresh; /** 按照分段存储simhash,查找更快速 */ - private List>> storage; - private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final List>> storage; + private final StampedLock lock = new StampedLock(); /** * 构造 @@ -57,7 +55,7 @@ public class Simhash { this.hammingThresh = hammingThresh; this.storage = new ArrayList<>(fracCount); for (int i = 0; i < fracCount; i++) { - storage.add(new HashMap>()); + storage.add(new HashMap<>()); } } @@ -104,8 +102,7 @@ public class Simhash { String frac; Map> fracMap; - final ReadLock readLock = this.lock.readLock(); - readLock.lock(); + final long stamp = this.lock.readLock(); try { for (int i = 0; i < fracCount; i++) { frac = fracList.get(i); @@ -120,7 +117,7 @@ public class Simhash { } } } finally { - readLock.unlock(); + this.lock.unlockRead(stamp); } return false; } @@ -137,8 +134,7 @@ public class Simhash { String frac; Map> fracMap; - final WriteLock writeLock = this.lock.writeLock(); - writeLock.lock(); + final long stamp = this.lock.writeLock(); try { for (int i = 0; i < fracCount; i++) { frac = lFrac.get(i); @@ -152,7 +148,7 @@ public class Simhash { } } } finally { - writeLock.unlock(); + this.lock.unlockWrite(stamp); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java b/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java index cccafb1fc..35aa70a8a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java @@ -1,15 +1,15 @@ package cn.hutool.core.text; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import cn.hutool.core.lang.PatternPool; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * 字符串切分器 * @author Looly @@ -149,10 +149,10 @@ public class StrSpliter { */ public static List split(String str, char separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase){ if(StrUtil.isEmpty(str)){ - return new ArrayList(0); + return new ArrayList<>(0); } if(limit == 1){ - return addToList(new ArrayList(1), str, isTrim, ignoreEmpty); + return addToList(new ArrayList<>(1), str, isTrim, ignoreEmpty); } final ArrayList list = new ArrayList<>(limit > 0 ? limit : 16); @@ -288,10 +288,10 @@ public class StrSpliter { */ public static List split(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase){ if(StrUtil.isEmpty(str)){ - return new ArrayList(0); + return new ArrayList<>(0); } if(limit == 1){ - return addToList(new ArrayList(1), str, isTrim, ignoreEmpty); + return addToList(new ArrayList<>(1), str, isTrim, ignoreEmpty); } if(StrUtil.isEmpty(separator)){//分隔符为空时按照空白符切分 @@ -350,10 +350,10 @@ public class StrSpliter { */ public static List split(String str, int limit){ if(StrUtil.isEmpty(str)){ - return new ArrayList(0); + return new ArrayList<>(0); } if(limit == 1){ - return addToList(new ArrayList(1), str, true, true); + return addToList(new ArrayList<>(1), str, true, true); } final ArrayList list = new ArrayList<>(); @@ -413,10 +413,10 @@ public class StrSpliter { */ public static List split(String str, Pattern separatorPattern, int limit, boolean isTrim, boolean ignoreEmpty){ if(StrUtil.isEmpty(str)){ - return new ArrayList(0); + return new ArrayList<>(0); } if(limit == 1){ - return addToList(new ArrayList(1), str, isTrim, ignoreEmpty); + return addToList(new ArrayList<>(1), str, isTrim, ignoreEmpty); } if(null == separatorPattern){//分隔符为空时按照空白符切分 @@ -507,7 +507,7 @@ public class StrSpliter { * @return Array */ private static String[] toArray(List list){ - return list.toArray(new String[list.size()]); + return list.toArray(new String[0]); } //---------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/UnicodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/UnicodeUtil.java index e2cb31f8b..7c9cd6d46 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/UnicodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/UnicodeUtil.java @@ -43,7 +43,7 @@ public class UnicodeUtil { pos = i + 2; } } else { - pos = i;//非Unicode符,结束 + //非Unicode符,结束 break; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java index d65bb56ee..f41987037 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java @@ -14,6 +14,7 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -143,17 +144,6 @@ public class CsvBaseReader implements Serializable { return read(FileUtil.getReader(path, charset)); } - /** - * 从Reader中读取CSV数据,读取后关闭Reader - * - * @param reader Reader - * @param rowHandler 行处理器,用于一行一行的处理数据 - * @throws IORuntimeException IO异常 - */ - public void read(Reader reader, CsvRowHandler rowHandler) throws IORuntimeException { - read(parse(reader), rowHandler); - } - /** * 从Reader中读取CSV数据,读取后关闭Reader * @@ -170,6 +160,52 @@ public class CsvBaseReader implements Serializable { return new CsvData(header, rows); } + /** + * 从Reader中读取CSV数据,结果为Map,读取后关闭Reader。
    + * 此方法默认识别首行为标题行。 + * + * @param reader Reader + * @return {@link CsvData},包含数据列表和行信息 + * @throws IORuntimeException IO异常 + */ + public List> readMapList(Reader reader) throws IORuntimeException { + // 此方法必须包含标题 + this.config.setContainsHeader(true); + + final List> result = new ArrayList<>(); + read(reader, (row) -> result.add(row.getFieldMap())); + return result; + } + + /** + * 从Reader中读取CSV数据并转换为Bean列表,读取后关闭Reader。
    + * 此方法默认识别首行为标题行。 + * + * @param Bean类型 + * @param reader Reader + * @param clazz Bean类型 + * @return Bean列表 + */ + public List read(Reader reader, Class clazz) { + // 此方法必须包含标题 + this.config.setContainsHeader(true); + + final List result = new ArrayList<>(); + read(reader, (row) -> result.add(row.toBean(clazz))); + return result; + } + + /** + * 从Reader中读取CSV数据,读取后关闭Reader + * + * @param reader Reader + * @param rowHandler 行处理器,用于一行一行的处理数据 + * @throws IORuntimeException IO异常 + */ + public void read(Reader reader, CsvRowHandler rowHandler) throws IORuntimeException { + read(parse(reader), rowHandler); + } + //--------------------------------------------------------------------------------------------- Private method start /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java index 09ed65a08..0ccd01a16 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java @@ -1,5 +1,12 @@ package cn.hutool.core.text.csv; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; + import java.io.Closeable; import java.io.IOException; import java.io.Reader; @@ -11,13 +18,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.text.StrBuilder; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; - /** * CSV行解析器,参考:FastCSV * @@ -96,12 +96,8 @@ public final class CsvParser implements Closeable, Serializable { while (false == finished) { startingLineNo = ++lineNo; currentFields = readLine(); - if(null == currentFields) { - break; - } fieldCount = currentFields.size(); - // 末尾 - if (fieldCount == 0) { + if(fieldCount < 1){ break; } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java index 0f76e3987..aaeedee39 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java @@ -1,5 +1,7 @@ package cn.hutool.core.text.csv; +import cn.hutool.core.bean.BeanUtil; + import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; @@ -74,7 +76,7 @@ public final class CsvRow implements List { /** * 获取标题与字段值对应的Map * - * @return an unmodifiable map of header names and field values of this row + * @return 标题与字段值对应的Map * @throws IllegalStateException CSV文件无标题行抛出此异常 */ public Map getFieldMap() { @@ -82,7 +84,7 @@ public final class CsvRow implements List { throw new IllegalStateException("No header available"); } - final Map fieldMap = new LinkedHashMap<>(headerMap.size()); + final Map fieldMap = new LinkedHashMap<>(headerMap.size(), 1); String key; Integer col; String val; @@ -96,6 +98,18 @@ public final class CsvRow implements List { return fieldMap; } + /** + * 一行数据转换为Bean对象 + * + * @param Bean类型 + * @param clazz bean类 + * @return Bean + * @since 5.3.6 + */ + public T toBean(Class clazz){ + return BeanUtil.mapToBean(getFieldMap(), clazz, true); + } + /** * 获取字段格式 * diff --git a/hutool-core/src/main/java/cn/hutool/core/text/escape/NumericEntityUnescaper.java b/hutool-core/src/main/java/cn/hutool/core/text/escape/NumericEntityUnescaper.java index fa5e82780..b9d274241 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/escape/NumericEntityUnescaper.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/escape/NumericEntityUnescaper.java @@ -48,7 +48,7 @@ public class NumericEntityUnescaper extends StrReplacer { return 0; } out.append((char)entityValue); - return 2 + end - start + (isHex ? 1 : 0) + (isSemiNext ? 1 : 0); + return 2 + end - start + (isHex ? 1 : 0) + 1; } } return 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/text/replacer/ReplacerChain.java b/hutool-core/src/main/java/cn/hutool/core/text/replacer/ReplacerChain.java index 9cac7ee26..4729948ac 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/replacer/ReplacerChain.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/replacer/ReplacerChain.java @@ -1,12 +1,12 @@ package cn.hutool.core.text.replacer; +import cn.hutool.core.lang.Chain; +import cn.hutool.core.text.StrBuilder; + import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import cn.hutool.core.lang.Chain; -import cn.hutool.core.text.StrBuilder; - /** * 字符串替换链,用于组合多个字符串替换逻辑 * @@ -16,7 +16,7 @@ import cn.hutool.core.text.StrBuilder; public class ReplacerChain extends StrReplacer implements Chain { private static final long serialVersionUID = 1L; - private List replacers = new LinkedList<>(); + private final List replacers = new LinkedList<>(); /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ConcurrencyTester.java b/hutool-core/src/main/java/cn/hutool/core/thread/ConcurrencyTester.java index 1673054be..20b42f4ee 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/ConcurrencyTester.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ConcurrencyTester.java @@ -17,8 +17,8 @@ import cn.hutool.core.date.TimeInterval; * @author kwer */ public class ConcurrencyTester { - private SyncFinisher sf; - private TimeInterval timeInterval; + private final SyncFinisher sf; + private final TimeInterval timeInterval; private long interval; public ConcurrencyTester(int threadSize) { diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/RejectPolicy.java b/hutool-core/src/main/java/cn/hutool/core/thread/RejectPolicy.java index 0babcb018..c95545985 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/RejectPolicy.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/RejectPolicy.java @@ -23,7 +23,7 @@ public enum RejectPolicy { /** 由主线程来直接执行 */ CALLER_RUNS(new ThreadPoolExecutor.CallerRunsPolicy()); - private RejectedExecutionHandler value; + private final RejectedExecutionHandler value; RejectPolicy(RejectedExecutionHandler handler) { this.value = handler; diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java b/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java index 9cdcfc304..c81fd06da 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java @@ -16,9 +16,9 @@ import java.util.concurrent.Semaphore; public class SemaphoreRunnable implements Runnable { /** 实际执行的逻辑 */ - private Runnable runnable; + private final Runnable runnable; /** 信号量 */ - private Semaphore semaphore; + private final Semaphore semaphore; /** * 构造 @@ -31,16 +31,28 @@ public class SemaphoreRunnable implements Runnable { this.semaphore = semaphore; } + /** + * 获得信号量 + * + * @return {@link Semaphore} + * @since 5.3.6 + */ + public Semaphore getSemaphore(){ + return this.semaphore; + } + @Override public void run() { if (null != this.semaphore) { - try { + try{ semaphore.acquire(); - this.runnable.run(); - } catch (InterruptedException e) { + try { + this.runnable.run(); + } finally { + semaphore.release(); + } + }catch (InterruptedException e) { Thread.currentThread().interrupt(); - } finally { - semaphore.release(); } } } diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java b/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java index e4127f48f..8555a7dbc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java @@ -1,13 +1,13 @@ package cn.hutool.core.thread; +import cn.hutool.core.exceptions.NotInitedException; +import cn.hutool.core.exceptions.UtilException; + import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; -import cn.hutool.core.exceptions.NotInitedException; -import cn.hutool.core.exceptions.UtilException; - /** * 线程同步结束器
    * 在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 @@ -27,13 +27,13 @@ import cn.hutool.core.exceptions.UtilException; */ public class SyncFinisher { - private Set workers; - private int threadSize; - private ExecutorService executorService; + private final Set workers; + private final int threadSize; + private final ExecutorService executorService; private boolean isBeginAtSameTime; /** 启动同步器,用于保证所有worker线程同时开始 */ - private CountDownLatch beginLatch; + private final CountDownLatch beginLatch; /** 结束同步器,用于等待所有worker线程同时结束 */ private CountDownLatch endLatch; diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java index 379e4ff8d..1093d9bcd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java @@ -226,11 +226,23 @@ public class ThreadUtil { if (millis == null) { return true; } + return sleep(millis.longValue()); + } - try { - Thread.sleep(millis.longValue()); - } catch (InterruptedException e) { - return false; + /** + * 挂起当前线程 + * + * @param millis 挂起的毫秒数 + * @return 被中断返回false,否则true + * @since 5.3.2 + */ + public static boolean sleep(long millis) { + if (millis > 0) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + return false; + } } return true; } @@ -243,15 +255,36 @@ public class ThreadUtil { * @see ThreadUtil#sleep(Number) */ public static boolean safeSleep(Number millis) { - long millisLong = millis.longValue(); + if (millis == null) { + return true; + } + + return safeSleep(millis.longValue()); + } + + /** + * 考虑{@link Thread#sleep(long)}方法有可能时间不足给定毫秒数,此方法保证sleep时间不小于给定的毫秒数 + * + * @param millis 给定的sleep时间 + * @return 被中断返回false,否则true + * @see ThreadUtil#sleep(Number) + * @since 5.3.2 + */ + public static boolean safeSleep(long millis) { long done = 0; - while (done < millisLong) { - long before = System.currentTimeMillis(); - if (false == sleep(millisLong - done)) { + long before; + long spendTime; + while (done >= 0 && done < millis) { + before = System.currentTimeMillis(); + if (false == sleep(millis - done)) { return false; } - long after = System.currentTimeMillis(); - done += (after - before); + spendTime = System.currentTimeMillis() - before; + if (spendTime <= 0) { + // Sleep花费时间为0或者负数,说明系统时间被拨动 + break; + } + done += spendTime; } return true; } @@ -318,6 +351,13 @@ public class ThreadUtil { } } + /** + * 等待当前线程结束. 调用 {@link Thread#join()} 并忽略 {@link InterruptedException} + */ + public static void waitForDie() { + waitForDie(Thread.currentThread()); + } + /** * 等待线程结束. 调用 {@link Thread#join()} 并忽略 {@link InterruptedException} * diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java new file mode 100644 index 000000000..12191a31f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java @@ -0,0 +1,43 @@ +package cn.hutool.core.thread.lock; + +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.StampedLock; + +/** + * 锁相关工具 + * + * @author looly + * @since 5.2.5 + */ +public class LockUtil { + + private static final NoLock NO_LOCK = new NoLock(); + + /** + * 创建{@link StampedLock}锁 + * + * @return {@link StampedLock}锁 + */ + public static StampedLock createStampLock() { + return new StampedLock(); + } + + /** + * 创建{@link ReentrantReadWriteLock}锁 + * + * @param fair 是否公平锁 + * @return {@link ReentrantReadWriteLock}锁 + */ + public static ReentrantReadWriteLock createReadWriteLock(boolean fair) { + return new ReentrantReadWriteLock(fair); + } + + /** + * 获取单例的无锁对象 + * + * @return {@link NoLock} + */ + public static NoLock getNoLock(){ + return NO_LOCK; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java index 12032abb0..7f67b73cc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java @@ -17,7 +17,7 @@ public class NoLock implements Lock{ } @Override - public void lockInterruptibly() throws InterruptedException { + public void lockInterruptibly() { } @Override @@ -25,8 +25,9 @@ public class NoLock implements Lock{ return true; } + @SuppressWarnings("NullableProblems") @Override - public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + public boolean tryLock(long time, TimeUnit unit) { return true; } @@ -34,9 +35,10 @@ public class NoLock implements Lock{ public void unlock() { } + @SuppressWarnings("NullableProblems") @Override public Condition newCondition() { - return null; + throw new UnsupportedOperationException("NoLock`s newCondition method is unsupported"); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 67cf26783..57422dd04 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -3,6 +3,7 @@ package cn.hutool.core.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Editor; import cn.hutool.core.lang.Filter; @@ -13,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; @@ -838,6 +840,27 @@ public class ArrayUtil { return list.toArray(Arrays.copyOf(array, list.size())); } + /** + * 编辑数组
    + * 编辑过程通过传入的Editor实现来返回需要的元素内容,这个Editor实现可以实现以下功能: + * + *

    +	 * 1、修改元素对象,返回集合中为修改后的对象
    +	 * 
    + * + * 注意:此方法会修改原数组! + * + * @param 数组元素类型 + * @param array 数组 + * @param editor 编辑器接口 + * @since 5.3.3 + */ + public static void edit(T[] array, Editor editor) { + for(int i = 0; i < array.length; i++){ + array[i] = editor.edit(array[i]); + } + } + /** * 过滤
    * 过滤过程通过传入的Filter实现来过滤返回需要的元素内容,这个Filter实现可以实现以下功能: @@ -2402,7 +2425,7 @@ public class ArrayUtil { if (ArrayUtil.isArray(item)) { sb.append(join(ArrayUtil.wrap(item), conjunction, prefix, suffix)); } else if (item instanceof Iterable) { - sb.append(IterUtil.join((Iterable) item, conjunction, prefix, suffix)); + sb.append(CollUtil.join((Iterable) item, conjunction, prefix, suffix)); } else if (item instanceof Iterator) { sb.append(IterUtil.join((Iterator) item, conjunction, prefix, suffix)); } else { @@ -2412,6 +2435,39 @@ public class ArrayUtil { return sb.toString(); } + /** + * 以 conjunction 为分隔符将数组转换为字符串 + * + * @param 被处理的集合 + * @param array 数组 + * @param conjunction 分隔符 + * @param editor 每个元素的编辑器,null表示不编辑 + * @return 连接后的字符串 + * @since 5.3.3 + */ + public static String join(T[] array, CharSequence conjunction, Editor editor) { + if (null == array) { + return null; + } + + final StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + for (T item : array) { + if (isFirst) { + isFirst = false; + } else { + sb.append(conjunction); + } + if(null != editor){ + item = editor.edit(item); + } + if(null != item){ + sb.append(StrUtil.toString(item)); + } + } + return sb.toString(); + } + /** * 以 conjunction 为分隔符将数组转换为字符串 * @@ -3339,19 +3395,32 @@ public class ArrayUtil { // ------------------------------------------------------------------------------------------------------------ min and max /** * 取最小值 - * + * * @param 元素类型 * @param numberArray 数字数组 * @return 最小值 * @since 3.0.9 */ public static > T min(T[] numberArray) { + return min(numberArray, null); + } + + /** + * 取最小值 + * + * @param 元素类型 + * @param numberArray 数字数组 + * @param comparator 比较器,null按照默认比较 + * @return 最小值 + * @since 5.3.4 + */ + public static > T min(T[] numberArray, Comparator comparator) { if (isEmpty(numberArray)) { throw new IllegalArgumentException("Number array must not empty !"); } T min = numberArray[0]; for (T t : numberArray) { - if (ObjectUtil.compare(min, t) > 0) { + if (CompareUtil.compare(min, t, comparator) > 0) { min = t; } } @@ -3500,19 +3569,32 @@ public class ArrayUtil { /** * 取最大值 - * + * * @param 元素类型 * @param numberArray 数字数组 * @return 最大值 * @since 3.0.9 */ public static > T max(T[] numberArray) { + return max(numberArray, null); + } + + /** + * 取最大值 + * + * @param 元素类型 + * @param numberArray 数字数组 + * @param comparator 比较器,null表示默认比较器 + * @return 最大值 + * @since 5.3.4 + */ + public static > T max(T[] numberArray, Comparator comparator) { if (isEmpty(numberArray)) { throw new IllegalArgumentException("Number array must not empty !"); } T max = numberArray[0]; for (int i = 1; i < numberArray.length; i++) { - if (ObjectUtil.compare(max, numberArray[i]) < 0) { + if (CompareUtil.compare(max, numberArray[i], comparator) < 0) { max = numberArray[i]; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/BooleanUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/BooleanUtil.java index 6a0f84ad9..8cba53eb2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/BooleanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/BooleanUtil.java @@ -11,7 +11,7 @@ import cn.hutool.core.convert.Convert; public class BooleanUtil { /** 表示为真的字符串 */ - private static final String[] TRUE_ARRAY = { "true", "yes", "y", "t", "ok", "1", "on", "是", "对", "真", }; + private static final String[] TRUE_ARRAY = { "true", "yes", "y", "t", "ok", "1", "on", "是", "对", "真", "對", "√"}; /** * 取相反值 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java index 4610c0877..f46e87e93 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java @@ -332,4 +332,15 @@ public class CharUtil { public static int getType(int c) { return Character.getType(c); } + + /** + * 获取给定字符的16进制数值 + * + * @param b 字符 + * @return 16进制字符 + * @since 5.3.1 + */ + public static int digit16(int b) { + return Character.digit(b, 16); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java index 5d21c0b05..c6478af57 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java @@ -1,65 +1,113 @@ package cn.hutool.core.util; +import cn.hutool.core.io.FileUtil; + import java.io.File; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; -import cn.hutool.core.io.FileUtil; - /** * 字符集工具类 - * @author xiaoleilu * + * @author xiaoleilu */ public class CharsetUtil { - - /** ISO-8859-1 */ + + /** + * ISO-8859-1 + */ public static final String ISO_8859_1 = "ISO-8859-1"; - /** UTF-8 */ + /** + * UTF-8 + */ public static final String UTF_8 = "UTF-8"; - /** GBK */ + /** + * GBK + */ public static final String GBK = "GBK"; - - /** ISO-8859-1 */ + + /** + * ISO-8859-1 + */ public static final Charset CHARSET_ISO_8859_1 = StandardCharsets.ISO_8859_1; - /** UTF-8 */ + /** + * UTF-8 + */ public static final Charset CHARSET_UTF_8 = StandardCharsets.UTF_8; - /** GBK */ + /** + * GBK + */ public static final Charset CHARSET_GBK; - static{ + static { //避免不支持GBK的系统中运行报错 issue#731 Charset _CHARSET_GBK = null; - try{ + try { _CHARSET_GBK = Charset.forName(GBK); - } catch (UnsupportedCharsetException e){ + } catch (UnsupportedCharsetException e) { //ignore } CHARSET_GBK = _CHARSET_GBK; } - + /** * 转换为Charset对象 + * * @param charsetName 字符集,为空则返回默认字符集 * @return Charset * @throws UnsupportedCharsetException 编码不支持 */ - public static Charset charset(String charsetName) throws UnsupportedCharsetException{ + public static Charset charset(String charsetName) throws UnsupportedCharsetException { return StrUtil.isBlank(charsetName) ? Charset.defaultCharset() : Charset.forName(charsetName); } - + + /** + * 解析字符串编码为Charset对象,解析失败返回系统默认编码 + * + * @param charsetName 字符集,为空则返回默认字符集 + * @return Charset + * @since 5.2.6 + */ + public static Charset parse(String charsetName) { + return parse(charsetName, Charset.defaultCharset()); + } + + /** + * 解析字符串编码为Charset对象,解析失败返回默认编码 + * + * @param charsetName 字符集,为空则返回默认字符集 + * @param defaultCharset 解析失败使用的默认编码 + * @return Charset + * @since 5.2.6 + */ + public static Charset parse(String charsetName, Charset defaultCharset) { + if (StrUtil.isBlank(charsetName)) { + return defaultCharset; + } + + Charset result; + try { + result = Charset.forName(charsetName); + } catch (UnsupportedCharsetException e) { + result = defaultCharset; + } + + return result; + } + /** * 转换字符串的字符集编码 - * @param source 字符串 - * @param srcCharset 源字符集,默认ISO-8859-1 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 * @param destCharset 目标字符集,默认UTF-8 * @return 转换后的字符集 */ public static String convert(String source, String srcCharset, String destCharset) { return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); } - + /** * 转换字符串的字符集编码
    * 当以错误的编码读取为字符串时,打印字符串将出现乱码。
    @@ -69,33 +117,33 @@ public class CharsetUtil { * 客户端 -》 GBK编码 -》 Servlet容器 -》 UTF-8解码 -》 乱码 * 乱码 -》 UTF-8编码 -》 GBK解码 -》 正确内容 * - * - * @param source 字符串 - * @param srcCharset 源字符集,默认ISO-8859-1 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 * @param destCharset 目标字符集,默认UTF-8 * @return 转换后的字符集 */ public static String convert(String source, Charset srcCharset, Charset destCharset) { - if(null == srcCharset) { + if (null == srcCharset) { srcCharset = StandardCharsets.ISO_8859_1; } - - if(null == destCharset) { + + if (null == destCharset) { destCharset = StandardCharsets.UTF_8; } - + if (StrUtil.isBlank(source) || srcCharset.equals(destCharset)) { return source; } return new String(source.getBytes(srcCharset), destCharset); } - + /** * 转换文件编码
    * 此方法用于转换文件编码,读取的文件实际编码必须与指定的srcCharset编码一致,否则导致乱码 - * - * @param file 文件 - * @param srcCharset 原文件的编码,必须与文件内容的编码保持一致 + * + * @param file 文件 + * @param srcCharset 原文件的编码,必须与文件内容的编码保持一致 * @param destCharset 转码后的编码 * @return 被转换编码的文件 * @since 3.1.0 @@ -104,41 +152,41 @@ public class CharsetUtil { final String str = FileUtil.readString(file, srcCharset); return FileUtil.writeString(str, file, destCharset); } - + /** * 系统字符集编码,如果是Windows,则默认为GBK编码,否则取 {@link CharsetUtil#defaultCharsetName()} - * - * @see CharsetUtil#defaultCharsetName() + * * @return 系统字符集编码 + * @see CharsetUtil#defaultCharsetName() * @since 3.1.2 */ public static String systemCharsetName() { return systemCharset().name(); } - + /** * 系统字符集编码,如果是Windows,则默认为GBK编码,否则取 {@link CharsetUtil#defaultCharsetName()} - * - * @see CharsetUtil#defaultCharsetName() + * * @return 系统字符集编码 + * @see CharsetUtil#defaultCharsetName() * @since 3.1.2 */ public static Charset systemCharset() { return FileUtil.isWindows() ? CHARSET_GBK : defaultCharset(); } - + /** * 系统默认字符集编码 - * + * * @return 系统字符集编码 */ public static String defaultCharsetName() { return defaultCharset().name(); } - + /** * 系统默认字符集编码 - * + * * @return 系统字符集编码 */ public static Charset defaultCharset() { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java index b7bca158f..bdb76fb32 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java @@ -7,23 +7,24 @@ import cn.hutool.core.lang.UUID; /** * ID生成器工具类,此工具类中主要封装: - * + * *
      * 1. 唯一性ID生成器:UUID、ObjectId(MongoDB)、Snowflake
      * 
    - * + * *

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

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

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

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

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

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

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

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

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

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

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

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

    * 参考:http://www.cnblogs.com/relucent/p/4955340.html - * - * @param workerId 终端ID + * + * @param workerId 终端ID * @param datacenterId 数据中心ID * @return {@link Snowflake} * @since 4.5.9 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java index e4fd48a7e..e2f4ad944 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java @@ -41,91 +41,91 @@ public class IdcardUtil { /** * 省市代码表 */ - private static Map cityCodes = new HashMap<>(); + private static final Map CITY_CODES = new HashMap<>(); /** * 台湾身份首字母对应数字 */ - private static Map twFirstCode = new HashMap<>(); + private static final Map TW_FIRST_CODE = new HashMap<>(); /** * 香港身份首字母对应数字 */ - private static Map hkFirstCode = new HashMap<>(); + private static final Map HK_FIRST_CODE = new HashMap<>(); static { - cityCodes.put("11", "北京"); - cityCodes.put("12", "天津"); - cityCodes.put("13", "河北"); - cityCodes.put("14", "山西"); - cityCodes.put("15", "内蒙古"); - cityCodes.put("21", "辽宁"); - cityCodes.put("22", "吉林"); - cityCodes.put("23", "黑龙江"); - cityCodes.put("31", "上海"); - cityCodes.put("32", "江苏"); - cityCodes.put("33", "浙江"); - cityCodes.put("34", "安徽"); - cityCodes.put("35", "福建"); - cityCodes.put("36", "江西"); - cityCodes.put("37", "山东"); - cityCodes.put("41", "河南"); - cityCodes.put("42", "湖北"); - cityCodes.put("43", "湖南"); - cityCodes.put("44", "广东"); - cityCodes.put("45", "广西"); - cityCodes.put("46", "海南"); - cityCodes.put("50", "重庆"); - cityCodes.put("51", "四川"); - cityCodes.put("52", "贵州"); - cityCodes.put("53", "云南"); - cityCodes.put("54", "西藏"); - cityCodes.put("61", "陕西"); - cityCodes.put("62", "甘肃"); - cityCodes.put("63", "青海"); - cityCodes.put("64", "宁夏"); - cityCodes.put("65", "新疆"); - cityCodes.put("71", "台湾"); - cityCodes.put("81", "香港"); - cityCodes.put("82", "澳门"); - cityCodes.put("91", "国外"); + CITY_CODES.put("11", "北京"); + CITY_CODES.put("12", "天津"); + CITY_CODES.put("13", "河北"); + CITY_CODES.put("14", "山西"); + CITY_CODES.put("15", "内蒙古"); + CITY_CODES.put("21", "辽宁"); + CITY_CODES.put("22", "吉林"); + CITY_CODES.put("23", "黑龙江"); + CITY_CODES.put("31", "上海"); + CITY_CODES.put("32", "江苏"); + CITY_CODES.put("33", "浙江"); + CITY_CODES.put("34", "安徽"); + CITY_CODES.put("35", "福建"); + CITY_CODES.put("36", "江西"); + CITY_CODES.put("37", "山东"); + CITY_CODES.put("41", "河南"); + CITY_CODES.put("42", "湖北"); + CITY_CODES.put("43", "湖南"); + CITY_CODES.put("44", "广东"); + CITY_CODES.put("45", "广西"); + CITY_CODES.put("46", "海南"); + CITY_CODES.put("50", "重庆"); + CITY_CODES.put("51", "四川"); + CITY_CODES.put("52", "贵州"); + CITY_CODES.put("53", "云南"); + CITY_CODES.put("54", "西藏"); + CITY_CODES.put("61", "陕西"); + CITY_CODES.put("62", "甘肃"); + CITY_CODES.put("63", "青海"); + CITY_CODES.put("64", "宁夏"); + CITY_CODES.put("65", "新疆"); + CITY_CODES.put("71", "台湾"); + CITY_CODES.put("81", "香港"); + CITY_CODES.put("82", "澳门"); + CITY_CODES.put("91", "国外"); - twFirstCode.put("A", 10); - twFirstCode.put("B", 11); - twFirstCode.put("C", 12); - twFirstCode.put("D", 13); - twFirstCode.put("E", 14); - twFirstCode.put("F", 15); - twFirstCode.put("G", 16); - twFirstCode.put("H", 17); - twFirstCode.put("J", 18); - twFirstCode.put("K", 19); - twFirstCode.put("L", 20); - twFirstCode.put("M", 21); - twFirstCode.put("N", 22); - twFirstCode.put("P", 23); - twFirstCode.put("Q", 24); - twFirstCode.put("R", 25); - twFirstCode.put("S", 26); - twFirstCode.put("T", 27); - twFirstCode.put("U", 28); - twFirstCode.put("V", 29); - twFirstCode.put("X", 30); - twFirstCode.put("Y", 31); - twFirstCode.put("W", 32); - twFirstCode.put("Z", 33); - twFirstCode.put("I", 34); - twFirstCode.put("O", 35); + TW_FIRST_CODE.put("A", 10); + TW_FIRST_CODE.put("B", 11); + TW_FIRST_CODE.put("C", 12); + TW_FIRST_CODE.put("D", 13); + TW_FIRST_CODE.put("E", 14); + TW_FIRST_CODE.put("F", 15); + TW_FIRST_CODE.put("G", 16); + TW_FIRST_CODE.put("H", 17); + TW_FIRST_CODE.put("J", 18); + TW_FIRST_CODE.put("K", 19); + TW_FIRST_CODE.put("L", 20); + TW_FIRST_CODE.put("M", 21); + TW_FIRST_CODE.put("N", 22); + TW_FIRST_CODE.put("P", 23); + TW_FIRST_CODE.put("Q", 24); + TW_FIRST_CODE.put("R", 25); + TW_FIRST_CODE.put("S", 26); + TW_FIRST_CODE.put("T", 27); + TW_FIRST_CODE.put("U", 28); + TW_FIRST_CODE.put("V", 29); + TW_FIRST_CODE.put("X", 30); + TW_FIRST_CODE.put("Y", 31); + TW_FIRST_CODE.put("W", 32); + TW_FIRST_CODE.put("Z", 33); + TW_FIRST_CODE.put("I", 34); + TW_FIRST_CODE.put("O", 35); //来自http://shenfenzheng.bajiu.cn/?rid=40 - hkFirstCode.put("A", 1);// 持证人拥有香港居留权 - hkFirstCode.put("B", 2);// 持证人所报称的出生日期或地点自首次登记以后,曾作出更改 - hkFirstCode.put("C", 3);// 持证人登记领证时在香港的居留受到入境事务处处长的限制 - hkFirstCode.put("N", 14);// 持证人所报的姓名自首次登记以后,曾作出更改 - hkFirstCode.put("O", 15);// 持证人报称在香港、澳门及中国以外其他地区或国家出生 - hkFirstCode.put("R", 18);// 持证人拥有香港入境权 - hkFirstCode.put("U", 21);// 持证人登记领证时在香港的居留不受入境事务处处长的限制 - hkFirstCode.put("W", 23);// 持证人报称在澳门地区出生 - hkFirstCode.put("X", 24);// 持证人报称在中国大陆出生 - hkFirstCode.put("Z", 26);// 持证人报称在香港出生 + HK_FIRST_CODE.put("A", 1);// 持证人拥有香港居留权 + HK_FIRST_CODE.put("B", 2);// 持证人所报称的出生日期或地点自首次登记以后,曾作出更改 + HK_FIRST_CODE.put("C", 3);// 持证人登记领证时在香港的居留受到入境事务处处长的限制 + HK_FIRST_CODE.put("N", 14);// 持证人所报的姓名自首次登记以后,曾作出更改 + HK_FIRST_CODE.put("O", 15);// 持证人报称在香港、澳门及中国以外其他地区或国家出生 + HK_FIRST_CODE.put("R", 18);// 持证人拥有香港入境权 + HK_FIRST_CODE.put("U", 21);// 持证人登记领证时在香港的居留不受入境事务处处长的限制 + HK_FIRST_CODE.put("W", 23);// 持证人报称在澳门地区出生 + HK_FIRST_CODE.put("X", 24);// 持证人报称在中国大陆出生 + HK_FIRST_CODE.put("Z", 26);// 持证人报称在香港出生 } /** @@ -218,6 +218,12 @@ public class IdcardUtil { return false; } + // 省份 + final String proCode = idCard.substring(0, 2); + if (null == CITY_CODES.get(proCode)) { + return false; + } + //校验生日 if (false == Validator.isBirthday(idCard.substring(6, 14))) { return false; @@ -248,7 +254,7 @@ public class IdcardUtil { if (ReUtil.isMatch(PatternPool.NUMBERS, idCard)) { // 省份 String proCode = idCard.substring(0, 2); - if (null == cityCodes.get(proCode)) { + if (null == CITY_CODES.get(proCode)) { return false; } @@ -314,7 +320,7 @@ public class IdcardUtil { return false; } String start = idCard.substring(0, 1); - Integer iStart = twFirstCode.get(start); + Integer iStart = TW_FIRST_CODE.get(start); if (null == iStart) { return false; } @@ -352,7 +358,7 @@ public class IdcardUtil { sum = 522 + (Character.toUpperCase(card.charAt(0)) - 55) * 8; } String start = idCard.substring(0, 1); - Integer iStart = hkFirstCode.get(start); + Integer iStart = HK_FIRST_CODE.get(start); if (null == iStart) { return false; } @@ -512,7 +518,7 @@ public class IdcardUtil { int len = idCard.length(); if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH) { String sProvinNum = idCard.substring(0, 2); - return cityCodes.get(sProvinNum); + return CITY_CODES.get(sProvinNum); } return null; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java index 26c03a29c..80c81addf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java @@ -45,7 +45,7 @@ public class ModifierUtil { STRICT(Modifier.STRICT); /** 修饰符枚举对应的int修饰符值 */ - private int value; + private final int value; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java index 69085986d..6b2a8261a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java @@ -397,18 +397,8 @@ public class ObjectUtil { if (false == (obj instanceof Serializable)) { return null; } - - FastByteArrayOutputStream byteOut = new FastByteArrayOutputStream(); - ObjectOutputStream oos = null; - try { - oos = new ObjectOutputStream(byteOut); - oos.writeObject(obj); - oos.flush(); - } catch (Exception e) { - throw new UtilException(e); - } finally { - IoUtil.close(oos); - } + final FastByteArrayOutputStream byteOut = new FastByteArrayOutputStream(); + IoUtil.writeObjects(byteOut, false, (Serializable) obj); return byteOut.toByteArray(); } @@ -416,20 +406,16 @@ public class ObjectUtil { * 反序列化
    * 对象必须实现Serializable接口 * + *

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

    + * * @param 对象类型 * @param bytes 反序列化的字节码 * @return 反序列化后的对象 */ - @SuppressWarnings("unchecked") public static T deserialize(byte[] bytes) { - ObjectInputStream ois; - try { - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - ois = new ObjectInputStream(bais); - return (T) ois.readObject(); - } catch (Exception e) { - throw new UtilException(e); - } + return IoUtil.readObj(new ByteArrayInputStream(bytes)); } /** @@ -569,7 +555,7 @@ public class ObjectUtil { } /** - * 是否存都为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 + * 是否全都为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 * * @param objs 被检查的对象,一个或者多个 * @return 是否都为空 @@ -579,7 +565,7 @@ public class ObjectUtil { } /** - * 是否存都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 + * 是否全都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 * * @param objs 被检查的对象,一个或者多个 * @return 是否都不为空 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java index f15665bf7..e2b47675a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java @@ -1,5 +1,14 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.lang.UUID; +import cn.hutool.core.lang.WeightRandom; +import cn.hutool.core.lang.WeightRandom.WeightObj; + import java.awt.Color; import java.math.BigDecimal; import java.math.RoundingMode; @@ -14,15 +23,6 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateField; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.lang.UUID; -import cn.hutool.core.lang.WeightRandom; -import cn.hutool.core.lang.WeightRandom.WeightObj; - /** * 随机工具类 * @@ -487,7 +487,7 @@ public class RandomUtil { * @return 随机数字字符 * @since 3.1.2 */ - public static int randomNumber() { + public static char randomNumber() { return randomChar(BASE_NUMBER); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index f805ff586..222eb936e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java @@ -13,6 +13,7 @@ import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -149,7 +150,7 @@ public class ReflectUtil { final Field[] fields = getFields(beanClass); if (ArrayUtil.isNotEmpty(fields)) { for (Field field : fields) { - if ((name.equals(field.getName()))) { + if ((name.equals(getFieldName(field)))) { return field; } } @@ -769,13 +770,32 @@ public class ReflectUtil { /** * 尝试遍历并调用此类的所有构造方法,直到构造成功并返回 + *

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

    +	 *     Map       -》 HashMap
    +	 *     Collction -》 ArrayList
    +	 *     List      -》 ArrayList
    +	 *     Set       -》 HashSet
    +	 * 
    * * @param 对象类型 * @param beanClass 被构造的类 * @return 构造后的对象 */ + @SuppressWarnings("unchecked") public static T newInstanceIfPossible(Class beanClass) { Assert.notNull(beanClass); + + // 某些特殊接口的实例化按照默认实现进行 + if (beanClass.isAssignableFrom(AbstractMap.class)) { + beanClass = (Class) HashMap.class; + } else if (beanClass.isAssignableFrom(List.class)) { + beanClass = (Class) ArrayList.class; + } else if (beanClass.isAssignableFrom(Set.class)) { + beanClass = (Class) HashSet.class; + } + try { return newInstance(beanClass); } catch (Exception e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index a612534a1..f4d4f4c16 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -1,5 +1,8 @@ package cn.hutool.core.util; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -7,12 +10,9 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; - /** * 系统运行时工具类,用于执行系统命令的工具 - * + * * @author Looly * @since 3.1.1 */ @@ -20,7 +20,7 @@ public class RuntimeUtil { /** * 执行系统命令,使用系统默认编码 - * + * * @param cmds 命令列表,每个元素代表一条命令 * @return 执行结果 * @throws IORuntimeException IO异常 @@ -31,9 +31,9 @@ public class RuntimeUtil { /** * 执行系统命令,使用系统默认编码 - * + * * @param charset 编码 - * @param cmds 命令列表,每个元素代表一条命令 + * @param cmds 命令列表,每个元素代表一条命令 * @return 执行结果 * @throws IORuntimeException IO异常 * @since 3.1.2 @@ -44,7 +44,7 @@ public class RuntimeUtil { /** * 执行系统命令,使用系统默认编码 - * + * * @param cmds 命令列表,每个元素代表一条命令 * @return 执行结果,按行区分 * @throws IORuntimeException IO异常 @@ -55,9 +55,9 @@ public class RuntimeUtil { /** * 执行系统命令,使用系统默认编码 - * + * * @param charset 编码 - * @param cmds 命令列表,每个元素代表一条命令 + * @param cmds 命令列表,每个元素代表一条命令 * @return 执行结果,按行区分 * @throws IORuntimeException IO异常 * @since 3.1.2 @@ -69,7 +69,7 @@ public class RuntimeUtil { /** * 执行命令
    * 命令带参数时参数可作为其中一个参数,也可以将命令和参数组合为一个字符串传入 - * + * * @param cmds 命令 * @return {@link Process} */ @@ -99,7 +99,7 @@ public class RuntimeUtil { /** * 执行命令
    * 命令带参数时参数可作为其中一个参数,也可以将命令和参数组合为一个字符串传入 - * + * * @param envp 环境变量参数,传入形式为key=value,null表示继承系统环境变量 * @param cmds 命令 * @return {@link Process} @@ -112,9 +112,9 @@ public class RuntimeUtil { /** * 执行命令
    * 命令带参数时参数可作为其中一个参数,也可以将命令和参数组合为一个字符串传入 - * + * * @param envp 环境变量参数,传入形式为key=value,null表示继承系统环境变量 - * @param dir 执行命令所在目录(用于相对路径命令执行),null表示使用当前进程执行的目录 + * @param dir 执行命令所在目录(用于相对路径命令执行),null表示使用当前进程执行的目录 * @param cmds 命令 * @return {@link Process} * @since 4.1.6 @@ -140,9 +140,10 @@ public class RuntimeUtil { } // -------------------------------------------------------------------------------------------------- result + /** * 获取命令执行结果,使用系统默认编码,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @return 命令执行结果列表 */ @@ -152,7 +153,7 @@ public class RuntimeUtil { /** * 获取命令执行结果,使用系统默认编码,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @param charset 编码 * @return 命令执行结果列表 @@ -162,7 +163,7 @@ public class RuntimeUtil { InputStream in = null; try { in = process.getInputStream(); - return IoUtil.readLines(in, charset, new ArrayList()); + return IoUtil.readLines(in, charset, new ArrayList<>()); } finally { IoUtil.close(in); destroy(process); @@ -171,7 +172,7 @@ public class RuntimeUtil { /** * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @return 命令执行结果列表 * @since 3.1.2 @@ -182,7 +183,7 @@ public class RuntimeUtil { /** * 获取命令执行结果,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @param charset 编码 * @return 命令执行结果列表 @@ -201,7 +202,7 @@ public class RuntimeUtil { /** * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @return 命令执行结果列表 * @since 4.1.21 @@ -212,7 +213,7 @@ public class RuntimeUtil { /** * 获取命令执行异常结果,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @param charset 编码 * @return 命令执行结果列表 @@ -231,7 +232,7 @@ public class RuntimeUtil { /** * 销毁进程 - * + * * @param process 进程 * @since 3.1.2 */ @@ -243,11 +244,61 @@ public class RuntimeUtil { /** * 增加一个JVM关闭后的钩子,用于在JVM关闭时执行某些操作 - * + * * @param hook 钩子 * @since 4.0.5 */ public static void addShutdownHook(Runnable hook) { Runtime.getRuntime().addShutdownHook((hook instanceof Thread) ? (Thread) hook : new Thread(hook)); } + + /** + * 获得JVM可用的处理器数量(一般为CPU核心数) + * + * @return 可用的处理器数量 + * @since 5.3.0 + */ + public static int getProcessorCount() { + return Runtime.getRuntime().availableProcessors(); + } + + /** + * 获得JVM中剩余的内存数,单位byte + * + * @return JVM中剩余的内存数,单位byte + * @since 5.3.0 + */ + public static long getFreeMemory() { + return Runtime.getRuntime().freeMemory(); + } + + /** + * 获得JVM已经从系统中获取到的总共的内存数,单位byte + * + * @return JVM中剩余的内存数,单位byte + * @since 5.3.0 + */ + public static long getTotalMemory() { + return Runtime.getRuntime().totalMemory(); + } + + /** + * 获得JVM中可以从系统中获取的最大的内存数,单位byte,以-Xmx参数为准 + * + * @return JVM中剩余的内存数,单位byte + * @since 5.3.0 + */ + public static long getMaxMemory() { + return Runtime.getRuntime().maxMemory(); + } + + /** + * 获得JVM最大可用内存,计算方法为:
    + * 最大内存-总内存+剩余内存 + * + * @return 最大可用内存 + */ + public final long getUsableMemory() { + return getMaxMemory() - getTotalMemory() + getFreeMemory(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index 67462516e..9b11156d6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -1,14 +1,5 @@ package cn.hutool.core.util; -import java.io.StringReader; -import java.io.StringWriter; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.text.MessageFormat; -import java.util.*; -import java.util.Map.Entry; -import java.util.regex.Pattern; - import cn.hutool.core.comparator.VersionComparator; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Assert; @@ -19,11 +10,24 @@ import cn.hutool.core.text.StrFormatter; import cn.hutool.core.text.StrSpliter; import cn.hutool.core.text.TextSimilarity; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Pattern; + /** * 字符串工具类 - * - * @author xiaoleilu * + * @author xiaoleilu */ public class StrUtil { @@ -74,12 +78,13 @@ public class StrUtil { public static final String EMPTY_JSON = "{}"; // ------------------------------------------------------------------------ Blank + /** * 字符串是否为空白 空白的定义如下:
    * 1、为null
    * 2、为不可见字符(如空格)
    * 3、""
    - * + * * @param str 被检测的字符串 * @return 是否为空 */ @@ -105,7 +110,7 @@ public class StrUtil { * 1、为null
    * 2、为不可见字符(如空格)
    * 3、""
    - * + * * @param obj 对象 * @return 如果为字符串是否为空串 * @since 3.3.0 @@ -124,7 +129,7 @@ public class StrUtil { * 1、不为null
    * 2、不为不可见字符(如空格)
    * 3、不为""
    - * + * * @param str 被检测的字符串 * @return 是否为非空 */ @@ -134,7 +139,7 @@ public class StrUtil { /** * 是否包含空字符串 - * + * * @param strs 字符串列表 * @return 是否包含空字符串 */ @@ -153,7 +158,7 @@ public class StrUtil { /** * 给定所有字符串是否为空白 - * + * * @param strs 字符串 * @return 所有字符串是否为空白 */ @@ -171,11 +176,12 @@ public class StrUtil { } // ------------------------------------------------------------------------ Empty + /** * 字符串是否为空,空的定义如下:
    * 1、为null
    * 2、为""
    - * + * * @param str 被检测的字符串 * @return 是否为空 */ @@ -187,7 +193,7 @@ public class StrUtil { * 如果对象是字符串是否为空串空的定义如下:
    * 1、为null
    * 2、为""
    - * + * * @param obj 对象 * @return 如果为字符串是否为空串 * @since 3.3.0 @@ -205,21 +211,21 @@ public class StrUtil { * 字符串是否为非空白 空白的定义如下:
    * 1、不为null
    * 2、不为""
    - * + * * @param str 被检测的字符串 * @return 是否为非空 */ public static boolean isNotEmpty(CharSequence str) { return false == isEmpty(str); } - + /** * 当给定字符串为null时,转换为Empty - * + * * @param str 被检查的字符串 * @return 原字符串或者空串 - * @since 4.6.3 * @see #nullToEmpty(CharSequence) + * @since 4.6.3 */ public static String emptyIfNull(CharSequence str) { return nullToEmpty(str); @@ -227,7 +233,7 @@ public class StrUtil { /** * 当给定字符串为null时,转换为Empty - * + * * @param str 被转换的字符串 * @return 转换后的字符串 */ @@ -237,17 +243,16 @@ public class StrUtil { /** * 如果字符串是null,则返回指定默认字符串,否则返回字符串本身。 - * + * *
     	 * nullToDefault(null, "default")  = "default"
     	 * nullToDefault("", "default")    = ""
     	 * nullToDefault("  ", "default")  = "  "
     	 * nullToDefault("bat", "default") = "bat"
     	 * 
    - * - * @param str 要转换的字符串 + * + * @param str 要转换的字符串 * @param defaultStr 默认字符串 - * * @return 字符串本身或指定的默认字符串 */ public static String nullToDefault(CharSequence str, String defaultStr) { @@ -256,17 +261,16 @@ public class StrUtil { /** * 如果字符串是null或者"",则返回指定默认字符串,否则返回字符串本身。 - * + * *
     	 * emptyToDefault(null, "default")  = "default"
     	 * emptyToDefault("", "default")    = "default"
     	 * emptyToDefault("  ", "default")  = "  "
     	 * emptyToDefault("bat", "default") = "bat"
     	 * 
    - * - * @param str 要转换的字符串 + * + * @param str 要转换的字符串 * @param defaultStr 默认字符串 - * * @return 字符串本身或指定的默认字符串 * @since 4.1.0 */ @@ -276,17 +280,16 @@ public class StrUtil { /** * 如果字符串是null或者""或者空白,则返回指定默认字符串,否则返回字符串本身。 - * + * *
     	 * emptyToDefault(null, "default")  = "default"
     	 * emptyToDefault("", "default")    = "default"
     	 * emptyToDefault("  ", "default")  = "default"
     	 * emptyToDefault("bat", "default") = "bat"
     	 * 
    - * - * @param str 要转换的字符串 + * + * @param str 要转换的字符串 * @param defaultStr 默认字符串 - * * @return 字符串本身或指定的默认字符串 * @since 4.1.0 */ @@ -296,7 +299,7 @@ public class StrUtil { /** * 当给定字符串为空字符串时,转换为null - * + * * @param str 被转换的字符串 * @return 转换后的字符串 */ @@ -306,7 +309,7 @@ public class StrUtil { /** * 是否包含空字符串 - * + * * @param strs 字符串列表 * @return 是否包含空字符串 */ @@ -325,7 +328,7 @@ public class StrUtil { /** * 是否全部为空字符串 - * + * * @param strs 字符串列表 * @return 是否全部为空字符串 */ @@ -355,7 +358,7 @@ public class StrUtil { /** * 检查字符串是否为null、“null”、“undefined” - * + * * @param str 被检查的字符串 * @return 是否为null、“null”、“undefined” * @since 4.0.10 @@ -369,7 +372,7 @@ public class StrUtil { /** * 检查字符串是否为null、“”、“null”、“undefined” - * + * * @param str 被检查的字符串 * @return 是否为null、“”、“null”、“undefined” * @since 4.0.10 @@ -383,7 +386,7 @@ public class StrUtil { /** * 检查字符串是否为null、空白串、“null”、“undefined” - * + * * @param str 被检查的字符串 * @return 是否为null、空白串、“null”、“undefined” * @since 4.0.10 @@ -397,7 +400,7 @@ public class StrUtil { /** * 是否为“null”、“undefined”,不做空指针检查 - * + * * @param str 字符串 * @return 是否为“null”、“undefined” */ @@ -407,12 +410,13 @@ public class StrUtil { } // ------------------------------------------------------------------------ Trim + /** * 除去字符串头尾部的空白,如果字符串是null,依然返回null。 - * + * *

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

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

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

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

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

     	 * trimEnd(null)       = null
     	 * trimEnd("")         = ""
    @@ -523,9 +525,8 @@ public class StrUtil {
     	 * trimEnd("abc  ")    = "abc"
     	 * trimEnd(" abc ")    = " abc"
     	 * 
    - * + * * @param str 要处理的字符串 - * * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null */ public static String trimEnd(CharSequence str) { @@ -534,10 +535,9 @@ public class StrUtil { /** * 除去字符串头尾部的空白符,如果字符串是null,依然返回null。 - * - * @param str 要处理的字符串 + * + * @param str 要处理的字符串 * @param mode -1表示trimStart,0表示trim全部, 1表示trimEnd - * * @return 除去指定字符后的的字符串,如果原字串为null,则返回null */ public static String trim(CharSequence str, int mode) { @@ -572,9 +572,9 @@ public class StrUtil { /** * 字符串是否以给定字符开始 - * + * * @param str 字符串 - * @param c 字符 + * @param c 字符 * @return 是否开始 */ public static boolean startWith(CharSequence str, char c) { @@ -584,9 +584,9 @@ public class StrUtil { /** * 是否以指定字符串开头
    * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false - * - * @param str 被监测字符串 - * @param prefix 开头字符串 + * + * @param str 被监测字符串 + * @param prefix 开头字符串 * @param isIgnoreCase 是否忽略大小写 * @return 是否以指定字符串开头 */ @@ -604,8 +604,8 @@ public class StrUtil { /** * 是否以指定字符串开头 - * - * @param str 被监测字符串 + * + * @param str 被监测字符串 * @param prefix 开头字符串 * @return 是否以指定字符串开头 */ @@ -615,8 +615,8 @@ public class StrUtil { /** * 是否以指定字符串开头,忽略大小写 - * - * @param str 被监测字符串 + * + * @param str 被监测字符串 * @param prefix 开头字符串 * @return 是否以指定字符串开头 */ @@ -627,8 +627,8 @@ public class StrUtil { /** * 给定字符串是否以任何一个字符串开始
    * 给定字符串和数组为空都返回false - * - * @param str 给定字符串 + * + * @param str 给定字符串 * @param prefixes 需要检测的开始字符串 * @return 给定字符串是否以任何一个字符串开始 * @since 3.0.6 @@ -648,9 +648,9 @@ public class StrUtil { /** * 字符串是否以给定字符结尾 - * + * * @param str 字符串 - * @param c 字符 + * @param c 字符 * @return 是否结尾 */ public static boolean endWith(CharSequence str, char c) { @@ -660,9 +660,9 @@ public class StrUtil { /** * 是否以指定字符串结尾
    * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false - * - * @param str 被监测字符串 - * @param suffix 结尾字符串 + * + * @param str 被监测字符串 + * @param suffix 结尾字符串 * @param isIgnoreCase 是否忽略大小写 * @return 是否以指定字符串结尾 */ @@ -680,8 +680,8 @@ public class StrUtil { /** * 是否以指定字符串结尾 - * - * @param str 被监测字符串 + * + * @param str 被监测字符串 * @param suffix 结尾字符串 * @return 是否以指定字符串结尾 */ @@ -691,8 +691,8 @@ public class StrUtil { /** * 是否以指定字符串结尾,忽略大小写 - * - * @param str 被监测字符串 + * + * @param str 被监测字符串 * @param suffix 结尾字符串 * @return 是否以指定字符串结尾 */ @@ -703,8 +703,8 @@ public class StrUtil { /** * 给定字符串是否以任何一个字符串结尾
    * 给定字符串和数组为空都返回false - * - * @param str 给定字符串 + * + * @param str 给定字符串 * @param suffixes 需要检测的结尾字符串 * @return 给定字符串是否以任何一个字符串结尾 * @since 3.0.6 @@ -724,8 +724,8 @@ public class StrUtil { /** * 指定字符是否在字符串中出现过 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchChar 被查找的字符 * @return 是否包含 * @since 3.1.2 @@ -737,13 +737,13 @@ public class StrUtil { /** * 指定字符串是否在字符串中出现过 * - * @param str 字符串 + * @param str 字符串 * @param searchStr 被查找的字符串 * @return 是否包含 * @since 5.1.1 */ public static boolean contains(CharSequence str, CharSequence searchStr) { - if(null == str || null == searchStr){ + if (null == str || null == searchStr) { return false; } return str.toString().contains(searchStr); @@ -751,8 +751,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testStrs 需要检查的字符串数组 * @return 是否包含任意一个字符串 * @since 3.2.0 @@ -763,8 +763,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符列表中的任意一个字符 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testChars 需要检查的字符数组 * @return 是否包含任意一个字符 * @since 4.1.11 @@ -783,8 +783,8 @@ public class StrUtil { /** * 检查指定字符串中是否只包含给定的字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param testChars 检查的字符 * @return 字符串含有非检查的字符,返回false * @since 4.4.1 @@ -804,7 +804,7 @@ public class StrUtil { /** * 给定字符串是否包含空白符(空白符包括空格、制表符、全角空格和不间断空格)
    * 如果给定字符串为null或者"",则返回false - * + * * @param str 字符串 * @return 是否包含空白符 * @since 4.0.8 @@ -828,8 +828,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串,如果包含返回找到的第一个字符串 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testStrs 需要检查的字符串数组 * @return 被包含的第一个字符串 * @since 3.2.0 @@ -848,8 +848,8 @@ public class StrUtil { /** * 是否包含特定字符,忽略大小写,如果给定两个参数都为null,返回true - * - * @param str 被检测字符串 + * + * @param str 被检测字符串 * @param testStr 被测试是否包含的字符串 * @return 是否包含 */ @@ -864,8 +864,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串
    * 忽略大小写 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testStrs 需要检查的字符串数组 * @return 是否包含任意一个字符串 * @since 3.2.0 @@ -877,8 +877,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串,如果包含返回找到的第一个字符串
    * 忽略大小写 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testStrs 需要检查的字符串数组 * @return 被包含的第一个字符串 * @since 3.2.0 @@ -898,13 +898,13 @@ public class StrUtil { /** * 获得set或get或is方法对应的标准属性名
    * 例如:setName 返回 name - * + * *
     	 * getName =》name
     	 * setName =》name
     	 * isName  =》name
     	 * 
    - * + * * @param getOrSetMethodName Get或Set方法名 * @return 如果是set或get方法名,返回field, 否则null */ @@ -912,7 +912,7 @@ public class StrUtil { final String getOrSetMethodNameStr = getOrSetMethodName.toString(); if (getOrSetMethodNameStr.startsWith("get") || getOrSetMethodNameStr.startsWith("set")) { return removePreAndLowerFirst(getOrSetMethodName, 3); - } else if(getOrSetMethodNameStr.startsWith("is")) { + } else if (getOrSetMethodNameStr.startsWith("is")) { return removePreAndLowerFirst(getOrSetMethodName, 2); } return null; @@ -921,7 +921,7 @@ public class StrUtil { /** * 生成set方法名
    * 例如:name 返回 setName - * + * * @param fieldName 属性名 * @return setXxx */ @@ -931,7 +931,7 @@ public class StrUtil { /** * 生成get方法名 - * + * * @param fieldName 属性名 * @return getXxx */ @@ -942,8 +942,8 @@ public class StrUtil { /** * 移除字符串中所有给定字符串
    * 例:removeAll("aa-bb-cc-dd", "-") =》 aabbccdd - * - * @param str 字符串 + * + * @param str 字符串 * @param strToRemove 被移除的字符串 * @return 移除后的字符串 */ @@ -956,8 +956,8 @@ public class StrUtil { /** * 去除字符串中指定的多个字符,如有多个则全部去除 - * - * @param str 字符串 + * + * @param str 字符串 * @param chars 字符列表 * @return 去除后的字符 * @since 4.2.2 @@ -983,12 +983,12 @@ public class StrUtil { /** * 去除所有换行符,包括: - * + * *
     	 * 1. \r
     	 * 1. \n
     	 * 
    - * + * * @param str 字符串 * @return 处理后的字符串 * @since 4.2.2 @@ -1000,8 +1000,8 @@ public class StrUtil { /** * 去掉首部指定长度的字符串并将剩余字符串首字母小写
    * 例如:str=setName, preLength=3 =》 return name - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param preLength 去掉的长度 * @return 处理后的字符串,不符合规范返回null */ @@ -1023,8 +1023,8 @@ public class StrUtil { /** * 去掉首部指定长度的字符串并将剩余字符串首字母小写
    * 例如:str=setName, prefix=set =》 return name - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefix 前缀 * @return 处理后的字符串,不符合规范返回null */ @@ -1034,8 +1034,8 @@ public class StrUtil { /** * 原字符串首字母大写并在其首部添加指定字符串 例如:str=name, preString=get =》 return getName - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param preString 添加的首部 * @return 处理后的字符串 */ @@ -1049,7 +1049,7 @@ public class StrUtil { /** * 大写首字母
    * 例如:str = name, return Name - * + * * @param str 字符串 * @return 字符串 */ @@ -1069,7 +1069,7 @@ public class StrUtil { /** * 小写首字母
    * 例如:str = Name, return name - * + * * @param str 字符串 * @return 字符串 */ @@ -1088,8 +1088,8 @@ public class StrUtil { /** * 去掉指定前缀 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @return 切掉后的字符串,若前缀不是 preffix, 返回原字符串 */ @@ -1107,8 +1107,8 @@ public class StrUtil { /** * 忽略大小写去掉指定前缀 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @return 切掉后的字符串,若前缀不是 prefix, 返回原字符串 */ @@ -1126,8 +1126,8 @@ public class StrUtil { /** * 去掉指定后缀 - * - * @param str 字符串 + * + * @param str 字符串 * @param suffix 后缀 * @return 切掉后的字符串,若后缀不是 suffix, 返回原字符串 */ @@ -1145,8 +1145,8 @@ public class StrUtil { /** * 去掉指定后缀,并小写首字母 - * - * @param str 字符串 + * + * @param str 字符串 * @param suffix 后缀 * @return 切掉后的字符串,若后缀不是 suffix, 返回原字符串 */ @@ -1156,8 +1156,8 @@ public class StrUtil { /** * 忽略大小写去掉指定后缀 - * - * @param str 字符串 + * + * @param str 字符串 * @param suffix 后缀 * @return 切掉后的字符串,若后缀不是 suffix, 返回原字符串 */ @@ -1175,8 +1175,8 @@ public class StrUtil { /** * 去除两边的指定字符串 - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefixOrSuffix 前缀或后缀 * @return 处理后的字符串 * @since 3.1.2 @@ -1191,8 +1191,8 @@ public class StrUtil { /** * 去除两边的指定字符串 - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 处理后的字符串 @@ -1219,8 +1219,8 @@ public class StrUtil { /** * 去除两边的指定字符串,忽略大小写 - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefixOrSuffix 前缀或后缀 * @return 处理后的字符串 * @since 3.1.2 @@ -1231,8 +1231,8 @@ public class StrUtil { /** * 去除两边的指定字符串,忽略大小写 - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 处理后的字符串 @@ -1257,8 +1257,8 @@ public class StrUtil { /** * 如果给定字符串不是以prefix开头的,在开头补充 prefix - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @return 补充后的字符串 */ @@ -1277,8 +1277,8 @@ public class StrUtil { /** * 如果给定字符串不是以suffix结尾的,在尾部补充 suffix - * - * @param str 字符串 + * + * @param str 字符串 * @param suffix 后缀 * @return 补充后的字符串 */ @@ -1297,7 +1297,7 @@ public class StrUtil { /** * 清理空白字符 - * + * * @param str 被清理的字符串 * @return 清理后的字符串 */ @@ -1319,10 +1319,11 @@ public class StrUtil { } // ------------------------------------------------------------------------------ Split + /** * 切分字符串 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 * @return 切分后的数组 */ @@ -1332,8 +1333,8 @@ public class StrUtil { /** * 切分字符串为long数组 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符 * @return 切分后long数组 * @since 4.0.6 @@ -1344,8 +1345,8 @@ public class StrUtil { /** * 切分字符串为long数组 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符串 * @return 切分后long数组 * @since 4.0.6 @@ -1356,8 +1357,8 @@ public class StrUtil { /** * 切分字符串为int数组 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符 * @return 切分后long数组 * @since 4.0.6 @@ -1368,8 +1369,8 @@ public class StrUtil { /** * 切分字符串为int数组 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符串 * @return 切分后long数组 * @since 4.0.6 @@ -1382,8 +1383,8 @@ public class StrUtil { * 切分字符串
    * a#b#c =》 [a,b,c]
    * a##b#c =》 [a,"",b,c] - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 * @return 切分后的集合 */ @@ -1393,25 +1394,25 @@ public class StrUtil { /** * 切分字符串 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 - * @param limit 限制分片数 + * @param limit 限制分片数 * @return 切分后的数组 */ public static String[] splitToArray(CharSequence str, char separator, int limit) { if (null == str) { - return new String[] {}; + return new String[]{}; } return StrSpliter.splitToArray(str.toString(), separator, limit, false, false); } /** * 切分字符串,不去除切分后每个元素两边的空白符,不去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 + * @param limit 限制分片数,-1不限制 * @return 切分后的集合 */ public static List split(CharSequence str, char separator, int limit) { @@ -1420,8 +1421,8 @@ public class StrUtil { /** * 切分字符串,去除切分后每个元素两边的空白符,去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 * @return 切分后的集合 * @since 3.1.2 @@ -1432,8 +1433,8 @@ public class StrUtil { /** * 切分字符串,去除切分后每个元素两边的空白符,去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 * @return 切分后的集合 * @since 3.2.0 @@ -1444,10 +1445,10 @@ public class StrUtil { /** * 切分字符串,去除切分后每个元素两边的空白符,去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 + * @param limit 限制分片数,-1不限制 * @return 切分后的集合 * @since 3.1.0 */ @@ -1457,10 +1458,10 @@ public class StrUtil { /** * 切分字符串,去除切分后每个元素两边的空白符,去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 + * @param limit 限制分片数,-1不限制 * @return 切分后的集合 * @since 3.2.0 */ @@ -1470,10 +1471,10 @@ public class StrUtil { /** * 切分字符串,不限制分片数量 - * - * @param str 被切分的字符串 - * @param separator 分隔符字符 - * @param isTrim 是否去除切分字符串后每个元素两边的空格 + * + * @param str 被切分的字符串 + * @param separator 分隔符字符 + * @param isTrim 是否去除切分字符串后每个元素两边的空格 * @param ignoreEmpty 是否忽略空串 * @return 切分后的集合 * @since 3.0.8 @@ -1484,11 +1485,11 @@ public class StrUtil { /** * 切分字符串 - * - * @param str 被切分的字符串 - * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 - * @param isTrim 是否去除切分字符串后每个元素两边的空格 + * + * @param str 被切分的字符串 + * @param separator 分隔符字符 + * @param limit 限制分片数,-1不限制 + * @param isTrim 是否去除切分字符串后每个元素两边的空格 * @param ignoreEmpty 是否忽略空串 * @return 切分后的集合 * @since 3.0.8 @@ -1502,11 +1503,11 @@ public class StrUtil { /** * 切分字符串 - * - * @param str 被切分的字符串 - * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 - * @param isTrim 是否去除切分字符串后每个元素两边的空格 + * + * @param str 被切分的字符串 + * @param separator 分隔符字符 + * @param limit 限制分片数,-1不限制 + * @param isTrim 是否去除切分字符串后每个元素两边的空格 * @param ignoreEmpty 是否忽略空串 * @return 切分后的集合 * @since 3.2.0 @@ -1520,15 +1521,15 @@ public class StrUtil { } /** - * 切分字符串 - * - * @param str 被切分的字符串 + * 切分字符串,如果分隔符不存在则返回原字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符 * @return 字符串 */ public static String[] split(CharSequence str, CharSequence separator) { if (str == null) { - return new String[] {}; + return new String[]{}; } final String separatorStr = (null == separator) ? null : separator.toString(); @@ -1537,7 +1538,7 @@ public class StrUtil { /** * 根据给定长度,将给定字符串截取为多个部分 - * + * * @param str 字符串 * @param len 每一个小节的长度 * @return 截取后的字符串数组 @@ -1545,7 +1546,7 @@ public class StrUtil { */ public static String[] split(CharSequence str, int len) { if (null == str) { - return new String[] {}; + return new String[]{}; } return StrSpliter.splitByLength(str.toString(), len); } @@ -1558,10 +1559,10 @@ public class StrUtil { * 如果经过修正的index中from大于to,则互换from和to example:
    * abcdefgh 2 3 =》 c
    * abcdefgh 2 -3 =》 cde
    - * - * @param str String + * + * @param str String * @param fromIndex 开始的index(包括) - * @param toIndex 结束的index(不包括) + * @param toIndex 结束的index(不包括) * @return 字串 */ public static String sub(CharSequence str, int fromIndex, int toIndex) { @@ -1604,9 +1605,9 @@ public class StrUtil { /** * 通过CodePoint截取字符串,可以截断Emoji * - * @param str String + * @param str String * @param fromIndex 开始的index(包括) - * @param toIndex 结束的index(不包括) + * @param toIndex 结束的index(不包括) * @return 字串 */ public static String subByCodePoint(CharSequence str, int fromIndex, int toIndex) { @@ -1631,8 +1632,8 @@ public class StrUtil { /** * 截取部分字符串,这里一个汉字的长度认为是2 * - * @param str 字符串 - * @param len 切割的位置 + * @param str 字符串 + * @param len 切割的位置 * @param suffix 切割后加上后缀 * @return 切割后的字符串 * @since 3.1.1 @@ -1662,7 +1663,7 @@ public class StrUtil { /** * 限制字符串长度,如果超过指定长度,截取指定长度并在末尾加"..." - * + * * @param string 字符串 * @param length 最大长度 * @return 切割后的剩余的前半部分字符串+"..." @@ -1681,8 +1682,8 @@ public class StrUtil { /** * 切割指定位置之前部分的字符串 - * - * @param string 字符串 + * + * @param string 字符串 * @param toIndex 切割到的位置(不包括) * @return 切割后的剩余的前半部分字符串 */ @@ -1692,8 +1693,8 @@ public class StrUtil { /** * 切割指定位置之后部分的字符串 - * - * @param string 字符串 + * + * @param string 字符串 * @param fromIndex 切割开始的位置(包括) * @return 切割后后剩余的后半部分字符串 */ @@ -1706,7 +1707,7 @@ public class StrUtil { /** * 切割指定长度的后部分的字符串 - * + * *
     	 * StrUtil.subSufByLength("abcde", 3)      =    "cde"
     	 * StrUtil.subSufByLength("abcde", 0)      =    ""
    @@ -1716,7 +1717,7 @@ public class StrUtil {
     	 * StrUtil.subSufByLength("abcde", 10)     =    "abcde"
     	 * StrUtil.subSufByLength(null, 3)               =    null
     	 * 
    - * + * * @param string 字符串 * @param length 切割长度 * @return 切割后后剩余的后半部分字符串 @@ -1735,10 +1736,10 @@ public class StrUtil { /** * 截取字符串,从指定位置开始,截取指定长度的字符串
    * author weibaohui - * - * @param input 原始字符串 + * + * @param input 原始字符串 * @param fromIndex 开始的index,包括 - * @param length 要截取的长度 + * @param length 要截取的长度 * @return 截取后的字符串 */ public static String subWithLength(String input, int fromIndex, int length) { @@ -1749,20 +1750,20 @@ public class StrUtil { * 截取分隔字符串之前的字符串,不包括分隔字符串
    * 如果给定的字符串为空串(null或"")或者分隔字符串为null,返回原字符串
    * 如果分隔字符串为空串"",则返回空串,如果分隔字符串未找到,返回原字符串,举例如下: - * + * *
    -	 * StrUtil.subBefore(null, *)      = null
    -	 * StrUtil.subBefore("", *)        = ""
    -	 * StrUtil.subBefore("abc", "a")   = ""
    -	 * StrUtil.subBefore("abcba", "b") = "a"
    -	 * StrUtil.subBefore("abc", "c")   = "ab"
    -	 * StrUtil.subBefore("abc", "d")   = "abc"
    -	 * StrUtil.subBefore("abc", "")    = ""
    -	 * StrUtil.subBefore("abc", null)  = "abc"
    +	 * StrUtil.subBefore(null, *, false)      = null
    +	 * StrUtil.subBefore("", *, false)        = ""
    +	 * StrUtil.subBefore("abc", "a", false)   = ""
    +	 * StrUtil.subBefore("abcba", "b", false) = "a"
    +	 * StrUtil.subBefore("abc", "c", false)   = "ab"
    +	 * StrUtil.subBefore("abc", "d", false)   = "abc"
    +	 * StrUtil.subBefore("abc", "", false)    = ""
    +	 * StrUtil.subBefore("abc", null, false)  = "abc"
     	 * 
    - * - * @param string 被查找的字符串 - * @param separator 分隔字符串(不包括) + * + * @param string 被查找的字符串 + * @param separator 分隔字符串(不包括) * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 * @return 切割后的字符串 * @since 3.1.1 @@ -1791,18 +1792,18 @@ public class StrUtil { * 截取分隔字符串之前的字符串,不包括分隔字符串
    * 如果给定的字符串为空串(null或"")或者分隔字符串为null,返回原字符串
    * 如果分隔字符串未找到,返回原字符串,举例如下: - * + * *
    -	 * StrUtil.subBefore(null, *)      = null
    -	 * StrUtil.subBefore("", *)        = ""
    -	 * StrUtil.subBefore("abc", 'a')   = ""
    -	 * StrUtil.subBefore("abcba", 'b') = "a"
    -	 * StrUtil.subBefore("abc", 'c')   = "ab"
    -	 * StrUtil.subBefore("abc", 'd')   = "abc"
    +	 * StrUtil.subBefore(null, *, false)      = null
    +	 * StrUtil.subBefore("", *, false)        = ""
    +	 * StrUtil.subBefore("abc", 'a', false)   = ""
    +	 * StrUtil.subBefore("abcba", 'b', false) = "a"
    +	 * StrUtil.subBefore("abc", 'c', false)   = "ab"
    +	 * StrUtil.subBefore("abc", 'd', false)   = "abc"
     	 * 
    - * - * @param string 被查找的字符串 - * @param separator 分隔字符串(不包括) + * + * @param string 被查找的字符串 + * @param separator 分隔字符串(不包括) * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 * @return 切割后的字符串 * @since 4.1.15 @@ -1829,18 +1830,18 @@ public class StrUtil { * 如果分隔字符串为空串(null或""),则返回空串,如果分隔字符串未找到,返回空串,举例如下: * *
    -	 * StrUtil.subAfter(null, *)      = null
    -	 * StrUtil.subAfter("", *)        = ""
    -	 * StrUtil.subAfter(*, null)      = ""
    -	 * StrUtil.subAfter("abc", "a")   = "bc"
    -	 * StrUtil.subAfter("abcba", "b") = "cba"
    -	 * StrUtil.subAfter("abc", "c")   = ""
    -	 * StrUtil.subAfter("abc", "d")   = ""
    -	 * StrUtil.subAfter("abc", "")    = "abc"
    +	 * StrUtil.subAfter(null, *, false)      = null
    +	 * StrUtil.subAfter("", *, false)        = ""
    +	 * StrUtil.subAfter(*, null, false)      = ""
    +	 * StrUtil.subAfter("abc", "a", false)   = "bc"
    +	 * StrUtil.subAfter("abcba", "b", false) = "cba"
    +	 * StrUtil.subAfter("abc", "c", false)   = ""
    +	 * StrUtil.subAfter("abc", "d", false)   = ""
    +	 * StrUtil.subAfter("abc", "", false)    = "abc"
     	 * 
    * - * @param string 被查找的字符串 - * @param separator 分隔字符串(不包括) + * @param string 被查找的字符串 + * @param separator 分隔字符串(不包括) * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 * @return 切割后的字符串 * @since 3.1.1 @@ -1867,16 +1868,16 @@ public class StrUtil { * 如果分隔字符串为空串(null或""),则返回空串,如果分隔字符串未找到,返回空串,举例如下: * *
    -	 * StrUtil.subAfter(null, *)      = null
    -	 * StrUtil.subAfter("", *)        = ""
    -	 * StrUtil.subAfter("abc", 'a')   = "bc"
    -	 * StrUtil.subAfter("abcba", 'b') = "cba"
    -	 * StrUtil.subAfter("abc", 'c')   = ""
    -	 * StrUtil.subAfter("abc", 'd')   = ""
    +	 * StrUtil.subAfter(null, *, false)      = null
    +	 * StrUtil.subAfter("", *, false)        = ""
    +	 * StrUtil.subAfter("abc", 'a', false)   = "bc"
    +	 * StrUtil.subAfter("abcba", 'b', false) = "cba"
    +	 * StrUtil.subAfter("abc", 'c', false)   = ""
    +	 * StrUtil.subAfter("abc", 'd', false)   = ""
     	 * 
    * - * @param string 被查找的字符串 - * @param separator 分隔字符串(不包括) + * @param string 被查找的字符串 + * @param separator 分隔字符串(不包括) * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 * @return 切割后的字符串 * @since 4.1.15 @@ -1895,9 +1896,9 @@ public class StrUtil { /** * 截取指定字符串中间部分,不包括标识字符串
    - * + *

    * 栗子: - * + * *

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

    * 栗子: - * + * *

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

    * 栗子: * *

    @@ -1978,58 +1979,36 @@ public class StrUtil {
     	 * StrUtil.subBetweenAll("[yabc[zy]abcz]", "[", "]");   = ["zy"]           重叠时只截取内部,
     	 * 
    * - * @param str 被切割的字符串 - * @param regexBefore 截取开始的字符串标识 - * @param regexAfter 截取到的字符串标识 + * @param str 被切割的字符串 + * @param prefix 截取开始的字符串标识 + * @param suffix 截取到的字符串标识 * @return 截取后的字符串 * @author dahuoyzs * @since 5.2.5 */ - public static String[] subBetweenAll(CharSequence str, CharSequence regexBefore, CharSequence regexAfter) { - if (str == null || regexBefore == null || regexAfter == null || str.length() < 1 || regexBefore.length() < 1 || regexAfter.length() < 1) { + public static String[] subBetweenAll(CharSequence str, CharSequence prefix, CharSequence suffix) { + if (hasEmpty(str, prefix, suffix) || + // 不包含起始字符串,则肯定没有子串 + false == contains(str, prefix)) { return new String[0]; } - final String before = regexBefore.toString().replace("\\", ""); - final String after = regexAfter.toString().replace("\\", ""); - final Integer beforeNumber = StrUtil.count(str, before); - final Integer afterNumber = StrUtil.count(str, after); - if (beforeNumber<1||afterNumber<1){ - return new String[0]; - } - - LinkedList betweenList = new LinkedList<>(); - if (beforeNumber.compareTo(afterNumber) > 0) { - String[] fragments = str.toString().split(regexAfter.toString()); - for (int i = 0; i < fragments.length - 1; i++) { - String fragment = fragments[i]; - if (fragment.contains(before)) { - int beforeIndex = StrUtil.lastIndexOf(fragment, before, 0, false); - String between = fragment.substring(beforeIndex); - if (between.length()>0) - betweenList.add(between); - } - } - } else { - String[] fragments = str.toString().split(regexBefore.toString()); - for (int i = 1; i < fragments.length; i++) { - String fragment = fragments[i]; - if (fragment.contains(after)) { - int afterIndex = StrUtil.indexOf(fragment, after, 0, false); - String between = fragment.substring(0, afterIndex); - if (between.length()>0) - betweenList.add(between); - } + final List result = new LinkedList<>(); + final String[] split = split(str, prefix); + for (String fragment : split(str, prefix)) { + int suffixIndex = fragment.indexOf(suffix.toString()); + if (suffixIndex > 0) { + result.add(fragment.substring(0, suffixIndex)); } } - return betweenList.toArray(new String[0]); + return result.toArray(new String[0]); } /** * 给定字符串是否被字符包围 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 是否包围,空串不包围 @@ -2048,8 +2027,8 @@ public class StrUtil { /** * 给定字符串是否被字符包围 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 是否包围,空串不包围 @@ -2067,8 +2046,8 @@ public class StrUtil { /** * 重复某个字符 - * - * @param c 被重复的字符 + * + * @param c 被重复的字符 * @param count 重复的数目,如果小于等于0则返回"" * @return 重复字符字符串 */ @@ -2086,8 +2065,8 @@ public class StrUtil { /** * 重复某个字符串 - * - * @param str 被重复的字符 + * + * @param str 被重复的字符 * @param count 重复的数目 * @return 重复字符字符串 */ @@ -2122,8 +2101,8 @@ public class StrUtil { /** * 重复某个字符串到指定长度 - * - * @param str 被重复的字符 + * + * @param str 被重复的字符 * @param padLen 指定长度 * @return 重复字符字符串 * @since 4.3.2 @@ -2152,15 +2131,15 @@ public class StrUtil { /** * 重复某个字符串并通过分界符连接 - * + * *
     	 * StrUtil.repeatAndJoin("?", 5, ",")   = "?,?,?,?,?"
     	 * StrUtil.repeatAndJoin("?", 0, ",")   = ""
     	 * StrUtil.repeatAndJoin("?", 5, null) = "?????"
     	 * 
    - * - * @param str 被重复的字符串 - * @param count 数量 + * + * @param str 被重复的字符串 + * @param count 数量 * @param conjunction 分界符 * @return 连接后的字符串 * @since 4.0.1 @@ -2184,7 +2163,7 @@ public class StrUtil { /** * 比较两个字符串(大小写敏感)。 - * + * *
     	 * equals(null, null)   = true
     	 * equals(null, "abc")  = false
    @@ -2192,10 +2171,9 @@ public class StrUtil {
     	 * equals("abc", "abc") = true
     	 * equals("abc", "ABC") = false
     	 * 
    - * + * * @param str1 要比较的字符串1 * @param str2 要比较的字符串2 - * * @return 如果两个字符串相同,或者都是null,则返回true */ public static boolean equals(CharSequence str1, CharSequence str2) { @@ -2204,7 +2182,7 @@ public class StrUtil { /** * 比较两个字符串(大小写不敏感)。 - * + * *
     	 * equalsIgnoreCase(null, null)   = true
     	 * equalsIgnoreCase(null, "abc")  = false
    @@ -2212,10 +2190,9 @@ public class StrUtil {
     	 * equalsIgnoreCase("abc", "abc") = true
     	 * equalsIgnoreCase("abc", "ABC") = true
     	 * 
    - * + * * @param str1 要比较的字符串1 * @param str2 要比较的字符串2 - * * @return 如果两个字符串相同,或者都是null,则返回true */ public static boolean equalsIgnoreCase(CharSequence str1, CharSequence str2) { @@ -2224,9 +2201,9 @@ public class StrUtil { /** * 比较两个字符串是否相等。 - * - * @param str1 要比较的字符串1 - * @param str2 要比较的字符串2 + * + * @param str1 要比较的字符串1 + * @param str2 要比较的字符串2 * @param ignoreCase 是否忽略大小写 * @return 如果两个字符串相同,或者都是null,则返回true * @since 3.2.0 @@ -2244,14 +2221,14 @@ public class StrUtil { if (ignoreCase) { return str1.toString().equalsIgnoreCase(str2.toString()); } else { - return str1.equals(str2); + return str1.toString().contentEquals(str2); } } /** * 给定字符串是否与提供的中任一字符串相同(忽略大小写),相同则返回{@code true},没有相同的返回{@code false}
    * 如果参与比对的字符串列表为空,返回{@code false} - * + * * @param str1 给定需要检查的字符串 * @param strs 需要参与比对的字符串列表 * @return 是否相同 @@ -2264,7 +2241,7 @@ public class StrUtil { /** * 给定字符串是否与提供的中任一字符串相同,相同则返回{@code true},没有相同的返回{@code false}
    * 如果参与比对的字符串列表为空,返回{@code false} - * + * * @param str1 给定需要检查的字符串 * @param strs 需要参与比对的字符串列表 * @return 是否相同 @@ -2277,10 +2254,10 @@ public class StrUtil { /** * 给定字符串是否与提供的中任一字符串相同,相同则返回{@code true},没有相同的返回{@code false}
    * 如果参与比对的字符串列表为空,返回{@code false} - * - * @param str1 给定需要检查的字符串 + * + * @param str1 给定需要检查的字符串 * @param ignoreCase 是否忽略大小写 - * @param strs 需要参与比对的字符串列表 + * @param strs 需要参与比对的字符串列表 * @return 是否相同 * @since 4.3.2 */ @@ -2305,9 +2282,9 @@ public class StrUtil { * 通常使用:format("this is {} for {}", "a", "b") =》 this is a for b
    * 转义{}: format("this is \\{} for {}", "a", "b") =》 this is \{} for a
    * 转义\: format("this is \\\\{} for {}", "a", "b") =》 this is \a for b
    - * + * * @param template 文本模板,被替换的部分用 {} 表示 - * @param params 参数值 + * @param params 参数值 * @return 格式化后的文本 */ public static String format(CharSequence template, Object... params) { @@ -2324,8 +2301,8 @@ public class StrUtil { * 有序的格式化文本,使用{number}做为占位符
    * 例:
    * 通常使用:format("this is {0} for {1}", "a", "b") =》 this is a for b
    - * - * @param pattern 文本格式 + * + * @param pattern 文本格式 * @param arguments 参数 * @return 格式化后的文本 */ @@ -2336,9 +2313,9 @@ public class StrUtil { /** * 格式化文本,使用 {varName} 占位
    * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue - * + * * @param template 文本模板,被替换的部分用 {key} 表示 - * @param map 参数值对 + * @param map 参数值对 * @return 格式化后的文本 */ public static String format(CharSequence template, Map map) { @@ -2362,7 +2339,7 @@ public class StrUtil { /** * 编码字符串,编码为UTF-8 - * + * * @param str 字符串 * @return 编码后的字节码 */ @@ -2373,7 +2350,7 @@ public class StrUtil { /** * 编码字符串
    * 使用系统默认编码 - * + * * @param str 字符串 * @return 编码后的字节码 */ @@ -2383,8 +2360,8 @@ public class StrUtil { /** * 编码字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 编码后的字节码 */ @@ -2394,8 +2371,8 @@ public class StrUtil { /** * 编码字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 编码后的字节码 */ @@ -2413,7 +2390,7 @@ public class StrUtil { /** * 将对象转为字符串
    * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 - * + * * @param obj 对象 * @return 字符串 */ @@ -2422,10 +2399,14 @@ public class StrUtil { } /** - * 将对象转为字符串
    - * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 - * - * @param obj 对象 + * 将对象转为字符串 + * + *
    +	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
    +	 * 2、对象数组会调用Arrays.toString方法
    +	 * 
    + * + * @param obj 对象 * @param charsetName 字符集 * @return 字符串 */ @@ -2434,10 +2415,13 @@ public class StrUtil { } /** - * 将对象转为字符串
    - * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 - * - * @param obj 对象 + * 将对象转为字符串 + *
    +	 * 	 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
    +	 * 	 2、对象数组会调用Arrays.toString方法
    +	 * 
    + * + * @param obj 对象 * @param charset 字符集 * @return 字符串 */ @@ -2463,8 +2447,8 @@ public class StrUtil { /** * 将byte数组转为字符串 - * - * @param bytes byte数组 + * + * @param bytes byte数组 * @param charset 字符集 * @return 字符串 */ @@ -2474,8 +2458,8 @@ public class StrUtil { /** * 解码字节码 - * - * @param data 字符串 + * + * @param data 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 解码后的字符串 */ @@ -2492,8 +2476,8 @@ public class StrUtil { /** * 将Byte数组转为字符串 - * - * @param bytes byte数组 + * + * @param bytes byte数组 * @param charset 字符集 * @return 字符串 */ @@ -2503,8 +2487,8 @@ public class StrUtil { /** * 解码字节码 - * - * @param data 字符串 + * + * @param data 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 解码后的字符串 */ @@ -2525,8 +2509,8 @@ public class StrUtil { /** * 将编码的byteBuffer数据转换为字符串 - * - * @param data 数据 + * + * @param data 数据 * @param charset 字符集,如果为空使用当前系统字符集 * @return 字符串 */ @@ -2540,8 +2524,8 @@ public class StrUtil { /** * 将编码的byteBuffer数据转换为字符串 - * - * @param data 数据 + * + * @param data 数据 * @param charset 字符集,如果为空使用当前系统字符集 * @return 字符串 */ @@ -2554,7 +2538,7 @@ public class StrUtil { /** * {@link CharSequence} 转为字符串,null安全 - * + * * @param cs {@link CharSequence} * @return 字符串 */ @@ -2564,7 +2548,7 @@ public class StrUtil { /** * 调用对象的toString方法,null会返回“null” - * + * * @param obj 对象 * @return 字符串 * @since 4.1.3 @@ -2575,8 +2559,8 @@ public class StrUtil { /** * 字符串转换为byteBuffer - * - * @param str 字符串 + * + * @param str 字符串 * @param charset 编码 * @return byteBuffer */ @@ -2586,12 +2570,11 @@ public class StrUtil { /** * 以 conjunction 为分隔符将多个对象转换为字符串 - * - * @see ArrayUtil#join(Object, CharSequence) - * + * * @param conjunction 分隔符 - * @param objs 数组 + * @param objs 数组 * @return 连接后的字符串 + * @see ArrayUtil#join(Object, CharSequence) */ public static String join(CharSequence conjunction, Object... objs) { return ArrayUtil.join(objs, conjunction); @@ -2600,7 +2583,7 @@ public class StrUtil { /** * 将驼峰式命名的字符串转换为下划线方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。
    * 例如: - * + * *
     	 * HelloWorld=》hello_world
     	 * Hello_World=》hello_world
    @@ -2617,7 +2600,7 @@ public class StrUtil {
     	/**
     	 * 将驼峰式命名的字符串转换为使用符号连接方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。
    * - * @param str 转换前的驼峰式命名的字符串,也可以为符号连接形式 + * @param str 转换前的驼峰式命名的字符串,也可以为符号连接形式 * @param symbol 连接符 * @return 转换后符号连接方式命名的字符串 * @since 4.0.10 @@ -2703,8 +2686,8 @@ public class StrUtil { /** * 包装指定字符串
    * 当前缀和后缀一致时使用此方法 - * - * @param str 被包装的字符串 + * + * @param str 被包装的字符串 * @param prefixAndSuffix 前缀和后缀 * @return 包装后的字符串 * @since 3.1.0 @@ -2715,8 +2698,8 @@ public class StrUtil { /** * 包装指定字符串 - * - * @param str 被包装的字符串 + * + * @param str 被包装的字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 包装后的字符串 @@ -2727,9 +2710,9 @@ public class StrUtil { /** * 包装多个字符串 - * + * * @param prefixAndSuffix 前缀和后缀 - * @param strs 多个字符串 + * @param strs 多个字符串 * @return 包装的字符串数组 * @since 4.0.7 */ @@ -2739,10 +2722,10 @@ public class StrUtil { /** * 包装多个字符串 - * + * * @param prefix 前缀 * @param suffix 后缀 - * @param strs 多个字符串 + * @param strs 多个字符串 * @return 包装的字符串数组 * @since 4.0.7 */ @@ -2756,8 +2739,8 @@ public class StrUtil { /** * 包装指定字符串,如果前缀或后缀已经包含对应的字符串,则不再包装 - * - * @param str 被包装的字符串 + * + * @param str 被包装的字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 包装后的字符串 @@ -2788,9 +2771,9 @@ public class StrUtil { /** * 包装多个字符串,如果已经包装,则不再包装 - * + * * @param prefixAndSuffix 前缀和后缀 - * @param strs 多个字符串 + * @param strs 多个字符串 * @return 包装的字符串数组 * @since 4.0.7 */ @@ -2800,10 +2783,10 @@ public class StrUtil { /** * 包装多个字符串,如果已经包装,则不再包装 - * + * * @param prefix 前缀 * @param suffix 后缀 - * @param strs 多个字符串 + * @param strs 多个字符串 * @return 包装的字符串数组 * @since 4.0.7 */ @@ -2817,8 +2800,8 @@ public class StrUtil { /** * 去掉字符包装,如果未被包装则返回原字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前置字符串 * @param suffix 后置字符串 * @return 去掉包装字符的字符串 @@ -2833,8 +2816,8 @@ public class StrUtil { /** * 去掉字符包装,如果未被包装则返回原字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前置字符 * @param suffix 后置字符 * @return 去掉包装字符的字符串 @@ -2852,8 +2835,8 @@ public class StrUtil { /** * 去掉字符包装,如果未被包装则返回原字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefixAndSuffix 前置和后置字符 * @return 去掉包装字符的字符串 * @since 4.0.1 @@ -2864,8 +2847,8 @@ public class StrUtil { /** * 指定字符串是否被包装 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 是否被包装 @@ -2880,8 +2863,8 @@ public class StrUtil { /** * 指定字符串是否被同一字符包装(前后都有这些字符串) - * - * @param str 字符串 + * + * @param str 字符串 * @param wrapper 包装字符串 * @return 是否被包装 */ @@ -2891,8 +2874,8 @@ public class StrUtil { /** * 指定字符串是否被同一字符包装(前后都有这些字符串) - * - * @param str 字符串 + * + * @param str 字符串 * @param wrapper 包装字符 * @return 是否被包装 */ @@ -2902,8 +2885,8 @@ public class StrUtil { /** * 指定字符串是否被包装 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefixChar 前缀 * @param suffixChar 后缀 * @return 是否被包装 @@ -2918,16 +2901,16 @@ public class StrUtil { /** * 补充字符串以满足最小长度 - * + * *
     	 * StrUtil.padPre(null, *, *);//null
     	 * StrUtil.padPre("1", 3, "ABC");//"AB1"
     	 * StrUtil.padPre("123", 2, "ABC");//"12"
     	 * 
    - * - * @param str 字符串 + * + * @param str 字符串 * @param minLength 最小长度 - * @param padStr 补充的字符 + * @param padStr 补充的字符 * @return 补充后的字符串 */ public static String padPre(CharSequence str, int minLength, CharSequence padStr) { @@ -2946,16 +2929,16 @@ public class StrUtil { /** * 补充字符串以满足最小长度 - * + * *
     	 * StrUtil.padPre(null, *, *);//null
     	 * StrUtil.padPre("1", 3, '0');//"001"
     	 * StrUtil.padPre("123", 2, '0');//"12"
     	 * 
    - * - * @param str 字符串 + * + * @param str 字符串 * @param minLength 最小长度 - * @param padChar 补充的字符 + * @param padChar 补充的字符 * @return 补充后的字符串 */ public static String padPre(CharSequence str, int minLength, char padChar) { @@ -2974,16 +2957,16 @@ public class StrUtil { /** * 补充字符串以满足最小长度 - * + * *
     	 * StrUtil.padAfter(null, *, *);//null
     	 * StrUtil.padAfter("1", 3, '0');//"100"
     	 * StrUtil.padAfter("123", 2, '0');//"23"
     	 * 
    - * - * @param str 字符串,如果为null,按照空串处理 + * + * @param str 字符串,如果为null,直接返回null * @param minLength 最小长度 - * @param padChar 补充的字符 + * @param padChar 补充的字符 * @return 补充后的字符串 */ public static String padAfter(CharSequence str, int minLength, char padChar) { @@ -3002,16 +2985,16 @@ public class StrUtil { /** * 补充字符串以满足最小长度 - * + * *
     	 * StrUtil.padAfter(null, *, *);//null
     	 * StrUtil.padAfter("1", 3, "ABC");//"1AB"
     	 * StrUtil.padAfter("123", 2, "ABC");//"23"
     	 * 
    - * - * @param str 字符串,如果为null,按照空串处理 + * + * @param str 字符串,如果为null,直接返回null * @param minLength 最小长度 - * @param padStr 补充的字符 + * @param padStr 补充的字符 * @return 补充后的字符串 * @since 4.3.2 */ @@ -3031,7 +3014,7 @@ public class StrUtil { /** * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串 - * + * *
     	 * StrUtil.center(null, *)   = null
     	 * StrUtil.center("", 4)     = "    "
    @@ -3041,7 +3024,7 @@ public class StrUtil {
     	 * StrUtil.center("a", 4)    = " a  "
     	 * 
    * - * @param str 字符串 + * @param str 字符串 * @param size 指定长度 * @return 补充后的字符串 * @since 4.3.2 @@ -3052,7 +3035,7 @@ public class StrUtil { /** * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串 - * + * *
     	 * StrUtil.center(null, *, *)     = null
     	 * StrUtil.center("", 4, ' ')     = "    "
    @@ -3064,8 +3047,8 @@ public class StrUtil {
     	 * StrUtil.center("abc", 7, ' ')   = "  abc  "
     	 * 
    * - * @param str 字符串 - * @param size 指定长度 + * @param str 字符串 + * @param size 指定长度 * @param padChar 两边补充的字符 * @return 补充后的字符串 * @since 4.3.2 @@ -3086,7 +3069,7 @@ public class StrUtil { /** * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串 - * + * *
     	 * StrUtil.center(null, *, *)     = null
     	 * StrUtil.center("", 4, " ")     = "    "
    @@ -3099,8 +3082,8 @@ public class StrUtil {
     	 * StrUtil.center("abc", 7, "")   = "  abc  "
     	 * 
    * - * @param str 字符串 - * @param size 指定长度 + * @param str 字符串 + * @param size 指定长度 * @param padStr 两边补充的字符串 * @return 补充后的字符串 */ @@ -3123,7 +3106,7 @@ public class StrUtil { /** * 创建StringBuilder对象 - * + * * @return StringBuilder对象 */ public static StringBuilder builder() { @@ -3132,7 +3115,7 @@ public class StrUtil { /** * 创建StrBuilder对象 - * + * * @return StrBuilder对象 * @since 4.0.1 */ @@ -3142,7 +3125,7 @@ public class StrUtil { /** * 创建StringBuilder对象 - * + * * @param capacity 初始大小 * @return StringBuilder对象 */ @@ -3152,7 +3135,7 @@ public class StrUtil { /** * 创建StrBuilder对象 - * + * * @param capacity 初始大小 * @return StrBuilder对象 * @since 4.0.1 @@ -3163,7 +3146,7 @@ public class StrUtil { /** * 创建StringBuilder对象 - * + * * @param strs 初始字符串列表 * @return StringBuilder对象 */ @@ -3177,7 +3160,7 @@ public class StrUtil { /** * 创建StrBuilder对象 - * + * * @param strs 初始字符串列表 * @return StrBuilder对象 */ @@ -3187,7 +3170,7 @@ public class StrUtil { /** * 获得StringReader - * + * * @param str 字符串 * @return StringReader */ @@ -3200,7 +3183,7 @@ public class StrUtil { /** * 获得StringWriter - * + * * @return StringWriter */ public static StringWriter getWriter() { @@ -3221,7 +3204,7 @@ public class StrUtil { * StrUtil.count("abba", "xxx") = 0 *
    * - * @param content 被查找的字符串 + * @param content 被查找的字符串 * @param strForSearch 需要查找的字符串 * @return 查找到的个数 */ @@ -3243,8 +3226,8 @@ public class StrUtil { /** * 统计指定内容中包含指定字符的数量 - * - * @param content 内容 + * + * @param content 内容 * @param charForSearch 被统计的字符 * @return 包含数量 */ @@ -3264,8 +3247,8 @@ public class StrUtil { /** * 将字符串切分为N等份 - * - * @param str 字符串 + * + * @param str 字符串 * @param partLength 每等份的长度 * @return 切分后的数组 * @since 3.0.6 @@ -3276,7 +3259,7 @@ public class StrUtil { } int len = str.length(); if (len < partLength) { - return new String[] { str.toString() }; + return new String[]{str.toString()}; } int part = NumberUtil.count(len, partLength); final String[] array = new String[part]; @@ -3290,8 +3273,8 @@ public class StrUtil { /** * 将给定字符串,变成 "xxx...xxx" 形式的字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param maxLength 最大长度 * @return 截取后的字符串 */ @@ -3311,7 +3294,7 @@ public class StrUtil { /** * 比较两个字符串,用于排序 - * + * *
     	 * StrUtil.compare(null, null, *)     = 0
     	 * StrUtil.compare(null , "a", true)  < 0
    @@ -3324,9 +3307,9 @@ public class StrUtil {
     	 * StrUtil.compare("a", "B", *)       > 0
     	 * StrUtil.compare("ab", "abc", *)    < 0
     	 * 
    - * - * @param str1 字符串1 - * @param str2 字符串2 + * + * @param str1 字符串1 + * @param str2 字符串2 * @param nullIsLess {@code null} 值是否排在前(null是否小于非空值) * @return 排序值。负数:str1 < str2,正数:str1 > str2, 0:str1 == str2 */ @@ -3345,7 +3328,7 @@ public class StrUtil { /** * 比较两个字符串,用于排序,大小写不敏感 - * + * *
     	 * StrUtil.compareIgnoreCase(null, null, *)     = 0
     	 * StrUtil.compareIgnoreCase(null , "a", true)  < 0
    @@ -3360,9 +3343,9 @@ public class StrUtil {
     	 * StrUtil.compareIgnoreCase("A", "b", *)       < 0
     	 * StrUtil.compareIgnoreCase("ab", "abc", *)    < 0
     	 * 
    - * - * @param str1 字符串1 - * @param str2 字符串2 + * + * @param str1 字符串1 + * @param str2 字符串2 * @param nullIsLess {@code null} 值是否排在前(null是否小于非空值) * @return 排序值。负数:str1 < str2,正数:str1 > str2, 0:str1 == str2 */ @@ -3382,7 +3365,7 @@ public class StrUtil { /** * 比较两个版本
    * null版本排在最小:即: - * + * *
     	 * StrUtil.compareVersion(null, "v1") < 0
     	 * StrUtil.compareVersion("v1", "v1")  = 0
    @@ -3393,7 +3376,7 @@ public class StrUtil {
     	 * StrUtil.compareVersion("1.13.0", "1.12.1c") > 0
     	 * StrUtil.compareVersion("V0.0.20170102", "V0.0.20170101") > 0
     	 * 
    - * + * * @param version1 版本1 * @param version2 版本2 * @return 排序值。负数:version1 < version2,正数:version1 > version2, 0:version1 == version2 @@ -3405,8 +3388,8 @@ public class StrUtil { /** * 指定范围内查找指定字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchChar 被查找的字符 * @return 位置 */ @@ -3416,10 +3399,10 @@ public class StrUtil { /** * 指定范围内查找指定字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchChar 被查找的字符 - * @param start 起始位置,如果小于0,从0开始查找 + * @param start 起始位置,如果小于0,从0开始查找 * @return 位置 */ public static int indexOf(final CharSequence str, char searchChar, int start) { @@ -3432,11 +3415,11 @@ public class StrUtil { /** * 指定范围内查找指定字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchChar 被查找的字符 - * @param start 起始位置,如果小于0,从0开始查找 - * @param end 终止位置,如果超过str.length()则默认查找到字符串末尾 + * @param start 起始位置,如果小于0,从0开始查找 + * @param end 终止位置,如果超过str.length()则默认查找到字符串末尾 * @return 位置 */ public static int indexOf(final CharSequence str, char searchChar, int start, int end) { @@ -3457,7 +3440,7 @@ public class StrUtil { /** * 指定范围内查找字符串,忽略大小写
    - * + * *
     	 * StrUtil.indexOfIgnoreCase(null, *, *)          = -1
     	 * StrUtil.indexOfIgnoreCase(*, null, *)          = -1
    @@ -3471,8 +3454,8 @@ public class StrUtil {
     	 * StrUtil.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
     	 * StrUtil.indexOfIgnoreCase("abc", "", 9)        = -1
     	 * 
    - * - * @param str 字符串 + * + * @param str 字符串 * @param searchStr 需要查找位置的字符串 * @return 位置 * @since 3.2.1 @@ -3483,7 +3466,7 @@ public class StrUtil { /** * 指定范围内查找字符串 - * + * *
     	 * StrUtil.indexOfIgnoreCase(null, *, *)          = -1
     	 * StrUtil.indexOfIgnoreCase(*, null, *)          = -1
    @@ -3497,8 +3480,8 @@ public class StrUtil {
     	 * StrUtil.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
     	 * StrUtil.indexOfIgnoreCase("abc", "", 9)        = -1
     	 * 
    - * - * @param str 字符串 + * + * @param str 字符串 * @param searchStr 需要查找位置的字符串 * @param fromIndex 起始位置 * @return 位置 @@ -3510,10 +3493,10 @@ public class StrUtil { /** * 指定范围内查找字符串 - * - * @param str 字符串 - * @param searchStr 需要查找位置的字符串 - * @param fromIndex 起始位置 + * + * @param str 字符串 + * @param searchStr 需要查找位置的字符串 + * @param fromIndex 起始位置 * @param ignoreCase 是否忽略大小写 * @return 位置 * @since 3.2.1 @@ -3549,8 +3532,8 @@ public class StrUtil { /** * 指定范围内查找字符串,忽略大小写 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchStr 需要查找位置的字符串 * @return 位置 * @since 3.2.1 @@ -3562,8 +3545,8 @@ public class StrUtil { /** * 指定范围内查找字符串,忽略大小写
    * fromIndex 为搜索起始位置,从后往前计数 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchStr 需要查找位置的字符串 * @param fromIndex 起始位置,从后往前计数 * @return 位置 @@ -3576,10 +3559,10 @@ public class StrUtil { /** * 指定范围内查找字符串
    * fromIndex 为搜索起始位置,从后往前计数 - * - * @param str 字符串 - * @param searchStr 需要查找位置的字符串 - * @param fromIndex 起始位置,从后往前计数 + * + * @param str 字符串 + * @param searchStr 需要查找位置的字符串 + * @param fromIndex 起始位置,从后往前计数 * @param ignoreCase 是否忽略大小写 * @return 位置 * @since 3.2.1 @@ -3616,9 +3599,9 @@ public class StrUtil { *

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

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

     	 * StrUtil.ordinalIndexOf(null, *, *)          = -1
     	 * StrUtil.ordinalIndexOf(*, null, *)          = -1
    @@ -3633,9 +3616,9 @@ public class StrUtil {
     	 * StrUtil.ordinalIndexOf("aabaabaa", "", 2)   = 0
     	 * 
    * - * @param str 被检查的字符串,可以为null + * @param str 被检查的字符串,可以为null * @param searchStr 被查找的字符串,可以为null - * @param ordinal 第几次出现的位置 + * @param ordinal 第几次出现的位置 * @return 查找到的位置 * @since 3.2.3 */ @@ -3659,14 +3642,14 @@ public class StrUtil { } // ------------------------------------------------------------------------------------------------------------------ Append and prepend + /** * 如果给定字符串不是以给定的一个或多个字符串为结尾,则在尾部添加结尾字符串
    * 不忽略大小写 * - * @param str 被检查的字符串 - * @param suffix 需要添加到结尾的字符串 + * @param str 被检查的字符串 + * @param suffix 需要添加到结尾的字符串 * @param suffixes 需要额外检查的结尾字符串,如果以这些中的一个为结尾,则不再添加 - * * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3678,10 +3661,9 @@ public class StrUtil { * 如果给定字符串不是以给定的一个或多个字符串为结尾,则在尾部添加结尾字符串
    * 忽略大小写 * - * @param str 被检查的字符串 - * @param suffix 需要添加到结尾的字符串 + * @param str 被检查的字符串 + * @param suffix 需要添加到结尾的字符串 * @param suffixes 需要额外检查的结尾字符串,如果以这些中的一个为结尾,则不再添加 - * * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3692,11 +3674,10 @@ public class StrUtil { /** * 如果给定字符串不是以给定的一个或多个字符串为结尾,则在尾部添加结尾字符串 * - * @param str 被检查的字符串 - * @param suffix 需要添加到结尾的字符串 + * @param str 被检查的字符串 + * @param suffix 需要添加到结尾的字符串 * @param ignoreCase 检查结尾时是否忽略大小写 - * @param suffixes 需要额外检查的结尾字符串,如果以这些中的一个为结尾,则不再添加 - * + * @param suffixes 需要额外检查的结尾字符串,如果以这些中的一个为结尾,则不再添加 * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3718,10 +3699,9 @@ public class StrUtil { * 如果给定字符串不是以给定的一个或多个字符串为开头,则在首部添加起始字符串
    * 不忽略大小写 * - * @param str 被检查的字符串 - * @param prefix 需要添加到首部的字符串 + * @param str 被检查的字符串 + * @param prefix 需要添加到首部的字符串 * @param prefixes 需要额外检查的首部字符串,如果以这些中的一个为起始,则不再添加 - * * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3733,10 +3713,9 @@ public class StrUtil { * 如果给定字符串不是以给定的一个或多个字符串为开头,则在首部添加起始字符串
    * 忽略大小写 * - * @param str 被检查的字符串 - * @param prefix 需要添加到首部的字符串 + * @param str 被检查的字符串 + * @param prefix 需要添加到首部的字符串 * @param prefixes 需要额外检查的首部字符串,如果以这些中的一个为起始,则不再添加 - * * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3747,11 +3726,10 @@ public class StrUtil { /** * 如果给定字符串不是以给定的一个或多个字符串为开头,则在首部添加起始字符串 * - * @param str 被检查的字符串 - * @param prefix 需要添加到首部的字符串 + * @param str 被检查的字符串 + * @param prefix 需要添加到首部的字符串 * @param ignoreCase 检查结尾时是否忽略大小写 - * @param prefixes 需要额外检查的首部字符串,如果以这些中的一个为起始,则不再添加 - * + * @param prefixes 需要额外检查的首部字符串,如果以这些中的一个为起始,则不再添加 * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3772,7 +3750,7 @@ public class StrUtil { /** * 反转字符串
    * 例如:abcd =》dcba - * + * * @param str 被反转的字符串 * @return 反转后的字符串 * @since 3.0.9 @@ -3784,10 +3762,10 @@ public class StrUtil { /** * 将已有字符串填充为规定长度,如果已有字符串超过这个长度则返回这个字符串
    * 字符填充于字符串前 - * - * @param str 被填充的字符串 + * + * @param str 被填充的字符串 * @param filledChar 填充的字符 - * @param len 填充长度 + * @param len 填充长度 * @return 填充后的字符串 * @since 3.1.2 */ @@ -3798,10 +3776,10 @@ public class StrUtil { /** * 将已有字符串填充为规定长度,如果已有字符串超过这个长度则返回这个字符串
    * 字符填充于字符串后 - * - * @param str 被填充的字符串 + * + * @param str 被填充的字符串 * @param filledChar 填充的字符 - * @param len 填充长度 + * @param len 填充长度 * @return 填充后的字符串 * @since 3.1.2 */ @@ -3811,11 +3789,11 @@ public class StrUtil { /** * 将已有字符串填充为规定长度,如果已有字符串超过这个长度则返回这个字符串 - * - * @param str 被填充的字符串 + * + * @param str 被填充的字符串 * @param filledChar 填充的字符 - * @param len 填充长度 - * @param isPre 是否填充在前 + * @param len 填充长度 + * @param isPre 是否填充在前 * @return 填充后的字符串 * @since 3.1.2 */ @@ -3832,12 +3810,12 @@ public class StrUtil { /** * 截取两个字符串的不同部分(长度一致),判断截取的子串是否相同
    * 任意一个字符串为null返回false - * - * @param str1 第一个字符串 - * @param start1 第一个字符串开始的位置 - * @param str2 第二个字符串 - * @param start2 第二个字符串开始的位置 - * @param length 截取长度 + * + * @param str1 第一个字符串 + * @param start1 第一个字符串开始的位置 + * @param str2 第二个字符串 + * @param start2 第二个字符串开始的位置 + * @param length 截取长度 * @param ignoreCase 是否忽略大小写 * @return 子串是否相同 * @since 3.2.1 @@ -3852,8 +3830,8 @@ public class StrUtil { /** * 字符串的每一个字符是否都与定义的匹配器匹配 - * - * @param value 字符串 + * + * @param value 字符串 * @param matcher 匹配器 * @return 是否全部匹配 * @since 3.2.3 @@ -3872,9 +3850,9 @@ public class StrUtil { /** * 替换字符串中的指定字符串,忽略大小写 - * - * @param str 字符串 - * @param searchStr 被查找的字符串 + * + * @param str 字符串 + * @param searchStr 被查找的字符串 * @param replacement 被替换的字符串 * @return 替换后的字符串 * @since 4.0.3 @@ -3885,9 +3863,9 @@ public class StrUtil { /** * 替换字符串中的指定字符串 - * - * @param str 字符串 - * @param searchStr 被查找的字符串 + * + * @param str 字符串 + * @param searchStr 被查找的字符串 * @param replacement 被替换的字符串 * @return 替换后的字符串 * @since 4.0.3 @@ -3898,11 +3876,11 @@ public class StrUtil { /** * 替换字符串中的指定字符串 - * - * @param str 字符串 - * @param searchStr 被查找的字符串 + * + * @param str 字符串 + * @param searchStr 被查找的字符串 * @param replacement 被替换的字符串 - * @param ignoreCase 是否忽略大小写 + * @param ignoreCase 是否忽略大小写 * @return 替换后的字符串 * @since 4.0.3 */ @@ -3912,12 +3890,12 @@ public class StrUtil { /** * 替换字符串中的指定字符串 - * - * @param str 字符串 - * @param fromIndex 开始位置(包括) - * @param searchStr 被查找的字符串 + * + * @param str 字符串 + * @param fromIndex 开始位置(包括) + * @param searchStr 被查找的字符串 * @param replacement 被替换的字符串 - * @param ignoreCase 是否忽略大小写 + * @param ignoreCase 是否忽略大小写 * @return 替换后的字符串 * @since 4.0.3 */ @@ -3959,10 +3937,10 @@ public class StrUtil { /** * 替换指定字符串的指定区间内字符为固定字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param startInclude 开始位置(包含) - * @param endExclude 结束位置(不包含) + * @param endExclude 结束位置(不包含) * @param replacedChar 被替换的字符 * @return 替换后的字符串 * @since 3.2.1 @@ -3996,9 +3974,9 @@ public class StrUtil { /** * 替换所有正则匹配的文本,并使用自定义函数决定如何替换 - * - * @param str 要替换的字符串 - * @param pattern 用于匹配的正则式 + * + * @param str 要替换的字符串 + * @param pattern 用于匹配的正则式 * @param replaceFun 决定如何替换的函数 * @return 替换后的字符串 * @see ReUtil#replaceAll(CharSequence, Pattern, Func1) @@ -4010,9 +3988,9 @@ public class StrUtil { /** * 替换所有正则匹配的文本,并使用自定义函数决定如何替换 - * - * @param str 要替换的字符串 - * @param regex 用于匹配的正则式 + * + * @param str 要替换的字符串 + * @param regex 用于匹配的正则式 * @param replaceFun 决定如何替换的函数 * @return 替换后的字符串 * @see ReUtil#replaceAll(CharSequence, String, Func1) @@ -4025,9 +4003,9 @@ public class StrUtil { /** * 替换指定字符串的指定区间内字符为"*" * - * @param str 字符串 + * @param str 字符串 * @param startInclude 开始位置(包含) - * @param endExclude 结束位置(不包含) + * @param endExclude 结束位置(不包含) * @return 替换后的字符串 * @since 4.1.14 */ @@ -4038,9 +4016,9 @@ public class StrUtil { /** * 替换字符字符数组中所有的字符为replacedStr
    * 提供的chars为所有需要被替换的字符,例如:"\r\n",则"\r"和"\n"都会被替换,哪怕他们单独存在 - * - * @param str 被检查的字符串 - * @param chars 需要替换的字符列表,用一个字符串表示这个字符列表 + * + * @param str 被检查的字符串 + * @param chars 需要替换的字符列表,用一个字符串表示这个字符列表 * @param replacedStr 替换成的字符串 * @return 新字符串 * @since 3.2.2 @@ -4054,9 +4032,9 @@ public class StrUtil { /** * 替换字符字符数组中所有的字符为replacedStr - * - * @param str 被检查的字符串 - * @param chars 需要替换的字符列表 + * + * @param str 被检查的字符串 + * @param chars 需要替换的字符列表 * @param replacedStr 替换成的字符串 * @return 新字符串 * @since 3.2.2 @@ -4082,7 +4060,7 @@ public class StrUtil { /** * 计算两个字符串的相似度 - * + * * @param str1 字符串1 * @param str2 字符串2 * @return 相似度 @@ -4094,9 +4072,9 @@ public class StrUtil { /** * 计算连个字符串的相似度百分比 - * - * @param str1 字符串1 - * @param str2 字符串2 + * + * @param str1 字符串1 + * @param str2 字符串2 * @param scale 相似度 * @return 相似度百分比 * @since 3.2.3 @@ -4110,10 +4088,10 @@ public class StrUtil { * 如果字符串为null,返回false
    * 如果给定的位置大于字符串长度,返回false
    * 如果给定的位置小于0,返回false - * - * @param str 字符串 + * + * @param str 字符串 * @param position 位置 - * @param c 需要对比的字符 + * @param c 需要对比的字符 * @return 字符串指定位置的字符是否与给定字符相同 * @since 3.3.1 */ @@ -4127,7 +4105,7 @@ public class StrUtil { /** * 给定字符串数组的总长度
    * null字符长度定义为0 - * + * * @param strs 字符串数组 * @return 总长度 * @since 4.0.1 @@ -4144,11 +4122,11 @@ public class StrUtil { * 循环位移指定位置的字符串为指定距离
    * 当moveLength大于0向右位移,小于0向左位移,0不位移
    * 当moveLength大于字符串长度时采取循环位移策略,即位移到头后从头(尾)位移,例如长度为10,位移13则表示位移3 - * - * @param str 字符串 + * + * @param str 字符串 * @param startInclude 起始位置(包括) - * @param endExclude 结束位置(不包括) - * @param moveLength 移动距离,负数表示左移,正数为右移 + * @param endExclude 结束位置(不包括) + * @param moveLength 移动距离,负数表示左移,正数为右移 * @return 位移后的字符串 * @since 4.0.7 */ @@ -4182,10 +4160,10 @@ public class StrUtil { /** * 生成随机UUID - * + * * @return UUID字符串 - * @since 4.0.10 * @see IdUtil#randomUUID() + * @since 4.0.10 */ public static String uuid() { return IdUtil.randomUUID(); @@ -4193,9 +4171,9 @@ public class StrUtil { /** * 连接多个字符串为一个 - * + * * @param isNullToEmpty 是否null转为"" - * @param strs 字符串数组 + * @param strs 字符串数组 * @return 连接后的字符串 * @since 4.1.0 */ @@ -4209,12 +4187,12 @@ public class StrUtil { /** * 给定字符串中的字母是否全部为大写,判断依据如下: - * + * *
     	 * 1. 大写字母包括A-Z
     	 * 2. 其它非字母的Unicode符都算作大写
     	 * 
    - * + * * @param str 被检查的字符串 * @return 是否全部为大写 * @since 4.2.2 @@ -4234,12 +4212,12 @@ public class StrUtil { /** * 给定字符串中的字母是否全部为小写,判断依据如下: - * + * *
     	 * 1. 小写字母包括a-z
     	 * 2. 其它非字母的Unicode符都算作小写
     	 * 
    - * + * * @param str 被检查的字符串 * @return 是否全部为小写 * @since 4.2.2 @@ -4267,11 +4245,11 @@ public class StrUtil { public static int length(CharSequence cs) { return cs == null ? 0 : cs.length(); } - + /** * 给定字符串转为bytes后的byte数(byte长度) - * - * @param cs 字符串 + * + * @param cs 字符串 * @param charset 编码 * @return byte长度 * @since 4.5.2 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java index 85cb22443..a4b3eb549 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java @@ -59,11 +59,7 @@ public class TypeUtil { if (null == field) { return null; } - Type type = field.getGenericType(); - if (null == type) { - type = field.getType(); - } - return type; + return field.getGenericType(); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 56e4ea049..e2ebfb6b5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -6,56 +6,94 @@ import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.net.URLDecoder; import cn.hutool.core.net.URLEncoder; +import cn.hutool.core.net.url.UrlQuery; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.net.URLDecoder; +import java.net.URLConnection; import java.net.URLStreamHandler; import java.nio.charset.Charset; +import java.util.Map; import java.util.jar.JarFile; /** - * 统一资源定位符相关工具类 + * URL(Uniform Resource Locator)统一资源定位符相关工具类 + * + *

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

    + * URL组成: + *
    + *   协议://主机名[:端口]/ 路径/[:参数] [?查询]#Fragment
    + *   protocol :// hostname[:port] / path / [:parameters][?query]#fragment
    + * 
    * * @author xiaoleilu - * */ public class URLUtil { - /** 针对ClassPath路径的伪协议前缀(兼容Spring): "classpath:" */ + /** + * 针对ClassPath路径的伪协议前缀(兼容Spring): "classpath:" + */ public static final String CLASSPATH_URL_PREFIX = "classpath:"; - /** URL 前缀表示文件: "file:" */ + /** + * URL 前缀表示文件: "file:" + */ public static final String FILE_URL_PREFIX = "file:"; - /** URL 前缀表示jar: "jar:" */ + /** + * URL 前缀表示jar: "jar:" + */ public static final String JAR_URL_PREFIX = "jar:"; - /** URL 前缀表示war: "war:" */ + /** + * URL 前缀表示war: "war:" + */ public static final String WAR_URL_PREFIX = "war:"; - /** URL 协议表示文件: "file" */ + /** + * URL 协议表示文件: "file" + */ public static final String URL_PROTOCOL_FILE = "file"; - /** URL 协议表示Jar文件: "jar" */ + /** + * URL 协议表示Jar文件: "jar" + */ public static final String URL_PROTOCOL_JAR = "jar"; - /** URL 协议表示zip文件: "zip" */ + /** + * URL 协议表示zip文件: "zip" + */ public static final String URL_PROTOCOL_ZIP = "zip"; - /** URL 协议表示WebSphere文件: "wsjar" */ + /** + * URL 协议表示WebSphere文件: "wsjar" + */ public static final String URL_PROTOCOL_WSJAR = "wsjar"; - /** URL 协议表示JBoss zip文件: "vfszip" */ + /** + * URL 协议表示JBoss zip文件: "vfszip" + */ public static final String URL_PROTOCOL_VFSZIP = "vfszip"; - /** URL 协议表示JBoss文件: "vfsfile" */ + /** + * URL 协议表示JBoss文件: "vfsfile" + */ public static final String URL_PROTOCOL_VFSFILE = "vfsfile"; - /** URL 协议表示JBoss VFS资源: "vfs" */ + /** + * URL 协议表示JBoss VFS资源: "vfs" + */ public static final String URL_PROTOCOL_VFS = "vfs"; - /** Jar路径以及内部文件路径的分界符: "!/" */ + /** + * Jar路径以及内部文件路径的分界符: "!/" + */ public static final String JAR_URL_SEPARATOR = "!/"; - /** WAR路径及内部文件路径分界符 */ + /** + * WAR路径及内部文件路径分界符 + */ public static final String WAR_URL_SEPARATOR = "*/"; /** @@ -71,7 +109,7 @@ public class URLUtil { /** * 通过一个字符串形式的URL地址创建URL对象 * - * @param url URL + * @param url URL * @param handler {@link URLStreamHandler} * @return URL对象 * @since 4.1.1 @@ -111,7 +149,7 @@ public class URLUtil { /** * 将URL字符串转换为URL对象,并做必要验证 * - * @param urlStr URL字符串 + * @param urlStr URL字符串 * @param handler {@link URLStreamHandler} * @return URL * @since 4.1.9 @@ -167,7 +205,7 @@ public class URLUtil { /** * 获得URL * - * @param path 相对给定 class所在的路径 + * @param path 相对给定 class所在的路径 * @param clazz 指定class * @return URL * @see ResourceUtil#getResource(String, Class) @@ -181,7 +219,7 @@ public class URLUtil { * * @param file URL对应的文件对象 * @return URL - * @exception UtilException MalformedURLException + * @throws UtilException MalformedURLException */ public static URL getURL(File file) { Assert.notNull(file, "File is null !"); @@ -197,7 +235,7 @@ public class URLUtil { * * @param files URL对应的文件对象 * @return URL - * @exception UtilException MalformedURLException + * @throws UtilException MalformedURLException */ public static URL[] getURLs(File... files) { final URL[] urls = new URL[files.length]; @@ -219,8 +257,8 @@ public class URLUtil { * @return 域名的URI * @since 4.6.9 */ - public static URI getHost(URL url){ - if(null == url){ + public static URI getHost(URL url) { + if (null == url) { return null; } @@ -234,12 +272,26 @@ public class URLUtil { /** * 补全相对路径 * - * @param baseUrl 基准URL + * @param baseUrl 基准URL * @param relativePath 相对URL * @return 相对路径 - * @exception UtilException MalformedURLException + * @throws UtilException MalformedURLException + * @deprecated 拼写错误,请使用{@link #completeUrl(String, String)} */ + @Deprecated public static String complateUrl(String baseUrl, String relativePath) { + return completeUrl(baseUrl, relativePath); + } + + /** + * 补全相对路径 + * + * @param baseUrl 基准URL + * @param relativePath 相对URL + * @return 相对路径 + * @throws UtilException MalformedURLException + */ + public static String completeUrl(String baseUrl, String relativePath) { baseUrl = normalize(baseUrl, false); if (StrUtil.isBlank(baseUrl)) { return null; @@ -260,7 +312,7 @@ public class URLUtil { * * @param url URL * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ public static String encodeAll(String url) { return encodeAll(url, CharsetUtil.CHARSET_UTF_8); @@ -270,12 +322,15 @@ public class URLUtil { * 编码URL
    * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。 * - * @param url URL - * @param charset 编码 + * @param url URL + * @param charset 编码,为null表示不编码 * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ public static String encodeAll(String url, Charset charset) throws UtilException { + if (null == charset) { + return url; + } try { return java.net.URLEncoder.encode(url, charset.toString()); } catch (UnsupportedEncodingException e) { @@ -290,7 +345,7 @@ public class URLUtil { * * @param url URL * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException * @since 3.1.2 */ public static String encode(String url) throws UtilException { @@ -304,7 +359,7 @@ public class URLUtil { * * @param url URL * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException * @since 3.1.2 */ public static String encodeQuery(String url) throws UtilException { @@ -316,7 +371,7 @@ public class URLUtil { * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
    * 此方法用于URL自动编码,类似于浏览器中键入地址自动编码,对于像类似于“/”的字符不再编码 * - * @param url 被编码内容 + * @param url 被编码内容 * @param charset 编码 * @return 编码后的字符 * @since 4.4.1 @@ -336,7 +391,7 @@ public class URLUtil { * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
    * 此方法用于POST请求中的请求体自动编码,转义大部分特殊字符 * - * @param url 被编码内容 + * @param url 被编码内容 * @param charset 编码 * @return 编码后的字符 * @since 4.4.1 @@ -356,10 +411,10 @@ public class URLUtil { * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
    * 此方法用于URL自动编码,类似于浏览器中键入地址自动编码,对于像类似于“/”的字符不再编码 * - * @param url URL + * @param url URL * @param charset 编码 * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ public static String encode(String url, String charset) throws UtilException { if (StrUtil.isEmpty(url)) { @@ -373,10 +428,10 @@ public class URLUtil { * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
    * 此方法用于POST请求中的请求体自动编码,转义大部分特殊字符 * - * @param url URL + * @param url URL * @param charset 编码 * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ public static String encodeQuery(String url, String charset) throws UtilException { return encodeQuery(url, StrUtil.isBlank(charset) ? CharsetUtil.defaultCharset() : CharsetUtil.charset(charset)); @@ -388,7 +443,7 @@ public class URLUtil { * * @param url URL * @return 解码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException * @since 3.1.2 */ public static String decode(String url) throws UtilException { @@ -396,38 +451,32 @@ public class URLUtil { } /** - * 解码application/x-www-form-urlencoded字符 + * 解码application/x-www-form-urlencoded字符
    + * 将%开头的16进制表示的内容解码。 * * @param content 被解码内容 - * @param charset 编码 + * @param charset 编码,null表示不解码 * @return 编码后的字符 * @since 4.4.1 */ public static String decode(String content, Charset charset) { if (null == charset) { - charset = CharsetUtil.defaultCharset(); + return content; } - return decode(content, charset.name()); + return URLDecoder.decode(content, charset); } /** - * 解码URL
    + * 解码application/x-www-form-urlencoded字符
    * 将%开头的16进制表示的内容解码。 * - * @param url URL + * @param content URL * @param charset 编码 * @return 解码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ - public static String decode(String url, String charset) throws UtilException { - if (StrUtil.isEmpty(url)) { - return url; - } - try { - return URLDecoder.decode(url, charset); - } catch (UnsupportedEncodingException e) { - throw new UtilException(e, "Unsupported encoding: [{}]", charset); - } + public static String decode(String content, String charset) throws UtilException { + return decode(content, CharsetUtil.charset(charset)); } /** @@ -435,7 +484,7 @@ public class URLUtil { * * @param uriStr URI路径 * @return path - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException */ public static String getPath(String uriStr) { URI uri; @@ -476,7 +525,7 @@ public class URLUtil { * * @param url URL * @return URI - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException */ public static URI toURI(URL url) throws UtilException { return toURI(url, false); @@ -485,10 +534,10 @@ public class URLUtil { /** * 转URL为URI * - * @param url URL + * @param url URL * @param isEncode 是否编码参数中的特殊字符(默认UTF-8编码) * @return URI - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException * @since 4.6.9 */ public static URI toURI(URL url, boolean isEncode) throws UtilException { @@ -504,7 +553,7 @@ public class URLUtil { * * @param location 字符串路径 * @return URI - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException */ public static URI toURI(String location) throws UtilException { return toURI(location, false); @@ -516,11 +565,11 @@ public class URLUtil { * @param location 字符串路径 * @param isEncode 是否编码参数中的特殊字符(默认UTF-8编码) * @return URI - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException * @since 4.6.9 */ public static URI toURI(String location, boolean isEncode) throws UtilException { - if(isEncode){ + if (isEncode) { location = encode(location); } try { @@ -590,7 +639,7 @@ public class URLUtil { /** * 获得Reader * - * @param url {@link URL} + * @param url {@link URL} * @param charset 编码 * @return {@link BufferedReader} * @since 3.2.1 @@ -636,7 +685,7 @@ public class URLUtil { * 1. 多个/替换为一个 * * - * @param url URL字符串 + * @param url URL字符串 * @param isEncodePath 是否对URL中path部分的中文和特殊字符做转义(不包括 http:, /和域名部分) * @return 标准化后的URL字符串 * @since 4.4.1 @@ -663,7 +712,7 @@ public class URLUtil { body = StrUtil.subPre(body, paramsSepIndex); } - if(StrUtil.isNotEmpty(body)){ + if (StrUtil.isNotEmpty(body)) { // 去除开头的\或者/ //noinspection ConstantConditions body = body.replaceAll("^[\\\\/]+", StrUtil.EMPTY); @@ -683,4 +732,100 @@ public class URLUtil { } return protocol + domain + StrUtil.nullToEmpty(path) + StrUtil.nullToEmpty(params); } + + /** + * 将Map形式的Form表单数据转换为Url参数形式
    + * paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")
    + * 会自动url编码键和值 + * + *
    +	 * key1=v1&key2=&key3=v3
    +	 * 
    + * + * @param paramMap 表单数据 + * @param charset 编码,编码为null表示不编码 + * @return url参数 + */ + public static String buildQuery(Map paramMap, Charset charset) { + return UrlQuery.of(paramMap).build(charset); + } + + /** + * 获取指定URL对应资源的内容长度,对于Http,其长度使用Content-Length头决定。 + * + * @param url URL + * @return 内容长度,未知返回-1 + * @throws IORuntimeException IO异常 + * @since 5.3.4 + */ + public static long getContentLength(URL url) throws IORuntimeException { + if (null == url) { + return -1; + } + + URLConnection conn = null; + try { + conn = url.openConnection(); + return conn.getContentLengthLong(); + } catch (IOException e) { + throw new IORuntimeException(e); + } finally { + if (conn instanceof HttpURLConnection) { + ((HttpURLConnection) conn).disconnect(); + } + } + } + + /** + * Data URI Scheme封装。data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,
    + * 目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。常用于将图片嵌入网页。 + * + *

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

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

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

    +	 *     data:[<mime type>][;charset=<charset>][;<encoding>],<encoded data>
    +	 * 
    + * + * @param mimeType 可选项(null表示无),数据类型(image/png、text/plain等) + * @param charset 可选项(null表示无),源文本的字符集编码方式 + * @param encoding 数据编码方式(US-ASCII,BASE64等) + * @param data 编码后的数据 + * @return Data URI字符串 + * @since 5.3.6 + */ + public static String getDataUri(String mimeType, Charset charset, String encoding, String data) { + final StringBuilder builder = StrUtil.builder("data:"); + if (StrUtil.isNotBlank(mimeType)) { + builder.append(mimeType); + } + if (null != charset) { + builder.append(";charset=").append(charset.name()); + } + if (StrUtil.isNotBlank(encoding)) { + builder.append(';').append(encoding); + } + builder.append(',').append(data); + + return builder.toString(); + } } \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java index 078879ad5..366f81763 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java @@ -6,18 +6,26 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.BiMap; import cn.hutool.core.map.MapUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.*; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; @@ -26,12 +34,20 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.beans.XMLDecoder; import java.beans.XMLEncoder; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; /** * XML工具类
    @@ -56,6 +72,11 @@ public class XmlUtil { */ private static String defaultDocumentBuilderFactory = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"; + /** + * 是否打开命名空间支持 + */ + private static boolean namespaceAware = true; + /** * 禁用默认的DocumentBuilderFactory,禁用后如果有第三方的实现(如oracle的xdb包中的xmlparse),将会自动加载实现。 */ @@ -63,6 +84,16 @@ public class XmlUtil { defaultDocumentBuilderFactory = null; } + /** + * 设置是否打开命名空间支持,默认打开 + * + * @param isNamespaceAware 是否命名空间支持 + * @since 5.3.1 + */ + synchronized public static void setNamespaceAware(boolean isNamespaceAware) { + namespaceAware = isNamespaceAware; + } + // -------------------------------------------------------------------------------------- Read /** @@ -164,7 +195,7 @@ public class XmlUtil { throw new IllegalArgumentException("XML content string is empty !"); } xmlStr = cleanInvalid(xmlStr); - return readXML(new InputSource(StrUtil.getReader(xmlStr))); + return readXML(StrUtil.getReader(xmlStr)); } /** @@ -249,7 +280,7 @@ public class XmlUtil { * @since 3.0.9 */ public static String toStr(Document doc, String charset, boolean isPretty) { - return toStr(doc, charset, isPretty,false); + return toStr(doc, charset, isPretty, false); } /** @@ -409,7 +440,7 @@ public class XmlUtil { * @param omitXmlDeclaration 是否输出 xml Declaration * @since 5.1.2 */ - public static void transform(Source source, Result result, String charset, int indent,boolean omitXmlDeclaration) { + public static void transform(Source source, Result result, String charset, int indent, boolean omitXmlDeclaration) { final TransformerFactory factory = TransformerFactory.newInstance(); try { final Transformer xformer = factory.newTransformer(); @@ -420,7 +451,7 @@ public class XmlUtil { if (StrUtil.isNotBlank(charset)) { xformer.setOutputProperty(OutputKeys.ENCODING, charset); } - if (omitXmlDeclaration){ + if (omitXmlDeclaration) { xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); } xformer.transform(source, result); @@ -475,7 +506,7 @@ public class XmlUtil { factory = DocumentBuilderFactory.newInstance(); } // 默认打开NamespaceAware,getElementsByTagNameNS可以使用命名空间 - factory.setNamespaceAware(true); + factory.setNamespaceAware(namespaceAware); return disableXXE(factory); } @@ -501,7 +532,7 @@ public class XmlUtil { */ public static Document createXml(String rootElementName, String namespace) { final Document doc = createXml(); - doc.appendChild(null == namespace ? doc.createElement(rootElementName) : doc.createElementNS(rootElementName, namespace)); + doc.appendChild(null == namespace ? doc.createElement(rootElementName) : doc.createElementNS(namespace, rootElementName)); return doc; } @@ -519,6 +550,17 @@ public class XmlUtil { return (null == doc) ? null : doc.getDocumentElement(); } + /** + * 获取节点所在的Document + * + * @param node 节点 + * @return {@link Document} + * @since 5.3.0 + */ + public static Document getOwnerDocument(Node node) { + return (node instanceof Document) ? (Document) node : node.getOwnerDocument(); + } + /** * 去除XML文本中的无效字符 * @@ -706,7 +748,31 @@ public class XmlUtil { * @since 3.2.0 */ public static Object getByXPath(String expression, Object source, QName returnType) { + NamespaceContext nsContext = null; + if (source instanceof Node) { + nsContext = new UniversalNamespaceCache((Node) source, false); + } + return getByXPath(expression, source, returnType, nsContext); + } + + /** + * 通过XPath方式读取XML节点等信息
    + * Xpath相关文章:
    + * https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
    + * https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/ + * + * @param expression XPath表达式 + * @param source 资源,可以是Docunent、Node节点等 + * @param returnType 返回类型,{@link javax.xml.xpath.XPathConstants} + * @param nsContext {@link NamespaceContext} + * @return 匹配返回类型的值 + * @since 5.3.1 + */ + public static Object getByXPath(String expression, Object source, QName returnType, NamespaceContext nsContext) { final XPath xPath = createXPath(); + if (null != nsContext) { + xPath.setNamespaceContext(nsContext); + } try { if (source instanceof InputSource) { return xPath.evaluate(expression, (InputSource) source, returnType); @@ -762,14 +828,18 @@ public class XmlUtil { /** * XML转Java Bean * - * @param bean类型 + * @param bean类型 * @param node XML节点 * @param bean bean类 * @return bean * @since 5.2.4 */ - public static T xmlToBean(Node node, Class bean){ - return BeanUtil.toBean(xmlToMap(node), bean); + public static T xmlToBean(Node node, Class bean) { + final Map map = xmlToMap(node); + if(null != map && map.size() == 1){ + return BeanUtil.toBean(map.get(bean.getSimpleName()), bean); + } + return BeanUtil.toBean(map, bean); } /** @@ -831,7 +901,7 @@ public class XmlUtil { final Map map = xmlToMap(childEle); if (MapUtil.isNotEmpty(map)) { newValue = map; - } else{ + } else { newValue = childEle.getTextContent(); } } else { @@ -857,7 +927,7 @@ public class XmlUtil { /** * 将Map转换为XML格式的字符串 * - * @param data Map类型数据 + * @param data Map类型数据 * @return XML格式的字符串 * @since 5.1.2 */ @@ -873,8 +943,8 @@ public class XmlUtil { * @return XML格式的字符串 * @since 5.1.2 */ - public static String mapToXmlStr(Map data,boolean omitXmlDeclaration) { - return toStr(mapToXml(data, "xml"),CharsetUtil.UTF_8,false,omitXmlDeclaration); + public static String mapToXmlStr(Map data, boolean omitXmlDeclaration) { + return toStr(mapToXml(data, "xml"), CharsetUtil.UTF_8, false, omitXmlDeclaration); } /** @@ -943,7 +1013,7 @@ public class XmlUtil { * @return XML格式的字符串 * @since 5.1.2 */ - public static String mapToXmlStr(Map data, String rootName, String namespace, String charset,boolean isPretty, boolean omitXmlDeclaration) { + public static String mapToXmlStr(Map data, String rootName, String namespace, String charset, boolean isPretty, boolean omitXmlDeclaration) { return toStr(mapToXml(data, rootName, namespace), charset, isPretty, omitXmlDeclaration); } @@ -956,7 +1026,6 @@ public class XmlUtil { * @since 4.0.9 */ public static Document mapToXml(Map data, String rootName) { - return mapToXml(data, rootName, null); } @@ -973,10 +1042,21 @@ public class XmlUtil { final Document doc = createXml(); final Element root = appendChild(doc, rootName, namespace); - mapToXml(doc, root, data); + appendMap(doc, root, data); return doc; } + /** + * 将Bean转换为XML + * + * @param bean Bean对象 + * @return XML + * @since 5.3.4 + */ + public static Document beanToXml(Object bean) { + return beanToXml(bean, null); + } + /** * 将Bean转换为XML * @@ -986,7 +1066,7 @@ public class XmlUtil { * @since 5.2.4 */ public static Document beanToXml(Object bean, String namespace) { - if(null == bean){ + if (null == bean) { return null; } return mapToXml(BeanUtil.beanToMap(bean), bean.getClass().getSimpleName(), namespace); @@ -1025,59 +1105,106 @@ public class XmlUtil { * @since 5.0.4 */ public static Element appendChild(Node node, String tagName, String namespace) { - final Document doc = (node instanceof Document) ? (Document) node : node.getOwnerDocument(); + final Document doc = getOwnerDocument(node); final Element child = (null == namespace) ? doc.createElement(tagName) : doc.createElementNS(namespace, tagName); node.appendChild(child); return child; } + /** + * 创建文本子节点 + * + * @param node 节点 + * @param text 文本 + * @return 子节点 + * @since 5.3.0 + */ + public static Node appendText(Node node, CharSequence text) { + return appendText(getOwnerDocument(node), node, text); + } // ---------------------------------------------------------------------------------------- Private method start /** - * 将Map转换为XML格式的字符串 + * 追加数据子节点,可以是Map、集合、文本 * - * @param doc {@link Document} - * @param element 节点 - * @param data Map类型数据 - * @since 4.0.8 + * @param doc {@link Document} + * @param node 节点 + * @param data 数据 */ @SuppressWarnings("rawtypes") - private static void mapToXml(Document doc, Element element, Map data) { - Element filedEle; - Object key; - for (Entry entry : data.entrySet()) { - key = entry.getKey(); - if (null == key) { - continue; - } - // key作为标签名,无值的节点作为空节点创建 - filedEle = doc.createElement(key.toString()); - element.appendChild(filedEle); - // value作为标签内的值。 - final Object value = entry.getValue(); - if (null == value) { - continue; - } - if (value instanceof List) { - for (Object listEle : (List) value) { - if (listEle instanceof Map) { - // 如果值依旧为map,递归继续 - mapToXml(doc, filedEle, (Map) listEle); - } else { - // 创建文本节点 - filedEle.appendChild(doc.createTextNode(value.toString())); - } - } - } else if (value instanceof Map) { - // 如果值依旧为map,递归继续 - mapToXml(doc, filedEle, (Map) value); - } else { - filedEle.appendChild(doc.createTextNode(value.toString())); + private static void append(Document doc, Node node, Object data) { + if (data instanceof Map) { + // 如果值依旧为map,递归继续 + appendMap(doc, node, (Map) data); + } else if (data instanceof Iterator) { + // 如果值依旧为map,递归继续 + appendIterator(doc, node, (Iterator) data); + } else if (data instanceof Iterable) { + // 如果值依旧为map,递归继续 + appendIterator(doc, node, ((Iterable) data).iterator()); + } else { + appendText(doc, node, data.toString()); + } + } + /** + * 追加Map数据子节点 + * + * @param doc {@link Document} + * @param node 当前节点 + * @param data Map类型数据 + * @since 4.0.8 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private static void appendMap(Document doc, Node node, Map data) { + data.forEach((key, value) -> { + if (null != key) { + final Element child = appendChild(node, key.toString()); + if (null != value) { + append(doc, child, value); + } + } + }); + } + + /** + * 追加集合节点 + * + * @param doc {@link Document} + * @param node 节点 + * @param data 数据 + */ + @SuppressWarnings("rawtypes") + private static void appendIterator(Document doc, Node node, Iterator data) { + final Node parentNode = node.getParentNode(); + boolean isFirst = true; + Object eleData; + while (data.hasNext()) { + eleData = data.next(); + if (isFirst) { + append(doc, node, eleData); + isFirst = false; + } else { + final Node cloneNode = node.cloneNode(false); + parentNode.appendChild(cloneNode); + append(doc, cloneNode, eleData); } } } + /** + * 追加文本节点 + * + * @param doc {@link Document} + * @param node 节点 + * @param text 文本内容 + * @return 增加的子节点,即Text节点 + * @since 5.3.0 + */ + private static Node appendText(Document doc, Node node, CharSequence text) { + return node.appendChild(doc.createTextNode(StrUtil.str(text))); + } + /** * 关闭XXE,避免漏洞攻击
    * see: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J @@ -1114,6 +1241,109 @@ public class XmlUtil { } return dbf; } + + /** + * 全局命名空间上下文
    + * 见:https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/ + */ + public static class UniversalNamespaceCache implements NamespaceContext { + private static final String DEFAULT_NS = "DEFAULT"; + private final BiMap prefixUri = new BiMap<>(new HashMap<>()); + + /** + * This constructor parses the document and stores all namespaces it can + * find. If toplevelOnly is true, only namespaces in the root are used. + * + * @param node source Node + * @param toplevelOnly restriction of the search to enhance performance + */ + public UniversalNamespaceCache(Node node, boolean toplevelOnly) { + examineNode(node.getFirstChild(), toplevelOnly); + } + + /** + * A single node is read, the namespace attributes are extracted and stored. + * + * @param node to examine + * @param attributesOnly, if true no recursion happens + */ + private void examineNode(Node node, boolean attributesOnly) { + final NamedNodeMap attributes = node.getAttributes(); + if(null != attributes){ + for (int i = 0; i < attributes.getLength(); i++) { + Node attribute = attributes.item(i); + storeAttribute(attribute); + } + } + + if (false == attributesOnly) { + final NodeList childNodes = node.getChildNodes(); + if(null != childNodes){ + Node item; + for (int i = 0; i < childNodes.getLength(); i++) { + item = childNodes.item(i); + if (item.getNodeType() == Node.ELEMENT_NODE) + examineNode(item, false); + } + } + } + } + + /** + * This method looks at an attribute and stores it, if it is a namespace + * attribute. + * + * @param attribute to examine + */ + private void storeAttribute(Node attribute) { + if(null == attribute){ + return; + } + // examine the attributes in namespace xmlns + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attribute.getNamespaceURI())) { + // Default namespace xmlns="uri goes here" + if (XMLConstants.XMLNS_ATTRIBUTE.equals(attribute.getNodeName())) { + prefixUri.put(DEFAULT_NS, attribute.getNodeValue()); + } else { + // The defined prefixes are stored here + prefixUri.put(attribute.getLocalName(), attribute.getNodeValue()); + } + } + + } + + /** + * This method is called by XPath. It returns the default namespace, if the + * prefix is null or "". + * + * @param prefix to search for + * @return uri + */ + @Override + public String getNamespaceURI(String prefix) { + if (prefix == null || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) { + return prefixUri.get(DEFAULT_NS); + } else { + return prefixUri.get(prefix); + } + } + + /** + * This method is not needed in this context, but can be implemented in a + * similar way. + */ + @Override + public String getPrefix(String namespaceURI) { + return prefixUri.getInverse().get(namespaceURI); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + // Not implemented + return null; + } + + } // ---------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 39b7335b2..36544e68d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -938,7 +938,6 @@ public class ZipUtil { addDir(subPath, out); } // 压缩目录下的子文件或目录 - //noinspection ConstantConditions for (File childFile : files) { zip(childFile, srcRootDir, out, filter); } diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index b1fa0d05d..6e458bece 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -4,14 +4,15 @@ import cn.hutool.core.annotation.Alias; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Console; import cn.hutool.core.map.MapUtil; +import lombok.Data; import lombok.Getter; import lombok.Setter; import org.junit.Assert; import org.junit.Test; import java.beans.PropertyDescriptor; +import java.io.Serializable; import java.lang.reflect.Type; import java.time.LocalDate; import java.time.LocalDateTime; @@ -74,6 +75,22 @@ public class BeanUtilTest { Assert.assertEquals(person.getOpenid(), "DFDFSDFWERWER"); } + @Test + public void toBeanTest(){ + SubPerson person = new SubPerson(); + person.setAge(14); + person.setOpenid("11213232"); + person.setName("测试A11"); + person.setSubName("sub名字"); + + final Map map = BeanUtil.toBean(person, Map.class); + Assert.assertEquals("测试A11", map.get("name")); + Assert.assertEquals(14, map.get("age")); + Assert.assertEquals("11213232", map.get("openid")); + // static属性应被忽略 + Assert.assertFalse(map.containsKey("SUBNAME")); + } + @Test public void mapToBeanIgnoreCaseTest() { HashMap map = CollUtil.newHashMap(); @@ -154,10 +171,19 @@ public class BeanUtilTest { person.setSlow(true); Map map = BeanUtil.beanToMap(person); - Console.log(map); Assert.assertEquals("sub名字", map.get("aliasSubName")); } + @Test + public void mapToBeanWithAliasTest() { + Map map = MapUtil.newHashMap(); + map.put("aliasSubName", "sub名字"); + map.put("slow", true); + + final SubPersonWithAlias subPersonWithAlias = BeanUtil.mapToBean(map, SubPersonWithAlias.class, false); + Assert.assertEquals("sub名字", subPersonWithAlias.getSubName()); + } + @Test public void beanToMapWithLocalDateTimeTest() { final LocalDateTime now = LocalDateTime.now(); @@ -205,12 +231,13 @@ public class BeanUtilTest { } @Test - public void copyProperties() { + public void copyPropertiesTest() { SubPerson person = new SubPerson(); person.setAge(14); person.setOpenid("11213232"); person.setName("测试A11"); person.setSubName("sub名字"); + SubPerson person1 = BeanUtil.copyProperties(person, SubPerson.class); Assert.assertEquals(14, person1.getAge()); Assert.assertEquals("11213232", person1.getOpenid()); @@ -323,9 +350,9 @@ public class BeanUtilTest { } @Test - public void beanToBeanTest(){ + public void beanToBeanTest() { // 修复对象无getter方法导致报错的问题 - Page page1=new Page(); + Page page1 = new Page(); BeanUtil.toBean(page1, Page.class); } @@ -341,4 +368,30 @@ public class BeanUtilTest { return this; } } + + @Test + public void copyBeanToBeanTest() { + // 测试在copyProperties方法中alias是否有效 + Food info = new Food(); + info.setBookID("0"); + info.setCode("123"); + HllFoodEntity entity = new HllFoodEntity(); + BeanUtil.copyProperties(info, entity); + Assert.assertEquals(info.getBookID(), entity.getBookId()); + Assert.assertEquals(info.getCode(), entity.getCode2()); + } + + @Data + public static class Food { + @Alias("bookId") + private String bookID; + private String code; + } + + @Data + public static class HllFoodEntity implements Serializable { + private String bookId; + @Alias("code") + private String code2; + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/clone/CloneTest.java b/hutool-core/src/test/java/cn/hutool/core/clone/CloneTest.java index e7f7f165d..569a32994 100644 --- a/hutool-core/src/test/java/cn/hutool/core/clone/CloneTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/clone/CloneTest.java @@ -1,5 +1,7 @@ package cn.hutool.core.clone; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.junit.Assert; import org.junit.Test; @@ -17,7 +19,10 @@ public class CloneTest { Cat cat = new Cat(); Cat cat2 = cat.clone(); Assert.assertEquals(cat, cat2); - + } + + @Test + public void cloneTest2(){ //继承CloneSupport类 Dog dog = new Dog(); Dog dog2 = dog.clone(); @@ -30,7 +35,8 @@ public class CloneTest { * @author Looly * */ - private static class Cat implements Cloneable{ + @Data + static class Cat implements Cloneable{ private String name = "miaomiao"; private int age = 2; @@ -42,35 +48,6 @@ public class CloneTest { throw new CloneRuntimeException(e); } } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + age; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Cat other = (Cat) obj; - if (age != other.age) { - return false; - } - if (name == null) { - return other.name == null; - } else return name.equals(other.name); - } } /** @@ -78,37 +55,10 @@ public class CloneTest { * @author Looly * */ - private static class Dog extends CloneSupport{ + @EqualsAndHashCode(callSuper = false) + @Data + static class Dog extends CloneSupport{ private String name = "wangwang"; private int age = 3; - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + age; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Dog other = (Dog) obj; - if (age != other.age) { - return false; - } - if (name == null) { - return other.name == null; - } else return name.equals(other.name); - } } } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java index d473f2f58..c3b98bd01 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java @@ -5,11 +5,14 @@ import cn.hutool.core.lang.Dict; import cn.hutool.core.lang.Editor; import cn.hutool.core.lang.Filter; import cn.hutool.core.map.MapUtil; +import lombok.AllArgsConstructor; +import lombok.Data; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; @@ -20,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.SortedSet; /** * 集合工具类单元测试 @@ -29,6 +33,11 @@ import java.util.Set; */ public class CollUtilTest { + @Test + public void isNotEmptyTest(){ + Assert.assertFalse(CollUtil.isNotEmpty((Collection) null)); + } + @Test public void newHashSetTest() { Set set = CollUtil.newHashSet((String[]) null); @@ -277,6 +286,46 @@ public class CollUtilTest { Assert.assertEquals("张三", list.get(2).getName()); } + @Test + public void fieldValueMapTest() { + List list = CollUtil.newArrayList(new TestBean("张三", 12, DateUtil.parse("2018-05-01")), // + new TestBean("李四", 13, DateUtil.parse("2018-03-01")), // + new TestBean("王五", 12, DateUtil.parse("2018-04-01"))// + ); + + final Map map = CollUtil.fieldValueMap(list, "name"); + Assert.assertEquals("李四", map.get("李四").getName()); + Assert.assertEquals("王五", map.get("王五").getName()); + Assert.assertEquals("张三", map.get("张三").getName()); + } + + @Test + public void fieldValueAsMapTest() { + List list = CollUtil.newArrayList(new TestBean("张三", 12, DateUtil.parse("2018-05-01")), // + new TestBean("李四", 13, DateUtil.parse("2018-03-01")), // + new TestBean("王五", 14, DateUtil.parse("2018-04-01"))// + ); + + final Map map = CollUtil.fieldValueAsMap(list, "name", "age"); + Assert.assertEquals(new Integer(12), map.get("张三")); + Assert.assertEquals(new Integer(13), map.get("李四")); + Assert.assertEquals(new Integer(14), map.get("王五")); + } + + @Test + public void emptyTest() { + final SortedSet emptySortedSet = CollUtil.empty(SortedSet.class); + Assert.assertEquals(Collections.emptySortedSet(), emptySortedSet); + + final Set emptySet = CollUtil.empty(Set.class); + Assert.assertEquals(Collections.emptySet(), emptySet); + + final List emptyList = CollUtil.empty(List.class); + Assert.assertEquals(Collections.emptyList(), emptyList); + } + + @Data + @AllArgsConstructor public static class TestBean { private String name; private int age; @@ -286,41 +335,6 @@ public class CollUtilTest { this.name = name; this.age = age; } - - public TestBean(String name, int age, Date createTime) { - this.name = name; - this.age = age; - this.createTime = createTime; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public Date getCreateTime() { - return createTime; - } - - public void setCreateTime(Date createTime) { - this.createTime = createTime; - } - - @Override - public String toString() { - return "TestBeans [name=" + name + ", age=" + age + "]"; - } } @Test @@ -574,8 +588,11 @@ public class CollUtilTest { public void containsAllTest() { ArrayList list1 = CollUtil.newArrayList(1, 2, 3, 4, 5); ArrayList list2 = CollUtil.newArrayList(5, 3, 1); - Assert.assertTrue(CollUtil.containsAll(list1, list2)); + + ArrayList list3 = CollUtil.newArrayList(1); + ArrayList list4 = CollUtil.newArrayList(); + Assert.assertTrue(CollUtil.containsAll(list3, list4)); } @Test @@ -600,4 +617,25 @@ public class CollUtilTest { Assert.assertEquals(3, map.get("c").intValue()); Assert.assertEquals(4, map.get("d").intValue()); } + + @Test + public void toMapTest(){ + Collection keys = CollUtil.newArrayList("a", "b", "c", "d"); + final Map map = CollUtil.toMap(keys, new HashMap<>(), (value)->"key" + value); + Assert.assertEquals("a", map.get("keya")); + Assert.assertEquals("b", map.get("keyb")); + Assert.assertEquals("c", map.get("keyc")); + Assert.assertEquals("d", map.get("keyd")); + } + + @Test + public void countMapTest() { + ArrayList list = CollUtil.newArrayList("a", "b", "c", "c", "a", "b", "d"); + Map countMap = CollUtil.countMap(list); + + Assert.assertEquals(Integer.valueOf(2), countMap.get("a")); + Assert.assertEquals(Integer.valueOf(2), countMap.get("b")); + Assert.assertEquals(Integer.valueOf(2), countMap.get("c")); + Assert.assertEquals(Integer.valueOf(1), countMap.get("d")); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java index 11351f461..0ab5d784a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java @@ -1,11 +1,10 @@ package cn.hutool.core.collection; -import java.util.ArrayList; -import java.util.Map; - import org.junit.Assert; import org.junit.Test; +import java.util.*; + /** * {@link IterUtil} 单元测试 * @author looly @@ -13,42 +12,56 @@ import org.junit.Test; */ public class IterUtilTest { - @Test - public void countMapTest() { - ArrayList list = CollUtil.newArrayList("a", "b", "c", "c", "a", "b", "d"); - Map countMap = IterUtil.countMap(list); - - Assert.assertEquals(Integer.valueOf(2), countMap.get("a")); - Assert.assertEquals(Integer.valueOf(2), countMap.get("b")); - Assert.assertEquals(Integer.valueOf(2), countMap.get("c")); - Assert.assertEquals(Integer.valueOf(1), countMap.get("d")); - } - @Test public void fieldValueMapTest() { ArrayList carList = CollUtil.newArrayList(new Car("123", "大众"), new Car("345", "奔驰"), new Car("567", "路虎")); - Map carNameMap = IterUtil.fieldValueMap(carList, "carNumber"); - + Map carNameMap = IterUtil.fieldValueMap(carList.iterator(), "carNumber"); + Assert.assertEquals("大众", carNameMap.get("123").getCarName()); Assert.assertEquals("奔驰", carNameMap.get("345").getCarName()); Assert.assertEquals("路虎", carNameMap.get("567").getCarName()); } - + @Test public void joinTest() { ArrayList list = CollUtil.newArrayList("1", "2", "3", "4"); - String join = IterUtil.join(list, ":"); + String join = IterUtil.join(list.iterator(), ":"); Assert.assertEquals("1:2:3:4", join); ArrayList list1 = CollUtil.newArrayList(1, 2, 3, 4); - String join1 = IterUtil.join(list1, ":"); + String join1 = IterUtil.join(list1.iterator(), ":"); Assert.assertEquals("1:2:3:4", join1); ArrayList list2 = CollUtil.newArrayList("1", "2", "3", "4"); - String join2 = IterUtil.join(list2, ":", "\"", "\""); + String join2 = IterUtil.join(list2.iterator(), ":", "\"", "\""); Assert.assertEquals("\"1\":\"2\":\"3\":\"4\"", join2); } + @Test + public void testToListMap() { + Map> expectedMap = new HashMap<>(); + expectedMap.put("a", Collections.singletonList("and")); + expectedMap.put("b", Arrays.asList("brave", "back")); + + Map> testMap = IterUtil.toListMap(Arrays.asList("and", "brave", "back"), + v -> v.substring(0, 1)); + Assert.assertEquals(testMap, expectedMap); + } + + @Test + public void testToMap() { + Map expectedMap = new HashMap<>(); + + Car bmw = new Car("123", "bmw"); + expectedMap.put("123", bmw); + + Car benz = new Car("456", "benz"); + expectedMap.put("456", benz); + + Map testMap = IterUtil.toMap(Arrays.asList(bmw, benz), Car::getCarNumber); + Assert.assertEquals(expectedMap, testMap); + } + public static class Car { private String carNumber; private String carName; diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 36ce84875..61aa2855c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -15,4 +15,13 @@ public class ListUtilTest { Assert.assertEquals("edit2", filter.get(1)); Assert.assertEquals("edit3", filter.get(2)); } + + @Test + public void indexOfAll() { + List a = ListUtil.toLinkedList("1", "2", "3", "4", "3", "2", "1"); + final int[] indexArray = ListUtil.indexOfAll(a, "2"::equals); + Assert.assertArrayEquals(new int[]{1,5}, indexArray); + final int[] indexArray2 = ListUtil.indexOfAll(a, "1"::equals); + Assert.assertArrayEquals(new int[]{0,6}, indexArray2); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java new file mode 100644 index 000000000..b1378be80 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java @@ -0,0 +1,16 @@ +package cn.hutool.core.comparator; + +import org.junit.Assert; +import org.junit.Test; + +public class CompareUtilTest { + + @Test + public void compareTest(){ + int compare = CompareUtil.compare(null, "a", true); + Assert.assertTrue(compare > 0); + + compare = CompareUtil.compare(null, "a", false); + Assert.assertTrue(compare < 0); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToBeanTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToBeanTest.java index b9ed303d9..166dc6dde 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToBeanTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToBeanTest.java @@ -1,15 +1,15 @@ package cn.hutool.core.convert; +import cn.hutool.core.bean.BeanUtilTest.SubPerson; +import cn.hutool.core.lang.Console; +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import cn.hutool.core.lang.Console; -import org.junit.Assert; -import org.junit.Test; - -import cn.hutool.core.bean.BeanUtilTest.SubPerson; - /** * 类型转换工具单元测试
    * 转换为数组 @@ -45,6 +45,12 @@ public class ConvertToBeanTest { Assert.assertEquals("测试A11", map.get("name")); Assert.assertEquals("14", map.get("age")); Assert.assertEquals("11213232", map.get("openid")); + + final LinkedHashMap map2 = Convert.convert( + new TypeReference>() {}, person); + Assert.assertEquals("测试A11", map2.get("name")); + Assert.assertEquals("14", map2.get("age")); + Assert.assertEquals("11213232", map2.get("openid")); } @Test diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToCollectionTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToCollectionTest.java index 778a3fbff..eb735c110 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToCollectionTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToCollectionTest.java @@ -1,5 +1,10 @@ package cn.hutool.core.convert; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; @@ -7,12 +12,6 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import org.junit.Assert; -import org.junit.Test; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.TypeReference; - /** * 转换为集合测试 * @@ -107,7 +106,7 @@ public class ConvertToCollectionTest { public void numberToListTest() { Integer i = 1; ArrayList list = Convert.convert(ArrayList.class, i); - Assert.assertTrue(i == list.get(0)); + Assert.assertSame(i, list.get(0)); BigDecimal b = BigDecimal.ONE; ArrayList list2 = Convert.convert(ArrayList.class, b); diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java new file mode 100644 index 000000000..f8b30cf26 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java @@ -0,0 +1,34 @@ +package cn.hutool.core.convert; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicLong; + +public class ConvertToNumberTest { + @Test + public void dateToLongTest(){ + final DateTime date = DateUtil.parse("2020-05-17 12:32:00"); + final Long dateLong = Convert.toLong(date); + assert date != null; + Assert.assertEquals(date.getTime(), dateLong.longValue()); + } + + @Test + public void dateToIntTest(){ + final DateTime date = DateUtil.parse("2020-05-17 12:32:00"); + final Integer dateInt = Convert.toInt(date); + assert date != null; + Assert.assertEquals((int)date.getTime(), dateInt.intValue()); + } + + @Test + public void dateToAtomicLongTest(){ + final DateTime date = DateUtil.parse("2020-05-17 12:32:00"); + final AtomicLong dateLong = Convert.convert(AtomicLong.class, date); + assert date != null; + Assert.assertEquals(date.getTime(), dateLong.longValue()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/MapConvertTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/MapConvertTest.java index 36b409627..01cd00b2d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/MapConvertTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/MapConvertTest.java @@ -1,15 +1,13 @@ package cn.hutool.core.convert; +import cn.hutool.core.map.MapBuilder; +import org.junit.Assert; +import org.junit.Test; + import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import org.junit.Assert; -import org.junit.Test; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.map.MapBuilder; - /** * Map转换单元测试 * @@ -31,7 +29,10 @@ public class MapConvertTest { @Test public void mapToMapTest() { - Map srcMap = MapBuilder.create(new HashMap()).put("name", "AAA").put("age", 45).map(); + Map srcMap = MapBuilder + .create(new HashMap()) + .put("name", "AAA") + .put("age", 45).map(); LinkedHashMap map = Convert.convert(LinkedHashMap.class, srcMap); Assert.assertEquals("AAA", map.get("name")); diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/NumberWordFormatTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/NumberWordFormatTest.java index fdeb94091..8eafddea1 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/NumberWordFormatTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/NumberWordFormatTest.java @@ -3,16 +3,14 @@ package cn.hutool.core.convert; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.convert.NumberWordFormater; - public class NumberWordFormatTest { @Test public void formatTest() { - String format = NumberWordFormater.format(100.23); + String format = NumberWordFormatter.format(100.23); Assert.assertEquals("ONE HUNDRED AND CENTS TWENTY THREE ONLY", format); - String format2 = NumberWordFormater.format("2100.00"); + String format2 = NumberWordFormatter.format("2100.00"); Assert.assertEquals("TWO THOUSAND ONE HUNDRED AND CENTS ONLY", format2); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java index f5f10b30d..5795b7897 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java @@ -67,12 +67,12 @@ public class DateTimeTest { // 默认情况下DateTime为可变对象 DateTime offsite = dateTime.offset(DateField.YEAR, 0); - Assert.assertTrue(offsite == dateTime); + Assert.assertSame(offsite, dateTime); // 设置为不可变对象后变动将返回新对象 dateTime.setMutable(false); offsite = dateTime.offset(DateField.YEAR, 0); - Assert.assertFalse(offsite == dateTime); + Assert.assertNotSame(offsite, dateTime); } @Test @@ -86,6 +86,7 @@ public class DateTimeTest { @Test public void monthTest() { + //noinspection ConstantConditions int month = DateUtil.parse("2017-07-01").month(); Assert.assertEquals(6, month); } @@ -93,6 +94,7 @@ public class DateTimeTest { @Test public void weekOfYearTest() { DateTime date = DateUtil.parse("2016-12-27"); + //noinspection ConstantConditions Assert.assertEquals(2016, date.year()); //跨年的周返回的总是1 Assert.assertEquals(1, date.weekOfYear()); diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 3688a7f8b..73af5b1f6 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.BetweenFormater.Level; import cn.hutool.core.date.format.FastDateFormat; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.text.SimpleDateFormat; @@ -329,38 +330,46 @@ public class DateUtilTest { Assert.assertEquals("20190321", ymd); } - @SuppressWarnings("ConstantConditions") @Test public void parseTest5() { // 测试时间解析 + //noinspection ConstantConditions String time = DateUtil.parse("22:12:12").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("22:12:12", time); + //noinspection ConstantConditions time = DateUtil.parse("2:12:12").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:12:12", time); + //noinspection ConstantConditions time = DateUtil.parse("2:2:12").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:02:12", time); + //noinspection ConstantConditions time = DateUtil.parse("2:2:1").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:02:01", time); + //noinspection ConstantConditions time = DateUtil.parse("22:2:1").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("22:02:01", time); + //noinspection ConstantConditions time = DateUtil.parse("2:22:1").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:22:01", time); // 测试两位时间解析 + //noinspection ConstantConditions time = DateUtil.parse("2:22").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:22:00", time); + //noinspection ConstantConditions time = DateUtil.parse("12:22").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("12:22:00", time); + //noinspection ConstantConditions time = DateUtil.parse("12:2").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("12:02:00", time); } - @SuppressWarnings("ConstantConditions") @Test public void parseTest6() { String str = "Tue Jun 4 16:25:15 +0800 2019"; DateTime dateTime = DateUtil.parse(str); + assert dateTime != null; Assert.assertEquals("2019-06-04 16:25:15", dateTime.toString()); } @@ -371,12 +380,20 @@ public class DateUtilTest { Assert.assertEquals("2019-06-01 19:45:43", dateTime.toString()); } - @SuppressWarnings("ConstantConditions") + @Test + @Ignore + public void parseTest8() { + String str = "2020-04-24 9:00:00"; + DateTime dateTime = DateUtil.parse(str); + Assert.assertEquals("2019-06-01 19:45:43", dateTime.toString()); + } + @Test public void parseAndOffsetTest() { // 检查UTC时间偏移是否准确 String str = "2019-09-17T13:26:17.948Z"; DateTime dateTime = DateUtil.parse(str); + assert dateTime != null; Assert.assertEquals("2019-09-17 13:26:17", dateTime.toString()); DateTime offset = DateUtil.offsetHour(dateTime, 8); @@ -459,7 +476,6 @@ public class DateUtilTest { Assert.assertEquals(dt1, dt2); } - @SuppressWarnings("ConstantConditions") @Test public void parseUTCTest() { String dateStr1 = "2018-09-13T05:34:31Z"; @@ -489,11 +505,13 @@ public class DateUtilTest { dateStr1 = "2018-09-13T13:34:34+0800"; dt = DateUtil.parse(dateStr1); + assert dt != null; dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00")); Assert.assertEquals("2018-09-13 13:34:34", dateStr); dateStr1 = "2018-09-13T13:34:35+08:00"; dt = DateUtil.parse(dateStr1); + assert dt != null; dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00")); Assert.assertEquals("2018-09-13 13:34:35", dateStr); @@ -511,11 +529,13 @@ public class DateUtilTest { dateStr1 = "2018-09-13T13:34:38.999+0800"; dt = DateUtil.parse(dateStr1); + assert dt != null; dateStr = dt.toString(simpleDateFormat); Assert.assertEquals("2018-09-13 13:34:38.999", dateStr); dateStr1 = "2018-09-13T13:34:39.999+08:00"; dt = DateUtil.parse(dateStr1); + assert dt != null; dateStr = dt.toString(simpleDateFormat); Assert.assertEquals("2018-09-13 13:34:39.999", dateStr); } @@ -555,6 +575,13 @@ public class DateUtilTest { Assert.assertEquals("2019-05-16 17:57:18", Objects.requireNonNull(time).toString()); } + @Test + public void parseISOTest() { + String dateStr = "2020-04-23T02:31:00.000Z"; + DateTime time = DateUtil.parse(dateStr); + Assert.assertEquals("2020-04-23 02:31:00", Objects.requireNonNull(time).toString()); + } + @Test public void endOfYearTest() { DateTime date = DateUtil.date(); @@ -688,6 +715,13 @@ public class DateUtilTest { Assert.assertEquals(18, age); } + @Test(expected = IllegalArgumentException.class) + public void ageTest2(){ + String d1 = "2019-02-29"; + String d2 = "2018-02-28"; + DateUtil.age(DateUtil.parseDate(d1), DateUtil.parseDate(d2)); + } + @Test public void isExpiredTest(){ DateTime startDate = DateUtil.parse("2019-12-01 17:02:30"); diff --git a/hutool-core/src/test/java/cn/hutool/core/img/FontUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/img/FontUtilTest.java new file mode 100644 index 000000000..c9f1cad3e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/img/FontUtilTest.java @@ -0,0 +1,15 @@ +package cn.hutool.core.img; + +import org.junit.Assert; +import org.junit.Test; + +import java.awt.Font; + +public class FontUtilTest { + + @Test + public void createFontTest(){ + final Font font = FontUtil.createFont(); + Assert.assertNotNull(font); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java index e35cc04ac..6e2036e22 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java @@ -4,6 +4,9 @@ import cn.hutool.core.io.FileUtil; import org.junit.Ignore; import org.junit.Test; +import java.awt.Color; +import java.awt.Font; + public class ImgTest { @Test @@ -17,10 +20,30 @@ public class ImgTest { public void compressTest() { Img.from(FileUtil.file("f:/test/4347273249269e3fb272341acc42d4e.jpg")).setQuality(0.8).write(FileUtil.file("f:/test/test_dest.jpg")); } + + @Test + @Ignore + public void writeTest() { + final Img from = Img.from(FileUtil.file("d:/test/81898311-001d6100-95eb-11ea-83c2-a14d7b1010bd.png")); + ImgUtil.write(from.getImg(), FileUtil.file("d:/test/dest.jpg")); + } @Test @Ignore public void roundTest() { Img.from(FileUtil.file("e:/pic/face.jpg")).round(0.5).write(FileUtil.file("e:/pic/face_round.png")); } + + @Test + @Ignore + public void pressTextTest() { + Img.from(FileUtil.file("d:/test/617180969474805871.jpg")) + .setPositionBaseCentre(false) + .pressText("版权所有", Color.RED, // + new Font("黑体", Font.BOLD, 100), // + 0, // + 100, // + 1f) + .write(FileUtil.file("d:/test/test2_result.png")); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java index 7ab6944e0..b27be1f37 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java @@ -1,20 +1,19 @@ package cn.hutool.core.img; +import cn.hutool.core.io.FileUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import javax.imageio.ImageIO; import java.awt.Color; import java.awt.Font; import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; +import java.io.File; import java.io.IOException; -import javax.imageio.ImageIO; - -import cn.hutool.core.lang.Console; -import org.junit.Ignore; -import org.junit.Test; - -import cn.hutool.core.io.FileUtil; - public class ImgUtilTest { @Test @@ -63,20 +62,23 @@ public class ImgUtilTest { @Test @Ignore public void pressImgTest() { - ImgUtil.pressImage(FileUtil.file("d:/picTest/1.jpg"), FileUtil.file("d:/picTest/dest.jpg"), ImgUtil.read(FileUtil.file("d:/picTest/1432613.jpg")), 0, 0, 0.1f); + ImgUtil.pressImage( + FileUtil.file("d:/test/617180969474805871.jpg"), + FileUtil.file("d:/test/dest.png"), + ImgUtil.read(FileUtil.file("d:/test/vbbb.png")), 0, 0, 0.9f); } @Test @Ignore public void pressTextTest() { ImgUtil.pressText(// - FileUtil.file("e:/pic/face.jpg"), // - FileUtil.file("e:/pic/test2_result.png"), // - "版权所有", Color.WHITE, // + FileUtil.file("d:/test/617180969474805871.jpg"), // + FileUtil.file("d:/test/test2_result.png"), // + "版权所有", Color.RED, // new Font("黑体", Font.BOLD, 100), // 0, // 0, // - 0.8f); + 1f); } @Test @@ -100,7 +102,8 @@ public class ImgUtilTest { @Test @Ignore public void compressTest() { - ImgUtil.compress(FileUtil.file("e:/pic/1111.png"), FileUtil.file("e:/pic/1111_target.jpg"), 0.8f); + ImgUtil.compress(FileUtil.file("d:/test/dest.png"), + FileUtil.file("d:/test/1111_target.jpg"), 0.1f); } @Test @@ -109,4 +112,25 @@ public class ImgUtilTest { BufferedImage image = ImgUtil.copyImage(ImgUtil.read("f:/pic/test.png"), BufferedImage.TYPE_INT_RGB); ImgUtil.write(image, FileUtil.file("f:/pic/test_dest.jpg")); } + + @Test + public void toHexTest(){ + final String s = ImgUtil.toHex(Color.RED); + Assert.assertEquals("#FF0000", s); + } + + @Test + @Ignore + public void backgroundRemovalTest() { + // 图片 背景 换成 透明的 + ImgUtil.backgroundRemoval( + "d:/test/617180969474805871.jpg", + "d:/test/2.jpg", 10); + + // 图片 背景 换成 红色的 + ImgUtil.backgroundRemoval(new File( + "d:/test/617180969474805871.jpg"), + new File("d:/test/3.jpg"), + new Color(200, 0, 0), 10); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 6154baab3..a5c240e0a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -1,18 +1,17 @@ package cn.hutool.core.io; +import cn.hutool.core.io.file.LineSeparator; +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.CharsetUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import cn.hutool.core.io.file.LineSeparator; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.CharsetUtil; - /** * {@link FileUtil} 单元测试类 * @@ -87,13 +86,31 @@ public class FileUtilTest { @Test @Ignore - public void copyFilesFromDir() { + public void copyFilesFromDirTest() { File srcFile = FileUtil.file("D:\\驱动"); File destFile = FileUtil.file("d:\\驱动备份"); FileUtil.copyFilesFromDir(srcFile, destFile, true); } + @Test + @Ignore + public void copyDirTest() { + File srcFile = FileUtil.file("D:\\test"); + File destFile = FileUtil.file("E:\\"); + + FileUtil.copy(srcFile, destFile, true); + } + + @Test + @Ignore + public void moveDirTest() { + File srcFile = FileUtil.file("E:\\test2"); + File destFile = FileUtil.file("D:\\"); + + FileUtil.move(srcFile, destFile, true); + } + @Test public void equlasTest() { // 源文件和目标文件都不存在 diff --git a/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java new file mode 100644 index 000000000..36b2d72bc --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java @@ -0,0 +1,13 @@ +package cn.hutool.core.io.resource; + +import org.junit.Assert; +import org.junit.Test; + +public class ResourceUtilTest { + + @Test + public void readXmlTest(){ + final String str = ResourceUtil.readUtf8Str("test.xml"); + Assert.assertNotNull(str); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index b9e12e5ca..6a4c73264 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -1,12 +1,10 @@ package cn.hutool.core.lang; -import org.junit.Assert; -import org.junit.Test; - import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; -import cn.hutool.core.lang.Range; +import org.junit.Assert; +import org.junit.Test; /** * {@link Range} 单元测试 @@ -20,16 +18,11 @@ public class RangeTest { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-02"); - final Range range = new Range(start, end, new Range.Steper(){ - - @Override - public DateTime step(DateTime current, DateTime end, int index) { - if(current.isAfterOrEquals(end)) { - return null; - } - return current.offsetNew(DateField.DAY_OF_YEAR, 1); + final Range range = new Range<>(start, end, (current, end1, index) -> { + if (current.isAfterOrEquals(end1)) { + return null; } - + return current.offsetNew(DateField.DAY_OF_YEAR, 1); }); Assert.assertTrue(range.hasNext()); @@ -41,14 +34,7 @@ public class RangeTest { @Test public void intRangeTest() { - final Range range = new Range(1, 1, new Range.Steper(){ - - @Override - public Integer step(Integer current, Integer end, int index) { - return current >= end ? null : current +10; - } - - }); + final Range range = new Range<>(1, 1, (current, end, index) -> current >= end ? null : current + 10); Assert.assertTrue(range.hasNext()); Assert.assertEquals(Integer.valueOf(1), range.next()); diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java new file mode 100644 index 000000000..bac03887b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java @@ -0,0 +1,46 @@ +package cn.hutool.core.lang; + +import cn.hutool.core.thread.ThreadUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SimpleCacheTest { + + @Before + public void putTest(){ + final SimpleCache cache = new SimpleCache<>(); + ThreadUtil.execute(()->cache.put("key1", "value1")); + ThreadUtil.execute(()->cache.get("key1")); + ThreadUtil.execute(()->cache.put("key2", "value2")); + ThreadUtil.execute(()->cache.get("key2")); + ThreadUtil.execute(()->cache.put("key3", "value3")); + ThreadUtil.execute(()->cache.get("key3")); + ThreadUtil.execute(()->cache.put("key4", "value4")); + ThreadUtil.execute(()->cache.get("key4")); + ThreadUtil.execute(()->cache.get("key5", ()->"value5")); + + cache.get("key5", ()->"value5"); + } + + @Test + public void getTest(){ + final SimpleCache cache = new SimpleCache<>(); + cache.put("key1", "value1"); + cache.get("key1"); + cache.put("key2", "value2"); + cache.get("key2"); + cache.put("key3", "value3"); + cache.get("key3"); + cache.put("key4", "value4"); + cache.get("key4"); + cache.get("key5", ()->"value5"); + + Assert.assertEquals("value1", cache.get("key1")); + Assert.assertEquals("value2", cache.get("key2")); + Assert.assertEquals("value3", cache.get("key3")); + Assert.assertEquals("value4", cache.get("key4")); + Assert.assertEquals("value5", cache.get("key5")); + Assert.assertEquals("value6", cache.get("key6", ()-> "value6")); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java new file mode 100644 index 000000000..1a9d0a434 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java @@ -0,0 +1,51 @@ +package cn.hutool.core.lang; + +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.thread.ThreadUtil; +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +public class SingletonTest { + + @Test + public void getTest(){ + // 此测试中使用1000个线程获取单例对象,其间对象只被创建一次 + ThreadUtil.concurrencyTest(1000, ()-> Singleton.get(TestBean.class)); + } + + @Data + static class TestBean{ + private static volatile TestBean testSingleton; + + public TestBean(){ + if(null != testSingleton){ + throw new UtilException("单例测试中,对象被创建了两次!"); + } + testSingleton = this; + } + + private String name; + private String age; + } + + /** + * 测试单例构建属性锁死问题 + * C构建单例时候,同时构建B,此时在SimpleCache中会有写锁竞争(写入C时获取了写锁,此时要写入B,也要获取写锁) + */ + @Test(timeout = 1000L) + public void reentrantTest(){ + final C c = Singleton.get(C.class); + Assert.assertEquals("aaa", c.getB().getA()); + } + + @Data + static class B{ + private String a = "aaa"; + } + + @Data + static class C{ + private B b = Singleton.get(B.class); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeSearchTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeSearchTest.java new file mode 100644 index 000000000..67e19ac04 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeSearchTest.java @@ -0,0 +1,37 @@ +package cn.hutool.core.lang.tree; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class TreeSearchTest { + static List> all_menu=new ArrayList<>(); + static { + /* + * root + * /module-A + * /module-A-menu-1 + * /module-B + * /module-B-menu-1 + * /module-B-menu-2 + */ + all_menu.add(new TreeNode<>(1L, 0L, "root", 0L)); + all_menu.add(new TreeNode<>(2L,1L,"module-A",0L)); + all_menu.add(new TreeNode<>(3L,1L,"module-B",0L)); + all_menu.add(new TreeNode<>(4L,2L,"module-A-menu-1",0L)); + all_menu.add(new TreeNode<>(5L,3L,"module-B-menu-1",0L)); + all_menu.add(new TreeNode<>(6L,3L,"module-B-menu-2",0L)); + } + + @Test + public void searchNode() { + List> treeItems=TreeUtil.build(all_menu, 0L); + + Tree tree=treeItems.get(0); + Tree searchResult=tree.getNode(3L); + + Assert.assertEquals("module-B", searchResult.getName()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/map/BiMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/BiMapTest.java new file mode 100644 index 000000000..308f9e4ee --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/BiMapTest.java @@ -0,0 +1,22 @@ +package cn.hutool.core.map; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class BiMapTest { + + @Test + public void getTest(){ + BiMap biMap = new BiMap<>(new HashMap<>()); + biMap.put("aaa", 111); + biMap.put("bbb", 222); + + Assert.assertEquals(new Integer(111), biMap.get("aaa")); + Assert.assertEquals(new Integer(222), biMap.get("bbb")); + + Assert.assertEquals("aaa", biMap.getKey(111)); + Assert.assertEquals("bbb", biMap.getKey(222)); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/map/TableMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/TableMapTest.java new file mode 100644 index 000000000..9fc3bf72b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/TableMapTest.java @@ -0,0 +1,20 @@ +package cn.hutool.core.map; + +import org.junit.Assert; +import org.junit.Test; + +public class TableMapTest { + + @Test + public void getTest(){ + TableMap tableMap = new TableMap<>(16); + tableMap.put("aaa", 111); + tableMap.put("bbb", 222); + + Assert.assertEquals(new Integer(111), tableMap.get("aaa")); + Assert.assertEquals(new Integer(222), tableMap.get("bbb")); + + Assert.assertEquals("aaa", tableMap.getKey(111)); + Assert.assertEquals("bbb", tableMap.getKey(222)); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/map/TolerantMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/TolerantMapTest.java index dba466118..b6ed7cbe2 100644 --- a/hutool-core/src/test/java/cn/hutool/core/map/TolerantMapTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/map/TolerantMapTest.java @@ -9,32 +9,32 @@ import java.util.HashMap; public class TolerantMapTest { - private final TolerantMap map = TolerantMap.of(new HashMap<>(), "default"); + private final TolerantMap map = TolerantMap.of(new HashMap<>(), "default"); - @Before - public void before() { - map.put("monday", "星期一"); - map.put("tuesday", "星期二"); - } + @Before + public void before() { + map.put("monday", "星期一"); + map.put("tuesday", "星期二"); + } - @Test - public void testSerialize() { - byte[] bytes = ObjectUtil.serialize(map); - TolerantMap serializedMap = ObjectUtil.deserialize(bytes); - assert serializedMap != map; - assert map.equals(serializedMap); - } + @Test + public void testSerialize() { + byte[] bytes = ObjectUtil.serialize(map); + TolerantMap serializedMap = ObjectUtil.deserialize(bytes); + assert serializedMap != map; + assert map.equals(serializedMap); + } - @Test - public void testClone() { - TolerantMap clonedMap = ObjectUtil.clone(map); - assert clonedMap != map; - assert map.equals(clonedMap); - } + @Test + public void testClone() { + TolerantMap clonedMap = ObjectUtil.clone(map); + assert clonedMap != map; + assert map.equals(clonedMap); + } - @Test - public void testGet() { - assert "星期二".equals(map.get("tuesday")); - assert "default".equals(map.get(RandomUtil.randomString(6))); - } + @Test + public void testGet() { + assert "星期二".equals(map.get("tuesday")); + assert "default".equals(map.get(RandomUtil.randomString(6))); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/math/CombinationTest.java b/hutool-core/src/test/java/cn/hutool/core/math/CombinationTest.java index cd24b568f..7331635ba 100644 --- a/hutool-core/src/test/java/cn/hutool/core/math/CombinationTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/math/CombinationTest.java @@ -1,10 +1,10 @@ package cn.hutool.core.math; -import java.util.List; - import org.junit.Assert; import org.junit.Test; +import java.util.List; + /** * 组合单元测试 * @@ -49,6 +49,6 @@ public class CombinationTest { Assert.assertEquals(Combination.countAll(5), selectAll.size()); List list2 = combination.select(0); - Assert.assertTrue(1 == list2.size()); + Assert.assertEquals(1, list2.size()); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java index 32a16a7ad..475bd1a01 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java @@ -6,7 +6,9 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.net.HttpCookie; import java.net.InetAddress; +import java.util.List; /** * NetUtil单元测试 @@ -54,7 +56,22 @@ public class NetUtilTest { } @Test + @Ignore public void isUsableLocalPortTest(){ Assert.assertTrue(NetUtil.isUsableLocalPort(80)); } + + @Test + public void parseCookiesTest(){ + String cookieStr = "cookieName=\"cookieValue\";Path=\"/\";Domain=\"cookiedomain.com\""; + final List httpCookies = NetUtil.parseCookies(cookieStr); + Assert.assertEquals(1, httpCookies.size()); + + final HttpCookie httpCookie = httpCookies.get(0); + Assert.assertEquals(0, httpCookie.getVersion()); + Assert.assertEquals("cookieName", httpCookie.getName()); + Assert.assertEquals("cookieValue", httpCookie.getValue()); + Assert.assertEquals("/", httpCookie.getPath()); + Assert.assertEquals("cookiedomain.com", httpCookie.getDomain()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java new file mode 100644 index 000000000..c5f2f1a68 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -0,0 +1,201 @@ +package cn.hutool.core.net; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.util.CharsetUtil; +import org.junit.Assert; +import org.junit.Test; + +public class UrlBuilderTest { + + @Test + public void buildTest() { + String buildUrl = UrlBuilder.create().setHost("www.hutool.cn").build(); + Assert.assertEquals("http://www.hutool.cn/", buildUrl); + } + + @Test + public void testHost() { + String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.hutool.cn").build(); + Assert.assertEquals("https://www.hutool.cn/", buildUrl); + } + + @Test + public void testHostPort() { + String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.hutool.cn") + .setPort(8080) + .build(); + Assert.assertEquals("https://www.hutool.cn:8080/", buildUrl); + } + + @Test + public void testPathAndQuery() { + final String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.hutool.cn") + .addPath("/aaa").addPath("bbb") + .addQuery("ie", "UTF-8") + .addQuery("wd", "test") + .build(); + + Assert.assertEquals("https://www.hutool.cn/aaa/bbb?ie=UTF-8&wd=test", buildUrl); + } + + @Test + public void testQueryWithChinese() { + final String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.hutool.cn") + .addPath("/aaa").addPath("bbb") + .addQuery("ie", "UTF-8") + .addQuery("wd", "测试") + .build(); + + Assert.assertEquals("https://www.hutool.cn/aaa/bbb?ie=UTF-8&wd=%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void testMultiQueryWithChinese() { + final String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.hutool.cn") + .addPath("/s") + .addQuery("ie", "UTF-8") + .addQuery("ie", "GBK") + .addQuery("wd", "测试") + .build(); + + Assert.assertEquals("https://www.hutool.cn/s?ie=UTF-8&ie=GBK&wd=%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void testFragment() { + String buildUrl = new UrlBuilder() + .setScheme("https") + .setHost("www.hutool.cn") + .setFragment("abc").build(); + Assert.assertEquals("https://www.hutool.cn/#abc", buildUrl); + } + + @Test + public void testChineseFragment() { + String buildUrl = new UrlBuilder() + .setScheme("https") + .setHost("www.hutool.cn") + .setFragment("测试").build(); + Assert.assertEquals("https://www.hutool.cn/#%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void testChineseFragmentWithPath() { + String buildUrl = new UrlBuilder() + .setScheme("https") + .setHost("www.hutool.cn") + .addPath("/s") + .setFragment("测试").build(); + Assert.assertEquals("https://www.hutool.cn/s#%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void testChineseFragmentWithPathAndQuery() { + String buildUrl = new UrlBuilder() + .setScheme("https") + .setHost("www.hutool.cn") + .addPath("/s") + .addQuery("wd", "test") + .setFragment("测试").build(); + Assert.assertEquals("https://www.hutool.cn/s?wd=test#%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void ofTest() { + final UrlBuilder builder = UrlBuilder.of("http://www.hutool.cn/aaa/bbb/?a=1&b=2#frag1", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("http", builder.getScheme()); + Assert.assertEquals("www.hutool.cn", builder.getHost()); + + Assert.assertEquals("aaa", builder.getPath().getSegment(0)); + Assert.assertEquals("bbb", builder.getPath().getSegment(1)); + + Assert.assertEquals("1", builder.getQuery().get("a")); + Assert.assertEquals("2", builder.getQuery().get("b")); + + Assert.assertEquals("frag1", builder.getFragment()); + } + + @Test + public void ofWithChineseTest() { + final UrlBuilder builder = UrlBuilder.ofHttp("www.hutool.cn/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("http", builder.getScheme()); + Assert.assertEquals("www.hutool.cn", builder.getHost()); + + Assert.assertEquals("aaa", builder.getPath().getSegment(0)); + Assert.assertEquals("bbb", builder.getPath().getSegment(1)); + + Assert.assertEquals("张三", builder.getQuery().get("a")); + Assert.assertEquals("李四", builder.getQuery().get("b")); + + Assert.assertEquals("frag1", builder.getFragment()); + } + + @Test + public void ofWithBlankTest() { + final UrlBuilder builder = UrlBuilder.ofHttp(" www.hutool.cn/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("http", builder.getScheme()); + Assert.assertEquals("www.hutool.cn", builder.getHost()); + + Assert.assertEquals("aaa", builder.getPath().getSegment(0)); + Assert.assertEquals("bbb", builder.getPath().getSegment(1)); + + Assert.assertEquals("张三", builder.getQuery().get("a")); + Assert.assertEquals("李四", builder.getQuery().get("b")); + + Assert.assertEquals("frag1", builder.getFragment()); + } + + @Test + public void ofSpecialTest() { + //测试不规范的或者无需解码的字符串是否成功解码 + final UrlBuilder builder = UrlBuilder.ofHttp(" www.hutool.cn/aaa/bbb/?a=张三&b=%%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("http", builder.getScheme()); + Assert.assertEquals("www.hutool.cn", builder.getHost()); + + Assert.assertEquals("aaa", builder.getPath().getSegment(0)); + Assert.assertEquals("bbb", builder.getPath().getSegment(1)); + + Assert.assertEquals("张三", builder.getQuery().get("a")); + Assert.assertEquals("%四", builder.getQuery().get("b")); + + Assert.assertEquals("frag1", builder.getFragment()); + } + + @Test + public void weixinUrlTest(){ + String urlStr = "https://mp.weixin.qq.com/s?" + + "__biz=MzI5NjkyNTIxMg==" + + "&mid=100000465" + + "&idx=1" + + "&sn=1044c0d19723f74f04f4c1da34eefa35" + + "&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7"; + final UrlBuilder builder = UrlBuilder.ofHttp(urlStr, CharsetUtil.CHARSET_UTF_8); + // 原URL中的&替换为&,value中的=被编码为%3D + Assert.assertEquals("https://mp.weixin.qq.com/s?" + + "__biz=MzI5NjkyNTIxMg%3D%3D" + + "&mid=100000465&idx=1" + + "&sn=1044c0d19723f74f04f4c1da34eefa35" + + "&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7", + builder.toString()); + } + + @Test + public void endWithSlashTest(){ + // 原URL中以/结尾,则这个规则需保留,issue#I1G44J@Gitee + final String today = DateUtil.date().toString("yyyyMMdd"); + final String getWorkDayUrl = "https://tool.bitefu.net/jiari/?info=1&d=" + today; + final UrlBuilder builder = UrlBuilder.ofHttp(getWorkDayUrl, CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals(getWorkDayUrl, builder.toString()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardMonitorTest.java b/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardMonitorTest.java index 200534790..b19b1051b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardMonitorTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardMonitorTest.java @@ -1,42 +1,27 @@ package cn.hutool.core.swing; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.Transferable; - +import cn.hutool.core.lang.Console; +import cn.hutool.core.swing.clipboard.ClipboardUtil; import org.junit.Ignore; import org.junit.Test; -import cn.hutool.core.lang.Console; -import cn.hutool.core.swing.clipboard.ClipboardListener; -import cn.hutool.core.swing.clipboard.ClipboardUtil; - public class ClipboardMonitorTest { @Test @Ignore public void monitorTest() { // 第一个监听 - ClipboardUtil.listen(new ClipboardListener() { - - @Override - public Transferable onChange(Clipboard clipboard, Transferable contents) { - Object object = ClipboardUtil.getStr(contents); - Console.log("1# {}", object); - return contents; - } - + ClipboardUtil.listen((clipboard, contents) -> { + Object object = ClipboardUtil.getStr(contents); + Console.log("1# {}", object); + return contents; }, false); // 第二个监听 - ClipboardUtil.listen(new ClipboardListener() { - - @Override - public Transferable onChange(Clipboard clipboard, Transferable contents) { - Object object = ClipboardUtil.getStr(contents); - Console.log("2# {}", object); - return contents; - } - + ClipboardUtil.listen((clipboard, contents) -> { + Object object = ClipboardUtil.getStr(contents); + Console.log("2# {}", object); + return contents; }); } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index bb93a6eb4..6ed052300 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -1,12 +1,11 @@ package cn.hutool.core.text.csv; -import java.io.StringReader; - -import org.junit.Assert; -import org.junit.Test; - import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.io.StringReader; public class CsvParserTest { @@ -15,6 +14,7 @@ public class CsvParserTest { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); CsvParser parser = new CsvParser(reader, null); CsvRow row = parser.nextRow(); + //noinspection ConstantConditions Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } @@ -24,6 +24,7 @@ public class CsvParserTest { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); CsvParser parser = new CsvParser(reader, null); CsvRow row = parser.nextRow(); + //noinspection ConstantConditions Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } @@ -33,6 +34,7 @@ public class CsvParserTest { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); CsvParser parser = new CsvParser(reader, null); CsvRow row = parser.nextRow(); + //noinspection ConstantConditions Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } @@ -42,6 +44,7 @@ public class CsvParserTest { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); CsvParser parser = new CsvParser(reader, null); CsvRow row = parser.nextRow(); + //noinspection ConstantConditions Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java index 59d9e61fe..0db3d8142 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java @@ -1,10 +1,14 @@ package cn.hutool.core.text.csv; +import cn.hutool.core.annotation.Alias; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.CharsetUtil; +import lombok.Data; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.util.CharsetUtil; +import java.util.List; +import java.util.Map; public class CsvReaderTest { @@ -14,4 +18,57 @@ public class CsvReaderTest { CsvData data = reader.read(ResourceUtil.getReader("test.csv", CharsetUtil.CHARSET_UTF_8)); Assert.assertEquals("关注\"对象\"", data.getRow(0).get(2)); } + + @Test + public void readMapListTest(){ + final CsvReader reader = CsvUtil.getReader(); + final List> result = reader.readMapList( + ResourceUtil.getUtf8Reader("test_bean.csv")); + + Assert.assertEquals("张三", result.get(0).get("姓名")); + Assert.assertEquals("男", result.get(0).get("gender")); + Assert.assertEquals("无", result.get(0).get("focus")); + Assert.assertEquals("33", result.get(0).get("age")); + + Assert.assertEquals("李四", result.get(1).get("姓名")); + Assert.assertEquals("男", result.get(1).get("gender")); + Assert.assertEquals("好对象", result.get(1).get("focus")); + Assert.assertEquals("23", result.get(1).get("age")); + + Assert.assertEquals("王妹妹", result.get(2).get("姓名")); + Assert.assertEquals("女", result.get(2).get("gender")); + Assert.assertEquals("特别关注", result.get(2).get("focus")); + Assert.assertEquals("22", result.get(2).get("age")); + } + + @Test + public void readBeanListTest(){ + final CsvReader reader = CsvUtil.getReader(); + final List result = reader.read( + ResourceUtil.getUtf8Reader("test_bean.csv"), TestBean.class); + + Assert.assertEquals("张三", result.get(0).getName()); + Assert.assertEquals("男", result.get(0).getGender()); + Assert.assertEquals("无", result.get(0).getFocus()); + Assert.assertEquals(Integer.valueOf(33), result.get(0).getAge()); + + Assert.assertEquals("李四", result.get(1).getName()); + Assert.assertEquals("男", result.get(1).getGender()); + Assert.assertEquals("好对象", result.get(1).getFocus()); + Assert.assertEquals(Integer.valueOf(23), result.get(1).getAge()); + + Assert.assertEquals("王妹妹", result.get(2).getName()); + Assert.assertEquals("女", result.get(2).getGender()); + Assert.assertEquals("特别关注", result.get(2).getFocus()); + Assert.assertEquals(Integer.valueOf(22), result.get(2).getAge()); + } + + @Data + private static class TestBean{ + @Alias("姓名") + private String name; + private String gender; + private String focus; + private Integer age; + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java index cf04be8fd..43eea4a40 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java @@ -24,9 +24,7 @@ public class CsvUtilTest { @Test public void readTest2() { CsvReader reader = CsvUtil.getReader(); - reader.read(FileUtil.getUtf8Reader("test.csv"), (csvRow)->{ - Assert.notEmpty(csvRow.getRawList()); - }); + reader.read(FileUtil.getUtf8Reader("test.csv"), (csvRow)-> Assert.notEmpty(csvRow.getRawList())); } @Test diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/ConcurrencyTesterTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/ConcurrencyTesterTest.java index 5999b97f6..fdd4279a5 100644 --- a/hutool-core/src/test/java/cn/hutool/core/thread/ConcurrencyTesterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/thread/ConcurrencyTesterTest.java @@ -11,14 +11,10 @@ public class ConcurrencyTesterTest { @Test @Ignore public void concurrencyTesterTest() { - ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, new Runnable() { - - @Override - public void run() { - long delay = RandomUtil.randomLong(100, 1000); - ThreadUtil.sleep(delay); - Console.log("{} test finished, delay: {}", Thread.currentThread().getName(), delay); - } + ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, () -> { + long delay = RandomUtil.randomLong(100, 1000); + ThreadUtil.sleep(delay); + Console.log("{} test finished, delay: {}", Thread.currentThread().getName(), delay); }); Console.log(tester.getInterval()); } diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java index ef240d436..94e898611 100644 --- a/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java @@ -9,13 +9,6 @@ public class ThreadUtilTest { public void executeTest() { final boolean isValid = true; - ThreadUtil.execute(new Runnable() { - - @Override - public void run() { - Assert.assertTrue(isValid); - } - }); - + ThreadUtil.execute(() -> Assert.assertTrue(isValid)); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index c613c01a9..0f41afde3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -6,7 +6,9 @@ import cn.hutool.core.lang.Filter; import org.junit.Assert; import org.junit.Test; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Comparator; import java.util.Map; import java.util.Objects; @@ -40,6 +42,11 @@ public class ArrayUtilTest { isEmpty = ArrayUtil.isEmpty(d); //noinspection ConstantConditions Assert.assertTrue(isEmpty); + + // Object数组 + Object[] e = new Object[]{"1", "2", 3, 4D}; + final boolean empty = ArrayUtil.isEmpty(e); + Assert.assertFalse(empty); } @Test @@ -161,6 +168,17 @@ public class ArrayUtilTest { double maxDouble = ArrayUtil.max(1D, 2.4D, 13.0D, 4.55D, 5D); Assert.assertEquals(13.0, maxDouble, 2); + + BigDecimal one = new BigDecimal("1.00"); + BigDecimal two = new BigDecimal("2.0"); + BigDecimal three = new BigDecimal("3"); + BigDecimal[] bigDecimals = {two,one,three}; + + BigDecimal minAccuracy = ArrayUtil.min(bigDecimals, Comparator.comparingInt(BigDecimal::scale)); + Assert.assertEquals(minAccuracy,three); + + BigDecimal maxAccuracy = ArrayUtil.max(bigDecimals,Comparator.comparingInt(BigDecimal::scale)); + Assert.assertEquals(maxAccuracy,one); } @Test diff --git a/hutool-core/src/test/java/cn/hutool/core/util/EnumUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/EnumUtilTest.java index 018aeaa55..d32e64c7e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/EnumUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/EnumUtilTest.java @@ -1,12 +1,11 @@ package cn.hutool.core.util; -import java.util.List; -import java.util.Map; - +import cn.hutool.core.collection.CollUtil; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.collection.CollUtil; +import java.util.List; +import java.util.Map; /** * EnumUtil单元测试 @@ -49,17 +48,18 @@ public class EnumUtilTest { @Test public void getNameFieldMapTest() { Map enumMap = EnumUtil.getNameFieldMap(TestEnum.class, "type"); + assert enumMap != null; Assert.assertEquals("type1", enumMap.get("TEST1")); } public enum TestEnum{ TEST1("type1"), TEST2("type2"), TEST3("type3"); - + TestEnum(String type) { this.type = type; } - - private String type; + + private final String type; private String name; public String getType() { diff --git a/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java index 99553f241..4193ba412 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java @@ -3,9 +3,6 @@ package cn.hutool.core.util; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.HexUtil; - /** * HexUtil单元测试 * @author Looly diff --git a/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java index f52adca64..99b17bef2 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java @@ -1,13 +1,5 @@ package cn.hutool.core.util; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.collection.ConcurrentHashSet; import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.TimeInterval; @@ -15,6 +7,13 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Console; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.thread.ThreadUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; /** * {@link IdUtil} 单元测试 @@ -56,6 +55,7 @@ public class IdUtilTest { timer.restart(); for (int i = 0; i < 1000000; i++) { + //noinspection ResultOfMethodCallIgnored UUID.randomUUID().toString().replace("-", ""); } Console.log(timer.interval()); @@ -85,17 +85,13 @@ public class IdUtilTest { final int idCountPerThread = 10000; final CountDownLatch latch = new CountDownLatch(threadCount); for(int i =0; i < threadCount; i++) { - ThreadUtil.execute(new Runnable() { - - @Override - public void run() { - for(int i =0; i < idCountPerThread; i++) { - long id = snowflake.nextId(); - set.add(id); + ThreadUtil.execute(() -> { + for(int i1 = 0; i1 < idCountPerThread; i1++) { + long id = snowflake.nextId(); + set.add(id); // Console.log("Add new id: {}", id); - } - latch.countDown(); } + latch.countDown(); }); } @@ -118,17 +114,13 @@ public class IdUtilTest { final int idCountPerThread = 10000; final CountDownLatch latch = new CountDownLatch(threadCount); for(int i =0; i < threadCount; i++) { - ThreadUtil.execute(new Runnable() { - - @Override - public void run() { - for(int i =0; i < idCountPerThread; i++) { - long id = IdUtil.getSnowflake(1, 1).nextId(); - set.add(id); + ThreadUtil.execute(() -> { + for(int i1 = 0; i1 < idCountPerThread; i1++) { + long id = IdUtil.getSnowflake(1, 1).nextId(); + set.add(id); // Console.log("Add new id: {}", id); - } - latch.countDown(); } + latch.countDown(); }); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java index b37e10098..c17eac233 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java @@ -35,4 +35,10 @@ public class RandomUtilTest { public void randomBooleanTest() { Console.log(RandomUtil.randomBoolean()); } + + @Test + public void randomNumberTest() { + final char c = RandomUtil.randomNumber(); + Assert.assertTrue(c <= '9'); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index f0fd31419..f18c345c0 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -346,6 +346,9 @@ public class StrUtilTest { String str1 = "TableTestOfDay"; String result1 = StrUtil.toCamelCase(str1); Assert.assertEquals("TableTestOfDay", result1); + + String abc1d = StrUtil.toCamelCase("abc_1d"); + Assert.assertEquals("abc1d", abc1d); } @Test @@ -424,8 +427,26 @@ public class StrUtilTest { @Test public void subBetweenAllTest() { - Assert.assertArrayEquals(new String[]{"yz","abc"},StrUtil.subBetweenAll("saho[yz]fdsadp[abc]a","\\[","\\]")); - Assert.assertArrayEquals(new String[]{"abc"}, StrUtil.subBetweenAll("saho[yzfdsadp[abc]a]","\\[","\\]")); + Assert.assertArrayEquals(new String[]{"yz","abc"},StrUtil.subBetweenAll("saho[yz]fdsadp[abc]a","[","]")); + Assert.assertArrayEquals(new String[]{"abc"}, StrUtil.subBetweenAll("saho[yzfdsadp[abc]a]","[","]")); + Assert.assertArrayEquals(new String[]{"abc", "abc"}, StrUtil.subBetweenAll("yabczyabcz","y","z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll(null,"y","z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("","y","z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc",null,"z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc","y",null)); + } + + @Test + public void subBetweenAllTest2() { + //issue#861@Github,起始不匹配的时候,应该直接空 + String src1 = "/* \n* hutool */ asdas /* \n* hutool */"; + String src2 = "/ * hutool */ asdas / * hutool */"; + + String[] results1 = StrUtil.subBetweenAll(src1,"/**","*/"); + Assert.assertEquals(0, results1.length); + + String[] results2 = StrUtil.subBetweenAll(src2,"/*","*/"); + Assert.assertEquals(0, results2.length); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java index 6ec91be1d..44bf1afbf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java @@ -1,8 +1,10 @@ package cn.hutool.core.util; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.map.MapBuilder; import cn.hutool.core.map.MapUtil; +import lombok.Data; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -14,12 +16,11 @@ import java.util.Map; /** * {@link XmlUtil} 工具类 - * - * @author Looly * + * @author Looly */ public class XmlUtilTest { - + @Test public void parseTest() { String result = ""// @@ -65,6 +66,14 @@ public class XmlUtilTest { Assert.assertEquals("ok", value); } + @Test + public void xpathTest2() { + String result = ResourceUtil.readUtf8Str("test.xml"); + Document docResult = XmlUtil.parseXml(result); + Object value = XmlUtil.getByXPath("//returnsms/message", docResult, XPathConstants.STRING); + Assert.assertEquals("ok", value); + } + @Test public void xmlToMapTest() { String xml = ""// @@ -84,7 +93,7 @@ public class XmlUtilTest { Assert.assertEquals("1490", map.get("remainpoint")); Assert.assertEquals("885", map.get("taskID")); Assert.assertEquals("1", map.get("successCounts")); - Assert.assertEquals("subText", ((Map)map.get("newNode")).get("sub")); + Assert.assertEquals("subText", ((Map) map.get("newNode")).get("sub")); } @Test @@ -106,17 +115,33 @@ public class XmlUtilTest { Document doc = XmlUtil.mapToXml(map, "user"); // Console.log(XmlUtil.toStr(doc, false)); Assert.assertEquals(""// - + ""// - + "张三"// - + "12"// - + ""// - + "<昵称>Looly"// - + "14"// - + ""// - + "", // + + ""// + + "张三"// + + "12"// + + ""// + + "<昵称>Looly"// + + "14"// + + ""// + + "", // XmlUtil.toStr(doc, false)); } - + + @Test + public void mapToXmlTest2() { + // 测试List + Map map = MapBuilder.create(new LinkedHashMap()) + .put("Town", CollUtil.newArrayList("town1", "town2")) + .build(); + + Document doc = XmlUtil.mapToXml(map, "City"); + Assert.assertEquals("" + + "" + + "town1" + + "town2" + + "", + XmlUtil.toStr(doc)); + } + @Test public void readTest() { Document doc = XmlUtil.readXML("test.xml"); @@ -127,9 +152,56 @@ public class XmlUtilTest { public void mapToXmlTestWithOmitXmlDeclaration() { Map map = MapBuilder.create(new LinkedHashMap()) - .put("name", "ddatsh") - .build(); - String xml=XmlUtil.mapToXmlStr(map,true); - Assert.assertEquals(xml,"ddatsh"); - } + .put("name", "ddatsh") + .build(); + String xml = XmlUtil.mapToXmlStr(map, true); + Assert.assertEquals("ddatsh", xml); + } + + @Test + public void getByPathTest(){ + String xmlStr = "\n" + + "\n" + + " \n" + + " \n" + + " 2020/04/15 21:01:21\n" + + " \n" + + " \n" + + "\n"; + + Document document = XmlUtil.readXML(xmlStr); + Object value = XmlUtil.getByXPath( + "//soap:Envelope/soap:Body/ns2:testResponse/return", + document,XPathConstants.STRING);// + Assert.assertEquals("2020/04/15 21:01:21", value); + } + + @Test + public void xmlToBeanTest(){ + final TestBean testBean = new TestBean(); + testBean.setReqCode("1111"); + testBean.setAccountName("账户名称"); + testBean.setOperator("cz"); + testBean.setProjectCode("123"); + testBean.setBankCode("00001"); + + final Document doc = XmlUtil.beanToXml(testBean); + Assert.assertEquals(TestBean.class.getSimpleName(), doc.getDocumentElement().getTagName()); + + final TestBean testBean2 = XmlUtil.xmlToBean(doc, TestBean.class); + Assert.assertEquals(testBean.getReqCode(), testBean2.getReqCode()); + Assert.assertEquals(testBean.getAccountName(), testBean2.getAccountName()); + Assert.assertEquals(testBean.getOperator(), testBean2.getOperator()); + Assert.assertEquals(testBean.getProjectCode(), testBean2.getProjectCode()); + Assert.assertEquals(testBean.getBankCode(), testBean2.getBankCode()); + } + + @Data + public static class TestBean{ + private String ReqCode; + private String AccountName; + private String Operator; + private String ProjectCode; + private String BankCode; + } } diff --git a/hutool-core/src/test/resources/test.xml b/hutool-core/src/test/resources/test.xml index d831b2820..dc1eb8f6d 100644 --- a/hutool-core/src/test/resources/test.xml +++ b/hutool-core/src/test/resources/test.xml @@ -1,4 +1,8 @@ + + Success(成功) ok diff --git a/hutool-core/src/test/resources/test_bean.csv b/hutool-core/src/test/resources/test_bean.csv new file mode 100644 index 000000000..40d6862d8 --- /dev/null +++ b/hutool-core/src/test/resources/test_bean.csv @@ -0,0 +1,4 @@ +姓名,gender,focus,age +张三,男,无,33 +李四,男,好对象,23 +王妹妹,女,特别关注,22 \ No newline at end of file diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 4a9afa1f5..6ba0bb8a2 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java b/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java index c9ef127d9..84d7317a2 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java @@ -19,13 +19,13 @@ public class CronTimer extends Thread implements Serializable { private static final Log log = LogFactory.get(); /** 定时单元:秒 */ - private long TIMER_UNIT_SECOND = DateUnit.SECOND.getMillis(); + private final long TIMER_UNIT_SECOND = DateUnit.SECOND.getMillis(); /** 定时单元:分 */ - private long TIMER_UNIT_MINUTE = DateUnit.MINUTE.getMillis(); + private final long TIMER_UNIT_MINUTE = DateUnit.MINUTE.getMillis(); /** 定时任务是否已经被强制关闭 */ - private boolean isStoped; - private Scheduler scheduler; + private boolean isStop; + private final Scheduler scheduler; /** * 构造 @@ -42,18 +42,20 @@ public class CronTimer extends Thread implements Serializable { long thisTime = System.currentTimeMillis(); long nextTime; long sleep; - while(false == isStoped){ + while(false == isStop){ //下一时间计算是按照上一个执行点开始时间计算的 + //此处除以定时单位是为了清零单位以下部分,例如单位是分则秒和毫秒清零 nextTime = ((thisTime / timerUnit) + 1) * timerUnit; sleep = nextTime - System.currentTimeMillis(); - if (sleep > 0 && false == ThreadUtil.safeSleep(sleep)) { - //等待直到下一个时间点,如果被中断直接退出Timer - break; + if(isValidSleepMillis(sleep, timerUnit)){ + if (false == ThreadUtil.safeSleep(sleep)) { + //等待直到下一个时间点,如果被中断直接退出Timer + break; + } + //执行点,时间记录为执行开始的时间,而非结束时间 + thisTime = System.currentTimeMillis(); + spawnLauncher(thisTime); } - - //执行点,时间记录为执行开始的时间,而非结束时间 - thisTime = System.currentTimeMillis(); - spawnLauncher(thisTime); } log.debug("Hutool-cron timer stoped."); } @@ -62,7 +64,7 @@ public class CronTimer extends Thread implements Serializable { * 关闭定时器 */ synchronized public void stopTimer() { - this.isStoped = true; + this.isStop = true; ThreadUtil.interrupt(this, true); } @@ -73,4 +75,22 @@ public class CronTimer extends Thread implements Serializable { private void spawnLauncher(final long millis){ this.scheduler.taskLauncherManager.spawnLauncher(millis); } + + /** + * 检查是否为有效的sleep毫秒数,包括: + *
    +	 *     1. 是否>0,防止用户向未来调整时间
    +	 *     1. 是否<两倍的间隔单位,防止用户向历史调整时间
    +	 * 
    + * + * @param millis 毫秒数 + * @param timerUnit 定时单位,为秒或者分的毫秒值 + * @return 是否为有效的sleep毫秒数 + * @since 5.3.2 + */ + private static boolean isValidSleepMillis(long millis, long timerUnit){ + return millis > 0 && + // 防止用户向前调整时间导致的长时间sleep + millis < (2 * timerUnit); + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java b/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java index a24928580..02959d57b 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java @@ -55,7 +55,7 @@ import java.util.concurrent.locks.ReentrantLock; public class Scheduler implements Serializable { private static final long serialVersionUID = 1L; - private Lock lock = new ReentrantLock(); + private final Lock lock = new ReentrantLock(); /** 时区 */ private TimeZone timezone; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/TaskExecutor.java b/hutool-cron/src/main/java/cn/hutool/cron/TaskExecutor.java index 0478abbe2..5365751f0 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/TaskExecutor.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/TaskExecutor.java @@ -11,8 +11,8 @@ import cn.hutool.cron.task.Task; */ public class TaskExecutor implements Runnable { - private Scheduler scheduler; - private Task task; + private final Scheduler scheduler; + private final Task task; /** * 获得任务对象 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/TaskLauncher.java b/hutool-cron/src/main/java/cn/hutool/cron/TaskLauncher.java index 63c079e42..675ae8576 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/TaskLauncher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/TaskLauncher.java @@ -10,8 +10,8 @@ package cn.hutool.cron; */ public class TaskLauncher implements Runnable{ - private Scheduler scheduler; - private long millis; + private final Scheduler scheduler; + private final long millis; public TaskLauncher(Scheduler scheduler, long millis) { this.scheduler = scheduler; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java index d5e4b8aef..242c6e075 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java @@ -1,5 +1,8 @@ package cn.hutool.cron; +import cn.hutool.cron.pattern.CronPattern; +import cn.hutool.cron.task.Task; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -9,9 +12,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import cn.hutool.cron.pattern.CronPattern; -import cn.hutool.cron.task.Task; - /** * 定时任务表
    * 任务表将ID、表达式、任务一一对应,定时任务执行过程中,会周期性检查定时任务表中的所有任务表达式匹配情况,从而执行其对应的任务
    @@ -22,14 +22,14 @@ import cn.hutool.cron.task.Task; public class TaskTable implements Serializable { private static final long serialVersionUID = 1L; - private ReadWriteLock lock = new ReentrantReadWriteLock(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private Scheduler scheduler; - private TimeZone timezone; + private final Scheduler scheduler; + private final TimeZone timezone; - private List ids = new ArrayList<>(); - private List patterns = new ArrayList<>(); - private List tasks = new ArrayList<>(); + private final List ids = new ArrayList<>(); + private final List patterns = new ArrayList<>(); + private final List tasks = new ArrayList<>(); private int size; /** diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java index a0e11407b..4fe7babcf 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java @@ -1,11 +1,5 @@ package cn.hutool.cron.pattern; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.TimeZone; - import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.cron.CronException; @@ -22,6 +16,12 @@ import cn.hutool.cron.pattern.parser.SecondValueParser; import cn.hutool.cron.pattern.parser.ValueParser; import cn.hutool.cron.pattern.parser.YearValueParser; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.TimeZone; + /** * 定时任务表达式
    * 表达式类似于Linux的crontab表达式,表达式使用空格分成5个部分,按顺序依次为: @@ -90,22 +90,22 @@ public class CronPattern { private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser(); private static final ValueParser YEAR_VALUE_PARSER = new YearValueParser(); - private String pattern; + private final String pattern; /** 秒字段匹配列表 */ - private List secondMatchers = new ArrayList<>(); + private final List secondMatchers = new ArrayList<>(); /** 分字段匹配列表 */ - private List minuteMatchers = new ArrayList<>(); + private final List minuteMatchers = new ArrayList<>(); /** 时字段匹配列表 */ - private List hourMatchers = new ArrayList<>(); + private final List hourMatchers = new ArrayList<>(); /** 每月几号字段匹配列表 */ - private List dayOfMonthMatchers = new ArrayList<>(); + private final List dayOfMonthMatchers = new ArrayList<>(); /** 月字段匹配列表 */ - private List monthMatchers = new ArrayList<>(); + private final List monthMatchers = new ArrayList<>(); /** 星期字段匹配列表 */ - private List dayOfWeekMatchers = new ArrayList<>(); + private final List dayOfWeekMatchers = new ArrayList<>(); /** 年字段匹配列表 */ - private List yearMatchers = new ArrayList<>(); + private final List yearMatchers = new ArrayList<>(); /** 匹配器个数,取决于复合任务表达式中的单一表达式个数 */ private int matcherSize; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java index a22676b88..b1bd068f2 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java @@ -10,7 +10,7 @@ import java.util.List; */ public class YearValueMatcher implements ValueMatcher{ - private List valueList; + private final List valueList; public YearValueMatcher(List intValueList) { this.valueList = intValueList; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java index cd73642f5..10b3217cb 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java @@ -13,7 +13,13 @@ public class SimpleValueParser implements ValueParser { protected int min; /** 最大值(包括) */ protected int max; - + + /** + * 构造 + * + * @param min 最小值(包括) + * @param max 最大值(包括) + */ public SimpleValueParser(int min, int max) { if(min > max){ this.min = max; @@ -26,6 +32,11 @@ public class SimpleValueParser implements ValueParser { @Override public int parse(String value) throws CronException { + if("L".equalsIgnoreCase(value)){ + // L表示最大值 + return max; + } + int i; try { i = Integer.parseInt(value); diff --git a/hutool-cron/src/main/java/cn/hutool/cron/task/InvokeTask.java b/hutool-cron/src/main/java/cn/hutool/cron/task/InvokeTask.java index b1285c719..3993aaf82 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/task/InvokeTask.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/task/InvokeTask.java @@ -1,7 +1,5 @@ package cn.hutool.cron.task; -import java.lang.reflect.Method; - import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.ClassUtil; @@ -9,6 +7,8 @@ import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.cron.CronException; +import java.lang.reflect.Method; + /** * 反射执行任务
    * 通过传入类名#方法名,通过反射执行相应的方法
    @@ -19,8 +19,8 @@ import cn.hutool.cron.CronException; */ public class InvokeTask implements Task{ - private Object obj; - private Method method; + private final Object obj; + private final Method method; /** * 构造 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/task/RunnableTask.java b/hutool-cron/src/main/java/cn/hutool/cron/task/RunnableTask.java index 0f4fbe046..533166b05 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/task/RunnableTask.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/task/RunnableTask.java @@ -6,7 +6,7 @@ package cn.hutool.cron.task; * */ public class RunnableTask implements Task{ - private Runnable runnable; + private final Runnable runnable; public RunnableTask(Runnable runnable) { this.runnable = runnable; diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/AddAndRemoveMainTest.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/AddAndRemoveMainTest.java index a3fee6cbb..06575160f 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/AddAndRemoveMainTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/AddAndRemoveMainTest.java @@ -10,23 +10,12 @@ public class AddAndRemoveMainTest { CronUtil.setMatchSecond(true); CronUtil.start(false); CronUtil.getScheduler().clear(); - String id = CronUtil.schedule("*/2 * * * * *", new Runnable() { - - @Override - public void run() { - Console.log("task running : 2s"); - } - }); + String id = CronUtil.schedule("*/2 * * * * *", (Runnable) () -> Console.log("task running : 2s")); ThreadUtil.sleep(3000); CronUtil.remove(id); Console.log("Task Removed"); - id = CronUtil.schedule("*/3 * * * * *", new Runnable() { - @Override - public void run() { - Console.log("New task add running : 3s"); - } - }); + CronUtil.schedule("*/3 * * * * *", (Runnable) () -> Console.log("New task add running : 3s")); Console.log("New Task added."); } } diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java index 72ad80c2a..2f166c2c9 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java @@ -41,8 +41,9 @@ public class CronTest { // 支持秒级别定时任务 CronUtil.setMatchSecond(true); CronUtil.start(); - - ThreadUtil.sleep(30000); + + ThreadUtil.waitForDie(); + Console.log("Exit."); } @Test diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/TestJob.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/TestJob.java index a25fc5b43..810a52385 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/TestJob.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/TestJob.java @@ -13,7 +13,7 @@ import cn.hutool.core.util.IdUtil; */ public class TestJob { - private String jobId = IdUtil.simpleUUID(); + private final String jobId = IdUtil.simpleUUID(); /** * 执行定时任务内容 diff --git a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java index e1d57107c..3785330c0 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java @@ -125,6 +125,22 @@ public class CronPatternTest { assertMatch(pattern, "2017-02-19 04:20:33"); } + @Test + public void lastTest() { + // 每月最后一天的任意时间 + CronPattern pattern = new CronPattern("* * * L * ?"); + assertMatch(pattern, "2017-07-31 04:20:00"); + assertMatch(pattern, "2017-02-28 04:20:00"); + + // 最后一个月的任意时间 + pattern = new CronPattern("* * * * L ?"); + assertMatch(pattern, "2017-12-02 04:20:00"); + + // 任意天的最后时间 + pattern = new CronPattern("L L L * * ?"); + assertMatch(pattern, "2017-12-02 23:59:59"); + } + @Test(expected = CronException.class) public void rangeYearTest() { // year的范围是1970~2099年,超出报错 diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 8986ece74..1a6c2d7b9 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index aad2a8a73..67e19b29b 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,7 +1,26 @@ package cn.hutool.crypto; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm; +import cn.hutool.crypto.symmetric.SymmetricAlgorithm; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.DESedeKeySpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; import java.io.File; import java.io.InputStream; +import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -17,32 +36,15 @@ import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.interfaces.RSAPrivateCrtKey; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.security.spec.X509EncodedKeySpec; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.DESKeySpec; -import javax.crypto.spec.DESedeKeySpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm; -import cn.hutool.crypto.symmetric.SymmetricAlgorithm; - /** * 密钥工具类 * @@ -906,4 +908,49 @@ public class KeyUtil { public static PublicKey decodeECPoint(byte[] encodeByte, String curveName) { return BCUtil.decodeECPoint(encodeByte, curveName); } + + /** + * 通过RSA私钥生成RSA公钥 + * + * @param privateKey RSA私钥 + * @return RSA公钥,null表示私钥不被支持 + * @since 5.3.6 + */ + public static PublicKey getRSAPublicKey(PrivateKey privateKey){ + if(privateKey instanceof RSAPrivateCrtKey){ + final RSAPrivateCrtKey privk = (RSAPrivateCrtKey)privateKey; + return getRSAPublicKey(privk.getModulus(), privk.getPublicExponent()); + } + return null; + } + + /** + * 获得RSA公钥对象 + * + * @param modulus Modulus + * @param publicExponent Public Exponent + * @return 公钥 + * @since 5.3.6 + */ + public static PublicKey getRSAPublicKey(String modulus, String publicExponent){ + return getRSAPublicKey( + new BigInteger(modulus, 16), new BigInteger(publicExponent, 16)); + } + + /** + * 获得RSA公钥对象 + * + * @param modulus Modulus + * @param publicExponent Public Exponent + * @return 公钥 + * @since 5.3.6 + */ + public static PublicKey getRSAPublicKey(BigInteger modulus, BigInteger publicExponent){ + final RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent); + try { + return getKeyFactory("RSA").generatePublic(publicKeySpec); + } catch (InvalidKeySpecException e) { + throw new CryptoException(e); + } + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AbstractAsymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AbstractAsymmetricCrypto.java index a50df5ab7..5f56761ae 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AbstractAsymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AbstractAsymmetricCrypto.java @@ -1,10 +1,5 @@ package cn.hutool.crypto.asymmetric; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.security.PrivateKey; -import java.security.PublicKey; - import cn.hutool.core.codec.BCD; import cn.hutool.core.codec.Base64; import cn.hutool.core.io.IORuntimeException; @@ -12,9 +7,13 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.SecureUtil; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.security.PrivateKey; +import java.security.PublicKey; + /** * 抽象的非对称加密对象,包装了加密和解密为Hex和Base64的封装 * @@ -201,7 +200,6 @@ public abstract class AbstractAsymmetricCrypto { protected SM2Engine engine; protected SM2Signer signer; - private SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2; private ECPrivateKeyParameters privateKeyParams; private ECPublicKeyParameters publicKeyParams; + private DSAEncoding encoding = StandardDSAEncoding.INSTANCE; + private Digest digest = new SM3Digest(); + private SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2; + // ------------------------------------------------------------------ Constructor start /** @@ -70,8 +79,8 @@ public class SM2 extends AbstractAsymmetricCrypto { */ public SM2(byte[] privateKey, byte[] publicKey) { this(// - SecureUtil.generatePrivateKey(ALGORITHM_SM2, privateKey), // - SecureUtil.generatePublicKey(ALGORITHM_SM2, publicKey)// + KeyUtil.generatePrivateKey(ALGORITHM_SM2, privateKey), // + KeyUtil.generatePublicKey(ALGORITHM_SM2, publicKey)// ); } @@ -182,7 +191,6 @@ public class SM2 extends AbstractAsymmetricCrypto { if (KeyType.PublicKey != keyType) { throw new IllegalArgumentException("Encrypt is only support by public key"); } - checkKey(keyType); return encrypt(data, new ParametersWithRandom(getCipherParameters(keyType))); } @@ -229,7 +237,6 @@ public class SM2 extends AbstractAsymmetricCrypto { if (KeyType.PrivateKey != keyType) { throw new IllegalArgumentException("Decrypt is only support by private key"); } - checkKey(keyType); return decrypt(data, getCipherParameters(keyType)); } @@ -416,16 +423,51 @@ public class SM2 extends AbstractAsymmetricCrypto { } /** - * 设置加密类型 + * 设置DSA signatures的编码为PlainDSAEncoding + * + * @return this + * @since 5.3.1 + */ + public SM2 usePlainEncoding() { + return setEncoding(PlainDSAEncoding.INSTANCE); + } + + /** + * 设置DSA signatures的编码 + * + * @param encoding {@link DSAEncoding}实现 + * @return this + * @since 5.3.1 + */ + public SM2 setEncoding(DSAEncoding encoding) { + this.encoding = encoding; + this.signer = null; + return this; + } + + /** + * 设置Hash算法 + * + * @param digest {@link Digest}实现 + * @return this + * @since 5.3.1 + */ + public SM2 setDigest(Digest digest) { + this.digest = digest; + this.engine = null; + this.signer = null; + return this; + } + + /** + * 设置SM2模式,旧版是C1C2C3,新版本是C1C3C2 * * @param mode {@link SM2Engine.Mode} * @return this */ public SM2 setMode(SM2Engine.Mode mode) { this.mode = mode; - if (null != this.engine) { - this.engine = null; - } + this.engine = null; return this; } @@ -450,26 +492,6 @@ public class SM2 extends AbstractAsymmetricCrypto { return null; } - /** - * 检查对应类型的Key是否存在 - * - * @param keyType key类型 - */ - private void checkKey(KeyType keyType) { - switch (keyType) { - case PublicKey: - if (null == this.publicKey) { - throw new NullPointerException("No public key provided"); - } - break; - case PrivateKey: - if (null == this.privateKey) { - throw new NullPointerException("No private key provided"); - } - break; - } - } - /** * 获取{@link SM2Engine},此对象为懒加载模式 * @@ -477,7 +499,7 @@ public class SM2 extends AbstractAsymmetricCrypto { */ private SM2Engine getEngine() { if (null == this.engine) { - this.engine = new SM2Engine(this.mode); + this.engine = new SM2Engine(this.digest, this.mode); } return this.engine; } @@ -489,10 +511,9 @@ public class SM2 extends AbstractAsymmetricCrypto { */ private SM2Signer getSigner() { if (null == this.signer) { - this.signer = new SM2Signer(); + this.signer = new SM2Signer(this.encoding, this.digest); } return this.signer; } - // ------------------------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java index 44f4ba037..cde306de9 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java @@ -33,7 +33,7 @@ public enum SignAlgorithm { SHA384withECDSA("SHA384withECDSA"), // SHA512withECDSA("SHA512withECDSA");// - private String value; + private final String value; /** * 构造 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/BCrypt.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/BCrypt.java index cb86a3313..7113bb467 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/BCrypt.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/BCrypt.java @@ -1,9 +1,9 @@ package cn.hutool.crypto.digest; -import java.security.SecureRandom; - import cn.hutool.core.util.CharsetUtil; +import java.security.SecureRandom; + /** * BCrypt加密算法实现。由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥。 *

    @@ -177,7 +177,7 @@ public class BCrypt { * @return the decoded value of x */ private static byte char64(char x) { - if ((int) x < 0 || (int) x > index_64.length) + if ((int) x > index_64.length) return -1; return index_64[(int) x]; } @@ -191,6 +191,7 @@ public class BCrypt { * @return an array containing the decoded bytes * @throws IllegalArgumentException if maxolen is invalid */ + @SuppressWarnings("SameParameterValue") private static byte[] decodeBase64(String s, int maxolen) throws IllegalArgumentException { final StringBuilder rs = new StringBuilder(); int off = 0, slen = s.length(), olen = 0; @@ -285,8 +286,8 @@ public class BCrypt { * Initialise the Blowfish key schedule */ private void init_key() { - P = (int[]) P_orig.clone(); - S = (int[]) S_orig.clone(); + P = P_orig.clone(); + S = S_orig.clone(); } /** @@ -438,7 +439,7 @@ public class BCrypt { saltb = decodeBase64(real_salt, BCRYPT_SALT_LEN); bcrypt = new BCrypt(); - hashed = bcrypt.crypt(passwordb, saltb, rounds, (int[]) bf_crypt_ciphertext.clone()); + hashed = bcrypt.crypt(passwordb, saltb, rounds, bf_crypt_ciphertext.clone()); rs.append("$2"); if (minor >= 'a') diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestAlgorithm.java index 5411371b1..b3560ee00 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestAlgorithm.java @@ -14,7 +14,7 @@ public enum DigestAlgorithm { SHA384("SHA-384"), SHA512("SHA-512"); - private String value; + private final String value; /** * 构造 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java index 7af08f6df..363c5057a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java @@ -1,13 +1,5 @@ package cn.hutool.crypto.digest; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; -import java.io.Serializable; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; @@ -17,6 +9,13 @@ import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.digest.mac.MacEngine; import cn.hutool.crypto.digest.mac.MacEngineFactory; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.Serializable; +import java.security.Key; + /** * HMAC摘要算法
    * HMAC,全称为“Hash Message Authentication Code”,中文名“散列消息鉴别码”
    @@ -30,7 +29,7 @@ import cn.hutool.crypto.digest.mac.MacEngineFactory; public class HMac implements Serializable { private static final long serialVersionUID = 1L; - private MacEngine engine; + private final MacEngine engine; // ------------------------------------------------------------------------------------------- Constructor start /** @@ -38,7 +37,7 @@ public class HMac implements Serializable { * @param algorithm 算法 {@link HmacAlgorithm} */ public HMac(HmacAlgorithm algorithm) { - this(algorithm, (SecretKey)null); + this(algorithm, (Key)null); } /** @@ -55,7 +54,7 @@ public class HMac implements Serializable { * @param algorithm 算法 {@link HmacAlgorithm} * @param key 密钥 */ - public HMac(HmacAlgorithm algorithm, SecretKey key) { + public HMac(HmacAlgorithm algorithm, Key key) { this(algorithm.getValue(), key); } @@ -75,7 +74,7 @@ public class HMac implements Serializable { * @param key 密钥 * @since 4.5.13 */ - public HMac(String algorithm, SecretKey key) { + public HMac(String algorithm, Key key) { this(MacEngineFactory.createEngine(algorithm, key)); } @@ -224,5 +223,23 @@ public class HMac implements Serializable { public String digestHex(InputStream data, int bufferLength) { return HexUtil.encodeHexStr(digest(data, bufferLength)); } - + + /** + * 获取MAC算法块长度 + * @return MAC算法块长度 + * @since 5.3.3 + */ + public int getMacLength(){ + return this.engine.getMacLength(); + } + + /** + * 获取算法 + * + * @return 算法 + * @since 5.3.3 + */ + public String getAlgorithm() { + return this.engine.getAlgorithm(); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HmacAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HmacAlgorithm.java index df53ebf65..08b361218 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HmacAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HmacAlgorithm.java @@ -15,7 +15,7 @@ public enum HmacAlgorithm { /** HmacSM3算法实现,需要BouncyCastle库支持 */ HmacSM3("HmacSM3"); - private String value; + private final String value; HmacAlgorithm(String value) { this.value = value; diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java index e0db7ee49..9f61e5c0a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java @@ -1,16 +1,15 @@ package cn.hutool.crypto.digest.mac; -import java.io.IOException; -import java.io.InputStream; - +import cn.hutool.core.io.IoUtil; +import cn.hutool.crypto.CryptoException; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.params.KeyParameter; -import cn.hutool.core.io.IoUtil; -import cn.hutool.crypto.CryptoException; +import java.io.IOException; +import java.io.InputStream; /** * BouncyCastle的HMAC算法实现引擎,使用{@link Mac} 实现摘要
    @@ -94,4 +93,13 @@ public class BCHMacEngine implements MacEngine { return mac; } + @Override + public int getMacLength() { + return mac.getMacSize(); + } + + @Override + public String getAlgorithm() { + return this.mac.getAlgorithmName(); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java index 6136347e4..2ea6aef98 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java @@ -1,16 +1,16 @@ package cn.hutool.crypto.digest.mac; -import java.io.IOException; -import java.io.InputStream; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - import cn.hutool.core.io.IoUtil; import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.SecureUtil; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.io.InputStream; +import java.security.Key; + /** * 默认的HMAC算法实现引擎,使用{@link Mac} 实现摘要
    * 当引入BouncyCastle库时自动使用其作为Provider @@ -39,7 +39,7 @@ public class DefaultHMacEngine implements MacEngine { * @param key 密钥 * @since 4.5.13 */ - public DefaultHMacEngine(String algorithm, SecretKey key) { + public DefaultHMacEngine(String algorithm, Key key) { init(algorithm, key); } // ------------------------------------------------------------------------------------------- Constructor end @@ -61,7 +61,7 @@ public class DefaultHMacEngine implements MacEngine { * @return this * @throws CryptoException Cause by IOException */ - public DefaultHMacEngine init(String algorithm, SecretKey key){ + public DefaultHMacEngine init(String algorithm, Key key){ try { mac = SecureUtil.createMac(algorithm); if(null == key){ @@ -106,4 +106,14 @@ public class DefaultHMacEngine implements MacEngine { public Mac getMac() { return mac; } + + @Override + public int getMacLength() { + return mac.getMacLength(); + } + + @Override + public String getAlgorithm() { + return this.mac.getAlgorithm(); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java index 85fb8ec95..04d4d7f42 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java @@ -1,9 +1,9 @@ package cn.hutool.crypto.digest.mac; -import java.io.InputStream; - import cn.hutool.core.io.IoUtil; +import java.io.InputStream; + /** * MAC(Message Authentication Code)算法引擎 * @@ -20,4 +20,18 @@ public interface MacEngine { * @return 摘要bytes */ byte[] digest(InputStream data, int bufferLength); + + /** + * 获取MAC算法块大小 + * + * @return MAC算法块大小 + */ + int getMacLength(); + + /** + * 获取当前算法 + * + * @return 算法 + */ + String getAlgorithm(); } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java index 9288b6e72..49d0516dd 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java @@ -1,10 +1,10 @@ package cn.hutool.crypto.digest.mac; -import javax.crypto.SecretKey; - import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.digest.HmacAlgorithm; +import java.security.Key; + /** * {@link MacEngine} 实现工厂类 * @@ -19,7 +19,7 @@ public class MacEngineFactory { * @param key 密钥 * @return {@link MacEngine} */ - public static MacEngine createEngine(String algorithm, SecretKey key) { + public static MacEngine createEngine(String algorithm, Key key) { if(algorithm.equalsIgnoreCase(HmacAlgorithm.HmacSM3.getValue())) { // HmacSM3算法是BC库实现的 return SmUtil.createHmacSm3Engine(key.getEncoded()); diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java new file mode 100644 index 000000000..cc3598aa5 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java @@ -0,0 +1,126 @@ +package cn.hutool.crypto.digest.opt; + +import cn.hutool.crypto.digest.HMac; +import cn.hutool.crypto.digest.HmacAlgorithm; + +/** + *

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

    + * + *

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

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

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

    + * + *

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

    + * + * @author Looly + */ +public class TOPT extends HOTP { + + /** + * 默认步进 (30秒). + */ + public static final Duration DEFAULT_TIME_STEP = Duration.ofSeconds(30); + + private final Duration timeStep; + + /** + * 构造,使用默认HMAC算法(HmacSHA1) + * + * @param key 共享密码,RFC 4226要求最少128位 + */ + public TOPT(byte[] key) { + this(DEFAULT_TIME_STEP, key); + } + + /** + * 构造,使用默认HMAC算法(HmacSHA1) + * + * @param timeStep 日期步进,用于生成移动因子(moving factor) + * @param key 共享密码,RFC 4226要求最少128位 + */ + public TOPT(Duration timeStep, byte[] key) { + this(timeStep, DEFAULT_PASSWORD_LENGTH, key); + } + + /** + * 构造,使用默认HMAC算法(HmacSHA1) + * + * @param timeStep 日期步进,用于生成移动因子(moving factor) + * @param passwordLength 密码长度,可以是6,7,8 + * @param key 共享密码,RFC 4226要求最少128位 + */ + public TOPT(Duration timeStep, int passwordLength, byte[] key) { + this(timeStep, passwordLength, HOTP_HMAC_ALGORITHM, key); + } + + /** + * 构造 + * + * @param timeStep 日期步进,用于生成移动因子(moving factor) + * @param passwordLength 密码长度,可以是6,7,8 + * @param algorithm HMAC算法枚举 + * @param key 共享密码,RFC 4226要求最少128位 + */ + public TOPT(Duration timeStep, int passwordLength, HmacAlgorithm algorithm, byte[] key) { + super(passwordLength, algorithm, key); + this.timeStep = timeStep; + } + + /** + * 使用给定的时间戳生成一次性密码. + * + * @param timestamp 用于生成密码的时间戳 + * @return 一次性密码的int形式 + */ + public int generate(Instant timestamp) { + return this.generate(timestamp.toEpochMilli() / this.timeStep.toMillis()); + } + + /** + * 获取步进 + * + * @return 步进 + */ + public Duration getTimeStep() { + return this.timeStep; + } +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java new file mode 100644 index 000000000..e496dd688 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java @@ -0,0 +1,14 @@ +/** + * OTP 是 One-Time Password的简写,表示一次性密码。 + *

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

    + * OTP(K,C) = Truncate(HMAC-SHA-1(K,C))
    + * K:表示秘钥串
    + * C:是一个数字,表示随机数
    + * Truncate:是一个函数,就是怎么截取加密后的串,并取加密后串的哪些字段组成一个数字。
    + * 
    + * + * @author looly + */ +package cn.hutool.crypto.digest.opt; \ No newline at end of file diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java index 758bd7838..4b34b67e1 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java @@ -1,14 +1,14 @@ package cn.hutool.crypto.symmetric; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; - import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.SecureUtil; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + /** * AES加密算法实现
    * 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法
    @@ -105,7 +105,7 @@ public class AES extends SymmetricCrypto { * @since 4.6.7 */ public AES(Mode mode, Padding padding, SecretKey key, byte[] iv) { - this(mode, padding, key, ArrayUtil.isEmpty(iv) ? ((IvParameterSpec) null) : new IvParameterSpec(iv)); + this(mode, padding, key, ArrayUtil.isEmpty(iv) ? null : new IvParameterSpec(iv)); } /** @@ -153,7 +153,7 @@ public class AES extends SymmetricCrypto { public AES(String mode, String padding, byte[] key, byte[] iv) { this(mode, padding,// SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), key),// - ArrayUtil.isEmpty(iv) ? ((IvParameterSpec) null) : new IvParameterSpec(iv)); + ArrayUtil.isEmpty(iv) ? null : new IvParameterSpec(iv)); } /** diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricAlgorithm.java index c37f2e269..bedc93327 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricAlgorithm.java @@ -22,7 +22,7 @@ public enum SymmetricAlgorithm { PBEWithSHA1AndDESede("PBEWithSHA1AndDESede"), PBEWithSHA1AndRC2_40("PBEWithSHA1AndRC2_40"); - private String value; + private final String value; /** * 构造 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java index 77ec9314d..27261779d 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java @@ -48,7 +48,7 @@ public class SymmetricCrypto implements Serializable { * 是否0填充 */ private boolean isZeroPadding; - private Lock lock = new ReentrantLock(); + private final Lock lock = new ReentrantLock(); // ------------------------------------------------------------------ Constructor start @@ -436,7 +436,7 @@ public class SymmetricCrypto implements Serializable { } /** - * 解密Hex表示的字符串,默认UTF-8编码 + * 解密Hex(16进制)或Base64表示的字符串,默认UTF-8编码 * * @param data 被解密的String * @return 解密后的String diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java index 6afd56e99..1b9e99018 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java @@ -8,11 +8,6 @@ import org.junit.Test; public class BCUtilTest { - @Test - public void decodeECPointTest(){ - - } - /** * 密钥生成来自:https://i.goto327.top/CryptTools/SM2.aspx?tdsourcetag=s_pctim_aiomsg */ diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 5ca62af65..b038fd67d 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -1,14 +1,15 @@ package cn.hutool.crypto.test; -import java.security.KeyPair; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.GlobalBouncyCastleProvider; import cn.hutool.crypto.KeyUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; public class KeyUtilTest { @@ -23,4 +24,11 @@ public class KeyUtilTest { Assert.assertNotNull(pair); } + @Test + public void getRSAPublicKeyTest(){ + final KeyPair keyPair = KeyUtil.generateKeyPair("RSA"); + final PrivateKey aPrivate = keyPair.getPrivate(); + final PublicKey rsaPublicKey = KeyUtil.getRSAPublicKey(aPrivate); + Assert.assertEquals(rsaPublicKey, keyPair.getPublic()); + } } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java index 7772c00c7..a5ed710d8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java @@ -171,9 +171,26 @@ public class SM2Test { String id = "31323334353637383132333435363738"; final SM2 sm2 = new SM2(d, x, y); - final String sign = sm2.signHex(data, id); + Assert.assertTrue(sm2.verifyHex(data, sign)); + } + @Test + public void sm2PlainWithPointTest(){ + // 测试地址:https://i.goto327.top/CryptTools/SM2.aspx?tdsourcetag=s_pctim_aiomsg + + String d = "FAB8BBE670FAE338C9E9382B9FB6485225C11A3ECB84C938F10F20A93B6215F0"; + String x = "9EF573019D9A03B16B0BE44FC8A5B4E8E098F56034C97B312282DD0B4810AFC3"; + String y = "CC759673ED0FC9B9DC7E6FA38F0E2B121E02654BF37EA6B63FAF2A0D6013EADF"; + + String data = "434477813974bf58f94bcf760833c2b40f77a5fc360485b0b9ed1bd9682edb45"; + String id = "31323334353637383132333435363738"; + + final SM2 sm2 = new SM2(d, x, y); + // 生成的签名是64位 + sm2.usePlainEncoding(); + + String sign = "DCA0E80A7F46C93714B51C3EFC55A922BCEF7ECF0FE9E62B53BA6A7438B543A76C145A452CA9036F3CB70D7E6C67D4D9D7FE114E5367A2F6F5A4D39F2B10F3D6"; Assert.assertTrue(sm2.verifyHex(data, sign)); } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 84ab625ab..c76e32eea 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-db @@ -21,9 +21,9 @@ 0.9.5.5 2.7.0 9.0.30 - 1.1.21 + 1.1.20 2.4.13 - 3.12.1 + 3.12.2 3.30.1 2.5.0 3.2.0 @@ -120,7 +120,7 @@ org.postgresql postgresql - 42.2.10.jre7 + 42.2.11.jre7 test @@ -135,5 +135,11 @@ 1.7.26 test + + com.h2database + h2 + 1.4.200 + test + diff --git a/hutool-db/src/main/java/cn/hutool/db/DbUtil.java b/hutool-db/src/main/java/cn/hutool/db/DbUtil.java index 509c1bd77..1de03ef36 100644 --- a/hutool-db/src/main/java/cn/hutool/db/DbUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/DbUtil.java @@ -1,16 +1,5 @@ package cn.hutool.db; -import java.io.Closeable; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.sql.DataSource; - import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; import cn.hutool.db.dialect.Dialect; @@ -22,6 +11,11 @@ import cn.hutool.log.LogFactory; import cn.hutool.log.level.Level; import cn.hutool.setting.Setting; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; + /** * 数据库操作工具类 * @@ -156,30 +150,13 @@ public final class DbUtil { * * @param objsToClose 需要关闭的对象 */ - @SuppressWarnings("ConstantConditions") public static void close(Object... objsToClose) { for (Object obj : objsToClose) { - if (obj instanceof AutoCloseable) { - IoUtil.close((AutoCloseable) obj); - } else if (obj instanceof Closeable) { - IoUtil.close((Closeable) obj); - } else { - try { - if (obj != null) { - if (obj instanceof ResultSet) { - ((ResultSet) obj).close(); - } else if (obj instanceof Statement) { - ((Statement) obj).close(); - } else if (obj instanceof PreparedStatement) { - ((PreparedStatement) obj).close(); - } else if (obj instanceof Connection) { - ((Connection) obj).close(); - } else { - log.warn("Object {} not a ResultSet or Statement or PreparedStatement or Connection!", obj.getClass().getName()); - } - } - } catch (SQLException e) { - // ignore + if(null != obj){ + if (obj instanceof AutoCloseable) { + IoUtil.close((AutoCloseable) obj); + } else { + log.warn("Object {} not a ResultSet or Statement or PreparedStatement or Connection!", obj.getClass().getName()); } } } diff --git a/hutool-db/src/main/java/cn/hutool/db/Page.java b/hutool-db/src/main/java/cn/hutool/db/Page.java index 39bc195a1..d53b6bc99 100644 --- a/hutool-db/src/main/java/cn/hutool/db/Page.java +++ b/hutool-db/src/main/java/cn/hutool/db/Page.java @@ -134,10 +134,7 @@ public class Page implements Serializable { * @param orders 排序 */ public void addOrder(Order... orders) { - if (null != this.orders) { - ArrayUtil.append(this.orders, orders); - } - this.orders = orders; + this.orders = ArrayUtil.append(this.orders, orders); } // ---------------------------------------------------------- Getters and Setters end diff --git a/hutool-db/src/main/java/cn/hutool/db/PageResult.java b/hutool-db/src/main/java/cn/hutool/db/PageResult.java index 1be7b2cff..ffbec81fc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/PageResult.java +++ b/hutool-db/src/main/java/cn/hutool/db/PageResult.java @@ -1,9 +1,9 @@ package cn.hutool.db; -import java.util.ArrayList; - import cn.hutool.core.util.PageUtil; +import java.util.ArrayList; + /** * 分页数据结果集 * @@ -169,6 +169,6 @@ public class PageResult extends ArrayList { * @return 是否最后一页 */ public boolean isLast() { - return this.page >= this.totalPage; + return this.page >= (this.totalPage - 1); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/Session.java b/hutool-db/src/main/java/cn/hutool/db/Session.java index 9accc1530..8b3a223e8 100644 --- a/hutool-db/src/main/java/cn/hutool/db/Session.java +++ b/hutool-db/src/main/java/cn/hutool/db/Session.java @@ -1,13 +1,5 @@ package cn.hutool.db; -import java.io.Closeable; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Savepoint; - -import javax.sql.DataSource; - -import cn.hutool.core.lang.func.VoidFunc0; import cn.hutool.core.lang.func.VoidFunc1; import cn.hutool.core.util.StrUtil; import cn.hutool.db.dialect.Dialect; @@ -17,6 +9,12 @@ import cn.hutool.db.sql.Wrapper; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; +import javax.sql.DataSource; +import java.io.Closeable; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Savepoint; + /** * 数据库SQL执行会话
    * 会话通过共用Connection而可以实现JDBC事务
    @@ -246,7 +244,7 @@ public class Session extends AbstractDb implements Closeable { } /** - * 在事务中执行操作,通过实现{@link VoidFunc0}接口的call方法执行多条SQL语句从而完成事务 + * 在事务中执行操作,通过实现{@link VoidFunc1}接口的call方法执行多条SQL语句从而完成事务 * * @param func 函数抽象,在函数中执行多个SQL操作,多个操作会被合并为同一事务 * @throws SQLException SQL异常 @@ -264,7 +262,7 @@ public class Session extends AbstractDb implements Closeable { } /** - * 在事务中执行操作,通过实现{@link VoidFunc0}接口的call方法执行多条SQL语句从而完成事务 + * 在事务中执行操作,通过实现{@link VoidFunc1}接口的call方法执行多条SQL语句从而完成事务 * * @param func 函数抽象,在函数中执行多个SQL操作,多个操作会被合并为同一事务 * @since 3.2.3 diff --git a/hutool-db/src/main/java/cn/hutool/db/ThreadLocalConnection.java b/hutool-db/src/main/java/cn/hutool/db/ThreadLocalConnection.java index b8af20be4..60666ee69 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ThreadLocalConnection.java +++ b/hutool-db/src/main/java/cn/hutool/db/ThreadLocalConnection.java @@ -1,12 +1,11 @@ package cn.hutool.db; +import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; -import javax.sql.DataSource; - /** * 线程相关的数据库连接持有器
    * 此对象为单例类,用于存储线程相关的Connection对象。
    @@ -61,7 +60,7 @@ public enum ThreadLocalConnection { public static class GroupedConnection { /** 连接的Map,考虑到大部分情况是单数据库,故此处初始大小1 */ - private Map connMap = new HashMap<>(1, 1); + private final Map connMap = new HashMap<>(1, 1); /** * 获取连接,如果获取的连接为空或者已被关闭,重新创建连接 diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java index ac1711e01..8995d0f39 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java @@ -1,11 +1,5 @@ package cn.hutool.db.dialect; -import java.sql.Connection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import javax.sql.DataSource; - import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.dialect.impl.AnsiSqlDialect; @@ -17,6 +11,11 @@ import cn.hutool.db.dialect.impl.SqlServer2012Dialect; import cn.hutool.db.dialect.impl.Sqlite3Dialect; import cn.hutool.log.StaticLog; +import javax.sql.DataSource; +import java.sql.Connection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** * 方言工厂类 * @@ -26,35 +25,33 @@ import cn.hutool.log.StaticLog; public class DialectFactory { /** JDBC 驱动 MySQL */ - public final static String DRIVER_MYSQL = "com.mysql.jdbc.Driver"; + public static final String DRIVER_MYSQL = "com.mysql.jdbc.Driver"; /** JDBC 驱动 MySQL,在6.X版本中变动驱动类名,且使用SPI机制 */ - public final static String DRIVER_MYSQL_V6 = "com.mysql.cj.jdbc.Driver"; + public static final String DRIVER_MYSQL_V6 = "com.mysql.cj.jdbc.Driver"; /** JDBC 驱动 Oracle */ - public final static String DRIVER_ORACLE = "oracle.jdbc.OracleDriver"; + public static final String DRIVER_ORACLE = "oracle.jdbc.OracleDriver"; /** JDBC 驱动 Oracle,旧版使用 */ - public final static String DRIVER_ORACLE_OLD = "oracle.jdbc.driver.OracleDriver"; + public static final String DRIVER_ORACLE_OLD = "oracle.jdbc.driver.OracleDriver"; /** JDBC 驱动 PostgreSQL */ - public final static String DRIVER_POSTGRESQL = "org.postgresql.Driver"; + public static final String DRIVER_POSTGRESQL = "org.postgresql.Driver"; /** JDBC 驱动 SQLLite3 */ - public final static String DRIVER_SQLLITE3 = "org.sqlite.JDBC"; + public static final String DRIVER_SQLLITE3 = "org.sqlite.JDBC"; /** JDBC 驱动 SQLServer */ - public final static String DRIVER_SQLSERVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; + public static final String DRIVER_SQLSERVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; /** JDBC 驱动 Hive */ - public final static String DRIVER_HIVE = "org.apache.hadoop.hive.jdbc.HiveDriver"; + public static final String DRIVER_HIVE = "org.apache.hadoop.hive.jdbc.HiveDriver"; /** JDBC 驱动 Hive2 */ - public final static String DRIVER_HIVE2 = "org.apache.hive.jdbc.HiveDriver"; + public static final String DRIVER_HIVE2 = "org.apache.hive.jdbc.HiveDriver"; /** JDBC 驱动 H2 */ - public final static String DRIVER_H2 = "org.h2.Driver"; + public static final String DRIVER_H2 = "org.h2.Driver"; /** JDBC 驱动 Derby */ - public final static String DRIVER_DERBY = "org.apache.derby.jdbc.ClientDriver"; - /** JDBC 驱动 Derby嵌入式 */ - public final static String DRIVER_DERBY_EMBEDDED = "org.apache.derby.jdbc.EmbeddedDriver"; + public static final String DRIVER_DERBY = "org.apache.derby.jdbc.AutoloadedDriver"; /** JDBC 驱动 HSQLDB */ - public final static String DRIVER_HSQLDB = "org.hsqldb.jdbc.JDBCDriver"; + public static final String DRIVER_HSQLDB = "org.hsqldb.jdbc.JDBCDriver"; /** JDBC 驱动 达梦7 */ - public final static String DRIVER_DM7 = "dm.jdbc.driver.DmDriver"; + public static final String DRIVER_DM7 = "dm.jdbc.driver.DmDriver"; - private static Map dialectPool = new ConcurrentHashMap<>(); + private static final Map DIALECT_POOL = new ConcurrentHashMap<>(); private DialectFactory() { } @@ -127,12 +124,9 @@ public class DialectFactory { driver = DRIVER_HIVE; } else if (nameContainsProductInfo.contains("h2")) { driver = DRIVER_H2; - } else if (nameContainsProductInfo.startsWith("jdbc:derby://")) { - // Derby数据库网络连接方式 - driver = DRIVER_DERBY; } else if (nameContainsProductInfo.contains("derby")) { // 嵌入式Derby数据库 - driver = DRIVER_DERBY_EMBEDDED; + driver = DRIVER_DERBY; } else if (nameContainsProductInfo.contains("hsqldb")) { // HSQLDB driver = DRIVER_HSQLDB; @@ -150,15 +144,15 @@ public class DialectFactory { * @return {@link Dialect}方言 */ public static Dialect getDialect(DataSource ds) { - Dialect dialect = dialectPool.get(ds); + Dialect dialect = DIALECT_POOL.get(ds); if(null == dialect) { // 数据源作为锁的意义在于:不同数据源不会导致阻塞,相同数据源获取方言时可保证互斥 //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (ds) { - dialect = dialectPool.get(ds); + dialect = DIALECT_POOL.get(ds); if(null == dialect) { dialect = newDialect(ds); - dialectPool.put(ds, dialect); + DIALECT_POOL.put(ds, dialect); } } } diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java index 0d86f061a..404814c45 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java @@ -3,19 +3,17 @@ package cn.hutool.db.dialect.impl; import cn.hutool.db.Page; import cn.hutool.db.dialect.DialectName; import cn.hutool.db.sql.SqlBuilder; -import cn.hutool.db.sql.Wrapper; /** * H2数据库方言 - * - * @author loolly * + * @author loolly */ public class H2Dialect extends AnsiSqlDialect { private static final long serialVersionUID = 1490520247974768214L; public H2Dialect() { - wrapper = new Wrapper('"', '"'); +// wrapper = new Wrapper('"'); } @Override diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/AbstractDSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/AbstractDSFactory.java index 387f0cc98..318a488d1 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/AbstractDSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/AbstractDSFactory.java @@ -1,11 +1,5 @@ package cn.hutool.db.ds; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import javax.sql.DataSource; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.io.resource.NoResourceException; import cn.hutool.core.lang.Assert; @@ -15,6 +9,11 @@ import cn.hutool.db.DbUtil; import cn.hutool.db.dialect.DriverUtil; import cn.hutool.setting.Setting; +import javax.sql.DataSource; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** * 抽象数据源工厂
    * 此工厂抽象类用于实现数据源的缓存,当用户多次调用{@link #getDataSource(String)} 时,工厂只需创建一次即可。
    @@ -32,9 +31,9 @@ public abstract class AbstractDSFactory extends DSFactory { private static final String DEFAULT_DB_SETTING_PATH2 = "db.setting"; /** 数据库连接配置文件 */ - private Setting setting; + private final Setting setting; /** 数据源池 */ - private Map dsMap; + private final Map dsMap; /** * 构造 diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/DataSourceWrapper.java b/hutool-db/src/main/java/cn/hutool/db/ds/DataSourceWrapper.java index 334e6a9c5..078c810a1 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/DataSourceWrapper.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/DataSourceWrapper.java @@ -1,5 +1,8 @@ package cn.hutool.db.ds; +import cn.hutool.core.io.IoUtil; + +import javax.sql.DataSource; import java.io.Closeable; import java.io.PrintWriter; import java.sql.Connection; @@ -7,10 +10,6 @@ import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; -import javax.sql.DataSource; - -import cn.hutool.core.io.IoUtil; - /** * {@link DataSource} 数据源实现包装,通过包装,提供基本功能外的额外功能和参数持有,包括: * @@ -23,8 +22,8 @@ import cn.hutool.core.io.IoUtil; */ public class DataSourceWrapper implements DataSource, Closeable, Cloneable { - private DataSource ds; - private String driver; + private final DataSource ds; + private final String driver; /** * 包装指定的DataSource diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/DbSetting.java b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/DbSetting.java index 09b6485b0..698bee06e 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/DbSetting.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/DbSetting.java @@ -17,7 +17,7 @@ public class DbSetting { /** 默认的数据库连接配置文件路径 */ public final static String DEFAULT_DB_CONFIG_PATH = "config/db.setting"; - private Setting setting; + private final Setting setting; /** * 构造 diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java index 8c9f7063a..2c72b6905 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java @@ -1,11 +1,11 @@ package cn.hutool.db.ds.pooled; +import cn.hutool.db.DbUtil; + import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; -import cn.hutool.db.DbUtil; - /** * 池化 * @author Looly @@ -13,7 +13,7 @@ import cn.hutool.db.DbUtil; */ public class PooledConnection extends ConnectionWraper{ - private PooledDataSource ds; + private final PooledDataSource ds; private boolean isClosed; public PooledConnection(PooledDataSource ds) throws SQLException { diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledDataSource.java b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledDataSource.java index ee0d04e08..e03f44072 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledDataSource.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledDataSource.java @@ -1,11 +1,5 @@ package cn.hutool.db.ds.pooled; -import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.LinkedList; -import java.util.Queue; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.thread.ThreadUtil; @@ -13,6 +7,11 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.simple.AbstractDataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.Queue; + /** * 池化数据源 * @@ -24,7 +23,7 @@ public class PooledDataSource extends AbstractDataSource { private Queue freePool; private int activeCount; // 活跃连接数 - private DbConfig config; + private final DbConfig config; /** * 获得一个数据源 @@ -79,7 +78,7 @@ public class PooledDataSource extends AbstractDataSource { */ public PooledDataSource(DbConfig config) { this.config = config; - freePool = new LinkedList(); + freePool = new LinkedList<>(); int initialSize = config.getInitialSize(); try { while (initialSize-- > 0) { @@ -146,7 +145,7 @@ public class PooledDataSource extends AbstractDataSource { } @Override - synchronized public void close() throws IOException { + synchronized public void close() { if (CollectionUtil.isNotEmpty(this.freePool)) { for (PooledConnection pooledConnection : freePool) { pooledConnection.release(); @@ -157,7 +156,7 @@ public class PooledDataSource extends AbstractDataSource { } @Override - protected void finalize() throws Throwable { + protected void finalize() { IoUtil.close(this); } diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/EntityHandler.java b/hutool-db/src/main/java/cn/hutool/db/handler/EntityHandler.java index c1a38f340..8f63880b0 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/EntityHandler.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/EntityHandler.java @@ -1,11 +1,11 @@ package cn.hutool.db.handler; +import cn.hutool.db.Entity; + import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; -import cn.hutool.db.Entity; - /** * Entity对象处理器,只处理第一条数据 * @@ -16,7 +16,7 @@ public class EntityHandler implements RsHandler{ private static final long serialVersionUID = -8742432871908355992L; /** 是否大小写不敏感 */ - private boolean caseInsensitive; + private final boolean caseInsensitive; /** * 创建一个 EntityHandler对象 diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/EntityListHandler.java b/hutool-db/src/main/java/cn/hutool/db/handler/EntityListHandler.java index b7029a76f..5501c06ca 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/EntityListHandler.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/EntityListHandler.java @@ -1,12 +1,12 @@ package cn.hutool.db.handler; +import cn.hutool.db.Entity; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import cn.hutool.db.Entity; - /** * 结果集处理类 ,处理出的结果为Entity列表 * @author loolly @@ -16,7 +16,7 @@ public class EntityListHandler implements RsHandler>{ private static final long serialVersionUID = -2846240126316979895L; /** 是否大小写不敏感 */ - private boolean caseInsensitive; + private final boolean caseInsensitive; /** * 创建一个 EntityListHandler对象 diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/EntitySetHandler.java b/hutool-db/src/main/java/cn/hutool/db/handler/EntitySetHandler.java index 787760b65..28e814579 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/EntitySetHandler.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/EntitySetHandler.java @@ -1,11 +1,11 @@ package cn.hutool.db.handler; +import cn.hutool.db.Entity; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedHashSet; -import cn.hutool.db.Entity; - /** * 结果集处理类 ,处理出的结果为Entity列表,结果不能重复(按照Entity对象去重) * @author loolly @@ -15,7 +15,7 @@ public class EntitySetHandler implements RsHandler>{ private static final long serialVersionUID = 8191723216703506736L; /** 是否大小写不敏感 */ - private boolean caseInsensitive; + private final boolean caseInsensitive; /** * 创建一个 EntityHandler对象 diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java b/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java index deba5ef76..e9762bebc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java @@ -148,7 +148,6 @@ public class HandleHelper { * @since 3.3.1 */ public static T handleRow(T row, int columnCount, ResultSetMetaData meta, ResultSet rs, boolean withMetaInfo) throws SQLException { - String columnLabel; int type; for (int i = 1; i <= columnCount; i++) { type = meta.getColumnType(i); diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/PageResultHandler.java b/hutool-db/src/main/java/cn/hutool/db/handler/PageResultHandler.java index d4b7d08cf..3d7c78af0 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/PageResultHandler.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/PageResultHandler.java @@ -1,11 +1,11 @@ package cn.hutool.db.handler; -import java.sql.ResultSet; -import java.sql.SQLException; - import cn.hutool.db.Entity; import cn.hutool.db.PageResult; +import java.sql.ResultSet; +import java.sql.SQLException; + /** * 分页结果集处理类 ,处理出的结果为PageResult * @@ -14,11 +14,11 @@ import cn.hutool.db.PageResult; public class PageResultHandler implements RsHandler> { private static final long serialVersionUID = -1474161855834070108L; - private PageResult pageResult; + private final PageResult pageResult; /** * 是否大小写不敏感 */ - private boolean caseInsensitive; + private final boolean caseInsensitive; /** * 创建一个 EntityHandler对象
    diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/JdbcType.java b/hutool-db/src/main/java/cn/hutool/db/meta/JdbcType.java index e980ab615..969891170 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/JdbcType.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/JdbcType.java @@ -4,7 +4,10 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** + * JDBC中字段类型枚举 + * * @author Clinton Begin + * @see java.sql.Types */ public enum JdbcType { ARRAY(java.sql.Types.ARRAY), // @@ -60,10 +63,10 @@ public enum JdbcType { this.typeCode = code; } - private static Map codeMap = new ConcurrentHashMap<>(100, 1); + private static final Map CODE_MAP = new ConcurrentHashMap<>(100, 1); static { for (JdbcType type : JdbcType.values()) { - codeMap.put(type.typeCode, type); + CODE_MAP.put(type.typeCode, type); } } @@ -74,7 +77,7 @@ public enum JdbcType { * @return {@link JdbcType} */ public static JdbcType valueOf(int code) { - return codeMap.get(code); + return CODE_MAP.get(code); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 43d50aaef..f8e917ab9 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -144,7 +144,7 @@ public class MetaUtil { } } } - return columnNames.toArray(new String[columnNames.size()]); + return columnNames.toArray(new String[0]); } catch (Exception e) { throw new DbRuntimeException("Get columns error!", e); } finally { @@ -172,7 +172,6 @@ public class MetaUtil { * @param tableName 表名 * @return Table对象 */ - @SuppressWarnings("resource") public static Table getTableMeta(DataSource ds, String tableName) { final Table table = Table.create(tableName); Connection conn = null; diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/Table.java b/hutool-db/src/main/java/cn/hutool/db/meta/Table.java index b5e5f4098..62a12f5e0 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/Table.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/Table.java @@ -21,8 +21,8 @@ public class Table implements Serializable, Cloneable { /** 注释 */ private String comment; /** 主键字段名列表 */ - private Set pkNames = new LinkedHashSet(); - private Map columns = new LinkedHashMap<>(); + private Set pkNames = new LinkedHashSet<>(); + private final Map columns = new LinkedHashMap<>(); public static Table create(String tableName) { return new Table(tableName); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/TableType.java b/hutool-db/src/main/java/cn/hutool/db/meta/TableType.java index 84d231325..99fc289a5 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/TableType.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/TableType.java @@ -14,7 +14,7 @@ public enum TableType { ALIAS("ALIAS"), SYNONYM("SYNONYM"); - private String value; + private final String value; /** * 构造 diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java index da2f5a37c..38502b24b 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java @@ -353,17 +353,6 @@ public class MongoDS implements Closeable { log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost); } - // multiplier for connectionsPerHost for # of threads that can block if connectionsPerHost is 10, and threadsAllowedToBlockForConnectionMultiplier is 5, then 50 threads can block more than - // that and an exception will be throw --int - Integer threadsAllowedToBlockForConnectionMultiplier = setting.getInt(group + "threadsAllowedToBlockForConnectionMultiplier"); - if (StrUtil.isBlank(group) == false && threadsAllowedToBlockForConnectionMultiplier == null) { - threadsAllowedToBlockForConnectionMultiplier = setting.getInt("threadsAllowedToBlockForConnectionMultiplier"); - } - if (threadsAllowedToBlockForConnectionMultiplier != null) { - builder.threadsAllowedToBlockForConnectionMultiplier(threadsAllowedToBlockForConnectionMultiplier); - log.debug("MongoDB threadsAllowedToBlockForConnectionMultiplier: {}", threadsAllowedToBlockForConnectionMultiplier); - } - // 被阻塞线程从连接池获取连接的最长等待时间(ms) --int Integer connectTimeout = setting.getInt(group + "connectTimeout"); if (StrUtil.isBlank(group) == false && connectTimeout == null) { diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java index b616a866f..262c64bf7 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java @@ -1,13 +1,14 @@ package cn.hutool.db.nosql.mongo; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.RuntimeUtil; +import cn.hutool.setting.Setting; + import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.setting.Setting; - /** * MongoDB工厂类,用于创建 * @author looly @@ -19,16 +20,11 @@ public class MongoFactory { private final static String GROUP_SEPRATER = ","; /** 数据源池 */ - private static Map dsMap = new ConcurrentHashMap<>(); + private static final Map DS_MAP = new ConcurrentHashMap<>(); // JVM关闭前关闭MongoDB连接 static { - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - MongoFactory.closeAll(); - } - }); + RuntimeUtil.addShutdownHook(MongoFactory::closeAll); } // ------------------------------------------------------------------------ Get DS start @@ -41,11 +37,11 @@ public class MongoFactory { */ public static MongoDS getDS(String host, int port) { final String key = host + ":" + port; - MongoDS ds = dsMap.get(key); + MongoDS ds = DS_MAP.get(key); if (null == ds) { // 没有在池中加入之 ds = new MongoDS(host, port); - dsMap.put(key, ds); + DS_MAP.put(key, ds); } return ds; @@ -60,11 +56,11 @@ public class MongoFactory { */ public static MongoDS getDS(String... groups) { final String key = ArrayUtil.join(groups, GROUP_SEPRATER); - MongoDS ds = dsMap.get(key); + MongoDS ds = DS_MAP.get(key); if (null == ds) { // 没有在池中加入之 ds = new MongoDS(groups); - dsMap.put(key, ds); + DS_MAP.put(key, ds); } return ds; @@ -77,7 +73,7 @@ public class MongoFactory { * @return MongoDB连接 */ public static MongoDS getDS(Collection groups) { - return getDS(groups.toArray(new String[groups.size()])); + return getDS(groups.toArray(new String[0])); } /** @@ -89,11 +85,11 @@ public class MongoFactory { */ public static MongoDS getDS(Setting setting, String... groups) { final String key = setting.getSettingPath() + GROUP_SEPRATER + ArrayUtil.join(groups, GROUP_SEPRATER); - MongoDS ds = dsMap.get(key); + MongoDS ds = DS_MAP.get(key); if (null == ds) { // 没有在池中加入之 ds = new MongoDS(setting, groups); - dsMap.put(key, ds); + DS_MAP.put(key, ds); } return ds; @@ -107,7 +103,7 @@ public class MongoFactory { * @return MongoDB连接 */ public static MongoDS getDS(Setting setting, Collection groups) { - return getDS(setting, groups.toArray(new String[groups.size()])); + return getDS(setting, groups.toArray(new String[0])); } // ------------------------------------------------------------------------ Get DS ends @@ -115,11 +111,11 @@ public class MongoFactory { * 关闭全部连接 */ public static void closeAll() { - if(CollectionUtil.isNotEmpty(dsMap)){ - for(MongoDS ds : dsMap.values()) { + if(CollectionUtil.isNotEmpty(DS_MAP)){ + for(MongoDS ds : DS_MAP.values()) { ds.close(); } - dsMap.clear(); + DS_MAP.clear(); } } } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java b/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java index d5471a29e..017a2113a 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java @@ -1,37 +1,40 @@ package cn.hutool.db.sql; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - import cn.hutool.core.clone.CloneSupport; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.text.StrSpliter; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + /** * 条件对象
    - * - * @author Looly * + * @author Looly */ public class Condition extends CloneSupport { /** * SQL中 LIKE 语句查询方式
    - * - * @author Looly * + * @author Looly */ public enum LikeType { - /** 以给定值开头,拼接后的SQL "value%" */ + /** + * 以给定值开头,拼接后的SQL "value%" + */ StartWith, - /** 以给定值开头,拼接后的SQL "%value" */ + /** + * 以给定值开头,拼接后的SQL "%value" + */ EndWith, - /** 包含给定值,拼接后的SQL "%value%" */ + /** + * 包含给定值,拼接后的SQL "%value%" + */ Contains } @@ -44,21 +47,31 @@ public class Condition extends CloneSupport { private static final String VALUE_NULL = "NULL"; - /** 字段 */ + /** + * 字段 + */ private String field; - /** 运算符(大于号,小于号,等于号 like 等) */ + /** + * 运算符(大于号,小于号,等于号 like 等) + */ private String operator; - /** 值 */ + /** + * 值 + */ private Object value; - /** 是否使用条件值占位符 */ + /** + * 是否使用条件值占位符 + */ private boolean isPlaceHolder = true; - /** between firstValue and secondValue */ + /** + * between firstValue and secondValue + */ private Object secondValue; /** * 解析为Condition - * - * @param field 字段名 + * + * @param field 字段名 * @param expression 表达式或普通值 * @return Condition */ @@ -67,6 +80,7 @@ public class Condition extends CloneSupport { } // --------------------------------------------------------------- Constructor start + /** * 构造 */ @@ -75,7 +89,7 @@ public class Condition extends CloneSupport { /** * 构造 - * + * * @param isPlaceHolder 是否使用条件值占位符 */ public Condition(boolean isPlaceHolder) { @@ -84,7 +98,7 @@ public class Condition extends CloneSupport { /** * 构造,使用等于表达式(运算符是=) - * + * * @param field 字段 * @param value 值 */ @@ -95,10 +109,10 @@ public class Condition extends CloneSupport { /** * 构造 - * - * @param field 字段 + * + * @param field 字段 * @param operator 运算符(大于号,小于号,等于号 like 等) - * @param value 值 + * @param value 值 */ public Condition(String field, String operator, Object value) { this.field = field; @@ -108,9 +122,9 @@ public class Condition extends CloneSupport { /** * 构造 - * - * @param field 字段 - * @param value 值 + * + * @param field 字段 + * @param value 值 * @param likeType {@link LikeType} */ public Condition(String field, String value, LikeType likeType) { @@ -121,6 +135,7 @@ public class Condition extends CloneSupport { // --------------------------------------------------------------- Constructor end // --------------------------------------------------------------- Getters and Setters start + /** * @return 字段 */ @@ -130,7 +145,7 @@ public class Condition extends CloneSupport { /** * 设置字段名 - * + * * @param field 字段名 */ public void setField(String field) { @@ -140,7 +155,7 @@ public class Condition extends CloneSupport { /** * 获得运算符
    * 大于号,小于号,等于号 等 - * + * * @return 运算符 */ public String getOperator() { @@ -150,7 +165,7 @@ public class Condition extends CloneSupport { /** * 设置运算符
    * 大于号,小于号,等于号 等 - * + * * @param operator 运算符 */ public void setOperator(String operator) { @@ -159,7 +174,7 @@ public class Condition extends CloneSupport { /** * 获得值 - * + * * @return 值 */ public Object getValue() { @@ -168,7 +183,7 @@ public class Condition extends CloneSupport { /** * 设置值,不解析表达式 - * + * * @param value 值 */ public void setValue(Object value) { @@ -177,8 +192,8 @@ public class Condition extends CloneSupport { /** * 设置值 - * - * @param value 值 + * + * @param value 值 * @param isParse 是否解析值表达式 */ public void setValue(Object value, boolean isParse) { @@ -190,7 +205,7 @@ public class Condition extends CloneSupport { /** * 是否使用条件占位符 - * + * * @return 是否使用条件占位符 */ public boolean isPlaceHolder() { @@ -199,7 +214,7 @@ public class Condition extends CloneSupport { /** * 设置是否使用条件占位符 - * + * * @param isPlaceHolder 是否使用条件占位符 */ public void setPlaceHolder(boolean isPlaceHolder) { @@ -218,7 +233,7 @@ public class Condition extends CloneSupport { /** * 是否IN条件 - * + * * @return 是否IN条件 * @since 4.0.1 */ @@ -228,7 +243,7 @@ public class Condition extends CloneSupport { /** * 是否IS条件 - * + * * @return 是否IS条件 * @since 4.0.1 */ @@ -238,7 +253,7 @@ public class Condition extends CloneSupport { /** * 检查值是否为null,如果为null转换为 "IS NULL"形式 - * + * * @return this */ public Condition checkValueNull() { @@ -276,7 +291,7 @@ public class Condition extends CloneSupport { /** * 转换为条件字符串,并回填占位符对应的参数值 - * + * * @param paramValues 参数列表,用于回填占位符对应参数值 * @return 条件字符串 */ @@ -297,7 +312,7 @@ public class Condition extends CloneSupport { if (isPlaceHolder() && false == isOperatorIs()) { // 使用条件表达式占位符,条件表达式并不适用于 IS NULL conditionStrBuilder.append(" ?"); - if(null != paramValues) { + if (null != paramValues) { paramValues.add(this.value); } } else { @@ -310,19 +325,20 @@ public class Condition extends CloneSupport { } // ----------------------------------------------------------------------------------------------- Private method start + /** * 构建BETWEEN语句中的值部分
    * 开头必须加空格,类似:" ? AND ?" 或者 " 1 AND 2" - * + * * @param conditionStrBuilder 条件语句构建器 - * @param paramValues 参数集合,用于参数占位符对应参数回填 + * @param paramValues 参数集合,用于参数占位符对应参数回填 */ private void buildValuePartForBETWEEN(StringBuilder conditionStrBuilder, List paramValues) { // BETWEEN x AND y 的情况,两个参数 if (isPlaceHolder()) { // 使用条件表达式占位符 conditionStrBuilder.append(" ?"); - if(null != paramValues) { + if (null != paramValues) { paramValues.add(this.value); } } else { @@ -335,7 +351,7 @@ public class Condition extends CloneSupport { if (isPlaceHolder()) { // 使用条件表达式占位符 conditionStrBuilder.append(" ?"); - if(null != paramValues) { + if (null != paramValues) { paramValues.add(this.secondValue); } } else { @@ -347,9 +363,9 @@ public class Condition extends CloneSupport { /** * 构建IN语句中的值部分
    * 开头必须加空格,类似:" (?,?,?)" 或者 " (1,2,3,4)" - * + * * @param conditionStrBuilder 条件语句构建器 - * @param paramValues 参数集合,用于参数占位符对应参数回填 + * @param paramValues 参数集合,用于参数占位符对应参数回填 */ private void buildValuePartForIN(StringBuilder conditionStrBuilder, List paramValues) { conditionStrBuilder.append(" ("); @@ -361,12 +377,9 @@ public class Condition extends CloneSupport { valuesForIn = StrUtil.split((CharSequence) value, ','); } else { valuesForIn = Arrays.asList(Convert.convert(String[].class, value)); - if (null == valuesForIn) { - valuesForIn = CollUtil.newArrayList(Convert.toStr(value)); - } } conditionStrBuilder.append(StrUtil.repeatAndJoin("?", valuesForIn.size(), ",")); - if(null != paramValues) { + if (null != paramValues) { paramValues.addAll(valuesForIn); } } else { @@ -461,7 +474,7 @@ public class Condition extends CloneSupport { /** * 去掉包围在字符串两端的单引号或双引号 - * + * * @param value 值 * @return 去掉引号后的值 */ @@ -474,15 +487,15 @@ public class Condition extends CloneSupport { int from = 0; int to = value.length(); char startChar = value.charAt(0); - char endChar = value.charAt(to - 1); + char endChar = value.charAt(value.length() - 1); if (startChar == endChar) { if ('\'' == startChar || '"' == startChar) { from = 1; - to = to - 1; + to--; } } - if (from == 0 && to == value.length()) { + if (from == 0) { // 并不包含,返回原值 return value; } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/NamedSql.java b/hutool-db/src/main/java/cn/hutool/db/sql/NamedSql.java index e58eea699..5899c29e5 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/NamedSql.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/NamedSql.java @@ -1,12 +1,12 @@ package cn.hutool.db.sql; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.StrUtil; + import java.util.LinkedList; import java.util.List; import java.util.Map; -import cn.hutool.core.text.StrBuilder; -import cn.hutool.core.util.StrUtil; - /** * 使用命名占位符的SQL,例如:select * from table where field1=:name1
    * 支持的占位符格式为: @@ -22,7 +22,7 @@ import cn.hutool.core.util.StrUtil; public class NamedSql { private String sql; - private List params; + private final List params; /** * 构造 diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java index f5ca1de29..f1775ddcf 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java @@ -1,11 +1,5 @@ package cn.hutool.db.sql; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map.Entry; - import cn.hutool.core.builder.Builder; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; @@ -15,6 +9,12 @@ import cn.hutool.db.DbRuntimeException; import cn.hutool.db.Entity; import cn.hutool.db.dialect.DialectName; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map.Entry; + /** * SQL构建器
    * 首先拼接SQL语句,值使用 ? 占位
    @@ -404,12 +404,13 @@ public class SqlBuilder implements Builder{ } sql.append(" ORDER BY "); - String field = null; + String field; boolean isFirst = true; for (Order order : orders) { + field = order.getField(); if (null != wrapper) { // 包装字段名 - field = wrapper.wrap(order.getField()); + field = wrapper.wrap(field); } if (StrUtil.isBlank(field)) { continue; diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java index f7e93966b..0ba99bde8 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java @@ -70,8 +70,8 @@ public class SqlFormatter { boolean afterInsert = false; int inFunction = 0; int parensSinceSelect = 0; - private LinkedList parenCounts = new LinkedList<>(); - private LinkedList afterByOrFromOrSelects = new LinkedList<>(); + private final LinkedList parenCounts = new LinkedList<>(); + private final LinkedList afterByOrFromOrSelects = new LinkedList<>(); int indent = 1; @@ -273,14 +273,13 @@ public class SqlFormatter { } if (this.inFunction > 0) { this.inFunction -= 1; - out(); } else { if (!this.afterByOrSetOrFromOrSelect) { this.indent -= 1; newline(); } - out(); } + out(); this.beginLine = false; } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlUtil.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlUtil.java index 471fc182c..1a8bfa03d 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlUtil.java @@ -1,5 +1,12 @@ package cn.hutool.db.sql; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.DbRuntimeException; +import cn.hutool.db.Entity; +import cn.hutool.db.sql.Condition.LikeType; + import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; @@ -12,13 +19,6 @@ import java.sql.SQLException; import java.util.List; import java.util.Map.Entry; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.DbRuntimeException; -import cn.hutool.db.Entity; -import cn.hutool.db.sql.Condition.LikeType; - /** * SQL相关工具类,包括相关SQL语句拼接等 * @@ -105,7 +105,7 @@ public class SqlUtil { */ public static String buildLikeValue(String value, LikeType likeType, boolean withLikeKeyword) { if (null == value) { - return value; + return null; } StringBuilder likeValue = StrUtil.builder(withLikeKeyword ? "LIKE " : ""); diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java b/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java index 47418dfa1..525fcc5a2 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java @@ -1,15 +1,15 @@ package cn.hutool.db.sql; -import java.util.Arrays; -import java.util.Collection; -import java.util.Map.Entry; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Editor; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.Entity; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map.Entry; + /** * 包装器
    * 主要用于字段名的包装(在字段名的前后加字符,例如反引号来避免与数据库的关键字冲突) @@ -135,7 +135,7 @@ public class Wrapper { return fields; } - return Arrays.asList(wrap(fields.toArray(new String[fields.size()]))); + return Arrays.asList(wrap(fields.toArray(new String[0]))); } /** diff --git a/hutool-db/src/main/java/cn/hutool/db/transaction/TransactionLevel.java b/hutool-db/src/main/java/cn/hutool/db/transaction/TransactionLevel.java index 87e655103..a80f0def8 100644 --- a/hutool-db/src/main/java/cn/hutool/db/transaction/TransactionLevel.java +++ b/hutool-db/src/main/java/cn/hutool/db/transaction/TransactionLevel.java @@ -58,7 +58,7 @@ public enum TransactionLevel { SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE); /** 事务级别,对应Connection中的常量值 */ - private int level; + private final int level; TransactionLevel(int level) { this.level = level; diff --git a/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java b/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java index f916f6bc8..671c98069 100644 --- a/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java @@ -22,7 +22,7 @@ import java.util.List; */ public class CRUDTest { - private static Db db = Db.use("test"); + private static final Db db = Db.use("test"); @Test public void findIsNullTest() throws SQLException { @@ -87,7 +87,8 @@ public class CRUDTest { @Test public void findInTest2() throws SQLException { - List results = db.findAll(Entity.create("user").set("id", new Condition("id", new long[]{1, 2, 3}))); + List results = db.findAll(Entity.create("user") + .set("id", new Condition("id", new long[]{1, 2, 3}))); Assert.assertEquals(2, results.size()); } diff --git a/hutool-db/src/test/java/cn/hutool/db/ConcurentTest.java b/hutool-db/src/test/java/cn/hutool/db/ConcurentTest.java index b590e6117..61932406c 100644 --- a/hutool-db/src/test/java/cn/hutool/db/ConcurentTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/ConcurentTest.java @@ -1,16 +1,15 @@ package cn.hutool.db; -import java.sql.SQLException; -import java.util.List; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.db.handler.EntityListHandler; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import java.sql.SQLException; +import java.util.List; /** * SqlRunner线程安全测试 @@ -31,17 +30,14 @@ public class ConcurentTest { @Test public void findTest() { for(int i = 0; i < 10000; i++) { - ThreadUtil.execute(new Runnable() { - @Override - public void run() { - List find; - try { - find = db.find(CollectionUtil.newArrayList("name AS name2"), Entity.create("user"), new EntityListHandler()); - } catch (SQLException e) { - throw new DbRuntimeException(e); - } - Console.log(find); + ThreadUtil.execute(() -> { + List find; + try { + find = db.find(CollectionUtil.newArrayList("name AS name2"), Entity.create("user"), new EntityListHandler()); + } catch (SQLException e) { + throw new DbRuntimeException(e); } + Console.log(find); }); } diff --git a/hutool-db/src/test/java/cn/hutool/db/DerbyTest.java b/hutool-db/src/test/java/cn/hutool/db/DerbyTest.java new file mode 100644 index 000000000..19549cb14 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/DerbyTest.java @@ -0,0 +1,50 @@ +package cn.hutool.db; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import java.sql.SQLException; +import java.util.List; + +/** + * Derby数据库单元测试 + * + * @author looly + * + */ +public class DerbyTest { + + private static final String DS_GROUP_NAME = "derby"; + + @BeforeClass + public static void init() throws SQLException { + Db db = Db.use(DS_GROUP_NAME); + try{ + db.execute("CREATE TABLE test(a INTEGER, b BIGINT)"); + }catch (SQLException e){ + // 数据库已存在 + return; + } + + db.insert(Entity.create("test").set("a", 1).set("b", 11)); + db.insert(Entity.create("test").set("a", 2).set("b", 21)); + db.insert(Entity.create("test").set("a", 3).set("b", 31)); + db.insert(Entity.create("test").set("a", 4).set("b", 41)); + } + + @Test + @Ignore + public void queryTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).query("select * from test"); + Assert.assertEquals(4, query.size()); + } + + @Test + @Ignore + public void findTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).find(Entity.create("test")); + Assert.assertEquals(4, query.size()); + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/H2Test.java b/hutool-db/src/test/java/cn/hutool/db/H2Test.java new file mode 100644 index 000000000..86263c783 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/H2Test.java @@ -0,0 +1,42 @@ +package cn.hutool.db; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.sql.SQLException; +import java.util.List; + +/** + * H2数据库单元测试 + * + * @author looly + * + */ +public class H2Test { + + private static final String DS_GROUP_NAME = "h2"; + + @BeforeClass + public static void init() throws SQLException { + Db db = Db.use(DS_GROUP_NAME); + db.execute("CREATE TABLE test(a INTEGER, b BIGINT)"); + + db.insert(Entity.create("test").set("a", 1).set("b", 11)); + db.insert(Entity.create("test").set("a", 2).set("b", 21)); + db.insert(Entity.create("test").set("a", 3).set("b", 31)); + db.insert(Entity.create("test").set("a", 4).set("b", 41)); + } + + @Test + public void queryTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).query("select * from test"); + Assert.assertEquals(4, query.size()); + } + + @Test + public void findTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).find(Entity.create("test")); + Assert.assertEquals(4, query.size()); + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/HsqldbTest.java b/hutool-db/src/test/java/cn/hutool/db/HsqldbTest.java index e5c607c06..54669ce1e 100644 --- a/hutool-db/src/test/java/cn/hutool/db/HsqldbTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/HsqldbTest.java @@ -1,15 +1,12 @@ package cn.hutool.db; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + import java.sql.SQLException; import java.util.List; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import cn.hutool.db.Db; -import cn.hutool.db.Entity; - /** * HSQLDB数据库单元测试 * @@ -20,8 +17,8 @@ public class HsqldbTest { private static final String DS_GROUP_NAME = "hsqldb"; - @Before - public void init() throws SQLException { + @BeforeClass + public static void init() throws SQLException { Db db = Db.use(DS_GROUP_NAME); db.execute("CREATE TABLE test(a INTEGER, b BIGINT)"); db.insert(Entity.create("test").set("a", 1).set("b", 11)); @@ -35,4 +32,10 @@ public class HsqldbTest { List query = Db.use(DS_GROUP_NAME).query("select * from test"); Assert.assertEquals(4, query.size()); } + + @Test + public void findTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).find(Entity.create("test")); + Assert.assertEquals(4, query.size()); + } } diff --git a/hutool-db/src/test/java/cn/hutool/db/PageResultTest.java b/hutool-db/src/test/java/cn/hutool/db/PageResultTest.java new file mode 100644 index 000000000..61f9a2876 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/PageResultTest.java @@ -0,0 +1,14 @@ +package cn.hutool.db; + +import org.junit.Assert; +import org.junit.Test; + +public class PageResultTest { + + @Test + public void isLastTest(){ + // 每页2条,共10条,总共5页,第一页是0,最后一页应该是4 + final PageResult result = new PageResult<>(4, 2, 10); + Assert.assertTrue(result.isLast()); + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/PageTest.java b/hutool-db/src/test/java/cn/hutool/db/PageTest.java new file mode 100644 index 000000000..7c7ca116e --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/PageTest.java @@ -0,0 +1,17 @@ +package cn.hutool.db; + +import cn.hutool.db.sql.Order; +import org.junit.Assert; +import org.junit.Test; + +public class PageTest { + + @Test + public void addOrderTest() { + Page page = new Page(); + page.addOrder(new Order("aaa")); + Assert.assertEquals(page.getOrders().length, 1); + page.addOrder(new Order("aaa")); + Assert.assertEquals(page.getOrders().length, 2); + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/SessionTest.java b/hutool-db/src/test/java/cn/hutool/db/SessionTest.java index 978996edc..66b77e708 100644 --- a/hutool-db/src/test/java/cn/hutool/db/SessionTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/SessionTest.java @@ -1,11 +1,9 @@ package cn.hutool.db; -import java.sql.SQLException; - import org.junit.Ignore; import org.junit.Test; -import cn.hutool.core.lang.func.VoidFunc1; +import java.sql.SQLException; /** * 事务性数据库操作单元测试 @@ -30,11 +28,6 @@ public class SessionTest { @Test @Ignore public void txTest() throws SQLException { - Session.create("test").tx(new VoidFunc1() { - @Override - public void call(Session session) throws SQLException { - session.update(Entity.create().set("age", 78), Entity.create("user").set("name", "unitTestUser")); - } - }); + Session.create("test").tx(session -> session.update(Entity.create().set("age", 78), Entity.create("user").set("name", "unitTestUser"))); } } diff --git a/hutool-db/src/test/java/cn/hutool/db/nosql/RedisDSTest.java b/hutool-db/src/test/java/cn/hutool/db/nosql/RedisDSTest.java new file mode 100644 index 000000000..ec83d5555 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/nosql/RedisDSTest.java @@ -0,0 +1,15 @@ +package cn.hutool.db.nosql; + +import cn.hutool.db.nosql.redis.RedisDS; +import org.junit.Ignore; +import org.junit.Test; +import redis.clients.jedis.Jedis; + +public class RedisDSTest { + + @Test + @Ignore + public void redisDSTest(){ + final Jedis jedis = RedisDS.create().getJedis(); + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/sql/SqlBuilderTest.java b/hutool-db/src/test/java/cn/hutool/db/sql/SqlBuilderTest.java index 6f3027359..2ae41a39a 100644 --- a/hutool-db/src/test/java/cn/hutool/db/sql/SqlBuilderTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/sql/SqlBuilderTest.java @@ -19,4 +19,16 @@ public class SqlBuilderTest { SqlBuilder builder4 = SqlBuilder.create().select().from("user").where(LogicalOperator.AND, new Condition("name", "is not null")); Assert.assertEquals("SELECT * FROM user WHERE name IS NOT NULL", builder4.build()); } + + @Test + public void orderByTest(){ + SqlBuilder builder = SqlBuilder.create().select("id", "username").from("user") + .join("role", SqlBuilder.Join.INNER) + .on("user.id = role.user_id") + .where(new Condition("age", ">=", 18), + new Condition("username", "abc", Condition.LikeType.Contains) + ).orderBy(new Order("id")); + + Assert.assertEquals("SELECT id,username FROM user INNER JOIN role ON user.id = role.user_id WHERE age >= ? AND username LIKE ? ORDER BY id", builder.build()); + } } diff --git a/hutool-db/src/test/resources/config/db.setting b/hutool-db/src/test/resources/config/db.setting index 4d764d5a3..b3ef7cc0f 100644 --- a/hutool-db/src/test/resources/config/db.setting +++ b/hutool-db/src/test/resources/config/db.setting @@ -28,7 +28,17 @@ url = jdbc:sqlite:test.db [hsqldb] url = jdbc:hsqldb:mem:mem_hutool user = SA -pass = +pass = + +# 测试用HSQLDB数据库 +[h2] +url = jdbc:h2:mem:h2_hutool +user = sa +pass = + +# 测试用HSQLDB数据库 +[derby] +url = jdbc:derby:.derby/test_db;create=true # 测试用Oracle数据库 [orcl] diff --git a/hutool-db/src/test/resources/config/redis.setting b/hutool-db/src/test/resources/config/redis.setting index d9dec37cb..033299766 100644 --- a/hutool-db/src/test/resources/config/redis.setting +++ b/hutool-db/src/test/resources/config/redis.setting @@ -18,7 +18,7 @@ connectionTimeout = 2000 # 读取超时,默认timeout soTimeout = 2000 # 密码,默认无 -password = +#password = # 数据库序号,默认0 database = 0 # 客户端名,默认"Hutool" diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index a714f97f0..f8f84ebbd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-dfa diff --git a/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java b/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java index 8fbfa476e..c61f71b61 100644 --- a/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java +++ b/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java @@ -16,7 +16,7 @@ public final class SensitiveUtil { // private static final Log log = LogFactory.get(); public static final char DEFAULT_SEPARATOR = StrUtil.C_COMMA; - private static WordTree sensitiveTree = new WordTree(); + private static final WordTree sensitiveTree = new WordTree(); /** * @return 是否已经被初始化 diff --git a/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java b/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java index 69f76e393..0d715b338 100644 --- a/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java +++ b/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java @@ -1,5 +1,10 @@ package cn.hutool.dfa; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.StrUtil; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -7,11 +12,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.lang.Filter; -import cn.hutool.core.text.StrBuilder; -import cn.hutool.core.util.StrUtil; - /** * DFA(Deterministic Finite Automaton 确定有穷自动机) * DFA单词树(以下简称单词树),常用于在某大段文字中快速查找某几个关键词是否存在。
    @@ -33,7 +33,7 @@ public class WordTree extends HashMap { /** * 敏感词字符末尾标识,用于标识单词末尾字符 */ - private Set endCharacterSet = new HashSet<>(); + private final Set endCharacterSet = new HashSet<>(); /** * 字符过滤规则,通过定义字符串过滤规则,过滤不需要的字符,当accept为false时,此字符不参与匹配 */ diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index f43628c29..7ef57f39b 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.2 - 3.0.19.RELEASE + 3.1.2.RELEASE 1.3.0 2.3.30 4.8 @@ -30,6 +30,7 @@ 3.6 5.1.1 4.0.1 + 2.2.6.RELEASE @@ -210,13 +211,31 @@ org.springframework.boot spring-boot-starter - 2.2.5.RELEASE + ${spring-boot.version} + true + + + io.github.biezhi + TinyPinyin + 2.0.3.RELEASE + true + + + com.belerweb + pinyin4j + 2.5.1 + true + + + com.github.stuxuhai + jpinyin + 1.1.8 true org.springframework.boot spring-boot-starter-test - 2.2.5.RELEASE + ${spring-boot.version} test diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java index ff605b0a2..2b45d1ed9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java @@ -1,44 +1,48 @@ package cn.hutool.extra.ftp; -import java.io.Closeable; -import java.io.File; -import java.nio.charset.Charset; -import java.util.List; - import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; +import java.io.Closeable; +import java.io.File; +import java.nio.charset.Charset; +import java.util.List; + /** * 抽象FTP类,用于定义通用的FTP方法 - * + * * @author looly * @since 4.1.14 */ public abstract class AbstractFtp implements Closeable { - - public static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8 ; - - protected String host; - protected int port; - - protected String user; - protected String password; - - protected Charset charset; + + public static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + + protected FtpConfig ftpConfig; + + /** + * 构造 + * + * @param config FTP配置 + * @since 5.3.3 + */ + protected AbstractFtp(FtpConfig config) { + this.ftpConfig = config; + } /** * 如果连接超时的话,重新进行连接 - * @since 4.5.2 - * + * * @return this + * @since 4.5.2 */ public abstract AbstractFtp reconnectIfTimeout(); - + /** * 打开指定目录 - * + * * @param directory directory * @return 是否打开目录 */ @@ -46,7 +50,7 @@ public abstract class AbstractFtp implements Closeable { /** * 打开上级目录 - * + * * @return 是否打开目录 * @since 4.0.5 */ @@ -56,14 +60,14 @@ public abstract class AbstractFtp implements Closeable { /** * 远程当前目录(工作目录) - * + * * @return 远程当前目录 */ public abstract String pwd(); /** * 在当前远程目录(工作目录)下创建新的目录 - * + * * @param dir 目录名 * @return 是否创建成功 */ @@ -71,7 +75,7 @@ public abstract class AbstractFtp implements Closeable { /** * 文件或目录是否存在 - * + * * @param path 目录 * @return 是否存在 */ @@ -84,7 +88,7 @@ public abstract class AbstractFtp implements Closeable { /** * 遍历某个目录下所有文件和目录,不会递归遍历 - * + * * @param path 需要遍历的目录 * @return 文件和目录列表 */ @@ -92,7 +96,7 @@ public abstract class AbstractFtp implements Closeable { /** * 删除指定目录下的指定文件 - * + * * @param path 目录路径 * @return 是否存在 */ @@ -100,7 +104,7 @@ public abstract class AbstractFtp implements Closeable { /** * 删除文件夹及其文件夹下的所有文件 - * + * * @param dirPath 文件夹路径 * @return boolean 是否删除成功 */ @@ -108,23 +112,23 @@ public abstract class AbstractFtp implements Closeable { /** * 创建指定文件夹及其父目录,从根目录开始创建,创建完成后回到默认的工作目录 - * + * * @param dir 文件夹路径,绝对路径 */ public void mkDirs(String dir) { final String[] dirs = StrUtil.trim(dir).split("[\\\\/]+"); final String now = pwd(); - if(dirs.length > 0 && StrUtil.isEmpty(dirs[0])) { + if (dirs.length > 0 && StrUtil.isEmpty(dirs[0])) { //首位为空,表示以/开头 this.cd(StrUtil.SLASH); } - for (int i = 0; i < dirs.length; i++) { - if (StrUtil.isNotEmpty(dirs[i])) { - if (false == cd(dirs[i])) { + for (String s : dirs) { + if (StrUtil.isNotEmpty(s)) { + if (false == cd(s)) { //目录不存在时创建 - mkdir(dirs[i]); - cd(dirs[i]); + mkdir(s); + cd(s); } } } @@ -135,26 +139,36 @@ public abstract class AbstractFtp implements Closeable { /** * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与file文件名相同。 * 覆盖模式 - * + * * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param file 需要上传的文件 + * @param file 需要上传的文件 * @return 是否成功 */ public abstract boolean upload(String destPath, File file); /** * 下载文件 - * - * @param path 文件路径 + * + * @param path 文件路径 * @param outFile 输出文件或目录 */ public abstract void download(String path, File outFile); + /** + * 递归下载FTP服务器上文件到本地(文件目录和服务器同步), 服务器上有新文件会覆盖本地文件 + * + * @param sourcePath ftp服务器目录 + * @param destDir 本地目录 + * @since 5.3.5 + */ + public abstract void recursiveDownloadFolder(String sourcePath, File destDir); + // ---------------------------------------------------------------------------------------------------------------------------------------- Private method start + /** * 是否包含指定字符串,忽略大小写 - * - * @param names 文件或目录名列表 + * + * @param names 文件或目录名列表 * @param nameToFind 要查找的文件或目录名 * @return 是否包含 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index 9e3bb1be6..7f64b16b0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -1,7 +1,9 @@ package cn.hutool.extra.ftp; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; @@ -20,23 +22,27 @@ import java.util.List; /** * FTP客户端封装
    * 此客户端基于Apache-Commons-Net - * + * * @author looly * @since 4.1.8 */ public class Ftp extends AbstractFtp { - /** 默认端口 */ + /** + * 默认端口 + */ public static final int DEFAULT_PORT = 21; private FTPClient client; private FtpMode mode; - /** 执行完操作是否返回当前目录 */ + /** + * 执行完操作是否返回当前目录 + */ private boolean backToPwd; /** * 构造,匿名登录 - * + * * @param host 域名或IP */ public Ftp(String host) { @@ -45,7 +51,7 @@ public class Ftp extends AbstractFtp { /** * 构造,匿名登录 - * + * * @param host 域名或IP * @param port 端口 */ @@ -55,10 +61,10 @@ public class Ftp extends AbstractFtp { /** * 构造 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 */ public Ftp(String host, int port, String user, String password) { @@ -67,12 +73,12 @@ public class Ftp extends AbstractFtp { /** * 构造 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 - * @param charset 编码 + * @param charset 编码 */ public Ftp(String host, int port, String user, String password, Charset charset) { this(host, port, user, password, charset, null); @@ -81,19 +87,25 @@ public class Ftp extends AbstractFtp { /** * 构造 * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 - * @param charset 编码 - * @param mode 模式 + * @param charset 编码 + * @param mode 模式 */ public Ftp(String host, int port, String user, String password, Charset charset, FtpMode mode) { - this.host = host; - this.port = port; - this.user = user; - this.password = password; - this.charset = charset; + this(new FtpConfig(host, port, user, password, charset), mode); + } + + /** + * 构造 + * + * @param config FTP配置 + * @param mode 模式 + */ + public Ftp(FtpConfig config, FtpMode mode) { + super(config); this.mode = mode; this.init(); } @@ -104,15 +116,15 @@ public class Ftp extends AbstractFtp { * @return this */ public Ftp init() { - return this.init(this.host, this.port, this.user, this.password, this.mode); + return this.init(this.ftpConfig, this.mode); } /** * 初始化连接 * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 * @return this */ @@ -122,22 +134,35 @@ public class Ftp extends AbstractFtp { /** * 初始化连接 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 - * @param mode 模式 + * @param mode 模式 * @return this */ public Ftp init(String host, int port, String user, String password, FtpMode mode) { + return init(new FtpConfig(host, port, user, password, this.ftpConfig.getCharset()), mode); + } + + /** + * 初始化连接 + * + * @param config FTP配置 + * @param mode 模式 + * @return this + */ + public Ftp init(FtpConfig config, FtpMode mode) { final FTPClient client = new FTPClient(); - client.setControlEncoding(this.charset.toString()); + client.setControlEncoding(config.getCharset().toString()); + client.setConnectTimeout((int) config.getConnectionTimeout()); try { // 连接ftp服务器 - client.connect(host, port); + client.connect(config.getHost(), config.getPort()); + client.setSoTimeout((int) config.getSoTimeout()); // 登录ftp服务器 - client.login(user, password); + client.login(config.getUser(), config.getPassword()); } catch (IOException e) { throw new FtpException(e); } @@ -148,7 +173,7 @@ public class Ftp extends AbstractFtp { } catch (IOException e) { // ignore } - throw new FtpException("Login failed for user [{}], reply code is: [{}]", user, replyCode); + throw new FtpException("Login failed for user [{}], reply code is: [{}]", config.getUser(), replyCode); } this.client = client; if (mode != null) { @@ -159,7 +184,7 @@ public class Ftp extends AbstractFtp { /** * 设置FTP连接模式,可选主动和被动模式 - * + * * @param mode 模式枚举 * @return this * @since 4.1.19 @@ -167,19 +192,19 @@ public class Ftp extends AbstractFtp { public Ftp setMode(FtpMode mode) { this.mode = mode; switch (mode) { - case Active: - this.client.enterLocalActiveMode(); - break; - case Passive: - this.client.enterLocalPassiveMode(); - break; + case Active: + this.client.enterLocalActiveMode(); + break; + case Passive: + this.client.enterLocalPassiveMode(); + break; } return this; } /** * 设置执行完操作是否返回当前目录 - * + * * @param backToPwd 执行完操作是否返回当前目录 * @return this * @since 4.6.0 @@ -191,7 +216,7 @@ public class Ftp extends AbstractFtp { /** * 如果连接超时的话,重新进行连接 经测试,当连接超时时,client.isConnected()仍然返回ture,无法判断是否连接超时 因此,通过发送pwd命令的方式,检查连接是否超时 - * + * * @return this */ @Override @@ -211,7 +236,7 @@ public class Ftp extends AbstractFtp { /** * 改变目录 - * + * * @param directory 目录 * @return 是否成功 */ @@ -230,7 +255,7 @@ public class Ftp extends AbstractFtp { /** * 远程当前目录 - * + * * @return 远程当前目录 * @since 4.1.14 */ @@ -254,9 +279,37 @@ public class Ftp extends AbstractFtp { return fileNames; } + /** + * 遍历某个目录下所有文件和目录,不会递归遍历
    + * 此方法自动过滤"."和".."两种目录 + * + * @param path 目录 + * @param filter 过滤器,null表示不过滤,默认去掉"."和".."两种目录 + * @return 文件或目录列表 + * @since 5.3.5 + */ + public List lsFiles(String path, Filter filter) { + final FTPFile[] ftpFiles = lsFiles(path); + if (ArrayUtil.isEmpty(ftpFiles)) { + return ListUtil.empty(); + } + + final List result = new ArrayList<>(ftpFiles.length - 2); + String fileName; + for (FTPFile ftpFile : ftpFiles) { + fileName = ftpFile.getName(); + if (false == StrUtil.equals(".", fileName) && false == StrUtil.equals("..", fileName)) { + if (null == filter || filter.accept(ftpFile)) { + result.add(ftpFile); + } + } + } + return result; + } + /** * 遍历某个目录下所有文件和目录,不会递归遍历 - * + * * @param path 目录 * @return 文件或目录列表 */ @@ -291,7 +344,7 @@ public class Ftp extends AbstractFtp { /** * 判断ftp服务器文件是否存在 - * + * * @param path 文件路径 * @return 是否存在 */ @@ -356,15 +409,15 @@ public class Ftp extends AbstractFtp { /** * 上传文件到指定目录,可选: - * + * *
     	 * 1. path为null或""上传到当前路径
     	 * 2. path为相对路径则相对于当前路径的子路径
     	 * 3. path为绝对路径则上传到此路径
     	 * 
    - * + * * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param file 文件 + * @param file 文件 * @return 是否上传成功 */ @Override @@ -375,15 +428,15 @@ public class Ftp extends AbstractFtp { /** * 上传文件到指定目录,可选: - * + * *
     	 * 1. path为null或""上传到当前路径
     	 * 2. path为相对路径则相对于当前路径的子路径
     	 * 3. path为绝对路径则上传到此路径
     	 * 
    - * - * @param file 文件 - * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * + * @param file 文件 + * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 * @param fileName 自定义在服务端保存的文件名 * @return 是否上传成功 */ @@ -397,16 +450,15 @@ public class Ftp extends AbstractFtp { /** * 上传文件到指定目录,可选: - * + * *
     	 * 1. path为null或""上传到当前路径
     	 * 2. path为相对路径则相对于当前路径的子路径
     	 * 3. path为绝对路径则上传到此路径
     	 * 
    - * - * - * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param fileName 文件名 + * + * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * @param fileName 文件名 * @param fileStream 文件流 * @return 是否上传成功 */ @@ -443,8 +495,8 @@ public class Ftp extends AbstractFtp { /** * 下载文件 - * - * @param path 文件路径 + * + * @param path 文件路径 * @param outFile 输出文件或目录 */ @Override @@ -454,12 +506,42 @@ public class Ftp extends AbstractFtp { download(dir, fileName, outFile); } + /** + * 递归下载FTP服务器上文件到本地(文件目录和服务器同步) + * + * @param sourcePath ftp服务器目录 + * @param destDir 本地目录 + */ + @Override + public void recursiveDownloadFolder(String sourcePath, File destDir) { + String fileName; + String srcFile; + File destFile; + for (FTPFile ftpFile : lsFiles(sourcePath, null)) { + fileName = ftpFile.getName(); + srcFile = StrUtil.format("{}/{}", sourcePath, fileName); + destFile = FileUtil.file(destDir, fileName); + + if (false == ftpFile.isDirectory()) { + // 本地不存在文件或者ftp上文件有修改则下载 + if (false == FileUtil.exist(destFile) + || (ftpFile.getTimestamp().getTimeInMillis() > destFile.lastModified())) { + download(srcFile, destFile); + } + } else { + // 服务端依旧是目录,继续递归 + FileUtil.mkdir(destFile); + recursiveDownloadFolder(srcFile, destFile); + } + } + } + /** * 下载文件 - * - * @param path 文件路径 + * + * @param path 文件路径 * @param fileName 文件名 - * @param outFile 输出文件或目录 + * @param outFile 输出文件或目录 */ public void download(String path, String fileName, File outFile) { if (outFile.isDirectory()) { @@ -477,10 +559,10 @@ public class Ftp extends AbstractFtp { /** * 下载文件到输出流 - * - * @param path 文件路径 + * + * @param path 文件路径 * @param fileName 文件名 - * @param out 输出位置 + * @param out 输出位置 */ public void download(String path, String fileName, OutputStream out) { String pwd = null; @@ -503,7 +585,7 @@ public class Ftp extends AbstractFtp { /** * 获取FTPClient客户端对象 - * + * * @return {@link FTPClient} */ public FTPClient getClient() { diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/FtpConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/FtpConfig.java new file mode 100644 index 000000000..f87d494d0 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/FtpConfig.java @@ -0,0 +1,134 @@ +package cn.hutool.extra.ftp; + +import java.io.Serializable; +import java.nio.charset.Charset; + +/** + * FTP配置项,提供FTP各种参数信息 + * + * @author looly + */ +public class FtpConfig implements Serializable { + private static final long serialVersionUID = 1L; + + public static FtpConfig create(){ + return new FtpConfig(); + } + + /** + * 主机 + */ + private String host; + /** + * 端口 + */ + private int port; + /** + * 用户名 + */ + private String user; + /** + * 密码 + */ + private String password; + /** + * 编码 + */ + private Charset charset; + + /** + * 连接超时时长,单位毫秒 + */ + private long connectionTimeout; + + /** + * Socket连接超时时长,单位毫秒 + */ + private long soTimeout; + + /** + * 构造 + */ + public FtpConfig() { + } + + /** + * 构造 + * + * @param host 主机 + * @param port 端口 + * @param user 用户名 + * @param password 密码 + * @param charset 编码 + */ + public FtpConfig(String host, int port, String user, String password, Charset charset) { + this.host = host; + this.port = port; + this.user = user; + this.password = password; + this.charset = charset; + } + + public String getHost() { + return host; + } + + public FtpConfig setHost(String host) { + this.host = host; + return this; + } + + public int getPort() { + return port; + } + + public FtpConfig setPort(int port) { + this.port = port; + return this; + } + + public String getUser() { + return user; + } + + public FtpConfig setUser(String user) { + this.user = user; + return this; + } + + public String getPassword() { + return password; + } + + public FtpConfig setPassword(String password) { + this.password = password; + return this; + } + + public Charset getCharset() { + return charset; + } + + public FtpConfig setCharset(Charset charset) { + this.charset = charset; + return this; + } + + public long getConnectionTimeout() { + return connectionTimeout; + } + + public FtpConfig setConnectionTimeout(long connectionTimeout) { + this.connectionTimeout = connectionTimeout; + return this; + } + + public long getSoTimeout() { + return soTimeout; + } + + public FtpConfig setSoTimeout(long soTimeout) { + this.soTimeout = soTimeout; + return this; + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java index e0a80ae64..8290817ad 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java @@ -1,15 +1,15 @@ package cn.hutool.extra.mail; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; +import cn.hutool.core.util.ArrayUtil; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeUtility; - -import cn.hutool.core.util.ArrayUtil; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * 邮件内部工具类 @@ -30,15 +30,13 @@ public class InternalMailUtil { public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) { final List resultList = new ArrayList<>(addrStrs.length); InternetAddress[] addrs; - for (int i = 0; i < addrStrs.length; i++) { - addrs = parseAddress(addrStrs[i], charset); - if(ArrayUtil.isNotEmpty(addrs)) { - for(int j = 0 ; j < addrs.length; j++) { - resultList.add(addrs[j]); - } + for (String addrStr : addrStrs) { + addrs = parseAddress(addrStr, charset); + if (ArrayUtil.isNotEmpty(addrs)) { + Collections.addAll(resultList, addrs); } } - return resultList.toArray(new InternetAddress[resultList.size()]); + return resultList.toArray(new InternetAddress[0]); } /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java index d3d2284a5..fa8a40b52 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java @@ -1,10 +1,11 @@ package cn.hutool.extra.mail; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.Date; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import javax.activation.DataHandler; import javax.activation.DataSource; @@ -19,13 +20,11 @@ import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.util.ByteArrayDataSource; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Date; /** * 邮件发送客户端 @@ -38,7 +37,7 @@ public class Mail { /** * 邮箱帐户信息以及一些客户端配置信息 */ - private MailAccount mailAccount; + private final MailAccount mailAccount; /** * 收件人列表 */ @@ -70,7 +69,7 @@ public class Mail { /** * 正文、附件和图片的混合部分 */ - private Multipart multipart = new MimeMultipart(); + private final Multipart multipart = new MimeMultipart(); /** * 是否使用全局会话,默认为false */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java index c174a5e2e..8641c990d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java @@ -467,7 +467,7 @@ public class MailAccount implements Serializable { if (this.starttlsEnable) { //STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 - p.put(STARTTLS_ENABLE, String.valueOf(this.starttlsEnable)); + p.put(STARTTLS_ENABLE, "true"); if (null == this.sslEnable) { //为了兼容旧版本,当用户没有此项配置时,按照starttlsEnable开启状态时对待 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailUtil.java index adfd5bed0..1af4c9491 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailUtil.java @@ -1,5 +1,10 @@ package cn.hutool.extra.mail; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; + import java.io.File; import java.io.InputStream; import java.util.Collection; @@ -7,11 +12,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; - /** * 邮件工具类,基于javax.mail封装 * @@ -370,14 +370,14 @@ public class MailUtil { // 可选抄送人 if (CollUtil.isNotEmpty(ccs)) { - mail.setCcs(ccs.toArray(new String[ccs.size()])); + mail.setCcs(ccs.toArray(new String[0])); } // 可选密送人 if (CollUtil.isNotEmpty(bccs)) { - mail.setBccs(bccs.toArray(new String[bccs.size()])); + mail.setBccs(bccs.toArray(new String[0])); } - mail.setTos(tos.toArray(new String[tos.size()])); + mail.setTos(tos.toArray(new String[0])); mail.setTitle(subject); mail.setContent(content); mail.setHtml(isHtml); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/UserPassAuthenticator.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/UserPassAuthenticator.java index 3cd25a2fe..6e0e5a19f 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/UserPassAuthenticator.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/UserPassAuthenticator.java @@ -11,8 +11,8 @@ import javax.mail.PasswordAuthentication; */ public class UserPassAuthenticator extends Authenticator { - private String user; - private String pass; + private final String user; + private final String pass; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinEngine.java new file mode 100644 index 000000000..6df9a8446 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinEngine.java @@ -0,0 +1,53 @@ +package cn.hutool.extra.pinyin; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; + +/** + * 拼音引擎接口,具体的拼音实现通过实现此接口,完成具体实现功能 + * + * @author looly + * @since 5.3.3 + */ +public interface PinyinEngine { + + /** + * 如果c为汉字,则返回大写拼音;如果c不是汉字,则返回String.valueOf(c) + * + * @param c 任意字符,汉字返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 + */ + String getPinyin(char c); + + /** + * 获取字符串对应的完整拼音,非中文返回原字符 + * + * @param str 字符串 + * @param separator 拼音之间的分隔符 + * @return 拼音 + */ + String getPinyin(String str, String separator); + + /** + * 将输入字符串转为拼音首字母,其它字符原样返回 + * + * @param c 任意字符,汉字返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 + */ + default char getFirstLetter(char c) { + return getPinyin(c).charAt(0); + } + + /** + * 将输入字符串转为拼音首字母,其它字符原样返回 + * + * @param str 任意字符,汉字返回拼音,非汉字原样返回 + * @param separator 分隔符 + * @return 汉字返回拼音,非汉字原样返回 + */ + default String getFirstLetter(String str, String separator) { + final String splitSeparator = StrUtil.isEmpty(separator) ? "#" : separator; + final String[] split = StrUtil.split(getPinyin(str, splitSeparator), splitSeparator); + return ArrayUtil.join(split, separator, (s)->String.valueOf(s.charAt(0))); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinException.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinException.java new file mode 100644 index 000000000..ff1f5063c --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinException.java @@ -0,0 +1,33 @@ +package cn.hutool.extra.pinyin; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; + +/** + * 模板异常 + * + * @author xiaoleilu + */ +public class PinyinException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public PinyinException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public PinyinException(String message) { + super(message); + } + + public PinyinException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public PinyinException(String message, Throwable throwable) { + super(message, throwable); + } + + public PinyinException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java new file mode 100644 index 000000000..9e59bdc45 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java @@ -0,0 +1,85 @@ +package cn.hutool.extra.pinyin; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.pinyin.engine.PinyinFactory; + +/** + * 拼音工具类,封装了TinyPinyin、JPinyin、Pinyin4j,通过SPI自动识别。 + * + * @author looly + */ +public class PinyinUtil { + + private static final String CHINESE_REGEX = "[\\u4e00-\\u9fa5]"; + + /** + * 获得全局单例的拼音引擎 + * + * @return 全局单例的拼音引擎 + */ + public static PinyinEngine getEngine(){ + return PinyinFactory.get(); + } + + /** + * 如果c为汉字,则返回大写拼音;如果c不是汉字,则返回String.valueOf(c) + * + * @param c 任意字符,汉字返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 + */ + public static String getPinyin(char c) { + return getEngine().getPinyin(c); + } + + /** + * 将输入字符串转为拼音,每个字之间的拼音使用空格分隔 + * + * @param str 任意字符,汉字返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 + */ + public static String getPinyin(String str) { + return getPinyin(str, StrUtil.SPACE); + } + + /** + * 将输入字符串转为拼音,以字符为单位插入分隔符 + * + * @param str 任意字符,汉字返回拼音,非汉字原样返回 + * @param separator 每个字拼音之间的分隔符 + * @return 汉字返回拼音,非汉字原样返回 + */ + public static String getPinyin(String str, String separator) { + return getEngine().getPinyin(str, separator); + } + + /** + * 将输入字符串转为拼音首字母,其它字符原样返回 + * + * @param c 任意字符,汉字返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 + */ + public static char getFirstLetter(char c) { + return getEngine().getFirstLetter(c); + } + + /** + * 将输入字符串转为拼音首字母,其它字符原样返回 + * + * @param str 任意字符,汉字返回拼音,非汉字原样返回 + * @param separator 分隔符 + * @return 汉字返回拼音,非汉字原样返回 + */ + public static String getFirstLetter(String str, String separator) { + return getEngine().getFirstLetter(str, separator); + } + + /** + * 是否为中文字符 + * + * @param c 字符 + * @return 是否为中文字符 + */ + public static boolean isChinese(char c) { + return '〇' == c || String.valueOf(c).matches(CHINESE_REGEX); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java new file mode 100644 index 000000000..cef4a5fd0 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java @@ -0,0 +1,52 @@ +package cn.hutool.extra.pinyin.engine; + +import cn.hutool.core.lang.Singleton; +import cn.hutool.core.util.ServiceLoaderUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.pinyin.PinyinEngine; +import cn.hutool.extra.template.TemplateException; +import cn.hutool.log.StaticLog; + +/** + * 简单拼音引擎工厂,用于根据用户引入的拼音库jar,自动创建对应的拼音引擎对象 + * + * @author looly + */ +public class PinyinFactory { + + /** + * 获得单例的PinyinEngine + * + * @return 单例的PinyinEngine + */ + public static PinyinEngine get(){ + return Singleton.get(PinyinEngine.class.getName(), PinyinFactory::create); + } + + /** + * 根据用户引入的拼音引擎jar,自动创建对应的拼音引擎对象
    + * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@link PinyinEngine} + */ + public static PinyinEngine create() { + final PinyinEngine engine = doCreate(); + StaticLog.debug("Use [{}] Engine As Default.", StrUtil.removeSuffix(engine.getClass().getSimpleName(), "Engine")); + return engine; + } + + /** + * 根据用户引入的拼音引擎jar,自动创建对应的拼音引擎对象
    + * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@link PinyinEngine} + */ + private static PinyinEngine doCreate() { + final PinyinEngine engine = ServiceLoaderUtil.loadFirstAvailable(PinyinEngine.class); + if(null != engine){ + return engine; + } + + throw new TemplateException("No pinyin jar found ! Please add one of it to your project !"); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/JPinyinEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/JPinyinEngine.java new file mode 100644 index 000000000..d17cfc1d0 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/JPinyinEngine.java @@ -0,0 +1,64 @@ +package cn.hutool.extra.pinyin.engine.jpinyin; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.extra.pinyin.PinyinEngine; +import com.github.stuxuhai.jpinyin.PinyinException; +import com.github.stuxuhai.jpinyin.PinyinFormat; +import com.github.stuxuhai.jpinyin.PinyinHelper; + +/** + * 封装了Jpinyin的引擎。 + * + *

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

    + * + *

    + * 引入: + *

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

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

    + * + *

    + * 引入: + *

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

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

    + * + *

    + * 引入: + *

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

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

    + * + *

    + * 引入: + *

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

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

    + * + *

    + * 引入: + *

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

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

    + * + *

    + * 引入: + *

    + * <dependency>
    + *     <groupId>io.github.biezhi</groupId>
    + *     <artifactId>TinyPinyin</artifactId>
    + *     <version>2.0.3.RELEASE</version>
    + * </dependency>
    + * 
    + * + * @author looly + */ +package cn.hutool.extra.pinyin.engine.tinypinyin; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/package-info.java new file mode 100644 index 000000000..1e1bb8603 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/package-info.java @@ -0,0 +1,7 @@ +/** + * 拼音工具封装,基于TinyPinyin + * + * @author looly + * + */ +package cn.hutool.extra.pinyin; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/BufferedImageLuminanceSource.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/BufferedImageLuminanceSource.java index 50f2fcad3..c11f3e847 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/BufferedImageLuminanceSource.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/BufferedImageLuminanceSource.java @@ -99,6 +99,7 @@ public final class BufferedImageLuminanceSource extends LuminanceSource { return true; } + @SuppressWarnings("SuspiciousNameCombination") @Override public LuminanceSource rotateCounterClockwise() { diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java index 73c5927fa..f0cee3c74 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java @@ -1,5 +1,6 @@ package cn.hutool.extra.qrcode; +import cn.hutool.core.codec.Base64; import cn.hutool.core.img.Img; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.util.CharsetUtil; @@ -27,19 +28,75 @@ import java.util.HashMap; /** * 基于Zxing的二维码工具类 - * + * * @author looly * @since 4.0.2 - * */ public class QrCodeUtil { + /** + * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 + * + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @param logoBase64 logo 图片的 base64 编码 + * @return 图片 Base64 编码字符串 + */ + public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, String logoBase64) { + return generateAsBase64(content, qrConfig, imageType, Base64.decode(logoBase64)); + } + + /** + * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 + * + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @param logo logo 图片的byte[] + * @return 图片 Base64 编码字符串 + */ + public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, byte[] logo) { + return generateAsBase64(content, qrConfig, imageType, ImgUtil.toImage(logo)); + } + + /** + * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 + * + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @param logo logo 图片的byte[] + * @return 图片 Base64 编码字符串 + */ + public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, Image logo) { + qrConfig.setImg(logo); + return generateAsBase64(content, qrConfig, imageType); + } + + /** + * 生成 Base64 编码格式的二维码,以 String 形式表示 + * + *

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

    + * + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @return 图片 Base64 编码字符串 + */ + public static String generateAsBase64(String content, QrConfig qrConfig, String imageType) { + final BufferedImage img = generate(content, qrConfig); + return ImgUtil.toBase64DateUri(img, imageType); + } + /** * 生成PNG格式的二维码图片,以byte[]形式表示 - * + * * @param content 内容 - * @param width 宽度 - * @param height 高度 + * @param width 宽度 + * @param height 高度 * @return 图片的byte[] * @since 4.0.10 */ @@ -51,9 +108,9 @@ public class QrCodeUtil { /** * 生成PNG格式的二维码图片,以byte[]形式表示 - * + * * @param content 内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return 图片的byte[] * @since 4.1.2 */ @@ -65,10 +122,10 @@ public class QrCodeUtil { /** * 生成二维码到文件,二维码图片格式取决于文件的扩展名 - * - * @param content 文本内容 - * @param width 宽度 - * @param height 高度 + * + * @param content 文本内容 + * @param width 宽度 + * @param height 高度 * @param targetFile 目标文件,扩展名决定输出格式 * @return 目标文件 */ @@ -80,9 +137,9 @@ public class QrCodeUtil { /** * 生成二维码到文件,二维码图片格式取决于文件的扩展名 - * - * @param content 文本内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * + * @param content 文本内容 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @param targetFile 目标文件,扩展名决定输出格式 * @return 目标文件 * @since 4.1.2 @@ -95,12 +152,12 @@ public class QrCodeUtil { /** * 生成二维码到输出流 - * - * @param content 文本内容 - * @param width 宽度 - * @param height 高度 + * + * @param content 文本内容 + * @param width 宽度 + * @param height 高度 * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} - * @param out 目标流 + * @param out 目标流 */ public static void generate(String content, int width, int height, String imageType, OutputStream out) { final BufferedImage image = generate(content, width, height); @@ -109,11 +166,11 @@ public class QrCodeUtil { /** * 生成二维码到输出流 - * - * @param content 文本内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * + * @param content 文本内容 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} - * @param out 目标流 + * @param out 目标流 * @since 4.1.2 */ public static void generate(String content, QrConfig config, String imageType, OutputStream out) { @@ -123,10 +180,10 @@ public class QrCodeUtil { /** * 生成二维码图片 - * + * * @param content 文本内容 - * @param width 宽度 - * @param height 高度 + * @param width 宽度 + * @param height 高度 * @return 二维码图片(黑白) */ public static BufferedImage generate(String content, int width, int height) { @@ -135,11 +192,11 @@ public class QrCodeUtil { /** * 生成二维码或条形码图片 - * + * * @param content 文本内容 - * @param format 格式,可选二维码或者条形码 - * @param width 宽度 - * @param height 高度 + * @param format 格式,可选二维码或者条形码 + * @param width 宽度 + * @param height 高度 * @return 二维码图片(黑白) */ public static BufferedImage generate(String content, BarcodeFormat format, int width, int height) { @@ -148,9 +205,9 @@ public class QrCodeUtil { /** * 生成二维码图片 - * + * * @param content 文本内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return 二维码图片(黑白) * @since 4.1.2 */ @@ -161,10 +218,10 @@ public class QrCodeUtil { /** * 生成二维码或条形码图片
    * 只有二维码时QrConfig中的图片才有效 - * + * * @param content 文本内容 - * @param format 格式,可选二维码、条形码等 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param format 格式,可选二维码、条形码等 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return 二维码图片(黑白) * @since 4.1.14 */ @@ -197,12 +254,13 @@ public class QrCodeUtil { } // ------------------------------------------------------------------------------------------------------------------- encode + /** * 将文本内容编码为二维码 - * + * * @param content 文本内容 - * @param width 宽度 - * @param height 高度 + * @param width 宽度 + * @param height 高度 * @return {@link BitMatrix} */ public static BitMatrix encode(String content, int width, int height) { @@ -211,9 +269,9 @@ public class QrCodeUtil { /** * 将文本内容编码为二维码 - * + * * @param content 文本内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return {@link BitMatrix} * @since 4.1.2 */ @@ -223,11 +281,11 @@ public class QrCodeUtil { /** * 将文本内容编码为条形码或二维码 - * + * * @param content 文本内容 - * @param format 格式枚举 - * @param width 宽度 - * @param height 高度 + * @param format 格式枚举 + * @param width 宽度 + * @param height 高度 * @return {@link BitMatrix} */ public static BitMatrix encode(String content, BarcodeFormat format, int width, int height) { @@ -236,10 +294,10 @@ public class QrCodeUtil { /** * 将文本内容编码为条形码或二维码 - * + * * @param content 文本内容 - * @param format 格式枚举 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param format 格式枚举 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return {@link BitMatrix} * @since 4.1.2 */ @@ -260,9 +318,10 @@ public class QrCodeUtil { } // ------------------------------------------------------------------------------------------------------------------- decode + /** * 解码二维码图片为文本 - * + * * @param qrCodeInputstream 二维码输入流 * @return 解码文本 */ @@ -272,7 +331,7 @@ public class QrCodeUtil { /** * 解码二维码图片为文本 - * + * * @param qrCodeFile 二维码文件 * @return 解码文本 */ @@ -282,7 +341,7 @@ public class QrCodeUtil { /** * 将二维码图片解码为文本 - * + * * @param image {@link Image} 二维码图片 * @return 解码后的文本 */ @@ -292,9 +351,9 @@ public class QrCodeUtil { /** * 将二维码图片解码为文本 - * - * @param image {@link Image} 二维码图片 - * @param isTryHarder 是否优化精度 + * + * @param image {@link Image} 二维码图片 + * @param isTryHarder 是否优化精度 * @param isPureBarcode 是否使用复杂模式,扫描带logo的二维码设为true * @return 解码后的文本 * @since 4.3.1 @@ -330,8 +389,8 @@ public class QrCodeUtil { /** * BitMatrix转BufferedImage - * - * @param matrix BitMatrix + * + * @param matrix BitMatrix * @param foreColor 前景色 * @param backColor 背景色(null表示透明背景) * @return BufferedImage @@ -343,9 +402,9 @@ public class QrCodeUtil { BufferedImage image = new BufferedImage(width, height, null == backColor ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - if(matrix.get(x, y)) { + if (matrix.get(x, y)) { image.setRGB(x, y, foreColor); - } else if(null != backColor){ + } else if (null != backColor) { image.setRGB(x, y, backColor); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java index 70afe80fb..1f2ffd4d1 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java @@ -3,19 +3,23 @@ package cn.hutool.extra.servlet; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; +import cn.hutool.core.collection.ArrayIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.net.multipart.MultipartFormData; +import cn.hutool.core.net.multipart.UploadSetting; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import cn.hutool.extra.servlet.multipart.MultipartFormData; -import cn.hutool.extra.servlet.multipart.UploadSetting; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; @@ -227,13 +231,13 @@ public class ServletUtil { String ip; for (String header : headerNames) { ip = request.getHeader(header); - if (false == isUnknow(ip)) { - return getMultistageReverseProxyIp(ip); + if (false == NetUtil.isUnknown(ip)) { + return NetUtil.getMultistageReverseProxyIp(ip); } } ip = request.getRemoteAddr(); - return getMultistageReverseProxyIp(ip); + return NetUtil.getMultistageReverseProxyIp(ip); } /** @@ -262,7 +266,7 @@ public class ServletUtil { public static MultipartFormData getMultipart(ServletRequest request, UploadSetting uploadSetting) throws IORuntimeException { final MultipartFormData formData = new MultipartFormData(uploadSetting); try { - formData.parseRequest(request); + formData.parseRequestStream(request.getInputStream(), CharsetUtil.charset(request.getCharacterEncoding())); } catch (IOException e) { throw new IORuntimeException(e); } @@ -415,14 +419,15 @@ public class ServletUtil { * @return Cookie map */ public static Map readCookieMap(HttpServletRequest httpServletRequest) { - final Map cookieMap = new CaseInsensitiveMap<>(); final Cookie[] cookies = httpServletRequest.getCookies(); - if (null != cookies) { - for (Cookie cookie : cookies) { - cookieMap.put(cookie.getName(), cookie); - } + if(ArrayUtil.isEmpty(cookies)){ + return MapUtil.empty(); } - return cookieMap; + + return IterUtil.toMap( + new ArrayIter<>(httpServletRequest.getCookies()), + new CaseInsensitiveMap<>(), + Cookie::getName); } /** @@ -614,37 +619,4 @@ public class ServletUtil { } } // --------------------------------------------------------- Response end - - // --------------------------------------------------------- Private methd start - /** - * 从多级反向代理中获得第一个非unknown IP地址 - * - * @param ip 获得的IP地址 - * @return 第一个非unknown IP地址 - */ - private static String getMultistageReverseProxyIp(String ip) { - // 多级反向代理检测 - if (ip != null && ip.indexOf(",") > 0) { - final String[] ips = ip.trim().split(","); - for (String subIp : ips) { - if (false == isUnknow(subIp)) { - ip = subIp; - break; - } - } - } - return ip; - } - - /** - * 检测给定字符串是否为未知,多用于检测HTTP请求相关
    - * - * @param checkString 被检测的字符串 - * @return 是否未知 - */ - private static boolean isUnknow(String checkString) { - return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); - } - // --------------------------------------------------------- Private methd end - } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/package-info.java deleted file mode 100644 index 0328d88f6..000000000 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * 基于Servlet的文件上传封装 - * - * @author looly - * - */ -package cn.hutool.extra.servlet.multipart; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index f066c6f96..5a3b46c13 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -1,9 +1,12 @@ package cn.hutool.extra.spring; +import cn.hutool.core.util.ArrayUtil; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; +import java.util.Map; + /** * Spring(Spring boot)工具封装,包括: * @@ -19,12 +22,9 @@ public class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; - @SuppressWarnings("NullableProblems") @Override public void setApplicationContext(ApplicationContext applicationContext) { - if (SpringUtil.applicationContext == null) { - SpringUtil.applicationContext = applicationContext; - } + SpringUtil.applicationContext = applicationContext; } /** @@ -45,8 +45,8 @@ public class SpringUtil implements ApplicationContextAware { * @param name Bean名称 * @return Bean */ + @SuppressWarnings("unchecked") public static T getBean(String name) { - //noinspection unchecked return (T) applicationContext.getBean(name); } @@ -73,6 +73,59 @@ public class SpringUtil implements ApplicationContextAware { return applicationContext.getBean(name, clazz); } + /** + * 获取指定类型对应的所有Bean,包括子类 + * + * @param Bean类型 + * @param type 类、接口,null表示获取所有bean + * @return 类型对应的bean,key是bean注册的name,value是Bean + * @since 5.3.3 + */ + public static Map getBeansOfType(Class type){ + return applicationContext.getBeansOfType(type); + } + + /** + * 获取指定类型对应的Bean名称,包括子类 + * @param type 类、接口,null表示获取所有bean名称 + * @return bean名称 + * @since 5.3.3 + */ + public static String[] getBeanNamesForType(Class type){ + return applicationContext.getBeanNamesForType(type); + } + + /** + * 获取配置文件配置项的值 + * + * @param key 配置项key + * @return 属性值 + * @since 5.3.3 + */ + public static String getProperty(String key) { + return applicationContext.getEnvironment().getProperty(key); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + * @since 5.3.3 + */ + public static String[] getActiveProfiles(){ + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + * @since 5.3.3 + */ + public static String getActiveProfile(){ + final String[] activeProfiles = getActiveProfiles(); + return ArrayUtil.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java index b89f65fa6..aa2703da0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java @@ -1,13 +1,12 @@ package cn.hutool.extra.ssh; +import cn.hutool.core.lang.SimpleCache; import cn.hutool.core.util.StrUtil; import com.jcraft.jsch.Session; -import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; -import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; /** * Jsch会话池 @@ -20,11 +19,7 @@ public enum JschSessionPool { /** * SSH会话池,key:host,value:Session对象 */ - private Map sessionPool = new ConcurrentHashMap<>(); - /** - * 锁 - */ - private static final Object lock = new Object(); + private final SimpleCache cache = new SimpleCache<>(new HashMap<>()); /** * 获取Session,不存在返回null @@ -33,7 +28,7 @@ public enum JschSessionPool { * @return Session */ public Session get(String key) { - return sessionPool.get(key); + return cache.get(key); } /** @@ -47,17 +42,7 @@ public enum JschSessionPool { */ public Session getSession(String sshHost, int sshPort, String sshUser, String sshPass) { final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort); - Session session = get(key); - if (null == session || false == session.isConnected()) { - synchronized (lock) { - session = get(key); - if (null == session || false == session.isConnected()) { - session = JschUtil.openSession(sshHost, sshPort, sshUser, sshPass); - put(key, session); - } - } - } - return session; + return this.cache.get(key, ()-> JschUtil.openSession(sshHost, sshPort, sshUser, sshPass)); } /** @@ -72,17 +57,7 @@ public enum JschSessionPool { */ public Session getSession(String sshHost, int sshPort, String sshUser, String prvkey, byte[] passphrase) { final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort); - Session session = get(key); - if (null == session || false == session.isConnected()) { - synchronized (lock) { - session = get(key); - if (null == session || false == session.isConnected()) { - session = JschUtil.openSession(sshHost, sshPort, sshUser, prvkey, passphrase); - put(key, session); - } - } - } - return session; + return this.cache.get(key, ()->JschUtil.openSession(sshHost, sshPort, sshUser, prvkey, passphrase)); } /** @@ -92,7 +67,7 @@ public enum JschSessionPool { * @param session Session */ public void put(String key, Session session) { - this.sessionPool.put(key, session); + this.cache.put(key, session); } /** @@ -101,11 +76,11 @@ public enum JschSessionPool { * @param key 主机,格式为user@host:port */ public void close(String key) { - Session session = sessionPool.get(key); + Session session = get(key); if (session != null && session.isConnected()) { session.disconnect(); } - sessionPool.remove(key); + this.cache.remove(key); } /** @@ -116,7 +91,7 @@ public enum JschSessionPool { */ public void remove(Session session) { if (null != session) { - final Iterator> iterator = this.sessionPool.entrySet().iterator(); + final Iterator> iterator = this.cache.iterator(); Entry entry; while (iterator.hasNext()) { entry = iterator.next(); @@ -132,12 +107,13 @@ public enum JschSessionPool { * 关闭所有SSH连接会话 */ public void closeAll() { - Collection sessions = sessionPool.values(); - for (Session session : sessions) { - if (session.isConnected()) { + Session session; + for (Entry entry : this.cache) { + session = entry.getValue(); + if (session != null && session.isConnected()) { session.disconnect(); } } - sessionPool.clear(); + cache.clear(); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java index 55608f890..62211cab1 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java @@ -6,7 +6,13 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.net.LocalPortGenerater; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; -import com.jcraft.jsch.*; +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; import java.io.IOException; import java.io.InputStream; @@ -79,9 +85,24 @@ public class JschUtil { * @return SSH会话 */ public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass) { + return openSession(sshHost, sshPort, sshUser, sshPass, 0); + } + + /** + * 打开一个新的SSH会话 + * + * @param sshHost 主机 + * @param sshPort 端口 + * @param sshUser 用户名 + * @param sshPass 密码 + * @param timeout Socket连接超时时长,单位毫秒 + * @return SSH会话 + * @since 5.3.3 + */ + public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass, int timeout) { final Session session = createSession(sshHost, sshPort, sshUser, sshPass); try { - session.connect(); + session.connect(timeout); } catch (JSchException e) { throw new JschRuntimeException(e); } @@ -172,7 +193,7 @@ public class JschUtil { sshUser = "root"; } - if(null == jsch){ + if (null == jsch) { jsch = new JSch(); } @@ -251,7 +272,19 @@ public class JschUtil { * @since 4.0.3 */ public static ChannelSftp openSftp(Session session) { - return (ChannelSftp) openChannel(session, ChannelType.SFTP); + return openSftp(session, 0); + } + + /** + * 打开SFTP连接 + * + * @param session Session会话 + * @param timeout 连接超时时长,单位毫秒 + * @return {@link ChannelSftp} + * @since 5.3.3 + */ + public static ChannelSftp openSftp(Session session, int timeout) { + return (ChannelSftp) openChannel(session, ChannelType.SFTP, timeout); } /** @@ -299,9 +332,22 @@ public class JschUtil { * @since 4.5.2 */ public static Channel openChannel(Session session, ChannelType channelType) { + return openChannel(session, channelType, 0); + } + + /** + * 打开Channel连接 + * + * @param session Session会话 + * @param channelType 通道类型,可以是shell或sftp等,见{@link ChannelType} + * @param timeout 连接超时时长,单位毫秒 + * @return {@link Channel} + * @since 5.3.3 + */ + public static Channel openChannel(Session session, ChannelType channelType, int timeout) { final Channel channel = createChannel(session, channelType); try { - channel.connect(); + channel.connect(Math.max(timeout, 0)); } catch (JSchException e) { throw new JschRuntimeException(e); } @@ -343,7 +389,10 @@ public class JschUtil { } /** - * 执行Shell命令 + * 执行Shell命令(使用EXEC方式) + *

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

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

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

    + * + * @param session Session会话 + * @param cmd 命令 + * @param charset 发送和读取内容的编码 + * @return {@link ChannelExec} + * @since 5.2.5 + */ + public static String execByShell(Session session, String cmd, Charset charset) { + final ChannelShell shell = openShell(session); + // 开始连接 + shell.setPty(true); + OutputStream out = null; + InputStream in = null; + final StringBuilder result = StrUtil.builder(); + try { + out = shell.getOutputStream(); + in = shell.getInputStream(); + + out.write(StrUtil.bytes(cmd, charset)); + out.flush(); + + while (in.available() > 0) { + result.append(IoUtil.read(in, charset)); + } + } catch (IOException e) { + throw new IORuntimeException(e); + } finally { + IoUtil.close(out); + IoUtil.close(in); + close(shell); + } + return result.toString(); + } + /** * 关闭SSH连接会话 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index 8b8712caa..6ca943627 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -1,9 +1,12 @@ package cn.hutool.extra.ssh; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Filter; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.AbstractFtp; +import cn.hutool.extra.ftp.FtpConfig; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.ChannelSftp.LsEntry; import com.jcraft.jsch.ChannelSftp.LsEntrySelector; @@ -21,12 +24,12 @@ import java.util.Vector; * SFTP是Secure File Transfer Protocol的缩写,安全文件传送协议。可以为传输文件提供一种安全的加密方法。
    * SFTP 为 SSH的一部份,是一种传输文件到服务器的安全方式。SFTP是使用加密传输认证信息和传输的数据,所以,使用SFTP是非常安全的。
    * 但是,由于这种传输方式使用了加密/解密技术,所以传输效率比普通的FTP要低得多,如果您对网络安全性要求更高时,可以使用SFTP代替FTP。
    - * + * *

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

    - * + * * @author looly * @since 4.0.2 */ @@ -36,9 +39,10 @@ public class Sftp extends AbstractFtp { private ChannelSftp channel; // ---------------------------------------------------------------------------------------- Constructor start + /** * 构造 - * + * * @param sshHost 远程主机 * @param sshPort 远程主机端口 * @param sshUser 远程主机用户名 @@ -50,7 +54,7 @@ public class Sftp extends AbstractFtp { /** * 构造 - * + * * @param sshHost 远程主机 * @param sshPort 远程主机端口 * @param sshUser 远程主机用户名 @@ -59,12 +63,23 @@ public class Sftp extends AbstractFtp { * @since 4.1.14 */ public Sftp(String sshHost, int sshPort, String sshUser, String sshPass, Charset charset) { - init(sshHost, sshPort, sshUser, sshPass, charset); + this(new FtpConfig(sshHost, sshPort, sshUser, sshPass, charset)); } /** * 构造 - * + * + * @param config FTP配置 + * @since 5.3.3 + */ + public Sftp(FtpConfig config) { + super(config); + init(config); + } + + /** + * 构造 + * * @param session {@link Session} */ public Sftp(Session session) { @@ -73,29 +88,31 @@ public class Sftp extends AbstractFtp { /** * 构造 - * + * * @param session {@link Session} * @param charset 编码 * @since 4.1.14 */ public Sftp(Session session, Charset charset) { + super(FtpConfig.create().setCharset(charset)); init(session, charset); } /** * 构造 - * + * * @param channel {@link ChannelSftp} * @param charset 编码 */ public Sftp(ChannelSftp channel, Charset charset) { + super(FtpConfig.create().setCharset(charset)); init(channel, charset); } // ---------------------------------------------------------------------------------------- Constructor end /** * 构造 - * + * * @param sshHost 远程主机 * @param sshPort 远程主机端口 * @param sshUser 远程主机用户名 @@ -103,32 +120,47 @@ public class Sftp extends AbstractFtp { * @param charset 编码 */ public void init(String sshHost, int sshPort, String sshUser, String sshPass, Charset charset) { - this.host = sshHost; - this.port = sshPort; - this.user = sshUser; - this.password = sshPass; init(JschUtil.getSession(sshHost, sshPort, sshUser, sshPass), charset); } /** * 初始化 - * + * + * @since 5.3.3 + */ + public void init() { + init(this.ftpConfig); + } + + /** + * 初始化 + * + * @param config FTP配置 + * @since 5.3.3 + */ + public void init(FtpConfig config) { + init(config.getHost(), config.getPort(), config.getUser(), config.getPassword(), config.getCharset()); + } + + /** + * 初始化 + * * @param session {@link Session} * @param charset 编码 */ public void init(Session session, Charset charset) { this.session = session; - init(JschUtil.openSftp(session), charset); + init(JschUtil.openSftp(session, (int) this.ftpConfig.getConnectionTimeout()), charset); } /** * 初始化 - * + * * @param channel {@link ChannelSftp} * @param charset 编码 */ public void init(ChannelSftp channel, Charset charset) { - this.charset = charset; + this.ftpConfig.setCharset(charset); try { channel.setFilenameEncoding(charset.toString()); } catch (SftpException e) { @@ -139,15 +171,15 @@ public class Sftp extends AbstractFtp { @Override public Sftp reconnectIfTimeout() { - if (false == this.cd("/") && StrUtil.isNotBlank(this.host)) { - init(this.host, this.port, this.user, this.password, this.charset); + if (false == this.cd("/") && StrUtil.isNotBlank(this.ftpConfig.getHost())) { + init(this.ftpConfig); } return this; } /** * 获取SFTP通道客户端 - * + * * @return 通道客户端 * @since 4.1.14 */ @@ -157,7 +189,7 @@ public class Sftp extends AbstractFtp { /** * 远程当前目录 - * + * * @return 远程当前目录 */ @Override @@ -171,7 +203,7 @@ public class Sftp extends AbstractFtp { /** * 获取HOME路径 - * + * * @return HOME路径 * @since 4.0.5 */ @@ -185,7 +217,7 @@ public class Sftp extends AbstractFtp { /** * 遍历某个目录下所有文件或目录,不会递归遍历 - * + * * @param path 遍历某个目录下所有文件或目录 * @return 目录或文件名列表 * @since 4.0.5 @@ -197,7 +229,7 @@ public class Sftp extends AbstractFtp { /** * 遍历某个目录下所有目录,不会递归遍历 - * + * * @param path 遍历某个目录下所有目录 * @return 目录名列表 * @since 4.0.5 @@ -208,7 +240,7 @@ public class Sftp extends AbstractFtp { /** * 遍历某个目录下所有文件,不会递归遍历 - * + * * @param path 遍历某个目录下所有文件 * @return 文件名列表 * @since 4.0.5 @@ -218,29 +250,62 @@ public class Sftp extends AbstractFtp { } /** - * 遍历某个目录下所有文件或目录,不会递归遍历 - * - * @param path 遍历某个目录下所有文件或目录 + * 遍历某个目录下所有文件或目录,不会递归遍历
    + * 此方法自动过滤"."和".."两种目录 + * + * @param path 遍历某个目录下所有文件或目录 * @param filter 文件或目录过滤器,可以实现过滤器返回自己需要的文件或目录名列表 * @return 目录或文件名列表 * @since 4.0.5 */ public List ls(String path, final Filter filter) { - final List fileNames = new ArrayList<>(); + final List entries = lsEntries(path, filter); + if (CollUtil.isEmpty(entries)) { + return ListUtil.empty(); + } + return CollUtil.map(entries, LsEntry::getFilename, true); + } + + /** + * 遍历某个目录下所有文件或目录,生成LsEntry列表,不会递归遍历
    + * 此方法自动过滤"."和".."两种目录 + * + * @param path 遍历某个目录下所有文件或目录 + * @return 目录或文件名列表 + * @since 5.3.5 + */ + public List lsEntries(String path) { + return lsEntries(path, null); + } + + /** + * 遍历某个目录下所有文件或目录,生成LsEntry列表,不会递归遍历
    + * 此方法自动过滤"."和".."两种目录 + * + * @param path 遍历某个目录下所有文件或目录 + * @param filter 文件或目录过滤器,可以实现过滤器返回自己需要的文件或目录名列表 + * @return 目录或文件名列表 + * @since 5.3.5 + */ + public List lsEntries(String path, Filter filter) { + final List entryList = new ArrayList<>(); try { channel.ls(path, entry -> { - String fileName = entry.getFilename(); + final String fileName = entry.getFilename(); if (false == StrUtil.equals(".", fileName) && false == StrUtil.equals("..", fileName)) { if (null == filter || filter.accept(entry)) { - fileNames.add(entry.getFilename()); + entryList.add(entry); } } return LsEntrySelector.CONTINUE; }); } catch (SftpException e) { - throw new JschRuntimeException(e); + if (false == StrUtil.startWithIgnoreCase(e.getMessage(), "No such file")) { + throw new JschRuntimeException(e); + } + // 文件不存在忽略 } - return fileNames; + return entryList; } @Override @@ -255,7 +320,7 @@ public class Sftp extends AbstractFtp { /** * 打开指定目录,如果指定路径非目录或不存在返回false - * + * * @param directory directory * @return 是否打开目录 */ @@ -275,7 +340,7 @@ public class Sftp extends AbstractFtp { /** * 删除文件 - * + * * @param filePath 要删除的文件绝对路径 */ @Override @@ -290,7 +355,7 @@ public class Sftp extends AbstractFtp { /** * 删除文件夹及其文件夹下的所有文件 - * + * * @param dirPath 文件夹路径 * @return boolean 是否删除成功 */ @@ -341,9 +406,9 @@ public class Sftp extends AbstractFtp { /** * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。覆盖模式 - * + * * @param srcFilePath 本地文件路径 - * @param destPath 目标路径, + * @param destPath 目标路径, * @return this */ public Sftp put(String srcFilePath, String destPath) { @@ -352,23 +417,23 @@ public class Sftp extends AbstractFtp { /** * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。 - * + * * @param srcFilePath 本地文件路径 - * @param destPath 目标路径, - * @param mode {@link Mode} 模式 + * @param destPath 目标路径, + * @param mode {@link Mode} 模式 * @return this */ public Sftp put(String srcFilePath, String destPath, Mode mode) { return put(srcFilePath, destPath, null, mode); } - + /** * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。 - * + * * @param srcFilePath 本地文件路径 - * @param destPath 目标路径, - * @param monitor 上传进度监控,通过实现此接口完成进度显示 - * @param mode {@link Mode} 模式 + * @param destPath 目标路径, + * @param monitor 上传进度监控,通过实现此接口完成进度显示 + * @param mode {@link Mode} 模式 * @return this * @since 4.6.5 */ @@ -386,10 +451,41 @@ public class Sftp extends AbstractFtp { get(src, FileUtil.getAbsolutePath(destFile)); } + /** + * 递归下载FTP服务器上文件到本地(文件目录和服务器同步) + * + * @param sourcePath ftp服务器目录,必须为目录 + * @param destDir 本地目录 + */ + @Override + public void recursiveDownloadFolder(String sourcePath, File destDir) throws JschRuntimeException { + String fileName; + String srcFile; + File destFile; + for (LsEntry item : lsEntries(sourcePath)) { + fileName = item.getFilename(); + srcFile = StrUtil.format("{}/{}", sourcePath, fileName); + destFile = FileUtil.file(destDir, fileName); + + if (false == item.getAttrs().isDir()) { + // 本地不存在文件或者ftp上文件有修改则下载 + if (false == FileUtil.exist(destFile) + || (item.getAttrs().getMTime() > (destFile.lastModified() / 1000))) { + download(srcFile, destFile); + } + } else { + // 服务端依旧是目录,继续递归 + FileUtil.mkdir(destFile); + recursiveDownloadFolder(srcFile, destFile); + } + } + + } + /** * 获取远程文件 - * - * @param src 远程文件路径 + * + * @param src 远程文件路径 * @param dest 目标文件路径 * @return this */ @@ -408,18 +504,32 @@ public class Sftp extends AbstractFtp { JschUtil.close(this.session); } + @Override + public String toString() { + return "Sftp{" + + "host='" + this.ftpConfig.getHost() + '\'' + + ", port=" + this.ftpConfig.getPort() + + ", user='" + this.ftpConfig.getUser() + '\'' + + '}'; + } + /** * JSch支持的三种文件传输模式 - * - * @author looly * + * @author looly */ public enum Mode { - /** 完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。 */ + /** + * 完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。 + */ OVERWRITE, - /** 恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。 */ + /** + * 恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。 + */ RESUME, - /** 追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。 */ + /** + * 追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。 + */ APPEND } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/TemplateUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/template/TemplateUtil.java index bb0cc8354..d5311a987 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/TemplateUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/TemplateUtil.java @@ -18,7 +18,7 @@ public class TemplateUtil { * @since 4.1.11 */ public static TemplateEngine createEngine() { - return createEngine(new TemplateConfig()); + return TemplateFactory.create(); } /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/TemplateFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/TemplateFactory.java index f04207c92..9c7e4ed25 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/TemplateFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/TemplateFactory.java @@ -1,5 +1,6 @@ package cn.hutool.extra.template.engine; +import cn.hutool.core.lang.Singleton; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ServiceLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -14,6 +15,28 @@ import cn.hutool.log.StaticLog; * @author looly */ public class TemplateFactory { + + /** + * 根据用户引入的模板引擎jar,自动创建对应的模板引擎对象
    + * 获得的是单例的TemplateEngine + * + * @return 单例的TemplateEngine + */ + public static TemplateEngine get(){ + return Singleton.get(TemplateEngine.class.getName(), TemplateFactory::create); + } + + /** + * 根据用户引入的模板引擎jar,自动创建对应的模板引擎对象
    + * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@link TemplateEngine} + * @since 5.3.3 + */ + public static TemplateEngine create() { + return create(new TemplateConfig()); + } + /** * 根据用户引入的模板引擎jar,自动创建对应的模板引擎对象
    * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java index 050da1b66..eb94ef293 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java @@ -1,10 +1,8 @@ package cn.hutool.extra.template.engine.beetl; -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.Map; - +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.CharsetUtil; import org.beetl.core.Configuration; import org.beetl.core.GroupTemplate; import org.beetl.core.ResourceLoader; @@ -16,9 +14,10 @@ import org.beetl.core.resource.Matcher; import org.beetl.core.resource.StringTemplateResourceLoader; import org.beetl.core.resource.WebAppResourceLoader; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.util.CharsetUtil; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Map; /** * Beetl模板引擎工具类
    @@ -26,7 +25,9 @@ import cn.hutool.core.util.CharsetUtil; * 文档:http://ibeetl.com/guide/beetl.html * * @author Looly + * @deprecated 使用TemplateUtil替代 */ +@Deprecated public final class BeetlUtil { /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyEngine.java index 27dd9d019..4d62d01d2 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyEngine.java @@ -8,7 +8,6 @@ import cn.hutool.extra.template.TemplateConfig; import cn.hutool.extra.template.TemplateConfig.ResourceMode; import cn.hutool.extra.template.TemplateEngine; import com.jfinal.template.source.FileSourceFactory; -import org.beetl.core.GroupTemplate; /** * Enjoy库的引擎包装 @@ -80,7 +79,7 @@ public class EnjoyEngine implements TemplateEngine { * 创建引擎 * * @param config 模板配置 - * @return {@link GroupTemplate} + * @return {@link com.jfinal.template.Engine} */ private static com.jfinal.template.Engine createEngine(TemplateConfig config) { final com.jfinal.template.Engine engine = com.jfinal.template.Engine.create("Hutool-Enjoy-Engine-" + IdUtil.fastSimpleUUID()); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyTemplate.java index ab26ddf3d..db037f009 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyTemplate.java @@ -1,12 +1,12 @@ package cn.hutool.extra.template.engine.enjoy; +import cn.hutool.extra.template.AbstractTemplate; + import java.io.OutputStream; import java.io.Serializable; import java.io.Writer; import java.util.Map; -import cn.hutool.extra.template.AbstractTemplate; - /** * Engoy模板实现 * @@ -16,7 +16,7 @@ import cn.hutool.extra.template.AbstractTemplate; public class EnjoyTemplate extends AbstractTemplate implements Serializable { private static final long serialVersionUID = 1L; - private com.jfinal.template.Template rawTemplate; + private final com.jfinal.template.Template rawTemplate; /** * 包装Enjoy模板 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/SimpleStringTemplateLoader.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/SimpleStringTemplateLoader.java index f1b7f9df1..f25d7b658 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/SimpleStringTemplateLoader.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/SimpleStringTemplateLoader.java @@ -1,11 +1,10 @@ package cn.hutool.extra.template.engine.freemarker; -import java.io.IOException; +import freemarker.cache.TemplateLoader; + import java.io.Reader; import java.io.StringReader; -import freemarker.cache.TemplateLoader; - /** * {@link TemplateLoader} 字符串实现形式
    * 用于直接获取字符串模板 @@ -16,7 +15,7 @@ import freemarker.cache.TemplateLoader; public class SimpleStringTemplateLoader implements TemplateLoader { @Override - public Object findTemplateSource(String name) throws IOException { + public Object findTemplateSource(String name) { return name; } @@ -26,12 +25,12 @@ public class SimpleStringTemplateLoader implements TemplateLoader { } @Override - public Reader getReader(Object templateSource, String encoding) throws IOException { + public Reader getReader(Object templateSource, String encoding) { return new StringReader((String) templateSource); } @Override - public void closeTemplateSource(Object templateSource) throws IOException { + public void closeTemplateSource(Object templateSource) { // ignore } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java index bb7706000..23505dec5 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java @@ -1,15 +1,14 @@ package cn.hutool.extra.template.engine.rythm; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.TypeReference; +import cn.hutool.extra.template.AbstractTemplate; + import java.io.OutputStream; import java.io.Serializable; import java.io.Writer; import java.util.Map; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.lang.TypeReference; -import cn.hutool.extra.template.AbstractTemplate; -import cn.hutool.extra.template.engine.beetl.BeetlTemplate; - /** * Rythm模板包装 * @@ -19,13 +18,13 @@ import cn.hutool.extra.template.engine.beetl.BeetlTemplate; public class RythmTemplate extends AbstractTemplate implements Serializable { private static final long serialVersionUID = -132774960373894911L; - private org.rythmengine.template.ITemplate rawTemplate; + private final org.rythmengine.template.ITemplate rawTemplate; /** * 包装Rythm模板 * * @param template Rythm的模板对象 {@link org.rythmengine.template.ITemplate} - * @return {@link BeetlTemplate} + * @return {@link RythmTemplate} */ public static RythmTemplate wrap(org.rythmengine.template.ITemplate template) { return (null == template) ? null : new RythmTemplate(template); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java index a95b9e6b3..bb23ec78c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java @@ -1,21 +1,20 @@ package cn.hutool.extra.template.engine.thymeleaf; -import java.io.OutputStream; -import java.io.Serializable; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.Locale; -import java.util.Map; - -import org.thymeleaf.TemplateEngine; -import org.thymeleaf.context.Context; - import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.extra.template.AbstractTemplate; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +import java.io.OutputStream; +import java.io.Serializable; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Locale; +import java.util.Map; /** * Thymeleaf模板实现 @@ -26,9 +25,9 @@ import cn.hutool.extra.template.AbstractTemplate; public class ThymeleafTemplate extends AbstractTemplate implements Serializable { private static final long serialVersionUID = 781284916568562509L; - private TemplateEngine engine; - private String template; - private Charset charset; + private final TemplateEngine engine; + private final String template; + private final Charset charset; /** * 包装Thymeleaf模板 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java index 9b4d61f6d..5cee9c97c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java @@ -1,19 +1,18 @@ package cn.hutool.extra.template.engine.velocity; -import java.io.OutputStream; -import java.io.Serializable; -import java.io.Writer; -import java.util.Map; - -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; - import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.template.AbstractTemplate; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; + +import java.io.OutputStream; +import java.io.Serializable; +import java.io.Writer; +import java.util.Map; /** * Velocity模板包装 @@ -24,7 +23,7 @@ import cn.hutool.extra.template.AbstractTemplate; public class VelocityTemplate extends AbstractTemplate implements Serializable { private static final long serialVersionUID = -132774960373894911L; - private org.apache.velocity.Template rawTemplate; + private final org.apache.velocity.Template rawTemplate; private String charset; /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java index 02eaf43eb..6118a9670 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java @@ -1,5 +1,16 @@ package cn.hutool.extra.template.engine.velocity; +import cn.hutool.core.exceptions.NotInitedException; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.app.VelocityEngine; + import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; @@ -9,37 +20,30 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; -import org.apache.velocity.app.VelocityEngine; - -import cn.hutool.core.exceptions.NotInitedException; -import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.IdUtil; - /** * Velocity模板引擎工具类
    * 使用前必须初始化工具类 - * + * * @author xiaoleilu - * + * @deprecated 使用TemplateUtil替代 */ +@Deprecated public class VelocityUtil { - /** 是否初始化了默认引擎 */ + /** + * 是否初始化了默认引擎 + */ private static boolean isInited; - /** 全局上下文,当设定值时,对于每个模板都有效 */ - private static Map globalContext = new HashMap<>(); + /** + * 全局上下文,当设定值时,对于每个模板都有效 + */ + private static final Map globalContext = new HashMap<>(); /** * 设置Velocity全局上下文
    * 当设定值时,对于每个模板都有效 - * - * @param name 名 + * + * @param name 名 * @param value 值 */ public void putGlobalContext(String name, Object value) { @@ -48,9 +52,9 @@ public class VelocityUtil { /** * 初始化Velocity全局属性 - * + * * @param templateDir 模板所在目录,绝对路径 - * @param charset 编码 + * @param charset 编码 */ synchronized public static void init(String templateDir, String charset) { Velocity.init(_newInitedProp(templateDir, charset)); @@ -61,9 +65,9 @@ public class VelocityUtil { /** * 初始化全局属性 - * - * @param templateDir 模板目录 - * @param charset 字符集编码 + * + * @param templateDir 模板目录 + * @param charset 字符集编码 * @param initedGlobalContext 初始的全局上下文 */ public static void init(String templateDir, String charset, Map initedGlobalContext) { @@ -73,9 +77,9 @@ public class VelocityUtil { /** * 新建Velocity模板引擎 - * + * * @param templateDir 模板所在目录,绝对路径 - * @param charset 编码 + * @param charset 编码 * @return VelocityEngine */ public static VelocityEngine newEngine(String templateDir, String charset) { @@ -89,11 +93,11 @@ public class VelocityUtil { /** * 获得指定模板填充后的内容 - * - * @param templateDir 模板所在目录,绝对路径 + * + * @param templateDir 模板所在目录,绝对路径 * @param templateFileName 模板名称 - * @param context 上下文(变量值的容器) - * @param charset 字符集 + * @param context 上下文(变量值的容器) + * @param charset 字符集 * @return 模板和内容匹配后的内容 */ public static String getContent(String templateDir, String templateFileName, VelocityContext context, String charset) { @@ -105,10 +109,10 @@ public class VelocityUtil { /** * 获得指定模板填充后的内容 - * - * @param ve 模板引擎 + * + * @param ve 模板引擎 * @param templateFileName 模板名称 - * @param context 上下文(变量值的容器) + * @param context 上下文(变量值的容器) * @return 模板和内容匹配后的内容 */ public static String getContent(VelocityEngine ve, String templateFileName, VelocityContext context) { @@ -119,9 +123,9 @@ public class VelocityUtil { /** * 获得指定模板填充后的内容,使用默认引擎 - * + * * @param templateFileName 模板文件 - * @param context 上下文(变量值的容器) + * @param context 上下文(变量值的容器) * @return 模板和内容匹配后的内容 */ public static String getContent(String templateFileName, VelocityContext context) { @@ -132,11 +136,11 @@ public class VelocityUtil { /** * 生成文件 - * - * @param ve 模板引擎 + * + * @param ve 模板引擎 * @param templateFileName 模板文件名 - * @param context 上下文 - * @param destPath 目标路径(绝对) + * @param context 上下文 + * @param destPath 目标路径(绝对) */ public static void toFile(VelocityEngine ve, String templateFileName, VelocityContext context, String destPath) { toFile(ve.getTemplate(templateFileName), context, destPath); @@ -144,10 +148,10 @@ public class VelocityUtil { /** * 生成文件,使用默认引擎 - * + * * @param templateFileName 模板文件名 - * @param context 模板上下文 - * @param destPath 目标路径(绝对) + * @param context 模板上下文 + * @param destPath 目标路径(绝对) */ public static void toFile(String templateFileName, VelocityContext context, String destPath) { assertInit(); @@ -157,9 +161,9 @@ public class VelocityUtil { /** * 生成文件 - * + * * @param template 模板 - * @param context 模板上下文 + * @param context 模板上下文 * @param destPath 目标路径(绝对) */ public static void toFile(Template template, VelocityContext context, String destPath) { @@ -177,11 +181,11 @@ public class VelocityUtil { /** * 生成内容写入流
    * 会自动关闭Writer - * - * @param ve 引擎 + * + * @param ve 引擎 * @param templateFileName 模板文件名 - * @param context 上下文 - * @param writer 流 + * @param context 上下文 + * @param writer 流 */ public static void toWriter(VelocityEngine ve, String templateFileName, VelocityContext context, Writer writer) { final Template template = ve.getTemplate(templateFileName); @@ -191,10 +195,10 @@ public class VelocityUtil { /** * 生成内容写入流
    * 会自动关闭Writer - * + * * @param templateFileName 模板文件名 - * @param context 上下文 - * @param writer 流 + * @param context 上下文 + * @param writer 流 */ public static void toWriter(String templateFileName, VelocityContext context, Writer writer) { assertInit(); @@ -206,10 +210,10 @@ public class VelocityUtil { /** * 生成内容写到响应内容中
    * 模板的变量来自于Request的Attribute对象 - * + * * @param templateFileName 模板文件 - * @param request 请求对象,用于获取模板中的变量值 - * @param response 响应对象 + * @param request 请求对象,用于获取模板中的变量值 + * @param response 响应对象 */ public static void toWriter(String templateFileName, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) { final VelocityContext context = new VelocityContext(); @@ -229,9 +233,9 @@ public class VelocityUtil { /** * 融合模板和内容 - * + * * @param templateContent 模板的内容字符串 - * @param context 上下文 + * @param context 上下文 * @return 模板和内容匹配后的内容 */ public static String merge(String templateContent, VelocityContext context) { @@ -246,10 +250,10 @@ public class VelocityUtil { /** * 融合模板和内容并写入到Writer - * + * * @param template 模板 - * @param context 内容 - * @param writer Writer + * @param context 内容 + * @param writer Writer */ public static void merge(Template template, VelocityContext context, Writer writer) { if (template == null) { @@ -270,7 +274,7 @@ public class VelocityUtil { /** * 将Request中的数据转换为模板引擎
    * 取值包括Session和Request - * + * * @param context 内容 * @param request 请求对象 * @return VelocityContext @@ -289,7 +293,7 @@ public class VelocityUtil { /** * 将Session中的值放入模板上下文 - * + * * @param context 模板上下文 * @param session Session * @return VelocityContext @@ -309,9 +313,10 @@ public class VelocityUtil { } // -------------------------------------------------------------------------- Private method start + /** * 新建一个初始化后的属性对象 - * + * * @param templateDir 模板所在目录 * @return 初始化后的属性对象 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/AbstractResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/AbstractResult.java index cbcd242d7..5d01acfc9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/AbstractResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/AbstractResult.java @@ -49,7 +49,6 @@ public abstract class AbstractResult implements Result{ throw new UnsupportedOperationException("Jcseg result not allow to remove !"); } - @SuppressWarnings("NullableProblems") @Override public Iterator iterator() { return this; diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/TokenizerFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/TokenizerFactory.java index b8c800f12..9c2a059aa 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/TokenizerFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/TokenizerFactory.java @@ -1,5 +1,6 @@ package cn.hutool.extra.tokenizer.engine; +import cn.hutool.core.lang.Singleton; import cn.hutool.core.util.ServiceLoaderUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.tokenizer.TokenizerEngine; @@ -13,6 +14,18 @@ import cn.hutool.log.StaticLog; * */ public class TokenizerFactory { + + /** + * 根据用户引入的模板引擎jar,自动创建对应的分词引擎对象
    + * 获得的是单例的TokenizerEngine + * + * @return 单例的TokenizerEngine + * @since 5.3.3 + */ + public static TokenizerEngine get(){ + return Singleton.get(TokenizerEngine.class.getName(), TokenizerFactory::create); + } + /** * 根据用户引入的分词引擎jar,自动创建对应的分词引擎对象 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java index 1616705d9..523b01ce6 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java @@ -16,7 +16,7 @@ import cn.hutool.extra.tokenizer.Result; */ public class HanLPEngine implements TokenizerEngine { - private Segment seg; + private final Segment seg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPWord.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPWord.java index b7060bda0..cf4408d1a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPWord.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPWord.java @@ -13,7 +13,7 @@ import cn.hutool.extra.tokenizer.Word; public class HanLPWord implements Word { private static final long serialVersionUID = 1L; - private Term term; + private final Term term; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java index 7fba710f8..ff0429e95 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java @@ -15,7 +15,7 @@ import cn.hutool.extra.tokenizer.Result; */ public class IKAnalyzerEngine implements TokenizerEngine { - private IKSegmenter seg; + private final IKSegmenter seg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerResult.java index 3c102233a..2c3a2edbf 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerResult.java @@ -1,13 +1,12 @@ package cn.hutool.extra.tokenizer.engine.ikanalyzer; -import java.io.IOException; - -import org.wltea.analyzer.core.IKSegmenter; -import org.wltea.analyzer.core.Lexeme; - import cn.hutool.extra.tokenizer.AbstractResult; import cn.hutool.extra.tokenizer.TokenizerException; import cn.hutool.extra.tokenizer.Word; +import org.wltea.analyzer.core.IKSegmenter; +import org.wltea.analyzer.core.Lexeme; + +import java.io.IOException; /** * IKAnalyzer分词结果实现
    @@ -18,7 +17,7 @@ import cn.hutool.extra.tokenizer.Word; */ public class IKAnalyzerResult extends AbstractResult { - private IKSegmenter seg; + private final IKSegmenter seg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerWord.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerWord.java index ab062f93c..e63fee809 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerWord.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerWord.java @@ -13,7 +13,7 @@ import cn.hutool.extra.tokenizer.Word; public class IKAnalyzerWord implements Word { private static final long serialVersionUID = 1L; - private Lexeme word; + private final Lexeme word; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegEngine.java index 50c1b1219..98981dd1c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegEngine.java @@ -1,17 +1,16 @@ package cn.hutool.extra.tokenizer.engine.jcseg; -import java.io.IOException; -import java.io.StringReader; - +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.tokenizer.Result; +import cn.hutool.extra.tokenizer.TokenizerEngine; +import cn.hutool.extra.tokenizer.TokenizerException; import org.lionsoul.jcseg.ISegment; import org.lionsoul.jcseg.dic.ADictionary; import org.lionsoul.jcseg.dic.DictionaryFactory; import org.lionsoul.jcseg.segmenter.SegmenterConfig; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.tokenizer.TokenizerEngine; -import cn.hutool.extra.tokenizer.Result; -import cn.hutool.extra.tokenizer.TokenizerException; +import java.io.IOException; +import java.io.StringReader; /** * Jcseg分词引擎实现
    @@ -22,7 +21,7 @@ import cn.hutool.extra.tokenizer.TokenizerException; */ public class JcsegEngine implements TokenizerEngine { - private ISegment segment; + private final ISegment segment; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java index 7fbf20dbb..a0fcd86a9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java @@ -19,7 +19,7 @@ import java.util.NoSuchElementException; */ public class JcsegResult implements Result{ - private ISegment result; + private final ISegment result; private Word cachedWord; /** @@ -63,7 +63,6 @@ public class JcsegResult implements Result{ throw new UnsupportedOperationException("Jcseg result not allow to remove !"); } - @SuppressWarnings("NullableProblems") @Override public Iterator iterator() { return this; diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jieba/JiebaEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jieba/JiebaEngine.java index 6c7897194..59afb6fed 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jieba/JiebaEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jieba/JiebaEngine.java @@ -16,8 +16,8 @@ import cn.hutool.extra.tokenizer.Result; */ public class JiebaEngine implements TokenizerEngine { - private JiebaSegmenter jiebaSegmenter; - private SegMode mode; + private final JiebaSegmenter jiebaSegmenter; + private final SegMode mode; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegEngine.java index 8f1bcd1a1..ba9736013 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegEngine.java @@ -1,14 +1,13 @@ package cn.hutool.extra.tokenizer.engine.mmseg; -import java.io.StringReader; - +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.tokenizer.Result; +import cn.hutool.extra.tokenizer.TokenizerEngine; import com.chenlb.mmseg4j.ComplexSeg; import com.chenlb.mmseg4j.Dictionary; import com.chenlb.mmseg4j.MMSeg; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.tokenizer.TokenizerEngine; -import cn.hutool.extra.tokenizer.Result; +import java.io.StringReader; /** * mmseg4j分词引擎实现
    @@ -19,7 +18,7 @@ import cn.hutool.extra.tokenizer.Result; */ public class MmsegEngine implements TokenizerEngine { - private MMSeg mmSeg; + private final MMSeg mmSeg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegResult.java index 6192ce4c8..d4e67aa3c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegResult.java @@ -1,12 +1,11 @@ package cn.hutool.extra.tokenizer.engine.mmseg; -import java.io.IOException; - -import com.chenlb.mmseg4j.MMSeg; - import cn.hutool.extra.tokenizer.AbstractResult; import cn.hutool.extra.tokenizer.TokenizerException; import cn.hutool.extra.tokenizer.Word; +import com.chenlb.mmseg4j.MMSeg; + +import java.io.IOException; /** * mmseg4j分词结果实现
    @@ -17,7 +16,7 @@ import cn.hutool.extra.tokenizer.Word; */ public class MmsegResult extends AbstractResult { - private MMSeg mmSeg; + private final MMSeg mmSeg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpEngine.java index 640a7defc..da1ef98db 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpEngine.java @@ -17,7 +17,7 @@ import cn.hutool.extra.tokenizer.TokenizerEngine; */ public class MynlpEngine implements TokenizerEngine { - private Lexer lexer; + private final Lexer lexer; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpResult.java index 5fc0f73ea..bbef6ea36 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpResult.java @@ -1,12 +1,11 @@ package cn.hutool.extra.tokenizer.engine.mynlp; -import java.util.Iterator; - -import com.mayabot.nlp.segment.Sentence; -import com.mayabot.nlp.segment.WordTerm; - import cn.hutool.extra.tokenizer.Result; import cn.hutool.extra.tokenizer.Word; +import com.mayabot.nlp.segment.Sentence; +import com.mayabot.nlp.segment.WordTerm; + +import java.util.Iterator; /** * MYNLP 中文NLP工具包分词结果实现
    @@ -17,7 +16,7 @@ import cn.hutool.extra.tokenizer.Word; */ public class MynlpResult implements Result { - private Iterator result; + private final Iterator result; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordEngine.java index 8a1823c01..dcb954fdb 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordEngine.java @@ -17,7 +17,7 @@ import cn.hutool.extra.tokenizer.TokenizerEngine; */ public class WordEngine implements TokenizerEngine { - private Segmentation segmentation; + private final Segmentation segmentation; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordResult.java index 49f680ea0..032a4a112 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordResult.java @@ -1,11 +1,11 @@ package cn.hutool.extra.tokenizer.engine.word; -import java.util.Iterator; -import java.util.List; - import cn.hutool.extra.tokenizer.Result; import cn.hutool.extra.tokenizer.Word; +import java.util.Iterator; +import java.util.List; + /** * Word分词结果实现
    * 项目地址:https://github.com/ysc/word @@ -15,7 +15,7 @@ import cn.hutool.extra.tokenizer.Word; */ public class WordResult implements Result{ - private Iterator wordIter; + private final Iterator wordIter; /** * 构造 diff --git a/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.pinyin.PinyinEngine b/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.pinyin.PinyinEngine new file mode 100644 index 000000000..5526a0a33 --- /dev/null +++ b/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.pinyin.PinyinEngine @@ -0,0 +1,3 @@ +cn.hutool.extra.pinyin.engine.tinypinyin.TinyPinyinEngine +cn.hutool.extra.pinyin.engine.jpinyin.JPinyinEngine +cn.hutool.extra.pinyin.engine.pinyin4j.Pinyin4jEngine \ No newline at end of file diff --git a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java index ede110773..34a1e19d3 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java @@ -1,13 +1,13 @@ package cn.hutool.extra.ftp; -import java.util.List; - -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Console; +import cn.hutool.extra.ssh.Sftp; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; public class FtpTest { @@ -59,4 +59,25 @@ public class FtpTest { IoUtil.close(ftp); } + + @Test + @Ignore + public void recursiveDownloadFolder() { + Ftp ftp = new Ftp("looly.centos"); + ftp.recursiveDownloadFolder("/",FileUtil.file("d:/test/download")); + + IoUtil.close(ftp); + } + + @Test + @Ignore + public void recursiveDownloadFolderSftp() { + Sftp ftp = new Sftp("127.0.0.1", 22, "test", "test"); + + ftp.cd("/file/aaa"); + Console.log(ftp.pwd()); + ftp.recursiveDownloadFolder("/",FileUtil.file("d:/test/download")); + + IoUtil.close(ftp); + } } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java new file mode 100644 index 000000000..ef2164245 --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java @@ -0,0 +1,25 @@ +package cn.hutool.extra.pinyin; + +import org.junit.Assert; +import org.junit.Test; + +public class PinyinUtilTest { + + @Test + public void getPinyinTest(){ + final String pinyin = PinyinUtil.getPinyin("你好", " "); + Assert.assertEquals("ni hao", pinyin); + } + + @Test + public void getPinyinUpperCaseTest(){ + final String pinyin = PinyinUtil.getPinyin("你好怡", " "); + Assert.assertEquals("ni hao yi", pinyin); + } + + @Test + public void getFirstLetterTest(){ + final String result = PinyinUtil.getFirstLetter("H是第一个", ", "); + Assert.assertEquals("h, s, d, y, g", result); + } +} diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java index cc32c3d8c..f8600f8ff 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java @@ -1,14 +1,14 @@ package cn.hutool.extra.qrcode; -import java.awt.Color; - +import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Console; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.junit.Ignore; import org.junit.Test; -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Console; +import java.awt.Color; +import java.io.File; /** * 二维码工具类单元测试 @@ -51,4 +51,19 @@ public class QrCodeUtilTest { String decode = QrCodeUtil.decode(FileUtil.file("e:/pic/qr.png")); Console.log(decode); } + + @Test + @Ignore + public void generateAsBase64Test(){ + String base64 = QrCodeUtil.generateAsBase64("http://hutool.cn/", new QrConfig(400, 400), "png"); + System.out.println(base64); + + byte[] bytes = FileUtil.readBytes( + new File("d:/test/qr.png")); + String encode = Base64.encode(bytes); + String base641 = QrCodeUtil.generateAsBase64("http://hutool.cn/", new QrConfig(400, 400), "png", encode); + System.out.println(base641); + + } + } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/template/BeetlUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/template/BeetlUtilTest.java index 342906c0c..c7d27f621 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/template/BeetlUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/template/BeetlUtilTest.java @@ -1,7 +1,7 @@ package cn.hutool.extra.template; -import java.io.IOException; - +import cn.hutool.core.lang.Dict; +import cn.hutool.extra.template.engine.beetl.BeetlUtil; import org.beetl.core.Configuration; import org.beetl.core.GroupTemplate; import org.beetl.core.Template; @@ -9,8 +9,7 @@ import org.beetl.core.resource.StringTemplateResourceLoader; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.lang.Dict; -import cn.hutool.extra.template.engine.beetl.BeetlUtil; +import java.io.IOException; /** * BeetlUtil单元测试 @@ -18,6 +17,7 @@ import cn.hutool.extra.template.engine.beetl.BeetlUtil; * @author looly * */ +@SuppressWarnings("deprecation") public class BeetlUtilTest { @Test diff --git a/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java index 3b061cab5..195d2eec4 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java @@ -1,12 +1,6 @@ package cn.hutool.extra.tokenizer; -import java.util.Iterator; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.IterUtil; import cn.hutool.extra.tokenizer.engine.analysis.SmartcnEngine; import cn.hutool.extra.tokenizer.engine.hanlp.HanLPEngine; import cn.hutool.extra.tokenizer.engine.ikanalyzer.IKAnalyzerEngine; @@ -15,6 +9,11 @@ import cn.hutool.extra.tokenizer.engine.jieba.JiebaEngine; import cn.hutool.extra.tokenizer.engine.mmseg.MmsegEngine; import cn.hutool.extra.tokenizer.engine.mynlp.MynlpEngine; import cn.hutool.extra.tokenizer.engine.word.WordEngine; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.Iterator; /** * 模板引擎单元测试 @@ -38,7 +37,7 @@ public class TokenizerUtilTest { public void hanlpTest() { TokenizerEngine engine = new HanLPEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两 个 方法 的 区别 在于 返回 值", resultStr); } @@ -46,7 +45,7 @@ public class TokenizerUtilTest { public void ikAnalyzerTest() { TokenizerEngine engine = new IKAnalyzerEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这两个 方法 的 区别 在于 返回值", resultStr); } @@ -61,7 +60,7 @@ public class TokenizerUtilTest { public void jiebaTest() { TokenizerEngine engine = new JiebaEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两个 方法 的 区别 在于 返回值", resultStr); } @@ -76,7 +75,7 @@ public class TokenizerUtilTest { public void smartcnTest() { TokenizerEngine engine = new SmartcnEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两 个 方法 的 区别 在于 返回 值", resultStr); } @@ -84,7 +83,7 @@ public class TokenizerUtilTest { public void wordTest() { TokenizerEngine engine = new WordEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这两个 方法 的 区别 在于 返回值", resultStr); } @@ -94,12 +93,12 @@ public class TokenizerUtilTest { // 此单元测试需要JDK8,默认忽略 TokenizerEngine engine = new MynlpEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两个 方法 的 区别 在于 返回 值", resultStr); } private void checkResult(Result result) { - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两个 方法 的 区别 在于 返回 值", resultStr); } } diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 7ea30fb2b..592d1e0ab 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java index 759cd7d92..b60259b71 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java +++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java @@ -28,20 +28,42 @@ public enum ContentType { * Rest请求XML编码 */ XML("application/xml"), + /** + * text/plain编码 + */ + TEXT_PLAIN("text/plain"), /** * Rest请求text/xml编码 */ - TEXT_XML("text/xml"); + TEXT_XML("text/xml"), + /** + * text/html编码 + */ + TEXT_HTML("text/html"); - private String value; + private final String value; + /** + * 构造 + * @param value ContentType值 + */ ContentType(String value) { this.value = value; } + /** + * 获取value值 + * + * @return value值 + * @since 5.2.6 + */ + public String getValue() { + return value; + } + @Override public String toString() { - return value; + return getValue(); } /** @@ -62,7 +84,7 @@ public enum ContentType { * @since 4.1.5 */ public static boolean isDefault(String contentType) { - return null == contentType || isFormUrlEncoed(contentType); + return null == contentType || isFormUrlEncode(contentType); } /** @@ -71,7 +93,7 @@ public enum ContentType { * @param contentType 内容类型 * @return 是否为application/x-www-form-urlencoded */ - public static boolean isFormUrlEncoed(String contentType) { + public static boolean isFormUrlEncode(String contentType) { return StrUtil.startWithIgnoreCase(contentType, FORM_URLENCODED.toString()); } diff --git a/hutool-http/src/main/java/cn/hutool/http/Header.java b/hutool-http/src/main/java/cn/hutool/http/Header.java index 3ff1ef0f3..1c90606c5 100644 --- a/hutool-http/src/main/java/cn/hutool/http/Header.java +++ b/hutool-http/src/main/java/cn/hutool/http/Header.java @@ -121,14 +121,23 @@ public enum Header { */ LOCATION("Location"); - private String value; + private final String value; Header(String value) { this.value = value; } + /** + * 获取值 + * + * @return 值 + */ + public String getValue(){ + return this.value; + } + @Override public String toString() { - return value; + return getValue(); } } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index 128690fca..7e9aa63ca 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -1,5 +1,11 @@ package cn.hutool.http; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; + import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; @@ -8,11 +14,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.map.CaseInsensitiveMap; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; - /** * http基类 * @author Looly @@ -270,7 +271,7 @@ public abstract class HttpBase { */ public T charset(String charset) { if(StrUtil.isNotBlank(charset)){ - this.charset = Charset.forName(charset); + charset(Charset.forName(charset)); } return (T) this; } @@ -293,7 +294,9 @@ public abstract class HttpBase { StringBuilder sb = StrUtil.builder(); sb.append("Request Headers: ").append(StrUtil.CRLF); for (Entry> entry : this.headers.entrySet()) { - sb.append(" ").append(entry).append(StrUtil.CRLF); + sb.append(" ").append( + entry.getKey()).append(": ").append(CollUtil.join(entry.getValue(), ",")) + .append(StrUtil.CRLF); } sb.append("Request Body: ").append(StrUtil.CRLF); diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpConnection.java b/hutool-http/src/main/java/cn/hutool/http/HttpConnection.java index bd73c99b2..183bbee58 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpConnection.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpConnection.java @@ -33,8 +33,8 @@ import java.util.Map.Entry; */ public class HttpConnection { - private URL url; - private Proxy proxy; + private final URL url; + private final Proxy proxy; private HttpURLConnection conn; /** diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java b/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java index 09ca2fe44..9a51a9a62 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java @@ -1,5 +1,7 @@ package cn.hutool.http; +import cn.hutool.core.util.StrUtil; + import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -8,8 +10,6 @@ import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; -import cn.hutool.core.util.StrUtil; - /** * HTTP输入流,此流用于包装Http请求响应内容的流,用于解析各种压缩、分段的响应流内容 * @@ -79,11 +79,10 @@ public class HttpInputStream extends InputStream { try { this.in = (response.status < HttpStatus.HTTP_BAD_REQUEST) ? response.httpConnection.getInputStream() : response.httpConnection.getErrorStream(); } catch (IOException e) { - if (e instanceof FileNotFoundException) { - // 服务器无返回内容,忽略之 - } else { + if (false == (e instanceof FileNotFoundException)) { throw new HttpException(e); } + // 服务器无返回内容,忽略之 } // 在一些情况下,返回的流为null,此时提供状态码说明 diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index d8ffbdbfd..2637a30c5 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -1,22 +1,22 @@ package cn.hutool.http; import cn.hutool.core.codec.Base64; -import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.BytesResource; import cn.hutool.core.io.resource.FileResource; import cn.hutool.core.io.resource.MultiFileResource; -import cn.hutool.core.io.resource.MultiResource; import cn.hutool.core.io.resource.Resource; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; +import cn.hutool.http.body.MultipartBody; import cn.hutool.http.cookie.GlobalCookieManager; import cn.hutool.http.ssl.SSLSocketFactoryBuilder; @@ -24,18 +24,15 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSocketFactory; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.net.CookieManager; import java.net.HttpCookie; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URLStreamHandler; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; /** * http请求类
    @@ -45,12 +42,7 @@ import java.util.Map.Entry; */ public class HttpRequest extends HttpBase { - private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); - private static final byte[] BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY).getBytes(); - private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n"; - private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; - - private static final String CONTENT_TYPE_MULTIPART_PREFIX = "multipart/form-data; boundary="; + private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; /** @@ -96,7 +88,7 @@ public class HttpRequest extends HttpBase { GlobalCookieManager.setCookieManager(null); } - private String url; + private UrlBuilder url; private URLStreamHandler urlHandler; private Method method = Method.GET; /** @@ -112,9 +104,9 @@ public class HttpRequest extends HttpBase { */ private Map form; /** - * 文件表单对象,用于文件上传 + * 是否为Multipart表单 */ - private Map fileForm; + private boolean isMultiPart; /** * Cookie */ @@ -128,10 +120,6 @@ public class HttpRequest extends HttpBase { * 是否禁用缓存 */ private boolean isDisableCache; - /** - * 是否对url中的参数进行编码 - */ - private boolean encodeUrlParams; /** * 是否是REST请求模式 */ @@ -163,13 +151,21 @@ public class HttpRequest extends HttpBase { private SSLSocketFactory ssf; /** - * 构造 + * 构造,URL编码默认使用UTF-8 * * @param url URL */ public HttpRequest(String url) { - Assert.notBlank(url, "Param [url] can not be blank !"); - this.url = URLUtil.normalize(url, true); + this(UrlBuilder.ofHttp(url, CharsetUtil.CHARSET_UTF_8)); + } + + /** + * 构造 + * + * @param url {@link UrlBuilder} + */ + public HttpRequest(UrlBuilder url) { + this.url = url; // 给定一个默认头信息 this.header(GlobalHeaders.INSTANCE.headers); } @@ -265,7 +261,7 @@ public class HttpRequest extends HttpBase { * @since 4.1.8 */ public String getUrl() { - return url; + return url.toString(); } /** @@ -276,7 +272,19 @@ public class HttpRequest extends HttpBase { * @since 4.1.8 */ public HttpRequest setUrl(String url) { - this.url = url; + this.url = UrlBuilder.ofHttp(url, this.charset); + return this; + } + + /** + * 设置URL + * + * @param urlBuilder url字符串 + * @return this + * @since 5.3.1 + */ + public HttpRequest setUrl(UrlBuilder urlBuilder) { + this.url = urlBuilder; return this; } @@ -475,17 +483,17 @@ public class HttpRequest extends HttpBase { if (value instanceof File) { // 文件上传 return this.form(name, (File) value); - } else if (value instanceof Resource) { - // 自定义流上传 - return this.form(name, (Resource) value); - } else if (this.form == null) { - this.form = new LinkedHashMap<>(); } + if(value instanceof Resource){ + return form(name, (Resource)value); + } + + // 普通值 String strValue; if (value instanceof List) { // 列表对象 - strValue = CollectionUtil.join((List) value, ","); + strValue = CollUtil.join((List) value, ","); } else if (ArrayUtil.isArray(value)) { if (File.class == ArrayUtil.getComponentType(value)) { // 多文件 @@ -498,8 +506,7 @@ public class HttpRequest extends HttpBase { strValue = Convert.toStr(value, null); } - form.put(name, strValue); - return this; + return putToForm(name, strValue); } /** @@ -514,8 +521,7 @@ public class HttpRequest extends HttpBase { form(name, value); for (int i = 0; i < parameters.length; i += 2) { - name = parameters[i].toString(); - form(name, parameters[i + 1]); + form(parameters[i].toString(), parameters[i + 1]); } return this; } @@ -528,9 +534,7 @@ public class HttpRequest extends HttpBase { */ public HttpRequest form(Map formMap) { if (MapUtil.isNotEmpty(formMap)) { - for (Map.Entry entry : formMap.entrySet()) { - form(entry.getKey(), entry.getValue()); - } + formMap.forEach(this::form); } return this; } @@ -540,10 +544,13 @@ public class HttpRequest extends HttpBase { * 一旦有文件加入,表单变为multipart/form-data * * @param name 名 - * @param files 需要上传的文件 + * @param files 需要上传的文件,为空跳过 * @return this */ public HttpRequest form(String name, File... files) { + if(ArrayUtil.isEmpty(files)){ + return this; + } if (1 == files.length) { final File file = files[0]; return form(name, file, file.getName()); @@ -611,11 +618,8 @@ public class HttpRequest extends HttpBase { keepAlive(true); } - if (null == this.fileForm) { - fileForm = new HashMap<>(); - } - // 文件对象 - this.fileForm.put(name, resource); + this.isMultiPart = true; + return putToForm(name, resource); } return this; } @@ -636,7 +640,13 @@ public class HttpRequest extends HttpBase { * @since 3.3.0 */ public Map fileForm() { - return this.fileForm; + final Map result = MapUtil.newHashMap(); + this.form.forEach((key, value)->{ + if(value instanceof Resource){ + result.put(key, (Resource)value); + } + }); + return result; } // ---------------------------------------------------------------- Form end @@ -774,9 +784,10 @@ public class HttpRequest extends HttpBase { * @param isEncodeUrlParams 是否对URL中的参数进行编码 * @return this * @since 4.4.1 + * @deprecated 编码自动完成,无需设置 */ + @Deprecated public HttpRequest setEncodeUrlParams(boolean isEncodeUrlParams) { - this.encodeUrlParams = isEncodeUrlParams; return this; } @@ -860,7 +871,7 @@ public class HttpRequest extends HttpBase { public HttpRequest setSSLProtocol(String protocol) { Assert.notBlank(protocol, "protocol must be not blank!"); try { - this.ssf = SSLSocketFactoryBuilder.create().setProtocol(protocol).build(); + setSSLSocketFactory(SSLSocketFactoryBuilder.create().setProtocol(protocol).build()); } catch (Exception e) { throw new HttpException(e); } @@ -925,14 +936,8 @@ public class HttpRequest extends HttpBase { public HttpResponse execute(boolean isAsync) { // 初始化URL urlWithParamIfGet(); - // 编码URL - if (this.encodeUrlParams) { - this.url = HttpUtil.encodeParams(this.url, this.charset); - } - // 初始化 connection initConnection(); - // 发送请求 send(); @@ -971,6 +976,15 @@ public class HttpRequest extends HttpBase { header(Header.AUTHORIZATION, content, true); return this; } + + @Override + public String toString() { + StringBuilder sb = StrUtil.builder(); + sb.append("Request Url: ").append(this.url).append(StrUtil.CRLF); + sb.append(super.toString()); + return sb.toString(); + } + // ---------------------------------------------------------------- Private method start /** @@ -982,7 +996,8 @@ public class HttpRequest extends HttpBase { this.httpConnection.disconnectQuietly(); } - this.httpConnection = HttpConnection.create(URLUtil.toUrlForHttp(this.url, this.urlHandler), this.proxy)// + this.httpConnection = HttpConnection + .create(this.url.toURL(this.urlHandler), this.proxy)// .setMethod(this.method)// .setHttpsInfo(this.hostnameVerifier, this.ssf)// .setConnectTimeout(this.connectionTimeout)// @@ -1016,9 +1031,9 @@ public class HttpRequest extends HttpBase { if (Method.GET.equals(method) && false == this.isRest) { // 优先使用body形式的参数,不存在使用form if (ArrayUtil.isNotEmpty(this.bodyBytes)) { - this.url = HttpUtil.urlWithForm(this.url, StrUtil.str(this.bodyBytes, this.charset), this.charset, false); + this.url.getQuery().parse(StrUtil.str(this.bodyBytes, this.charset), this.charset); } else { - this.url = HttpUtil.urlWithForm(this.url, this.form, this.charset, false); + this.url.getQuery().addAll(this.form); } } } @@ -1047,7 +1062,7 @@ public class HttpRequest extends HttpBase { if (responseCode != HttpURLConnection.HTTP_OK) { if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_SEE_OTHER) { - this.url = httpConnection.header(Header.LOCATION); + setUrl(httpConnection.header(Header.LOCATION)); if (redirectCount < this.maxRedirectCount) { redirectCount++; return execute(); @@ -1069,10 +1084,10 @@ public class HttpRequest extends HttpBase { || Method.PUT.equals(this.method) // || Method.DELETE.equals(this.method) // || this.isRest) { - if (CollectionUtil.isEmpty(this.fileForm)) { - sendFormUrlEncoded();// 普通表单 - } else { + if (isMultipart()) { sendMultipart(); // 文件上传表单 + } else { + sendFormUrlEncoded();// 普通表单 } } else { this.httpConnection.connect(); @@ -1097,12 +1112,23 @@ public class HttpRequest extends HttpBase { } // Write的时候会优先使用body中的内容,write时自动关闭OutputStream - if (ArrayUtil.isNotEmpty(this.bodyBytes)) { - IoUtil.write(this.httpConnection.getOutputStream(), true, this.bodyBytes); - } else { - final String content = HttpUtil.toParams(this.form, this.charset); - IoUtil.write(this.httpConnection.getOutputStream(), this.charset, true, content); + byte[] content; + if(ArrayUtil.isNotEmpty(this.bodyBytes)){ + content = this.bodyBytes; + } else{ + content = StrUtil.bytes(getFormUrlEncoded(), this.charset); } + IoUtil.write(this.httpConnection.getOutputStream(), true, content); + } + + /** + * 获取编码后的表单数据,无表单数据返回"" + * + * @return 编码后的表单数据,无表单数据返回"" + * @since 5.3.2 + */ + private String getFormUrlEncoded() { + return HttpUtil.toParams(this.form, this.charset); } /** @@ -1115,94 +1141,15 @@ public class HttpRequest extends HttpBase { setMultipart();// 设置表单类型为Multipart try (OutputStream out = this.httpConnection.getOutputStream()) { - writeFileForm(out); - writeForm(out); - formEnd(out); + MultipartBody.create(this.form, this.charset).write(out); } } - // 普通字符串数据 - - /** - * 发送普通表单内容 - * - * @param out 输出流 - */ - private void writeForm(OutputStream out) { - if (CollectionUtil.isNotEmpty(this.form)) { - StringBuilder builder = StrUtil.builder(); - for (Entry entry : this.form.entrySet()) { - builder.append("--").append(BOUNDARY).append(StrUtil.CRLF); - builder.append(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, entry.getKey())); - builder.append(entry.getValue()).append(StrUtil.CRLF); - } - IoUtil.write(out, this.charset, false, builder); - } - } - - /** - * 发送文件对象表单 - * - * @param out 输出流 - */ - private void writeFileForm(OutputStream out) { - for (Entry entry : this.fileForm.entrySet()) { - appendPart(entry.getKey(), entry.getValue(), out); - } - } - - /** - * 添加Multipart表单的数据项 - * - * @param formFieldName 表单名 - * @param resource 资源,可以是文件等 - * @param out Http流 - * @since 4.1.0 - */ - private void appendPart(String formFieldName, Resource resource, OutputStream out) { - if (resource instanceof MultiResource) { - // 多资源 - for (Resource subResource : (MultiResource) resource) { - appendPart(formFieldName, subResource, out); - } - } else { - // 普通资源 - final StringBuilder builder = StrUtil.builder().append("--").append(BOUNDARY).append(StrUtil.CRLF); - final String fileName = resource.getName(); - builder.append(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName))); - // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 - builder.append(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream"))); - IoUtil.write(out, this.charset, false, builder); - InputStream in = null; - try { - in = resource.getStream(); - IoUtil.copy(in, out); - } finally { - IoUtil.close(in); - } - IoUtil.write(out, this.charset, false, StrUtil.CRLF); - } - - } - - // 添加结尾数据 - - /** - * 上传表单结束 - * - * @param out 输出流 - * @throws IOException IO异常 - */ - private void formEnd(OutputStream out) throws IOException { - out.write(BOUNDARY_END); - out.flush(); - } - /** * 设置表单类型为Multipart(文件上传) */ private void setMultipart() { - this.httpConnection.header(Header.CONTENT_TYPE, CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY, true); + this.httpConnection.header(Header.CONTENT_TYPE, MultipartBody.getContentType(), true); } /** @@ -1218,6 +1165,44 @@ public class HttpRequest extends HttpBase { || Method.OPTIONS == this.method // || Method.TRACE == this.method; } + + /** + * 判断是否为multipart/form-data表单,条件如下: + * + *
    +	 *     1. 存在资源对象(fileForm非空)
    +	 *     2. 用户自定义头为multipart/form-data开头
    +	 * 
    + * @return 是否为multipart/form-data表单 + * @since 5.3.5 + */ + private boolean isMultipart(){ + if(this.isMultiPart){ + return true; + } + + final String contentType = header(Header.CONTENT_TYPE); + return StrUtil.isNotEmpty(contentType) && + contentType.startsWith(ContentType.MULTIPART.getValue()); + } + + /** + * 将参数加入到form中,如果form为空,新建之。 + * + * @param name 表单属性名 + * @param value 属性值 + * @return this + */ + private HttpRequest putToForm(String name, Object value){ + if(null == name || null == value){ + return this; + } + if(null == this.form){ + this.form = new LinkedHashMap<>(); + } + this.form.put(name, value); + return this; + } // ---------------------------------------------------------------- Private method end } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java index f309b8669..8d5aa6bfa 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java @@ -43,7 +43,7 @@ public class HttpResponse extends HttpBase implements Closeable { /** 响应状态码 */ protected int status; /** 是否忽略读取Http响应体 */ - private boolean ignoreBody; + private final boolean ignoreBody; /** 从响应中获取的编码 */ private Charset charsetFromResponse; @@ -471,11 +471,11 @@ public class HttpResponse extends HttpBase implements Closeable { */ private String getFileNameFromDisposition() { String fileName = null; - final String desposition = header(Header.CONTENT_DISPOSITION); - if (StrUtil.isNotBlank(desposition)) { - fileName = ReUtil.get("filename=\"(.*?)\"", desposition, 1); + final String disposition = header(Header.CONTENT_DISPOSITION); + if (StrUtil.isNotBlank(disposition)) { + fileName = ReUtil.get("filename=\"(.*?)\"", disposition, 1); if (StrUtil.isBlank(fileName)) { - fileName = StrUtil.subAfter(desposition, "filename=", true); + fileName = StrUtil.subAfter(disposition, "filename=", true); } } return fileName; diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index 820e6e653..0ad059d8b 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -1,19 +1,19 @@ package cn.hutool.http; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FastByteArrayOutputStream; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.StreamProgress; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; +import cn.hutool.http.server.SimpleServer; import java.io.File; import java.io.InputStream; @@ -21,13 +21,9 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.nio.charset.Charset; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.regex.Pattern; /** @@ -357,6 +353,7 @@ public class HttpUtil { } /** + * 下载远程文件数据 * * @param url 请求的url @@ -377,7 +374,7 @@ public class HttpUtil { } /** - * 将Map形式的Form表单数据转换为Url参数形式,不做编码 + * 将Map形式的Form表单数据转换为Url参数形式,会自动url编码键和值 * * @param paramMap 表单数据 * @return url参数 @@ -408,44 +405,11 @@ public class HttpUtil { * * * @param paramMap 表单数据 - * @param charset 编码 + * @param charset 编码,null表示不encode键值对 * @return url参数 */ public static String toParams(Map paramMap, Charset charset) { - if (CollectionUtil.isEmpty(paramMap)) { - return StrUtil.EMPTY; - } - if (null == charset) {// 默认编码为系统编码 - charset = CharsetUtil.CHARSET_UTF_8; - } - - final StringBuilder sb = new StringBuilder(); - boolean isFirst = true; - String key; - Object value; - String valueStr; - for (Entry item : paramMap.entrySet()) { - if (isFirst) { - isFirst = false; - } else { - sb.append("&"); - } - key = item.getKey(); - value = item.getValue(); - if (value instanceof Iterable) { - value = CollUtil.join((Iterable) value, ","); - } else if (value instanceof Iterator) { - value = CollUtil.join((Iterator) value, ","); - } - valueStr = Convert.toStr(value); - if (StrUtil.isNotEmpty(key)) { - sb.append(URLUtil.encodeAll(key, charset)).append("="); - if (StrUtil.isNotEmpty(valueStr)) { - sb.append(URLUtil.encodeAll(valueStr, charset)); - } - } - } - return sb.toString(); + return URLUtil.buildQuery(paramMap, charset); } /** @@ -454,30 +418,33 @@ public class HttpUtil { * *

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

    * - * @param paramsStr url参数,可以包含url本身 - * @param charset 编码 + * @param urlWithParams url和参数,可以包含url本身,也可以单独参数 + * @param charset 编码 * @return 编码后的url和参数 * @since 4.0.1 */ - public static String encodeParams(String paramsStr, Charset charset) { - if (StrUtil.isBlank(paramsStr)) { + public static String encodeParams(String urlWithParams, Charset charset) { + if (StrUtil.isBlank(urlWithParams)) { return StrUtil.EMPTY; } String urlPart = null; // url部分,不包括问号 String paramPart; // 参数部分 - int pathEndPos = paramsStr.indexOf('?'); + final int pathEndPos = urlWithParams.indexOf('?'); if (pathEndPos > -1) { // url + 参数 - urlPart = StrUtil.subPre(paramsStr, pathEndPos); - paramPart = StrUtil.subSuf(paramsStr, pathEndPos + 1); + urlPart = StrUtil.subPre(urlWithParams, pathEndPos); + paramPart = StrUtil.subSuf(urlWithParams, pathEndPos + 1); if (StrUtil.isBlank(paramPart)) { // 无参数,返回url return urlPart; } + } else if (false == StrUtil.contains(urlWithParams, '=')) { + // 无参数的URL + return urlWithParams; } else { - // 无URL - paramPart = paramsStr; + // 无URL的参数 + paramPart = urlWithParams; } paramPart = normalizeParams(paramPart, charset); @@ -551,16 +518,27 @@ public class HttpUtil { * @param charset 字符集 * @return 参数Map * @since 4.0.2 + * @deprecated 请使用 {@link #decodeParamMap(String, Charset)} */ - public static HashMap decodeParamMap(String paramsStr, String charset) { - final Map> paramsMap = decodeParams(paramsStr, charset); - final HashMap result = MapUtil.newHashMap(paramsMap.size()); - List valueList; - for (Entry> entry : paramsMap.entrySet()) { - valueList = entry.getValue(); - result.put(entry.getKey(), CollUtil.isEmpty(valueList) ? null : valueList.get(0)); + @Deprecated + public static Map decodeParamMap(String paramsStr, String charset) { + return decodeParamMap(paramsStr, CharsetUtil.charset(charset)); + } + + /** + * 将URL参数解析为Map(也可以解析Post中的键值对参数) + * + * @param paramsStr 参数字符串(或者带参数的Path) + * @param charset 字符集 + * @return 参数Map + * @since 5.2.6 + */ + public static Map decodeParamMap(String paramsStr, Charset charset) { + final Map queryMap = UrlQuery.of(paramsStr, charset).getQueryMap(); + if (MapUtil.isEmpty(queryMap)) { + return MapUtil.empty(); } - return result; + return Convert.toMap(String.class, String.class, queryMap); } /** @@ -571,56 +549,29 @@ public class HttpUtil { * @return 参数Map */ public static Map> decodeParams(String paramsStr, String charset) { - if (StrUtil.isBlank(paramsStr)) { - return Collections.emptyMap(); + return decodeParams(paramsStr, CharsetUtil.charset(charset)); + } + + /** + * 将URL参数解析为Map(也可以解析Post中的键值对参数) + * + * @param paramsStr 参数字符串(或者带参数的Path) + * @param charset 字符集 + * @return 参数Map + * @since 5.2.6 + */ + public static Map> decodeParams(String paramsStr, Charset charset) { + final Map queryMap = UrlQuery.of(paramsStr, charset).getQueryMap(); + if (MapUtil.isEmpty(queryMap)) { + return MapUtil.empty(); } - // 去掉Path部分 - int pathEndPos = paramsStr.indexOf('?'); - if (pathEndPos > -1) { - paramsStr = StrUtil.subSuf(paramsStr, pathEndPos + 1); - if (StrUtil.isBlank(paramsStr)) { - return Collections.emptyMap(); - } - } - - final int len = paramsStr.length(); final Map> params = new LinkedHashMap<>(); - String name = null; - int pos = 0; // 未处理字符开始位置 - int i; // 未处理字符结束位置 - char c; // 当前字符 - for (i = 0; i < len; i++) { - c = paramsStr.charAt(i); - if (c == '=') { // 键值对的分界点 - if (null == name) { - // name可以是"" - name = paramsStr.substring(pos, i); - } - pos = i + 1; - } else if (c == '&') { // 参数对的分界点 - if (null == name && pos != i) { - // 对于像&a&这类无参数值的字符串,我们将name为a的值设为"" - addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset); - } else if (name != null) { - addParam(params, name, paramsStr.substring(pos, i), charset); - name = null; - } - pos = i + 1; - } - } - - // 处理结尾 - if (pos != i) { - if (name == null) { - addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset); - } else { - addParam(params, name, paramsStr.substring(pos, i), charset); - } - } else if (name != null) { - addParam(params, name, StrUtil.EMPTY, charset); - } - + queryMap.forEach((key, value) -> { + final List values = params.computeIfAbsent(StrUtil.str(key), k -> new ArrayList<>(1)); + // 一般是一个参数 + values.add(StrUtil.str(value)); + }); return params; } @@ -696,7 +647,22 @@ public class HttpUtil { if (conn == null) { return null; } - return ReUtil.get(CHARSET_PATTERN, conn.getContentType(), 1); + return getCharset(conn.getContentType()); + } + + /** + * 从Http连接的头信息中获得字符集
    + * 从ContentType中获取 + * + * @param contentType Content-Type + * @return 字符集 + * @since 5.2.6 + */ + public static String getCharset(String contentType) { + if (StrUtil.isBlank(contentType)) { + return null; + } + return ReUtil.get(CHARSET_PATTERN, contentType, 1); } /** @@ -794,23 +760,15 @@ public class HttpUtil { final ContentType contentType = ContentType.get(body); return (null == contentType) ? null : contentType.toString(); } - // ----------------------------------------------------------------------------------------- Private method start /** - * 将键值对加入到值为List类型的Map中 + * 创建简易的Http服务器 * - * @param params 参数 - * @param name key - * @param value value - * @param charset 编码 + * @param port 端口 + * @return {@link SimpleServer} + * @since 5.2.6 */ - private static void addParam(Map> params, String name, String value, String charset) { - name = URLUtil.decode(name, charset); - value = URLUtil.decode(value, charset); - final List values = params.computeIfAbsent(name, k -> new ArrayList<>(1)); - // 一般是一个参数 - values.add(value); + public static SimpleServer createServer(int port) { + return new SimpleServer(port); } - - // ----------------------------------------------------------------------------------------- Private method start end } diff --git a/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java new file mode 100644 index 000000000..05374c044 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java @@ -0,0 +1,154 @@ +package cn.hutool.http.body; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.MultiResource; +import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.ContentType; +import cn.hutool.http.HttpUtil; + +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Map; + +/** + * Multipart/form-data数据的请求体封装 + * + * @author looly + * @since 5.3.5 + */ +public class MultipartBody implements RequestBody{ + + private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); + private static final String BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY); + private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n"; + private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; + + private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; + private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; + + /** + * 存储表单数据 + */ + private final Map form; + /** + * 编码 + */ + private final Charset charset; + + /** + * 根据已有表单内容,构建MultipartBody + * @param form 表单 + * @param charset 编码 + * @return MultipartBody + */ + public static MultipartBody create(Map form, Charset charset){ + return new MultipartBody(form, charset); + } + + /** + * 获取Multipart的Content-Type类型 + * + * @return Multipart的Content-Type类型 + */ + public static String getContentType(){ + return CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY; + } + + /** + * 构造 + * + * @param form 表单 + * @param charset 编码 + */ + public MultipartBody(Map form, Charset charset) { + this.form = form; + this.charset = charset; + } + + /** + * 写出Multiparty数据,不关闭流 + * + * @param out out流 + */ + @Override + public void write(OutputStream out) { + writeForm(out); + formEnd(out); + } + + // 普通字符串数据 + + /** + * 发送文件对象表单 + * + * @param out 输出流 + */ + private void writeForm(OutputStream out) { + if (MapUtil.isNotEmpty(this.form)) { + for (Map.Entry entry : this.form.entrySet()) { + appendPart(entry.getKey(), entry.getValue(), out); + } + } + } + + /** + * 添加Multipart表单的数据项 + * + * @param formFieldName 表单名 + * @param value 值,可以是普通值、资源(如文件等) + * @param out Http流 + * @throws IORuntimeException IO异常 + */ + private void appendPart(String formFieldName, Object value, OutputStream out) throws IORuntimeException { + // 多资源 + if (value instanceof MultiResource) { + for (Resource subResource : (MultiResource) value) { + appendPart(formFieldName, subResource, out); + } + return; + } + + write(out, "--", BOUNDARY, StrUtil.CRLF); + + if(value instanceof Resource){ + // 文件资源(二进制资源) + final Resource resource = (Resource)value; + final String fileName = resource.getName(); + write(out, StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName))); + // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 + write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream"))); + resource.writeTo(out); + } else{ + // 普通数据 + write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)); + write(out, value); + } + + write(out, StrUtil.CRLF); + } + + /** + * 上传表单结束 + * + * @param out 输出流 + * @throws IORuntimeException IO异常 + */ + private void formEnd(OutputStream out) throws IORuntimeException { + write(out, BOUNDARY_END); + } + + /** + * 写出对象 + * + * @param out 输出流 + * @param objs 写出的对象(转换为字符串) + */ + private void write(OutputStream out, Object... objs) { + IoUtil.write(out, this.charset, false, objs); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java new file mode 100644 index 000000000..574a4ed6f --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java @@ -0,0 +1,16 @@ +package cn.hutool.http.body; + +import java.io.OutputStream; + +/** + * 定义请求体接口 + */ +public interface RequestBody { + + /** + * 写出数据,不关闭流 + * + * @param out out流 + */ + void write(OutputStream out); +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/package-info.java b/hutool-http/src/main/java/cn/hutool/http/body/package-info.java new file mode 100644 index 000000000..3e15dbfa0 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/package-info.java @@ -0,0 +1,7 @@ +/** + * 请求体封装实现 + * + * @author looly + * + */ +package cn.hutool.http.body; \ No newline at end of file diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java new file mode 100644 index 000000000..ff146ff89 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java @@ -0,0 +1,37 @@ +package cn.hutool.http.server; + +import cn.hutool.core.util.CharsetUtil; +import com.sun.net.httpserver.HttpExchange; + +import java.nio.charset.Charset; + +/** + * HttpServer公用对象,提供HttpExchange包装和公用方法 + * + * @author looly + * @since 5.2.6 + */ +public class HttpServerBase { + + final static Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + + final HttpExchange httpExchange; + + /** + * 构造 + * + * @param httpExchange {@link HttpExchange} + */ + public HttpServerBase(HttpExchange httpExchange) { + this.httpExchange = httpExchange; + } + + /** + * 获取{@link HttpExchange}对象 + * + * @return {@link HttpExchange}对象 + */ + public HttpExchange getHttpExchange() { + return this.httpExchange; + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java new file mode 100644 index 000000000..d62c96b67 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java @@ -0,0 +1,414 @@ +package cn.hutool.http.server; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.multi.ListValueMap; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.net.multipart.MultipartFormData; +import cn.hutool.core.net.multipart.UploadSetting; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.Header; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.Method; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpCookie; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * Http请求对象,对{@link HttpExchange}封装 + * + * @author looly + * @since 5.2.6 + */ +public class HttpServerRequest extends HttpServerBase { + + private Map cookieCache; + private ListValueMap paramsCache; + private MultipartFormData multipartFormDataCache; + private Charset charsetCache; + private byte[] bodyCache; + + /** + * 构造 + * + * @param httpExchange {@link HttpExchange} + */ + public HttpServerRequest(HttpExchange httpExchange) { + super(httpExchange); + } + + /** + * 获得Http Method + * + * @return Http Method + */ + public String getMethod() { + return this.httpExchange.getRequestMethod(); + } + + /** + * 是否为GET请求 + * + * @return 是否为GET请求 + */ + public boolean isGetMethod() { + return Method.GET.name().equalsIgnoreCase(getMethod()); + } + + /** + * 是否为POST请求 + * + * @return 是否为POST请求 + */ + public boolean isPostMethod() { + return Method.POST.name().equalsIgnoreCase(getMethod()); + } + + /** + * 获得请求URI + * + * @return 请求URI + */ + public URI getURI() { + return this.httpExchange.getRequestURI(); + } + + /** + * 获得请求路径Path + * + * @return 请求路径 + */ + public String getPath() { + return getURI().getPath(); + } + + /** + * 获取请求参数 + * + * @return 参数字符串 + */ + public String getQuery() { + return getURI().getQuery(); + } + + /** + * 获得请求header中的信息 + * + * @return header值 + */ + public Headers getHeaders() { + return this.httpExchange.getRequestHeaders(); + } + + /** + * 获得请求header中的信息 + * + * @param headerKey 头信息的KEY + * @return header值 + */ + public String getHeader(Header headerKey) { + return getHeader(headerKey.toString()); + } + + /** + * 获得请求header中的信息 + * + * @param headerKey 头信息的KEY + * @return header值 + */ + public String getHeader(String headerKey) { + return getHeaders().getFirst(headerKey); + } + + /** + * 获得请求header中的信息 + * + * @param headerKey 头信息的KEY + * @param charset 字符集 + * @return header值 + */ + public String getHeader(String headerKey, Charset charset) { + final String header = getHeader(headerKey); + if (null != header) { + return CharsetUtil.convert(header, CharsetUtil.CHARSET_ISO_8859_1, charset); + } + return null; + } + + /** + * 获取Content-Type头信息 + * + * @return Content-Type头信息 + */ + public String getContentType() { + return getHeader(Header.CONTENT_TYPE); + } + + /** + * 获取编码,获取失败默认使用UTF-8,获取规则如下: + * + *
    +	 *     1、从Content-Type头中获取编码,类似于:text/html;charset=utf-8
    +	 * 
    + * + * @return 编码,默认UTF-8 + */ + public Charset getCharset() { + if(null == this.charsetCache){ + final String contentType = getContentType(); + final String charsetStr = HttpUtil.getCharset(contentType); + this.charsetCache = CharsetUtil.parse(charsetStr, DEFAULT_CHARSET); + } + + return this.charsetCache; + } + + /** + * 获得User-Agent + * + * @return User-Agent字符串 + */ + public String getUserAgentStr() { + return getHeader(Header.USER_AGENT); + } + + /** + * 获得User-Agent,未识别返回null + * + * @return User-Agent字符串,未识别返回null + */ + public UserAgent getUserAgent() { + return UserAgentUtil.parse(getUserAgentStr()); + } + + /** + * 获得Cookie信息字符串 + * + * @return cookie字符串 + */ + public String getCookiesStr() { + return getHeader(Header.COOKIE); + } + + /** + * 获得Cookie信息列表 + * + * @return Cookie信息列表 + */ + public Collection getCookies() { + return getCookieMap().values(); + } + + /** + * 获得Cookie信息Map,键为Cookie名,值为HttpCookie对象 + * + * @return Cookie信息Map + */ + public Map getCookieMap() { + if (null == this.cookieCache) { + cookieCache = Collections.unmodifiableMap(CollUtil.toMap( + NetUtil.parseCookies(getCookiesStr()), + new CaseInsensitiveMap<>(), + HttpCookie::getName)); + } + return cookieCache; + } + + /** + * 获得指定Cookie名对应的HttpCookie对象 + * + * @param cookieName Cookie名 + * @return HttpCookie对象 + */ + public HttpCookie getCookie(String cookieName) { + return getCookieMap().get(cookieName); + } + + /** + * 是否为Multipart类型表单,此类型表单用于文件上传 + * + * @return 是否为Multipart类型表单,此类型表单用于文件上传 + */ + public boolean isMultipart() { + if (false == isPostMethod()) { + return false; + } + + final String contentType = getContentType(); + if (StrUtil.isBlank(contentType)) { + return false; + } + return contentType.toLowerCase().startsWith("multipart/"); + } + + /** + * 获取请求体文本,可以是form表单、json、xml等任意内容
    + * 使用{@link #getCharset()}判断编码,判断失败使用UTF-8编码 + * + * @return 请求 + */ + public String getBody() { + return getBody(getCharset()); + } + + /** + * 获取请求体文本,可以是form表单、json、xml等任意内容 + * + * @param charset 编码 + * @return 请求 + */ + public String getBody(Charset charset) { + return StrUtil.str(getBodyBytes(), charset); + } + + /** + * 获取body的bytes数组 + * + * @return body的bytes数组 + */ + public byte[] getBodyBytes(){ + if(null == this.bodyCache){ + this.bodyCache = IoUtil.readBytes(getBodyStream(), true); + } + return this.bodyCache; + } + + /** + * 获取请求体的流,流中可以读取请求内容,包括请求表单数据或文件上传数据 + * + * @return 流 + */ + public InputStream getBodyStream() { + return this.httpExchange.getRequestBody(); + } + + public ListValueMap getParams() { + if (null == this.paramsCache) { + this.paramsCache = new ListValueMap<>(); + final Charset charset = getCharset(); + + //解析URL中的参数 + final String query = getQuery(); + if(StrUtil.isNotBlank(query)){ + this.paramsCache.putAll(HttpUtil.decodeParams(query, charset)); + } + + // 解析multipart中的参数 + if(isMultipart()){ + this.paramsCache.putAll(getMultipart().getParamListMap()); + } else{ + // 解析body中的参数 + final String body = getBody(); + if(StrUtil.isNotBlank(body)){ + this.paramsCache.putAll(HttpUtil.decodeParams(body, charset)); + } + } + } + + return this.paramsCache; + } + + /** + * 获取客户端IP + * + *

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

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

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

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

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

    + * + * @param headerNames 自定义头,通常在Http服务器(例如Nginx)中配置 + * @return IP地址 + * @since 4.4.1 + */ + public String getClientIPByHeader(String... headerNames) { + String ip; + for (String header : headerNames) { + ip = getHeader(header); + if (false == NetUtil.isUnknown(ip)) { + return NetUtil.getMultistageReverseProxyIp(ip); + } + } + + ip = this.httpExchange.getRemoteAddress().getHostName(); + return NetUtil.getMultistageReverseProxyIp(ip); + } + + /** + * 获得MultiPart表单内容,多用于获得上传的文件 + * + * @return MultipartFormData + * @throws IORuntimeException IO异常 + * @since 5.3.0 + */ + public MultipartFormData getMultipart() throws IORuntimeException { + if(null == this.multipartFormDataCache){ + this.multipartFormDataCache = parseMultipart(new UploadSetting()); + } + return this.multipartFormDataCache; + } + + /** + * 获得multipart/form-data 表单内容
    + * 包括文件和普通表单数据
    + * 在同一次请求中,此方法只能被执行一次! + * + * @param uploadSetting 上传文件的设定,包括最大文件大小、保存在内存的边界大小、临时目录、扩展名限定等 + * @return MultiPart表单 + * @throws IORuntimeException IO异常 + * @since 5.3.0 + */ + public MultipartFormData parseMultipart(UploadSetting uploadSetting) throws IORuntimeException { + final MultipartFormData formData = new MultipartFormData(uploadSetting); + try { + formData.parseRequestStream(getBodyStream(), getCharset()); + } catch (IOException e) { + throw new IORuntimeException(e); + } + + return formData; + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java new file mode 100644 index 000000000..2b82b144d --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java @@ -0,0 +1,365 @@ +package cn.hutool.http.server; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.http.ContentType; +import cn.hutool.http.Header; +import cn.hutool.http.HttpStatus; +import cn.hutool.http.HttpUtil; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; + +/** + * Http响应对象,用于写出数据到客户端 + */ +public class HttpServerResponse extends HttpServerBase { + + private Charset charset; + /** + * 是否已经发送了Http状态码,如果没有,提前写出状态码 + */ + private boolean isSendCode; + + /** + * 构造 + * + * @param httpExchange {@link HttpExchange} + */ + public HttpServerResponse(HttpExchange httpExchange) { + super(httpExchange); + } + + /** + * 发送HTTP状态码 + * + * @param httpStatusCode HTTP状态码,见HttpStatus + * @return this + */ + public HttpServerResponse send(int httpStatusCode) { + return send(httpStatusCode, 0); + } + + /** + * 发送成功状态码 + * + * @return this + */ + public HttpServerResponse sendOk() { + return send(HttpStatus.HTTP_OK); + } + + /** + * 发送404错误页 + * + * @param content 错误页页面内容,默认text/html类型 + * @return this + */ + public HttpServerResponse send404(String content) { + return sendError(HttpStatus.HTTP_NOT_FOUND, content); + } + + /** + * 发送错误页 + * + * @param errorCode HTTP错误状态码,见HttpStatus + * @param content 错误页页面内容,默认text/html类型 + * @return this + */ + public HttpServerResponse sendError(int errorCode, String content) { + send(errorCode); + setContentType(ContentType.TEXT_HTML.toString()); + return write(content); + } + + /** + * 发送HTTP状态码 + * + * @param httpStatusCode HTTP状态码,见HttpStatus + * @param bodyLength 响应体长度,默认0 + * @return this + */ + public HttpServerResponse send(int httpStatusCode, long bodyLength) { + if (this.isSendCode) { + throw new IORuntimeException("Http status code has been send!"); + } + + try { + this.httpExchange.sendResponseHeaders(httpStatusCode, bodyLength); + } catch (IOException e) { + throw new IORuntimeException(e); + } + + this.isSendCode = true; + return this; + } + + /** + * 获得所有响应头,获取后可以添加新的响应头 + * + * @return 响应头 + */ + public Headers getHeaders() { + if (false == this.isSendCode) { + sendOk(); + } + return this.httpExchange.getResponseHeaders(); + } + + /** + * 添加响应头,如果已经存在,则追加 + * + * @param header 头key + * @param value 值 + * @return this + */ + public HttpServerResponse addHeader(String header, String value) { + getHeaders().add(header, value); + return this; + } + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param header 头key + * @param value 值 + * @return this + */ + public HttpServerResponse setHeader(Header header, String value) { + return setHeader(header.getValue(), value); + } + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param header 头key + * @param value 值 + * @return this + */ + public HttpServerResponse setHeader(String header, String value) { + getHeaders().set(header, value); + return this; + } + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param header 头key + * @param value 值列表 + * @return this + */ + public HttpServerResponse setHeader(String header, List value) { + getHeaders().put(header, value); + return this; + } + + /** + * 设置所有响应头,如果已经存在,则覆盖 + * + * @param headers 响应头map + * @return this + */ + public HttpServerResponse setHeaders(Map> headers) { + getHeaders().putAll(headers); + return this; + } + + /** + * 设置Content-Type头,类似于:text/html;charset=utf-8
    + * 如果用户传入的信息无charset信息,自动根据charset补充,charset设置见{@link #setCharset(Charset)} + * + * @param contentType Content-Type头内容 + * @return this + */ + public HttpServerResponse setContentType(String contentType) { + if (null != contentType && null != this.charset) { + if (false == contentType.contains(";charset=")) { + contentType = ContentType.build(contentType, this.charset); + } + } + + return setHeader(Header.CONTENT_TYPE, contentType); + } + + /** + * 设置Content-Length头 + * + * @param contentLength Content-Length头内容 + * @return this + */ + public HttpServerResponse setContentLength(long contentLength) { + return setHeader(Header.CONTENT_LENGTH, String.valueOf(contentLength)); + } + + /** + * 设置响应的编码 + * + * @param charset 编码 + * @return this + */ + public HttpServerResponse setCharset(Charset charset) { + this.charset = charset; + return this; + } + + /** + * 设置属性 + * + * @param name 属性名 + * @param value 属性值 + * @return this + */ + public HttpServerResponse setAttr(String name, Object value) { + this.httpExchange.setAttribute(name, value); + return this; + } + + /** + * 获取响应数据流 + * + * @return 响应数据流 + */ + public OutputStream getOut() { + if (false == this.isSendCode) { + sendOk(); + } + return this.httpExchange.getResponseBody(); + } + + /** + * 获取响应数据流 + * + * @return 响应数据流 + */ + public PrintWriter getWriter() { + final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET); + return new PrintWriter(new OutputStreamWriter(getOut(), charset)); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @param contentType Content-Type类型 + * @return this + */ + public HttpServerResponse write(String data, String contentType) { + setContentType(contentType); + return write(data); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @return this + */ + public HttpServerResponse write(String data) { + final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET); + return write(StrUtil.bytes(data, charset)); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @param contentType 返回的类型 + * @return this + */ + public HttpServerResponse write(byte[] data, String contentType) { + setContentType(contentType); + return write(data); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @return this + */ + public HttpServerResponse write(byte[] data) { + return write(new ByteArrayInputStream(data)); + } + + /** + * 返回数据给客户端 + * + * @param in 需要返回客户端的内容 + * @param contentType 返回的类型 + * @return this + * @since 5.2.6 + */ + public HttpServerResponse write(InputStream in, String contentType) { + setContentType(contentType); + return write(in); + } + + /** + * 写出数据到客户端 + * + * @param in 数据流 + * @return this + */ + public HttpServerResponse write(InputStream in) { + OutputStream out = null; + try { + out = getOut(); + IoUtil.copy(in, out); + } finally { + IoUtil.close(out); + IoUtil.close(in); + } + return this; + } + + /** + * 返回文件给客户端(文件下载) + * + * @param file 写出的文件对象 + * @return this + * @since 5.2.6 + */ + public HttpServerResponse write(File file) { + final String fileName = file.getName(); + final String contentType = ObjectUtil.defaultIfNull(HttpUtil.getMimeType(fileName), "application/octet-stream"); + BufferedInputStream in = null; + try { + in = FileUtil.getInputStream(file); + write(in, contentType, fileName); + } finally { + IoUtil.close(in); + } + return this; + } + + /** + * 返回文件数据给客户端(文件下载) + * + * @param in 需要返回客户端的内容 + * @param contentType 返回的类型 + * @param fileName 文件名 + * @since 5.2.6 + */ + public void write(InputStream in, String contentType, String fileName) { + final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET); + setHeader("Content-Disposition", StrUtil.format("attachment;filename={}", URLUtil.encode(fileName, charset))); + setContentType(contentType); + write(in); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java new file mode 100644 index 000000000..aa1377963 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java @@ -0,0 +1,127 @@ +package cn.hutool.http.server; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Console; +import cn.hutool.http.server.action.Action; +import cn.hutool.http.server.action.RootAction; +import cn.hutool.http.server.handler.ActionHandler; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.Executor; + +/** + * 简易Http服务器,基于{@link HttpServer} + * + * @author looly + * @since 5.2.5 + */ +public class SimpleServer { + + HttpServer server; + + /** + * 构造 + * + * @param port 监听端口 + */ + public SimpleServer(int port) { + this(new InetSocketAddress(port)); + } + + /** + * 构造 + * + * @param hostname 监听地址 + * @param port 监听端口 + */ + public SimpleServer(String hostname, int port) { + this(new InetSocketAddress(hostname, port)); + } + + /** + * 构造 + * + * @param address 监听地址 + */ + public SimpleServer(InetSocketAddress address) { + try { + this.server = HttpServer.create(address, 0); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 增加请求处理规则 + * + * @param path 路径 + * @param handler 处理器 + * @return this + */ + public SimpleServer addHandler(String path, HttpHandler handler) { + this.server.createContext(path, handler); + return this; + } + + /** + * 设置根目录,默认的页面从root目录中读取解析返回 + * + * @param root 路径 + * @return this + */ + public SimpleServer setRoot(String root) { + return addAction("/", new RootAction(root)); + } + + /** + * 增加请求处理规则 + * + * @param path 路径 + * @param action 处理器 + * @return this + */ + public SimpleServer addAction(String path, Action action) { + return addHandler(path, new ActionHandler(action)); + } + + /** + * 设置自定义线程池 + * + * @param executor {@link Executor} + * @return this + */ + public SimpleServer setExecutor(Executor executor) { + this.server.setExecutor(executor); + return this; + } + + /** + * 获得原始HttpServer对象 + * + * @return {@link HttpServer} + */ + public HttpServer getRawServer() { + return this.server; + } + + /** + * 获取服务器地址信息 + * + * @return {@link InetSocketAddress} + */ + public InetSocketAddress getAddress() { + return this.server.getAddress(); + } + + /** + * 启动Http服务器,启动后会阻塞当前线程 + */ + public void start() { + final InetSocketAddress address = getAddress(); + Console.log("Hutool Simple Http Server listen on 【{}:{}】", address.getHostName(), address.getPort()); + this.server.start(); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java new file mode 100644 index 000000000..73b69058e --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java @@ -0,0 +1,26 @@ +package cn.hutool.http.server.action; + +import cn.hutool.http.server.HttpServerRequest; +import cn.hutool.http.server.HttpServerResponse; + +import java.io.IOException; + +/** + * 请求处理接口
    + * 当用户请求某个Path,则调用相应Action的doAction方法 + * + * @author Looly + * @since 5.2.6 + */ +@FunctionalInterface +public interface Action { + + /** + * 处理请求 + * + * @param request 请求对象 + * @param response 响应对象 + * @throws IOException IO异常 + */ + void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException; +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java b/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java new file mode 100644 index 000000000..9b4c66803 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java @@ -0,0 +1,64 @@ +package cn.hutool.http.server.action; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.http.server.HttpServerRequest; +import cn.hutool.http.server.HttpServerResponse; + +import java.io.File; +import java.util.List; + +/** + * 默认的处理器,通过解析用户传入的path,找到网页根目录下对应文件后返回 + * + * @author looly + * @since 5.2.6 + */ +public class RootAction implements Action { + + public static final String DEFAULT_INDEX_FILE_NAME = "index.html"; + + private final String rootDir; + private final List indexFileNames; + + /** + * 构造 + * + * @param rootDir 网页根目录 + */ + public RootAction(String rootDir) { + this(rootDir, DEFAULT_INDEX_FILE_NAME); + } + + /** + * 构造 + * + * @param rootDir 网页根目录 + * @param indexFileNames 主页文件名列表 + */ + public RootAction(String rootDir, String... indexFileNames) { + this.rootDir = rootDir; + this.indexFileNames = CollUtil.toList(indexFileNames); + } + + @Override + public void doAction(HttpServerRequest request, HttpServerResponse response) { + final String path = request.getPath(); + File file = FileUtil.file(rootDir, path); + if (file.exists()) { + if (file.isDirectory()) { + for (String indexFileName : indexFileNames) { + //默认读取主页 + file = FileUtil.file(file, indexFileName); + if (file.exists() && file.isFile()) { + response.write(file); + } + } + } else{ + response.write(file); + } + } + + response.send404("404 Not Found !"); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java new file mode 100644 index 000000000..f102c8314 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java @@ -0,0 +1,37 @@ +package cn.hutool.http.server.handler; + +import cn.hutool.http.server.HttpServerRequest; +import cn.hutool.http.server.HttpServerResponse; +import cn.hutool.http.server.action.Action; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import java.io.IOException; + +/** + * Action处理器,用于将HttpHandler转换为Action形式 + * + * @author looly + * @since 5.2.6 + */ +public class ActionHandler implements HttpHandler { + + private final Action action; + + /** + * 构造 + * + * @param action Action + */ + public ActionHandler(Action action) { + this.action = action; + } + + @Override + public void handle(HttpExchange httpExchange) throws IOException { + action.doAction( + new HttpServerRequest(httpExchange), + new HttpServerResponse(httpExchange) + ); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/package-info.java b/hutool-http/src/main/java/cn/hutool/http/server/package-info.java new file mode 100644 index 000000000..0b65d48e7 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/package-info.java @@ -0,0 +1,7 @@ +/** + * Http服务器封装 + * + * @author looly + * + */ +package cn.hutool.http.server; \ No newline at end of file diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java b/hutool-http/src/main/java/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java index 90ce12fb0..e1630541b 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java @@ -2,15 +2,14 @@ package cn.hutool.http.ssl; import cn.hutool.core.util.ArrayUtil; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; - /** * 自定义支持协议类型的SSLSocketFactory * @@ -18,8 +17,8 @@ import javax.net.ssl.SSLSocketFactory; */ public class CustomProtocolsSSLFactory extends SSLSocketFactory { - private String[] protocols; - private SSLSocketFactory base; + private final String[] protocols; + private final SSLSocketFactory base; /** * 构造 diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/DefaultTrustManager.java b/hutool-http/src/main/java/cn/hutool/http/ssl/DefaultTrustManager.java index 0b848842e..008d1a025 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/DefaultTrustManager.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/DefaultTrustManager.java @@ -1,9 +1,7 @@ package cn.hutool.http.ssl; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - import javax.net.ssl.X509TrustManager; +import java.security.cert.X509Certificate; /** * 证书管理 diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentInfo.java b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentInfo.java index a16ecb60f..862c2183b 100644 --- a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentInfo.java +++ b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentInfo.java @@ -1,9 +1,9 @@ package cn.hutool.http.useragent; -import java.util.regex.Pattern; - import cn.hutool.core.util.ReUtil; +import java.util.regex.Pattern; + /** * User-agent信息 * @@ -15,9 +15,9 @@ public class UserAgentInfo { public static final String NameUnknown = "Unknown"; /** 信息名称 */ - private String name; + private final String name; /** 信息匹配模式 */ - private Pattern pattern; + private final Pattern pattern; /** * 构造 diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java index 6f95ddeed..99cc63938 100644 --- a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java +++ b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java @@ -1,6 +1,7 @@ package cn.hutool.http.useragent; import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; import java.util.regex.Pattern; @@ -19,6 +20,9 @@ public class UserAgentParser { * @return {@link UserAgent} */ public static UserAgent parse(String userAgentString) { + if(StrUtil.isBlank(userAgentString)){ + return null; + } final UserAgent userAgent = new UserAgent(); final Browser browser = parseBrowser(userAgentString); diff --git a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java index 5fedf4589..d2dfc74b6 100644 --- a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java +++ b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java @@ -1,11 +1,15 @@ package cn.hutool.http.webservice; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.XmlUtil; +import cn.hutool.http.HttpBase; +import cn.hutool.http.HttpGlobalConfig; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; import javax.xml.XMLConstants; import javax.xml.namespace.QName; @@ -18,17 +22,12 @@ import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.XmlUtil; -import cn.hutool.http.HttpGlobalConfig; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; /** * SOAP客户端 @@ -52,7 +51,7 @@ import cn.hutool.http.HttpResponse; * @author looly * @since 4.5.4 */ -public class SoapClient { +public class SoapClient extends HttpBase { /** * XML消息体的Content-Type @@ -63,10 +62,7 @@ public class SoapClient { * 请求的URL地址 */ private String url; - /** - * 编码 - */ - private Charset charset = CharsetUtil.CHARSET_UTF_8; + /** * 默认连接超时 */ @@ -91,7 +87,7 @@ public class SoapClient { /** * 应用于方法上的命名空间URI */ - private String namespaceURI; + private final String namespaceURI; /** * 创建SOAP客户端,默认使用soap1.1版本协议 @@ -204,11 +200,17 @@ public class SoapClient { * * @param charset 编码 * @return this + * @see #charset(Charset) */ public SoapClient setCharset(Charset charset) { - this.charset = charset; + return this.charset(charset); + } + + @Override + public SoapClient charset(Charset charset) { + super.charset(charset); try { - this.message.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, this.charset.toString()); + this.message.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, this.charset()); this.message.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "true"); } catch (SOAPException e) { // ignore @@ -229,17 +231,45 @@ public class SoapClient { } /** - * 设置头信息 + * 设置SOAP头信息 + * + * @param name 头信息标签名 + * @return this + * @deprecated 为了和Http Hrader区分,请使用{@link #setSOAPHeader(QName)} + */ + @Deprecated + public SoapClient setHeader(QName name) { + return setSOAPHeader(name, null, null, null, null); + } + + /** + * 设置SOAP头信息 * * @param name 头信息标签名 * @return this */ - public SoapClient setHeader(QName name) { - return setHeader(name, null, null, null, null); + public SoapClient setSOAPHeader(QName name) { + return setSOAPHeader(name, null, null, null, null); } /** - * 设置头信息 + * 设置SOAP头信息 + * + * @param name 头信息标签名 + * @param actorURI 中间的消息接收者 + * @param roleUri Role的URI + * @param mustUnderstand 标题项对于要对其进行处理的接收者来说是强制的还是可选的 + * @param relay relay属性 + * @return this + * @deprecated 为了和Http Hrader区分,请使用{@link #setSOAPHeader(QName, String, String, Boolean, Boolean)} + */ + @Deprecated + public SoapClient setHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) { + return setSOAPHeader(name, actorURI, roleUri, mustUnderstand, relay); + } + + /** + * 设置SOAP头信息 * * @param name 头信息标签名 * @param actorURI 中间的消息接收者 @@ -248,7 +278,7 @@ public class SoapClient { * @param relay relay属性 * @return this */ - public SoapClient setHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) { + public SoapClient setSOAPHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) { SOAPHeader header; SOAPHeaderElement ele; try { @@ -549,6 +579,7 @@ public class SoapClient { .setConnectionTimeout(this.connectionTimeout) .setReadTimeout(this.readTimeout) .contentType(getXmlContentType())// + .header(this.headers()) .body(getMsgStr(false))// .executeAsync(); } diff --git a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapProtocol.java b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapProtocol.java index 22900612d..8eee3c5d4 100644 --- a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapProtocol.java +++ b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapProtocol.java @@ -23,7 +23,7 @@ public enum SoapProtocol { this.value = value; } - private String value; + private final String value; /** * 获取版本值信息 diff --git a/hutool-http/src/test/java/cn/hutool/http/server/BlankServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/BlankServerTest.java new file mode 100644 index 000000000..b5819778e --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/server/BlankServerTest.java @@ -0,0 +1,13 @@ +package cn.hutool.http.server; + +import cn.hutool.http.HttpUtil; + +public class BlankServerTest { + public static void main(String[] args) { + HttpUtil.createServer(8888) + .addAction("/", (req, res)->{ + res.write("Hello Hutool Server"); + }) + .start(); + } +} diff --git a/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java new file mode 100644 index 000000000..e5685e2bc --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java @@ -0,0 +1,16 @@ +package cn.hutool.http.server; + +import cn.hutool.core.swing.DesktopUtil; +import cn.hutool.http.HttpUtil; + +public class DocServerTest { + + public static void main(String[] args) { + HttpUtil.createServer(80) + // 设置默认根目录, + .setRoot("D:\\workspace\\site\\hutool-site") + .start(); + + DesktopUtil.browse("http://localhost/"); + } +} diff --git a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java new file mode 100644 index 000000000..f76021b18 --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java @@ -0,0 +1,41 @@ +package cn.hutool.http.server; + +import cn.hutool.core.lang.Console; +import cn.hutool.core.net.multipart.UploadFile; +import cn.hutool.http.ContentType; +import cn.hutool.http.HttpUtil; + +public class SimpleServerTest { + + public static void main(String[] args) { + HttpUtil.createServer(8888) + // 设置默认根目录, + .setRoot("d:/test") + // get数据测试,返回请求的PATH + .addAction("/get", (request, response) -> + response.write(request.getURI().toString(), ContentType.TEXT_PLAIN.toString()) + ) + // 返回JSON数据测试 + .addAction("/restTest", (request, response) -> + response.write("{\"id\": 1, \"msg\": \"OK\"}", ContentType.JSON.toString()) + ) + // 获取表单数据测试 + // http://localhost:8888/formTest?a=1&a=2&b=3 + .addAction("/formTest", (request, response) -> + response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()) + ) + // 文件上传测试 + // http://localhost:8888/formTest?a=1&a=2&b=3 + .addAction("/file", (request, response) -> { + final UploadFile[] files = request.getMultipart().getFiles("file"); + // 传入目录,默认读取HTTP头中的文件名然后创建文件 + for (UploadFile file : files) { + file.write("d:/test/"); + Console.log("Write file: d:/test/" + file.getFileName()); + } + response.write(request.getMultipart().getParamMap().toString(), ContentType.TEXT_PLAIN.toString()); + } + ) + .start(); + } +} diff --git a/hutool-http/src/test/java/cn/hutool/http/test/DownloadTest.java b/hutool-http/src/test/java/cn/hutool/http/test/DownloadTest.java index 8cf664711..229ce0857 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/DownloadTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/DownloadTest.java @@ -44,7 +44,7 @@ public class DownloadTest { // 带进度显示的文件下载 HttpUtil.downloadFile("http://mirrors.sohu.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1810.iso", FileUtil.file("d:/"), new StreamProgress() { - long time = System.currentTimeMillis(); + final long time = System.currentTimeMillis(); @Override public void start() { diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java index 96836d5fb..a725b169e 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java @@ -1,11 +1,5 @@ package cn.hutool.http.test; -import java.util.List; -import java.util.Map; - -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.TimeInterval; import cn.hutool.core.lang.Console; @@ -13,6 +7,11 @@ import cn.hutool.core.util.CharsetUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.ssl.SSLSocketFactoryBuilder; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; +import java.util.Map; /** * {@link HttpRequest}单元测试 @@ -37,19 +36,16 @@ public class HttpRequestTest { HttpResponse res = HttpRequest.get("https://www.oschina.net/").execute(); String body = res.body(); Console.log(res.getCookies()); + Console.log(body); } @Test @Ignore - public void getWithParamsTest() { + public void toStringTest() { String url = "http://gc.ditu.aliyun.com/geocoding?ccc=你好"; - HttpRequest request = HttpRequest.get(url).setEncodeUrlParams(true).body("a=乌海"); - String body = request.execute().body(); - Console.log(body); - -// String body2 = HttpUtil.get(url); -// Console.log(body2); + HttpRequest request = HttpRequest.get(url).body("a=乌海"); + Console.log(request.toString()); } @Test diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java index 58ea75870..40ea4a3e1 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java @@ -1,13 +1,5 @@ package cn.hutool.http.test; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.util.CharsetUtil; @@ -15,6 +7,14 @@ import cn.hutool.core.util.ReUtil; import cn.hutool.http.Header; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; public class HttpUtilTest { @@ -72,6 +72,17 @@ public class HttpUtilTest { Console.log(str); } + @Test + @Ignore + public void getTest5() { + String url2 = "http://storage.chancecloud.com.cn/20200413_%E7%B2%A4B12313_386.pdf"; + ByteArrayOutputStream os2 = new ByteArrayOutputStream(); + HttpUtil.download(url2, os2, false); + + url2 = "http://storage.chancecloud.com.cn/20200413_粤B12313_386.pdf"; + HttpUtil.download(url2, os2, false); + } + @Test @Ignore public void get12306Test() { @@ -114,6 +125,14 @@ public class HttpUtilTest { Assert.assertEquals("?#@!$%^&=dsssss555555", map.get("c").get(0)); } + @Test + public void decodeParamMapTest() { + // 参数值存在分界标记等号时 + Map paramMap = HttpUtil.decodeParamMap("https://www.xxx.com/api.action?aa=123&f_token=NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("123",paramMap.get("aa")); + Assert.assertEquals("NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=",paramMap.get("f_token")); + } + @Test public void toParamsTest() { String paramsStr = "uuuu=0&a=b&c=3Ddsssss555555"; @@ -164,6 +183,16 @@ public class HttpUtilTest { paramsStr = "a=bbb&c=你好&哈喽&"; encode = HttpUtil.encodeParams(paramsStr, CharsetUtil.CHARSET_UTF_8); Assert.assertEquals("a=bbb&c=%E4%BD%A0%E5%A5%BD&%E5%93%88%E5%96%BD=", encode); + + // URL原样输出 + paramsStr = "https://www.hutool.cn/"; + encode = HttpUtil.encodeParams(paramsStr, CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals(paramsStr, encode); + + // URL原样输出 + paramsStr = "https://www.hutool.cn/?"; + encode = HttpUtil.encodeParams(paramsStr, CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("https://www.hutool.cn/", encode); } @Test @@ -274,4 +303,13 @@ public class HttpUtilTest { String mimeType = HttpUtil.getMimeType("aaa.aaa"); Assert.assertNull(mimeType); } + + @Test + @Ignore + public void getWeixinTest(){ + // 测试特殊URL,即URL中有&情况是否请求正常 + String url = "https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&mid=100000465&idx=1&sn=1044c0d19723f74f04f4c1da34eefa35&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7"; + final String s = HttpUtil.get(url); + Console.log(s); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/test/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/test/RestTest.java index c6e735200..2730ec6d9 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/RestTest.java @@ -14,13 +14,14 @@ import org.junit.Test; * @author looly * */ -@SuppressWarnings("ConstantConditions") public class RestTest { @Test public void contentTypeTest() { HttpRequest request = HttpRequest.post("http://localhost:8090/rest/restTest/")// - .body(JSONUtil.createObj().put("aaa", "aaaValue").put("键2", "值2").toString()); + .body(JSONUtil.createObj() + .set("aaa", "aaaValue") + .set("键2", "值2").toString()); Assert.assertEquals("application/json;charset=UTF-8", request.header("Content-Type")); } @@ -28,7 +29,9 @@ public class RestTest { @Ignore public void postTest() { HttpRequest request = HttpRequest.post("http://localhost:8090/rest/restTest/")// - .body(JSONUtil.createObj().put("aaa", "aaaValue").put("键2", "值2").toString()); + .body(JSONUtil.createObj() + .set("aaa", "aaaValue") + .set("键2", "值2").toString()); Console.log(request.execute().body()); } @@ -36,7 +39,8 @@ public class RestTest { @Ignore public void postTest2() { String result = HttpUtil.post("http://localhost:8090/rest/restTest/", JSONUtil.createObj()// - .put("aaa", "aaaValue").put("键2", "值2").toString()); + .set("aaa", "aaaValue") + .set("键2", "值2").toString()); Console.log(result); } @@ -44,7 +48,9 @@ public class RestTest { @Ignore public void postTest3() { HttpRequest request = HttpRequest.post("http://211.162.39.204:8181/jeesite-simple/a/open/bizGwbnService/test")// - .body(JSONUtil.createObj().put("aaa", "aaaValue").put("键2", "值2").toString()); + .body(JSONUtil.createObj() + .set("aaa", "aaaValue") + .set("键2", "值2").toString()); Console.log(request.execute().body()); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java b/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java index 8fdd17c30..6a8e151bd 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java @@ -1,15 +1,15 @@ package cn.hutool.http.test; -import java.io.File; -import java.util.HashMap; - -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Console; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.util.HashMap; /** * 文件上传单元测试 @@ -24,16 +24,16 @@ public class UploadTest { @Test @Ignore public void uploadFilesTest() { - File file = FileUtil.file("e:\\face.jpg"); - File file2 = FileUtil.file("e:\\face2.jpg"); + File file = FileUtil.file("d:\\图片1.JPG"); + File file2 = FileUtil.file("d:\\图片3.png"); // 方法一:自定义构建表单 HttpRequest request = HttpRequest// - .post("http://localhost:8090/file/upload")// + .post("http://localhost:8888/file")// .form("file", file2, file)// .form("fileType", "图片"); HttpResponse response = request.execute(); - System.out.println(response.body()); + Console.log(response.body()); } @Test diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index f5837ea56..03ec5d02b 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java index 4c55a0ccd..23963173a 100644 --- a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java @@ -1,5 +1,12 @@ package cn.hutool.json; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; + import java.io.IOException; import java.io.Writer; import java.time.temporal.TemporalAccessor; @@ -9,13 +16,6 @@ import java.util.Date; import java.util.Iterator; import java.util.Map; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; - /** * 内部JSON工具类,仅用于JSON内部使用 * @@ -31,7 +31,7 @@ final class InternalJSONUtil { * * @param writer Writer * @param value 值 - * @param indentFactor 每一级别的缩进量 + * @param indentFactor 缩进因子,定义每一级别增加的缩进量 * @param indent 缩进空格数 * @param config 配置项 * @return Writer @@ -188,26 +188,30 @@ final class InternalJSONUtil { * @return JSONObject */ protected static JSONObject propertyPut(JSONObject jsonObject, Object key, Object value) { - String keyStr = Convert.toStr(key); - String[] path = StrUtil.split(keyStr, StrUtil.DOT); + final String[] path = StrUtil.split(Convert.toStr(key), StrUtil.DOT); int last = path.length - 1; JSONObject target = jsonObject; for (int i = 0; i < last; i += 1) { String segment = path[i]; JSONObject nextTarget = target.getJSONObject(segment); if (nextTarget == null) { - nextTarget = new JSONObject(); - target.put(segment, nextTarget); + nextTarget = new JSONObject(target.getConfig()); + target.set(segment, nextTarget); } target = nextTarget; } - target.put(path[last], value); + target.set(path[last], value); return jsonObject; } /** - * 默认情况下是否忽略null值的策略选择
    - * JavaBean默认忽略null值,其它对象不忽略 + * 默认情况下是否忽略null值的策略选择,以下对象不忽略null值,其它对象忽略: + * + *
    +	 *     1. CharSequence
    +	 *     2. JSONTokener
    +	 *     3. Map
    +	 * 
    * * @param obj 需要检查的对象 * @return 是否忽略null值 @@ -234,13 +238,13 @@ final class InternalJSONUtil { //默认使用时间戳 long timeMillis; - if(dateObj instanceof TemporalAccessor){ - timeMillis = DateUtil.toInstant((TemporalAccessor)dateObj).toEpochMilli(); - } else if(dateObj instanceof Date){ + if (dateObj instanceof TemporalAccessor) { + timeMillis = DateUtil.toInstant((TemporalAccessor) dateObj).toEpochMilli(); + } else if (dateObj instanceof Date) { timeMillis = ((Date) dateObj).getTime(); - } else if(dateObj instanceof Calendar){ + } else if (dateObj instanceof Calendar) { timeMillis = ((Calendar) dateObj).getTimeInMillis(); - } else{ + } else { throw new UnsupportedOperationException("Unsupported Date type: " + dateObj.getClass()); } return String.valueOf(timeMillis); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSON.java b/hutool-json/src/main/java/cn/hutool/json/JSON.java index cdbe26e6f..dbc518002 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSON.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSON.java @@ -1,13 +1,13 @@ package cn.hutool.json; +import cn.hutool.core.bean.BeanPath; +import cn.hutool.core.lang.TypeReference; + import java.io.Serializable; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Type; -import cn.hutool.core.bean.BeanPath; -import cn.hutool.core.lang.TypeReference; - /** * JSON接口 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index bd8bd5d85..0afc46d02 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -3,6 +3,7 @@ package cn.hutool.json; import cn.hutool.core.bean.BeanPath; import cn.hutool.core.collection.ArrayIter; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -19,7 +20,7 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.*; +import static cn.hutool.json.JSONConverter.jsonConvert; /** * JSON数组
    @@ -41,7 +42,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando /** 持有原始数据的List */ private final List rawList; /** 配置项 */ - private JSONConfig config; + private final JSONConfig config; // -------------------------------------------------------------------------------------------------------------------- Constructor start /** @@ -189,6 +190,11 @@ public class JSONArray implements JSON, JSONGetter, List, Rando } // -------------------------------------------------------------------------------------------------------------------- Constructor start + @Override + public JSONConfig getConfig() { + return this.config; + } + /** * 设置转为字符串时的日期格式,默认为时间戳(null值) * @@ -252,8 +258,21 @@ public class JSONArray implements JSON, JSONGetter, List, Rando * * @param value 值,可以是: Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONNull.NULL。 * @return this. + * @see #set(Object) */ public JSONArray put(Object value) { + return set(value); + } + + /** + * Append an object value. This increases the array's length by one.
    + * 加入元素,数组长度+1,等同于 {@link JSONArray#add(Object)} + * + * @param value 值,可以是: Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONNull.NULL。 + * @return this. + * @since 5.2.5 + */ + public JSONArray set(Object value) { this.add(value); return this; } @@ -282,9 +301,9 @@ public class JSONArray implements JSON, JSONGetter, List, Rando if (names == null || names.size() == 0 || this.size() == 0) { return null; } - JSONObject jo = new JSONObject(); + final JSONObject jo = new JSONObject(this.config); for (int i = 0; i < names.size(); i += 1) { - jo.put(names.getStr(i), this.getObj(i)); + jo.set(names.getStr(i), this.getObj(i)); } return jo; } @@ -347,14 +366,13 @@ public class JSONArray implements JSON, JSONGetter, List, Rando return rawList.contains(o); } - @SuppressWarnings("NullableProblems") @Override public Object[] toArray() { return rawList.toArray(); } @Override - @SuppressWarnings({"unchecked", "NullableProblems"}) + @SuppressWarnings({"unchecked"}) public T[] toArray(T[] a) { return (T[]) JSONConverter.toArray(this, a.getClass().getComponentType()); } @@ -440,7 +458,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando while (index != this.size()) { this.add(JSONNull.NULL); } - this.put(element); + this.set(element); } } @@ -502,11 +520,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando */ @Override public String toString() { - try { - return this.toJSONString(0); - } catch (Exception e) { - return null; - } + return this.toJSONString(0); } @Override @@ -578,9 +592,11 @@ public class JSONArray implements JSON, JSONGetter, List, Rando } else if (source instanceof CharSequence) { // JSON字符串 init((CharSequence) source); + }else if (source instanceof JSONTokener) { + init((JSONTokener) source); } else { Iterator iter; - if (source.getClass().isArray()) {// 数组 + if (ArrayUtil.isArray(source)) {// 数组 iter = new ArrayIter<>(source); } else if (source instanceof Iterator) {// Iterator iter = ((Iterator) source); @@ -602,7 +618,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando */ private void init(CharSequence source) { if (null != source) { - init(new JSONTokener(StrUtil.trim(source))); + init(new JSONTokener(StrUtil.trim(source), this.config)); } } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConverter.java b/hutool-json/src/main/java/cn/hutool/json/JSONConverter.java index 30bd8e538..161319592 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConverter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConverter.java @@ -71,22 +71,16 @@ public class JSONConverter implements Converter { } if(value instanceof JSON) { - JSONDeserializer deserializer = GlobalSerializeMapping.getDeserializer(targetType); + final JSONDeserializer deserializer = GlobalSerializeMapping.getDeserializer(targetType); if(null != deserializer) { return (T) deserializer.deserialize((JSON)value); } } - Object targetValue; - try { - targetValue = Convert.convert(targetType, value); - } catch (ConvertException e) { - if (ignoreError) { - return null; - } - throw e; - } - + final T targetValue = ignoreError ? + Convert.convertQuietly(targetType, value): + Convert.convert(targetType, value); + if (null == targetValue && false == ignoreError) { if (StrUtil.isBlankIfStr(value)) { // 对于传入空字符串的情况,如果转换的目标对象是非字符串或非原始类型,转换器会返回false。 @@ -97,7 +91,7 @@ public class JSONConverter implements Converter { throw new ConvertException("Can not convert {} to type {}", value, ObjectUtil.defaultIfNull(TypeUtil.getClass(targetType), targetType)); } - return (T) targetValue; + return targetValue; } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 547e15c99..e05c03d75 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -5,12 +5,20 @@ import cn.hutool.core.getter.OptNullBasicTypeFromObjectGetter; /** * 用于JSON的Getter类,提供各种类型的Getter方法 - * @author Looly * * @param Key类型 + * @author Looly */ -public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{ - +public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { + + /** + * 获取JSON配置 + * + * @return {@link JSONConfig} + * @since 5.3.0 + */ + JSONConfig getConfig(); + /** * key对应值是否为null或无此key * @@ -20,10 +28,10 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{ default boolean isNull(K key) { return JSONNull.NULL.equals(this.getObj(key)); } - + /** * 获取字符串类型值,并转义不可见字符,如'\n'换行符会被转义为字符串"\n" - * + * * @param key 键 * @return 字符串类型值 * @since 4.2.2 @@ -31,11 +39,11 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{ default String getStrEscaped(K key) { return getStrEscaped(key, null); } - + /** * 获取字符串类型值,并转义不可见字符,如'\n'换行符会被转义为字符串"\n" - * - * @param key 键 + * + * @param key 键 * @param defaultValue 默认值 * @return 字符串类型值 * @since 4.2.2 @@ -43,51 +51,51 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{ default String getStrEscaped(K key, String defaultValue) { return JSONUtil.escape(getStr(key, defaultValue)); } - + /** * 获得JSONArray对象
    * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 - * + * * @param key KEY * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); - if(null == object) { + if (null == object) { return null; } - - if(object instanceof JSONArray) { + + if (object instanceof JSONArray) { return (JSONArray) object; } - return new JSONArray(object); + return new JSONArray(object, getConfig()); } /** * 获得JSONObject对象
    * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 - * + * * @param key KEY * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); - if(null == object) { + if (null == object) { return null; } - - if(object instanceof JSONObject) { + + if (object instanceof JSONObject) { return (JSONObject) object; } - return new JSONObject(object); + return new JSONObject(object, getConfig()); } - + /** * 从JSON中直接获取Bean对象
    * 先获取JSONObject对象,然后转为Bean对象 - * - * @param Bean类型 - * @param key KEY + * + * @param Bean类型 + * @param key KEY * @param beanType Bean类型 * @return Bean对象,如果值为null或者非JSONObject类型,返回null * @since 3.1.1 @@ -96,36 +104,36 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{ final JSONObject obj = getJSONObject(key); return (null == obj) ? null : obj.toBean(beanType); } - + /** * 获取指定类型的对象
    * 转换失败或抛出异常 - * - * @param 获取的对象类型 - * @param key 键 + * + * @param 获取的对象类型 + * @param key 键 * @param type 获取对象类型 * @return 对象 * @throws ConvertException 转换异常 * @since 3.0.8 */ - default T get(K key, Class type) throws ConvertException{ + default T get(K key, Class type) throws ConvertException { return get(key, type, false); } - + /** * 获取指定类型的对象 - * - * @param 获取的对象类型 - * @param key 键 - * @param type 获取对象类型 + * + * @param 获取的对象类型 + * @param key 键 + * @param type 获取对象类型 * @param ignoreError 是否跳过转换失败的对象或值 * @return 对象 * @throws ConvertException 转换异常 * @since 3.0.8 */ - default T get(K key, Class type, boolean ignoreError) throws ConvertException{ + default T get(K key, Class type, boolean ignoreError) throws ConvertException { final Object value = this.getObj(key); - if(null == value){ + if (null == value) { return null; } return JSONConverter.jsonConvert(type, value, ignoreError); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 2d2c4d316..7efe4ace8 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -25,16 +25,12 @@ public class JSONNull implements Serializable{ * @param object An object to test for nullness. * @return true if the object parameter is the JSONObject.NULL object or null. */ + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object object) { - return object == null || (object instanceof JSONNull && object == this); + return object == null || (object == this); } - @Override - public int hashCode() { - return super.hashCode(); - } - /** * Get the "null" string value. *获得“null”字符串 diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index 4ec0ce078..542dc5b38 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -7,6 +7,7 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjectUtil; @@ -22,33 +23,41 @@ import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; -import java.util.HashMap; +import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; +import java.util.ResourceBundle; import java.util.Set; /** * JSON对象
    * 例:
    - * + * *
      * json = new JSONObject().put("JSON", "Hello, World!").toString();
      * 
    - * + * * @author looly */ public class JSONObject implements JSON, JSONGetter, Map { private static final long serialVersionUID = -330220388580734346L; - /** 默认初始大小 */ - public static final int DEFAULT_CAPACITY = 16; + /** + * 默认初始大小 + */ + public static final int DEFAULT_CAPACITY = MapUtil.DEFAULT_INITIAL_CAPACITY; - /** JSON的KV持有Map */ + /** + * JSON的KV持有Map + */ private final Map rawHashMap; - /** 配置项 */ - private JSONConfig config; + /** + * 配置项 + */ + private final JSONConfig config; // -------------------------------------------------------------------------------------------------------------------- Constructor start + /** * 构造,初始容量为 {@link #DEFAULT_CAPACITY},KEY无序 */ @@ -58,7 +67,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 构造,初始容量为 {@link #DEFAULT_CAPACITY} - * + * * @param isOrder 是否有序 * @since 3.0.9 */ @@ -68,9 +77,9 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 构造 - * + * * @param capacity 初始大小 - * @param isOrder 是否有序 + * @param isOrder 是否有序 * @since 3.0.9 */ public JSONObject(int capacity, boolean isOrder) { @@ -79,19 +88,19 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 构造 - * - * @param capacity 初始大小 + * + * @param capacity 初始大小 * @param isIgnoreCase 是否忽略KEY大小写 - * @param isOrder 是否有序 + * @param isOrder 是否有序 * @since 3.3.1 */ public JSONObject(int capacity, boolean isIgnoreCase, boolean isOrder) { this(capacity, JSONConfig.create().setIgnoreCase(isIgnoreCase).setOrder(isOrder)); } - + /** * 构造 - * + * * @param config JSON配置项 * @since 4.6.5 */ @@ -101,16 +110,19 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 构造 - * + * * @param capacity 初始大小 - * @param config JSON配置项 + * @param config JSON配置项,null表示默认配置 * @since 4.1.19 */ public JSONObject(int capacity, JSONConfig config) { + if (null == config) { + config = JSONConfig.create(); + } if (config.isIgnoreCase()) { this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); } else { - this.rawHashMap = config.isOrder() ? new LinkedHashMap<>(capacity) : new HashMap<>(capacity); + this.rawHashMap = MapUtil.newHashMap(config.isOrder()); } this.config = config; } @@ -121,9 +133,10 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为Map,将键值对加入JSON对象
  • *
  • value为JSON字符串(CharSequence),使用JSONTokener解析
  • *
  • value为JSONTokener,直接解析
  • - *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • + *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。 + * 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * + * * @param source JavaBean或者Map对象或者String */ public JSONObject(Object source) { @@ -138,8 +151,8 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为JSONTokener,直接解析
  • *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * - * @param source JavaBean或者Map对象或者String + * + * @param source JavaBean或者Map对象或者String * @param ignoreNullValue 是否忽略空值 * @since 3.0.9 */ @@ -155,10 +168,10 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为JSONTokener,直接解析
  • *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * - * @param source JavaBean或者Map对象或者String + * + * @param source JavaBean或者Map对象或者String * @param ignoreNullValue 是否忽略空值,如果source为JSON字符串,不忽略空值 - * @param isOrder 是否有序 + * @param isOrder 是否有序 * @since 4.2.2 */ public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { @@ -175,11 +188,11 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为JSONTokener,直接解析
  • *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * + *

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

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

    * KEY或VALUE任意一个为null则不加入,字段不存在也不加入
    * 若names列表为空,则字段全部加入 * - * @param obj 包含需要字段的Bean对象或者Map对象 + * @param obj 包含需要字段的Bean对象或者Map对象 * @param names 需要构建JSONObject的字段名列表 */ public JSONObject(Object obj, String... names) { @@ -230,9 +243,9 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 从JSON字符串解析为JSON对象,对于排序单独配置参数 * - * @param source 以大括号 {} 包围的字符串,其中KEY和VALUE使用 : 分隔,每个键值对使用逗号分隔 + * @param source 以大括号 {} 包围的字符串,其中KEY和VALUE使用 : 分隔,每个键值对使用逗号分隔 * @param isOrder 是否有序 - * @exception JSONException JSON字符串语法错误 + * @throws JSONException JSON字符串语法错误 * @since 4.2.2 */ public JSONObject(CharSequence source, boolean isOrder) throws JSONException { @@ -241,19 +254,14 @@ public class JSONObject implements JSON, JSONGetter, Map // -------------------------------------------------------------------------------------------------------------------- Constructor end - /** - * 获取JSON配置 - * - * @return {@link JSONConfig} - * @since 4.3.1 - */ + @Override public JSONConfig getConfig() { return this.config; } /** * 设置转为字符串时的日期格式,默认为时间戳(null值) - * + * * @param format 格式,null表示使用时间戳 * @return this * @since 4.1.19 @@ -279,7 +287,7 @@ public class JSONObject implements JSON, JSONGetter, Map for (String name : names) { value = this.get(name); if (null != value) { - ja.put(value); + ja.set(value); } } return ja; @@ -332,14 +340,16 @@ public class JSONObject implements JSON, JSONGetter, Map } /** - * PUT 键值对到JSONObject中,如果值为null,将此键移除 + * PUT 键值对到JSONObject中,在忽略null模式下,如果值为null,将此键移除 * - * @param key 键 + * @param key 键 * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. * @return this. * @throws JSONException 值是无穷数字抛出此异常 + * @deprecated 此方法存在歧义,原Map接口返回的是之前的值,重写后返回this了,未来版本此方法会修改,请使用{@link #set(String, Object)} */ @Override + @Deprecated public JSONObject put(String key, Object value) throws JSONException { if (null == key) { return this; @@ -356,16 +366,29 @@ public class JSONObject implements JSON, JSONGetter, Map return this; } + /** + * 设置键值对到JSONObject中,在忽略null模式下,如果值为null,将此键移除 + * + * @param key 键 + * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @return this. + * @throws JSONException 值是无穷数字抛出此异常 + */ + public JSONObject set(String key, Object value) throws JSONException { + put(key, value); + return this; + } + /** * 一次性Put 键值对,如果key已经存在抛出异常,如果键值中有null值,忽略 * - * @param key 键 + * @param key 键 * @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. * @return this. * @throws JSONException 值是无穷数字、键重复抛出异常 */ public JSONObject putOnce(String key, Object value) throws JSONException { - if (key != null && value != null) { + if (key != null) { if (rawHashMap.containsKey(key)) { throw new JSONException("Duplicate key \"{}\"", key); } @@ -377,7 +400,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 在键和值都为非空的情况下put到JSONObject中 * - * @param key 键 + * @param key 键 * @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. * @return this. * @throws JSONException 值是无穷数字 @@ -400,7 +423,7 @@ public class JSONObject implements JSON, JSONGetter, Map * 积累值。类似于put,当key对应value已经存在时,与value组成新的JSONArray.
    * 如果只有一个值,此值就是value,如果多个值,则是添加到新的JSONArray中 * - * @param key 键 + * @param key 键 * @param value 被积累的值 * @return this. * @throws JSONException 如果给定键为null或者键对应的值存在且为非JSONArray @@ -409,11 +432,11 @@ public class JSONObject implements JSON, JSONGetter, Map InternalJSONUtil.testValidity(value); Object object = this.getObj(key); if (object == null) { - this.put(key, value instanceof JSONArray ? new JSONArray().put(value) : value); + this.put(key, value instanceof JSONArray ? new JSONArray().set(value) : value); } else if (object instanceof JSONArray) { - ((JSONArray) object).put(value); + ((JSONArray) object).set(value); } else { - this.put(key, new JSONArray().put(object).put(value)); + this.set(key, new JSONArray().set(object).set(value)); } return this; } @@ -421,7 +444,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 追加值,如果key无对应值,就添加一个JSONArray,其元素只有value,如果值已经是一个JSONArray,则添加到值JSONArray中。 * - * @param key 键 + * @param key 键 * @param value 值 * @return this. * @throws JSONException 如果给定键为null或者键对应的值存在且为非JSONArray @@ -430,9 +453,9 @@ public class JSONObject implements JSON, JSONGetter, Map InternalJSONUtil.testValidity(value); Object object = this.getObj(key); if (object == null) { - this.put(key, new JSONArray().put(value)); + this.set(key, new JSONArray().set(value)); } else if (object instanceof JSONArray) { - this.put(key, ((JSONArray) object).put(value)); + this.set(key, ((JSONArray) object).set(value)); } else { throw new JSONException("JSONObject [" + key + "] is not a JSONArray."); } @@ -478,19 +501,16 @@ public class JSONObject implements JSON, JSONGetter, Map rawHashMap.clear(); } - @SuppressWarnings("NullableProblems") @Override public Set keySet() { return this.rawHashMap.keySet(); } - @SuppressWarnings("NullableProblems") @Override public Collection values() { return rawHashMap.values(); } - @SuppressWarnings("NullableProblems") @Override public Set> entrySet() { return rawHashMap.entrySet(); @@ -531,11 +551,7 @@ public class JSONObject implements JSON, JSONGetter, Map */ @Override public String toString() { - try { - return this.toJSONString(0); - } catch (Exception e) { - return null; - } + return this.toJSONString(0); } @Override @@ -548,12 +564,13 @@ public class JSONObject implements JSON, JSONGetter, Map } // ------------------------------------------------------------------------------------------------- Private method start + /** * 将JSON内容写入Writer - * - * @param writer writer + * + * @param writer writer * @param indentFactor 缩进因子,定义每一级别增加的缩进量 - * @param indent 本级别缩进量 + * @param indent 本级别缩进量 * @return Writer * @throws JSONException JSON相关异常 */ @@ -599,7 +616,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * Bean对象转Map - * + * * @param bean Bean对象 */ private void populateMap(Object bean) { @@ -643,17 +660,17 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为JSONTokener,直接解析
  • *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * + * * @param source JavaBean或者Map对象或者String */ - @SuppressWarnings({"rawtypes", "unchecked", "StatementWithEmptyBody"}) + @SuppressWarnings({"rawtypes", "unchecked"}) private void init(Object source) { if (null == source) { return; } - + final JSONSerializer serializer = GlobalSerializeMapping.getSerializer(source.getClass()); - if(serializer instanceof JSONObjectSerializer) { + if (serializer instanceof JSONObjectSerializer) { // 自定义序列化 serializer.serialize(this, source); } else if (source instanceof Map) { @@ -667,9 +684,10 @@ public class JSONObject implements JSON, JSONGetter, Map } else if (source instanceof JSONTokener) { // JSONTokener init((JSONTokener) source); - } else if (source instanceof Number) { - // ignore Number - } else { + } else if (source instanceof ResourceBundle) { + // JSONTokener + init((ResourceBundle) source); + } else if (BeanUtil.isReadableBean(source.getClass())) { // 普通Bean this.populateMap(source); } @@ -677,16 +695,37 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 初始化 - * + * + * @param bundle ResourceBundle + * @since 5.3.1 + */ + private void init(ResourceBundle bundle) { + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + if (key != null) { + InternalJSONUtil.propertyPut(this, key, bundle.getString(key)); + } + } + } + + /** + * 初始化,可以判断字符串为JSON或者XML + * * @param source JSON字符串 */ private void init(CharSequence source) { - init(new JSONTokener(StrUtil.trim(source))); + final String jsonStr = StrUtil.trim(source); + if (StrUtil.startWith(jsonStr, '<')) { + // 可能为XML + XML.toJSONObject(this, jsonStr, false); + } + init(new JSONTokener(StrUtil.trim(source), this.config)); } /** * 初始化 - * + * * @param x JSONTokener */ private void init(JSONTokener x) { @@ -696,16 +735,16 @@ public class JSONObject implements JSON, JSONGetter, Map if (x.nextClean() != '{') { throw x.syntaxError("A JSONObject text must begin with '{'"); } - for (;;) { + while (true) { c = x.nextClean(); switch (c) { - case 0: - throw x.syntaxError("A JSONObject text must end with '}'"); - case '}': - return; - default: - x.back(); - key = x.nextValue().toString(); + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); } // The key is followed by ':'. @@ -719,17 +758,17 @@ public class JSONObject implements JSON, JSONGetter, Map // Pairs are separated by ','. switch (x.nextClean()) { - case ';': - case ',': - if (x.nextClean() == '}') { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': return; - } - x.back(); - break; - case '}': - return; - default: - throw x.syntaxError("Expected a ',' or '}'"); + default: + throw x.syntaxError("Expected a ',' or '}'"); } } } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObjectIter.java b/hutool-json/src/main/java/cn/hutool/json/JSONObjectIter.java index 660a8569a..fe27a2467 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObjectIter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObjectIter.java @@ -10,10 +10,10 @@ import java.util.Iterator; */ public class JSONObjectIter implements Iterable { - Iterator iter; + Iterator iterator; - public JSONObjectIter(Iterator iter) { - this.iter = iter; + public JSONObjectIter(Iterator iterator) { + this.iterator = iterator; } @Override @@ -22,17 +22,17 @@ public class JSONObjectIter implements Iterable { @Override public boolean hasNext() { - return iter.hasNext(); + return iterator.hasNext(); } @Override public JSONObject next() { - return (JSONObject) iter.next(); + return (JSONObject) iterator.next(); } @Override public void remove() { - iter.remove(); + iterator.remove(); } }; } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java index 0cca017a5..086c941d4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java @@ -1,5 +1,7 @@ package cn.hutool.json; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -38,7 +40,12 @@ public class JSONTokener { /** * 源 */ - private Reader reader; + private final Reader reader; + + /** + * JSON配置 + */ + private final JSONConfig config; // ------------------------------------------------------------------------------------ Constructor start @@ -46,8 +53,9 @@ public class JSONTokener { * 从Reader中构建 * * @param reader Reader + * @param config JSON配置 */ - public JSONTokener(Reader reader) { + public JSONTokener(Reader reader, JSONConfig config) { this.reader = reader.markSupported() ? reader : new BufferedReader(reader); this.eof = false; this.usePrevious = false; @@ -55,24 +63,27 @@ public class JSONTokener { this.index = 0; this.character = 1; this.line = 1; + this.config = config; } /** * 从InputStream中构建 * * @param inputStream InputStream + * @param config JSON配置 */ - public JSONTokener(InputStream inputStream) throws JSONException { - this(new InputStreamReader(inputStream)); + public JSONTokener(InputStream inputStream, JSONConfig config) throws JSONException { + this(new InputStreamReader(inputStream), config); } /** * 从字符串中构建 * - * @param s JSON字符串 + * @param s JSON字符串 + * @param config JSON配置 */ - public JSONTokener(String s) { - this(new StringReader(s)); + public JSONTokener(CharSequence s, JSONConfig config) { + this(new StringReader(StrUtil.str(s)), config); } // ------------------------------------------------------------------------------------ Constructor end @@ -318,10 +329,10 @@ public class JSONTokener { return this.nextString(c); case '{': this.back(); - return new JSONObject(this); + return new JSONObject(this, this.config); case '[': this.back(); - return new JSONArray(this); + return new JSONArray(this, this.config); } /* @@ -391,7 +402,7 @@ public class JSONTokener { * @return {@link JSONArray} */ public JSONArray toJSONArray() { - JSONArray jsonArray = new JSONArray(); + JSONArray jsonArray = new JSONArray(this.config); if (this.nextClean() != '[') { throw this.syntaxError("A JSONArray text must start with '['"); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 21d741b83..3bdbc3608 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -23,9 +23,8 @@ import java.lang.reflect.Type; import java.nio.charset.Charset; import java.time.temporal.TemporalAccessor; import java.util.Calendar; -import java.util.Collection; import java.util.Date; -import java.util.Enumeration; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; @@ -48,6 +47,17 @@ public final class JSONUtil { return new JSONObject(); } + /** + * 创建JSONObject + * + * @param config JSON配置 + * @return JSONObject + * @since 5.2.5 + */ + public static JSONObject createObj(JSONConfig config) { + return new JSONObject(config); + } + /** * 创建 JSONArray * @@ -57,6 +67,17 @@ public final class JSONUtil { return new JSONArray(); } + /** + * 创建 JSONArray + * + * @param config JSON配置 + * @return JSONArray + * @since 5.2.5 + */ + public static JSONArray createArray(JSONConfig config) { + return new JSONArray(config); + } + /** * JSON字符串转JSONObject对象 * @@ -78,6 +99,19 @@ public final class JSONUtil { return new JSONObject(obj); } + /** + * JSON字符串转JSONObject对象
    + * 此方法会忽略空值,但是对JSON字符串不影响 + * + * @param obj Bean对象或者Map + * @param config JSON配置 + * @return JSONObject + * @since 5.3.1 + */ + public static JSONObject parseObj(Object obj, JSONConfig config) { + return new JSONObject(obj, config); + } + /** * JSON字符串转JSONObject对象 * @@ -124,6 +158,18 @@ public final class JSONUtil { return new JSONArray(arrayOrCollection); } + /** + * JSON字符串转JSONArray + * + * @param arrayOrCollection 数组或集合对象 + * @param config JSON配置 + * @return JSONArray + * @since 5.3.1 + */ + public static JSONArray parseArray(Object arrayOrCollection, JSONConfig config) { + return new JSONArray(arrayOrCollection, config); + } + /** * JSON字符串转JSONArray * @@ -147,6 +193,22 @@ public final class JSONUtil { * @return JSON */ public static JSON parse(Object obj) { + return parse(obj, JSONConfig.create()); + } + + /** + * 转换对象为JSON
    + * 支持的对象:
    + * String: 转换为相应的对象
    + * Array、Iterable、Iterator:转换为JSONArray
    + * Bean对象:转为JSONObject + * + * @param obj 对象 + * @param config JSON配置 + * @return JSON + * @since 5.3.1 + */ + public static JSON parse(Object obj, JSONConfig config) { if (null == obj) { return null; } @@ -154,17 +216,13 @@ public final class JSONUtil { JSON json; if (obj instanceof JSON) { json = (JSON) obj; - } else if (obj instanceof String) { - String jsonStr = ((String) obj).trim(); - if (jsonStr.startsWith("[")) { - json = parseArray(jsonStr); - } else { - json = parseObj(jsonStr); - } - } else if (obj instanceof Collection || obj.getClass().isArray()) {// 列表 - json = new JSONArray(obj); + } else if (obj instanceof CharSequence) { + final String jsonStr = StrUtil.trim((CharSequence) obj); + json = StrUtil.startWith(jsonStr, '[') ? parseArray(jsonStr) : parseObj(jsonStr); + } else if (obj instanceof Iterable || obj instanceof Iterator || ArrayUtil.isArray(obj)) {// 列表 + json = new JSONArray(obj, config); } else {// 对象 - json = new JSONObject(obj); + json = new JSONObject(obj, config); } return json; @@ -184,8 +242,10 @@ public final class JSONUtil { * Map转化为JSONObject * * @param map {@link Map} - * @return JSONObject + * @return JSONObjec + * @deprecated 请直接使用 {@link #parseObj(Object)} */ + @Deprecated public static JSONObject parseFromMap(Map map) { return new JSONObject(map); } @@ -195,17 +255,11 @@ public final class JSONUtil { * * @param bundle ResourceBundle文件 * @return JSONObject + * @deprecated 请直接使用 {@link #parseObj(Object)} */ + @Deprecated public static JSONObject parseFromResourceBundle(ResourceBundle bundle) { - JSONObject jsonObject = new JSONObject(); - Enumeration keys = bundle.getKeys(); - while (keys.hasMoreElements()) { - String key = keys.nextElement(); - if (key != null) { - InternalJSONUtil.propertyPut(jsonObject, key, bundle.getString(key)); - } - } - return jsonObject; + return new JSONObject(bundle); } // -------------------------------------------------------------------- Pause end @@ -277,6 +331,19 @@ public final class JSONUtil { return json.toJSONString(0); } + /** + * 转为JSON字符串,并写出到write + * + * @param json JSON + * @param writer Writer + * @since 5.3.3 + */ + public static void toJsonStr(JSON json, Writer writer) { + if (null != json) { + json.write(writer); + } + } + /** * 转为JSON字符串 * @@ -300,12 +367,25 @@ public final class JSONUtil { if (null == obj) { return null; } - if (obj instanceof String) { - return (String) obj; + if (obj instanceof CharSequence) { + return StrUtil.str((CharSequence) obj); } return toJsonStr(parse(obj)); } + /** + * 转换为JSON字符串并写出到writer + * + * @param obj 被转为JSON的对象 + * @param writer Writer + * @since 5.3.3 + */ + public static void toJsonStr(Object obj, Writer writer) { + if (null != obj) { + toJsonStr(parse(obj), writer); + } + } + /** * 转换为格式化后的JSON字符串 * @@ -545,13 +625,13 @@ public final class JSONUtil { } char b; // 前一个字符 - char c = 0; // 当前字符 + char c; // 当前字符 int len = str.length(); if (isWrap) { writer.write('"'); } for (int i = 0; i < len; i++) { - b = c; +// b = c; c = str.charAt(i); switch (c) { case '\\': @@ -559,12 +639,13 @@ public final class JSONUtil { writer.write("\\"); writer.write(c); break; - case '/': - if (b == '<') { - writer.write('\\'); - } - writer.write(c); - break; + //此处转义导致输出不和预期一致 +// case '/': +// if (b == '<') { +// writer.write('\\'); +// } +// writer.write(c); +// break; default: writer.write(escape(c)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/XML.java b/hutool-json/src/main/java/cn/hutool/json/XML.java index 08ccb810a..9687112dc 100644 --- a/hutool-json/src/main/java/cn/hutool/json/XML.java +++ b/hutool-json/src/main/java/cn/hutool/json/XML.java @@ -9,44 +9,109 @@ import java.util.Iterator; /** * 提供静态方法在XML和JSONObject之间转换 - * + * * @author JSON.org */ public class XML { - /** The Character '&'. */ + /** + * The Character '&'. + */ public static final Character AMP = CharUtil.AMP; - /** The Character '''. */ + /** + * The Character '''. + */ public static final Character APOS = CharUtil.SINGLE_QUOTE; - /** The Character '!'. */ + /** + * The Character '!'. + */ public static final Character BANG = '!'; - /** The Character '='. */ + /** + * The Character '='. + */ public static final Character EQ = '='; - /** The Character '>'. */ + /** + * The Character '>'. + */ public static final Character GT = '>'; - /** The Character '<'. */ + /** + * The Character '<'. + */ public static final Character LT = '<'; - /** The Character '?'. */ + /** + * The Character '?'. + */ public static final Character QUEST = '?'; - /** The Character '"'. */ + /** + * The Character '"'. + */ public static final Character QUOT = CharUtil.DOUBLE_QUOTES; - /** The Character '/'. */ + /** + * The Character '/'. + */ public static final Character SLASH = CharUtil.SLASH; + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * Content text may be placed in a "content" member. Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param string The source string. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return toJSONObject(string, false); + } + + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * Content text may be placed in a "content" member. Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document. + * + * @param string The source string. + * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { + return toJSONObject(new JSONObject(), string, keepStrings); + } + + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * + * @param jo JSONObject + * @param string XML字符串 + * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + * @since 5.3.1 + */ + public static JSONObject toJSONObject(JSONObject jo, String string, boolean keepStrings) throws JSONException { + XMLTokener x = new XMLTokener(string, jo.getConfig()); + while (x.more() && x.skipPast("<")) { + parse(x, jo, null, keepStrings); + } + return jo; + } + /** * Scan the content following the named tag, attaching it to the context. - * - * @param x The XMLTokener containing the source string. + * + * @param x The XMLTokener containing the source string. * @param context The JSONObject that will include the new material. - * @param name The tag name. + * @param name The tag name. * @return true if the close tag is processed. * @throws JSONException JSON异常 */ @@ -135,7 +200,7 @@ public class XML { tagName = (String) token; token = null; jsonobject = new JSONObject(); - for (;;) { + for (; ; ) { if (token == null) { token = x.nextToken(); } @@ -169,7 +234,7 @@ public class XML { } else if (token == GT) { // Content, between <...> and - for (;;) { + for (; ; ) { token = x.nextContent(); if (token == null) { if (tagName != null) { @@ -203,43 +268,10 @@ public class XML { } } - /** - * 转换XML为JSONObject - * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 - * Content text may be placed in a "content" member. Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * - * @param string The source string. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown if there is an errors while parsing the string - */ - public static JSONObject toJSONObject(String string) throws JSONException { - return toJSONObject(string, false); - } - - /** - * 转换XML为JSONObject - * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 - * Content text may be placed in a "content" member. Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document. - * - * @param string The source string. - * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown if there is an errors while parsing the string - */ - public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { - JSONObject jo = new JSONObject(); - XMLTokener x = new XMLTokener(string); - while (x.more() && x.skipPast("<")) { - parse(x, jo, null, keepStrings); - } - return jo; - } - /** * 转换JSONObject为XML * Convert a JSONObject into a well-formed, element-normal XML string. - * + * * @param object A JSONObject. * @return A string. * @throws JSONException Thrown if there is an error parsing the string @@ -251,17 +283,17 @@ public class XML { /** * 转换JSONObject为XML * Convert a JSONObject into a well-formed, element-normal XML string. - * - * @param object A JSONObject. + * + * @param object A JSONObject. * @param tagName The optional name of the enclosing tag. * @return A string. * @throws JSONException Thrown if there is an error parsing the string */ public static String toXml(Object object, String tagName) throws JSONException { - if(null == object) { + if (null == object) { return null; } - + StringBuilder sb = new StringBuilder(); JSONArray ja; JSONObject jo; diff --git a/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java b/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java index 79e79b29d..9ed2559f8 100644 --- a/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java @@ -2,18 +2,19 @@ package cn.hutool.json; /** * XML分析器,继承自JSONTokener,提供XML的语法分析 - * + * * @author JSON.org */ public class XMLTokener extends JSONTokener { /** - * The table of entity values. It initially contains Character values for amp, apos, gt, lt, quot. + * The table of entity values. + * It initially contains Character values for amp, apos, gt, lt, quot. */ public static final java.util.HashMap entity; static { - entity = new java.util.HashMap(8); + entity = new java.util.HashMap<>(8); entity.put("amp", XML.AMP); entity.put("apos", XML.APOS); entity.put("gt", XML.GT); @@ -23,16 +24,17 @@ public class XMLTokener extends JSONTokener { /** * Construct an XMLTokener from a string. - * - * @param s A source string. + * + * @param s A source string. + * @param config JSON配置 */ - public XMLTokener(String s) { - super(s); + public XMLTokener(CharSequence s, JSONConfig config) { + super(s, config); } /** * Get the text in the CDATA block. - * + * * @return The string up to the ]]>. * @throws JSONException If the ]]> is not found. */ @@ -40,7 +42,7 @@ public class XMLTokener extends JSONTokener { char c; int i; StringBuilder sb = new StringBuilder(); - for (;;) { + for (; ; ) { c = next(); if (end()) { throw syntaxError("Unclosed CDATA"); @@ -55,7 +57,7 @@ public class XMLTokener extends JSONTokener { } /** - * Get the next XML outer token, trimming whitespace. + * Get the next XML outer token, trimming whitespace. * There are two kinds of tokens: the '>' character which begins a markup tag, and the content text between markup tags. * * @return A string, or a '>' Character, or null if there is no more source text. @@ -74,7 +76,7 @@ public class XMLTokener extends JSONTokener { return XML.LT; } sb = new StringBuilder(); - for (;;) { + for (; ; ) { if (c == '<' || c == 0) { back(); return sb.toString().trim(); @@ -90,14 +92,14 @@ public class XMLTokener extends JSONTokener { /** * Return the next entity. These entities are translated to Characters: & ' > < ". - * + * * @param ampersand An ampersand character. * @return A Character or an entity String if the entity is not recognized. * @throws JSONException If missing ';' in XML entity. */ public Object nextEntity(char ampersand) throws JSONException { StringBuilder sb = new StringBuilder(); - for (;;) { + for (; ; ) { char c = next(); if (Character.isLetterOrDigit(c) || c == '#') { sb.append(Character.toLowerCase(c)); @@ -114,7 +116,7 @@ public class XMLTokener extends JSONTokener { /** * Returns the next XML meta token. This is used for skipping over <!...> and <?...?> structures. - * + * * @return Syntax characters (< > / = ! ?) are returned as Character, and strings and names are returned as Boolean. We don't care what the values actually are. * @throws JSONException 字符串中属性未关闭或XML结构错误抛出此异常。If a string is not properly closed or if the XML is badly structured. */ @@ -142,7 +144,7 @@ public class XMLTokener extends JSONTokener { case '"': case '\'': q = c; - for (;;) { + for (; ; ) { c = next(); if (c == 0) { throw syntaxError("Unterminated string"); @@ -152,7 +154,7 @@ public class XMLTokener extends JSONTokener { } } default: - for (;;) { + for (; ; ) { c = next(); if (Character.isWhitespace(c)) { return Boolean.TRUE; @@ -177,7 +179,7 @@ public class XMLTokener extends JSONTokener { /** * Get the next XML Token. These tokens are found inside of angle brackets. It may be one of these characters: / > = ! ? or it may be a string wrapped in single quotes or double * quotes, or it may be a name. - * + * * @return a String or a Character. * @throws JSONException If the XML is not well formed. */ @@ -210,7 +212,7 @@ public class XMLTokener extends JSONTokener { case '\'': q = c; sb = new StringBuilder(); - for (;;) { + for (; ; ) { c = next(); if (c == 0) { throw syntaxError("Unterminated string"); @@ -229,7 +231,7 @@ public class XMLTokener extends JSONTokener { // Name sb = new StringBuilder(); - for (;;) { + for (; ; ) { sb.append(c); c = next(); if (Character.isWhitespace(c)) { @@ -258,7 +260,7 @@ public class XMLTokener extends JSONTokener { /** * Skip characters until past the requested string. If it is not found, we are left at the end of the source with a result of false. - * + * * @param to A string to skip past. * @return 是否成功skip * @throws JSONException JSON异常 @@ -286,7 +288,7 @@ public class XMLTokener extends JSONTokener { /* We will loop, possibly for all of the remaining characters. */ - for (;;) { + for (; ; ) { j = offset; b = true; diff --git a/hutool-json/src/test/java/cn/hutool/json/CustomSerializeTest.java b/hutool-json/src/test/java/cn/hutool/json/CustomSerializeTest.java index 18e46d590..2473bd8f3 100644 --- a/hutool-json/src/test/java/cn/hutool/json/CustomSerializeTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/CustomSerializeTest.java @@ -11,7 +11,7 @@ public class CustomSerializeTest { @Test public void serializeTest() { - JSONUtil.putSerializer(CustomBean.class, (JSONObjectSerializer) (json, bean) -> json.put("customName", bean.name)); + JSONUtil.putSerializer(CustomBean.class, (JSONObjectSerializer) (json, bean) -> json.set("customName", bean.name)); CustomBean customBean = new CustomBean(); customBean.name = "testName"; diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue867Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue867Test.java new file mode 100644 index 000000000..86058bef2 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue867Test.java @@ -0,0 +1,26 @@ +package cn.hutool.json; + +import cn.hutool.core.annotation.Alias; +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +public class Issue867Test { + + @Test + public void toBeanTest(){ + String json = "{\"abc_1d\":\"123\",\"abc_d\":\"456\",\"abc_de\":\"789\"}"; + Test02 bean = JSONUtil.toBean(JSONUtil.parseObj(json),Test02.class); + Assert.assertEquals("123", bean.getAbc1d()); + Assert.assertEquals("456", bean.getAbcD()); + Assert.assertEquals("789", bean.getAbcDe()); + } + + @Data + static class Test02 { + @Alias("abc_1d") + private String abc1d; + private String abcD; + private String abcDe; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI1F8M2.java b/hutool-json/src/test/java/cn/hutool/json/IssueI1F8M2.java new file mode 100644 index 000000000..5f928a337 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI1F8M2.java @@ -0,0 +1,38 @@ +package cn.hutool.json; + +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.time.LocalDateTime; + +/** + * https://gitee.com/loolly/dashboard/issues?id=I1F8M2 + */ +public class IssueI1F8M2 { + @Test + public void toBeanTest() { + String jsonStr = "{\"eventType\":\"fee\",\"fwdAlertingTime\":\"2020-04-22 16:34:13\",\"fwdAnswerTime\":\"\"}"; + Param param = JSONUtil.toBean(jsonStr, Param.class); + Assert.assertEquals("2020-04-22T16:34:13", param.getFwdAlertingTime().toString()); + Assert.assertNull(param.getFwdAnswerTime()); + } + + // Param类的字段 + @Data + static class Param { + /** + * fee表示话单事件 + */ + private String eventType; + /** + * 转接呼叫后振铃时间 + */ + private LocalDateTime fwdAlertingTime; + /** + * 转接呼叫后应答时间 + */ + private LocalDateTime fwdAnswerTime; + + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java b/hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java new file mode 100644 index 000000000..5004d2fac --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java @@ -0,0 +1,40 @@ +package cn.hutool.json; + +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +/** + * 测试同一对象作为对象的字段是否会有null的问题, + * 此问题原来出在BeanCopier中,判断循环引用使用了equals,并不严谨。 + * 修复后使用==判断循环引用。 + */ +public class IssueI1H2VN { + + @Test + public void toBeanTest() { + String jsonStr = "{'conditionsVo':[{'column':'StockNo','value':'abc','type':'='},{'column':'CheckIncoming','value':'1','type':'='}]," + + "'queryVo':{'conditionsVo':[{'column':'StockNo','value':'abc','type':'='},{'column':'CheckIncoming','value':'1','type':'='}],'queryVo':null}}"; + QueryVo vo = JSONUtil.toBean(jsonStr, QueryVo.class); + Assert.assertEquals(2, vo.getConditionsVo().size()); + final QueryVo subVo = vo.getQueryVo(); + Assert.assertNotNull(subVo); + Assert.assertEquals(2, subVo.getConditionsVo().size()); + Assert.assertNull(subVo.getQueryVo()); + } + + @Data + public static class ConditionVo { + private String column; + private String value; + private String type; + } + + @Data + public static class QueryVo { + private List conditionsVo; + private QueryVo queryVo; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java index 9d5b70401..f766bb2e3 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java @@ -1,14 +1,5 @@ package cn.hutool.json; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; @@ -17,6 +8,14 @@ import cn.hutool.core.util.CharsetUtil; import cn.hutool.json.test.bean.Exam; import cn.hutool.json.test.bean.JsonNode; import cn.hutool.json.test.bean.KeyBean; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * JSONArray单元测试 @@ -46,6 +45,17 @@ public class JSONArrayTest { Assert.assertEquals(array.get(0), "value1"); } + @Test + public void parseWithNullTest() { + String jsonStr = "[{\"grep\":\"4.8\",\"result\":\"右\"},{\"grep\":\"4.8\",\"result\":null}]"; + JSONArray jsonArray = JSONUtil.parseArray(jsonStr); + Assert.assertFalse(jsonArray.getJSONObject(1).containsKey("result")); + + // 不忽略null,则null的键值对被保留 + jsonArray = new JSONArray(jsonStr, false); + Assert.assertTrue(jsonArray.getJSONObject(1).containsKey("result")); + } + @Test public void parseFileTest() { JSONArray array = JSONUtil.readJSONArray(FileUtil.file("exam_test.json"), CharsetUtil.CHARSET_UTF_8); @@ -121,6 +131,7 @@ public class JSONArrayTest { String jsonStr = FileUtil.readString("exam_test.json", CharsetUtil.CHARSET_UTF_8); JSONArray array = JSONUtil.parseArray(jsonStr); + //noinspection SuspiciousToArrayCall Exam[] list = array.toArray(new Exam[0]); Assert.assertNotEquals(0, list.length); Assert.assertEquals(Exam.class, list[0].getClass()); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index cd870504f..a27f8f2cf 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -1,17 +1,7 @@ package cn.hutool.json; -import java.math.BigDecimal; -import java.util.Date; -import java.util.List; -import java.util.Objects; - import cn.hutool.core.annotation.Alias; import cn.hutool.core.collection.CollUtil; -import lombok.Data; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; @@ -30,6 +20,15 @@ import cn.hutool.json.test.bean.UserWithMap; import cn.hutool.json.test.bean.report.CaseReport; import cn.hutool.json.test.bean.report.StepReport; import cn.hutool.json.test.bean.report.SuiteReport; +import lombok.Data; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Objects; /** * JSONObject单元测试 @@ -61,32 +60,33 @@ public class JSONObjectTest { @Test public void toStringTest3() { JSONObject json = Objects.requireNonNull(JSONUtil.createObj()// - .put("dateTime", DateUtil.parse("2019-05-02 22:12:01")))// + .set("dateTime", DateUtil.parse("2019-05-02 22:12:01")))// .setDateFormat(DatePattern.NORM_DATE_PATTERN); Assert.assertEquals("{\"dateTime\":\"2019-05-02\"}", json.toString()); } @Test public void toStringWithDateTest() { - JSONObject json = JSONUtil.createObj().put("date", DateUtil.parse("2019-05-08 19:18:21")); + JSONObject json = JSONUtil.createObj().set("date", DateUtil.parse("2019-05-08 19:18:21")); assert json != null; Assert.assertEquals("{\"date\":1557314301000}", json.toString()); - json = Objects.requireNonNull(JSONUtil.createObj().put("date", DateUtil.parse("2019-05-08 19:18:21"))).setDateFormat(DatePattern.NORM_DATE_PATTERN); + json = Objects.requireNonNull(JSONUtil.createObj().set("date", DateUtil.parse("2019-05-08 19:18:21"))).setDateFormat(DatePattern.NORM_DATE_PATTERN); Assert.assertEquals("{\"date\":\"2019-05-08\"}", json.toString()); } + @Test public void putAllTest() { - JSONObject json1 = JSONUtil.createObj(); - json1.put("a", "value1"); - json1.put("b", "value2"); - json1.put("c", "value3"); - json1.put("d", true); + JSONObject json1 = JSONUtil.createObj() + .set("a", "value1") + .set("b", "value2") + .set("c", "value3") + .set("d", true); - JSONObject json2 = JSONUtil.createObj(); - json2.put("a", "value21"); - json2.put("b", "value22"); + JSONObject json2 = JSONUtil.createObj() + .set("a", "value21") + .set("b", "value22"); // putAll操作会覆盖相同key的值,因此a,b两个key的值改变,c的值不变 json1.putAll(json2); @@ -124,6 +124,14 @@ public class JSONObjectTest { Assert.assertEquals("体”、“文", json.getStr("test")); } + @Test + public void parseStringTest4() { + String jsonStr = "{'msg':'这里还没有内容','data':{'cards':[]},'ok':0}"; + JSONObject json = new JSONObject(jsonStr); + Assert.assertEquals(new Integer(0), json.getInt("ok")); + Assert.assertEquals(new JSONArray(), json.getJSONObject("data").getJSONArray("cards")); + } + @Test @Ignore public void parseStringWithBomTest() { @@ -134,13 +142,23 @@ public class JSONObjectTest { Console.log(json2); } - @SuppressWarnings("ConstantConditions") + @Test + public void parseStringWithSlashTest() { + //在5.3.2之前,中的/会被转义,修复此bug的单元测试 + String jsonStr = "{\"a\":\"
    aaa
    \"}"; + JSONObject json = new JSONObject(jsonStr); + Assert.assertEquals("
    aaa
    ", json.get("a")); + Assert.assertEquals(jsonStr, json.toString()); + } + @Test public void toBeanTest() { - JSONObject subJson = JSONUtil.createObj().put("value1", "strValue1").put("value2", "234"); - JSONObject json = JSONUtil.createObj().put("strValue", "strTest").put("intValue", 123) + JSONObject subJson = JSONUtil.createObj().set("value1", "strValue1").set("value2", "234"); + JSONObject json = JSONUtil.createObj().set("strValue", "strTest").set("intValue", 123) // 测试空字符串转对象 - .put("doubleValue", "").put("beanValue", subJson).put("list", JSONUtil.createArray().put("a").put("b")).put("testEnum", "TYPE_A"); + .set("doubleValue", "") + .set("beanValue", subJson) + .set("list", JSONUtil.createArray().set("a").set("b")).set("testEnum", "TYPE_A"); TestBean bean = json.toBean(TestBean.class); Assert.assertEquals("a", bean.getList().get(0)); @@ -153,14 +171,13 @@ public class JSONObjectTest { Assert.assertEquals(TestEnum.TYPE_A, bean.getTestEnum()); } - @SuppressWarnings("ConstantConditions") @Test public void toBeanNullStrTest() { JSONObject json = JSONUtil.createObj()// - .put("strValue", "null")// - .put("intValue", 123)// - .put("beanValue", "null")// - .put("list", JSONUtil.createArray().put("a").put("b")); + .set("strValue", "null")// + .set("intValue", 123)// + .set("beanValue", "null")// + .set("list", JSONUtil.createArray().set("a").set("b")); TestBean bean = json.toBean(TestBean.class); // 当JSON中为字符串"null"时应被当作字符串处理 @@ -186,12 +203,11 @@ public class JSONObjectTest { } @Test - public void toBeanTest3() { + public void toBeanWithNullTest() { String jsonStr = "{'data':{'userName':'ak','password': null}}"; + Console.log(JSONUtil.parseObj(jsonStr)); UserWithMap user = JSONUtil.toBean(JSONUtil.parseObj(jsonStr), UserWithMap.class); - String password = user.getData().get("password"); Assert.assertTrue(user.getData().containsKey("password")); - Assert.assertNull(password); } @Test @@ -222,15 +238,14 @@ public class JSONObjectTest { /** * 在JSON转Bean过程中,Bean中字段如果为父类定义的泛型类型,则应正确转换,此方法用于测试这类情况 */ - @SuppressWarnings("ConstantConditions") @Test public void toBeanTest6() { JSONObject json = JSONUtil.createObj() - .put("targetUrl", "http://test.com") - .put("success", "true") - .put("result", JSONUtil.createObj() - .put("token", "tokenTest") - .put("userId", "测试用户1")); + .set("targetUrl", "http://test.com") + .set("success", "true") + .set("result", JSONUtil.createObj() + .set("token", "tokenTest") + .set("userId", "测试用户1")); TokenAuthWarp2 bean = json.toBean(TokenAuthWarp2.class); Assert.assertEquals("http://test.com", bean.getTargetUrl()); @@ -260,7 +275,8 @@ public class JSONObjectTest { userA.setDate(new Date()); userA.setSqs(CollectionUtil.newArrayList(new Seq(null), new Seq("seq2"))); - JSONObject json = JSONUtil.parseObj(userA, false); + JSONObject json = JSONUtil.parseObj(userA, false, true); + Assert.assertTrue(json.containsKey("a")); Assert.assertTrue(json.getJSONArray("sqs").getJSONObject(0).containsKey("seq")); } @@ -282,10 +298,11 @@ public class JSONObjectTest { Assert.assertEquals(bean.toString(), bean2.toString()); } - @SuppressWarnings("ConstantConditions") @Test public void parseBeanTest3() { - JSONObject json = JSONUtil.createObj().put("code", 22).put("data", "{\"jobId\": \"abc\", \"videoUrl\": \"http://a.com/a.mp4\"}"); + JSONObject json = JSONUtil.createObj() + .set("code", 22) + .set("data", "{\"jobId\": \"abc\", \"videoUrl\": \"http://a.com/a.mp4\"}"); JSONBean bean = json.toBean(JSONBean.class); Assert.assertEquals(22, bean.getCode()); @@ -322,10 +339,12 @@ public class JSONObjectTest { Assert.assertEquals(DateUtil.parse("2018-10-25"), bean.getDate()); } - @SuppressWarnings("ConstantConditions") @Test public void beanTransTest3() { - JSONObject userAJson = JSONUtil.createObj().put("a", "AValue").put("name", "nameValue").put("date", "08:00:00"); + JSONObject userAJson = JSONUtil.createObj() + .set("a", "AValue") + .set("name", "nameValue") + .set("date", "08:00:00"); UserA bean = JSONUtil.toBean(userAJson.toString(), UserA.class); Assert.assertEquals(DateUtil.today() + " 08:00:00", DateUtil.date(bean.getDate()).toString()); } @@ -377,10 +396,9 @@ public class JSONObjectTest { Assert.assertEquals("张三", jsonObject.getStr("name")); Assert.assertEquals(new Integer(35), jsonObject.getInt("age")); - @SuppressWarnings("ConstantConditions") JSONObject json = JSONUtil.createObj() - .put("name", "张三") - .put("age", 35); + .set("name", "张三") + .set("age", 35); final BeanWithAlias bean = JSONUtil.toBean(Objects.requireNonNull(json).toString(), BeanWithAlias.class); Assert.assertEquals("张三", bean.getValue1()); Assert.assertEquals(new Integer(35), bean.getValue2()); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index d661ff278..c6ccf2e85 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -25,6 +25,24 @@ public class JSONUtilTest { Console.log(jsonArray); } + /** + * 数字解析为JSONArray报错 + */ + @Test(expected = JSONException.class) + public void parseNumberTest(){ + JSONArray json = JSONUtil.parseArray(123L); + Console.log(json); + } + + /** + * 数字解析为JSONObject忽略 + */ + @Test + public void parseNumberTest2(){ + JSONObject json = JSONUtil.parseObj(123L); + Assert.assertEquals(new JSONObject(), json); + } + @Test public void toJsonStrTest() { UserA a1 = new UserA(); @@ -67,19 +85,19 @@ public class JSONUtilTest { public void toJsonStrTest3() { // 验证某个字段为JSON字符串时转义是否规范 JSONObject object = new JSONObject(true); - object.put("name", "123123"); - object.put("value", "\\"); - object.put("value2", " map = MapUtil.newHashMap(); map.put("user", object.toString()); JSONObject json = JSONUtil.parseObj(map); - Assert.assertEquals("{\"name\":\"123123\",\"value\":\"\\\\\",\"value2\":\"<\\/\"}", json.get("user")); - Assert.assertEquals("{\"user\":\"{\\\"name\\\":\\\"123123\\\",\\\"value\\\":\\\"\\\\\\\\\\\",\\\"value2\\\":\\\"<\\\\/\\\"}\"}", json.toString()); + Assert.assertEquals("{\"name\":\"123123\",\"value\":\"\\\\\",\"value2\":\" implements Serializable { * @param message the message * @param result the result */ - ResultDto(int code, String message, T result) { + public ResultDto(int code, String message, T result) { super(); this.code(code).message(message).result(result); } diff --git a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserA.java b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserA.java index fa8b5e626..748944654 100644 --- a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserA.java +++ b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserA.java @@ -1,40 +1,14 @@ package cn.hutool.json.test.bean; +import lombok.Data; + import java.util.Date; import java.util.List; +@Data public class UserA { private String name; private String a; private Date date; private List sqs; - - public String getName() { - return name; - } - public void setName(String name) { - this.name = name; - } - public String getA() { - return a; - } - public void setA(String a) { - this.a = a; - } - public Date getDate() { - return date; - } - public void setDate(Date date) { - this.date = date; - } - public List getSqs() { - return sqs; - } - public void setSqs(List sqs) { - this.sqs = sqs; - } - @Override - public String toString() { - return "UserA [name=" + name + ", a=" + a + ", date=" + date + ", sqs=" + sqs + "]"; - } } diff --git a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java index aa5965b6c..4ed56d226 100644 --- a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java +++ b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java @@ -1,15 +1,10 @@ package cn.hutool.json.test.bean; +import lombok.Data; + import java.util.Map; +@Data public class UserWithMap { private Map data; - - public Map getData() { - return data; - } - - public void setData(Map data) { - this.data = data; - } } diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index e550aae74..f107727be 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-log @@ -21,7 +21,7 @@ 1.7.26 1.3.0-alpha5 1.2.17 - 2.13.0 + 2.13.1 1.2 1.3.6 3.4.1.Final diff --git a/hutool-log/src/main/java/cn/hutool/log/GlobalLogFactory.java b/hutool-log/src/main/java/cn/hutool/log/GlobalLogFactory.java index 30712abb7..035ec7a4e 100644 --- a/hutool-log/src/main/java/cn/hutool/log/GlobalLogFactory.java +++ b/hutool-log/src/main/java/cn/hutool/log/GlobalLogFactory.java @@ -1,12 +1,5 @@ package cn.hutool.log; -import cn.hutool.log.dialect.commons.ApacheCommonsLogFactory; -import cn.hutool.log.dialect.console.ConsoleLogFactory; -import cn.hutool.log.dialect.jdk.JdkLogFactory; -import cn.hutool.log.dialect.log4j.Log4jLogFactory; -import cn.hutool.log.dialect.log4j2.Log4j2LogFactory; -import cn.hutool.log.dialect.slf4j.Slf4jLogFactory; - /** * 全局日志工厂类
    * 用于减少日志工厂创建,减少日志库探测 @@ -37,12 +30,12 @@ public class GlobalLogFactory { /** * 自定义日志实现 * - * @see Slf4jLogFactory - * @see Log4jLogFactory - * @see Log4j2LogFactory - * @see ApacheCommonsLogFactory - * @see JdkLogFactory - * @see ConsoleLogFactory + * @see cn.hutool.log.dialect.slf4j.Slf4jLogFactory + * @see cn.hutool.log.dialect.log4j.Log4jLogFactory + * @see cn.hutool.log.dialect.log4j2.Log4j2LogFactory + * @see cn.hutool.log.dialect.commons.ApacheCommonsLogFactory + * @see cn.hutool.log.dialect.jdk.JdkLogFactory + * @see cn.hutool.log.dialect.console.ConsoleLogFactory * * @param logFactoryClass 日志工厂类 * @return 自定义的日志工厂类 @@ -57,14 +50,14 @@ public class GlobalLogFactory { /** * 自定义日志实现 - * - * @see Slf4jLogFactory - * @see Log4jLogFactory - * @see Log4j2LogFactory - * @see ApacheCommonsLogFactory - * @see JdkLogFactory - * @see ConsoleLogFactory - * + * + * @see cn.hutool.log.dialect.slf4j.Slf4jLogFactory + * @see cn.hutool.log.dialect.log4j.Log4jLogFactory + * @see cn.hutool.log.dialect.log4j2.Log4j2LogFactory + * @see cn.hutool.log.dialect.commons.ApacheCommonsLogFactory + * @see cn.hutool.log.dialect.jdk.JdkLogFactory + * @see cn.hutool.log.dialect.console.ConsoleLogFactory + * * @param logFactory 日志工厂类对象 * @return 自定义的日志工厂类 */ diff --git a/hutool-log/src/main/java/cn/hutool/log/LogFactory.java b/hutool-log/src/main/java/cn/hutool/log/LogFactory.java index 80648d667..b374fa4d6 100644 --- a/hutool-log/src/main/java/cn/hutool/log/LogFactory.java +++ b/hutool-log/src/main/java/cn/hutool/log/LogFactory.java @@ -3,14 +3,8 @@ package cn.hutool.log; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.caller.CallerUtil; import cn.hutool.core.util.ServiceLoaderUtil; -import cn.hutool.log.dialect.commons.ApacheCommonsLogFactory; import cn.hutool.log.dialect.console.ConsoleLogFactory; -import cn.hutool.log.dialect.jboss.JbossLogFactory; import cn.hutool.log.dialect.jdk.JdkLogFactory; -import cn.hutool.log.dialect.log4j.Log4jLogFactory; -import cn.hutool.log.dialect.log4j2.Log4j2LogFactory; -import cn.hutool.log.dialect.slf4j.Slf4jLogFactory; -import cn.hutool.log.dialect.tinylog.TinyLogFactory; import java.net.URL; import java.util.Map; @@ -20,14 +14,6 @@ import java.util.concurrent.ConcurrentHashMap; * 日志工厂类 * * @author Looly - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ public abstract class LogFactory { @@ -38,7 +24,7 @@ public abstract class LogFactory { /** * 日志对象缓存 */ - private Map logCache; + private final Map logCache; /** * 构造 @@ -67,12 +53,7 @@ public abstract class LogFactory { * @return 日志对象 */ public Log getLog(String name) { - Log log = logCache.get(name); - if (null == log) { - log = createLog(name); - logCache.put(name, log); - } - return log; + return logCache.computeIfAbsent(name, o -> createLog((String)o)); } /** @@ -82,12 +63,7 @@ public abstract class LogFactory { * @return 日志对象 */ public Log getLog(Class clazz) { - Log log = logCache.get(clazz); - if (null == log) { - log = createLog(clazz); - logCache.put(clazz, log); - } - return log; + return logCache.computeIfAbsent(clazz, o -> createLog((Class)o)); } /** @@ -131,14 +107,6 @@ public abstract class LogFactory { * * @param logFactoryClass 日志工厂类 * @return 自定义的日志工厂类 - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ public static LogFactory setCurrentLogFactory(Class logFactoryClass) { return GlobalLogFactory.set(logFactoryClass); @@ -149,14 +117,6 @@ public abstract class LogFactory { * * @param logFactory 日志工厂类对象 * @return 自定义的日志工厂类 - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ public static LogFactory setCurrentLogFactory(LogFactory logFactory) { return GlobalLogFactory.set(logFactory); @@ -195,14 +155,6 @@ public abstract class LogFactory { * 依次按照顺序检查日志库的jar是否被引入,如果未引入任何日志库,则检查ClassPath下的logging.properties,存在则使用JdkLogFactory,否则使用ConsoleLogFactory * * @return 日志实现类 - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ public static LogFactory create() { final LogFactory factory = doCreate(); @@ -216,18 +168,10 @@ public abstract class LogFactory { * 依次按照顺序检查日志库的jar是否被引入,如果未引入任何日志库,则检查ClassPath下的logging.properties,存在则使用JdkLogFactory,否则使用ConsoleLogFactory * * @return 日志实现类 - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ private static LogFactory doCreate() { final LogFactory factory = ServiceLoaderUtil.loadFirstAvailable(LogFactory.class); - if(null != factory){ + if (null != factory) { return factory; } diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java index e8f979bbc..4de619a00 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java @@ -19,7 +19,7 @@ public class ConsoleLog extends AbstractLog { private static final String logFormat = "[{date}] [{level}] {name}: {msg}"; private static Level currentLevel = Level.DEBUG; - private String name; + private final String name; //------------------------------------------------------------------------- Constructor diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/slf4j/Slf4jLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/slf4j/Slf4jLog.java index 99a02b3bb..708ec2417 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/slf4j/Slf4jLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/slf4j/Slf4jLog.java @@ -161,7 +161,6 @@ public class Slf4jLog extends AbstractLog { * @param t 异常 * @param msgTemplate 消息模板 * @param arguments 参数 - * @return 是否支持 LocationAwareLogger对象,如果不支持需要日志方法调用被包装类的相应方法 */ private void locationAwareLog(LocationAwareLogger logger, String fqcn, int level_int, Throwable t, String msgTemplate, Object[] arguments) { // ((LocationAwareLogger)this.logger).log(null, fqcn, level_int, msgTemplate, arguments, t); diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java index 9c82d3a32..945527f43 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java @@ -20,8 +20,8 @@ public class TinyLog extends AbstractLog { /** 堆栈增加层数,因为封装因此多了两层,此值用于正确获取当前类名 */ private static final int DEPTH = 4; - private int level; - private String name; + private final int level; + private final String name; // ------------------------------------------------------------------------- Constructor public TinyLog(Class clazz) { diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index de9ae72c0..8917a404d 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelPicUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelPicUtil.java index a5000445f..c1dad24b2 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelPicUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelPicUtil.java @@ -1,9 +1,8 @@ package cn.hutool.poi.excel; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; import org.apache.poi.hssf.usermodel.HSSFClientAnchor; import org.apache.poi.hssf.usermodel.HSSFPicture; import org.apache.poi.hssf.usermodel.HSSFPictureData; @@ -20,9 +19,9 @@ import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Excel图片工具类 @@ -87,7 +86,7 @@ public class ExcelPicUtil { * @return 图片映射,键格式:行_列,值:{@link PictureData} */ private static Map getPicMapXlsx(XSSFWorkbook workbook, int sheetIndex) { - final Map sheetIndexPicMap = new HashMap(); + final Map sheetIndexPicMap = new HashMap<>(); final XSSFSheet sheet = workbook.getSheetAt(sheetIndex); XSSFDrawing drawing; for (POIXMLDocumentPart dr : sheet.getRelations()) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 97908780f..38dc24c43 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -1,12 +1,16 @@ package cn.hutool.poi.excel; -import java.io.File; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Console; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.poi.excel.cell.CellEditor; +import cn.hutool.poi.excel.cell.CellUtil; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.extractor.ExcelExtractor; import org.apache.poi.ss.usermodel.Row; @@ -15,17 +19,12 @@ import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.extractor.XSSFExcelExtractor; import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.IterUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.poi.excel.cell.CellEditor; -import cn.hutool.poi.excel.cell.CellUtil; -import cn.hutool.poi.excel.editors.TrimEditor; +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Excel读取器
    @@ -153,7 +152,6 @@ public class ExcelReader extends ExcelBase { * * @param cellEditor 单元格值处理接口 * @return this - * @see TrimEditor */ public ExcelReader setCellEditor(CellEditor cellEditor) { this.cellEditor = cellEditor; @@ -242,6 +240,7 @@ public class ExcelReader extends ExcelBase { List rowList; for (int i = startRowIndex; i <= endRowIndex; i++) { rowList = readRow(i); + Console.log("### {}: {}", i, rowList); if (CollUtil.isNotEmpty(rowList) || false == ignoreEmptyRow) { if (null == rowList) { rowList = new ArrayList<>(0); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java index 03710ba52..1c8cddd27 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java @@ -15,7 +15,6 @@ import cn.hutool.poi.excel.sax.handler.RowHandler; import java.io.BufferedInputStream; import java.io.File; import java.io.InputStream; -import java.io.OutputStream; /** * Excel工具类 @@ -348,7 +347,7 @@ public class ExcelUtil { // ------------------------------------------------------------------------------------------------ getWriter /** * 获得{@link ExcelWriter},默认写出到第一个sheet
    - * 不传入写出的Excel文件路径,只能调用{@link ExcelWriter#flush(OutputStream)}方法写出到流
    + * 不传入写出的Excel文件路径,只能调用ExcelWriter#flush(OutputStream)方法写出到流
    * 若写出到文件,还需调用{@link ExcelWriter#setDestFile(File)}方法自定义写出的文件,然后调用{@link ExcelWriter#flush()}方法写出到文件 * * @return {@link ExcelWriter} @@ -364,7 +363,7 @@ public class ExcelUtil { /** * 获得{@link ExcelWriter},默认写出到第一个sheet
    - * 不传入写出的Excel文件路径,只能调用{@link ExcelWriter#flush(OutputStream)}方法写出到流
    + * 不传入写出的Excel文件路径,只能调用ExcelWriter#flush(OutputStream)方法写出到流
    * 若写出到文件,还需调用{@link ExcelWriter#setDestFile(File)}方法自定义写出的文件,然后调用{@link ExcelWriter#flush()}方法写出到文件 * * @param isXlsx 是否为xlsx格式 @@ -455,7 +454,7 @@ public class ExcelUtil { // ------------------------------------------------------------------------------------------------ getBigWriter /** * 获得{@link BigExcelWriter},默认写出到第一个sheet
    - * 不传入写出的Excel文件路径,只能调用{@link BigExcelWriter#flush(OutputStream)}方法写出到流
    + * 不传入写出的Excel文件路径,只能调用ExcelWriter#flush(OutputStream)方法写出到流
    * 若写出到文件,还需调用{@link BigExcelWriter#setDestFile(File)}方法自定义写出的文件,然后调用{@link BigExcelWriter#flush()}方法写出到文件 * * @return {@link BigExcelWriter} @@ -471,7 +470,7 @@ public class ExcelUtil { /** * 获得{@link BigExcelWriter},默认写出到第一个sheet
    - * 不传入写出的Excel文件路径,只能调用{@link BigExcelWriter#flush(OutputStream)}方法写出到流
    + * 不传入写出的Excel文件路径,只能调用ExcelWriter#flush(OutputStream)方法写出到流
    * 若写出到文件,还需调用{@link BigExcelWriter#setDestFile(File)}方法自定义写出的文件,然后调用{@link BigExcelWriter#flush()}方法写出到文件 * * @param rowAccessWindowSize 在内存中的行数 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java index cd7e2ee0b..3cec6ba4e 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java @@ -200,7 +200,6 @@ public class ExcelWriter extends ExcelBase { */ public ExcelWriter reset() { resetRow(); - this.aliasComparator = null; this.headLocationCache = null; return this; } @@ -426,6 +425,8 @@ public class ExcelWriter extends ExcelBase { */ public ExcelWriter setHeaderAlias(Map headerAlias) { this.headerAlias = headerAlias; + // 新增别名时清除比较器缓存 + this.aliasComparator = null; return this; } @@ -437,6 +438,8 @@ public class ExcelWriter extends ExcelBase { */ public ExcelWriter clearHeaderAlias() { this.headerAlias = null; + // 清空别名时清除比较器缓存 + this.aliasComparator = null; return this; } @@ -467,6 +470,32 @@ public class ExcelWriter extends ExcelBase { } this.headerAlias = headerAlias; headerAlias.put(name, alias); + // 新增别名时清除比较器缓存 + this.aliasComparator = null; + return this; + } + + /** + * 设置窗口冻结,之前冻结的窗口会被覆盖,如果rowSplit为0表示取消冻结 + * + * @param rowSplit 冻结的行及行数,2表示前两行 + * @return this + * @since 5.2.5 + */ + public ExcelWriter setFreezePane(int rowSplit){ + return setFreezePane(0, rowSplit); + } + + /** + * 设置窗口冻结,之前冻结的窗口会被覆盖,如果colSplit和rowSplit为0表示取消冻结 + * + * @param colSplit 冻结的列及列数,2表示前两列 + * @param rowSplit 冻结的行及行数,2表示前两行 + * @return this + * @since 5.2.5 + */ + public ExcelWriter setFreezePane(int colSplit, int rowSplit){ + getSheet().createFreezePane(colSplit, rowSplit); return this; } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 9a5e08587..cf36d5480 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -1,7 +1,6 @@ package cn.hutool.poi.excel; -import java.io.Serializable; - +import cn.hutool.poi.excel.style.StyleUtil; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.FillPatternType; @@ -11,7 +10,7 @@ import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; -import cn.hutool.poi.excel.style.StyleUtil; +import java.io.Serializable; /** * 样式集合,此样式集合汇集了整个工作簿的样式,用于减少样式的创建和冗余 @@ -23,7 +22,7 @@ public class StyleSet implements Serializable{ private static final long serialVersionUID = 1L; /** 工作簿引用 */ - private Workbook workbook; + private final Workbook workbook; /** 标题样式 */ protected CellStyle headCellStyle; /** 默认样式 */ diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/AttributeName.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/AttributeName.java new file mode 100644 index 000000000..9a51f77eb --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/AttributeName.java @@ -0,0 +1,45 @@ +package cn.hutool.poi.excel.sax; + +import org.xml.sax.Attributes; + +/** + * Excel的XML中属性名枚举 + * + * @author looly + * @since 5.3.6 + */ +public enum AttributeName { + + /** + * 行列号属性,行标签下此为行号属性名,cell标签下下为列号属性名 + */ + r, + /** + * ST(StylesTable) 的索引,样式index,用于获取行或单元格样式 + */ + s, + /** + * Type类型,单元格类型属性,见{@link CellDataType} + */ + t; + + /** + * 是否匹配给定属性 + * + * @param attributeName 属性 + * @return 是否匹配 + */ + public boolean match(String attributeName) { + return this.name().equals(attributeName); + } + + /** + * 从属性里列表中获取对应属性值 + * + * @param attributes 属性列表 + * @return 属性值 + */ + public String getValue(Attributes attributes){ + return attributes.getValue(name()); + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/CellDataType.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/CellDataType.java index 06273dcff..44baadb39 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/CellDataType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/CellDataType.java @@ -15,7 +15,7 @@ public enum CellDataType { FORMULA("str"), /** 富文本类型 */ INLINESTR("inlineStr"), - /** 字符串类型 */ + /** 共享字符串索引类型 */ SSTINDEX("s"), /** 数字类型 */ NUMBER(""), diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ElementName.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ElementName.java new file mode 100644 index 000000000..08614fc8e --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ElementName.java @@ -0,0 +1,28 @@ +package cn.hutool.poi.excel.sax; + +/** + * 标签名枚举 + * + * @author looly + * @since 5.3.6 + */ +public enum ElementName { + /** + * 行标签名,表示一行 + */ + row, + /** + * 单元格标签名,表示一个单元格 + */ + c; + + /** + * 给定标签名是否匹配当前标签 + * + * @param elementName 标签名 + * @return 是否匹配 + */ + public boolean match(String elementName){ + return this.name().equals(elementName); + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java index f3d97ccd3..da8d6152b 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java @@ -18,6 +18,7 @@ import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BlankRecord; import org.apache.poi.hssf.record.BoolErrRecord; import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.LabelRecord; import org.apache.poi.hssf.record.LabelSSTRecord; @@ -45,7 +46,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i /** * 如果为公式,true表示输出公式计算后的结果值,false表示输出公式本身 */ - private boolean isOutputFormulaValues = true; + private final boolean isOutputFormulaValues = true; /** * 用于解析公式 @@ -66,7 +67,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i /** * Sheet边界记录,此Record中可以获得Sheet名 */ - private List boundSheetRecords = new ArrayList<>(); + private final List boundSheetRecords = new ArrayList<>(); private boolean isOutputNextStringRecord; @@ -77,10 +78,10 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i * 自定义需要处理的sheet编号,如果-1表示处理所有sheet */ private int rid = -1; - // 当前表索引 + // 当前rid索引 private int curRid = -1; - private RowHandler rowHandler; + private final RowHandler rowHandler; /** * 构造 @@ -193,7 +194,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i if (record instanceof MissingCellDummyRecord) { // 空值的操作 MissingCellDummyRecord mc = (MissingCellDummyRecord) record; - addToRowCellList(mc.getColumn(), StrUtil.EMPTY); + addToRowCellList(mc); } else if (record instanceof LastCellOfRowDummyRecord) { // 行结束 processLastCell((LastCellOfRowDummyRecord) record); @@ -208,17 +209,40 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i // ---------------------------------------------------------------------------------------------- Private method start /** - * 将单元格数据加入到行列表中 - * @param index 加入位置 - * @param value 值 + * 将空数据加入到行列表中 + * + * @param record MissingCellDummyRecord */ - private void addToRowCellList(int index, Object value){ - while(index > this.rowCellList.size()){ + private void addToRowCellList(MissingCellDummyRecord record) { + addToRowCellList(record.getRow(), record.getColumn(), StrUtil.EMPTY); + } + + /** + * 将单元格数据加入到行列表中 + * + * @param record 单元格 + * @param value 值 + */ + private void addToRowCellList(CellValueRecordInterface record, Object value) { + addToRowCellList(record.getRow(), record.getColumn(), value); + } + + /** + * 将单元格数据加入到行列表中 + * + * @param row 行号 + * @param column 单元格 + * @param value 值 + */ + private void addToRowCellList(int row, int column, Object value) { + while (column > this.rowCellList.size()) { // 对于中间无数据的单元格补齐空白 this.rowCellList.add(StrUtil.EMPTY); + this.rowHandler.handleCell(this.curRid, row, rowCellList.size() - 1, value, null); } - this.rowCellList.add(index, value); + this.rowCellList.add(column, value); + this.rowHandler.handleCell(this.curRid, row, column, value, null); } /** @@ -232,12 +256,12 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i switch (record.getSid()) { case BlankRecord.sid: // 空白记录 - addToRowCellList(((BlankRecord) record).getColumn(), StrUtil.EMPTY); + addToRowCellList(((BlankRecord) record), StrUtil.EMPTY); break; case BoolErrRecord.sid: // 布尔类型 final BoolErrRecord berec = (BoolErrRecord) record; - addToRowCellList(berec.getColumn(), berec.getBooleanValue()); + addToRowCellList(berec, berec.getBooleanValue()); break; case FormulaRecord.sid: // 公式类型 @@ -253,7 +277,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i } else { value = StrUtil.wrap(HSSFFormulaParser.toFormulaString(stubWorkbook, formulaRec.getParsedExpression()), "\""); } - addToRowCellList(formulaRec.getColumn(), value); + addToRowCellList(formulaRec, value); break; case StringRecord.sid: // 单元格中公式的字符串 @@ -266,7 +290,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i case LabelRecord.sid: final LabelRecord lrec = (LabelRecord) record; value = lrec.getValue(); - addToRowCellList(lrec.getColumn(), value); + addToRowCellList(lrec, value); break; case LabelSSTRecord.sid: // 字符串类型 @@ -274,7 +298,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i if (null != sstRecord) { value = sstRecord.getString(lsrec.getSSTIndex()).toString(); } - addToRowCellList(lsrec.getColumn(), ObjectUtil.defaultIfNull(value, StrUtil.EMPTY)); + addToRowCellList(lsrec, ObjectUtil.defaultIfNull(value, StrUtil.EMPTY)); break; case NumberRecord.sid: // 数字类型 final NumberRecord numrec = (NumberRecord) record; @@ -296,7 +320,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i } } // 向容器加入列值 - addToRowCellList(numrec.getColumn(), value); + addToRowCellList(numrec, value); break; default: break; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java index 95a044ad8..8557a08ac 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java @@ -1,6 +1,7 @@ package cn.hutool.poi.excel.sax; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.sax.handler.RowHandler; import cn.hutool.poi.exceptions.POIException; @@ -29,56 +30,39 @@ import java.util.List; */ public class Excel07SaxReader extends AbstractExcelSaxReader implements ContentHandler { - /** - * Cell单元格元素 - */ - private static final String C_ELEMENT = "c"; - /** - * 行元素 - */ - private static final String ROW_ELEMENT = "row"; - /** - * Cell中的行列号(Reference),行模式下此为行号属性名,列模式下为列号属性名 - */ - private static final String R_ATTR = "r"; - /** - * Cell类型 - */ - private static final String T_ELEMENT = "t"; - /** - * SST(SharedStringsTable) 的索引,样式index - */ - private static final String S_ATTR_VALUE = "s"; - // 列中属性值 - private static final String T_ATTR_VALUE = "t"; // sheet r:Id前缀 private static final String RID_PREFIX = "rId"; + // 单元格的格式表,对应style.xml + private StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml private SharedStringsTable sharedStringsTable; - // 当前行 - private int curRow; + // sheet的索引 + private int sheetIndex; + + // 当前非空行 + private int index; // 当前列 private int curCell; - // 上一次的内容 - private StringBuilder lastContent = new StringBuilder(64); // 单元数据类型 private CellDataType cellDataType; + // 当前行号,从0开始 + private long rowNumber; // 当前列坐标, 如A1,B5 private String curCoordinate; // 前一个列的坐标 private String preCoordinate; // 行的最大列坐标 private String maxCellCoordinate; - // 单元格的格式表,对应style.xml - private StylesTable stylesTable; + // 单元格样式 + private XSSFCellStyle xssfCellStyle; // 单元格存储的格式化字符串,nmtFmt的formatCode属性的值 private String numFmtString; - // sheet的索引 - private int sheetIndex; + // 上一次的内容 + private final StrBuilder lastContent = StrUtil.strBuilder(); // 存储每行的列元素 - List rowCellList = new ArrayList<>(); + private List rowCellList = new ArrayList<>(); /** * 行处理器 @@ -140,9 +124,9 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i final XSSFReader xssfReader = new XSSFReader(opcPackage); // 获取共享样式表 - try{ + try { stylesTable = xssfReader.getStylesTable(); - } catch (Exception e){ + } catch (Exception e) { //ignore } // 获取共享字符串表 @@ -159,7 +143,7 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i final Iterator sheetInputStreams = xssfReader.getSheetsData(); while (sheetInputStreams.hasNext()) { // 重新读取一个sheet时行归零 - curRow = 0; + index = 0; this.sheetIndex++; sheetInputStream = sheetInputStreams.next(); ExcelSaxUtil.readFrom(sheetInputStream, this); @@ -182,24 +166,11 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { - // 单元格元素 - if (C_ELEMENT.equals(localName)) { - // 获取当前列坐标 - String tempCurCoordinate = attributes.getValue(R_ATTR); - // 前一列为null,则将其设置为"@",A为第一列,ascii码为65,前一列即为@,ascii码64 - if (preCoordinate == null) { - preCoordinate = String.valueOf(ExcelSaxUtil.CELL_FILL_CHAR); - } else { - // 存在,则前一列要设置为上一列的坐标 - preCoordinate = curCoordinate; - } - // 重置当前列 - curCoordinate = tempCurCoordinate; - // 设置单元格类型 - setCellType(attributes); + if (ElementName.row.match(localName)) {// 行开始 + startRow(attributes); + } else if (ElementName.c.match(localName)) {// 单元格元素 + startCell(attributes); } - - lastContent.setLength(0); } /** @@ -207,42 +178,10 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i */ @Override public void endElement(String uri, String localName, String qName) { - final String contentStr = StrUtil.trim(lastContent); - -// if (T_ELEMENT.equals(qName)) { -// // type标签 -// // rowCellList.add(curCell++, contentStr); -// } else - if (C_ELEMENT.equals(localName)) { - // cell标签 - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); - // 补全单元格之间的空格 - fillBlankCell(preCoordinate, curCoordinate, false); - rowCellList.add(curCell++, value); - } else if (ROW_ELEMENT.equals(localName)) { - // 如果是row标签,说明已经到了一行的结尾 - // 最大列坐标以第一行的为准 - if (curRow == 0) { - maxCellCoordinate = curCoordinate; - } - - // 补全一行尾部可能缺失的单元格 - if (maxCellCoordinate != null) { - fillBlankCell(curCoordinate, maxCellCoordinate, true); - } - - rowHandler.handle(sheetIndex, curRow, rowCellList); - - // 一行结束 - // 新建一个新列,之前的列抛弃(可能被回收或rowHandler处理) - rowCellList = new ArrayList<>(curCell + 1); - // 行数增加 - curRow++; - // 当前列置0 - curCell = 0; - // 置空当前列坐标和前一列坐标 - curCoordinate = null; - preCoordinate = null; + if (ElementName.c.match(localName)) { // 单元格结束 + endCell(); + } else if (ElementName.row.match(localName)) {// 行结束 + endRow(); } } @@ -301,6 +240,89 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i // --------------------------------------------------------------------------------------- Pass method end // --------------------------------------------------------------------------------------- Private method start + + /** + * 行开始 + * + * @param attributes 属性列表 + */ + private void startRow(Attributes attributes) { + this.rowNumber = Long.parseLong(AttributeName.r.getValue(attributes)) - 1; + } + + /** + * 单元格开始 + * + * @param attributes 属性列表 + */ + private void startCell(Attributes attributes) { + // 获取当前列坐标 + final String tempCurCoordinate = AttributeName.r.getValue(attributes); + // 前一列为null,则将其设置为"@",A为第一列,ascii码为65,前一列即为@,ascii码64 + if (preCoordinate == null) { + preCoordinate = String.valueOf(ExcelSaxUtil.CELL_FILL_CHAR); + } else { + // 存在,则前一列要设置为上一列的坐标 + preCoordinate = curCoordinate; + } + // 重置当前列 + curCoordinate = tempCurCoordinate; + // 设置单元格类型 + setCellType(attributes); + + // 清空之前的数据 + lastContent.reset(); + } + + /** + * 一个单元格结尾 + */ + private void endCell() { + final String contentStr = StrUtil.trim(lastContent); + final Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + // 补全单元格之间的空格 + fillBlankCell(preCoordinate, curCoordinate, false); + addCellValue(curCell++, value); + } + + /** + * 一行结尾 + */ + private void endRow() { + // 最大列坐标以第一个非空行的为准 + if (index == 0) { + maxCellCoordinate = curCoordinate; + } + + // 补全一行尾部可能缺失的单元格 + if (maxCellCoordinate != null) { + fillBlankCell(curCoordinate, maxCellCoordinate, true); + } + + rowHandler.handle(sheetIndex, rowNumber, rowCellList); + + // 一行结束 + // 新建一个新列,之前的列抛弃(可能被回收或rowHandler处理) + rowCellList = new ArrayList<>(curCell + 1); + // 行数增加 + index++; + // 当前列置0 + curCell = 0; + // 置空当前列坐标和前一列坐标 + curCoordinate = null; + preCoordinate = null; + } + + /** + * 在一行中的指定列增加值 + * @param index 位置 + * @param value 值 + */ + private void addCellValue(int index, Object value){ + this.rowCellList.add(index, value); + this.rowHandler.handleCell(this.sheetIndex, this.rowNumber, index, value, this.xssfCellStyle); + } + /** * 填充空白单元格,如果前一个单元格大于后一个,不需要填充
    * @@ -315,7 +337,7 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i len++; } while (len-- > 0) { - rowCellList.add(curCell++, ""); + addCellValue(curCell++, ""); } } } @@ -323,19 +345,19 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i /** * 设置单元格的类型 * - * @param attribute 属性 + * @param attributes 属性 */ - private void setCellType(Attributes attribute) { + private void setCellType(Attributes attributes) { // numFmtString的值 numFmtString = ""; - this.cellDataType = CellDataType.of(attribute.getValue(T_ATTR_VALUE)); + this.cellDataType = CellDataType.of(AttributeName.t.getValue(attributes)); // 获取单元格的xf索引,对应style.xml中cellXfs的子元素xf - if(null != this.stylesTable){ - final String xfIndexStr = attribute.getValue(S_ATTR_VALUE); + if (null != this.stylesTable) { + final String xfIndexStr = AttributeName.s.getValue(attributes); if (null != xfIndexStr) { int xfIndex = Integer.parseInt(xfIndexStr); - final XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(xfIndex); + this.xssfCellStyle = stylesTable.getStyleAt(xfIndex); numFmtString = xssfCellStyle.getDataFormatString(); // 单元格存储格式的索引,对应style.xml中的numFmts元素的子元素索引 int numFmtIndex = xssfCellStyle.getDataFormat(); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java index f76f534e7..aff168c36 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java @@ -1,5 +1,7 @@ package cn.hutool.poi.excel.sax.handler; +import org.apache.poi.ss.usermodel.CellStyle; + import java.util.List; /** @@ -9,12 +11,24 @@ import java.util.List; */ @FunctionalInterface public interface RowHandler { - + + /** + * 处理一个单元格的数据 + * @param sheetIndex 当前Sheet序号 + * @param rowIndex 当前行号 + * @param cellIndex 当前列号 + * @param value 单元格的值 + * @param xssfCellStyle 单元格样式 + */ + default void handleCell(int sheetIndex, long rowIndex, int cellIndex, Object value, CellStyle xssfCellStyle){ + //pass + } + /** * 处理一行数据 * @param sheetIndex 当前Sheet序号 - * @param rowIndex 当前行号 + * @param rowIndex 当前行号,从0开始计数 * @param rowList 行数据列表 */ - void handle(int sheetIndex, int rowIndex, List rowList); + void handle(int sheetIndex, long rowIndex, List rowList); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 41d44c485..6ac83eee9 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -28,7 +28,7 @@ public enum PicType { this.value = value; } - private int value; + private final int value; /** * 获取图片类型对应值 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java b/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java index 89492a2a2..0eadaba39 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java @@ -28,7 +28,7 @@ import java.io.OutputStream; */ public class Word07Writer implements Closeable { - private XWPFDocument doc; + private final XWPFDocument doc; /** * 目标文件 */ diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java index 2f3816802..9fe934b08 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java @@ -1,17 +1,16 @@ package cn.hutool.poi.excel.test; -import java.util.List; -import java.util.Map; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.map.MapUtil; import cn.hutool.poi.excel.ExcelReader; import cn.hutool.poi.excel.ExcelUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; +import java.util.Map; /** * Excel读取单元测试 @@ -208,4 +207,5 @@ public class ExcelReadTest { Assert.assertEquals(11L, read.get(1).get(2)); Assert.assertEquals(11L, read.get(2).get(2)); } + } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelSaxReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelSaxReadTest.java index fb3393fcd..e3764c82c 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelSaxReadTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelSaxReadTest.java @@ -7,15 +7,17 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.sax.Excel03SaxReader; import cn.hutool.poi.excel.sax.handler.RowHandler; +import org.apache.poi.ss.usermodel.CellStyle; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.util.List; + /** * Excel sax方式读取 - * - * @author looly * + * @author looly */ public class ExcelSaxReadTest { @@ -64,4 +66,39 @@ public class ExcelSaxReadTest { } }; } + + @Test + @Ignore + public void handle07CellTest() { + ExcelUtil.readBySax("d:/test/test.xlsx", -1, new RowHandler() { + + @Override + public void handleCell(int sheetIndex, long rowIndex, int cellIndex, Object value, CellStyle xssfCellStyle) { + Console.log("{} {} {}", rowIndex, cellIndex, value); + } + + @Override + public void handle(int sheetIndex, long rowIndex, List rowList) { + + } + } + ); + } + + @Test + @Ignore + public void handle03CellTest() { + ExcelUtil.readBySax("d:/test/test.xls", -1, new RowHandler() { + + @Override + public void handleCell(int sheetIndex, long rowIndex, int cellIndex, Object value, CellStyle xssfCellStyle) { + Console.log("{} {} {}", rowIndex, cellIndex, value); + } + + @Override + public void handle(int sheetIndex, long rowIndex, List rowList) { + } + } + ); + } } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java index 9bca94c59..c192efdcc 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java @@ -90,6 +90,8 @@ public class ExcelWriteTest { // 一次性写出内容,使用默认样式 writer.write(rows); writer.autoSizeColumn(0, true); + //冻结前两行 + writer.setFreezePane(0, 2); // 关闭writer,释放内存 writer.close(); } @@ -448,15 +450,26 @@ public class ExcelWriteTest { rows.add(tempList); } ExcelWriter writer = ExcelUtil.getWriter("D:\\test\\multiSheet.xlsx", "正常数据"); + writer.addHeaderAlias("1", "row1"); + writer.addHeaderAlias("3", "row2"); + writer.setOnlyAlias(true); + writer.write(rows, true); writer.autoSizeColumnAll(); + //表2 writer.setSheet("当前重复数据"); + writer.clearHeaderAlias(); + writer.addHeaderAlias("3", "行3"); + writer.addHeaderAlias("1", "行1"); writer.write(rows, true); writer.autoSizeColumnAll(); + + //表3 writer.setSheet("历史重复数据"); writer.write(rows, true); writer.autoSizeColumnAll(); + writer.close(); } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/TestBean.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/TestBean.java index af68870bd..62c8405ba 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/TestBean.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/TestBean.java @@ -1,51 +1,14 @@ package cn.hutool.poi.excel.test; +import lombok.Data; + import java.util.Date; +@Data public class TestBean { private String name; private int age; private double score; private boolean isPass; private Date examDate; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public double getScore() { - return score; - } - - public void setScore(double score) { - this.score = score; - } - - public boolean isPass() { - return isPass; - } - - public void setPass(boolean isPass) { - this.isPass = isPass; - } - - public Date getExamDate() { - return examDate; - } - - public void setExamDate(Date examDate) { - this.examDate = examDate; - } } diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 8125484cc..56df02915 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -1,24 +1,50 @@ - + 4.0.0 jar - + cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-script ${project.artifactId} Hutool 脚本执行封装 - + + + 2.7.2 + 3.0.1 + 3.0.2 + + cn.hutool hutool-core ${project.parent.version} + + org.python + jython + ${jython.version} + provided + + + org.luaj + luaj-jse + ${luaj.version} + provided + + + org.codehaus.groovy + groovy-all + ${groovy.version} + pom + provided + diff --git a/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java b/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java index d6f0b9d46..ca776c9d3 100644 --- a/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java +++ b/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java @@ -1,65 +1,64 @@ package cn.hutool.script; -import java.io.Reader; - import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngineFactory; -import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import java.io.Reader; /** * Javascript引擎类 - * @author Looly * + * @author Looly */ -public class JavaScriptEngine extends FullSupportScriptEngine{ - +public class JavaScriptEngine extends FullSupportScriptEngine { + public JavaScriptEngine() { - super(new ScriptEngineManager().getEngineByName("javascript")); + super(ScriptUtil.createJsEngine()); } - + /** * 引擎实例 + * * @return 引擎实例 */ - public static JavaScriptEngine instance(){ + public static JavaScriptEngine instance() { return new JavaScriptEngine(); } //----------------------------------------------------------------------------------------------- Invocable @Override public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException { - return ((Invocable)engine).invokeMethod(thiz, name, args); + return ((Invocable) engine).invokeMethod(thiz, name, args); } @Override public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException { - return ((Invocable)engine).invokeFunction(name, args); + return ((Invocable) engine).invokeFunction(name, args); } @Override public T getInterface(Class clasz) { - return ((Invocable)engine).getInterface(clasz); + return ((Invocable) engine).getInterface(clasz); } @Override public T getInterface(Object thiz, Class clasz) { - return ((Invocable)engine).getInterface(thiz, clasz); + return ((Invocable) engine).getInterface(thiz, clasz); } //----------------------------------------------------------------------------------------------- Compilable @Override public CompiledScript compile(String script) throws ScriptException { - return ((Compilable)engine).compile(script); + return ((Compilable) engine).compile(script); } @Override public CompiledScript compile(Reader script) throws ScriptException { - return ((Compilable)engine).compile(script); + return ((Compilable) engine).compile(script); } //----------------------------------------------------------------------------------------------- ScriptEngine diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java index fe530ee02..d252a9099 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java @@ -1,10 +1,10 @@ package cn.hutool.script; -import javax.script.ScriptException; - import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.util.StrUtil; +import javax.script.ScriptException; + /** * 脚本运行时异常 * @@ -50,7 +50,6 @@ public class ScriptRuntimeException extends RuntimeException { super(message); this.fileName = fileName; this.lineNumber = lineNumber; - this.columnNumber = -1; } /** diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java index 559a58ea9..7c3061dc1 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java @@ -1,8 +1,12 @@ package cn.hutool.script; +import cn.hutool.core.lang.SimpleCache; +import cn.hutool.core.util.StrUtil; + import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; +import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; @@ -10,84 +14,225 @@ import javax.script.ScriptException; /** * 脚本工具类 - * - * @author Looly * + * @author Looly */ public class ScriptUtil { + private static final ScriptEngineManager MANAGER = new ScriptEngineManager(); + private static final SimpleCache CACHE = new SimpleCache<>(); + /** - * 获得 {@link ScriptEngine} 实例 - * - * @param name 脚本名称 + * 获得单例的{@link ScriptEngine} 实例 + * + * @param nameOrExtOrMime 脚本名称 * @return {@link ScriptEngine} 实例 */ - public static ScriptEngine getScript(String name) { - return new ScriptEngineManager().getEngineByName(name); + public static ScriptEngine getScript(String nameOrExtOrMime) { + return CACHE.get(nameOrExtOrMime, () -> createScript(nameOrExtOrMime)); } /** - * 获得 Javascript引擎 {@link JavaScriptEngine} - * + * 创建 {@link ScriptEngine} 实例 + * + * @param nameOrExtOrMime 脚本名称 + * @return {@link ScriptEngine} 实例 + * @since 5.2.6 + */ + public static ScriptEngine createScript(String nameOrExtOrMime) { + ScriptEngine engine = MANAGER.getEngineByName(nameOrExtOrMime); + if (null == engine) { + engine = MANAGER.getEngineByExtension(nameOrExtOrMime); + } + if (null == engine) { + engine = MANAGER.getEngineByMimeType(nameOrExtOrMime); + } + if (null == engine) { + throw new NullPointerException(StrUtil.format("Script for [{}] not support !", nameOrExtOrMime)); + } + return engine; + } + + /** + * 获得非单例的 Javascript引擎 {@link JavaScriptEngine} + * * @return {@link JavaScriptEngine} */ public static JavaScriptEngine getJavaScriptEngine() { return new JavaScriptEngine(); } - + /** - * 编译脚本 - * + * 获得单例的JavaScript引擎 + * + * @return Javascript引擎 + * @since 5.2.5 + */ + public static ScriptEngine getJsEngine() { + return getScript("js"); + } + + /** + * 创建新的JavaScript引擎 + * + * @return Javascript引擎 + * @since 5.2.6 + */ + public static ScriptEngine createJsEngine() { + return createScript("js"); + } + + /** + * 获得单例的Python引擎
    + * 需要引入org.python:jython + * + * @return Python引擎 + * @since 5.2.5 + */ + public static ScriptEngine getPythonEngine() { + System.setProperty("python.import.site", "false"); + return getScript("python"); + } + + /** + * 创建Python引擎
    + * 需要引入org.python:jython + * + * @return Python引擎 + * @since 5.2.6 + */ + public static ScriptEngine createPythonEngine() { + System.setProperty("python.import.site", "false"); + return createScript("python"); + } + + /** + * 获得单例的Lua引擎
    + * 需要引入org.luaj:luaj-jse + * + * @return Lua引擎 + * @since 5.2.5 + */ + public static ScriptEngine getLuaEngine() { + return getScript("lua"); + } + + /** + * 创建Lua引擎
    + * 需要引入org.luaj:luaj-jse + * + * @return Lua引擎 + * @since 5.2.6 + */ + public static ScriptEngine createLuaEngine() { + return createScript("lua"); + } + + /** + * 获得单例的Groovy引擎
    + * 需要引入org.codehaus.groovy:groovy-all + * + * @return Groovy引擎 + * @since 5.2.5 + */ + public static ScriptEngine getGroovyEngine() { + return getScript("groovy"); + } + + /** + * 创建Groovy引擎
    + * 需要引入org.codehaus.groovy:groovy-all + * + * @return Groovy引擎 + * @since 5.2.6 + */ + public static ScriptEngine createGroovyEngine() { + return createScript("groovy"); + } + + /** + * 执行Javascript脚本,返回Invocable + * * @param script 脚本内容 - * @return {@link CompiledScript} + * @return 执行结果 + * @throws ScriptRuntimeException 脚本异常 + * @since 5.3.6 + */ + public static Invocable evalInvocable(String script) throws ScriptRuntimeException { + return (Invocable) eval(script); + } + + /** + * 执行Javascript脚本 + * + * @param script 脚本内容 + * @return 执行结果 * @throws ScriptRuntimeException 脚本异常 * @since 3.2.0 */ public static Object eval(String script) throws ScriptRuntimeException { try { - return compile(script).eval(); - } catch (ScriptException e) { - throw new ScriptRuntimeException(e); - } - } - - /** - * 编译脚本 - * - * @param script 脚本内容 - * @param context 脚本上下文 - * @return {@link CompiledScript} - * @throws ScriptRuntimeException 脚本异常 - * @since 3.2.0 - */ - public static Object eval(String script, ScriptContext context) throws ScriptRuntimeException { - try { - return compile(script).eval(context); - } catch (ScriptException e) { - throw new ScriptRuntimeException(e); - } - } - - /** - * 编译脚本 - * - * @param script 脚本内容 - * @param bindings 绑定的参数 - * @return {@link CompiledScript} - * @throws ScriptRuntimeException 脚本异常 - * @since 3.2.0 - */ - public static Object eval(String script, Bindings bindings) throws ScriptRuntimeException { - try { - return compile(script).eval(bindings); + return getJsEngine().eval(script); } catch (ScriptException e) { throw new ScriptRuntimeException(e); } } /** - * 编译脚本 - * + * 执行脚本 + * + * @param script 脚本内容 + * @param context 脚本上下文 + * @return 执行结果 + * @throws ScriptRuntimeException 脚本异常 + * @since 3.2.0 + */ + public static Object eval(String script, ScriptContext context) throws ScriptRuntimeException { + try { + return getJsEngine().eval(script, context); + } catch (ScriptException e) { + throw new ScriptRuntimeException(e); + } + } + + /** + * 执行脚本 + * + * @param script 脚本内容 + * @param bindings 绑定的参数 + * @return 执行结果 + * @throws ScriptRuntimeException 脚本异常 + * @since 3.2.0 + */ + public static Object eval(String script, Bindings bindings) throws ScriptRuntimeException { + try { + return getJsEngine().eval(script, bindings); + } catch (ScriptException e) { + throw new ScriptRuntimeException(e); + } + } + + /** + * 执行JS脚本中的指定方法 + * + * @param script js脚本 + * @param func 方法名 + * @param args 方法参数 + * @return 结果 + * @since 5.3.6 + */ + public static Object invoke(String script, String func, Object... args) { + final Invocable eval = evalInvocable(script); + try { + return eval.invokeFunction(func, args); + } catch (ScriptException | NoSuchMethodException e) { + throw new ScriptRuntimeException(e); + } + } + + /** + * 编译Javascript脚本 + * * @param script 脚本内容 * @return {@link CompiledScript} * @throws ScriptRuntimeException 脚本异常 @@ -95,15 +240,15 @@ public class ScriptUtil { */ public static CompiledScript compile(String script) throws ScriptRuntimeException { try { - return compile(getJavaScriptEngine(), script); + return compile(getJsEngine(), script); } catch (ScriptException e) { throw new ScriptRuntimeException(e); } } /** - * 编译脚本 - * + * 编译Javascript脚本 + * * @param engine 引擎 * @param script 脚本内容 * @return {@link CompiledScript} @@ -111,7 +256,7 @@ public class ScriptUtil { */ public static CompiledScript compile(ScriptEngine engine, String script) throws ScriptException { if (engine instanceof Compilable) { - Compilable compEngine = (Compilable) engine; + final Compilable compEngine = (Compilable) engine; return compEngine.compile(script); } return null; diff --git a/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java b/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java index fda465b73..6fcbf57c3 100644 --- a/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java +++ b/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java @@ -1,12 +1,12 @@ package cn.hutool.script.test; -import javax.script.CompiledScript; -import javax.script.ScriptException; - -import org.junit.Test; - import cn.hutool.script.ScriptRuntimeException; import cn.hutool.script.ScriptUtil; +import org.junit.Test; + +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptException; /** * 脚本单元测试类 @@ -30,4 +30,22 @@ public class ScriptUtilTest { public void evalTest() { ScriptUtil.eval("print('Script test!');"); } + + @Test + public void pythonTest() throws ScriptException { + final ScriptEngine pythonEngine = ScriptUtil.getPythonEngine(); + pythonEngine.eval("print('Hello Python')"); + } + + @Test + public void luaTest() throws ScriptException { + final ScriptEngine engine = ScriptUtil.getLuaEngine(); + engine.eval("print('Hello Lua')"); + } + + @Test + public void groovyTest() throws ScriptException { + final ScriptEngine engine = ScriptUtil.getGroovyEngine(); + engine.eval("println 'Hello Groovy'"); + } } diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index dff1168d2..b8b8aeb00 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-setting diff --git a/hutool-setting/src/main/java/cn/hutool/setting/GroupedMap.java b/hutool-setting/src/main/java/cn/hutool/setting/GroupedMap.java index 59f7f2cbc..4d5b944fe 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/GroupedMap.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/GroupedMap.java @@ -1,18 +1,18 @@ package cn.hutool.setting; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; + import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; - /** * 基于分组的Map
    * 此对象方法线程安全 @@ -226,7 +226,6 @@ public class GroupedMap extends LinkedHashMap keySet() { readLock.lock(); @@ -277,7 +276,6 @@ public class GroupedMap extends LinkedHashMap>> entrySet() { readLock.lock(); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java b/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java index 13adfaf2e..59266129d 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java @@ -1,5 +1,12 @@ package cn.hutool.setting; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -13,13 +20,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; - /** * 分组化的Set集合类
    * 在配置文件中可以用中括号分隔不同的分组,每个分组会放在独立的Set中,用group区别
    @@ -94,9 +94,6 @@ public class GroupedSet extends HashMap> { throw new RuntimeException("Null GroupSet file!"); } final URL url = URLUtil.getURL(configFile); - if (url == null) { - throw new RuntimeException(StrUtil.format("Can not find GroupSet file: [{}]", configFile.getAbsolutePath())); - } this.init(url, charset); } diff --git a/hutool-setting/src/main/java/cn/hutool/setting/Setting.java b/hutool-setting/src/main/java/cn/hutool/setting/Setting.java index 9974619b8..b3990659d 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/Setting.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/Setting.java @@ -1,18 +1,5 @@ package cn.hutool.setting; -import java.io.File; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.nio.file.WatchEvent; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.function.Consumer; - import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; @@ -31,6 +18,19 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; import cn.hutool.setting.dialect.Props; +import java.io.File; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.function.Consumer; + /** * 设置工具类。 用于支持设置(配置)文件
    * BasicSetting用于替换Properties类,提供功能更加强大的配置文件,同时对Properties文件向下兼容 @@ -638,7 +638,6 @@ public class Setting extends AbsSetting implements Map { * * @param m Map */ - @SuppressWarnings("NullableProblems") @Override public void putAll(Map m) { this.groupedMap.putAll(DEFAULT_GROUP, m); @@ -657,7 +656,6 @@ public class Setting extends AbsSetting implements Map { * * @return 默认分组(空分组)中的所有键列表 */ - @SuppressWarnings("NullableProblems") @Override public Set keySet() { return this.groupedMap.keySet(DEFAULT_GROUP); @@ -668,7 +666,6 @@ public class Setting extends AbsSetting implements Map { * * @return 默认分组(空分组)中的所有值列表 */ - @SuppressWarnings("NullableProblems") @Override public Collection values() { return this.groupedMap.values(DEFAULT_GROUP); @@ -679,7 +676,6 @@ public class Setting extends AbsSetting implements Map { * * @return 默认分组(空分组)中的所有键值对列表 */ - @SuppressWarnings("NullableProblems") @Override public Set> entrySet() { return this.groupedMap.entrySet(DEFAULT_GROUP); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java index 5a7f3eea3..542bceeaa 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java @@ -8,7 +8,6 @@ import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.Log; -import cn.hutool.log.LogFactory; import java.io.BufferedReader; import java.io.IOException; @@ -28,7 +27,7 @@ import java.util.Set; * */ public class SettingLoader { - private static Log log = LogFactory.get(); + private static final Log log = Log.get(); /** 注释符号(当有此符号在行首,表示此行为注释) */ private final static char COMMENT_FLAG_PRE = '#'; @@ -38,11 +37,11 @@ public class SettingLoader { private String varRegex = "\\$\\{(.*?)\\}"; /** 本设置对象的字符集 */ - private Charset charset; + private final Charset charset; /** 是否使用变量 */ - private boolean isUseVariable; + private final boolean isUseVariable; /** GroupedMap */ - private GroupedMap groupedMap; + private final GroupedMap groupedMap; /** * 构造 diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java index f7a90358f..44c7ba089 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java @@ -17,8 +17,7 @@ public class SettingUtil { /** * 配置文件缓存 */ - private static Map settingMap = new ConcurrentHashMap<>(); - private static final Object lock = new Object(); + private static final Map SETTING_MAP = new ConcurrentHashMap<>(); /** * 获取当前环境下的配置文件
    @@ -28,10 +27,10 @@ public class SettingUtil { * @return 当前环境下配置文件 */ public static Setting get(String name) { - Setting setting = settingMap.get(name); + Setting setting = SETTING_MAP.get(name); if (null == setting) { - synchronized (lock) { - setting = settingMap.get(name); + synchronized (SettingUtil.class) { + setting = SETTING_MAP.get(name); if (null == setting) { String filePath = name; String extName = FileUtil.extName(filePath); @@ -39,7 +38,7 @@ public class SettingUtil { filePath = filePath + "." + Setting.EXT_NAME; } setting = new Setting(filePath, true); - settingMap.put(name, setting); + SETTING_MAP.put(name, setting); } } } @@ -56,7 +55,6 @@ public class SettingUtil { * @since 5.1.3 */ public static Setting getFirstFound(String... names) { - Setting setting; for (String name : names) { try { return get(name); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java b/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java index b1d1ab434..31cfed5ec 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java @@ -19,7 +19,7 @@ public class PropsUtil { /** * 配置文件缓存 */ - private static Map propsMap = new ConcurrentHashMap<>(); + private static final Map propsMap = new ConcurrentHashMap<>(); private static final Object lock = new Object(); /** @@ -57,7 +57,6 @@ public class PropsUtil { * @return 当前环境下配置文件 */ public static Props getFirstFound(String... names) { - Props props; for (String name : names) { try { return get(name); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/profile/Profile.java b/hutool-setting/src/main/java/cn/hutool/setting/profile/Profile.java index 2505a26bc..a3e0c7495 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/profile/Profile.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/profile/Profile.java @@ -1,14 +1,14 @@ package cn.hutool.setting.profile; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.setting.Setting; + import java.io.Serializable; import java.nio.charset.Charset; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.hutool.setting.Setting; - /** * Profile可以让我们定义一系列的配置信息,然后指定其激活条件。
    * 此类中我们规范一套规则如下:
    @@ -36,7 +36,7 @@ public class Profile implements Serializable { /** 是否使用变量 */ private boolean useVar; /** 配置文件缓存 */ - private Map settingMap = new ConcurrentHashMap<>(); + private final Map settingMap = new ConcurrentHashMap<>(); // -------------------------------------------------------------------------------- Constructor start /** diff --git a/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java b/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java index 29faa349a..817bc4901 100644 --- a/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java +++ b/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java @@ -72,7 +72,6 @@ public class PropsTest { @Test public void toBeanWithNullPrefixTest(){ Props configProp = new Props(); - Boolean isInit = configProp.getBool("isInit"); configProp.setProperty("createTime", Objects.requireNonNull(DateUtil.parse("2020-01-01"))); configProp.setProperty("isInit", true); diff --git a/hutool-setting/src/test/java/cn/hutool/setting/test/SettingUtilTest.java b/hutool-setting/src/test/java/cn/hutool/setting/test/SettingUtilTest.java index d714a7cfb..768f5fbc7 100644 --- a/hutool-setting/src/test/java/cn/hutool/setting/test/SettingUtilTest.java +++ b/hutool-setting/src/test/java/cn/hutool/setting/test/SettingUtilTest.java @@ -21,7 +21,9 @@ public class SettingUtilTest { @Test public void getFirstFoundTest() { - String driver = SettingUtil.getFirstFound("test2", "test").get("demo", "driver"); + //noinspection ConstantConditions + String driver = SettingUtil.getFirstFound("test2", "test") + .get("demo", "driver"); Assert.assertEquals("com.mysql.jdbc.Driver", driver); } } diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index b1be9e927..cf8d45849 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-socket diff --git a/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java b/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java index 7a43611ee..6984fd519 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java @@ -1,8 +1,9 @@ package cn.hutool.socket; -import java.io.Serializable; - import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.RuntimeUtil; + +import java.io.Serializable; /** * Socket通讯配置 @@ -14,7 +15,7 @@ public class SocketConfig implements Serializable{ private static final long serialVersionUID = 1L; /** CPU核心数 */ - private static int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + private static final int CPU_COUNT = RuntimeUtil.getProcessorCount(); /** 共享线程池大小,此线程池用于接收和处理用户连接 */ private int threadPoolSize = CPU_COUNT; diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java index c5c002e3c..6ab08524e 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java @@ -1,5 +1,8 @@ package cn.hutool.socket.nio; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; + import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; @@ -11,9 +14,6 @@ import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; - /** * 基于NIO的Socket服务端实现 * @@ -130,7 +130,7 @@ public abstract class NioServer implements Closeable { } @Override - public void close() throws IOException { + public void close() { IoUtil.close(this.selector); IoUtil.close(this.serverSocketChannel); } @@ -166,6 +166,7 @@ public abstract class NioServer implements Closeable { try { channel.configureBlocking(false); // 注册通道 + //noinspection MagicConstant channel.register(selector, ops.getValue()); } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java index b3fb4a033..66d414bc6 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java @@ -18,7 +18,7 @@ public enum Operation { /** 接受连接操作 */ ACCEPT(SelectionKey.OP_ACCEPT); - private int value; + private final int value; /** * 构造 diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 20d5ff1fd..ef4bb4a1c 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool-system @@ -26,7 +26,7 @@ com.github.oshi oshi-core - 4.4.2 + 5.1.0 provided diff --git a/hutool-system/src/main/java/cn/hutool/system/HostInfo.java b/hutool-system/src/main/java/cn/hutool/system/HostInfo.java index d536251b6..6446a9161 100644 --- a/hutool-system/src/main/java/cn/hutool/system/HostInfo.java +++ b/hutool-system/src/main/java/cn/hutool/system/HostInfo.java @@ -1,10 +1,10 @@ package cn.hutool.system; +import cn.hutool.core.net.NetUtil; + import java.io.Serializable; import java.net.InetAddress; -import cn.hutool.core.net.NetUtil; - /** * 代表当前主机的信息。 */ @@ -16,8 +16,13 @@ public class HostInfo implements Serializable { public HostInfo() { final InetAddress localhost = NetUtil.getLocalhost(); - HOST_NAME = localhost.getHostName(); - HOST_ADDRESS = localhost.getHostAddress(); + if(null != localhost){ + HOST_NAME = localhost.getHostName(); + HOST_ADDRESS = localhost.getHostAddress(); + } else{ + HOST_NAME = null; + HOST_ADDRESS = null; + } } /** diff --git a/hutool-system/src/main/java/cn/hutool/system/JavaInfo.java b/hutool-system/src/main/java/cn/hutool/system/JavaInfo.java index 6776f672e..a74d625ab 100644 --- a/hutool-system/src/main/java/cn/hutool/system/JavaInfo.java +++ b/hutool-system/src/main/java/cn/hutool/system/JavaInfo.java @@ -1,5 +1,6 @@ package cn.hutool.system; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ReUtil; import java.io.Serializable; @@ -7,7 +8,7 @@ import java.io.Serializable; /** * 代表Java Implementation的信息。 */ -public class JavaInfo implements Serializable{ +public class JavaInfo implements Serializable { private static final long serialVersionUID = 1L; private final String JAVA_VERSION = SystemUtil.get("java.version", false); @@ -32,13 +33,11 @@ public class JavaInfo implements Serializable{ /** * 取得当前Java impl.的版本(取自系统属性:java.version)。 - * + * *

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

    * 例如: - * + * *

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

    * 例如: - * + * *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      *
    • 测试JDK 1.2:isJavaVersionAtLeast(120)
    • *
    • 测试JDK 1.2.1:isJavaVersionAtLeast(131)
    • *
    - * - * + * * @param requiredVersion 需要的版本 - * * @return 如果当前Java版本大于或等于指定的版本,则返回true */ public final boolean isJavaVersionAtLeast(int requiredVersion) { @@ -376,7 +344,7 @@ public class JavaInfo implements Serializable{ /** * 将Java Implementation的信息转换成字符串。 - * + * * @return JVM impl.的字符串表示 */ @Override diff --git a/hutool-system/src/main/java/cn/hutool/system/RuntimeInfo.java b/hutool-system/src/main/java/cn/hutool/system/RuntimeInfo.java index ef6b380a3..328992fbc 100644 --- a/hutool-system/src/main/java/cn/hutool/system/RuntimeInfo.java +++ b/hutool-system/src/main/java/cn/hutool/system/RuntimeInfo.java @@ -1,59 +1,64 @@ package cn.hutool.system; -import java.io.Serializable; - import cn.hutool.core.io.FileUtil; +import java.io.Serializable; + /** * 运行时信息,包括内存总大小、已用大小、可用大小等 - * @author looly * + * @author looly */ -public class RuntimeInfo implements Serializable{ +public class RuntimeInfo implements Serializable { private static final long serialVersionUID = 1L; - - private Runtime currentRuntime = Runtime.getRuntime(); - + + private final Runtime currentRuntime = Runtime.getRuntime(); + /** * 获得运行时对象 + * * @return {@link Runtime} */ - public final Runtime getRuntime(){ + public final Runtime getRuntime() { return currentRuntime; } - + /** * 获得JVM最大内存 + * * @return 最大内存 */ - public final long getMaxMemory(){ + public final long getMaxMemory() { return currentRuntime.maxMemory(); } - + /** * 获得JVM已分配内存 + * * @return 已分配内存 */ - public final long getTotalMemory(){ + public final long getTotalMemory() { return currentRuntime.totalMemory(); } - + /** * 获得JVM已分配内存中的剩余空间 + * * @return 已分配内存中的剩余空间 */ - public final long getFreeMemory(){ + public final long getFreeMemory() { return currentRuntime.freeMemory(); } - + /** * 获得JVM最大可用内存 + * * @return 最大可用内存 */ - public final long getUsableMemory(){ + public final long getUsableMemory() { return currentRuntime.maxMemory() - currentRuntime.totalMemory() + currentRuntime.freeMemory(); } - + @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java b/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java new file mode 100644 index 000000000..f2141ef4c --- /dev/null +++ b/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java @@ -0,0 +1,131 @@ +package cn.hutool.system.oshi; + +import java.text.DecimalFormat; + +/** + *

    + *

    2020-05-21 14:19

    + * + * @author Dai Yuanchuan + **/ +public class CpuInfo { + + /** + * cpu核心数 + */ + private Integer cpuNum; + + /** + * CPU总的使用率 + */ + private double toTal; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + /** + * CPU型号信息 + */ + private String cpuModel; + + public CpuInfo() { + } + + public CpuInfo(Integer cpuNum, double toTal, double sys, double used, double wait, double free, String cpuModel) { + this.cpuNum = cpuNum; + this.toTal = toTal; + this.sys = sys; + this.used = used; + this.wait = wait; + this.free = free; + this.cpuModel = cpuModel; + } + + public Integer getCpuNum() { + return cpuNum; + } + + public void setCpuNum(Integer cpuNum) { + this.cpuNum = cpuNum; + } + + public double getToTal() { + return toTal; + } + + public void setToTal(double toTal) { + this.toTal = toTal; + } + + public double getSys() { + return sys; + } + + public void setSys(double sys) { + this.sys = sys; + } + + public double getUsed() { + return used; + } + + public void setUsed(double used) { + this.used = used; + } + + public double getWait() { + return wait; + } + + public void setWait(double wait) { + this.wait = wait; + } + + public double getFree() { + return free; + } + + public void setFree(double free) { + this.free = free; + } + + public String getCpuModel() { + return cpuModel; + } + + public void setCpuModel(String cpuModel) { + this.cpuModel = cpuModel; + } + + @Override + public String toString() { + DecimalFormat format = new DecimalFormat("#.00"); + return "CpuInfo{" + + "cpu核心数=" + cpuNum + + ", CPU总的使用率=" + toTal + + ", CPU系统使用率=" + sys + + ", CPU用户使用率=" + used + + ", CPU当前等待率=" + wait + + ", CPU当前空闲率=" + free + + ", CPU利用率=" + Double.parseDouble(format.format((100 - getFree()))) + + ", CPU型号信息='" + cpuModel + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java b/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java index b44a11101..5497b328e 100644 --- a/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java +++ b/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java @@ -6,8 +6,13 @@ import oshi.hardware.ComputerSystem; import oshi.hardware.GlobalMemory; import oshi.hardware.HWDiskStore; import oshi.hardware.HardwareAbstractionLayer; +import oshi.hardware.NetworkIF; import oshi.hardware.Sensors; import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +import java.text.DecimalFormat; +import java.util.List; /** * Oshi库封装的工具类,通过此工具类,可获取系统、硬件相关信息 @@ -94,8 +99,77 @@ public class OshiUtil { * 获取磁盘相关信息,可能有多个磁盘(包括可移动磁盘等) * * @return 磁盘相关信息 + * @since 5.3.6 */ - public static HWDiskStore[] getDiskStores() { + public static List getDiskStores() { return hardware.getDiskStores(); } + + /** + * 获取网络相关信息,可能多块网卡 + * @return 网络相关信息 + * @since 5.3.6 + */ + public static List getNetworkIFs(){ + return hardware.getNetworkIFs(); + } + + // ------------------------------------------------------------------ cpu + + /** + * 获取系统CPU 系统使用率、用户使用率、利用率等等 相关信息 + * + * @return 系统 CPU 使用率 等信息 + */ + public static CpuInfo getCpuInfo() { + return getCpuInfo(1000); + } + + /** + * 获取系统CPU 系统使用率、用户使用率、利用率等等 相关信息 + * + * @param waitingTime 设置等待时间 + * @return 系统 CPU 使用率 等信息 + */ + public static CpuInfo getCpuInfo(long waitingTime) { + return getCpuInfo(OshiUtil.getProcessor(), waitingTime); + } + + /** + * 获取系统CPU 系统使用率、用户使用率、利用率等等 相关信息 + * + * @param processor {@link CentralProcessor} + * @param waitingTime 设置等待时间 + * @return 系统 CPU 使用率 等信息 + */ + private static CpuInfo getCpuInfo(CentralProcessor processor, long waitingTime) { + CpuInfo cpuInfo = new CpuInfo(); + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + // 这里必须要设置延迟 + Util.sleep(waitingTime); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()]; + long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()]; + long softIrq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; + long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()]; + long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()]; + long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()]; + long ioWait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()]; + long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()]; + long totalCpu = Math.max(user + nice + cSys + idle + ioWait + irq + softIrq + steal, 0); + final DecimalFormat format = new DecimalFormat("#.00"); + cpuInfo.setCpuNum(processor.getLogicalProcessorCount()); + cpuInfo.setToTal(totalCpu); + cpuInfo.setSys(Double.parseDouble(format.format(cSys <= 0 ? 0 : (100d * cSys / totalCpu)))); + cpuInfo.setUsed(Double.parseDouble(format.format(user <= 0 ? 0 : (100d * user / totalCpu)))); + if (totalCpu == 0) { + cpuInfo.setWait(0); + } else { + cpuInfo.setWait(Double.parseDouble(format.format(100d * ioWait / totalCpu))); + } + cpuInfo.setFree(Double.parseDouble(format.format(idle <= 0 ? 0 : (100d * idle / totalCpu)))); + cpuInfo.setCpuModel(processor.toString()); + return cpuInfo; + } } diff --git a/hutool-system/src/test/java/cn/hutool/system/OshiTest.java b/hutool-system/src/test/java/cn/hutool/system/OshiTest.java index ebc638156..7bfee6a89 100644 --- a/hutool-system/src/test/java/cn/hutool/system/OshiTest.java +++ b/hutool-system/src/test/java/cn/hutool/system/OshiTest.java @@ -1,10 +1,10 @@ package cn.hutool.system; +import cn.hutool.system.oshi.CpuInfo; +import cn.hutool.system.oshi.OshiUtil; import org.junit.Assert; import org.junit.Test; -import cn.hutool.system.oshi.OshiUtil; - public class OshiTest { @Test @@ -12,4 +12,10 @@ public class OshiTest { long total = OshiUtil.getMemory().getTotal(); Assert.assertTrue(total > 0); } + + @Test + public void getCupInfo() { + CpuInfo cpuInfo = OshiUtil.getCpuInfo(); + Assert.assertNotNull(cpuInfo); + } } diff --git a/pom.xml b/pom.xml index 020ceffb0..45982c59a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,9 +8,9 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.3.6-SNAPSHOT hutool - 提供丰富的Java工具方法 + Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool