Repository: vipshop/vjtools Branch: master Commit: 2f780edd504e Files: 317 Total size: 1022.4 KB Directory structure: gitextract_3z1lwxqk/ ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── docs/ │ ├── .nojekyll │ ├── README.md │ ├── _coverpage.md │ ├── _sidebar.md │ ├── index.html │ ├── other/ │ │ └── othertools.md │ └── standard/ │ ├── README.md │ ├── _sidebar.md │ ├── ali.md │ ├── chapter01.md │ ├── chapter02.md │ ├── chapter03.md │ ├── chapter04.md │ ├── chapter05.md │ ├── chapter06.md │ ├── chapter07.md │ ├── chapter08.md │ ├── chapter09.md │ ├── chapter10.md │ ├── chapter11.md │ ├── chapter12.md │ ├── merge.bat │ └── merge.sh ├── pom.xml ├── standard/ │ ├── README.md │ ├── formatter/ │ │ ├── README.md │ │ ├── vjtools-code-conventions-eclipse.xml │ │ └── vjtools-code-conventions-idea.xml │ └── sonar-vj/ │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── vip/ │ │ └── vjkit/ │ │ └── sonarvj/ │ │ ├── SonarCheckRegistrar.java │ │ ├── SonarDefinition.java │ │ ├── SonarPlugin.java │ │ ├── SonarRulesList.java │ │ └── checks/ │ │ ├── BadConstantNameCheck.java │ │ ├── CatchUsesExceptionWithContextCheck.java │ │ ├── HardcodedIpCheck.java │ │ ├── MissingCurlyBracesCheck.java │ │ ├── NoSonarCheck.java │ │ ├── OperatorPrecedenceCheck.java │ │ ├── UnusedMethodParameterCheck.java │ │ └── UnusedPrivateFieldCheck.java │ └── resources/ │ └── com/ │ └── vip/ │ └── java/ │ └── rules/ │ ├── S1068_java.html │ ├── S1068_java.json │ ├── S115_java.html │ ├── S115_java.json │ ├── S1166_java.html │ ├── S1166_java.json │ ├── S1172_java.html │ ├── S1172_java.json │ ├── S121_java.html │ ├── S121_java.json │ ├── S1291_java.html │ ├── S1291_java.json │ ├── S1313_java.html │ ├── S1313_java.json │ ├── S864_java.html │ └── S864_java.json ├── vjdump/ │ ├── README.md │ ├── README_EN.md │ └── vjdump.sh ├── vjkit/ │ ├── README.md │ ├── docs/ │ │ ├── data_masking.md │ │ └── direct_3rd.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── vip/ │ │ │ └── vjtools/ │ │ │ └── vjkit/ │ │ │ ├── base/ │ │ │ │ ├── BooleanUtil.java │ │ │ │ ├── EnumUtil.java │ │ │ │ ├── ExceptionUtil.java │ │ │ │ ├── MoreValidate.java │ │ │ │ ├── ObjectUtil.java │ │ │ │ ├── Platforms.java │ │ │ │ ├── PropertiesUtil.java │ │ │ │ ├── RuntimeUtil.java │ │ │ │ ├── SystemPropertiesUtil.java │ │ │ │ ├── ValueValidator.java │ │ │ │ ├── annotation/ │ │ │ │ │ ├── NotNull.java │ │ │ │ │ ├── Nullable.java │ │ │ │ │ └── VisibleForTesting.java │ │ │ │ └── type/ │ │ │ │ ├── CloneableException.java │ │ │ │ ├── CloneableRuntimeException.java │ │ │ │ ├── Pair.java │ │ │ │ ├── Triple.java │ │ │ │ └── UncheckedException.java │ │ │ ├── collection/ │ │ │ │ ├── ArrayUtil.java │ │ │ │ ├── CollectionUtil.java │ │ │ │ ├── ListUtil.java │ │ │ │ ├── MapUtil.java │ │ │ │ ├── QueueUtil.java │ │ │ │ ├── SetUtil.java │ │ │ │ └── type/ │ │ │ │ ├── ConcurrentHashSet.java │ │ │ │ ├── MoreLists.java │ │ │ │ ├── MoreMaps.java │ │ │ │ ├── MoreQueues.java │ │ │ │ ├── SortedArrayList.java │ │ │ │ └── primitive/ │ │ │ │ ├── IntObjectHashMap.java │ │ │ │ ├── IntObjectMap.java │ │ │ │ ├── LongObjectHashMap.java │ │ │ │ └── LongObjectMap.java │ │ │ ├── concurrent/ │ │ │ │ ├── Concurrents.java │ │ │ │ ├── ThreadDumpper.java │ │ │ │ ├── ThreadUtil.java │ │ │ │ ├── jsr166e/ │ │ │ │ │ ├── LongAdder.java │ │ │ │ │ └── Striped64.java │ │ │ │ ├── limiter/ │ │ │ │ │ ├── RateLimiterUtil.java │ │ │ │ │ ├── Sampler.java │ │ │ │ │ └── TimeIntervalLimiter.java │ │ │ │ ├── threadpool/ │ │ │ │ │ ├── AbortPolicyWithReport.java │ │ │ │ │ ├── QueuableCachedThreadPool.java │ │ │ │ │ ├── ThreadPoolBuilder.java │ │ │ │ │ └── ThreadPoolUtil.java │ │ │ │ └── type/ │ │ │ │ ├── BasicFuture.java │ │ │ │ └── ThreadLocalContext.java │ │ │ ├── datamasking/ │ │ │ │ ├── DataMask.java │ │ │ │ ├── DataMaskJsonFilter.java │ │ │ │ ├── MaskMapping.java │ │ │ │ ├── MaskStrategy.java │ │ │ │ ├── Sensitive.java │ │ │ │ ├── SensitiveType.java │ │ │ │ └── strategy/ │ │ │ │ ├── EmailMask.java │ │ │ │ ├── HashMask.java │ │ │ │ ├── NameMask.java │ │ │ │ └── PartMask.java │ │ │ ├── id/ │ │ │ │ └── IdUtil.java │ │ │ ├── io/ │ │ │ │ ├── FilePathUtil.java │ │ │ │ ├── FileTreeWalker.java │ │ │ │ ├── FileUtil.java │ │ │ │ ├── IOUtil.java │ │ │ │ ├── ResourceUtil.java │ │ │ │ ├── URLResourceUtil.java │ │ │ │ └── type/ │ │ │ │ └── StringBuilderWriter.java │ │ │ ├── logging/ │ │ │ │ └── PerformanceUtil.java │ │ │ ├── mapper/ │ │ │ │ ├── BeanMapper.java │ │ │ │ ├── JsonMapper.java │ │ │ │ └── XmlMapper.java │ │ │ ├── net/ │ │ │ │ ├── IPUtil.java │ │ │ │ └── NetUtil.java │ │ │ ├── number/ │ │ │ │ ├── MathUtil.java │ │ │ │ ├── MoneyUtil.java │ │ │ │ ├── NumberUtil.java │ │ │ │ ├── RandomUtil.java │ │ │ │ ├── SizeUnit.java │ │ │ │ └── UnitConverter.java │ │ │ ├── reflect/ │ │ │ │ ├── AnnotationUtil.java │ │ │ │ ├── ClassLoaderUtil.java │ │ │ │ ├── ClassUtil.java │ │ │ │ └── ReflectionUtil.java │ │ │ ├── security/ │ │ │ │ └── CryptoUtil.java │ │ │ ├── text/ │ │ │ │ ├── Charsets.java │ │ │ │ ├── CsvUtil.java │ │ │ │ ├── EncodeUtil.java │ │ │ │ ├── EscapeUtil.java │ │ │ │ ├── HashUtil.java │ │ │ │ ├── MoreStringUtil.java │ │ │ │ ├── StringBuilderHolder.java │ │ │ │ ├── TextValidator.java │ │ │ │ └── WildcardMatcher.java │ │ │ └── time/ │ │ │ ├── CachingDateFormatter.java │ │ │ ├── ClockUtil.java │ │ │ ├── DateFormatUtil.java │ │ │ └── DateUtil.java │ │ └── resources/ │ │ └── sys_data_mask.properties │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── vip/ │ │ └── vjtools/ │ │ ├── test/ │ │ │ ├── data/ │ │ │ │ └── RandomData.java │ │ │ ├── log/ │ │ │ │ ├── LogbackListAppender.java │ │ │ │ └── LogbackListAppenderTest.java │ │ │ └── rule/ │ │ │ └── TestProgress.java │ │ └── vjkit/ │ │ ├── base/ │ │ │ ├── BooleanUtilTest.java │ │ │ ├── EnumUtilTest.java │ │ │ ├── ExceptionUtilTest.java │ │ │ ├── MoreValidateTest.java │ │ │ ├── ObjectUtilTest.java │ │ │ ├── PairTest.java │ │ │ ├── PlatformsTest.java │ │ │ ├── PropertiesUtilTest.java │ │ │ ├── RuntimeUtilTest.java │ │ │ ├── SystemPropertiesUtilTest.java │ │ │ └── ValueValidatorTest.java │ │ ├── collection/ │ │ │ ├── ArrayUtilTest.java │ │ │ ├── CollectionUtilTest.java │ │ │ ├── ListUtilTest.java │ │ │ ├── MapUtilTest.java │ │ │ ├── QueueUtilTest.java │ │ │ ├── SetUtilTest.java │ │ │ └── type/ │ │ │ ├── ConcurrentHashSetTest.java │ │ │ └── SortedArrayListTest.java │ │ ├── concurrent/ │ │ │ ├── ConcurrentsTest.java │ │ │ ├── ThreadDumpperTest.java │ │ │ ├── ThreadUtilTest.java │ │ │ ├── limiter/ │ │ │ │ ├── RateLimiterUtilTest.java │ │ │ │ ├── SamplerTest.java │ │ │ │ └── TimeIntervalLimiterTest.java │ │ │ ├── threadpool/ │ │ │ │ ├── AbortPolicyWithReportTest.java │ │ │ │ ├── QueuableCachedThreadPoolTest.java │ │ │ │ ├── ThreadPoolBuilderTest.java │ │ │ │ └── ThreadPoolUtilTest.java │ │ │ └── type/ │ │ │ ├── BasicFutureTest.java │ │ │ └── ThreadLocalContextTest.java │ │ ├── datamasking/ │ │ │ ├── DataMaskJsonFilterTest.java │ │ │ ├── DataMaskTest.java │ │ │ ├── MaskMappingTest.java │ │ │ ├── data/ │ │ │ │ ├── TestChild.java │ │ │ │ ├── TestData.java │ │ │ │ ├── TestParent.java │ │ │ │ └── TestUserMapingData.java │ │ │ └── strategy/ │ │ │ ├── EmailMaskTest.java │ │ │ ├── HashMaskTest.java │ │ │ ├── NameMaskTest.java │ │ │ └── PartMaskTest.java │ │ ├── id/ │ │ │ └── IdUtilTest.java │ │ ├── io/ │ │ │ ├── FilePathUtilTest.java │ │ │ ├── FileTreeWalkerTest.java │ │ │ ├── FileUtilTest.java │ │ │ ├── IOUtilTest.java │ │ │ ├── ResourceUtilTest.java │ │ │ └── URLResourceTest.java │ │ ├── logging/ │ │ │ └── PerformanceUtilsTest.java │ │ ├── mapper/ │ │ │ ├── BeanMapperTest.java │ │ │ ├── JsonMapperTest.java │ │ │ └── XmlMapperTest.java │ │ ├── net/ │ │ │ ├── IPUtilTest.java │ │ │ └── NetUtilTest.java │ │ ├── number/ │ │ │ ├── MathUtilTest.java │ │ │ ├── MoneyUtilTest.java │ │ │ ├── NumberUtilTest.java │ │ │ ├── RandomUtilTest.java │ │ │ └── UnitConverterTest.java │ │ ├── reflect/ │ │ │ ├── ClassUtilTest.java │ │ │ ├── ClassloaderUtilTest.java │ │ │ └── ReflectionUtilTest.java │ │ ├── security/ │ │ │ └── CryptoUtilTest.java │ │ ├── text/ │ │ │ ├── CsvUtilTest.java │ │ │ ├── EncodeUtilTest.java │ │ │ ├── EscapeUtilTest.java │ │ │ ├── HashUtilTest.java │ │ │ ├── MoreStringUtilTest.java │ │ │ ├── StringBuilderHolderTest.java │ │ │ ├── TextValidatorTest.java │ │ │ └── WildcardMatcherTest.java │ │ └── time/ │ │ ├── CachingDatFormatterTest.java │ │ ├── ClockUtilTest.java │ │ ├── DateFormatUtilTest.java │ │ └── DateUtilTest.java │ └── resources/ │ ├── application.properties │ ├── data_mask.properties │ ├── logback-test.xml │ └── test.txt ├── vjmap/ │ ├── README.md │ ├── README_EN.md │ ├── pom.xml │ └── src/ │ └── main/ │ ├── assembly/ │ │ ├── distribution.xml │ │ ├── vjmap.bat │ │ └── vjmap.sh │ └── java/ │ └── com/ │ └── vip/ │ └── vjtools/ │ └── vjmap/ │ ├── ClassStats.java │ ├── ResultPrinter.java │ ├── VJMap.java │ ├── oops/ │ │ ├── GenAddressAccessor.java │ │ ├── HeapHistogramVisitor.java │ │ ├── HeapUtils.java │ │ ├── LoadedClassAccessor.java │ │ ├── OldgenAccessor.java │ │ └── SurvivorAccessor.java │ └── utils/ │ ├── FormatUtils.java │ ├── ProgressNotifier.java │ └── TimeController.java ├── vjmxcli/ │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ ├── assembly/ │ │ ├── distribution.xml │ │ ├── vjmxcli.bat │ │ └── vjmxcli.sh │ └── java/ │ └── com/ │ └── vip/ │ └── vjtools/ │ └── jmx/ │ ├── Client.java │ ├── ExtraCommand.java │ └── GCutilExpression.java ├── vjstar/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── vip/ │ │ │ └── vjstar/ │ │ │ ├── .gitkeep │ │ │ ├── gc/ │ │ │ │ ├── CleanUpScheduler.java │ │ │ │ └── ProactiveGcTask.java │ │ │ └── window/ │ │ │ ├── AtomicBitSet.java │ │ │ ├── RequestSlidingWindow.java │ │ │ └── TimeSlidingWindow.java │ │ └── script/ │ │ ├── docker-cpus/ │ │ │ └── README.md │ │ └── jvm-options/ │ │ └── jvm-options.sh │ └── test/ │ └── java/ │ └── com/ │ └── vip/ │ └── vjstar/ │ ├── .gitkeep │ ├── gc/ │ │ ├── Enchanter.java │ │ └── ProactiveGcTaskDemo.java │ └── window/ │ ├── RequestSlidingWindowTest.java │ └── TimeSlidingWindowTest.java └── vjtop/ ├── README.md ├── README_EN.md ├── pom.xml └── src/ ├── main/ │ ├── assembly/ │ │ ├── distribution.xml │ │ ├── vjtop.bat │ │ └── vjtop.sh │ └── java/ │ └── com/ │ └── vip/ │ └── vjtools/ │ └── vjtop/ │ ├── InteractiveTask.java │ ├── ThreadPrinter.java │ ├── TopThreadInfo.java │ ├── VJTop.java │ ├── VMDetailView.java │ ├── VMInfo.java │ ├── WarningRule.java │ ├── data/ │ │ ├── PerfData.java │ │ ├── ProcFileData.java │ │ └── jmx/ │ │ ├── JmxBufferPoolManager.java │ │ ├── JmxClient.java │ │ ├── JmxGarbageCollectorManager.java │ │ └── JmxMemoryPoolManager.java │ └── util/ │ ├── Formats.java │ ├── LongObjectHashMap.java │ ├── LongObjectMap.java │ ├── OptionAdvanceParser.java │ ├── SelectPid.java │ └── Utils.java └── test/ └── java/ └── com/ └── vip/ └── vjtools/ └── vjtop/ └── util/ ├── FormatsTest.java └── UtilsTest.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Eclipse project files .classpath .project .settings/ # Intellij project files *.iml .idea/ # Otherse *.log target/ vip-java-standard.md release.sh vjmap/vjmap/ vjtop/vjtop/ vjmxcli/vjmxcli ================================================ FILE: .travis.yml ================================================ language: java jdk: - openjdk9 branches: only: - master script: mvn clean package ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [2006-2012] [www.springside.org.cn] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ ![VJTools](/docs/images/logo.jpg) [![Build Status](https://travis-ci.org/vipshop/vjtools.svg?branch=master)](https://travis-ci.org/vipshop/vjtools) 主力于Java的唯品会,关于Java的一些小家底。 各位看官看着是好的,烦请“Star”。 [1.0.8版](https://github.com/vipshop/vjtools/releases/tag/v.1.0.8) - 2018.9.24 ## Java Standard | Project | Description | | -------- | -------- | | [standard](https://vipshop.github.io/vjtools/#/standard/) | 唯品会Java开发手册 | | [code formatter](/standard/formatter) | IDE格式化模板 | | [sonar rule](/standard/sonar-vj) | Sonar规则定制示例 | ## Java Core Library | Project | Description | | -------- | -------- | | [vjkit](/vjkit) | 关于文本,集合,并发等基础功能的核心类库 | | [vjstar](/vjstar) | 关于后端应用的性能、可用性的最佳实践 | ## Java Tools | Project | Description | Manual | | -------- | -------- | -------- | | [vjtop](/vjtop) | 观察JVM进程指标及其繁忙线程 | [Chinese](/vjtop/README.md)| | [vjmap](/vjmap) | JMAP的分代打印版 |[Chinese](/vjmap/README.md)| | [vjdump](/vjdump) | 线上紧急收集JVM数据脚本 | [Chinese](/vjdump/README.md), [English](/vjdump/README_EN.md)| | [vjmxcli](/vjmxcli) | JMX 查看工具 | [Chinese](/vjmxcli/README.md)| 视频:[《VJTools如何利用佛性技术玩转JVM》](http://kai.vkaijiang.com/product/course?courseID=120897) 文档:[《入门科普,围绕JVM的各种外挂技术》](https://mp.weixin.qq.com/s/cwU2rLOuwock048rKBz3ew) 其他直接使用的工具,见[常用工具](docs/other/othertools.md) ## Contributing VJTools官方微信讨论群,请搜索微信号viptech128(唯技术),添加好友后加入。 所有报Bug、建议与咨询,请在[Issues](https://github.com/vipshop/vjtools/issues)发起;所有代码提交,请走[Pull Request](https://github.com/vipshop/vjtools/pulls)流程。 对于优秀的代码提交和建议,唯品会将不吝发挥电商本色,给予[vip.com](https://www.vip.com)购物卡的奖励 !!! ## Developers 唯品会团队: [江南白衣](http://calvin1978.blogcn.com), [郑德惠](https://github.com/zhengdehui), [黄云斌](https://github.com/huangyunbin), [梁耀曾](https://github.com/AJ-Liang), [林铭恺](https://github.com/acxlam), [李炫彬](https://github.com/lixuanbin) , [张晓玲](https://github.com/hjzhangxiaoling) 曾经一起战斗: [杨镌颖@阿里](https://github.com/yangjuanying), 陈维治@阿里 ================================================ FILE: docs/.nojekyll ================================================ ================================================ FILE: docs/README.md ================================================ All about java in vip.com - Standard - Core Libraries - Tools ================================================ FILE: docs/_coverpage.md ================================================ ![VJTools](images/logo2.png) - Standard - Core Libraries - Tools [GitHub Project](https://github.com/vipshop/vjtools) [Java Coding Guidelines](standard/) ================================================ FILE: docs/_sidebar.md ================================================ - [GitHub Project](https://github.com/vipshop/vjtools) - [Java Coding Guidelines](standard/) ================================================ FILE: docs/index.html ================================================ VJTools
================================================ FILE: docs/other/othertools.md ================================================ # 1.问题排查 ## BTrace 系 - [btrace](https://github.com/btraceio/btrace) - [greys](https://github.com/oldmanpushcart/greys-anatomy) 交互式免脚本,比btrace更易用 [Java神器Btrace,从入门到熟练小工手册](https://mp.weixin.qq.com/s/4bZ6iSvpqPsjdvkSoFVhrg) ## 在线日志分析 - [easygc.io](http://www.gceasy.io/) gc日志分析 - [fastthread.io](http://fastthread.io/) thread dump分析 ## HeapDump分析 - [Eclipse MAT](https://www.eclipse.org/mat/) # 2.性能Profile - [Java Mission Control](http://www.oracle.com/technetwork/java/javaseproducts/mission-control/index.html) JDK自带Profiler - [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) 火焰图生成工具 ================================================ FILE: docs/standard/README.md ================================================ # 《唯品会Java开发手册》1.0.3版 ## 1. 概述 [《阿里巴巴Java开发手册》](https://github.com/alibaba/p3c),是首个对外公布的企业级Java开发手册,对整个业界都有重要的意义。 我们结合唯品会的内部经验,参考《Clean Code》、《Effective Java》等重磅资料,增补了一些条目,也做了些精简。 感谢阿里授权我们定制和再发布。 ## 2. 规范正文 1. [命名规约](standard/chapter01.md) 2. [格式规约](standard/chapter02.md) 3. [注释规约](standard/chapter03.md) 4. [方法设计](standard/chapter04.md) 5. [类设计](standard/chapter05.md) 6. [控制语句](standard/chapter06.md) 7. [基本类型](standard/chapter07.md) 8. [集合处理](standard/chapter08.md) 9. [并发处理](standard/chapter09.md) 10. [异常处理](standard/chapter10.md) 11. [日志规约](standard/chapter11.md) 12. [其他设计](standard/chapter12.md) 注意: 如需全文pdf版,请下载源码,在docs/standard/目录运行merge.sh生成。 ## 3. 规范落地 规则落地主要依靠代码格式模版与[Sonar代码规则检查](https://www.sonarqube.org/)。 其中Sonar规则不如人意的地方,我们进行了定制。 * [Eclipse/Intellij 格式模板](https://github.com/vipshop/vjtools/tree/master/standard/formatter) * [Sonar 规则修改示例](https://github.com/vipshop/vjtools/tree/master/standard/sonar-vj) ## 4. 参考资料 * [《Clean Code》](https://book.douban.com/subject/4199741/) * [《Effective Java 2nd》](https://book.douban.com/subject/3360807/) * [《SEI CERT Oracle Coding Standard for Java》(在线版)](https://www.securecoding.cert.org/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java) * [Sonar Java Rules](https://rules.sonarsource.com/java/) ## 5. 定制记录 * [《唯品会Java开发手册》-与阿里手册的比较文学I](http://calvin1978.blogcn.com/?p=1771) * [阿里手册的增补与删减记录](standard/ali.md) ================================================ FILE: docs/standard/_sidebar.md ================================================ - [命名规约](standard/chapter01.md) - [格式规约](standard/chapter02.md) - [注释规约](standard/chapter03.md) - [方法设计](standard/chapter04.md) - [类设计](standard/chapter05.md) - [控制语句](standard/chapter06.md) - [基本类型](standard/chapter07.md) - [集合处理](standard/chapter08.md) - [并发处理](standard/chapter09.md) - [异常处理](standard/chapter10.md) - [日志规约](standard/chapter11.md) - [其他设计](standard/chapter12.md) - [定制记录](standard/ali.md) ================================================ FILE: docs/standard/ali.md ================================================ # 《阿里Java开发手册》定制纪录 只记录较大的改动,对更多条目内容的重新组织与扩写,则未一一冗述。 * [《唯品会Java开发手册》-与阿里手册的比较文学I](http://calvin1978.blogcn.com/?p=1771) ## (一) 命名规约 对应 [阿里规范《命名风格》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/命名风格.md) | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | | 13. 变量、参数重名覆盖| |新增规则| | 1. 禁止拼音缩写 | 2. 严禁使用拼音与英文混合的方式 |改写规则| | 3. 禁用其他编程语言风格的前缀和后缀 | 1. 代码中的命名均不能以下划线或美元符号开始| 扩写规则,把其他语言的啰嗦都禁止掉| | 4. 命名的好坏,在于其“模糊度”| 11. 为了达到代码自解释的目标 | 扩写规则,参考《Clean Code》的更多例子| | 6. 常量命名全部大写| 5.常量名大写| 扩写规则| | | 7. 类型与中括号紧挨相连来定义数组 | 删除规则,非命名风格,也不重要| | | 13. 接口类中的方法和成员变量不要加任何修饰符号| 移动规则,非命名风格,移到类设计| | | 16. 各层命名规约| 删除规则,各公司有自己的习惯| ---- ## (二) 格式规约 对应 [阿里规范《代码格式》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/代码格式.md) | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | | 1. 项目组统一的代码格式模板| 规则1-8 | 用IDE模版代替逐条描述
同时对Tab/空格不做硬性规定| | 3. 用小括号来限定运算优先级| | 新增规则| | 4. 类内方法定义的顺序| | 新增规则| | 5. 通过空行进行逻辑分段| 11. 不同逻辑、不同语义| 改写规则| | 6. 避免IDE格式化| | 新增规则| | | 10.单个方法行数不超过80行| 删除规则,非格式规约,移动方法设计| | | 11.没有必要增加若干空格来对齐| 删除规则,现在很少人这么做| ---- ## (三) 注释规约 对应 [阿里规范《注释规约》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/注释规约.md) | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | | 2. 删除空注释,无意义注释| |增加规则| | 7. JavaDoc中不要大量使用HTML标签和转义字符 | | 增加规则| | 1. 注释的基本要求| 9. 对于注释的要求|扩写规则| | 4.避免创建人的注释 | 3.所有的类都必须添加创建者| 冲突规则| | | 2.所有的抽象方法必须用Javadoc注释| 删除规则,因为规则2不强制,并入规则1 | | | 4.方法内部单行注释,使用//注释| 删除规则,区别不大不强求 | | | 5. 所有的枚举类型字段必须要有注释|删除规则,因为规则2不强制| ---- ## (四) 方法设计 * 规则 6,7,12,13 从[阿里规范《控制语句》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/控制语句.md) 移入 * 规则 9 从[阿里规范《异常处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/异常处理.md) 移入 * 规则1,2,3,4,5,8,10,11,14为新建规则 ---- ## (五) 类设计 对应 [阿里规范《OOP规范》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/OOP规范.md) | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | |2.减少类之间的依赖 | |增加规则| |3.定义变量与方法参数时,尽量使用接口 | |增加规则| |4.类的长度度量 | |增加规则| |5.Builder模式 | |增加规则| |8.静态方法不能被覆写 | |增加规则| |9.静态方法的访问原则 | |扩写规则| |10.内部类原则 | |增加规则| |12-14.hashCode,equals,toString的规则 | |增加规则| |16.总是移除无用属性、方法与参数 | |增加规则| |18.【推荐】得墨忒耳法则 | |增加规则| | | 3. 提倡同学们尽量不用可变参数编程|删除规则| | | 9. 定义DO/DTO/VO等POJO类时,不要设定任何属性默认值|删除规则| | | 10. 序列化类新增属性时,请不要修改serialVersionUID字段|删除规则| | | 13. 使用索引访问用String的split方法时|删除规则| | | 19. 慎用Object的clone方法来拷贝对象|删除规则| | | 规则4,5 |移到《方法规约》| | | 规则6 |移到《通用设计》| | | 规则7,8,17 |移到《基础类型》| | | 规则14,15|移到《格式规约》| ---- ## (六) 控制语句 对应 [阿里规范《控制语句》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/控制语句.md) | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | |4.布尔表达式中的运算符个数不超过4个 | |扩写规则| |5.善用三目运算符 | |增加规则| |6.能造成短路概率较大的逻辑放前面 | |增加规则| |10.能用while循环实现的代码,就不用do-while循环 | |增加规则| | | 3. 在高并发场景中,避免使用 ”等于”作为条件|删除规则| | | 8. 接口入参保护|删除规则| | | 9. 下列情形,需要进行参数校验|移到《方法规约》| | | 10. 下列情形,不需要进行参数校|移到《方法规约》| ---- ## (七) 基本类型与字符串 * 规则1,3 从 阿里规范[《OOP规范》](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/OOP规范.md)移入,并对2进行扩写 * 规则5 从 阿里规范[《常量定义》](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/常量定义.md)移入并扩写 * 规则8 从 阿里规范[《其他》](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/其他.md)移入 * 规则4,6,7为新建规则 ---- ## (八) 集合处理 对应 [阿里规范《集合处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/集合处理.md) | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | | 2. foreach语法遍历| |增加规则| | 7. 长生命周期的集合| |增加规则| | 8. 并发集合| |增加规则| | 9. 泛型的通配符| |增加规则| | 10. `List, List` 与 `List`的选择| |增加规则| | 11. EnumMap| |增加规则| | | 2. ArrayList的subList结果|删除规则| | | 6. 泛型通配符|删除规则| | | 12.合理利用好集合的有序性|删除规则| | | 13.利用Set元素唯一的特性|删除规则| |12.Array 与 List互转的|使用集合转数组的方法,必须使用集合的toArray|某位老大的测试,new String[0]也不错| ---- ## (九) 并发处理: 并发与多线程 对应 [阿里规范《并发处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/并发处理.md) | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | |1. 指定线程名 | |扩写规则| |4. 正确停止线程 | |扩写规则| |5. 编写可中断的Runnable| |增加规则| |6. Runnable中必须捕获一切异常 |9.多线程并行处理定时任务 |扩写规则| |7. 全局变量的线程安全 | |扩写规则| |10. 选择分离锁, 分散锁甚至无锁的数据结构| |增加规则| |13. volatile修饰符,AtomicXX系列的正确使用 | |扩写规则| | |8.并发修改同一记录时,需要加锁 |删除规则| | |10.使用CountDownLatch进行异步转同步操作 |删除规则| | |14.HashMap在容量不够进行resize|移到《集合规约》一章| |14. 延时初始化的正确写法 |12.双重检查锁 |冲突规则| ---- ## (十) 异常处理 对应 [阿里规范《异常处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/异常处理.md) | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | |2.在特定场合,避免每次构造异常| |增加规则| |5.异常抛出的原则| |增加规则| |6.异常捕获的原则| |增加规则| |7.异常处理的原则| |增加规则| | |8.捕获异常与抛异常,必须是完全匹配 |删除规则| | |12.对于公司外的开放接口必须使用“错误码” |删除规则| | |13.DRY原则 |删除规则,为什么会出现在这章,太著名了| | |9.返回值可以为null |移到《方法设计》一章| | |10. 【推荐】防止NPE,是程序员的基本修养 |拆开到各章| | |11.避免直接抛出RuntimeException |规则冲突| ---- ## (十一) 日志规约 对应 [阿里规范《日志规约》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/日志规约.md) | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | |4.尽量使用异步日志| |增加规则| |5.禁止使用System.out()| |增加规则| |6.禁止配置日志框架打印日志打印时的类名,行号等信息| |增加规则| | |2.日志文件推荐至少保存15天 |删除规则| | |3.应用中的扩展日志命名方式 |删除规则| | |6.异常信息应该包括两类信息 |移到《异常处理》| |2.合理使用使用占位符 |4.对trace/debug/info级别的日志使用占位符 |还是要判断日志是否必然输出,
并强调条件判断与占位符之间的差别| ---- ## (十二) 其他规约 保留 [阿里规范《常量定义》一章](dhttps://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/常量定义.md)的规则1 | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | | | 规则2-4 | 删除规则| | | 规则5. 如果变量值仅在一个固定范围内变化用enum类型来定义 | 移到《基本类型》| 保留 [阿里规范《其他》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/其他.md)的规则7 | VIP 规范 | 阿里规范 | 修改| | -------- | -------- |-------- | | | 规则2-4,6-8 | 删除规则 | | | 规则1. 在使用正则表达式时,利用好其预编译功 | 移到《基本类型》| * 规则3,4,5,6,7均为新增规则 ================================================ FILE: docs/standard/chapter01.md ================================================ # (一) 命名规约 **Rule 1. 【强制】禁止拼音缩写,避免阅读者费劲猜测;尽量不用拼音,除非中国式业务词汇没有通用易懂的英文对应。** ```text 禁止: DZ[打折] / getPFByName() [评分] 尽量避免:Dazhe / DaZhePrice ``` ---- **Rule 2. 【强制】禁止使用非标准的英文缩写** ```text 反例: AbstractClass 缩写成 AbsClass;condition 缩写成 condi。 ``` ---- **Rule 3. 【强制】禁用其他编程语言风格的前缀和后缀** 在其它编程语言中使用的特殊前缀或后缀,如 `_name`、`name_`、`mName`、`i_name`,在 Java 中都禁止使用。 ---- **Rule 4. 【推荐】命名的好坏,在于其“模糊度”** 1)如果上下文很清晰,局部变量可以使用 `list` 这种简略命名, 否则应该使用 `userList` 这种更清晰的命名。 2)禁止 `a1, a2, a3` 这种带编号的没诚意的命名方式。 3)方法的参数名叫 `bookList` ,方法里的局部变量名叫 `theBookList` 也是很没诚意。 4)如果一个应用里同时存在 `Account、AccountInfo、AccountData` 类,或者一个类里同时有 `getAccountInfo()、getAccountData()`, `save()、 store()` 的函数,阅读者将非常困惑。 5) `callerId` 与 `calleeId`, `mydearfriendswitha` 与 `mydearfriendswithb` 这种拼写极度接近,考验阅读者眼力的。 ---- **Rule 5. 【推荐】包名全部小写。点分隔符之间尽量只有一个英语单词,即使有多个单词也不使用下划线或大小写分隔** ```text 正例: com.vip.javatool 反例: com.vip.java_tool, com.vip.javaTool ``` * [Sonar-120:Package names should comply with a naming convention](https://rules.sonarsource.com/java/RSPEC-120) ---- **Rule 6. 【强制】类名与接口名使用UpperCamelCase风格,遵从驼峰形式** Tcp, Xml等缩写也遵循驼峰形式,可约定例外如:DTO/ VO等。 ``` text 正例:UserId / XmlService / TcpUdpDeal / UserVO 反例:UserID / XMLService / TCPUDPDeal / UserVo ``` * [Sonar-101:Class names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-101) * [Sonar-114:Interface names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-114) ---- **Rule 7. 【强制】方法名、参数名、成员变量、局部变量使用lowerCamelCase风格,遵从驼峰形式** ```text 正例: localValue / getHttpMessage(); ``` * [Sonar-100:Method names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-100) * [Sonar-116:Field names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-116) * [Sonar-117:Local variable and method parameter names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-117) ---- **Rule 8. 【强制】常量命名全大写,单词间用下划线隔开。力求语义表达完整清楚,不要嫌名字长** ```text 正例: MAX_STOCK_COUNT 反例: MAX_COUNT ``` 例外:当一个static final字段不是一个真正常量,比如不是基本类型时,不需要使用大写命名。 ```java private static final Logger logger = Logger.getLogger(MyClass.class); ``` 例外:枚举常量推荐全大写,但如果历史原因未遵循也是允许的,所以我们修改了Sonar的规则。 * [Sonar-115:Constant names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-115) * [Sonar-308:Static non-final field names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-308) ---- **Rule 9. 【推荐】如果使用到了通用的设计模式,在类名中体现,有利于阅读者快速理解设计思想** ``` text 正例:OrderFactory, LoginProxy ,ResourceObserver ``` ---- **Rule 10. 【推荐】枚举类名以Enum结尾; 抽象类使用Abstract或Base开头;异常类使用Exception结尾;测试类以它要测试的类名开始,以Test结尾** ```text 正例:DealStatusEnum, AbstractView,BaseView, TimeoutException,UserServiceTest ``` * [Sonar-2166:Classes named like "Exception" should extend "Exception" or a subclass](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-2166) * [Sonar-3577:Test classes should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-3577) ---- **Rule 11. 【推荐】实现类尽量用Impl的后缀与接口关联,除了形容能力的接口** ```text 正例:CacheServiceImpl 实现 CacheService接口。 正例: Foo 实现 Translatable接口。 ``` ---- **Rule 12. 【强制】POJO类中布尔类型的变量名,不要加is前缀,否则部分框架解析会引起序列化错误** ```text 反例:Boolean isSuccess的成员变量,它的GET方法也是isSuccess(),部分框架在反射解析的时候,“以为”对应的成员变量名称是success,导致出错。 ``` ---- **Rule 13. 【强制】避免成员变量,方法参数,局部变量的重名复写,引起混淆** * 类的私有成员变量名,不与父类的成员变量重名 * 方法的参数名/局部变量名,不与类的成员变量重名 (getter/setter例外) 下面错误的地方,Java在编译时很坑人的都是合法的,但给阅读者带来极大的障碍。 ```java public class A { int foo; } public class B extends A { int foo; //WRONG int bar; public void hello(int bar) { //WRONG int foo = 0; //WRONG } public void setBar(int bar) { //OK this.bar = bar; } } ``` * [Sonar-2387: Child class fields should not shadow parent class fields](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-2387) * [Sonar: Local variables should not shadow class fields](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-1117) ---- ================================================ FILE: docs/standard/chapter02.md ================================================ # (二) 格式规约 **Rule 1. 【强制】使用项目组统一的代码格式模板,基于IDE自动的格式化** 1)IDE的默认代码格式模板,能简化绝大部分关于格式规范(如空格,括号)的描述。 2)统一的模板,并在接手旧项目先进行一次全面格式化,可以避免, 不同开发者之间,因为格式不统一产生代码合并冲突,或者代码变更日志中因为格式不同引起的变更,掩盖了真正的逻辑变更。 3)设定项目组统一的行宽,建议120。 4)设定项目组统一的缩进方式(Tab或二空格,四空格均可),基于IDE自动转换。 * [VIP代码格式化模板](https://github.com/vipshop/vjtools/tree/master/standard/formatter) ---- **Rule 2. 【强制】IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用Windows格式** ---- **Rule 3. 【推荐】 用小括号来限定运算优先级** 我们没有理由假设读者能记住整个Java运算符优先级表。除非作者和Reviewer都认为去掉小括号也不会使代码被误解,甚至更易于阅读。 ```java if ((a == b) && (c == d)) ``` * [Sonar-1068:Limited dependence should be placed on operator precedence rules in expressions](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-1068),我们修改了三目运算符 `foo!=null?foo:""` 不需要加括号。 ---- **Rule 4. 【推荐】类内方法定义的顺序,不要“总是在类的最后添加新方法”** 一个类就是一篇文章,想象一个阅读者的存在,合理安排方法的布局。 1)顺序依次是:构造函数 > (公有方法>保护方法>私有方法) > getter/setter方法。 如果公有方法可以分成几组,私有方法也紧跟公有方法的分组。 2)当一个类有多个构造方法,或者多个同名的重载方法,这些方法应该放置在一起。其中参数较多的方法在后面。 ```java public Foo(int a) {...} public Foo(int a, String b) {...} public void foo(int a) {...} public void foo(int a, String b) {...} ``` 3)作为调用者的方法,尽量放在被调用的方法前面。 ```java public void foo() { bar(); } public void bar() {...} ``` ---- **Rule 5. 【推荐】通过空行进行逻辑分段** 一段代码也是一段文章,需要合理的分段而不是一口气读到尾。 不同组的变量之间,不同业务逻辑的代码行之间,插入一个空行,起逻辑分段的作用。 而联系紧密的变量之间、语句之间,则尽量不要插入空行。 ```java int width; int height; String name; ``` ---- **Rule 6.【推荐】避免IDE格式化** 对于一些特殊场景(如使用大量的字符串拼接成一段文字,或者想把大量的枚举值排成一列),为了避免IDE自动格式化,土办法是把注释符号//加在每一行的末尾,但这有视觉的干扰,可以使用@formatter:off和@formatter:on来包装这段代码,让IDE跳过它。 ``` java // @formatter:off ... // @formatter:on ``` ---- ================================================ FILE: docs/standard/chapter03.md ================================================ # (三) 注释规约 **Rule 1.【推荐】基本的注释要求** 完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。 代码将被大量后续维护,注释如果对阅读者有帮助,不要吝啬在注释上花费的时间。(但也综合参见规则2,3) 第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。 除了特别清晰的类,都尽量编写类级别注释,说明类的目的和使用方法。 除了特别清晰的方法,对外提供的公有方法,抽象类的方法,同样尽量清晰的描述:期待的输入,对应的输出,错误的处理和返回码,以及可能抛出的异常。 ---- **Rule 2. 【推荐】通过更清晰的代码来避免注释** 在编写注释前,考虑是否可以通过更好的命名,更清晰的代码结构,更好的函数和变量的抽取,让代码不言自明,此时不需要额外的注释。 ---- **Rule 3. 【推荐】删除空注释,无意义注释** 《Clean Code》建议,如果没有想说的,不要留着IDE自动生成的,空的@param,@return,@throws 标记,让代码更简洁。 反例:方法名为put,加上两个有意义的变量名elephant和fridge,已经说明了这是在干什么,不需要任何额外的注释。 ```java /** * put elephant into fridge. * * @param elephant * @param fridge * @return */ public void put(Elephant elephant, Fridge fridge); ``` ---- **Rule 4.【推荐】避免创建人,创建日期,及更新日志的注释** 代码后续还会有多人多次维护,而创建人可能会离职,让我们相信源码版本控制系统对更新记录能做得更好。 ---- **Rule 5. 【强制】代码修改的同时,注释也要进行相应的修改。尤其是参数、返回值、异常、核心逻辑等的修改** ---- **Rule 6. 【强制】类、类的公有成员、方法的注释必须使用Javadoc规范,使用/\*\* xxx \*/格式,不得使用`//xxx`方式** 正确的JavaDoc格式可以在IDE中,查看调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。 ---- **Rule 7. 【推荐】JavaDoc中不要为了HTML格式化而大量使用HTML标签和转义字符** 如果为了Html版JavaDoc的显示,大量使用`` ``这样的html标签,以及`<` `"` 这样的html转义字符,严重影响了直接阅读代码时的直观性,而直接阅读代码的几率其实比看Html版的JavaDoc大得多。 另外IDE对JavaDoc的格式化也要求`

`之类的标签来换行,可以配置让IDE不对JavaDoc的格式化。 ---- **Rule 8. 【推荐】注释不要为了英文而英文** 如果没有国际化要求,中文能表达得更清晰时还是用中文。 ---- **Rule 9. 【推荐】TODO 标记,清晰说明代办事项和处理人** “对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释. ” 清晰描述待修改的事项,保证过几个月后仍然能够清楚要做什么修改。 如果近期会处理的事项,写明处理人。如果远期的,写明提出人。 通过 IDE 和 Sonar 的标记扫描,经常清理此类标记,线上故障经常来源于这些标记但未处理的代码。 正例: ```java // TODO(who): to do what. // TODO(who): when to do what. // TODO(calvin): use xxx to replace yyy. ``` 反例: ```java // TODO: refactor it ``` 不推荐使用 FIXME 标记。这样,在项目中搜索 TODO 就可以得到完整的 TODO list,而不需要搜索 TODO 和 FIXME。 * [Sonar: Track uses of "TODO" tags](https://rules.sonarsource.com/java/RSPEC-1135) ---- **Rule 10. 【推荐】合理处理注释掉的代码** 如果后续会恢复此段代码,在目标代码上方用`///`说明注释动机,而不是简单的注释掉代码。 如果很大概率不再使用,则直接删除(版本管理工具保存了历史代码)。 * [Sonar: Sections of code should not be "commented out"](https://rules.sonarsource.com/java/RSPEC-125) ---- ================================================ FILE: docs/standard/chapter04.md ================================================ # (四) 方法设计 **Rule 1. 【推荐】方法的长度度量** 方法尽量不要超过100行,或其他团队共同商定的行数。 另外,方法长度超过8000个字节码时,将不会被JIT编译成二进制码。 * [Sonar-107: Methods should not have too many lines](https://rules.sonarsource.com/java/RSPEC-107),默认值改为100 * Facebook-Contrib:Performance - This method is too long to be compiled by the JIT ---- **Rule 2. 【推荐】方法的语句在同一个抽象层级上** 反例:一个方法里,前20行代码在进行很复杂的基本价格计算,然后调用一个折扣计算函数,再调用一个赠品计算函数。 此时可将前20行也封装成一个价格计算函数,使整个方法在同一抽象层级上。 ---- **Rule 3. 【推荐】为了帮助阅读及方法内联,将小概率发生的异常处理及其他极小概率进入的代码路径,封装成独立的方法** ```java if(seldomHappenCase) { hanldMethod(); } try { ... } catch(SeldomHappenException e) { handleException(); } ``` ---- **Rule 4. 【推荐】尽量减少重复的代码,抽取方法** 超过5行以上重复的代码,都可以考虑抽取公用的方法。 ---- **Rule 5. 【推荐】方法参数最好不超过3个,最多不超过7个** 1)如果多个参数同属于一个对象,直接传递对象。 例外: 你不希望依赖整个对象,传播了类之间的依赖性。 2)将多个参数合并为一个新创建的逻辑对象。 例外: 多个参数之间毫无逻辑关联。 3)将函数拆分成多个函数,让每个函数所需的参数减少。 * [Sonar-107: Methods should not have too many parameters](https://rules.sonarsource.com/java/RSPEC-107) ---- **Rule 6.【推荐】下列情形,需要进行参数校验** 1) 调用频次低的方法。 2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,代价更大。 3) 需要极高稳定性和可用性的方法。 4) 对外提供的开放接口,不管是RPC/HTTP/公共类库的API接口。 如果使用Apache Validate 或 Guava Precondition进行校验,并附加错误提示信息时,注意不要每次校验都做一次字符串拼接。 ```java //WRONG Validate.isTrue(length > 2, "length is "+keys.length+", less than 2", length); //RIGHT Validate.isTrue(length > 2, "length is %d, less than 2", length); ``` ---- **Rule 7.【推荐】下列情形,不需要进行参数校验** 1) 极有可能被循环调用的方法。 2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。 比如,一般DAO层与Service层都在同一个应用中,所以DAO层的参数校验,可以省略。 3) 被声明成private,或其他只会被自己代码所调用的方法,如果能够确定在调用方已经做过检查,或者肯定不会有问题则可省略。 即使忽略检查,也尽量在方法说明里注明参数的要求,比如vjkit中的@NotNull,@Nullable标识。 ---- **Rule 8.【推荐】禁用assert做参数校验** assert断言仅用于测试环境调试,无需在生产环境时进行的校验。因为它需要增加-ea启动参数才会被执行。而且校验失败会抛出一个AssertionError(属于Error,需要捕获Throwable) 因此在生产环境进行的校验,需要使用Apache Commons Lang的Validate或Guava的Precondition。 ---- **Rule 9.【推荐】返回值可以为Null,可以考虑使用JDK8的Optional类** 不强制返回空集合,或者空对象。但需要添加注释充分说明什么情况下会返回null值。 本手册明确`防止NPE是调用者的责任`。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。 JDK8的Optional类的使用这里不展开。 ---- **Rule 10.【推荐】返回值可以为内部数组和集合** 如果觉得被外部修改的可能性不大,或没有影响时,不强制在返回前包裹成Immutable集合,或进行数组克隆。 ---- **Rule 11.【推荐】不能使用有继承关系的参数类型来重载方法** 因为方法重载的参数类型是根据编译时表面类型匹配的,不根据运行时的实际类型匹配。 ```java class A { void hello(List list); void hello(ArrayList arrayList); } List arrayList = new ArrayList(); // 下句调用的是hello(List list),因为arrayList的定义类型是List a.hello(arrayList); ``` ---- **Rule 12.【强制】正被外部调用的接口,不允许修改方法签名,避免对接口的调用方产生影响** 只能新增新接口,并对已过时接口加@Deprecated注解,并清晰地说明新接口是什么。 ---- **Rule 13.【推荐】不使用`@Deprecated`的类或方法** 接口提供方既然明确是过时接口并提供新接口,那么作为调用方来说,有义务去考证过时方法的新实现是什么。 比如java.net.URLDecoder 中的方法decode(String encodeStr) 这个方法已经过时,应该使用双参数decode(String source, String encode)。 ---- **Rule 14.【推荐】不使用不稳定方法,如com.sun.\*包下的类,底层类库中internal包下的类** `com.sun.*`,`sun.*`包下的类,或者底层类库中名称为internal的包下的类,都是不对外暴露的,可随时被改变的不稳定类。 * [Sonar-1191: Classes from "sun.*" packages should not be used](https://rules.sonarsource.com/java/RSPEC-1191) ---- ================================================ FILE: docs/standard/chapter05.md ================================================ # (五) 类设计 **Rule 1. 【推荐】类成员与方法的可见性最小化** 任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。思考:如果是一个private的方法,想删除就删除,可是一个public的service方法,或者一个public的成员变量,删除一下,不得手心冒点汗吗? 例外:为了单元测试,有时也可能将访问范围扩大,此时需要加上JavaDoc说明或vjkit中的`@VisibleForTesting`注解。 ---- **Rule 2.【推荐】 减少类之间的依赖** 比如如果A类只依赖B类的某个属性,在构造函数和方法参数中,只传入该属性。让阅读者知道,A类只依赖了B类的这个属性,而不依赖其他属性,也不会调用B类的任何方法。 ```java a.foo(b); //WRONG a.foo(b.bar); //RIGHT ``` ---- **Rule 3.【推荐】 定义变量与方法参数时,尽量使用接口而不是具体类** 使用接口可以保持一定的灵活性,也能向读者更清晰的表达你的需求:变量和参数只是要求有一个Map,而不是特定要求一个HashMap。 例外:如果变量和参数要求某种特殊类型的特性,则需要清晰定义该参数类型,同样是为了向读者表达你的需求。 ---- **Rule 4. 【推荐】类的长度度量** 类尽量不要超过300行,或其他团队共同商定的行数。 对过大的类进行分拆时,可考虑其内聚性,即类的属性与类的方法的关联程度,如果有些属性没有被大部分的方法使用,其内聚性是低的。 ---- **Rule 5.【推荐】 构造函数如果有很多参数,且有多种参数组合时,建议使用Builder模式** ```java Executor executor = new ThreadPoolBuilder().coreThread(10).queueLenth(100).build(); ``` 即使仍然使用构造函数,也建议使用chain constructor模式,逐层加入默认值传递调用,仅在参数最多的构造函数里实现构造逻辑。 ```java public A(){ A(DEFAULT_TIMEOUT); } public A(int timeout) { ... } ``` ---- **Rule 6.【推荐】构造函数要简单,尤其是存在继承关系的时候** 可以将复杂逻辑,尤其是业务逻辑,抽取到独立函数,如init(),start(),让使用者显式调用。 ```java Foo foo = new Foo(); foo.init(); ``` ---- **Rule 7.【强制】所有的子类覆写方法,必须加`@Override`注解** 比如有时候子类的覆写方法的拼写有误,或方法签名有误,导致没能真正覆写,加`@Override`可以准确判断是否覆写成功。 而且,如果在父类中对方法签名进行了修改,子类会马上编译报错。 另外,也能提醒阅读者这是个覆写方法。 最后,建议在IDE的Save Action中配置自动添加`@Override`注解,如果无意间错误同名覆写了父类方法也能被发现。 * [Sonar-1161: "@Override" should be used on overriding and implementing methods](https://rules.sonarsource.com/java/RSPEC-1161) ---- **Rule 8.【强制】静态方法不能被子类覆写。** 因为它只会根据表面类型来决定调用的方法。 ```java Base base = new Children(); // 下句实际调用的是父类的静态方法,虽然对象实例是子类的。 base.staticMethod(); ``` ---- **Rule 9.静态方法访问的原则** **9.1【推荐】避免通过一个类的对象引用访问此类的静态变量或静态方法,直接用类名来访问即可** 目的是向读者更清晰传达调用的是静态方法。可在IDE的Save Action中配置自动转换。 ```java int i = objectA.staticMethod(); // WRONG int i = ClassA.staticMethod(); // RIGHT ``` * [Sonar-2209: "static" members should be accessed statically](https://rules.sonarsource.com/java/RSPEC-2209) * [Sonar-2440: Classes with only "static" methods should not be instantiated](https://rules.sonarsource.com/java/RSPEC-2440) **9.2 【推荐】除测试用例,不要static import 静态方法** 静态导入后忽略掉的类名,给阅读者造成障碍。 例外:测试环境中的assert语句,大家都太熟悉了。 * [Sonar-3030: Classes should not have too many "static" imports](https://rules.sonarsource.com/java/RSPEC-3030) 但IDEA经常自动转换static import,所以暂不作为规则。 **9.3【推荐】尽量避免在非静态方法中修改静态成员变量的值** ```java // WRONG public void foo() { ClassA.staticFiled = 1; } ``` * [Sonar-2696: Instance methods should not write to "static" fields](https://rules.sonarsource.com/java/RSPEC-2696) * [Sonar-3010: Static fields should not be updated in constructors](https://rules.sonarsource.com/java/RSPEC-3010) ---- **Rule 10.【推荐】 内部类的定义原则** 当一个类与另一个类关联非常紧密,处于从属的关系,特别是只有该类会访问它时,可定义成私有内部类以提高封装性。 另外,内部类也常用作回调函数类,在JDK8下建议写成Lambda。 内部类分匿名内部类,内部类,静态内部类三种。 1) 匿名内部类 与 内部类,按需使用: 在性能上没有区别;当内部类会被多个地方调用,或匿名内部类的长度太长,已影响对调用它的方法的阅读时,定义有名字的内部类。 2) 静态内部类 与 内部类,优先使用静态内部类: 1. 非静态内部类持有外部类的引用,能访问外类的实例方法与属性。构造时多传入一个引用对性能没有太大影响,更关键的是向阅读者传递自己的意图,内部类会否访问外部类。 2. 非静态内部类里不能定义static的属性与方法。 * [Sonar-2694: Inner classes which do not reference their owning classes should be "static"](https://rules.sonarsource.com/java/RSPEC-2694) * [Sonar-1604: Anonymous inner classes containing only one method should become lambdas](https://rules.sonarsource.com/java/RSPEC-1604) ---- **Rule 11.【推荐】使用getter/setter方法,还是直接public成员变量的原则。** 除非因为特殊原因方法内联失败,否则使用getter方法与直接访问成员变量的性能是一样的。 使用getter/setter,好处是可以进一步的处理: 1. 通过隐藏setter方法使得成员变量只读 2. 增加简单的校验逻辑 3. 增加简单的值处理,值类型转换等 建议通过IDE生成getter/setter。 但getter/seter中不应有复杂的业务处理,建议另外封装函数,并且不要以getXX/setXX命名。 如果是内部类,以及无逻辑的POJO/VO类,使用getter/setter除了让一些纯OO论者感觉舒服,没有任何的好处,建议直接使用public成员变量。 例外:有些序列化框架只能从getter/setter反射,不能直接反射public成员变量。 ---- **Rule 12.【强制】POJO类必须覆写toString方法。** 便于记录日志,排查问题时调用POJO的toString方法打印其属性值。否则默认的Object.toString()只打印`类名@数字`的无效信息。 ---- **Rule 13. hashCode和equals方法的处理,遵循如下规则:** **13.1【强制】只要重写equals,就必须重写hashCode。 而且选取相同的属性进行运算。** **13.2【推荐】只选取真正能决定对象是否一致的属性,而不是所有属性,可以改善性能。** **13.3【推荐】对不可变对象,可以缓存hashCode值改善性能(比如String就是例子)。** **13.4【强制】类的属性增加时,及时重新生成toString,hashCode和equals方法。** * [Sonar-1206: "equals(Object obj)" and "hashCode()" should be overridden in pairs](https://rules.sonarsource.com/java/RSPEC-1206) ---- **Rule 14.【强制】使用 IDE 生成 toString、hashCode 和 equals 方法。** 使用 IDE 生成而不是手写,能保证 toString 有统一的格式,equals 和 hashCode 则避免不正确的 null 值处理。 子类生成 toString 时,还需要勾选父类的属性。 ---- **Rule 15. 【强制】Object的equals方法容易抛空指针异常,应使用常量或确定非空的对象来调用equals** 推荐使用java.util.Objects#equals(JDK7引入的工具类) ```java "test".equals(object); //RIGHT Objects.equals(object, "test"); //RIGHT ``` * [Sonar-1132: Strings literals should be placed on the left side when checking for equality](https://rules.sonarsource.com/java/RSPEC-1132) ---- **Rule 16.【强制】除了保持兼容性的情况,总是移除无用属性、方法与参数** 特别是private的属性、方法、内部类,private方法上的参数,一旦无用立刻移除。信任代码版本管理系统。 * [Sonar-3985: Unused "private" classes should be removed](https://rules.sonarsource.com/java/RSPEC-3985) * [Sonar-1068: Unused "private" fields should be removed](https://rules.sonarsource.com/java/RSPEC-1068) * [Sonar: Unused "private" methods should be removed](https://rules.sonarsource.com/java/RSPEC-1144) * [Sonar-1481: Unused local variables should be removed](https://rules.sonarsource.com/java/RSPEC-1481) * [Sonar-1172: Unused method parameters should be removed](https://rules.sonarsource.com/java/RSPEC-1172) Sonar-VJ版只对private方法的无用参数告警。 ---- **Rule 17.【推荐】final关键字与性能无关,仅用于下列不可修改的场景** 1) 定义类及方法时,类不可继承,方法不可覆写; 2) 定义基本类型的函数参数和变量,不可重新赋值; 3) 定义对象型的函数参数和变量,仅表示变量所指向的对象不可修改,而对象自身的属性是可以修改的。 ---- **Rule 18.【推荐】得墨忒耳法则,不要和陌生人说话** 以下调用,一是导致了对A对象的内部结构(B,C)的紧耦合,二是连串的调用很容易产生NPE,因此链式调用尽量不要过长。 ```java obj.getA().getB().getC().hello(); ``` ---- ================================================ FILE: docs/standard/chapter06.md ================================================ # (六) 控制语句 **Rule 1. 【强制】if, else, for, do, while语句必须使用大括号,即使只有单条语句** 曾经试过合并代码时,因为没加括号,单条语句合并成两条语句后,仍然认为只有单条语句,另一条语句在循环外执行。 其他增加调试语句等情况也经常引起同样错误。 可在IDE的Save Action中配置自动添加。 ```java if (a == b) { ... } ``` 例外:一般由IDE生成的equals()函数 * [Sonar-121: Control structures should use curly braces](https://rules.sonarsource.com/java/RSPEC-121) Sonar-VJ版豁免了equals()函数 ---- **Rule 2.【推荐】少用if-else方式,多用哨兵语句式以减少嵌套层次** ```java if (condition) { ... return obj; } // 接着写else的业务逻辑代码; ``` * Facebook-Contrib: Style - Method buries logic to the right (indented) more than it needs to be ---- **Rule 3.【推荐】限定方法的嵌套层次** 所有if/else/for/while/try的嵌套,当层次过多时,将引起巨大的阅读障碍,因此一般推荐嵌套层次不超过4。 通过抽取方法,或哨兵语句(见Rule 2)来减少嵌套。 ```java public void applyDriverLicense() { if (isTooYoung()) { System.out.println("You are too young to apply driver license."); return; } if (isTooOld()) { System.out.println("You are too old to apply driver license."); return; } System.out.println("You've applied the driver license successfully."); return; } ``` * [Sonar-134: Control flow statements "if", "for", "while", "switch" and "try" should not be nested too deeply](https://rules.sonarsource.com/java/RSPEC-134),增大为4 ---- **Rule 4.【推荐】布尔表达式中的布尔运算符(&&,||)的个数不超过4个,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性** ```java //WRONG if ((file.open(fileName, "w") != null) && (...) || (...)|| (...)) { ... } //RIGHT boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed || (...)) { ... } ``` * [Sonar-1067: Expressions should not be too complex](https://rules.sonarsource.com/java/RSPEC-1067),增大为4 ---- **Rule 5.【推荐】简单逻辑,善用三目运算符,减少if-else语句的编写** ```java s != null ? s : ""; ``` ---- **Rule 6.【推荐】减少使用取反的逻辑** 不使用取反的逻辑,有利于快速理解。且大部分情况,取反逻辑存在对应的正向逻辑写法。 ```java //WRONG if (!(x >= 268) { ... } //RIGHT if (x < 268) { ... } ``` * [Sonar-1940: Boolean checks should not be inverted](https://rules.sonarsource.com/java/RSPEC-1940) ---- **Rule 7.【推荐】表达式中,能造成短路概率较大的逻辑尽量放前面,使得后面的判断可以免于执行** ```java if (maybeTrue() || maybeFalse()) { ... } if (maybeFalse() && maybeTrue()) { ... } ``` ---- **Rule 8.【强制】switch的规则** 1)在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止; 2)在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。 ```java String animal = "tomcat"; switch (animal) { case "cat": System.out.println("It's a cat."); break; case "lion": // 执行到tiger case "tiger": System.out.println("It's a beast."); break; default: // 什么都不做,也要有default break; } ``` * [Sonar: "switch" statements should end with "default" clauses](https://rules.sonarsource.com/java/RSPEC-131) ---- **Rule 9.【推荐】循环体中的语句要考量性能,操作尽量移至循环体外处理** 1)不必要的耗时较大的对象构造; 2)不必要的try-catch(除非出错时需要循环下去)。 ---- **Rule 10.【推荐】能用while循环实现的代码,就不用do-while循环** while语句能在循环开始的时候就看到循环条件,便于帮助理解循环内的代码; do-while语句要在循环最后才看到循环条件,不利于代码维护,代码逻辑容易出错。 ---- ================================================ FILE: docs/standard/chapter07.md ================================================ # (七) 基本类型与字符串 **Rule 1. 原子数据类型(int等)与包装类型(Integer等)的使用原则** **1.1 【推荐】需要序列化的POJO类属性使用包装数据类型** **1.2 【推荐】RPC方法的返回值和参数使用包装数据类型** **1.3 【推荐】局部变量尽量使用基本数据类型** 包装类型的坏处: 1)Integer 24字节,而原子类型 int 4字节。 2)包装类型每次赋值还需要额外创建对象,如Integer var = 200, 除非数值在缓存区间内(见Integer.IntegerCache与Long.LongCache)才会复用已缓存对象。默认缓存区间为-128到127,其中Integer的缓存区间还受启动参数的影响,如-XX:AutoBoxCacheMax=20000。 3)包装类型还有==比较的陷阱(见规则3) 包装类型的好处: 1)包装类型能表达 null 的语义。 比如数据库的查询结果可能是null,如果用基本数据类型有NPE风险。又比如显示成交总额涨跌情况,如果调用的RPC服务不成功时,应该返回null,显示成-%,而不是0%。 2)集合需要包装类型,除非使用数组,或者特殊的原子类型集合。 3)泛型需要包装类型,如`Result`。 ---- **Rule 2.原子数据类型与包装类型的转换原则** **2.1【推荐】自动转换(AutoBoxing)有一定成本,调用者与被调用函数间尽量使用同一类型,减少默认转换** ```java //WRONG, sum 类型为Long, i类型为long,每次相加都需要AutoBoxing。 Long sum=0L; for( long i = 0; i < 10000; i++) { sum+=i; } //RIGHT, 准确使用API返回正确的类型 Integer i = Integer.valueOf(str); int i = Integer.parseInt(str); ``` * [Sonar-2153: Boxing and unboxing should not be immediately reversed](https://rules.sonarsource.com/java/RSPEC-2153) **2.2 【推荐】自动拆箱有可能产生NPE,要注意处理** ```java //如果intObject为null,产生NPE int i = intObject; ``` ---- **Rule 3. 数值equals比较的原则** **3.1【强制】 所有包装类对象之间值的比较,全部使用equals方法比较** \==判断对象是否同一个。Integer var = ?在缓存区间的赋值(见规则1),会复用已有对象,因此这个区间内的Integer使用==进行判断可通过,但是区间之外的所有数据,则会在堆上新产生,不会通过。因此如果用\== 来比较数值,很可能在小的测试数据中通过,而到了生产环境才出问题。 **3.2【强制】 BigDecimal需要使用compareTo()** 因为BigDecimal的equals()还会比对精度,2.0与2.00不一致。 * Facebook-Contrib: Correctness - Method calls BigDecimal.equals() **3.3【强制】 Atomic\* 系列,不能使用 equals 方法** 因为 Atomic\* 系列没有覆写 equals 方法。 ```java // RIGHT if (counter1.get() == counter2.get()) { // ... } ``` * [Sonar-2204: ".equals()" should not be used to test the values of "Atomic" classes](https://rules.sonarsource.com/java/RSPEC-2204) **3.4【强制】 double 及 float 的比较,要特殊处理** 因为精度问题,浮点数间的 equals 非常不可靠,在 vjkit 的 NumberUtil 中有对应的封装函数。 ```java float f1 = 0.15f; float f2 = 0.45f/3; // 实际等于0.14999999 // 反例 if (f1 == f2) { // ... } // 反例 if (Double.compare(f1, f2) == 0) { // ... } // 正例 // 绝对误差 static final float EPSILON = 0.00001f; if (Math.abs(f1 - f2) < EPSILON) { // ... } // 正例 // 相对误差 static final float EPSILON = 0.001f; if (Math.abs(f1 - f2) / (Math.abs(f1) + Math.abs(f2)) < EPSILON) { // ... } ``` 推荐使用“相对误差”的方式比较浮点数。 * [Sonar-1244: Floating point numbers should not be tested for equality](https://rules.sonarsource.com/java/RSPEC-1244) ---- **Rule 4. 数字类型的计算原则** **4.1【强制】数字运算表达式,因为先进行等式右边的运算,再赋值给等式左边的变量,所以等式两边的类型要一致** 例子1: int与int相除后,哪怕被赋值给float或double,结果仍然是四舍五入取整的int。 需要强制将除数或被除数转换为float或double。 ```java double d = 24/7; //结果是3.0 double d = (double)24/7; //结果是正确的3.42857 ``` 例子2: int与int相乘,哪怕被赋值给long,仍然会溢出。 需要强制将乘数的一方转换为long。 ```java long l = Integer.MAX_VALUE * 2; // 结果是溢出的-2 long l = Integer.MAX_VALUE * 2L; //结果是正确的4294967294 ``` 另外,int的最大值约21亿,留意可能溢出的情况。 * [Sonar-2184: Math operands should be cast before assignment](https://rules.sonarsource.com/java/RSPEC-2184) **4.2【强制】数字取模的结果不一定是正数,负数取模的结果仍然负数** 取模做数组下标时,如果不处理负数的情况,很容易ArrayIndexOutOfBoundException。 另外,Integer.MIN_VALUE取绝对值也仍然是负数。因此,vjkit的MathUtil对上述情况做了安全的封装。 ```java -4 % 3 = -1; Math.abs(Integer.MIN_VALUE) = -2147483648; ``` * Findbugs: Style - Remainder of hashCode could be negative **4.3【推荐】 double 或 float 计算时有不可避免的精度问题** ```java float f = 0.45f/3; //结果是0.14999999 double d1 = 0.45d/3; //结果是正确的0.15 double d2 = 1.03d - 0.42d; //结果是0.6100000000000001 ``` 尽量用double而不用float,但如果是金融货币的计算,则必须使用如下选择: 选项1, 使用性能较差的BigDecimal。BigDecimal还能精确控制四舍五入或是其他取舍的方式。 选项2, 在预知小数精度的情况下,将浮点运算放大为整数计数,比如货币以"分"而不是以"元"计算。 * [Sonar-2164: Math should not be performed on floats](https://rules.sonarsource.com/java/RSPEC-2164) ---- **Rule 5. 【推荐】如果变量值仅有有限的可选值,用枚举类来定义常量** 尤其是变量还希望带有名称之外的延伸属性时,如下例: ```java //WRONG public String MONDAY = "MONDAY"; public int MONDAY_SEQ = 1; //RIGHT public enum SeasonEnum { SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4); int seq; SeasonEnum(int seq) { this.seq = seq; } } ``` 业务代码中不要依赖ordinary()函数进行业务运算,而是自定义数字属性,以免枚举值的增减调序造成影响。 例外:永远不会有变化的枚举,比如上例的一年四季。 ---- **Rule 6. 字符串拼接的原则** **6.1 【推荐】 当字符串拼接不在一个命令行内写完,而是存在多次拼接时(比如循环),使用StringBuilder的append()** ```java String s = "hello" + str1 + str2; //Almost OK,除非初始长度有问题,见第3点. String s = "hello"; //WRONG if (condition) { s += str1; } String str = "start"; //WRONG for (int i = 0; i < 100; i++) { str = str + "hello"; } ``` 反编译出的字节码文件显示,其实每条用`+`进行字符拼接的语句,都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象。所以上面两个错误例子,会重复构造StringBuilder,重复toString()造成资源浪费。 * [Sonar-1643: Strings should not be concatenated using '+' in a loop](https://rules.sonarsource.com/java/RSPEC-1643) **6.2 【强制】 字符串拼接对象时,不要显式调用对象的toString()** 如上,`+`实际是StringBuilder,本身会调用对象的toString(),且能很好的处理null的情况。 ```java //WRONG str = "result:" + myObject.toString(); // myObject 为 null时,抛 NPE //RIGHT str = "result:" + myObject; // myObject 为 null 时,输出 result:null ``` **6.3【强制】使用StringBuilder,而不是有所有方法都有同步修饰符的StringBuffer** 因为内联不成功,逃逸分析并不能抹除StringBuffer上的同步修饰符 * [Sonar-1149: Synchronized classes Vector, Hashtable, Stack and StringBuffer should not be used](https://rules.sonarsource.com/java/RSPEC-1149) **6.4 【推荐】当拼接后字符串的长度远大于16时,指定StringBuilder的大概长度,避免容量不足时的成倍扩展** **6.5 【推荐】如果字符串长度很大且频繁拼接,可考虑ThreadLocal重用StringBuilder对象** 参考BigDecimal的toString()实现,及vjkit中的StringBuilderHolder。 ---- **Rule 7. 【推荐】字符操作时,优先使用字符参数,而不是字符串,能提升性能** ```java //WRONG str.indexOf("e"); //RIGHT stringBuilder.append('a'); str.indexOf('e'); str.replace('m','z'); ``` 其他包括split等方法,在JDK String中未提供针对字符参数的方法,可考虑使用Apache Commons StringUtils 或Guava的Splitter。 * [Sonar-3027: String function use should be optimized for single characters](https://rules.sonarsource.com/java/RSPEC-3027) ---- **Rule 8. 【推荐】利用好正则表达式的预编译功能,可以有效加快正则匹配速度** 反例: ```java //直接使用String的matches()方法 result = "abc".matches("[a-zA-z]"); //每次重新构造Pattern Pattern pattern = Pattern.compile("[a-zA-z]"); result = pattern.matcher("abc").matches(); ``` 正例: ```java //在某个地方预先编译Pattern,比如类的静态变量 private static Pattern pattern = Pattern.compile("[a-zA-z]"); ... //真正使用Pattern的地方 result = pattern.matcher("abc").matches(); ``` ---- ================================================ FILE: docs/standard/chapter08.md ================================================ # (八) 集合处理 **Rule 1. 【推荐】底层数据结构是数组的集合,指定集合初始大小** 底层数据结构为数组的集合包括 ArrayList,HashMap,HashSet,ArrayDequeue等。 数组有大小限制,当超过容量时,需要进行复制式扩容,新申请一个是原来容量150% or 200%的数组,将原来的内容复制过去,同时浪费了内存与性能。HashMap/HashSet的扩容,还需要所有键值对重新落位,消耗更大。 默认构造函数使用默认的数组大小,比如ArrayList默认大小为10,HashMap为16。因此建议使用ArrayList(int initialCapacity)等构造函数,明确初始化大小。 HashMap/HashSet的初始值还要考虑加载因子: 为了降低哈希冲突的概率(Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条需要遍历的Entry链),默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定`100/0.75 +1=135`的数组大小。vjkit的MapUtil的Map创建函数封装了该计算。 如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。 ---- **Rule 2. 【推荐】尽量使用新式的foreach语法遍历Collection与数组** foreach是语法糖,遍历集合的实际字节码等价于基于Iterator的循环。 foreach代码一来代码简洁,二来有效避免了有多个循环或嵌套循环时,因为不小心的复制粘贴,用错了iterator或循环计数器(i,j)的情况。 ---- **Rule 3. 【强制】不要在foreach循环里进行元素的remove/add操作,remove元素可使用Iterator方式** ```java //WRONG for (String str : list) { if (condition) { list.remove(str); } } //RIGHT Iterator it = list.iterator(); while (it.hasNext()) { String str = it.next(); if (condition) { it.remove(); } } ``` * Facebook-Contrib: Correctness - Method modifies collection element while iterating * Facebook-Contrib: Correctness - Method deletes collection element while iterating ---- **Rule 4. 【强制】使用entrySet遍历Map类集合Key/Value,而不是keySet 方式进行遍历** keySet遍历的方式,增加了N次用key获取value的查询。 * [Sonar-2864:"entrySet()" should be iterated when both the key and value are needed](https://rules.sonarsource.com/java/RSPEC-2864) ---- **Rule 5. 【强制】当对象用于集合时,下列情况需要重新实现hashCode()和 equals()** 1) 以对象做为Map的KEY时; 2) 将对象存入Set时。 上述两种情况,都需要使用hashCode和equals比较对象,默认的实现会比较是否同一个对象(对象的引用相等)。 另外,对象放入集合后,会影响hashCode(),equals()结果的属性,将不允许修改。 * [Sonar-2141:Classes that don't define "hashCode()" should not be used in hashes](https://rules.sonarsource.com/java/RSPEC-2141) ---- **Rule 6. 【强制】高度注意各种Map类集合Key/Value能不能存储null值的情况** | Map | Key | Value | | -------- | -------- |-------- | |HashMap|Nullable | Nullable| |ConcurrentHashMap| NotNull| NotNull| |TreeMap| NotNull| Nullable | 由于HashMap的干扰,很多人认为ConcurrentHashMap是可以置入null值。同理,Set中的value实际是Map中的key。 ---- **Rule 7. 【强制】长生命周期的集合,里面内容需要及时清理,避免内存泄漏** 长生命周期集合包括下面情况,都要小心处理。 1) 静态属性定义; 2) 长生命周期对象的属性; 3) 保存在ThreadLocal中的集合。 如无法保证集合的大小是有限的,使用合适的缓存方案代替直接使用HashMap。 另外,如果使用WeakHashMap保存对象,当对象本身失效时,就不会因为它在集合中存在引用而阻止回收。但JDK的WeakHashMap并不支持并发版本,如果需要并发可使用Guava Cache的实现。 ---- **Rule 8. 【强制】集合如果存在并发修改的场景,需要使用线程安全的版本** 1) 著名的反例,HashMap扩容时,遇到并发修改可能造成100%CPU占用。 推荐使用`java.util.concurrent(JUC)`工具包中的并发版集合,如ConcurrentHashMap等,优于使用Collections.synchronizedXXX()系列函数进行同步化封装(等价于在每个方法都加上synchronized关键字)。 例外:ArrayList所对应的CopyOnWriteArrayList,每次更新时都会复制整个数组,只适合于读多写很少的场景。如果频繁写入,可能退化为使用Collections.synchronizedList(list)。 2) 即使线程安全类仍然要注意函数的正确使用。 例如:即使用了 ConcurrentHashMap,但直接使用 get/put 方法,仍然可能会多线程间互相覆盖。 ```java //WRONG E e = map.get(key); if (e == null) { e = new E(); map.put(key, e); //仍然能两条线程并发执行put,互相覆盖 } return e; //RIGHT E e = map.get(key); if (e == null) { e = new E(); E previous = map.putIfAbsent(key, e); if(previous != null) { return previous; } } return e; ``` ---- **Rule 9. 【推荐】正确使用集合泛型的通配符** `List`并不是`List`的子类,如果希望泛型的集合能向上向下兼容转型,而不仅仅适配唯一类,则需定义通配符,可以按需要extends 和 super的字面意义,也可以遵循`PECS(Producer Extends Consumer Super)`原则: 1) 如果集合要被读取,定义成`` ```java Class Stack{ public void pushAll(Iterable src){ for (E e: src) push(e); } } Stack stack = new Stack(); Iterable integers = ...; stack.pushAll(integers); ``` 2) 如果集合要被写入,定义成`` ```java Class Stack{ public void popAll(Collection dist){ while(!isEmpty()) dist.add(pop); } } Stack stack = new Stack(); Collection objects = ...; stack.popAll(objects); ``` ---- **Rule 10. 【推荐】`List`, `List` 与 `List`的选择** 定义成`List`,会被IDE提示需要定义泛型。 如果实在无法确定泛型,就仓促定义成`List`来蒙混过关的话,该list只能读,不能增改。定义成`List`呢,如规则9所述,`List` 并不是`List`的子类,除非函数定义使用了通配符。 因此实在无法明确其泛型时,使用`List`也是可以的。 ---- **Rule 11. 【推荐】如果Key只有有限的可选值,先将Key封装成Enum,并使用EnumMap** EnumMap,以Enum为Key的Map,内部存储结构为`Object[enum.size]`,访问时以`value = Object[enum.ordinal()]`获取值,同时具备HashMap的清晰结构与数组的性能。 ```java public enum COLOR { RED, GREEN, BLUE, ORANGE; } EnumMap moodMap = new EnumMap (COLOR.class); ``` * [Sonar-1640: Maps with keys that are enum values should be replaced with EnumMap](https://rules.sonarsource.com/java/RSPEC-1640) ---- **Rule 12. 【推荐】Array 与 List互转的正确写法** ```java // list -> array,构造数组时不需要设定大小 String[] array = (String[])list.toArray(); //WRONG; String[] array = list.toArray(new String[0]); //RIGHT String[] array = list.toArray(new String[list.size()]); //RIGHT,但list.size()可用0代替。 // array -> list //非原始类型数组,且List不能再扩展 List list = Arrays.asList(array); //非原始类型数组, 但希望List能再扩展 List list = new ArrayList(array.length); Collections.addAll(list, array); //原始类型数组,JDK8 List myList = Arrays.stream(intArray).boxed().collect(Collectors.toList()); //原始类型数组,JDK7则要自己写个循环来加入了 ``` Arrays.asList(array),如果array是原始类型数组如int[],会把整个array当作List的一个元素,String[] 或 Foo[]则无此问题。 Collections.addAll()实际是循环加入元素,性能相对较低,同样会把int[]认作一个元素。 * Facebook-Contrib: Correctness - Impossible downcast of toArray() result * Facebook-Contrib: Correctness - Method calls Array.asList on an array of primitive values ---- ================================================ FILE: docs/standard/chapter09.md ================================================ # (九) 并发处理 **Rule 1. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯** 1)创建单条线程时直接指定线程名称 ```java Thread t = new Thread(); t.setName("cleanup-thread"); ``` 2) 线程池则使用guava或自行封装的ThreadFactory,指定命名规则。 ```java //guava 或自行封装的ThreadFactory ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(threadNamePrefix + "-%d").build(); ThreadPoolExecutor executor = new ThreadPoolExecutor(..., threadFactory, ...); ``` ---- **Rule 2. 【推荐】尽量使用线程池来创建线程** 除特殊情况,尽量不要自行创建线程,更好的保护线程资源。 ```java //WRONG Thread thread = new Thread(...); thread.start(); ``` 同理,定时器也不要使用Timer,而应该使用ScheduledExecutorService。 因为Timer只有单线程,不能并发的执行多个在其中定义的任务,而且如果其中一个任务抛出异常,整个Timer也会挂掉,而ScheduledExecutorService只有那个没捕获到异常的任务不再定时执行,其他任务不受影响。 ---- **Rule 3. 【强制】线程池不允许使用 Executors去创建,避资源耗尽风险** Executors返回的线程池对象的弊端 : 1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 应通过 new ThreadPoolExecutor(xxx,xxx,xxx,xxx)这样的方式,更加明确线程池的运行规则,合理设置Queue及线程池的core size和max size,建议使用vjkit封装的ThreadPoolBuilder。 ---- **Rule 4. 【强制】正确停止线程** Thread.stop()不推荐使用,强行的退出太不安全,会导致逻辑不完整,操作不原子,已被定义成Deprecate方法。 停止单条线程,执行Thread.interrupt()。 停止线程池: * ExecutorService.shutdown(): 不允许提交新任务,等待当前任务及队列中的任务全部执行完毕后退出; * ExecutorService.shutdownNow(): 通过Thread.interrupt()试图停止所有正在执行的线程,并不再处理还在队列中等待的任务。 最优雅的退出方式是先执行shutdown(),再执行shutdownNow(),vjkit的`ThreadPoolUtil`进行了封装。 注意,Thread.interrupt()并不保证能中断正在运行的线程,需编写可中断退出的Runnable,见规则5。 ---- **Rule 5. 【强制】编写可停止的Runnable** 执行Thread.interrupt()时,如果线程处于sleep(), wait(), join(), lock.lockInterruptibly()等blocking状态,会抛出InterruptedException,如果线程未处于上述状态,则将线程状态设为interrupted。 因此,如下的代码无法中断线程: ```java public void run() { while (true) { //WRONG,无判断线程状态。 sleep(); } public void sleep() { try { Thread.sleep(1000); } catch (InterruptedException e) { logger.warn("Interrupted!", e); //WRONG,吃掉了异常,interrupt状态未再传递 } } } ``` **5.1 正确处理InterruptException** 因为InterruptException异常是个必须处理的Checked Exception,所以run()所调用的子函数很容易吃掉异常并简单的处理成打印日志,但这等于停止了中断的传递,外层函数将收不到中断请求,继续原有循环或进入下一个堵塞。 正确处理是调用`Thread.currentThread().interrupt();` 将中断往外传递。 ```java //RIGHT public void myMethod() { try { ... } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } ``` * [Sonar-2142: "InterruptedException" should not be ignored](https://rules.sonarsource.com/java/RSPEC-2142) **5.2 主循环及进入阻塞状态前要判断线程状态** ```java //RIGHT public void run() { try { while (!Thread.isInterrupted()) { // do stuff } } catch (InterruptedException e) { logger.warn("Interrupted!", e); } } ``` 其他如Thread.sleep()的代码,在正式sleep前也会判断线程状态。 ---- **Rule 6. 【强制】Runnable中必须捕获一切异常** 如果Runnable中没有捕获RuntimeException而向外抛出,会发生下列情况: 1) ScheduledExecutorService执行定时任务,任务会被中断,该任务将不再定时调度,但线程池里的线程还能用于其他任务。 2) ExecutorService执行任务,当前线程会中断,线程池需要创建新的线程来响应后续任务。 3) 如果没有在ThreadFactory设置自定义的UncaughtExceptionHanlder,则异常最终只打印在System.err,而不会打印在项目的日志中。 因此建议自写的Runnable都要保证捕获异常; 如果是第三方的Runnable,可以将其再包裹一层vjkit中的SafeRunnable。 ```java executor.execute(ThreadPoolUtil.safeRunner(runner)); ``` ---- **Rule 7. 【强制】全局的非线程安全的对象可考虑使用ThreadLocal存放** 全局变量包括单例对象,static成员变量。 著名的非线程安全类包括SimpleDateFormat,MD5/SHA1的Digest。 对这些类,需要每次使用时创建。 但如果创建有一定成本,可以使用ThreadLocal存放并重用。 ThreadLocal变量需要定义成static,并在每次使用前重置。 ```java private static final ThreadLocal SHA1_DIGEST = new ThreadLocal() { @Override protected MessageDigest initialValue() { try { return MessageDigest.getInstance("SHA"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("...", e); } } }; public void digest(byte[] input) { MessageDigest digest = SHA1_DIGEST.get(); digest.reset(); return digest.digest(input); } ``` * [Sonar-2885: Non-thread-safe fields should not be static](https://rules.sonarsource.com/java/RSPEC-2885) * Facebook-Contrib: Correctness - Field is an instance based ThreadLocal variable ---- **Rule 8. 【推荐】缩短锁** 1) 能锁区块,就不要锁整个方法体; ```java //锁整个方法,等价于整个方法体内synchronized(this) public synchronized boolean foo(){}; //锁区块方法,仅对需要保护的原子操作的连续代码块进行加锁。 public boolean foo() { synchronized(this) { ... ... } //other stuff } ``` 2)能用对象锁,就不要用类锁。 ```java //对象锁,只影响使用同一个对象加锁的线程 synchronized(this) { ... } //类锁,使用类对象作为锁对象,影响所有线程。 synchronized(A.class) { ... } ``` ---- **Rule 10. 【推荐】选择分离锁,分散锁甚至无锁的数据结构** * 分离锁: 1) 读写分离锁ReentrantReadWriteLock,读读之间不加锁,仅在写读和写写之间加锁; 2) Array Base的queue一般是全局一把锁,而Linked Base的queue一般是队头队尾两把锁。 * 分散锁(又称分段锁): 1)如JDK7的ConcurrentHashMap,分散成16把锁; 2)对于经常写,少量读的计数器,推荐使用JDK8或vjkit封装的LongAdder对象性能更好(内部分散成多个counter,减少乐观锁的使用,取值时再相加所有counter) * 无锁的数据结构: 1)完全无锁无等待的结构,如JDK8的ConcurrentHashMap; 2)基于CAS的无锁有等待的数据结构,如AtomicXXX系列。 ---- **Rule 11. 【推荐】基于ThreadLocal来避免锁** 比如Random实例虽然是线程安全的,但其实它的seed的访问是有锁保护的。因此建议使用JDK7的ThreadLocalRandom,通过在每个线程里放一个seed来避免了加锁。 ---- **Rule 12. 【推荐】规避死锁风险** 对多个资源多个对象的加锁顺序要一致。 如果无法确定完全避免死锁,可以使用带超时控制的tryLock语句加锁。 ---- **Rule 13. 【推荐】volatile修饰符,AtomicXX系列的正确使用** 多线程共享的对象,在单一线程内的修改并不保证对所有线程可见。使用volatile定义变量可以解决(解决了可见性)。 但是如果多条线程并发进行基于当前值的修改,如并发的counter++,volatile则无能为力(解决不了原子性)。 此时可使用Atomic*系列: ```java AtomicInteger count = new AtomicInteger(); count.addAndGet(2); ``` 但如果需要原子地同时对多个AtomicXXX的Counter进行操作,则仍然需要使用synchronized将改动代码块加锁。 ---- **Rule 14. 【推荐】延时初始化的正确写法** 通过双重检查锁(double-checked locking)实现延迟初始化存在隐患,需要将目标属性声明为volatile型,为了更高的性能,还要把volatile属性赋予给临时变量,写法复杂。 所以如果只是想简单的延迟初始化,可用下面的静态类的做法,利用JDK本身的class加载机制保证唯一初始化。 ```java private static class LazyObjectHolder { static final LazyObject instance = new LazyObject(); } public void myMethod() { LazyObjectHolder.instance.doSomething(); } ``` * [Sonar-2168: Double-checked locking should not be used](https://rules.sonarsource.com/java/RSPEC-2168) ---- ================================================ FILE: docs/standard/chapter10.md ================================================ # (十) 异常处理 **Rule 1. 【强制】创建异常的消耗大,只用在真正异常的场景** 构造异常时,需要获得整个调用栈,有一定消耗。 不要用来做流程控制,条件控制,因为异常的处理效率比条件判断低。 发生概率较高的条件,应该先进行检查规避,比如:IndexOutOfBoundsException,NullPointerException等,所以如果代码里捕获这些异常通常是个坏味道。 ```java //WRONG try { return obj.method(); } catch (NullPointerException e) { return false; } //RIGHT if (obj == null) { return false; } ``` * [Sonar-1696: "NullPointerException" should not be caught](https://rules.sonarsource.com/java/RSPEC-1696) ---- **Rule 2. 【推荐】在特定场景,避免每次构造异常** 如上,异常的构造函数需要获得整个调用栈。 如果异常频繁发生,且不需要打印完整的调用栈时,可以考虑绕过异常的构造函数。 1) 如果异常的message不变,将异常定义为静态成员变量; 下例定义静态异常,并简单定义一层的StackTrace。`ExceptionUtil`见vjkit。 ```java private static RuntimeException TIMEOUT_EXCEPTION = ExceptionUtil.setStackTrace(new RuntimeException("Timeout"), MyClass.class, "mymethod"); ... throw TIMEOUT_EXCEPTION; ``` 2) 如果异常的message会变化,则对静态的异常实例进行clone()再修改message。 Exception默认不是Cloneable的,`CloneableException`见vjkit。 ```java private static CloneableException TIMEOUT_EXCEPTION = new CloneableException("Timeout") .setStackTrace(My.class, "hello"); ... throw TIMEOUT_EXCEPTION.clone("Timeout for 40ms"); ``` 3)自定义异常,也可以考虑重载fillStackTrace()为空函数,但相对没那么灵活,比如无法按场景指定一层的StackTrace。 ---- **Rule 3. 【推荐】自定义异常,建议继承`RuntimeException`** 详见《Clean Code》,争论已经结束,不再推荐原本初衷很好的CheckedException。 因为CheckedException需要在抛出异常的地方,与捕获处理异常的地方之间,层层定义throws XXX来传递Exception,如果底层代码改动,将影响所有上层函数的签名,导致编译出错,对封装的破坏严重。对CheckedException的处理也给上层程序员带来了额外的负担。因此其他语言都没有CheckedException的设计。 ---- **Rule 4. 【推荐】异常日志应包含排查问题的足够信息** 异常信息应包含排查问题时足够的上下文信息。 捕获异常并记录异常日志的地方,同样需要记录没有包含在异常信息中,而排查问题需要的信息,比如捕获处的上下文信息。 ```java //WRONG new TimeoutException("timeout"); logger.error(e.getMessage(), e); //RIGHT new TimeoutException("timeout:" + eclapsedTime + ", configuration:" + configTime); logger.error("user[" + userId + "] expired:" + e.getMessage(), e); ``` * Facebook-Contrib: Style - Method throws exception with static message string ---- **Rule 5. 异常抛出的原则** **5.1 【推荐】尽量使用JDK标准异常,项目标准异常** 尽量使用JDK标准的Runtime异常如`IllegalArgumentException`,`IllegalStateException`,`UnsupportedOperationException`,项目定义的Exception如`ServiceException`。 **5.2 【推荐】根据调用者的需要来定义异常类,直接使用`RuntimeException`是允许的** 是否定义独立的异常类,关键是调用者会如何处理这个异常,如果没有需要特别的处理,直接抛出RuntimeException也是允许的。 ---- **Rule 6. 异常捕获的原则** **6.1 【推荐】按需要捕获异常,捕获`Exception`或`Throwable`是允许的** 如果无特殊处理逻辑,统一捕获Exception统一处理是允许的。 捕获Throwable是为了捕获Error类异常,包括其实无法处理的`OOM` `StackOverflow` `ThreadDeath`,以及类加载,反射时可能抛出的`NoSuchMethodError` `NoClassDefFoundError`等。 **6.2【推荐】多个异常的处理逻辑一致时,使用JDK7的语法避免重复代码** ```java try { ... } catch (AException | BException | CException ex) { handleException(ex); } ``` * [Sonar-2147: Catches should be combined](https://rules.sonarsource.com/java/RSPEC-2147) ---- **Rule 7.异常处理的原则** **7.1 【强制】捕获异常一定要处理;如果故意捕获并忽略异常,须要注释写明原因** 方便后面的阅读者知道,此处不是漏了处理。 ```java //WRONG try { } catch(Exception e) { } //RIGHT try { } catch(Exception ignoredExcetpion) { //continue the loop } ``` **7.2 【强制】异常处理不能吞掉原异常,要么在日志打印,要么在重新抛出的异常里包含原异常** ```java //WRONG throw new MyException("message"); //RIGHT 记录日志后抛出新异常,向上次调用者屏蔽底层异常 logger.error("message", ex); throw new MyException("message"); //RIGHT 传递底层异常 throw new MyException("message", ex); ``` * [Sonar-1166: Exception handlers should preserve the original exceptions](https://rules.sonarsource.com/java/RSPEC-1166),其中默认包含了InterruptedException, NumberFormatException,NoSuchMethodException等若干例外 **7.3 【强制】如果不想处理异常,可以不进行捕获。但最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容** ---- **Rule 8. finally块的处理原则** **8.1 【强制】必须对资源对象、流对象进行关闭,或使用语法try-with-resource** 关闭动作必需放在finally块,不能放在try块 或 catch块,这是经典的错误。 更加推荐直接使用JDK7的try-with-resource语法自动关闭Closeable的资源,无需在finally块处理,避免潜在问题。 ```java try (Writer writer = ...) { writer.append(content); } ``` **8.2 【强制】如果处理过程中有抛出异常的可能,也要做try-catch,否则finally块中抛出的异常,将代替try块中抛出的异常** ```java //WRONG try { ... throw new TimeoutException(); } finally { file.close();//如果file.close()抛出IOException, 将代替TimeoutException } //RIGHT, 在finally块中try-catch try { ... throw new TimeoutException(); } finally { IOUtil.closeQuietly(file); //该方法中对所有异常进行了捕获 } ``` * [Sonar-1163: Exceptions should not be thrown in finally blocks](https://rules.sonarsource.com/java/RSPEC-1163) **8.3 【强制】不能在finally块中使用return,finally块中的return将代替try块中的return及throw Exception** ```java //WRONG try { ... return 1; } finally { return 2; //实际return 2 而不是1 } try { ... throw TimeoutException(); } finally { return 2; //实际return 2 而不是TimeoutException } ``` * [Sonar-1143: Jump statements should not occur in "finally" blocks](https://rules.sonarsource.com/java/RSPEC-1143) ---- ================================================ FILE: docs/standard/chapter11.md ================================================ # (十一) 日志规约 **Rule 1. 【强制】应用中不可直接使用日志库(Log4j、Logback)中的API,而应使用日志框架SLF4J中的API** 使用门面模式的日志框架,有利于维护各个类的日志处理方式统一。 ```java import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static Logger logger = LoggerFactory.getLogger(Foo.class); ``` ---- **Rule 2. 【推荐】对不确定会否输出的日志,采用占位符或条件判断** ```java //WRONG logger.debug("Processing trade with id: " + id + " symbol: " + symbol); ``` 如果日志级别是info,上述日志不会打印,但是会执行1)字符串拼接操作,2)如果symbol是对象,还会执行toString()方法,浪费了系统资源,最终日志却没有打印。 ```java //RIGHT logger.debug("Processing trade with id: {} symbol : {} ", id, symbol); ``` 但如果symbol.getMessage()本身是个消耗较大的动作,占位符在此时并没有帮助,须要改为条件判断方式来完全避免它的执行。 ```java //WRONG logger.debug("Processing trade with id: {} symbol : {} ", id, symbol.getMessage()); //RIGHT if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " symbol: " + symbol.getMessage()); } ``` ---- **Rule 3. 【推荐】对确定输出,而且频繁输出的日志,采用直接拼装字符串的方式** 如果这是一条WARN,ERROR级别的日志,或者确定输出的INFO级别的业务日志,直接字符串拼接,比使用占位符替换,更加高效。 Slf4j的占位符并没有魔术,每次输出日志都要进行占位符的查找,字符串的切割与重新拼接。 ```java //RIGHT logger.info("I am a business log with id: " + id + " symbol: " + symbol); //RIGHT logger.warn("Processing trade with id: " + id + " symbol: " + symbol); ``` ---- **Rule 4. 【推荐】尽量使用异步日志** 低延时的应用,使用异步输出的形式(以AsyncAppender串接真正的Appender),可减少IO造成的停顿。 需要正确配置异步队列长度及队列满的行为,是丢弃还是等待可用,业务上允许丢弃的尽量选丢弃。 ---- **Rule 5. 【强制】禁止使用性能很低的System.out()打印日志信息** 同理也禁止e.printStackTrace(); 例外: 应用启动和关闭时,担心日志框架还未初始化或已关闭。 * [Sonar-106: Standard outputs should not be used directly to log anything](https://rules.sonarsource.com/java/RSPEC-106) * [Sonar-1148: Throwable.printStackTrace(...) should not be called](https://rules.sonarsource.com/java/RSPEC-1148) ---- **Rule 6. 【强制】禁止配置日志框架输出日志打印处的类名,方法名及行号的信息** 日志框架在每次打印时,通过主动获得当前线程的StackTrace来获取上述信息的消耗非常大,尽量通过Logger名本身给出足够信息。 ---- **Rule 7. 【推荐】谨慎地记录日志,避免大量输出无效日志,信息不全的日志** 大量地输出无效日志,不利于系统性能,也不利于快速定位错误点。 记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处? ---- **Rule 8. 【推荐】使用warn级别而不是error级别,记录外部输入参数错误的情况** 如非必要,请不在此场景打印error级别日志,避免频繁报警。 error级别只记录系统逻辑出错、异常或重要的错误信息。 ---- ================================================ FILE: docs/standard/chapter12.md ================================================ # (十二) 其他规约 **Rule 1. 【参考】尽量不要让魔法值(即未经定义的数字或字符串常量)直接出现在代码中** ```java //WRONG String key = "Id#taobao_"+tradeId; cache.put(key, value); ``` 例外:-1,0,1,2,3 不认为是魔法数 * [Sonar-109: Magic numbers should not be used](https://rules.sonarsource.com/java/RSPEC-109) 但现实中所谓魔法数还是太多,该规则不能被真正执行。 ---- **Rule 2. 【推荐】时间获取的原则** 1)获取当前毫秒数System.currentTimeMillis() 而不是new Date().getTime(),后者的消耗要大得多。 2)如果要获得更精确的,且不受NTP时间调整影响的流逝时间,使用System.nanoTime()获得机器从启动到现在流逝的纳秒数。 3)如果希望在测试用例中控制当前时间的值,则使用vjkit的Clock类封装,在测试和生产环境中使用不同的实现。 ---- **Rule 3. 【推荐】变量声明尽量靠近使用的分支** 不要在一个代码块的开头把局部变量一次性都声明了(这是c语言的做法),而是在第一次需要使用它时才声明。 否则如果方法已经退出或进入其他分支,就白白初始化了变量。 ```java //WRONG Foo foo = new Foo(); if(ok){ return; } foo.bar(); ``` ---- **Rule 4. 【推荐】不要像C那样一行里做多件事情** ```java //WRONG fooBar.fChar = barFoo.lchar = 'c'; argv++; argc--; int level, size; ``` * [Sonar-1659: Multiple variables should not be declared on the same line](https://rules.sonarsource.com/java/RSPEC-1659) ---- **Rule 5. 【推荐】不要为了性能而使用JNI本地方法** Java在JIT后并不比C代码慢,JNI方法因为要反复跨越JNI与Java的边界反而有额外的性能损耗。 因此JNI方法仅建议用于调用"JDK所没有包括的, 对特定操作系统的系统调用" ---- **Rule 6. 【推荐】正确使用反射,减少性能损耗** 获取Method/Field对象的性能消耗较大, 而如果对Method与Field对象进行缓存再反复调用,则并不会比直接调用类的方法与成员变量慢(前15次使用NativeAccessor,第15次后会生成GeneratedAccessorXXX,bytecode为直接调用实际方法) ```java //用于对同一个方法多次调用 private Method method = .... public void foo(){ method.invoke(obj, args); } //用于仅会对同一个方法单次调用 ReflectionUtils.invoke(obj, methodName, args); ``` ---- **Rule 7.【推荐】可降低优先级的常见代码检查规则** 1. 接口内容的定义中,去除所有modifier,如public等。 (多个public也没啥,反正大家都看惯了) 2. 工具类,定义private构造函数使其不能被实例化。(命名清晰的工具类,也没人会去实例化它,对静态方法通过类来访问也能避免实例化) ---- ================================================ FILE: docs/standard/merge.bat ================================================ @echo off type README.md > vip-java-standard.md type chapter01.md >> vip-java-standard.md type chapter02.md >> vip-java-standard.md type chapter03.md >> vip-java-standard.md type chapter04.md >> vip-java-standard.md type chapter05.md >> vip-java-standard.md type chapter06.md >> vip-java-standard.md type chapter07.md >> vip-java-standard.md type chapter08.md >> vip-java-standard.md type chapter09.md >> vip-java-standard.md type chapter10.md >> vip-java-standard.md type chapter11.md >> vip-java-standard.md type chapter12.md >> vip-java-standard.md type ali.md >> vip-java-standard.md echo vip-java-standard.md is ready to be converted to pdf by any tools you like. echo press any key to exit. pause > nul exit ================================================ FILE: docs/standard/merge.sh ================================================ #!/bin/sh cat README.md > vip-java-standard.md cat chapter01.md >> vip-java-standard.md cat chapter02.md >> vip-java-standard.md cat chapter03.md >> vip-java-standard.md cat chapter04.md >> vip-java-standard.md cat chapter05.md >> vip-java-standard.md cat chapter06.md >> vip-java-standard.md cat chapter07.md >> vip-java-standard.md cat chapter08.md >> vip-java-standard.md cat chapter09.md >> vip-java-standard.md cat chapter10.md >> vip-java-standard.md cat chapter11.md >> vip-java-standard.md cat chapter12.md >> vip-java-standard.md cat ali.md >> vip-java-standard.md echo vip-java-standard.md is ready to be converted to pdf by any tools you like. ================================================ FILE: pom.xml ================================================ 4.0.0 org.sonatype.oss oss-parent 7 com.vip.vjtools vjtools 1.0.9-SNAPSHOT vjtools pom VJTools - VIP's core libraries and tools UTF-8 sonatype-nexus-snapshots https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-releases https://oss.sonatype.org/service/local/staging/deploy/maven2 jdk7 1.7 vjkit vjstar vjmap vjtop vjmxcli jdk8 1.8 vjkit vjstar vjmap vjtop vjmxcli standard/sonar-vj jdk9 9 vjkit vjstar release vjkit vjmap vjtop vjmxcli org.apache.maven.plugins maven-release-plugin 2.5.3 v.@{project.version} org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign scm:git:https://github.com/vipshop/vjtools.git scm:git:https://github.com/vipshop/vjtools.git https://github.com/vipshop/vjtools v.1.0.2 https://github.com/vipshop/vjtools Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo calvin Calvin Xiao calvin.xiao at vipshop.com developer +8 ================================================ FILE: standard/README.md ================================================ # Java Standard | Project | Description | | -------- | -------- | | [standard](https://vipshop.github.io/vjtools/#/standard/) | 唯品会Java开发手册 | | [code formatter](formatter) | IDE格式化模板 | | [sonar rule](sonar-vj) | Sonar规则定制示例 | ================================================ FILE: standard/formatter/README.md ================================================ # 公司通用代码格式化模板 定制原因详见[《唯品会Java开发手册》 第二章:格式规约](https://vipshop.github.io/vjtools/#/standard/chapter02),同时参考了一些Intellij IDEA默认模板的部分设置。 将下列profile下载并导入IDE即可,导入后Profile名称为`vipshop2.0`: * [Eclipse Code Formatter Profile](https://raw.githubusercontent.com/vipshop/vjtools/master/standard/formatter/vjtools-code-conventions-eclipse.xml) * [Intellij Code Formatter Profile](https://raw.githubusercontent.com/vipshop/vjtools/master/standard/formatter/vjtools-code-conventions-idea.xml) 因为Intellij导入Eclipse Profile存在问题,因此同时提供了两者的Profile。 ## 1. 与 Eclipse 4.6 的`Eclipse [build-in]`模板的区别 * 不格式化JavaDoc * 注释行宽从80改为120 * 打开format on/off标志 * 参考Intellij IDEA默认模板的修改(见后) 注意:Eclipse后来的build-in模板,代码行宽已经默认120。 ## 2. 与IDEA默认模板的区别 本模板参考了Intellij IDEA默认模板中如下部分: * 简单的if语句,如果没有括号,则格式化成同一行。(勾选Control Statement->if else->Keep simple 'if' on one line) ```java if (2 < 3) return; ``` 当然,我们还是建议用括号,此处格式化成一行只是兜底的保护。 * 主动输入的空行,最多可保留两行 (Blank Lines->Existing blank lines -> Number of empty lines to preserve 从1 改为 2) * switch 和 case 之间缩进(勾选Indentation-> Indent->Statements within switch body) ```java switch (a) { case 0: doCase0(); break; default: doDefault(); } ``` * 数组构造时不要那么多空格(取消White Space->Arrays->Array Initializers->before opening brace,after opening brace,before closing brace) ```java int[] a = new int[]{1, 2, 3}; ``` ================================================ FILE: standard/formatter/vjtools-code-conventions-eclipse.xml ================================================ ================================================ FILE: standard/formatter/vjtools-code-conventions-idea.xml ================================================ ================================================ FILE: standard/sonar-vj/README.md ================================================ # Sonar VJ 规则 ## 概述 我们使用[Sonar](https://www.sonarqube.org/)代码检查工具来辅助[《唯品会Java开发手册》](https://vipshop.github.io/vjtools/#/standard/)的落地。选择的原因是它同时提供了服务端的公共报表,及IDE端的Sonar Lint插件,而且对比PMD与FindBugs,规则也相对容易定制。 因为Sonar有些规则存在误报的情况,我们在力所能及的范围内对规则的实现进行了修改,以符合我们的规范。 ## 实现方式 根据[Writing Custom Java Rules 101](https://docs.sonarqube.org/display/PLUG/Writing+Custom+Java+Rules+101),从[Sample Project](https://github.com/SonarSource/sonar-custom-rules-examples/tree/master/java-custom-rules)复制创建,对[Sonar Java](https://github.com/SonarSource/sonar-java/tree/master/java-checks/src/main/java/org/sonar/java/checks)规则进行修改。 ## 使用方式 官方的Sonar Java Plugin在不断更新,以下修改未必对应其最新版,仅作为修改示例供大家参考(修改部分在代码中以//VJ 标注)。 如果需要直接使用,编译后扔进sonar的lib目录,重启sonar后取消对原规则的检查,改为使用这些编号一样,带标题带VJ字样的规则即可。 ## 修改规则列表 | 编号 | 等级 | 规则描述 | 修改 | | -------- | -------- |-------- | -------- | | 1068| Major | Unused "private" fields should be removed | 忽略由Lombok自动生成的getter/setter的类,私有变量不算无用变量 | | 1172| Major | Unused method parameters should be removed | 只检查private方法是否有无用参数, 忽略其他公共方法 | | 1166| Major | Exception handlers should preserve the original exceptions | 忽略异常变量名含ignore字样的检查,可以不进行处理,如catch(Exception ignore) | | 121| Major | Control structures should use curly braces | if语句忽略一般由IDE生成的equals()方法,以及if(condition) return;的单行模式| | 1068| Major |Limited dependence should be placed on operator precedence rules in expressions| 忽略三目运算符,不需要加括号来清晰优先级 | | 115| Minor| Constant names should comply with a naming convention| 忽略对枚举成员的全大写检查 | | 1312| Minor| IP addresses should not be hardcoded | 忽略对"127.0.0.1"的检查 | | 1291| Info | Track uses of "NOSONAR" comments| 忽略行内含 Exception/Throwable, System.in/System.err的//NOSOANR 检查 | ================================================ FILE: standard/sonar-vj/pom.xml ================================================ 4.0.0 com.vip sonar-vj 1.0.9-SNAPSHOT sonar-plugin 6.7.1 5.0.1.12818 1.21 2.6.2 UTF-8 org.sonarsource.sonarqube sonar-plugin-api ${sonar.version} provided org.sonarsource.java sonar-java-plugin sonar-plugin ${java.plugin.version} provided org.sonarsource.java java-frontend ${java.plugin.version} org.sonarsource.sslr-squid-bridge sslr-squid-bridge 2.6.1 org.codehaus.sonar.sslr sslr-core org.codehaus.sonar sonar-plugin-api org.codehaus.sonar.sslr sslr-xpath org.slf4j jcl-over-slf4j org.slf4j slf4j-api com.google.code.gson gson ${gson.version} org.sonarsource.sslr sslr-testing-harness ${sslr.version} test org.slf4j slf4j-api 1.6.2 junit junit 4.11 test org.assertj assertj-core 3.6.1 test ch.qos.logback logback-classic 0.9.30 test org.sonarsource.sonar-packaging-maven-plugin sonar-packaging-maven-plugin 1.17 true vj-rules VJ Rules com.vip.vjkit.sonarvj.SonarPlugin vip sonar java plugin true 6.7 org.apache.maven.plugins maven-compiler-plugin 3.7.0 1.8 1.8 ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarCheckRegistrar.java ================================================ package com.vip.vjkit.sonarvj; import com.vip.vjkit.sonarvj.checks.*; import org.sonar.plugins.java.api.CheckRegistrar; import org.sonar.plugins.java.api.JavaCheck; import java.util.Arrays; public class SonarCheckRegistrar implements CheckRegistrar { @Override public void register(RegistrarContext registrarContext) { registrarContext.registerClassesForRepository(SonarDefinition.REPOSITORY_KEY, Arrays.asList(checkClasses()), Arrays.asList(testCheckClasses())); } public static Class[] checkClasses() { return new Class[] { BadConstantNameCheck.class, OperatorPrecedenceCheck.class, UnusedMethodParameterCheck.class, UnusedPrivateFieldCheck.class, MissingCurlyBracesCheck.class, HardcodedIpCheck.class, NoSonarCheck.class, CatchUsesExceptionWithContextCheck.class }; } public static Class[] testCheckClasses() { return new Class[] {}; } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarDefinition.java ================================================ package com.vip.vjkit.sonarvj; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Iterables; import com.google.common.io.Resources; import com.google.gson.Gson; import org.apache.commons.lang.StringUtils; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rules.RuleType; import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.api.server.rule.RulesDefinitionAnnotationLoader; import org.sonar.api.utils.AnnotationUtils; import org.sonar.check.Cardinality; import org.sonar.squidbridge.annotations.RuleTemplate; import javax.annotation.Nullable; import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Locale; /** * Declare rule metadata in server repository of rules. * That allows to list the rules in the page "Rules". */ public class SonarDefinition implements RulesDefinition { // don't change that because the path is hard coded in CheckVerifier private static final String RESOURCE_BASE_PATH = "/com/vip/java/rules"; public static final String REPOSITORY_KEY = "v-squid"; private final Gson gson = new Gson(); @Override public void define(Context context) { NewRepository repository = context .createRepository(REPOSITORY_KEY, "java") .setName("VIPAnalyzer "); List checks = SonarRulesList.getChecks(); new RulesDefinitionAnnotationLoader().load(repository, Iterables.toArray(checks, Class.class)); for (Class ruleClass : checks) { newRule(ruleClass, repository); } repository.done(); } @VisibleForTesting protected void newRule(Class ruleClass, NewRepository repository) { org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(ruleClass, org.sonar.check.Rule.class); if (ruleAnnotation == null) { throw new IllegalArgumentException("No Rule annotation was found on " + ruleClass); } String ruleKey = ruleAnnotation.key(); if (StringUtils.isEmpty(ruleKey)) { throw new IllegalArgumentException("No key is defined in Rule annotation of " + ruleClass); } NewRule rule = repository.rule(ruleKey); if (rule == null) { throw new IllegalStateException("No rule was created for " + ruleClass + " in " + repository.key()); } ruleMetadata(ruleClass, rule); rule.setTemplate(AnnotationUtils.getAnnotation(ruleClass, RuleTemplate.class) != null); if (ruleAnnotation.cardinality() == Cardinality.MULTIPLE) { throw new IllegalArgumentException("Cardinality is not supported, use the RuleTemplate annotation instead for " + ruleClass); } } private String ruleMetadata(Class ruleClass, NewRule rule) { String metadataKey = rule.key(); org.sonar.java.RspecKey rspecKeyAnnotation = AnnotationUtils.getAnnotation(ruleClass, org.sonar.java.RspecKey.class); if (rspecKeyAnnotation != null) { metadataKey = rspecKeyAnnotation.value(); rule.setInternalKey(metadataKey); } addHtmlDescription(rule, metadataKey); addMetadata(rule, metadataKey); return metadataKey; } private void addMetadata(NewRule rule, String metadataKey) { URL resource = SonarDefinition.class.getResource(RESOURCE_BASE_PATH + "/" + metadataKey + "_java.json"); if (resource != null) { RuleMetatada metatada = gson.fromJson(readResource(resource), RuleMetatada.class); rule.setSeverity(metatada.defaultSeverity.toUpperCase(Locale.US)); rule.setName(metatada.title); rule.addTags(metatada.tags); rule.setType(RuleType.valueOf(metatada.type)); rule.setStatus(RuleStatus.valueOf(metatada.status.toUpperCase(Locale.US))); if (metatada.remediation != null) { rule.setDebtRemediationFunction(metatada.remediation.remediationFunction(rule.debtRemediationFunctions())); rule.setGapDescription(metatada.remediation.linearDesc); } } } private static void addHtmlDescription(NewRule rule, String metadataKey) { URL resource = SonarDefinition.class.getResource(RESOURCE_BASE_PATH + "/" + metadataKey + "_java.html"); if (resource != null) { rule.setHtmlDescription(readResource(resource)); } } private static String readResource(URL resource) { try { return Resources.toString(resource, StandardCharsets.UTF_8); } catch (IOException e) { throw new IllegalStateException("Failed to read: " + resource, e); } } private static class RuleMetatada { String title; String status; @Nullable Remediation remediation; String type; String[] tags; String defaultSeverity; } private static class Remediation { String func; String constantCost; String linearDesc; String linearOffset; String linearFactor; public DebtRemediationFunction remediationFunction(DebtRemediationFunctions drf) { if (func.startsWith("Constant")) { return drf.constantPerIssue(constantCost.replace("mn", "min")); } if ("Linear".equals(func)) { return drf.linear(linearFactor.replace("mn", "min")); } return drf.linearWithOffset(linearFactor.replace("mn", "min"), linearOffset.replace("mn", "min")); } } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarPlugin.java ================================================ package com.vip.vjkit.sonarvj; import org.sonar.api.Plugin; /** * Created by cloud.huang on 18/1/5. */ public class SonarPlugin implements Plugin { @Override public void define(Context context) { context.addExtension(SonarDefinition.class); context.addExtension(SonarCheckRegistrar.class); } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarRulesList.java ================================================ package com.vip.vjkit.sonarvj; import com.google.common.collect.ImmutableList; import com.vip.vjkit.sonarvj.checks.*; import org.sonar.plugins.java.api.JavaCheck; import java.util.List; /** * Created by cloud.huang on 18/1/5. */ public class SonarRulesList { public static List getChecks() { return ImmutableList.builder().addAll(getJavaChecks()).addAll(getJavaTestChecks()).build(); } public static List> getJavaChecks() { return ImmutableList.>builder() .add(BadConstantNameCheck.class) .add(OperatorPrecedenceCheck.class) .add(UnusedPrivateFieldCheck.class) .add(UnusedMethodParameterCheck.class) .add(MissingCurlyBracesCheck.class) .add(HardcodedIpCheck.class) .add(NoSonarCheck.class) .add(CatchUsesExceptionWithContextCheck.class) .build(); } public static List> getJavaTestChecks() { return ImmutableList.>builder() .build(); } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/BadConstantNameCheck.java ================================================ /* * SonarQube Java Copyright (C) 2012-2018 SonarSource SA mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General * Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.vip.vjkit.sonarvj.checks; import java.util.List; import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.java.checks.serialization.SerializableContract; import org.sonar.java.resolve.JavaType; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.Modifier; import org.sonar.plugins.java.api.tree.ModifierKeywordTree; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.VariableTree; import com.google.common.collect.ImmutableList; /** * 不对枚举的命名进行检查 * * 另顺便修正了isConstantTyp一个BUG,加强了报错信息 * * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/naming/BadConstantNameCheck.java * * 0d54578 Jan 8, 2018 */ @Rule(key = "S115") public class BadConstantNameCheck extends IssuableSubscriptionVisitor { private static final String DEFAULT_FORMAT = "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"; @RuleProperty(key = "format", description = "Regular expression used to check the constant names against.", defaultValue = "" + DEFAULT_FORMAT) public String format = DEFAULT_FORMAT; private Pattern pattern = null; @Override public List nodesToVisit() { // VJ Replace return ImmutableList.of(Tree.Kind.CLASS, Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE); // return ImmutableList.of(Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE); } @Override public void scanFile(JavaFileScannerContext context) { if (pattern == null) { pattern = Pattern.compile(format, Pattern.DOTALL); } super.scanFile(context); } @Override public void visitNode(Tree tree) { ClassTree classTree = (ClassTree) tree; for (Tree member : classTree.members()) { if (member.is(Tree.Kind.VARIABLE) && hasSemantic()) { VariableTree variableTree = (VariableTree) member; Type symbolType = variableTree.type().symbolType(); if (isConstantType(symbolType) && (classTree.is(Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE) || isStaticFinal(variableTree))) { checkName(variableTree); } } // VJ Remove // // else if (member.is(Tree.Kind.ENUM_CONSTANT)) { // checkName((VariableTree) member); // } } } //VJ: 加上symbolType instanceof JavaType的判断,防止数组转换出错 private static boolean isConstantType(Type symbolType) { return symbolType.isPrimitive() || symbolType.is("java.lang.String") || symbolType.is("java.lang.Byte") || symbolType.is("java.lang.Character") || symbolType.is("java.lang.Short") || symbolType.is("java.lang.Integer") || symbolType.is("java.lang.Long") || symbolType.is("java.lang.Float") || symbolType.is("java.lang.Double") || symbolType.is("java.lang.Boolean"); } private void checkName(VariableTree variableTree) { String name = variableTree.simpleName().name(); if (!SerializableContract.SERIAL_VERSION_UID_FIELD.equals(name) && !pattern.matcher(name).matches()) { //VJ 报错信息加上变量名 reportIssue(variableTree.simpleName(), "Rename this constant name '" + name + "' to match the regular expression '" + format + "'."); } } private static boolean isStaticFinal(VariableTree variableTree) { boolean isStatic = false; boolean isFinal = false; for (ModifierKeywordTree modifierKeywordTree : variableTree.modifiers().modifiers()) { Modifier modifier = modifierKeywordTree.modifier(); if (modifier == Modifier.STATIC) { isStatic = true; } if (modifier == Modifier.FINAL) { isFinal = true; } } return isStatic && isFinal; } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/CatchUsesExceptionWithContextCheck.java ================================================ package com.vip.vjkit.sonarvj.checks; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.java.checks.helpers.ExpressionsHelper; import org.sonar.java.matcher.MethodMatcher; import org.sonar.java.matcher.TypeCriteria; import org.sonar.plugins.java.api.JavaFileScanner; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.*; import org.sonar.plugins.java.api.tree.Tree.Kind; import java.util.*; /** * catch异常的变量名包含ignore,忽略检查 */ @Rule(key = "S1166") public class CatchUsesExceptionWithContextCheck extends BaseTreeVisitor implements JavaFileScanner { private static final String EXCLUDED_EXCEPTION_TYPE = "java.lang.InterruptedException, " + "java.lang.NumberFormatException, " + "java.lang.NoSuchMethodException, " + "java.text.ParseException, " + "java.net.MalformedURLException, " + "java.time.format.DateTimeParseException"; @RuleProperty( key = "exceptions", description = "List of exceptions which should not be checked", defaultValue = "" + EXCLUDED_EXCEPTION_TYPE) public String exceptionsCommaSeparated = EXCLUDED_EXCEPTION_TYPE; private JavaFileScannerContext context; private Deque> validUsagesStack; private Iterable exceptions; private List exceptionIdentifiers; private Set excludedCatchTrees = new HashSet<>(); @Override public void scanFile(JavaFileScannerContext context) { this.context = context; validUsagesStack = new ArrayDeque<>(); exceptions = Splitter.on(",").trimResults().split(exceptionsCommaSeparated); exceptionIdentifiers = Lists.newArrayList(); for (String exception : exceptions) { exceptionIdentifiers.add(exception.substring(exception.lastIndexOf('.') + 1)); } if (context.getSemanticModel() != null) { scan(context.getTree()); } excludedCatchTrees.clear(); } @Override public void visitTryStatement(TryStatementTree tree) { if (containsEnumValueOf(tree.block())) { tree.catches().stream() .filter(c -> c.parameter().symbol().type().is("java.lang.IllegalArgumentException")) .findAny() .ifPresent(excludedCatchTrees::add); } super.visitTryStatement(tree); } private static boolean containsEnumValueOf(Tree tree) { EnumValueOfVisitor visitor = new EnumValueOfVisitor(); tree.accept(visitor); return visitor.hasEnumValueOf; } private static class EnumValueOfVisitor extends BaseTreeVisitor { private static final MethodMatcher ENUM_VALUE_OF = MethodMatcher.create() .typeDefinition(TypeCriteria.subtypeOf("java.lang.Enum")) .name("valueOf") .withAnyParameters(); private boolean hasEnumValueOf = false; @Override public void visitMethodInvocation(MethodInvocationTree tree) { if (ENUM_VALUE_OF.matches(tree)) { hasEnumValueOf = true; } super.visitMethodInvocation(tree); } @Override public void visitClass(ClassTree tree) { // skip anonymous classes } @Override public void visitLambdaExpression(LambdaExpressionTree lambdaExpressionTree) { // skip lambdas } } @Override public void visitCatch(CatchTree tree) { if (!isExcludedType(tree.parameter().type()) && !excludedCatchTrees.contains(tree)) { Symbol exception = tree.parameter().symbol(); //catch异常的变量名包含ignore,忽略检查 if (StringUtils.containsIgnoreCase(exception.name(), "ignore")) { return; } validUsagesStack.addFirst(Lists.newArrayList(exception.usages())); super.visitCatch(tree); Collection usages = validUsagesStack.pop(); if (usages.isEmpty()) { context.reportIssue(this, tree.parameter(), "Either log or rethrow this exception."); } } } @Override public void visitMemberSelectExpression(MemberSelectExpressionTree tree) { IdentifierTree identifier = null; ExpressionTree expression = tree.expression(); if (expression.is(Kind.IDENTIFIER)) { identifier = (IdentifierTree) expression; } else if (expression.is(Kind.PARENTHESIZED_EXPRESSION) && ((ParenthesizedTree) expression).expression().is(Kind.IDENTIFIER)) { identifier = (IdentifierTree) ((ParenthesizedTree) expression).expression(); } if (!validUsagesStack.isEmpty() && identifier != null) { Iterator> iterator = validUsagesStack.iterator(); while (iterator.hasNext()) { iterator.next().remove(identifier); } } super.visitMemberSelectExpression(tree); } private boolean isExcludedType(Tree tree) { return isUnqualifiedExcludedType(tree) || isQualifiedExcludedType(tree); } private boolean isUnqualifiedExcludedType(Tree tree) { return tree.is(Kind.IDENTIFIER) && exceptionIdentifiers.contains(((IdentifierTree) tree).name()); } private boolean isQualifiedExcludedType(Tree tree) { if (!tree.is(Kind.MEMBER_SELECT)) { return false; } return Iterables.contains(exceptions, concatenate((MemberSelectExpressionTree) tree)); } public static String concatenate(ExpressionTree tree) { if(tree == null) { return ""; } Deque pieces = new LinkedList<>(); ExpressionTree expr = tree; while (expr.is(Tree.Kind.MEMBER_SELECT)) { MemberSelectExpressionTree mse = (MemberSelectExpressionTree) expr; pieces.push(mse.identifier().name()); pieces.push("."); expr = mse.expression(); } if (expr.is(Tree.Kind.IDENTIFIER)) { IdentifierTree idt = (IdentifierTree) expr; pieces.push(idt.name()); } StringBuilder sb = new StringBuilder(); for (String piece: pieces) { sb.append(piece); } return sb.toString(); } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/HardcodedIpCheck.java ================================================ package com.vip.vjkit.sonarvj.checks; import com.google.common.base.Splitter; import org.sonar.check.Rule; import org.sonar.java.model.LiteralUtils; import org.sonar.plugins.java.api.JavaFileScanner; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.LiteralTree; import org.sonar.plugins.java.api.tree.Tree; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 忽略127.0.0.1 * * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/HardcodedIpCheck.java * * 0d54578 Jan 8, 2018 */ @Rule(key = "S1313") public class HardcodedIpCheck extends BaseTreeVisitor implements JavaFileScanner { private static final Matcher IP = Pattern .compile("([^\\d.]*\\/)?(?(?:\\d{1,3}\\.){3}\\d{1,3}(?!\\d|\\.))(\\/.*)?").matcher(""); private JavaFileScannerContext context; @Override public void scanFile(final JavaFileScannerContext context) { this.context = context; scan(context.getTree()); } @Override public void visitLiteral(LiteralTree tree) { if (tree.is(Tree.Kind.STRING_LITERAL)) { String value = LiteralUtils.trimQuotes(tree.value()); IP.reset(value); if (IP.matches()) { String ip = IP.group("ip"); // VJ:ADD 忽略127.0.0.1 if ("127.0.0.1".equals(ip)) { return; } // VJ:END if (areAllBelow256(Splitter.on('.').split(ip))) { context.reportIssue(this, tree, "Make this IP \"" + ip + "\" address configurable."); } } } } private static boolean areAllBelow256(Iterable numbersAsStrings) { for (String numberAsString : numbersAsStrings) { if (Integer.valueOf(numberAsString) > 255) { return false; } } return true; } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/MissingCurlyBracesCheck.java ================================================ package com.vip.vjkit.sonarvj.checks; import com.google.common.collect.ImmutableList; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.tree.*; import java.util.List; /** * 1. equals 方法忽略if的检查 2. if(conditon) return true; 忽略在同一行的模式 * * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/MissingCurlyBracesCheck.java * * 0d54578 Jan 8, 2018 */ @Rule(key = "S121") public class MissingCurlyBracesCheck extends IssuableSubscriptionVisitor { @Override public List nodesToVisit() { return ImmutableList.of(Tree.Kind.IF_STATEMENT, Tree.Kind.FOR_EACH_STATEMENT, Tree.Kind.FOR_STATEMENT, Tree.Kind.WHILE_STATEMENT, Tree.Kind.DO_STATEMENT); } @Override public void visitNode(Tree tree) { switch (tree.kind()) { case WHILE_STATEMENT: WhileStatementTree whileStatementTree = (WhileStatementTree) tree; checkStatement(whileStatementTree.whileKeyword(), whileStatementTree.statement()); break; case DO_STATEMENT: DoWhileStatementTree doWhileStatementTree = (DoWhileStatementTree) tree; checkStatement(doWhileStatementTree.doKeyword(), doWhileStatementTree.statement()); break; case FOR_STATEMENT: ForStatementTree forStatementTree = (ForStatementTree) tree; checkStatement(forStatementTree.forKeyword(), forStatementTree.statement()); break; case FOR_EACH_STATEMENT: ForEachStatement forEachStatement = (ForEachStatement) tree; checkStatement(forEachStatement.forKeyword(), forEachStatement.statement()); break; case IF_STATEMENT: checkIfStatement((IfStatementTree) tree); break; default: break; } } private void checkIfStatement(IfStatementTree ifStmt) { // equals 方法忽略if的检查, 如果if 与处理函数在同一行忽略。 if (isInEqualsMethod(ifStmt)) { return; } if (!isSameLine(ifStmt)) { checkStatement(ifStmt.ifKeyword(), ifStmt.thenStatement()); } StatementTree elseStmt = ifStmt.elseStatement(); if (elseStmt != null && !elseStmt.is(Tree.Kind.IF_STATEMENT)) { checkStatement(ifStmt.elseKeyword(), elseStmt); } } private boolean isSameLine(IfStatementTree ifStmt) { StatementTree thenStmt = ifStmt.thenStatement(); if (thenStmt.is(Tree.Kind.BLOCK)) { return false; } return ifStmt.firstToken().line() == thenStmt.firstToken().line(); } private boolean isInEqualsMethod(IfStatementTree ifStmt) { Tree tree = ifStmt.parent(); while (tree != null && !(tree instanceof MethodTree)) { tree = tree.parent(); } if (tree == null) { return false; } MethodTree methodTree = (MethodTree) tree; if (methodTree.simpleName().toString().equals("equals")) { return true; } return false; } private void checkStatement(SyntaxToken reportToken, StatementTree statement) { if (!statement.is(Tree.Kind.BLOCK)) { reportIssue(reportToken, "Missing curly brace."); } } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/NoSonarCheck.java ================================================ /* * SonarQube Java Copyright (C) 2012-2018 SonarSource SA mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General * Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.vip.vjkit.sonarvj.checks; import java.util.Collections; import java.util.List; import org.sonar.check.Rule; import org.sonar.java.RspecKey; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.tree.Tree; /** * 忽略在system.out, e.printStacktrace, catch exception 后设置的NONSAR * * 基于TooLongLineCheck完全重写 */ @Rule(key = "NoSonar") @RspecKey("S1291") public class NoSonarCheck extends IssuableSubscriptionVisitor { private static final String PATTERN = "NOSONAR"; private static final String[] IGNORE_PATTERNS = new String[] { "Exception", "Throwable", "System.out", "System.err", "printStackTrace" }; private static final String MESSAGE = "//NOSONAR found: "; @Override public List nodesToVisit() { return Collections.emptyList(); } @Override public void scanFile(JavaFileScannerContext context) { super.context = context; super.scanFile(context); visitFile(); } private void visitFile() { List lines = context.getFileLines(); for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); if (line.contains(PATTERN) && !ignoredLine(line)) { addIssue(i + 1, MESSAGE + line); } } } private boolean ignoredLine(String line) { for (String ignorePattern : IGNORE_PATTERNS) { if (line.contains(ignorePattern)) { return true; } } return false; } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/OperatorPrecedenceCheck.java ================================================ /* * SonarQube Java * Copyright (C) 2012-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.vip.vjkit.sonarvj.checks; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Iterables; import com.google.common.collect.Table; import org.apache.commons.lang.BooleanUtils; import org.sonar.check.Rule; import org.sonar.plugins.java.api.JavaFileScanner; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.tree.*; import java.util.*; /** * 三目运算符中的表达式可以不要括号 * * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/OperatorPrecedenceCheck.java * * 0d54578 Jan 8, 2018 */ @Rule(key = "S864") public class OperatorPrecedenceCheck extends BaseTreeVisitor implements JavaFileScanner { private static final Table OPERATORS_RELATION_TABLE; private static final Set ARITHMETIC_OPERATORS = EnumSet.of( Tree.Kind.MINUS, Tree.Kind.REMAINDER, Tree.Kind.MULTIPLY, Tree.Kind.PLUS ); private static final Set EQUALITY_RELATIONAL_OPERATORS = EnumSet.of( Tree.Kind.EQUAL_TO, Tree.Kind.GREATER_THAN, Tree.Kind.GREATER_THAN_OR_EQUAL_TO, Tree.Kind.LESS_THAN, Tree.Kind.LESS_THAN_OR_EQUAL_TO, Tree.Kind.NOT_EQUAL_TO ); private static final Set SHIFT_OPERATORS = EnumSet.of( Tree.Kind.LEFT_SHIFT, Tree.Kind.RIGHT_SHIFT, Tree.Kind.UNSIGNED_RIGHT_SHIFT ); private static final Tree.Kind[] CONDITIONAL_EXCLUSIONS = new Tree.Kind[]{ Tree.Kind.METHOD_INVOCATION, Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT, Tree.Kind.PARENTHESIZED_EXPRESSION, Tree.Kind.TYPE_CAST, Tree.Kind.NEW_CLASS, Tree.Kind.ARRAY_ACCESS_EXPRESSION, Tree.Kind.NEW_ARRAY }; static { OPERATORS_RELATION_TABLE = HashBasedTable.create(); put(ARITHMETIC_OPERATORS, Iterables.concat(SHIFT_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.XOR, Tree.Kind.OR))); put(SHIFT_OPERATORS, Iterables.concat(ARITHMETIC_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.XOR, Tree.Kind.OR))); put(EnumSet.of(Tree.Kind.AND), Iterables.concat(ARITHMETIC_OPERATORS, SHIFT_OPERATORS, EnumSet.of(Tree.Kind.XOR, Tree.Kind.OR))); put(EnumSet.of(Tree.Kind.XOR), Iterables.concat(ARITHMETIC_OPERATORS, SHIFT_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.OR))); put(EnumSet.of(Tree.Kind.OR), Iterables.concat(ARITHMETIC_OPERATORS, SHIFT_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.XOR))); put(EnumSet.of(Tree.Kind.CONDITIONAL_AND), EnumSet.of(Tree.Kind.CONDITIONAL_OR)); put(EnumSet.of(Tree.Kind.CONDITIONAL_OR), EnumSet.of(Tree.Kind.CONDITIONAL_AND)); } private JavaFileScannerContext context; private Deque stack = new LinkedList<>(); private Set reportedLines = new HashSet<>(); private static void put(Iterable firstSet, Iterable secondSet) { for (Tree.Kind first : firstSet) { for (Tree.Kind second : secondSet) { OPERATORS_RELATION_TABLE.put(first, second, true); } } } @Override public void scanFile(JavaFileScannerContext context) { this.context = context; reportedLines.clear(); scan(context.getTree()); reportedLines.clear(); } @Override public void visitAnnotation(AnnotationTree tree) { stack.push(null); for (ExpressionTree argument : tree.arguments()) { if (argument.is(Tree.Kind.ASSIGNMENT)) { scan(((AssignmentExpressionTree) argument).expression()); } else { scan(argument); } } stack.pop(); } @Override public void visitArrayAccessExpression(ArrayAccessExpressionTree tree) { scan(tree.expression()); stack.push(null); scan(tree.dimension()); stack.pop(); } @Override public void visitBinaryExpression(BinaryExpressionTree tree) { Tree.Kind peek = stack.peek(); Tree.Kind kind = tree.kind(); if (requiresParenthesis(peek, kind)) { raiseIssue(tree.operatorToken().line(), tree); } stack.push(kind); super.visitBinaryExpression(tree); stack.pop(); } private static boolean requiresParenthesis(Tree.Kind kind1, Tree.Kind kind2) { return BooleanUtils.isTrue(OPERATORS_RELATION_TABLE.get(kind1, kind2)); } @Override public void visitIfStatement(IfStatementTree tree) { super.visitIfStatement(tree); ExpressionTree condition = tree.condition(); if (condition.is(Tree.Kind.ASSIGNMENT) && EQUALITY_RELATIONAL_OPERATORS.contains(((AssignmentExpressionTree) condition).expression().kind())) { raiseIssue(((AssignmentExpressionTree) condition).operatorToken().line(), tree); } } @Override public void visitMethodInvocation(MethodInvocationTree tree) { scan(tree.methodSelect()); scan(tree.typeArguments()); for (ExpressionTree argument : tree.arguments()) { stack.push(null); scan(argument); stack.pop(); } } @Override public void visitNewArray(NewArrayTree tree) { stack.push(null); super.visitNewArray(tree); stack.pop(); } @Override public void visitNewClass(NewClassTree tree) { stack.push(null); super.visitNewClass(tree); stack.pop(); } @Override public void visitParenthesized(ParenthesizedTree tree) { stack.push(null); super.visitParenthesized(tree); stack.pop(); } @Override public void visitConditionalExpression(ConditionalExpressionTree tree) { checkConditionalOperand(tree.trueExpression()); checkConditionalOperand(tree.falseExpression()); super.visitConditionalExpression(tree); } private void checkConditionalOperand(ExpressionTree tree) { if (!(tree.is(CONDITIONAL_EXCLUSIONS) || tree instanceof LiteralTree || tree instanceof UnaryExpressionTree)) { //VJ ADD 三目运算符中的表达式可以不要括号 if (tree.parent() != null && tree.parent().kind().equals(Tree.Kind.CONDITIONAL_EXPRESSION)) { return; } //VJ END raiseIssue(tree.firstToken().line(), tree); } } private void raiseIssue(int line, Tree tree) { if (reportedLines.add(line)) { context.reportIssue(this, tree, "Add parentheses to make the operator precedence explicit."); } } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/UnusedMethodParameterCheck.java ================================================ /* * SonarQube Java * Copyright (C) 2012-2018 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.vip.vjkit.sonarvj.checks; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.sonar.check.Rule; import org.sonar.java.model.ModifiersUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.Modifier; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.VariableTree; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; /** * 只判读private方法里的无用参数 * * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedMethodParameterCheck.java * * 2c86dbb Jan 12, 2018 */ @Rule(key = "S1172") public class UnusedMethodParameterCheck extends IssuableSubscriptionVisitor { private static final String AUTHORIZED_ANNOTATION = "javax.enterprise.event.Observes"; private static final String STRUTS_ACTION_SUPERCLASS = "org.apache.struts.action.Action"; private static final Collection EXCLUDED_STRUTS_ACTION_PARAMETER_TYPES = ImmutableList.of( "org.apache.struts.action.ActionMapping", "org.apache.struts.action.ActionForm", "javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse"); @Override public List nodesToVisit() { return ImmutableList.of(Tree.Kind.METHOD, Tree.Kind.CONSTRUCTOR); } @Override public void visitNode(Tree tree) { if (!hasSemantic()) { return; } MethodTree methodTree = (MethodTree) tree; if (methodTree.block() == null || !isIncluded(methodTree)) { return; } List unused = Lists.newArrayList(); for (VariableTree var : methodTree.parameters()) { Symbol symbol = var.symbol(); if (symbol.usages().isEmpty() && !symbol.metadata().isAnnotatedWith(AUTHORIZED_ANNOTATION) && !isStrutsActionParameter(var)) { unused.add(var.simpleName()); } } Set unresolvedIdentifierNames = unresolvedIdentifierNames(methodTree.block()); // kill the noise regarding unresolved identifiers, and remove the one with matching names from the list of // unused unused = unused.stream().filter(id -> !unresolvedIdentifierNames.contains(id.name())) .collect(Collectors.toList()); if (!unused.isEmpty()) { reportUnusedParameters(unused); } } private void reportUnusedParameters(List unused) { List locations = new ArrayList<>(); for (IdentifierTree identifier : unused) { locations.add(new JavaFileScannerContext.Location( "Remove this unused method parameter " + identifier.name() + "\".", identifier)); } IdentifierTree firstUnused = unused.get(0); String msg; if (unused.size() > 1) { msg = "Remove these unused method parameters."; } else { msg = "Remove this unused method parameter \"" + firstUnused.name() + "\"."; } reportIssue(firstUnused, msg, locations, null); } // VJ: 改为只判读private方法里的无用参数,替代原isExclude // 大量删除原来exclude里的复杂判断 private static boolean isIncluded(MethodTree tree) { return isPrivateMethod(tree); } private static boolean isStrutsActionParameter(VariableTree variableTree) { Type superClass = variableTree.symbol().enclosingClass().superClass(); return superClass != null && superClass.isSubtypeOf(STRUTS_ACTION_SUPERCLASS) && EXCLUDED_STRUTS_ACTION_PARAMETER_TYPES.contains(variableTree.symbol().type().fullyQualifiedName()); } private static boolean isPrivateMethod(MethodTree methodTree) { return ModifiersUtils.hasModifier(methodTree.modifiers(), Modifier.PRIVATE); } private static Set unresolvedIdentifierNames(Tree tree) { UnresolvedIdentifierVisitor visitor = new UnresolvedIdentifierVisitor(); tree.accept(visitor); return visitor.unresolvedIdentifierNames; } private static class UnresolvedIdentifierVisitor extends BaseTreeVisitor { private Set unresolvedIdentifierNames = new HashSet<>(); @Override public void visitMemberSelectExpression(MemberSelectExpressionTree tree) { // skip annotations and identifier, a method parameter will only be used in expression side (before the dot) scan(tree.expression()); } @Override public void visitMethodInvocation(MethodInvocationTree tree) { ExpressionTree methodSelect = tree.methodSelect(); if (!methodSelect.is(Tree.Kind.IDENTIFIER)) { // not interested in simple method invocations, we are targeting usage of method parameters scan(methodSelect); } scan(tree.typeArguments()); scan(tree.arguments()); } @Override public void visitIdentifier(IdentifierTree tree) { if (tree.symbol().isUnknown()) { unresolvedIdentifierNames.add(tree.name()); } super.visitIdentifier(tree); } } } ================================================ FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/UnusedPrivateFieldCheck.java ================================================ /* * SonarQube Java Copyright (C) 2012-2018 SonarSource SA mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General * Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.vip.vjkit.sonarvj.checks; import java.util.HashSet; import java.util.List; import java.util.Set; import org.sonar.check.Rule; import org.sonar.java.model.ExpressionUtils; import org.sonar.java.model.ModifiersUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.ImportTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.MethodReferenceTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.Modifier; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.Tree.Kind; import org.sonar.plugins.java.api.tree.VariableTree; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; /** * 不检查lombok自动生成getter/setter的类 * * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java * 0d54578 Jan 8, 2018 */ @Rule(key = "S1068") public class UnusedPrivateFieldCheck extends IssuableSubscriptionVisitor { private static final Tree.Kind[] ASSIGNMENT_KINDS = { Tree.Kind.ASSIGNMENT, Tree.Kind.MULTIPLY_ASSIGNMENT, Tree.Kind.DIVIDE_ASSIGNMENT, Tree.Kind.REMAINDER_ASSIGNMENT, Tree.Kind.PLUS_ASSIGNMENT, Tree.Kind.MINUS_ASSIGNMENT, Tree.Kind.LEFT_SHIFT_ASSIGNMENT, Tree.Kind.RIGHT_SHIFT_ASSIGNMENT, Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, Tree.Kind.AND_ASSIGNMENT, Tree.Kind.XOR_ASSIGNMENT, Tree.Kind.OR_ASSIGNMENT }; private List classes = Lists.newArrayList(); private ListMultimap assignments = ArrayListMultimap.create(); private Set unknownIdentifiers = new HashSet<>(); private boolean hasNativeMethod = false; private boolean lombokClass = false; @Override public List nodesToVisit() { return ImmutableList.of(Tree.Kind.IMPORT, Tree.Kind.CLASS, Tree.Kind.METHOD, Tree.Kind.EXPRESSION_STATEMENT, Tree.Kind.IDENTIFIER); } @Override public void scanFile(JavaFileScannerContext context) { super.scanFile(context); if (!hasNativeMethod && !lombokClass) { classes.forEach(this::checkClassFields); } classes.clear(); assignments.clear(); unknownIdentifiers.clear(); hasNativeMethod = false; lombokClass = false; } @Override public void visitNode(Tree tree) { if (!hasSemantic()) { return; } switch (tree.kind()) { case METHOD: checkIfNativeMethod((MethodTree) tree); break; case CLASS: classes.add((ClassTree) tree); break; case IMPORT:// VJ checkIfLombokClass((ImportTree) tree); break; case EXPRESSION_STATEMENT: collectAssignment(((ExpressionStatementTree) tree).expression()); break; case IDENTIFIER: collectUnknownIdentifier((IdentifierTree) tree); break; default: throw new IllegalStateException("Unexpected subscribed tree."); } } // VJ private void checkIfLombokClass(ImportTree tree) { String importStr = fullQualifiedName(tree.qualifiedIdentifier()); if (importStr.contains("lombok")) { lombokClass = true; } } // from WildcardImportsShouldNotBeUsedCheck private static String fullQualifiedName(Tree tree) { if (tree.is(Tree.Kind.IDENTIFIER)) { return ((IdentifierTree) tree).name(); } else if (tree.is(Tree.Kind.MEMBER_SELECT)) { MemberSelectExpressionTree m = (MemberSelectExpressionTree) tree; return fullQualifiedName(m.expression()) + "." + m.identifier().name(); } throw new UnsupportedOperationException(String.format("Kind/Class '%s' not supported", tree.getClass())); } private void collectUnknownIdentifier(IdentifierTree identifier) { if (identifier.symbol().isUnknown() && !isMethodIdentifier(identifier)) { unknownIdentifiers.add(identifier.name()); } } private static boolean isMethodIdentifier(IdentifierTree identifier) { Tree parent = identifier.parent(); while (parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION, Tree.Kind.METHOD_REFERENCE)) { parent = parent.parent(); } if (parent == null) { return false; } if (parent.is(Tree.Kind.METHOD_INVOCATION)) { return identifier.equals(methodName((MethodInvocationTree) parent)); } else { return identifier.equals(((MethodReferenceTree) parent).method()); } } //VJ: copy from MethodsHelper public static IdentifierTree methodName(MethodInvocationTree mit) { IdentifierTree id; if (mit.methodSelect().is(Tree.Kind.IDENTIFIER)) { id = (IdentifierTree) mit.methodSelect(); } else { id = ((MemberSelectExpressionTree) mit.methodSelect()).identifier(); } return id; } private void checkIfNativeMethod(MethodTree method) { if (ModifiersUtils.hasModifier(method.modifiers(), Modifier.NATIVE)) { hasNativeMethod = true; } } private void checkClassFields(ClassTree classTree) { classTree.members().stream().filter(member -> member.is(Tree.Kind.VARIABLE)).map(VariableTree.class::cast) .forEach(this::checkIfUnused); } public void checkIfUnused(VariableTree tree) { if (hasNoAnnotation(tree)) { Symbol symbol = tree.symbol(); String name = symbol.name(); if (symbol.isPrivate() && onlyUsedInVariableAssignment(symbol) && !"serialVersionUID".equals(name) && !unknownIdentifiers.contains(name)) { reportIssue(tree.simpleName(), "Remove this unused \"" + name + "\" private field."); } } } private boolean onlyUsedInVariableAssignment(Symbol symbol) { return symbol.usages().size() == assignments.get(symbol).size(); } private static boolean hasNoAnnotation(VariableTree tree) { return tree.modifiers().annotations().isEmpty(); } private void collectAssignment(ExpressionTree expressionTree) { if (expressionTree.is(ASSIGNMENT_KINDS)) { addAssignment(((AssignmentExpressionTree) expressionTree).variable()); } } private void addAssignment(ExpressionTree tree) { ExpressionTree variable = ExpressionUtils.skipParentheses(tree); if (variable.is(Tree.Kind.IDENTIFIER)) { addAssignment((IdentifierTree) variable); } else if (variable.is(Tree.Kind.MEMBER_SELECT)) { addAssignment(((MemberSelectExpressionTree) variable).identifier()); } } private void addAssignment(IdentifierTree identifier) { Symbol reference = identifier.symbol(); if (!reference.isUnknown()) { assignments.put(reference, identifier); } } } ================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1068_java.html ================================================

VJ update: 使用lobmok的类不告警

If a private field is declared but not used in the program, it can be considered dead code and should therefore be removed. This will improve maintainability because developers will not wonder what the variable is used for.

Note that this rule does not take reflection into account, which means that issues will be raised on private fields that are only accessed using the reflection API.

Noncompliant Code Example

public class MyClass {
  private int foo = 42;

  public int compute(int a) {
    return a * 42;
  }

}

Compliant Solution

public class MyClass {
  public int compute(int a) {
    return a * 42;
  }
}

Exceptions

The Java serialization runtime associates with each serializable class a version number, called serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization.

A serializable class can declare its own serialVersionUID explicitly by declaring a field named serialVersionUID that must be static, final, and of type long. By definition those serialVersionUID fields should not be reported by this rule:

public class MyClass implements java.io.Serializable {
  private static final long serialVersionUID = 42L;
}

Moreover, this rule doesn't raise any issue on annotated fields.

================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1068_java.json ================================================ { "title": "VJ: Unused \"private\" fields should be removed", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cert", "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1068", "sqKey": "S1068" } ================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S115_java.html ================================================

VJ update: 枚举可以是小写

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all constant names match a provided regular expression.

Noncompliant Code Example

With the default regular expression ^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$:

public class MyClass {
  public static final int first = 1;
}

Compliant Solution

public class MyClass {
  public static final int FIRST = 1;
}

================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S115_java.json ================================================ { "title": "VJ: Constant names should comply with a naming convention", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention" ], "defaultSeverity": "Minor" } ================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1166_java.html ================================================

VJ update: catch异常的变量名包含ignore,忽略检查

When handling a caught exception, the original exception's message and stack trace should be logged or passed forward.

Noncompliant Code Example

try {
  /* ... */
} catch (Exception e) {   // Noncompliant - exception is lost
  LOGGER.info("context");
}

try {
  /* ... */
} catch (Exception e) {  // Noncompliant - exception is lost (only message is preserved)
  LOGGER.info(e.getMessage());
}

try {
  /* ... */
} catch (Exception e) {  // Noncompliant - original exception is lost
  throw new RuntimeException("context");
}

Compliant Solution

try {
  /* ... */
} catch (Exception e) {
  LOGGER.info(e);  // exception is logged
}

try {
  /* ... */
} catch (Exception e) {
  throw new RuntimeException(e);   // exception stack trace is propagated
}

try {
  /* ... */
} catch (RuntimeException e) {
  doSomething();
  throw e;  // original exception passed forward
} catch (Exception e) {
  throw new RuntimeException(e);  // Conversion into unchecked exception is also allowed
}

Exceptions

InterruptedException, NumberFormatException, DateTimeParseException, ParseException and MalformedURLException exceptions are arguably used to indicate nonexceptional outcomes. Similarly, handling NoSuchMethodException is often required when dealing with the Java reflection API.

Because they are part of Java, developers have no choice but to deal with them. This rule does not verify that those particular exceptions are correctly handled.

int myInteger;
try {
  myInteger = Integer.parseInt(myString);
} catch (NumberFormatException e) {
  // It is perfectly acceptable to not handle "e" here
  myInteger = 0;
}

See

  • CERT, ERR00-J. - Do not suppress or ignore checked exceptions
  • OWASP Top 10 2017 Category A10 - Insufficient Logging & Monitoring
================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1166_java.json ================================================ { "title": "VJ:Exception handlers should preserve the original exceptions", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "error-handling", "owasp-a10", "cert", "suspicious" ], "standards": [ "CERT" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1166", "sqKey": "S1166" } ================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1172_java.html ================================================

VJ update:只判断private函数

Unused parameters for private method misleading. Whatever the values passed to such parameters, the behavior will be the same.

Noncompliant Code Example

private void doSomething(int a, int b) {     // "b" is unused
  compute(a);
}

Compliant Solution

private void doSomething(int a) {
  compute(a);
}

Exceptions

The rule will not raise issues for protected or public methods

See

  • MISRA C++:2008, 0-1-11 - There shall be no unused parameters (named or unnamed) in nonvirtual functions.
  • MISRA C:2012, 2.7 - There should be no unused parameters in functions
  • CERT, MSC12-C. - Detect and remove code that has no effect or is never executed
  • CERT, MSC12-CPP. - Detect and remove code that has no effect
================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1172_java.json ================================================ { "title": "VJ: Unused private method parameters should be removed", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "misra", "cert", "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1172", "sqKey": "S1172" } ================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S121_java.html ================================================

VJ update: IDE自动生成的方法如equals忽略

While not technically incorrect, the omission of curly braces can be misleading, and may lead to the introduction of errors during maintenance.

Noncompliant Code Example

if (condition)  // Noncompliant
  executeSomething();

Compliant Solution

if (condition) {
  executeSomething();
}

See

  • MISRA C:2004, 14.8 - The statement forming the body of a switch, while, do ... while or for statement shall be a compound statement
  • MISRA C:2004, 14.9 - An if (expression) construct shall be followed by a compound statement. The else keyword shall be followed by either a compound statement, or another if statement
  • MISRA C++:2008, 6-3-1 - The statement forming the body of a switch, while, do ... while or for statement shall be a compound statement
  • MISRA C++:2008, 6-4-1 - An if (condition) construct shall be followed by a compound statement. The else keyword shall be followed by either a compound statement, or another if statement
  • MISRA C:2012, 15.6 - The body of an iteration-statement or a selection-statement shall be a compound-statement
  • CERT, EXP19-C. - Use braces for the body of an if, for, or while statement
  • CERT, EXP52-J. - Use braces for the body of an if, for, or while statement
================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S121_java.json ================================================ { "title": "VJ: Control structures should use curly braces", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "misra", "cert", "pitfall" ], "standards": [ "CERT" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-121", "sqKey": "S121" } ================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1291_java.html ================================================

VJ update: 忽略在异常捕获语句和System.out后的NOSONAR

Any issue to quality rule can be deactivated with the NOSONAR marker. This marker is pretty useful to exclude false-positive results but it can also be used abusively to hide real quality flaws.

This rule raises an issue when NOSONAR is used.

================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1291_java.json ================================================ { "title": "VJ: Track uses of \"NOSONAR\" comments", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Info", "ruleSpecification": "RSPEC-1291", "sqKey": "S1291" } ================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1313_java.html ================================================

VJ update: 忽略"127.0.0.1"

Hardcoding an IP address into source code is a bad idea for several reasons:

  • a recompile is required if the address changes
  • it forces the same address to be used in every environment (dev, sys, qa, prod)
  • it places the responsibility of setting the value to use in production on the shoulders of the developer
  • it allows attackers to decompile the code and thereby discover a potentially sensitive address

Noncompliant Code Example

String ip = "200.112.44.55";
Socket socket = new Socket(ip, 6667);

Compliant Solution

String ip = System.getProperty("myapplication.ip");
Socket socket = new Socket(ip, 6667);

See

================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1313_java.json ================================================ { "title": "VJ: IP addresses should not be hardcoded", "type": "VULNERABILITY", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cert" ], "standards": [ "CERT" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1313", "sqKey": "S1313" } ================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S864_java.html ================================================

VJ update: 三目运算符中的简单表达式可以不要括号

The rules of operator precedence are complicated and can lead to errors. For this reason, parentheses should be used for clarification in complex statements. However, this does not mean that parentheses should be gratuitously added around every operation.

This rule raises issues when && and || are used in combination, when assignment and equality or relational operators are used in together in a condition, and for other operator combinations according to the following table:

+, -, *, /, % <<, >>, >>> & ^ |
+, -, *, /, % x x x x
<<, >>, >>> x x x x
& x x x x
^ x x x x
| x x x x

Noncompliant Code Example

x = a + b - c;
x = a + 1 << b;  // Noncompliant

if ( a > b || c < d || a == d) {...}
if ( a > b && c < d || a == b) {...}  // Noncompliant
if (a = f(b,c) == 1) { ... } // Noncompliant; == evaluated first

Compliant Solution

x = a + b - c;
x = (a + 1) << b;

if ( a > b || c < d || a == d) {...}
if ( (a > b && c < d) || a == b) {...}
if ( (a = f(b,c)) == 1) { ... }

See

  • MISRA C:2004, 12.1 - Limited dependence should be placed on C's operator precedence rules in expressions
  • MISRA C:2004, 12.2 - The value of an expression shall be the same under any order of evaluation that the standard permits.
  • MISRA C:2004, 12.5 - The operands of a logical && or || shall be primary-expressions.
  • MISRA C++:2008, 5-0-1 - The value of an expression shall be the same under any order of evaluation that the standard permits.
  • MISRA C++:2008, 5-0-2 - Limited dependence should be placed on C++ operator precedence rules in expressions
  • MISRA C++:2008, 5-2-1 - Each operand of a logical && or || shall be a postfix-expression.
  • MISRA C:2012, 12.1 - The precedence of operators within expressions should be made explicit
  • CERT, EXP00-C. - Use parentheses for precedence of operation
  • CERT, EXP00-CPP. - Use parentheses for precedence of operation
  • CERT, EXP53-J. - Use parentheses for precedence of operation
  • MITRE, CWE-783 - Operator Precedence Logic Error
================================================ FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S864_java.json ================================================ { "title": "VJ: Limited dependence should be placed on operator precedence rules in expressions", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "cwe", "misra", "cert" ], "standards": [ "CWE", "CERT" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-864", "sqKey": "S864" } ================================================ FILE: vjdump/README.md ================================================ # 1. 概述 VJDump是线上JVM数据紧急收集脚本。 它可以在紧急场景下(比如马上要对进程进行重启),一键收集jstack、jmap以及GC日志等相关信息,并以zip包保存(默认在目录`/tmp/vjtools/vjdump`下),保证在紧急情况下仍能收集足够的问题排查信息,减轻运维团队的工作量,以及与开发团队的沟通成本。 收集数据包括: * thread dump数据:`jstack -l $PID` * vjtop JVM概况及繁忙线程:`vjtop.sh -n 1 $PID` (需要将vjtop.sh 加入用户的PATH变量中) * jmap histo 堆对象统计数据:`jmap -histo $PID` & `jmap -histo:live $PID` * GC日志(如果JVM有设定GC日志输出) * heap dump数据(需指定--liveheap开启):`jmap -dump:live,format=b,file=${DUMP_FILE} $PID` # 2. 下载 [vjdump.sh](https://raw.githubusercontent.com/vipshop/vjtools/master/vjdump/vjdump.sh) # 3. 快速入门 以目标JVM相同用户或root用户运行脚本: ```shell # 对指定的进程PID进行急诊 vjdump.sh $pid # 额外收集heap dump信息(jmap -dump:live的信息) vjdump.sh --liveheap $pid ``` 在收集过程中,某些命令如`jmap -histo:live $PID` 会造成JVM停顿,因此仅用于紧急情况或已摘流量的情况。为了避免连续停顿,在每条会造成停顿的收集指令之间,默认插入了1秒的执行间隔。 ================================================ FILE: vjdump/README_EN.md ================================================ # VJDump VJDump comes as a handy script for collecting diagnostic data for JVM during urgent failures to **allow for complete analysis later**. # 1.Introduction When major system failures occur for reasons not yet known, rebooting may be the only option to ease user complaints. There is so little time that even an experienced system admin might forget to pick up all he needs for offline analysis. VJDump script is written to make us eaiser, which just packs outputs from T jstack, jmap, gc logs into the form of a single zip file under `/tmp/vjtools/vjdump`. **[Important]**: Commands like jstack and jmap DO cause stop-of-the-world of the target app. Make sure the target app is isolated from user access before you run this full check in production. Items to be collected by VJDump are: * thread dump via `jstack -l $PID` * vjtop JVM overview and busy threads snapshot via `vjtop.sh -n 1 -d 3 $PID` (enabled when you have our vjtop installed and have the folder of vjtop.sh appended to the PATH environment variable) * jmap histo object statistics via`jmap -histo $PID` & `jmap -histo:live $PID` * GC logs if available * heap dump (which can be optionally switched on via --liveheap):`jmap -dump:live,format=b,file=${DUMP_FILE} $PID` # 2. Download [vjdump.sh](https://raw.githubusercontent.com/vipshop/vjtools/master/vjdump/vjdump.sh) # 3. Getting Started Use the following commands under **the same user who started the target process** and collect results in `/tmp/vjtools/vjdump`. ```shell # collect dignostics for target $pid vjdump.sh $pid # include heap dump also via jmap -dump:live, might take longer vjdump.sh --liveheap $pid ``` ================================================ FILE: vjdump/vjdump.sh ================================================ #!/bin/bash USAGE() { echo "usage: $0 [--liveheap][-nz|--nozip][-i|--interval] " } if [ $# -lt 1 ]; then USAGE exit -1 fi BASEDIR=/tmp/vjtools LOGDIR=${BASEDIR}/vjdump SLEEP_TIME=1 CLOSE_COMPRESS=0 NEED_HEAP_DUMP=0 PID="$1" while true; do case "$1" in -i|--interval) SLEEP_TIME="$2"; PID="$3"; shift 1;; -nz|--nozip) CLOSE_COMPRESS=1; PID="$2"; shift;; --liveheap) NEED_HEAP_DUMP=1; PID="$2"; shift;; *) break;; esac done CMD="$1" shift START() { if [[ x"$PID" == x ]]; then echo -e "The pid is empty, please enter pid". exit -1 else echo -e "The pid is ${PID}" fi # try to find $JAVA_HOME if not set if [ -z "$JAVA_HOME" ] ; then JAVA_HOME=`readlink -f \`which java 2>/dev/null\` 2>/dev/null | \ sed 's/\jre\/bin\/java//' | sed 's/\/bin\/java//'` fi if [ ! -f "$JAVA_HOME/bin/jstack" ] ; then echo -e "\033[31m\$JAVA_HOME not found. please export JAVA_HOME manually.\033[0m" exit -1 fi # clean all history logs rm -rf ${LOGDIR}/*.log ${LOGDIR}/*jmap_dump_live-*.bin mkdir -p ${LOGDIR} DATE=$(date "+%Y%m%d%H%M%S") echo -e "\033[34m$(date '+%Y-%m-%d %H:%M:%S') vjdump begin. command interval is ${SLEEP_TIME}s.\033[0m" # jstack echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jstack." JSTACK_LOG=${LOGDIR}/jstack-${PID}-${DATE}.log ${JAVA_HOME}/bin/jstack -l $PID > ${JSTACK_LOG} if [[ $? != 0 ]]; then echo -e "\033[31mprocess jstack error.\033[0m" fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jstack." sleep ${SLEEP_TIME} # vjtop VJTOP_SCRIPT=vjtop.sh which $VJTOP_SCRIPT 2>/dev/null if [[ $? == 0 ]]; then echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process vjtop." echo -e "It will take 3 seconds, please wait." VJTOP_LOG=${LOGDIR}/vjtop-${PID}-${DATE}.log $VJTOP_SCRIPT -n 3 -d 1 $PID > ${VJTOP_LOG} if [[ $? != 0 ]]; then echo -e "\033[31mprocess vjtop error.\033[0m" fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process vjtop." else # no vjtop, use other replacement # jinfo -flags $PID echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jinfo -flags." JINFO_FLAGS_LOG=${LOGDIR}/jinfo-flags-${PID}-${DATE}.log ${JAVA_HOME}/bin/jinfo -flags $PID 1>${JINFO_FLAGS_LOG} 2>&1 if [[ $? != 0 ]]; then echo -e "\033[31mprocess jinfo -flags error.\033[0m" fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jinfo -flags." #jmap -heap echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -heap." JMAP_HEAP_LOG=${LOGDIR}/jmap_heap-${PID}-${DATE}.log ${JAVA_HOME}/bin/jmap -heap $PID > ${JMAP_HEAP_LOG} if [[ $? != 0 ]]; then echo -e "\033[31mprocess jmap -heap error.\033[0m" fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -heap." fi # jmap -histo echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -histo." JMAP_HISTO_LOG=${LOGDIR}/jmap_histo-${PID}-${DATE}.log ${JAVA_HOME}/bin/jmap -histo $PID > ${JMAP_HISTO_LOG} if [[ $? != 0 ]]; then echo -e "\033[31mprocess jmap -histo error.\033[0m" fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -histo." sleep ${SLEEP_TIME} # jmap -histo:live echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -histo:live." JMAP_HISTO_LIVE_LOG=${LOGDIR}/jmap_histo_live-${PID}-${DATE}.log ${JAVA_HOME}/bin/jmap -histo:live $PID > ${JMAP_HISTO_LIVE_LOG} if [[ $? != 0 ]]; then echo -e "\033[31mprocess jmap -histo:live error.\033[0m" fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -histo:live." sleep ${SLEEP_TIME} # jmap -dump:live if [[ $NEED_HEAP_DUMP == 1 ]]; then JMAP_DUMP_FILE=${LOGDIR}/jmap_dump_live-${PID}-${DATE}.bin echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -dump:live." ${JAVA_HOME}/bin/jmap -dump:live,format=b,file=${JMAP_DUMP_FILE} $PID if [[ $? != 0 ]]; then echo -e "\033[31mprocess jmap -dump:live error.\033[0m" fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -dump:live." sleep ${SLEEP_TIME} fi # gc log echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process gc log." GCLOG=$(strings /proc/${PID}/cmdline |grep '\-Xloggc' |cut -d : -f 2) if [[ x"$GCLOG" == x ]]; then echo -e "No GC log existing." else # "\cp" means unalias cp, it can cover files without prompting \cp -rf $GCLOG ${LOGDIR}/ if [[ $? != 0 ]]; then echo -e "copy gc log error." fi fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process gc log." # packaging if [[ $CLOSE_COMPRESS == 1 ]]; then echo -e "The zip option is closed, no zip package will be generated." else echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to zip all files." # zip files without heap dump if [ -x "$(command -v zip)" ]; then COMPRESS_FILE=${BASEDIR}/vjdump-${PID}-${DATE}.zip zip -j ${COMPRESS_FILE} ${LOGDIR}/*.log else COMPRESS_FILE=${BASEDIR}/vjdump-${PID}-${DATE}.tar.gz (cd ${LOGDIR} && tar -zcvf ${COMPRESS_FILE} *.log) fi if [[ $? != 0 ]]; then echo -e "\033[31mzip files error.\033[0m" else echo -e "zip files success, the zip file is \033[34m${ZIP_FILE}\033[0m" fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to zip all files." if [[ $NEED_HEAP_DUMP == 1 ]]; then # compress all files echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to zip files which include dump file." if [ -x "$(command -v zip)" ]; then COMPRESS_FILE_WITH_HEAP_DUMP=${BASEDIR}/vjdump-with-heap-${PID}-${DATE}.zip zip -j ${COMPRESS_FILE_WITH_HEAP_DUMP} ${LOGDIR}/*.log ${JMAP_DUMP_FILE} else COMPRESS_FILE_WITH_HEAP_DUMP=${BASEDIR}/vjdump-with-heap-${PID}-${DATE}.tar.gz (cd ${LOGDIR} && tar -zcvf ${COMPRESS_FILE_WITH_HEAP_DUMP} *.log *.bin) fi if [[ $? != 0 ]]; then echo -e "\033[31mzip files which include dump file error.\033[0m" else echo -e "zip files which include dump file success, the zip path is \033[34m${ZIP_FILE_WITH_HEAP_DUMP}\033[0m" fi echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to zip files which include dump file." fi fi echo -e "\033[34m$(date '+%Y-%m-%d %H:%M:%S') vjdump finish. \033[0m" } case "$CMD" in help) USAGE;; *) START;; esac ================================================ FILE: vjkit/README.md ================================================ # 1. Overview 唯品会Java开发基础类库,综合各门各派众多开源类库的精华而成, 让开发人员避免底层代码的重复开发,默认就拥有最佳实践,尤其在性能的方面。 综合众多开源类库的精华而成, 让开发人员避免底层代码的重复开发,默认就拥有最佳实践,尤其在性能的方面。 针对“基础,文本,数字,日期,文件,集合,并发,反射,日志脱敏”这些开发人员的日常,VJKit做了两件事情: 一是对[Guava](https://github.com/google/guava) 与[Common Lang](https://github.com/apache/commons-lang)中最常用的API的提炼归类,避免了大家直面茫茫多的API(但有些工具类如Guava Cache还是建议直接使用,详见[直用三方工具类](docs/direct_3rd.md) ) 二是对各门各派的精华的借鉴移植:比如一些大项目的附送基础库: [Netty](https://github.com/netty/netty/),[ElasticSearch](https://github.com/elastic/elasticsearch), 一些专业的基础库 : [Jodd](https://github.com/oblac/jodd/), [commons-io](https://github.com/apache/commons-io), [commons-collections](https://github.com/apache/commons-collections); 一些大厂的基础库:[Facebook JCommon](https://github.com/facebook/jcommon),[twitter commons](https://github.com/twitter/commons) 具体使用文档请在IDE中阅读JavaDoc,以及对应的单元测试写法。 * [日志脱敏](docs/data_masking.md) # 2. Usage Maven : ``` com.vip.vjtools vjkit 1.0.8 ``` Download: [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.vip.vjtools/vjkit/badge.svg)](http://search.maven.org/#search|gav|1|g:"com.vip.vjtools"%20AND%20a:"vjkit") # 3. Dependency 要求JDK 7.0及以上版本。 | Project | Version | Optional| |--- | --- | --- | |[Guava](https://github.com/google/guava) | 20.0 || |[Apache Common Lang](https://github.com/apache/commons-lang) | 3.7 || |[Slf4j](https://www.slf4j.org) | 1.7.25 || |[Dozer](http://dozermapper.github.io/) | 5.5.1 |Optional for BeanMapper,[选型](https://github.com/vipshop/vjtools/blob/master/vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/BeanMapper.java#L11) | 如果使用Optional的依赖,请参考pom文件在业务项目自行引入 # 4. Sonar Status [https://sonarcloud.io/dashboard?id=com.vip.vjtools:vjkit](https://sonarcloud.io/dashboard?id=com.vip.vjtools:vjkit) (sonarcloud use "Sonar Way") ================================================ FILE: vjkit/docs/data_masking.md ================================================ # 1.简介 vjmask是唯品会的日志脱敏组件,在业务中广泛使用。基于性能和通用性考虑,采用了现在的方案。让使用方用最少的配置和代码,就可以轻松实现敏感信息过滤。 # 2.使用 ## 2.1 依赖 ``` com.vip.vjtools vjkit ${version} ``` ## 2.2 最佳实践 脱敏组件支持对单个字符串进行脱敏,也支持toJSON和toString的序列化脱敏 ``` import com.vip.vjtools.vjkit.datamasking.DataMask; // 单个字符串,按中文姓名规则脱敏 logger.info("some sensitive info:{}",DataMask.mask(name,SensitiveType.Name)); //单个字符串,按默认方式脱敏,结果为 "t***"; logger.info("some sensitive info:{}",DataMask.mask("test")); //对象json序列化脱敏 logger.info("some sensitive object:{}",DataMask.toJSONString(obj)); //对象toString序列化脱敏 logger.info("some sensitive object:{}",DataMask.toString(obj)); ``` 对类的序列化脱敏,先要对相关敏感字段标记 @Sensitive,支持字符串类型的字段,包括String、String[]和Collection. 嵌套类的敏感字段也是可以识别的。 ``` public class User{ @Sensitive(type = SensitiveType.Phone) type参考2.3 private String phone; } ``` ## 2.3 脱敏规则 我们已经定义实现了常用的脱敏类型,可以直接使用 敏感信息 | 脱敏要求 | 样例 | SensitiveType ---|---|---|--- 中文姓名 | 三个字及以下,只显示最后一个字;三个字以上,显示最后两个字 | *明,****小明 | Name 手机号/固定电话 | 只显示前三后三 | 138*****111 | Phone 身份证号 | 显示前五个和后二个字符 | 44010************58 | IDCard 银行卡号 | 显示前四个和后二个字符 | 6228************89 | BankCard 地址 | 保留前9个字符 | 广东省广州市荔湾区****** | Address 电子邮箱 | 只显示前一后一及@和后面的内容 | a***b@abc.com | Email 验证码 | 只显示前一后一 | a**b | Captcha 护照/军官号 | 只显示前二后二 | EI****64 | Passport 账号 | 只显示前一后一 | a****b | Account 密码 | 不显示任意字符 | ********* | Password 散列 | sha1(source+salt) ,可以通过DataMask.setSalt设置salt,用于希望在日志系统中精确找回这条日志的场景,可以再自己hash获得hash值 | 6b76e070c5b5d1b889295506faa8b98e97da7e87 | Hash # 3.详细介绍 ## 3.1 Annotation标注 要使用序列化脱敏,先要对敏感字段标注@Sensitive,注意,只对字符串相关的类型字段会生效。 ``` //根据类型来标注 @Sensitive(type = SensitiveType.Name) private String name; //也可以自定义掩码规则 @Sensitive(keepChars = 2) //首尾保留2个字符串,如果keepChars = {1,3} 表示头部保留1个字符,尾部保留3个字符 private String[] phone; // 散列的方式 @Sensitive(type = SensitiveType.Hash) private String hash; //默认的方式,只保留第一个字符串 @Sensitive private List account; ``` ## 3.2 映射配置 如果你不想对一个个类字段标注@Sensitive,也可以在resource 目录下新建一个data_mask。properties,添加敏感字段映射 ``` #SensitiveType=字段名称 Name=nickName ``` 那么nickName字段即使没有标注@Sensitive ,在序列化的时候,也会自动脱敏,按照SentiveType.Name的方式脱敏。 ==注意,标注@Sensitive优先级高于文件配置== 组件中已经默认配置映射了以下配置,sys_data_mask.properties ``` Name=chineseName,userName Phone=phone,phoneNum,mobile,tel,telephone IDCard=IDCard,IdNo BankCard=bankCard Address=address,addr Email=mail,email Captcha=captcha Passport=passport Account=account Password=password,passwd ``` # 4.Benchmark 我们使用JMH基准测试,测试了2个使用场景。 ## 4.1 场景1 通用场景,场景1包括了10个左右的Sensitive字段。toJSONString() 平均耗时 0.003ms,toString() 平均耗时0.005ms。 ``` Benchmark Mode Score Error Units DataMaskTest.testJson(不脱敏) avgt 0.001 ± 0.001 ms/op DataMaskTest.testMaskJson avgt 0.003 ± 0.002 ms/op DataMaskTest.testMaskToString avgt 0.005 ± 0.003 ms/op DataMaskTest.testToString avgt ≈ 10⁻⁴ ms/op ``` ## 4.2 场景2 极限场景,场景2测试超大的类序列化,有160多个字段需要脱敏,3层嵌套。toJSONString() 平均耗时 0.064ms,toString() 平均耗时0.098ms。 ``` Benchmark Mode Score Error Units DataMaskTest.testJson avgt 0.043 ± 0.025 ms/op DataMaskTest.testMaskJson avgt 0.064 ± 0.002 ms/op DataMaskTest.testMaskToString avgt 0.098 ± 0.095 ms/op DataMaskTest.testToString avgt ≈ 10⁻⁴ ms/op ``` ## 4.3 结论 脱敏处理的性能和脱敏字段的数量相关。 a.如果需要脱敏的字段不多,时间在0.001ms~0.009ms范围内,对于业务来说,不会造成太多额外开销,在可控范围内。 b.如果需要脱敏的字段特别多,建议要性能测试评估 c.toString() 性能比toJson() 要慢,建议是优先使用toJson() ================================================ FILE: vjkit/docs/direct_3rd.md ================================================ # 建议直接使用的第三方类 | Project | Class | |--- | --- | |Common Lang | StringUtils | | | Validate| |Guava | Cache | | | Ordering | |JDK|Arrays| | |Collections| ================================================ FILE: vjkit/pom.xml ================================================ 4.0.0 com.vip.vjtools vjkit 1.0.9-SNAPSHOT jar vjkit VIP's core java libraries 20.0 3.8.1 1.7.25 1.1.11 5.5.1 2.9.10.4 4.12 2.6.0 2.18.3 1.2.70 UTF-8 1.7 ${java.version} ${java.version} com.google.guava guava ${guava.version} org.apache.commons commons-lang3 ${commons-lang3.version} org.slf4j slf4j-api ${slf4j.version} net.sf.dozer dozer ${dozer.version} true com.fasterxml.jackson.core jackson-databind ${jackson.version} true com.alibaba fastjson ${fastjson.version} true ch.qos.logback logback-classic ${logback.version} true junit junit ${junit.version} test org.assertj assertj-core ${assertj.version} test org.mockito mockito-core ${mockito.version} test dom4j dom4j 1.6.1 test jaxen jaxen 1.1.6 test org.skyscreamer jsonassert 1.5.0 test release org.apache.maven.plugins maven-javadoc-plugin 2.10.4 false attach-javadocs jar org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar org.apache.maven.plugins maven-release-plugin 2.5.3 v.@{project.version} org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign sonar 1.7 **/primitive/**/*.java,**/jsr166e/**/*.java,**/SortedArrayList.java,**/WildcardMatcher.java,**/CsvUtil.java org.jacoco jacoco-maven-plugin 0.7.2.201409121644 true agent-for-ut prepare-agent org.sonarsource.scanner.maven sonar-maven-plugin 3.4.0.905 jdk9 9 org.apache.maven.plugins maven-surefire-plugin 2.22.0 --add-modules java.xml.bind sonatype-nexus-snapshots https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-releases https://oss.sonatype.org/service/local/staging/deploy/maven2 https://github.com/vipshop/vjtools Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:https://github.com/vipshop/vjtools.git scm:git:https://github.com/vipshop/vjtools.git https://github.com/vipshop/vjtools v.1.0.2 calvin Calvin Xiao calvin.xiao at vipshop.com developer +8 ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/BooleanUtil.java ================================================ package com.vip.vjtools.vjkit.base; import org.apache.commons.lang3.BooleanUtils; /** * 1. 从String(true/false, yes/no),转换为Boolean或boolean * * 2. 逻辑运算:取反,多个boolean的and,or 计算 * * 封装 {@code org.apache.commons.lang3.BooleanUtils} */ public class BooleanUtil { /** * 使用标准JDK,只分析是否忽略大小写的"true", str为空时返回false */ public static boolean toBoolean(String str) { return Boolean.parseBoolean(str); } /** * 使用标准JDK,只分析是否忽略大小写的"true", str为空时返回null */ public static Boolean toBooleanObject(String str) { return str != null ? Boolean.valueOf(str) : null; } /** * 使用标准JDK,只分析是否忽略大小写的"true", str为空时返回defaultValue */ public static Boolean toBooleanObject(String str, Boolean defaultValue) { return str != null ? Boolean.valueOf(str) : defaultValue; } /** * 支持true/false, on/off, y/n, yes/no的转换, str为空或无法分析时返回null */ public static Boolean parseGeneralString(String str) { return BooleanUtils.toBooleanObject(str); } /** * 支持true/false,on/off, y/n, yes/no的转换, str为空或无法分析时返回defaultValue */ public static Boolean parseGeneralString(String str, Boolean defaultValue) { return BooleanUtils.toBooleanDefaultIfNull(BooleanUtils.toBooleanObject(str), defaultValue); } /** * 取反 */ public static boolean negate(final boolean bool) { return !bool; } /** * 取反 */ public static Boolean negate(final Boolean bool) { return BooleanUtils.negate(bool); } /** * 多个值的and */ public static boolean and(final boolean... array) { return BooleanUtils.and(array); } /** * 多个值的or */ public static boolean or(final boolean... array) { return BooleanUtils.or(array); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/EnumUtil.java ================================================ package com.vip.vjtools.vjkit.base; import java.util.EnumSet; import org.apache.commons.lang3.EnumUtils; /** * 枚举工具集 * * 1. 将多个枚举值按bit与long的转换 * * 2. 与String的转换 * * 封装 {@code org.apache.commons.lang3.EnumUtils} */ public class EnumUtil { /** * 将若干个枚举值转换为long(按bits 1,2,4,8...的方式叠加),用于使用long保存多个选项的情况. */ public static > long generateBits(final Class enumClass, final Iterable values) { return EnumUtils.generateBitVector(enumClass, values); } /** * 将若干个枚举值转换为long(按bits 1,2,4,8...的方式叠加),用于使用long保存多个选项的情况. */ public static > long generateBits(final Class enumClass, final E... values) { return EnumUtils.generateBitVector(enumClass, values); } /** * long重新解析为若干个枚举值,用于使用long保存多个选项的情况. */ public static > EnumSet processBits(final Class enumClass, final long value) { return EnumUtils.processBitVector(enumClass, value); } /** * Enum转换为String */ public static String toString(Enum e) { return e.name(); } /** * String转换为Enum */ public static > T fromString(Class enumClass, String value) { return Enum.valueOf(enumClass, value); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/ExceptionUtil.java ================================================ package com.vip.vjtools.vjkit.base; import java.io.PrintWriter; import java.lang.reflect.UndeclaredThrowableException; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import com.google.common.base.Throwables; import com.vip.vjtools.vjkit.base.annotation.NotNull; import com.vip.vjtools.vjkit.base.annotation.Nullable; import com.vip.vjtools.vjkit.base.type.UncheckedException; import com.vip.vjtools.vjkit.io.type.StringBuilderWriter; /** * 关于异常的工具类. * * 1. Checked/Unchecked及Wrap(如ExecutionException)的转换. * * 2. 打印Exception的辅助函数. (其中一些来自Common Lang ExceptionUtils) * * 3. 查找Cause(其中一些来自Guava Throwables) * * 4. StackTrace性能优化相关,尽量使用静态异常避免异常生成时获取StackTrace(Netty) * * @see com.vip.vjtools.vjkit.base.type.CloneableException */ public class ExceptionUtil { private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; ///// Checked/Unchecked及Wrap(如ExecutionException)的转换///// /** * 将CheckedException转换为RuntimeException重新抛出, 可以减少函数签名中的CheckException定义. * * CheckedException会用UndeclaredThrowableException包裹,RunTimeException和Error则不会被转变. * * copy from Commons Lang 3.5 ExceptionUtils. * * 虽然unchecked()里已直接抛出异常,但仍然定义返回值,方便欺骗Sonar。因此本函数也改变了一下返回值 * * 示例代码: * *
	 * try{ ... }catch(Exception e){ throw unchecked(t); }
	 * 
* * @see ExceptionUtils#wrapAndThrow(Throwable) */ public static RuntimeException unchecked(@Nullable Throwable t) { if (t instanceof RuntimeException) { throw (RuntimeException) t; } if (t instanceof Error) { throw (Error) t; } throw new UncheckedException(t); } /** * 如果是著名的包裹类,从cause中获得真正异常. 其他异常则不变. * * Future中使用的ExecutionException 与 反射时定义的InvocationTargetException, 真正的异常都封装在Cause中 * * 前面 unchecked() 使用的UncheckedException同理. */ public static Throwable unwrap(@Nullable Throwable t) { if (t instanceof UncheckedException || t instanceof java.util.concurrent.ExecutionException || t instanceof java.lang.reflect.InvocationTargetException || t instanceof UndeclaredThrowableException) { return t.getCause(); } return t; } /** * 组合unwrap与unchecked,用于处理反射/Callable的异常 */ public static RuntimeException unwrapAndUnchecked(@Nullable Throwable t) { throw unchecked(unwrap(t)); } ////// 输出内容相关 ////// /** * 将StackTrace[]转换为String, 供Logger或e.printStackTrace()外的其他地方使用. * * 为了使用StringBuilderWriter,没有用Throwables#getStackTraceAsString(Throwable) */ public static String stackTraceText(@NotNull Throwable t) { StringBuilderWriter stringWriter = new StringBuilderWriter(); t.printStackTrace(new PrintWriter(stringWriter)); // NOSONAR return stringWriter.toString(); } /** * 拼装 短异常类名: 异常信息. * * 与Throwable.toString()相比使用了短类名 * * @see ExceptionUtils#getMessage(Throwable) */ public static String toStringWithShortName(@Nullable Throwable t) { return ExceptionUtils.getMessage(t); } /** * 拼装 短异常类名: 异常信息 <-- RootCause的短异常类名: 异常信息 */ public static String toStringWithRootCause(@Nullable Throwable t) { if (t == null) { return StringUtils.EMPTY; } final String clsName = ClassUtils.getShortClassName(t, null); final String message = StringUtils.defaultString(t.getMessage()); Throwable cause = getRootCause(t); StringBuilder sb = new StringBuilder(128).append(clsName).append(": ").append(message); if (cause != t) { sb.append("; <---").append(toStringWithShortName(cause)); } return sb.toString(); } ////////// Cause 相关 ///////// /** * 获取异常的Root Cause. * * 如无底层Cause, 则返回自身 * * @see Throwables#getRootCause(Throwable) */ public static Throwable getRootCause(@NotNull Throwable t) { return Throwables.getRootCause(t); } /** * 获取某种类型的cause,如果没有则返回空 * * copy from Jodd ExceptionUtil */ public static T findCause(@NotNull Throwable throwable, Class cause) { while (throwable != null) { if (throwable.getClass().equals(cause)) { return (T) throwable; } throwable = throwable.getCause(); } return null; } /** * 判断异常是否由某些底层的异常引起. */ @SuppressWarnings("unchecked") public static boolean isCausedBy(@Nullable Throwable throwable, Class... causeExceptionClasses) { Throwable cause = throwable; while (cause != null) { for (Class causeClass : causeExceptionClasses) { if (causeClass.isInstance(cause)) { return true; } } cause = cause.getCause(); } return false; } /////////// StackTrace 性能优化相关//////// /** * copy from Netty, 为静态异常设置StackTrace. * * 对某些已知且经常抛出的异常, 不需要每次创建异常类并很消耗性能的并生成完整的StackTrace. 此时可使用静态声明的异常. * * 如果异常可能在多个地方抛出,使用本函数设置抛出的类名和方法名. * *
	 * private static RuntimeException TIMEOUT_EXCEPTION = ExceptionUtil.setStackTrace(new RuntimeException("Timeout"),
	 * 		MyClass.class, "mymethod");
	 * 
*/ public static T setStackTrace(@NotNull T throwable, Class throwClass, String throwClazz) { throwable.setStackTrace( new StackTraceElement[] { new StackTraceElement(throwClass.getName(), throwClazz, null, -1) }); return throwable; } /** * 清除StackTrace. 假设StackTrace已生成, 但把它打印出来也有不小的消耗. * * 如果不能控制StackTrace的生成,也不能控制它的打印端(如logger),可用此方法暴力清除Trace. * * 但Cause链依然不能清除, 只能清除每一个Cause的StackTrace. */ public static T clearStackTrace(@NotNull T throwable) { Throwable cause = throwable; while (cause != null) { cause.setStackTrace(EMPTY_STACK_TRACE); cause = cause.getCause(); } return throwable; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/MoreValidate.java ================================================ package com.vip.vjtools.vjkit.base; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 参数校验统一使用Apache Common Lang Validate, 补充一些缺少的. * * 为什么不用Guava的{@code com.google.common.base.Preconditions} , 一是少打几个字而已, 二是Validate的方法多,比如noNullElements()判断多个元素都不为空 * * 目前主要参考 {@code com.google.common.math.MathPreconditions} , 补充数字为正数或非负数的校验 * */ public class MoreValidate extends org.apache.commons.lang3.Validate { /** * 校验为正数则返回该数字,否则抛出异常. */ public static int positive(@Nullable String role, int x) { if (x <= 0) { throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); } return x; } /** * 校验为正数则返回该数字,否则抛出异常. */ public static Integer positive(@Nullable String role, Integer x) { if (x.intValue() <= 0) { throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); } return x; } /** * 校验为正数则返回该数字,否则抛出异常. */ public static long positive(@Nullable String role, long x) { if (x <= 0) { throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); } return x; } /** * 校验为正数则返回该数字,否则抛出异常. */ public static Long positive(@Nullable String role, Long x) { if (x.longValue() <= 0) { throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); } return x; } /** * 校验为正数则返回该数字,否则抛出异常. */ public static double positive(@Nullable String role, double x) { if (!(x > 0)) { // not x < 0, to work with NaN. throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } return x; } /** * 校验为非负数则返回该数字,否则抛出异常. */ public static int nonNegative(@Nullable String role, int x) { if (x < 0) { throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } return x; } /** * 校验为非负数则返回该数字,否则抛出异常. */ public static Integer nonNegative(@Nullable String role, Integer x) { if (x.intValue() < 0) { throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } return x; } /** * 校验为非负数则返回该数字,否则抛出异常. */ public static long nonNegative(@Nullable String role, long x) { if (x < 0) { throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } return x; } /** * 校验为非负数则返回该数字,否则抛出异常. */ public static Long nonNegative(@Nullable String role, Long x) { if (x.longValue() < 0) { throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } return x; } /** * 校验为非负数则返回该数字,否则抛出异常. */ public static double nonNegative(@Nullable String role, double x) { if (!(x >= 0)) { // not x < 0, to work with NaN. throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } return x; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/ObjectUtil.java ================================================ package com.vip.vjtools.vjkit.base; import java.util.Arrays; import com.google.common.base.Objects; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 1. Object打印优化,主要解决数组的打印 * * 2. 多个对象的HashCode串联 */ public class ObjectUtil { private static final String NULL = "null"; /** * JDK7 引入的Null安全的equals */ public static boolean equals(@Nullable Object a, @Nullable Object b) { return Objects.equal(a, b); } /** * 多个对象的HashCode串联, 组成新的HashCode */ public static int hashCode(Object... objects) { return Arrays.hashCode(objects); } /** * 对象的toString(), 处理了对象为数组的情况,JDK的默认toString()只打数组的地址如 "[Ljava.lang.Integer;@490d6c15. */ public static String toPrettyString(Object value) { if (value == null) { return NULL; } Class type = value.getClass(); if (type.isArray()) { Class componentType = type.getComponentType(); if (componentType.isPrimitive()) { return primitiveArrayToString(value, componentType); } else { return objectArrayToString(value); } } else if (value instanceof Iterable) { // 因为Collection的处理也是默认调用元素的toString(), // 为了处理元素是数组的情况,同样需要重载 return collectionToString(value); } return value.toString(); } private static String primitiveArrayToString(Object value, Class componentType) { StringBuilder sb = new StringBuilder(); if (componentType == int.class) { sb.append(Arrays.toString((int[]) value)); } else if (componentType == long.class) { sb.append(Arrays.toString((long[]) value)); } else if (componentType == double.class) { sb.append(Arrays.toString((double[]) value)); } else if (componentType == float.class) { sb.append(Arrays.toString((float[]) value)); } else if (componentType == boolean.class) { sb.append(Arrays.toString((boolean[]) value)); } else if (componentType == short.class) { sb.append(Arrays.toString((short[]) value)); } else if (componentType == byte.class) { sb.append(Arrays.toString((byte[]) value)); } else if (componentType == char.class) { sb.append(Arrays.toString((char[]) value)); } else { throw new IllegalArgumentException("unsupport array type"); } return sb.toString(); } private static String objectArrayToString(Object value) { StringBuilder sb = new StringBuilder(); sb.append('['); Object[] array = (Object[]) value; for (int i = 0; i < array.length; i++) { if (i > 0) { sb.append(", "); } sb.append(toPrettyString(array[i])); } sb.append(']'); return sb.toString(); } private static String collectionToString(Object value) { Iterable iterable = (Iterable) value; StringBuilder sb = new StringBuilder(); sb.append('{'); int i = 0; for (Object o : iterable) { if (i > 0) { sb.append(','); } sb.append(toPrettyString(o)); i++; } sb.append('}'); return sb.toString(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/Platforms.java ================================================ package com.vip.vjtools.vjkit.base; import java.io.File; import org.apache.commons.lang3.SystemUtils; /** * 关于系统设定,平台信息的变量(via Common Lang SystemUtils) */ public class Platforms { // 文件路径分隔符 public static final String FILE_PATH_SEPARATOR = File.separator; public static final char FILE_PATH_SEPARATOR_CHAR = File.separatorChar; public static final char WINDOWS_FILE_PATH_SEPARATOR_CHAR = '\\'; public static final char LINUX_FILE_PATH_SEPARATOR_CHAR = '/'; // ClassPath分隔符 public static final String CLASS_PATH_SEPARATOR = File.pathSeparator; public static final char CLASS_PATH_SEPARATOR_CHAR = File.pathSeparatorChar; // 换行符 public static final String LINE_SEPARATOR = System.lineSeparator(); // 临时目录 public static final String TMP_DIR = SystemUtils.JAVA_IO_TMPDIR; // 应用的工作目录 public static final String WORKING_DIR = SystemUtils.USER_DIR; // 用户 HOME目录 public static final String USER_HOME = SystemUtils.USER_HOME; // Java HOME目录 public static final String JAVA_HOME = SystemUtils.JAVA_HOME; // Java版本 public static final String JAVA_SPECIFICATION_VERSION = SystemUtils.JAVA_SPECIFICATION_VERSION; // e.g. 1.8 public static final String JAVA_VERSION = SystemUtils.JAVA_VERSION; // e.g. 1.8.0_102 public static final boolean IS_JAVA7 = SystemUtils.IS_JAVA_1_7; public static final boolean IS_JAVA8 = SystemUtils.IS_JAVA_1_8; public static final boolean IS_ATLEASET_JAVA7 = IS_JAVA7 || IS_JAVA8; public static final boolean IS_ATLEASET_JAVA8 = IS_JAVA8; // 操作系统类型及版本 public static final String OS_NAME = SystemUtils.OS_NAME; public static final String OS_VERSION = SystemUtils.OS_VERSION; public static final String OS_ARCH = SystemUtils.OS_ARCH; // e.g. x86_64 public static final boolean IS_LINUX = SystemUtils.IS_OS_LINUX; public static final boolean IS_UNIX = SystemUtils.IS_OS_UNIX; public static final boolean IS_WINDOWS = SystemUtils.IS_OS_WINDOWS; } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/PropertiesUtil.java ================================================ package com.vip.vjtools.vjkit.base; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vip.vjtools.vjkit.io.URLResourceUtil; import com.vip.vjtools.vjkit.number.NumberUtil; import com.vip.vjtools.vjkit.text.Charsets; /** * 关于Properties的工具类 * * 1. 统一风格读取Properties到各种数据类型 * * 2. 从文件或字符串装载Properties */ public class PropertiesUtil { private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class); /////////////////// 读取Properties //////////////////// public static Boolean getBoolean(Properties p, String name, Boolean defaultValue) { return BooleanUtil.toBooleanObject(p.getProperty(name), defaultValue); } public static Integer getInt(Properties p, String name, Integer defaultValue) { return NumberUtil.toIntObject(p.getProperty(name), defaultValue); } public static Long getLong(Properties p, String name, Long defaultValue) { return NumberUtil.toLongObject(p.getProperty(name), defaultValue); } public static Double getDouble(Properties p, String name, Double defaultValue) { return NumberUtil.toDoubleObject(p.getProperty(name), defaultValue); } public static String getString(Properties p, String name, String defaultValue) { return p.getProperty(name, defaultValue); } /////////// 加载Properties//////// /** * 从文件路径加载properties. 默认使用utf-8编码解析文件 * * 路径支持从外部文件或resources文件加载, "file://"或无前缀代表外部文件, "classpath:"代表resources */ public static Properties loadFromFile(String generalPath) { Properties p = new Properties(); try (Reader reader = new InputStreamReader(URLResourceUtil.asStream(generalPath), Charsets.UTF_8)) { p.load(reader); } catch (IOException e) { logger.warn("Load property from " + generalPath + " failed", e); } return p; } /** * 从字符串内容加载Properties */ public static Properties loadFromString(String content) { Properties p = new Properties(); try (Reader reader = new StringReader(content)) { p.load(reader); } catch (IOException ignored) { // NOSONAR } return p; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/RuntimeUtil.java ================================================ package com.vip.vjtools.vjkit.base; import java.lang.management.ManagementFactory; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.StringUtils; /** * 运行时工具类 * * 1.取得当前进程PID, JVM参数 * * 2.注册JVM关闭钩子, 获得CPU核数 * * 3.通过StackTrace 获得当前方法的类名方法名,调用者的类名方法名(获取StackTrace有消耗,不要滥用) */ public class RuntimeUtil { private static AtomicInteger shutdownHookThreadIndex = new AtomicInteger(0); /////// RuntimeMXBean相关 ////// /** * 获得当前进程的PID * * 当失败时返回-1 */ public static int getPid() { // format: "pid@hostname" String jvmName = ManagementFactory.getRuntimeMXBean().getName(); String[] split = jvmName.split("@"); if (split.length != 2) { return -1; } try { return Integer.parseInt(split[0]); } catch (Exception e) { // NOSONAR return -1; } } /** * 返回应用启动到现在的毫秒数 */ public static long getUpTime() { return ManagementFactory.getRuntimeMXBean().getUptime(); } /** * 返回输入的JVM参数列表 */ public static String getVmArguments() { List vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); return StringUtils.join(vmArguments, " "); } //////////// Runtime 相关//////////////// /** * 获取CPU核数 */ public static int getCores() { return Runtime.getRuntime().availableProcessors(); } /** * 注册JVM关闭时的钩子程序 */ public static void addShutdownHook(Runnable runnable) { Runtime.getRuntime().addShutdownHook( new Thread(runnable, "Thread-ShutDownHook-" + shutdownHookThreadIndex.incrementAndGet())); } //////// 通过StackTrace 获得当前方法的调用者 //// /** * 通过StackTrace,获得调用者的类名. * * 获取StackTrace有消耗,不要滥用 */ public static String getCallerClass() { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); if (stacktrace.length >= 4) { StackTraceElement element = stacktrace[3]; return element.getClassName(); } else { return StringUtils.EMPTY; } } /** * 通过StackTrace,获得调用者的"类名.方法名()" * * 获取StackTrace有消耗,不要滥用 */ public static String getCallerMethod() { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); if (stacktrace.length >= 4) { StackTraceElement element = stacktrace[3]; return element.getClassName() + '.' + element.getMethodName() + "()"; } else { return StringUtils.EMPTY; } } /** * 通过StackTrace,获得当前方法的类名. * * 获取StackTrace有消耗,不要滥用 */ public static String getCurrentClass() { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); if (stacktrace.length >= 3) { StackTraceElement element = stacktrace[2]; return element.getClassName(); } else { return StringUtils.EMPTY; } } /** * 通过StackTrace,获得当前方法的"类名.方法名()" * * 获取StackTrace有消耗,不要滥用 */ public static String getCurrentMethod() { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); if (stacktrace.length >= 3) { StackTraceElement element = stacktrace[2]; return element.getClassName() + '.' + element.getMethodName() + "()"; } else { return StringUtils.EMPTY; } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/SystemPropertiesUtil.java ================================================ package com.vip.vjtools.vjkit.base; import java.util.List; import java.util.Properties; import java.util.concurrent.CopyOnWriteArrayList; import com.vip.vjtools.vjkit.number.NumberUtil; /** * 关于SystemProperties的工具类 * * 1. 统一风格的读取系统变量到各种数据类型,其中Boolean.readBoolean的风格不统一,Double则不支持,都进行了扩展. * * 2. 简单的合并系统变量(-D),环境变量 和默认值,以系统变量优先,在未引入Commons Config时使用. * * 3. Properties 本质上是一个HashTable,每次读写都会加锁,所以不支持频繁的System.getProperty(name)来检查系统内容变化 因此扩展了一个ListenableProperties, * 在其所关心的属性变化时进行通知. */ public class SystemPropertiesUtil { /** * 读取Boolean类型的系统变量,为空时返回null,代表未设置,而不是Boolean.getBoolean()的false. */ public static Boolean getBoolean(String name) { String stringResult = System.getProperty(name); return BooleanUtil.toBooleanObject(stringResult); } /** * 读取Boolean类型的系统变量,为空时返回默认值, 而不是Boolean.getBoolean()的false. */ public static Boolean getBoolean(String name, Boolean defaultValue) { String stringResult = System.getProperty(name); return BooleanUtil.toBooleanObject(stringResult, defaultValue); } /** * 读取String类型的系统变量,为空时返回null. */ public static String getString(String name) { return System.getProperty(name); } /** * 读取String类型的系统变量,为空时返回默认值 */ public static String getString(String name, String defaultValue) { return System.getProperty(name, defaultValue); } /** * 读取Integer类型的系统变量,为空时返回null. */ public static Integer getInteger(String name) { return Integer.getInteger(name); } /** * 读取Integer类型的系统变量,为空时返回默认值 */ public static Integer getInteger(String name, Integer defaultValue) { return Integer.getInteger(name, defaultValue); } /** * 读取Long类型的系统变量,为空时返回null. */ public static Long getLong(String name) { return Long.getLong(name); } /** * 读取Integer类型的系统变量,为空时返回默认值 */ public static Long getLong(String name, Long defaultValue) { return Long.getLong(name, defaultValue); } /** * 读取Double类型的系统变量,为空时返回null. */ public static Double getDouble(String propertyName) { return NumberUtil.toDoubleObject(System.getProperty(propertyName), null); } /** * 读取Double类型的系统变量,为空时返回默认值. */ public static Double getDouble(String propertyName, Double defaultValue) { Double propertyValue = NumberUtil.toDoubleObject(System.getProperty(propertyName), null); return propertyValue != null ? propertyValue : defaultValue; } /////////// 简单的合并系统变量(-D),环境变量 和默认值,以系统变量优先./////////////// /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ public static String getString(String propertyName, String envName, String defaultValue) { checkEnvName(envName); String propertyValue = System.getProperty(propertyName); if (propertyValue != null) { return propertyValue; } else { propertyValue = System.getenv(envName); return propertyValue != null ? propertyValue : defaultValue; } } /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ public static Integer getInteger(String propertyName, String envName, Integer defaultValue) { checkEnvName(envName); Integer propertyValue = NumberUtil.toIntObject(System.getProperty(propertyName), null); if (propertyValue != null) { return propertyValue; } else { propertyValue = NumberUtil.toIntObject(System.getenv(envName), null); return propertyValue != null ? propertyValue : defaultValue; } } /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ public static Long getLong(String propertyName, String envName, Long defaultValue) { checkEnvName(envName); Long propertyValue = NumberUtil.toLongObject(System.getProperty(propertyName), null); if (propertyValue != null) { return propertyValue; } else { propertyValue = NumberUtil.toLongObject(System.getenv(envName), null); return propertyValue != null ? propertyValue : defaultValue; } } /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ public static Double getDouble(String propertyName, String envName, Double defaultValue) { checkEnvName(envName); Double propertyValue = NumberUtil.toDoubleObject(System.getProperty(propertyName), null); if (propertyValue != null) { return propertyValue; } else { propertyValue = NumberUtil.toDoubleObject(System.getenv(envName), null); return propertyValue != null ? propertyValue : defaultValue; } } /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ public static Boolean getBoolean(String propertyName, String envName, Boolean defaultValue) { checkEnvName(envName); Boolean propertyValue = BooleanUtil.toBooleanObject(System.getProperty(propertyName), null); if (propertyValue != null) { return propertyValue; } else { propertyValue = BooleanUtil.toBooleanObject(System.getenv(envName), null); return propertyValue != null ? propertyValue : defaultValue; } } /** * 检查环境变量名不能有'.',在linux下不支持 */ private static void checkEnvName(String envName) { if (envName == null || envName.indexOf('.') != -1) { throw new IllegalArgumentException("envName " + envName + "is null or has dot which is not valid"); } } /////////// ListenableProperties ///////////// /** * Properties 本质上是一个HashTable,每次读写都会加锁,所以不支持频繁的System.getProperty(name)来检查系统内容变化 因此扩展了一个ListenableProperties, * 在其所关心的属性变化时进行通知. * * @see ListenableProperties */ public static synchronized void registerSystemPropertiesListener(PropertiesListener listener) { Properties currentProperties = System.getProperties(); // 将System的properties实现替换为ListenableProperties if (!(currentProperties instanceof ListenableProperties)) { ListenableProperties newProperties = new ListenableProperties(currentProperties); System.setProperties(newProperties); currentProperties = newProperties; } ((ListenableProperties) currentProperties).register(listener); } /** * Properties 本质上是一个HashTable,每次读写都会加锁,所以不支持频繁的System.getProperty(name)来检查系统内容变化 因此扩展了Properties子类, * 在其所关心的属性变化时进行通知. * * @see PropertiesListener */ public static class ListenableProperties extends Properties { private static final long serialVersionUID = -8282465702074684324L; protected transient List listeners = new CopyOnWriteArrayList(); public ListenableProperties(Properties properties) { super(properties); } public void register(PropertiesListener listener) { listeners.add(listener); } @Override public synchronized Object setProperty(String key, String value) { Object result = put(key, value); for (PropertiesListener listener : listeners) { if (listener.propertyName.equals(key)) { listener.onChange(key, value); } } return result; } } /** * 获取所关心的Property变更的Listener基类. */ public abstract static class PropertiesListener { // 关心的Property protected String propertyName; public PropertiesListener(String propertyName) { this.propertyName = propertyName; } public abstract void onChange(String propertyName, String value); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/ValueValidator.java ================================================ package com.vip.vjtools.vjkit.base; import org.apache.commons.lang3.StringUtils; /** * 数值校验取值器 * * 提供对配置值进行校验,并根据结果决定是否使用默认值。 * * Guava, Commons Lang里的Validate类用于判断并抛异常。 * * 而ValueValidator的行为是取默认值,多用于配置值的处理。 * * 除默认提供的Validator,可自行扩写。 */ public class ValueValidator { /** * 对目标值进行校验,并根据校验结果取值 * * 使用示例(校验目标值是否大于0, 如果小于 0 则取值为 1) * * ValueValidator.checkAndGet(idleTime, 1, Validator.INTEGER_GT_ZERO_VALIDATOR) * * @param value 校验值 * @param defaultValue 校验失败默认值 * @param v 校验器 * @return 经Validator校验后的返回值,校验成功返回 value, 校验失败返回 defaultValue */ public static T checkAndGet(T value, T defaultValue, Validator v) { if (v.validate(value)) { return value; } return defaultValue; } /** * 对值进行规则匹配的验证器 */ public interface Validator { /** * 校验值是否匹配 */ boolean validate(T value); /** * 校验器: 数值配置不为null, 且大于0较验 */ Validator INTEGER_GT_ZERO_VALIDATOR = new Validator() { @Override public boolean validate(Integer value) { return (value != null && value > 0); } }; /** * 校验器: 字符串不为空串较验 */ Validator STRING_EMPTY_VALUE_VALIDATOR = new Validator() { @Override public boolean validate(String value) { return StringUtils.isNotEmpty(value); } }; /** * 校验器: BOOL字符串较验 */ Validator STRICT_BOOL_VALUE_VALIDATOR = new Validator() { @Override public boolean validate(String value) { return "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value); } }; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/annotation/NotNull.java ================================================ package com.vip.vjtools.vjkit.base.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Target; /** * 标注参数、属性、方法不可为 Null */ @Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD }) public @interface NotNull { } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/annotation/Nullable.java ================================================ package com.vip.vjtools.vjkit.base.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Target; /** * 标注参数、属性、方法可为 Null */ @Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD }) public @interface Nullable { } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/annotation/VisibleForTesting.java ================================================ package com.vip.vjtools.vjkit.base.annotation; /** * 标注因为方便UT,将方法/属性的可访问范围扩大了,参考Guava */ public @interface VisibleForTesting { } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/CloneableException.java ================================================ package com.vip.vjtools.vjkit.base.type; import com.vip.vjtools.vjkit.base.ExceptionUtil; /** * 适用于异常信息需要变更的情况, 可通过clone(),不经过构造函数(也就避免了获得StackTrace)地从之前定义的静态异常中克隆,再设定新的异常信息 * * private static CloneableException TIMEOUT_EXCEPTION = new CloneableException("Timeout") .setStackTrace(My.class, * "hello"); ... * * throw TIMEOUT_EXCEPTION.clone("Timeout for 40ms"); * */ public class CloneableException extends Exception implements Cloneable { private static final long serialVersionUID = -6270471689928560417L; protected String message; // NOSONAR public CloneableException() { super((Throwable) null); } public CloneableException(String message) { super((Throwable) null); this.message = message; } public CloneableException(String message, Throwable cause) { super(cause); this.message = message; } @Override public CloneableException clone() { // NOSONAR try { return (CloneableException) super.clone(); } catch (CloneNotSupportedException e) {// NOSONAR return null; } } @Override public String getMessage() { return message; } /** * 简便函数,定义静态异常时使用 */ public CloneableException setStackTrace(Class throwClazz, String throwMethod) { ExceptionUtil.setStackTrace(this, throwClazz, throwMethod); return this; } /** * 简便函数, clone并重新设定Message */ public CloneableException clone(String message) { CloneableException newException = this.clone(); newException.setMessage(message); return newException; } /** * 简便函数, 重新设定Message */ public CloneableException setMessage(String message) { this.message = message; return this; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/CloneableRuntimeException.java ================================================ package com.vip.vjtools.vjkit.base.type; import com.vip.vjtools.vjkit.base.ExceptionUtil; /** * 适用于异常信息需要变更的情况, 可通过clone(),不经过构造函数(也就避免了获得StackTrace)地从之前定义的静态异常中克隆,再设定新的异常信息 * * @see CloneableException */ public class CloneableRuntimeException extends RuntimeException implements Cloneable { private static final long serialVersionUID = 3984796576627959400L; protected String message; // NOSONAR public CloneableRuntimeException() { super((Throwable) null); } public CloneableRuntimeException(String message) { super((Throwable) null); this.message = message; } public CloneableRuntimeException(String message, Throwable cause) { super(cause); this.message = message; } @Override public CloneableRuntimeException clone() { // NOSONAR try { return (CloneableRuntimeException) super.clone(); } catch (CloneNotSupportedException e) { // NOSONAR return null; } } @Override public String getMessage() { return message; } /** * 简便函数,定义静态异常时使用 */ public CloneableRuntimeException setStackTrace(Class throwClazz, String throwMethod) { ExceptionUtil.setStackTrace(this, throwClazz, throwMethod); return this; } /** * 简便函数, clone并重新设定Message */ public CloneableRuntimeException clone(String message) { CloneableRuntimeException newException = this.clone(); newException.setMessage(message); return newException; } /** * 简便函数, 重新设定Message */ public CloneableRuntimeException setMessage(String message) { this.message = message; return this; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/Pair.java ================================================ package com.vip.vjtools.vjkit.base.type; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 引入一个简简单单的Pair, 用于返回值返回两个元素. * * copy from Twitter Common */ public class Pair { @Nullable private final L left; @Nullable private final R right; /** * Creates a new pair. */ public Pair(@Nullable L left, @Nullable R right) { this.left = left; this.right = right; } @Nullable public L getLeft() { return left; } @Nullable public R getRight() { return right; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((left == null) ? 0 : left.hashCode()); return prime * result + ((right == null) ? 0 : right.hashCode()); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Pair other = (Pair) obj; if (left == null) { if (other.left != null) { return false; } } else if (!left.equals(other.left)) { return false; } if (right == null) { if (other.right != null) { return false; } } else if (!right.equals(other.right)) { return false; } return true; } @Override public String toString() { return "Pair [left=" + left + ", right=" + right + ']'; } /** * 根据等号左边的泛型,自动构造合适的Pair */ public static Pair of(@Nullable L left, @Nullable R right) { return new Pair(left, right); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/Triple.java ================================================ package com.vip.vjtools.vjkit.base.type; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 引入一个简简单单的Triple, 用于返回值返回三个元素. * * copy from Twitter Common */ public class Triple { @Nullable private final L left; @Nullable private final M middle; @Nullable private final R right; /** * Creates a new Triple. */ public Triple(@Nullable L left, @Nullable M middle, @Nullable R right) { this.left = left; this.middle = middle; this.right = right; } @Nullable public L getLeft() { return left; } @Nullable public M getMiddle() { return middle; } @Nullable public R getRight() { return right; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((left == null) ? 0 : left.hashCode()); result = prime * result + ((middle == null) ? 0 : middle.hashCode()); return prime * result + ((right == null) ? 0 : right.hashCode()); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Triple other = (Triple) obj; if (left == null) { if (other.left != null) { return false; } } else if (!left.equals(other.left)) { return false; } if (middle == null) { if (other.middle != null) { return false; } } else if (!middle.equals(other.middle)) { return false; } if (right == null) { if (other.right != null) { return false; } } else if (!right.equals(other.right)) { return false; } return true; } @Override public String toString() { return "Triple [left=" + left + ", middle=" + middle + ", right=" + right + ']'; } /** * 根据等号左边的泛型,自动构造合适的Triple */ public static Triple of(@Nullable L left, @Nullable M middle, @Nullable R right) { return new Triple(left, middle, right); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/UncheckedException.java ================================================ package com.vip.vjtools.vjkit.base.type; /** * CheckedException的wrapper. * * 返回Message时, 将返回内层Exception的Message. */ public class UncheckedException extends RuntimeException { private static final long serialVersionUID = 4140223302171577501L; public UncheckedException(Throwable wrapped) { super(wrapped); } @Override public String getMessage() { return super.getCause().getMessage(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/ArrayUtil.java ================================================ package com.vip.vjtools.vjkit.collection; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Random; import com.google.common.collect.ObjectArrays; import com.google.common.primitives.Doubles; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 数组工具类. * * 1. 创建Array的函数 * * 2. 数组的乱序与contact相加 * * 3. 从Array转换到Guava的底层为原子类型的List * * JDK Arrays的其他函数,如sort(), toString() 请直接调用 * * Common Lang ArrayUtils的其他函数,如subarray(),reverse(),indexOf(), 请直接调用 */ public class ArrayUtil { /** * 传入类型与大小创建数组. * * Array.newInstance()的性能并不差 */ @SuppressWarnings("unchecked") public static T[] newArray(Class type, int length) { return (T[]) Array.newInstance(type, length); } /** * 从collection转为Array, 以 list.toArray(new String[0]); 最快 不需要创建list.size()的数组. * * 本函数等价于list.toArray(new String[0]); 用户也可以直接用后者. * * https://shipilev.net/blog/2016/arrays-wisdom-ancients/ */ @SuppressWarnings("unchecked") public static T[] toArray(Collection col, Class type) { return col.toArray((T[]) Array.newInstance(type, 0)); } /** * Swaps the two specified elements in the specified array. */ private static void swap(Object[] arr, int i, int j) { Object tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } /** * 将传入的数组乱序 */ public static T[] shuffle(T[] array) { if (array != null && array.length > 1) { Random rand = new Random(); return shuffle(array, rand); } else { return array; } } /** * 将传入的数组乱序 */ public static T[] shuffle(T[] array, Random random) { if (array != null && array.length > 1 && random != null) { for (int i = array.length; i > 1; i--) { swap(array, i - 1, random.nextInt(i)); } } return array; } /** * 添加元素到数组头. */ public static T[] concat(@Nullable T element, T[] array) { return ObjectArrays.concat(element, array); } /** * 添加元素到数组末尾. */ public static T[] concat(T[] array, @Nullable T element) { return ObjectArrays.concat(array, element); } ////////////////// guava Array 转换为底层为原子类型的List /////////// /** * 原版将数组转换为List. * * 注意转换后的List不能写入, 否则抛出UnsupportedOperationException * * @see java.util.Arrays#asList(Object...) */ public static List asList(T... a) { return Arrays.asList(a); } /** * Arrays.asList()的加强版, 返回一个底层为原始类型int的List * * 与保存Integer相比节约空间,同时只在读取数据时AutoBoxing. * * @see java.util.Arrays#asList(Object...) * @see com.google.common.primitives.Ints#asList(int...) * */ public static List intAsList(int... backingArray) { return Ints.asList(backingArray); } /** * Arrays.asList()的加强版, 返回一个底层为原始类型long的List * * 与保存Long相比节约空间,同时只在读取数据时AutoBoxing. * * @see java.util.Arrays#asList(Object...) * @see com.google.common.primitives.Longs#asList(long...) */ public static List longAsList(long... backingArray) { return Longs.asList(backingArray); } /** * Arrays.asList()的加强版, 返回一个底层为原始类型double的List * * 与保存Double相比节约空间,同时也避免了AutoBoxing. * * @see java.util.Arrays#asList(Object...) * @see com.google.common.primitives.Doubles#asList(double...) */ public static List doubleAsList(double... backingArray) { return Doubles.asList(backingArray); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/CollectionUtil.java ================================================ package com.vip.vjtools.vjkit.collection; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Ordering; import com.vip.vjtools.vjkit.base.type.Pair; /** * 通用Collection的工具集 * * 1. 集合是否为空,取得集合中首个及最后一个元素,判断集合是否完全相等 * * 2. 集合的最大最小值,及Top N, Bottom N * * 关于List, Map, Queue, Set的特殊工具集,另见特定的Util. * * 另JDK中缺少ComparableComparator和NullComparator,直到JDK8才补上。 * 因此平时请使用guava的Ordering.natural(),fluentable的API更好用,可以链式设置nullFirst,nullLast,reverse * * @see com.google.common.collect.Ordering */ public class CollectionUtil { /** * 判断是否为空. */ public static boolean isEmpty(Collection collection) { return (collection == null) || collection.isEmpty(); } /** * 判断是否不为空. */ public static boolean isNotEmpty(Collection collection) { return (collection != null) && !(collection.isEmpty()); } /** * 取得Collection的第一个元素,如果collection为空返回null. */ public static T getFirst(Collection collection) { if (isEmpty(collection)) { return null; } if (collection instanceof List) { return ((List) collection).get(0); } return collection.iterator().next(); } /** * 获取Collection的最后一个元素,如果collection为空返回null. */ public static T getLast(Collection collection) { if (isEmpty(collection)) { return null; } // 当类型List时,直接取得最后一个元素. if (collection instanceof List) { List list = (List) collection; return list.get(list.size() - 1); } return Iterators.getLast(collection.iterator()); } /** * 两个集合中的所有元素按顺序相等. */ public static boolean elementsEqual(Iterable iterable1, Iterable iterable2) { return Iterables.elementsEqual(iterable1, iterable2); } ///////////// 求最大最小值,及Top N, Bottom N////////// /** * 返回无序集合中的最小值,使用元素默认排序 */ public static > T min(Collection coll) { return Collections.min(coll); } /** * 返回无序集合中的最小值 */ public static T min(Collection coll, Comparator comp) { return Collections.min(coll, comp); } /** * 返回无序集合中的最大值,使用元素默认排序 */ public static > T max(Collection coll) { return Collections.max(coll); } /** * 返回无序集合中的最大值 */ public static T max(Collection coll, Comparator comp) { return Collections.max(coll, comp); } /** * 同时返回无序集合中的最小值和最大值,使用元素默认排序 * * 在返回的Pair中,第一个为最小值,第二个为最大值 */ public static > Pair minAndMax(Collection coll) { Iterator i = coll.iterator(); T minCandidate = i.next(); T maxCandidate = minCandidate; while (i.hasNext()) { T next = i.next(); if (next.compareTo(minCandidate) < 0) { minCandidate = next; } else if (next.compareTo(maxCandidate) > 0) { maxCandidate = next; } } return Pair.of(minCandidate, maxCandidate); } /** * 返回无序集合中的最小值和最大值 * * 在返回的Pair中,第一个为最小值,第二个为最大值 */ public static Pair minAndMax(Collection coll, Comparator comp) { Iterator i = coll.iterator(); T minCandidate = i.next(); T maxCandidate = minCandidate; while (i.hasNext()) { T next = i.next(); if (comp.compare(next, minCandidate) < 0) { minCandidate = next; } else if (comp.compare(next, maxCandidate) > 0) { maxCandidate = next; } } return Pair.of(minCandidate, maxCandidate); } /** * 返回Iterable中最大的N个对象, back by guava. */ public static > List topN(Iterable coll, int n) { return Ordering.natural().greatestOf(coll, n); } /** * 返回Iterable中最大的N个对象, back by guava. */ public static List topN(Iterable coll, int n, Comparator comp) { return Ordering.from(comp).greatestOf(coll, n); } /** * 返回Iterable中最小的N个对象, back by guava. */ public static > List bottomN(Iterable coll, int n) { return Ordering.natural().leastOf(coll, n); } /** * 返回Iterable中最小的N个对象, back by guava. */ public static List bottomN(Iterable coll, int n, Comparator comp) { return Ordering.from(comp).leastOf(coll, n); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/ListUtil.java ================================================ package com.vip.vjtools.vjkit.collection; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import com.google.common.collect.Lists; /** * 关于List的工具集合. * * 1. 常用函数(如是否为空,sort/binarySearch/shuffle/reverse(via JDK Collection) * * 2. 各种构造函数(from guava and JDK Collection) * * 3. 各种扩展List类型的创建函数 * * 5. 集合运算:交集,并集, 差集, 补集,from Commons Collection,但对其不合理的地方做了修正 */ @SuppressWarnings("unchecked") public class ListUtil { /** * 判断是否为空. */ public static boolean isEmpty(List list) { return (list == null) || list.isEmpty(); } /** * 判断是否不为空. */ public static boolean isNotEmpty(List list) { return (list != null) && !(list.isEmpty()); } /** * 获取第一个元素, 如果List为空返回 null. */ public static T getFirst(List list) { if (isEmpty(list)) { return null; } return list.get(0); } /** * 获取最后一个元素,如果List为空返回null. */ public static T getLast(List list) { if (isEmpty(list)) { return null; } return list.get(list.size() - 1); } ///////////////// from Guava的构造函数/////////////////// /** * 根据等号左边的类型,构造类型正确的ArrayList. * * @deprecated JDK7开始已经简化 */ @Deprecated public static ArrayList newArrayList() { return new ArrayList(); } /** * 根据等号左边的类型,构造类型正确的ArrayList, 并初始化元素. * * @see com.google.common.collect.Lists#newArrayList(Object...) */ public static ArrayList newArrayList(T... elements) { return Lists.newArrayList(elements); } /** * 根据等号左边的类型,构造类型正确的ArrayList, 并初始化元素. * * @see com.google.common.collect.Lists#newArrayList(Iterable) */ public static ArrayList newArrayList(Iterable elements) { return Lists.newArrayList(elements); } /** * 根据等号左边的类型,构造类型正确的ArrayList, 并初始化数组大小. * * @see com.google.common.collect.Lists#newArrayListWithCapacity(int) */ public static ArrayList newArrayListWithCapacity(int initSize) { return new ArrayList(initSize); } /** * 根据等号左边的类型,构造类型正确的LinkedList. * * @deprecated JDK7开始已经简化 */ @Deprecated public static LinkedList newLinkedList() { return new LinkedList(); } /** * 根据等号左边的类型,构造类型正确的LinkedList. * * @see com.google.common.collect.Lists#newLinkedList() */ public static LinkedList newLinkedList(Iterable elements) { return Lists.newLinkedList(elements); } /** * 根据等号左边的类型,构造类型正确的CopyOnWriteArrayList. * * @deprecated JDK7开始已经简化 */ @Deprecated public static CopyOnWriteArrayList newCopyOnWriteArrayList() { return new CopyOnWriteArrayList(); } /** * 根据等号左边的类型,构造类型转换的CopyOnWriteArrayList, 并初始化元素. */ public static CopyOnWriteArrayList newCopyOnWriteArrayList(T... elements) { return new CopyOnWriteArrayList(elements); } ///////////////// from JDK Collections的常用构造函数 /////////////////// /** * 返回一个空的结构特殊的List,节约空间. * * 注意返回的List不可写, 写入会抛出UnsupportedOperationException. * * @see java.util.Collections#emptyList() */ public static final List emptyList() { return Collections.emptyList(); } /** * 如果list为null,转化为一个安全的空List. * * 注意返回的List不可写, 写入会抛出UnsupportedOperationException. * * @see java.util.Collections#emptyList() */ public static List emptyListIfNull(final List list) { return list == null ? (List) Collections.EMPTY_LIST : list; } /** * 返回只含一个元素但结构特殊的List,节约空间. * * 注意返回的List不可写, 写入会抛出UnsupportedOperationException. * * @see java.util.Collections#singletonList(Object) */ public static List singletonList(T o) { return Collections.singletonList(o); } /** * 返回包装后不可修改的List. * * 如果尝试写入会抛出UnsupportedOperationException. * * @see java.util.Collections#unmodifiableList(List) */ public static List unmodifiableList(List list) { return Collections.unmodifiableList(list); } /** * 返回包装后同步的List,所有方法都会被synchronized原语同步. * * 用于CopyOnWriteArrayList与 ArrayDequeue均不符合的场景 */ public static List synchronizedList(List list) { return Collections.synchronizedList(list); } ///////////////// from JDK Collections的常用函数 /////////////////// /** * 升序排序, 采用JDK认为最优的排序算法, 使用元素自身的compareTo()方法 * * @see java.util.Collections#sort(List) */ public static > void sort(List list) { Collections.sort(list); } /** * 倒序排序, 采用JDK认为最优的排序算法,使用元素自身的compareTo()方法 * * @see java.util.Collections#sort(List) */ public static > void sortReverse(List list) { Collections.sort(list, Collections.reverseOrder()); } /** * 升序排序, 采用JDK认为最优的排序算法, 使用Comparator. * * @see java.util.Collections#sort(List, Comparator) */ public static void sort(List list, Comparator c) { Collections.sort(list, c); } /** * 倒序排序, 采用JDK认为最优的排序算法, 使用Comparator * * @see java.util.Collections#sort(List, Comparator) */ public static void sortReverse(List list, Comparator c) { Collections.sort(list, Collections.reverseOrder(c)); } /** * 二分法快速查找对象, 使用Comparable对象自身的比较. * * list必须已按升序排序. * * 如果不存在,返回一个负数,代表如果要插入这个对象,应该插入的位置 * * @see java.util.Collections#binarySearch(List, Object) */ public static int binarySearch(List> sortedList, T key) { return Collections.binarySearch(sortedList, key); } /** * 二分法快速查找对象,使用Comparator. * * list必须已按升序排序. * * 如果不存在,返回一个负数,代表如果要插入这个对象,应该插入的位置 * * @see java.util.Collections#binarySearch(List, Object, Comparator) */ public static int binarySearch(List sortedList, T key, Comparator c) { return Collections.binarySearch(sortedList, key, c); } /** * 随机乱序,使用默认的Random. * * @see java.util.Collections#shuffle(List) */ public static void shuffle(List list) { Collections.shuffle(list); } /** * 随机乱序,使用传入的Random. * * @see java.util.Collections#shuffle(List, Random) */ public static void shuffle(List list, Random rnd) { Collections.shuffle(list, rnd); } /** * 返回一个倒转顺序访问的List,仅仅是一个倒序的View,不会实际多生成一个List * * @see com.google.common.collect.Lists#reverse(List) */ public static List reverse(final List list) { return Lists.reverse(list); } ///////////////// from guava的函数 /////////////////// /** * List分页函数 */ public static List> partition(List list, int size) { return Lists.partition(list, size); } ///////////////// 其他处理函数 /////////////// /** * 清理掉List中的Null对象 */ public static void notNullList(List list) { if (isEmpty(list)) { return; } Iterator ite = list.iterator(); while (ite.hasNext()) { T obj = ite.next(); // 清理掉null的集合 if (null == obj) { ite.remove(); } } } public static void uniqueNotNullList(List list) { if (isEmpty(list)) { return; } Iterator ite = list.iterator(); Set set = new HashSet<>((int) (list.size() / 0.75F + 1.0F)); while (ite.hasNext()) { T obj = ite.next(); // 清理掉null的集合 if (null == obj) { ite.remove(); continue; } // 清理掉重复的集合 if (set.contains(obj)) { ite.remove(); continue; } set.add(obj); } } ///////////////// 集合运算 /////////////////// /** * list1,list2的并集(在list1或list2中的对象),产生新List * * 对比Apache Common Collection4 ListUtils, 优化了初始大小 */ public static List union(final List list1, final List list2) { final List result = new ArrayList(list1.size() + list2.size()); result.addAll(list1); result.addAll(list2); return result; } /** * list1, list2的交集(同时在list1和list2的对象),产生新List * * copy from Apache Common Collection4 ListUtils,但其做了不合理的去重,因此重新改为性能较低但不去重的版本 * * 与List.retainAll()相比,考虑了的List中相同元素出现的次数, 如"a"在list1出现两次,而在list2中只出现一次,则交集里会保留一个"a". */ public static List intersection(final List list1, final List list2) { List smaller = list1; List larger = list2; if (list1.size() > list2.size()) { smaller = list2; larger = list1; } // 克隆一个可修改的副本 List newSmaller = new ArrayList(smaller); List result = new ArrayList(smaller.size()); for (final T e : larger) { if (newSmaller.contains(e)) { result.add(e); newSmaller.remove(e); } } return result; } /** * list1, list2的差集(在list1,不在list2中的对象),产生新List. * * 与List.removeAll()相比,会计算元素出现的次数,如"a"在list1出现两次,而在list2中只出现一次,则差集里会保留一个"a". */ public static List difference(final List list1, final List list2) { final List result = new ArrayList(list1); final Iterator iterator = list2.iterator(); while (iterator.hasNext()) { result.remove(iterator.next()); } return result; } /** * list1, list2的补集(在list1或list2中,但不在交集中的对象,又叫反交集)产生新List. * * copy from Apache Common Collection4 ListUtils,但其并集-交集时,初始大小没有对交集*2,所以做了修改 */ public static List disjoint(final List list1, final List list2) { List intersection = intersection(list1, list2); List towIntersection = union(intersection, intersection); return difference(union(list1, list2), towIntersection); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/MapUtil.java ================================================ package com.vip.vjtools.vjkit.collection; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EnumMap; 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.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; import org.apache.commons.lang3.Validate; import com.google.common.base.Preconditions; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.vip.vjtools.vjkit.base.annotation.NotNull; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 关于Map的工具集合, * * 1. 常用函数(如是否为空, 两个map的Diff对比,针对value值的排序) * * 2. 对于并发Map,增加putIfAbsent(返回最终值版), createIfAbsent这两个重要函数(from Common Lang) * * 3. 便捷的构造函数(via guava,Java Collections,并增加了用数组,List等方式初始化Map的函数) * * 4. JDK Collections的empty,singleton */ @SuppressWarnings("unchecked") public class MapUtil { public static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 判断是否为空. */ public static boolean isEmpty(final Map map) { return (map == null) || map.isEmpty(); } /** * 判断是否为空. */ public static boolean isNotEmpty(final Map map) { return (map != null) && !map.isEmpty(); } /** * ConcurrentMap的putIfAbsent()返回之前的Value,此函数封装返回最终存储在Map中的Value * * @see org.apache.commons.lang3.concurrent.ConcurrentUtils#putIfAbsent(ConcurrentMap, Object, Object) */ public static V putIfAbsentReturnLast(@NotNull final ConcurrentMap map, final K key, final V value) { final V result = map.putIfAbsent(key, value); return result != null ? result : value; } /** * 如果Key不存在则创建,返回最后存储在Map中的Value. * * 如果创建Value对象有一定成本, 直接使用PutIfAbsent可能重复浪费,则使用此类,传入一个被回调的ValueCreator,Lazy创建对象。 * * @see org.apache.commons.lang3.concurrent.ConcurrentUtils#createIfAbsent(ConcurrentMap, Object, * org.apache.commons.lang3.concurrent.ConcurrentInitializer) */ public static V createIfAbsentReturnLast(@NotNull final ConcurrentMap map, final K key, @NotNull final ValueCreator creator) { final V value = map.get(key); if (value == null) { return putIfAbsentReturnLast(map, key, creator.get()); } return value; } /** * Lazy创建Value值的回调类 * * @see MapUtil#createIfAbsentReturnLast(ConcurrentMap, Object, ValueCreator) */ public interface ValueCreator { /** * 创建对象 */ T get(); } ///////////////// from Guava的构造函数/////////////////// /** * 根据等号左边的类型, 构造类型正确的HashMap. * * 未初始化数组大小, 默认为16个桶. * * @deprecated JDK7开始已经简化 */ @Deprecated public static HashMap newHashMap() { return new HashMap(); } /** * 根据等号左边的类型, 构造类型正确的HashMap. * * 注意HashMap中有0.75的加载因子的影响, 需要进行运算后才能正确初始化HashMap的大小. * * 加载因子也是HashMap中减少Hash冲突的重要一环,如果读写频繁,总记录数不多的Map,可以比默认值0.75进一步降低,建议0.5 * * @see com.google.common.collect.Maps#newHashMap */ public static HashMap newHashMapWithCapacity(int expectedSize, float loadFactor) { int finalSize = (int) (expectedSize / loadFactor + 1.0F); return new HashMap(finalSize, loadFactor); } /** * 根据等号左边的类型, 构造类型正确的HashMap. * * 同时初始化第一个元素 */ public static HashMap newHashMap(final K key, final V value) { HashMap map = new HashMap(); map.put(key, value); return map; } /** * 根据等号左边的类型, 构造类型正确的HashMap. * * 同时初始化元素. */ public static HashMap newHashMap(@NotNull final K[] keys, @NotNull final V[] values) { Validate.isTrue(keys.length == values.length, "keys.length is %d but values.length is %d", keys.length, values.length); HashMap map = new HashMap(keys.length * 2); for (int i = 0; i < keys.length; i++) { map.put(keys[i], values[i]); } return map; } /** * 根据等号左边的类型, 构造类型正确的HashMap. * * 同时初始化元素. */ public static HashMap newHashMap(@NotNull final List keys, @NotNull final List values) { Validate.isTrue(keys.size() == values.size(), "keys.length is %s but values.length is %s", keys.size(), values.size()); HashMap map = new HashMap(keys.size() * 2); Iterator keyIt = keys.iterator(); Iterator valueIt = values.iterator(); while (keyIt.hasNext()) { map.put(keyIt.next(), valueIt.next()); } return map; } /** * 根据等号左边的类型,构造类型正确的TreeMap. * * @see com.google.common.collect.Maps#newTreeMap() */ @SuppressWarnings("rawtypes") public static TreeMap newSortedMap() { return new TreeMap(); } /** * 根据等号左边的类型,构造类型正确的TreeMap. * * @see com.google.common.collect.Maps#newTreeMap(Comparator) */ public static TreeMap newSortedMap(@Nullable Comparator comparator) { return Maps.newTreeMap(comparator); } /** * 相比HashMap,当key是枚举类时, 性能与空间占用俱佳. */ public static , V> EnumMap newEnumMap(@NotNull Class type) { return new EnumMap(Preconditions.checkNotNull(type)); } /** * 根据等号左边的类型,构造类型正确的ConcurrentHashMap. */ public static ConcurrentHashMap newConcurrentHashMap() { return new ConcurrentHashMap(); } /** * 根据等号左边的类型,构造类型正确的ConcurrentSkipListMap. */ public static ConcurrentSkipListMap newConcurrentSortedMap() { return new ConcurrentSkipListMap(); } ///////////////// from JDK Collections的常用构造函数 /////////////////// /** * 返回一个空的结构特殊的Map,节约空间. * * 注意返回的Map不可写, 写入会抛出UnsupportedOperationException. * * @see java.util.Collections#emptyMap() */ public static Map emptyMap() { return Collections.emptyMap(); } /** * 如果map为null,转化为一个安全的空Map. * * 注意返回的Map不可写, 写入会抛出UnsupportedOperationException. * * @see java.util.Collections#emptyMap() */ public static Map emptyMapIfNull(final Map map) { return map == null ? (Map) Collections.EMPTY_MAP : map; } /** * 返回一个只含一个元素但结构特殊的Map,节约空间. * * 注意返回的Map不可写, 写入会抛出UnsupportedOperationException. * * @see java.util.Collections#singletonMap(Object, Object) */ public static Map singletonMap(final K key, final V value) { return Collections.singletonMap(key, value); } /** * 返回包装后不可修改的Map. * * 如果尝试修改,会抛出UnsupportedOperationException * * @see java.util.Collections#unmodifiableMap(Map) */ public static Map unmodifiableMap(final Map m) { return Collections.unmodifiableMap(m); } /** * 返回包装后不可修改的有序Map. * * @see java.util.Collections#unmodifiableSortedMap(SortedMap) */ public static SortedMap unmodifiableSortedMap(final SortedMap m) { return Collections.unmodifiableSortedMap(m); } //////// 对两个Map进行diff的操作 /////// /** * 对两个Map进行比较,返回MapDifference,然后各种妙用. * * 包括key的差集,key的交集,以及key相同但value不同的元素。 * * @see com.google.common.collect.MapDifference */ public static MapDifference difference(Map left, Map right) { return Maps.difference(left, right); } //////////// 按值排序及取TOP N的操作 ///////// /** * 对一个Map按Value进行排序,返回排序LinkedHashMap,多用于Value是Counter的情况. * * @param reverse 按Value的倒序 or 正序排列 */ public static Map sortByValue(Map map, final boolean reverse) { return sortByValueInternal(map, reverse ? Ordering.from(new ComparableEntryValueComparator()).reverse() : new ComparableEntryValueComparator()); } /** * 对一个Map按Value进行排序,返回排序LinkedHashMap. */ public static Map sortByValue(Map map, final Comparator comparator) { return sortByValueInternal(map, new EntryValueComparator(comparator)); } private static Map sortByValueInternal(Map map, Comparator> comparator) { Set> entrySet = map.entrySet(); Entry[] entryArray = entrySet.toArray(new Entry[0]); Arrays.sort(entryArray, comparator); Map result = new LinkedHashMap(); for (Entry entry : entryArray) { result.put(entry.getKey(), entry.getValue()); } return result; } /** * 对一个Map按Value进行排序,返回排序LinkedHashMap,最多只返回n条,多用于Value是Counter的情况. * @param reverse 按Value的倒序 or 正序排列 */ public static Map topNByValue(Map map, final boolean reverse, int n) { return topNByValueInternal(map, n, reverse ? Ordering.from(new ComparableEntryValueComparator()).reverse() : new ComparableEntryValueComparator()); } /** * 对一个Map按Value进行排序,返回排序LinkedHashMap, 最多只返回n条,多用于Value是Counter的情况. */ public static Map topNByValue(Map map, final Comparator comparator, int n) { return topNByValueInternal(map, n, new EntryValueComparator(comparator)); } private static Map topNByValueInternal(Map map, int n, Comparator> comparator) { Set> entrySet = map.entrySet(); Entry[] entryArray = entrySet.toArray(new Entry[0]); Arrays.sort(entryArray, comparator); Map result = new LinkedHashMap(); int size = Math.min(n, entryArray.length); for (int i = 0; i < size; i++) { Entry entry = entryArray[i]; result.put(entry.getKey(), entry.getValue()); } return result; } private static final class ComparableEntryValueComparator implements Comparator> { @Override public int compare(Entry o1, Entry o2) { return (o1.getValue()).compareTo(o2.getValue()); } } private static final class EntryValueComparator implements Comparator> { private final Comparator comparator; private EntryValueComparator(Comparator comparator2) { this.comparator = comparator2; } @Override public int compare(Entry o1, Entry o2) { return comparator.compare(o1.getValue(), o2.getValue()); } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/QueueUtil.java ================================================ package com.vip.vjtools.vjkit.collection; import java.util.ArrayDeque; import java.util.Deque; import java.util.LinkedList; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; /** * Queue工具集. * * 各种Queue,Dequeue的创建 */ public class QueueUtil { /** * 创建ArrayDeque (JDK无ArrayQueue) * * 需设置初始长度,默认为16,数组满时成倍扩容 */ public static ArrayDeque newArrayDeque(int initSize) { return new ArrayDeque(initSize); } /** * 创建LinkedDeque (LinkedList实现了Deque接口) */ public static LinkedList newLinkedDeque() { return new LinkedList(); } /** * 创建无阻塞情况下,性能最优的并发队列 */ public static ConcurrentLinkedQueue newConcurrentNonBlockingQueue() { return new ConcurrentLinkedQueue(); } /** * 创建无阻塞情况下,性能最优的并发双端队列 */ public static Deque newConcurrentNonBlockingDeque() { return new java.util.concurrent.ConcurrentLinkedDeque(); } /** * 创建并发阻塞情况下,长度不受限的队列. * * 长度不受限,即生产者不会因为满而阻塞,但消费者会因为空而阻塞. */ public static LinkedBlockingQueue newBlockingUnlimitQueue() { return new LinkedBlockingQueue(); } /** * 创建并发阻塞情况下,长度不受限的双端队列. * * 长度不受限,即生产者不会因为满而阻塞,但消费者会因为空而阻塞. */ public static LinkedBlockingDeque newBlockingUnlimitDeque() { return new LinkedBlockingDeque(); } /** * 创建并发阻塞情况下,长度受限,更节约内存,但共用一把锁的队列(无双端队列实现). */ public static ArrayBlockingQueue newArrayBlockingQueue(int capacity) { return new ArrayBlockingQueue(capacity); } /** * 创建并发阻塞情况下,长度受限,头队尾两把锁, 但使用更多内存的队列. */ public static LinkedBlockingQueue newLinkedBlockingQueue(int capacity) { return new LinkedBlockingQueue(capacity); } /** * 创建并发阻塞情况下,长度受限,头队尾两把锁, 但使用更多内存的双端队列. */ public static LinkedBlockingDeque newBlockingDeque(int capacity) { return new LinkedBlockingDeque(capacity); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/SetUtil.java ================================================ package com.vip.vjtools.vjkit.collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; import com.google.common.collect.Sets; import com.vip.vjtools.vjkit.base.annotation.Nullable; import com.vip.vjtools.vjkit.collection.type.ConcurrentHashSet; /** * 关于Set的工具集合. * * 1. 各种Set的创建函数, 包括ConcurrentHashSet * * 2. 集合运算函数(交集,并集等,from guava) */ public class SetUtil { /** * 根据等号左边的类型,构造类型正确的HashSet. * * @see com.google.common.collect.Sets#newHashSet() */ public static HashSet newHashSet() { return new HashSet(); } /** * 根据等号左边的类型,构造类型正确的HashSet, 并初始化元素. * * @see com.google.common.collect.Sets#newHashSet(Object...) */ @SuppressWarnings("unchecked") public static HashSet newHashSet(T... elements) { return Sets.newHashSet(elements); } /** * HashSet涉及HashMap大小,因此建议在构造时传入需要初始的集合,其他如TreeSet不需要. * * @see com.google.common.collect.Sets#newHashSet(Iterable) */ public static HashSet newHashSet(Iterable elements) { return Sets.newHashSet(elements); } /** * 创建HashSet并设置初始大小,因为HashSet内部是HashMap,会计算LoadFactor后的真实大小. * * @see com.google.common.collect.Sets#newHashSetWithExpectedSize(int) */ public static HashSet newHashSetWithCapacity(int expectedSize) { return Sets.newHashSetWithExpectedSize(expectedSize); } /** * 根据等号左边的类型,构造类型正确的TreeSet, 通过实现了Comparable的元素自身进行排序. * * @see com.google.common.collect.Sets#newTreeSet() */ @SuppressWarnings("rawtypes") public static TreeSet newSortedSet() { return new TreeSet(); } /** * 根据等号左边的类型,构造类型正确的TreeSet, 并设置comparator. * * @see com.google.common.collect.Sets#newTreeSet(Comparator) */ public static TreeSet newSortedSet(@Nullable Comparator comparator) { return Sets.newTreeSet(comparator); } /** * 根据等号左边的类型,构造类型正确的ConcurrentHashSet */ public static ConcurrentHashSet newConcurrentHashSet() { return new ConcurrentHashSet(); } ///////////////// from JDK Collections的常用构造函数 /////////////////// /** * 返回一个空的结构特殊的Set,节约空间. * * 注意返回的Set不可写, 写入会抛出UnsupportedOperationException. * * @see java.util.Collections#emptySet() */ public static Set emptySet() { return Collections.emptySet(); } /** * 如果set为null,转化为一个安全的空Set. * * 注意返回的Set不可写, 写入会抛出UnsupportedOperationException. * * @see java.util.Collections#emptySet() */ public static Set emptySetIfNull(final Set set) { return set == null ? (Set) Collections.EMPTY_SET : set; } /** * 返回只含一个元素但结构特殊的Set,节约空间. * * 注意返回的Set不可写, 写入会抛出UnsupportedOperationException. * * @see java.util.Collections#singleton(Object) */ public static Set singletonSet(T o) { return Collections.singleton(o); } /** * 返回包装后不可修改的Set. * * 如果尝试修改,会抛出UnsupportedOperationException * * @see java.util.Collections#unmodifiableSet(Set) */ public static Set unmodifiableSet(Set s) { return Collections.unmodifiableSet(s); } /** * 从Map构造Set的大杀器, 可以用来制造各种Set * * @see java.util.Collections#newSetFromMap(Map) */ public static Set newSetFromMap(Map map) { return Collections.newSetFromMap(map); } //////////////// from guava的集合运算函数///////////// /** * set1, set2的并集(在set1或set2的对象)的只读view,不复制产生新的Set对象. * * 如果尝试写入该View会抛出UnsupportedOperationException */ public static Set unionView(final Set set1, final Set set2) { return Sets.union(set1, set2); } /** * set1, set2的交集(同时在set1和set2的对象)的只读view,不复制产生新的Set对象. * * 如果尝试写入该View会抛出UnsupportedOperationException */ public static Set intersectionView(final Set set1, final Set set2) { return Sets.intersection(set1, set2); } /** * set1, set2的差集(在set1,不在set2中的对象)的只读view,不复制产生新的Set对象. * * 如果尝试写入该View会抛出UnsupportedOperationException */ public static Set differenceView(final Set set1, final Set set2) { return Sets.difference(set1, set2); } /** * set1, set2的补集(在set1或set2中,但不在交集中的对象,又叫反交集)的只读view,不复制产生新的Set对象. * * 如果尝试写入该View会抛出UnsupportedOperationException */ public static Set disjointView(final Set set1, final Set set2) { return Sets.symmetricDifference(set1, set2); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/ConcurrentHashSet.java ================================================ package com.vip.vjtools.vjkit.collection.type; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * JDK并没有提供ConcurrentHashSet,考虑到JDK的HashSet也是基于HashMap实现的,因此ConcurrentHashSet也由ConcurrentHashMap完成。 * * 虽然也可以通过Collections.newSetFromMap(new ConcurrentHashMap()), * * 但声明一个单独的类型,阅读代码时能更清晰的知道set的并发友好性,代码来自JDK的SetFromMap,去除JDK8接口. */ public class ConcurrentHashSet extends AbstractSet implements Set, java.io.Serializable { private static final long serialVersionUID = -8672117787651310382L; private final Map m; private transient Set s; // Its keySet public ConcurrentHashSet() { m = new ConcurrentHashMap(); s = m.keySet(); } public void clear() { m.clear(); } public int size() { return m.size(); } public boolean isEmpty() { return m.isEmpty(); } public boolean contains(Object o) { return m.containsKey(o); } public boolean remove(Object o) { return m.remove(o) != null; } public boolean add(E e) { return m.put(e, Boolean.TRUE) == null; } public Iterator iterator() { return s.iterator(); } public Object[] toArray() { return s.toArray(); } public T[] toArray(T[] a) { return s.toArray(a); } @Override public String toString() { return s.toString(); } public int hashCode() { return s.hashCode(); } public boolean equals(Object o) { return o == this || s.equals(o); } public boolean containsAll(Collection c) { return s.containsAll(c); } public boolean removeAll(Collection c) { return s.removeAll(c); } public boolean retainAll(Collection c) { return s.retainAll(c); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/MoreLists.java ================================================ package com.vip.vjtools.vjkit.collection.type; import java.util.Comparator; /** * 特殊的List类型 */ public class MoreLists { /** * 排序的ArrayList. * * from Jodd的新类型,插入时排序,但指定插入index的方法如add(index,element)不支持 */ @SuppressWarnings("rawtypes") public static SortedArrayList createSortedArrayList() { return new SortedArrayList(); } /** * 排序的ArrayList. * * from Jodd的新类型,插入时排序,但指定插入index的方法如add(index,element)不支持 */ public static SortedArrayList createSortedArrayList(Comparator c) { return new SortedArrayList(c); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/MoreMaps.java ================================================ package com.vip.vjtools.vjkit.collection.type; import java.util.Comparator; import java.util.HashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.mutable.MutableLong; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.MapMaker; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.TreeRangeMap; import com.google.common.util.concurrent.AtomicLongMap; import com.vip.vjtools.vjkit.collection.type.primitive.IntObjectHashMap; import com.vip.vjtools.vjkit.collection.type.primitive.LongObjectHashMap; /** * 来自Guava,Netty等的特殊Map类型 */ public class MoreMaps { /** * 创建Key为弱引用的ConcurrentMap,Key对象可被回收. * * JDK没有WeakHashMap的并发实现, 由Guava提供 */ public static ConcurrentMap createWeakKeyConcurrentMap(int initialCapacity, int concurrencyLevel) { return new MapMaker().weakKeys().initialCapacity(initialCapacity).concurrencyLevel(concurrencyLevel).makeMap(); } /** * 创建Value为弱引用的ConcurrentMap,Value对象可被回收. * * JDK没有WeakHashMap的并发实现, 由Guava提供 */ public static ConcurrentMap createWeakValueConcurrentMap(int initialCapacity, int concurrencyLevel) { return new MapMaker().weakValues().initialCapacity(initialCapacity).concurrencyLevel(concurrencyLevel) .makeMap(); } /** * 创建移植自Netty的key为int的优化HashMap * * @param initialCapacity 默认为8 * @param loadFactor 默认为0.5 */ public static IntObjectHashMap createPrimitiveIntKeyMap(int initialCapacity, float loadFactor) { return new IntObjectHashMap(initialCapacity, loadFactor); } /** * 创建移植自Netty的key为long的优化HashMap * * @param initialCapacity 默认为8 * @param loadFactor 默认为0.5 */ public static LongObjectHashMap createPrimitiveLongKeyMap(int initialCapacity, float loadFactor) { return new LongObjectHashMap(initialCapacity, loadFactor); } /** * 创建值为可更改的Integer的HashMap. 可更改的Integer在更改时不需要重新创建Integer对象,节约了内存 * * @param initialCapacity 建议为16 * @param loadFactor 建议为0.5 */ public static HashMap createMutableIntValueMap(int initialCapacity, float loadFactor) { return new HashMap(initialCapacity, loadFactor); } /** * 创建值为可更改的Long的HashMap. 可更改的Long在更改时不需要重新创建Long对象,节约了内存 * * @param initialCapacity 建议为16 * @param loadFactor 建议为0.5 */ public static HashMap createMutableLongValueMap(int initialCapacity, float loadFactor) { return new HashMap(initialCapacity, loadFactor); } /** * 以Guava的AtomicLongMap,实现线程安全的HashMap结构的Counter */ public static AtomicLongMap createConcurrentCounterMap() { return AtomicLongMap.create(); } /** * 以Guava的MultiMap,实现的HashMap>结构的一个Key对应多个值的map. * * 注意非线程安全, MultiMap无线程安全的实现. * * 另有其他结构存储values的MultiMap,请自行参考MultimapBuilder使用. * * @param expectedKeys 默认为16 * @param expectedValuesPerKey 默认为3 */ public static ArrayListMultimap createListMultiValueMap(int expectedKeys, int expectedValuesPerKey) { return ArrayListMultimap.create(expectedKeys, expectedValuesPerKey); } /** * 以Guava的MultiMap,实现的HashMap>结构的一个Key对应多个值的map. * * 注意非线程安全, MultiValueMap无线程安全的实现. * * 另有其他结构存储values的MultiMap,请自行参考MultimapBuilder使用. */ public static SortedSetMultimap createSortedSetMultiValueMap() { return MultimapBuilder.hashKeys().treeSetValues().build(); } /** * 以Guava的MultiMap,实现的HashMap>结构的一个Key对应多个值的map. * * 注意非线程安全, MultiValueMap无线程安全的实现. * * 另有其他结构存储values的MultiMap,请自行参考MultimapBuilder使用. */ public static SortedSetMultimap createSortedSetMultiValueMap(Comparator comparator) { return (SortedSetMultimap) MultimapBuilder.hashKeys().treeSetValues(comparator); } /** * 以Guava TreeRangeMap实现的, 一段范围的Key指向同一个Value的Map */ @SuppressWarnings("rawtypes") public static TreeRangeMap createRangeMap() { return TreeRangeMap.create(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/MoreQueues.java ================================================ package com.vip.vjtools.vjkit.collection.type; import java.util.ArrayDeque; import java.util.Collections; import java.util.Queue; import com.google.common.collect.EvictingQueue; import com.vip.vjtools.vjkit.collection.QueueUtil; /** * 特殊类型Queue:LIFO的Stack, LRU的Queue */ public class MoreQueues { //////////////// 特殊类型Queue:Stack /////////// /** * 支持后进先出的栈,用ArrayDeque实现, 经过Collections#asLifoQueue转换顺序 * * 需设置初始长度,默认为16,数组满时成倍扩容 * * @see Collections#asLifoQueue */ public static Queue createStack(int initSize) { return Collections.asLifoQueue(new ArrayDeque(initSize)); } /** * 支持后进先出的无阻塞的并发栈,用ConcurrentLinkedDeque实现,经过Collections#asLifoQueue转换顺序 * * 另对于BlockingQueue接口, JDK暂无Lifo倒转实现,因此只能直接使用未调转顺序的LinkedBlockingDeque * * @see Collections#asLifoQueue */ public static Queue createConcurrentStack() { return (Queue) Collections.asLifoQueue(QueueUtil.newConcurrentNonBlockingDeque()); } //////////////// 特殊类型Queue:LRUQueue /////////// /** * LRUQueue, 如果Queue已满,则删除最旧的元素. * * 内部实现是ArrayDeque */ public static EvictingQueue createLRUQueue(int maxSize) { return EvictingQueue.create(maxSize); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/SortedArrayList.java ================================================ // Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package com.vip.vjtools.vjkit.collection.type; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; /** * 从Jodd整体复制,部分指定了index的操作不支持,如 add(index, element) * * 修改包括:改进Comparator泛型定义,findInsertionPoint的位移改进 * * https://github.com/oblac/jodd/blob/master/jodd-core/src/main/java/jodd/util/collection/SortedArrayList.java * * An extension of ArrayList that insures that all of the items * added are sorted. This breaks original list contract!. * A binary search method is used to provide a quick way to * auto sort this list.Note: Not all methods for adding and * removing elements are supported. */ public final class SortedArrayList extends ArrayList { private static final long serialVersionUID = -8301136559614447593L; protected final Comparator comparator; /** * Constructs a new SortedArrayList. */ public SortedArrayList(Comparator c) { comparator = c; } /** * Constructs a new SortedArrayList expecting * elements are comparable. */ public SortedArrayList() { comparator = null; } /** * Constructs a new SortedArrayList expecting * elements are comparable. */ public SortedArrayList(Collection c) { comparator = null; addAll(c); } /** * Returns comparator assigned to this collection, if such exist. */ public Comparator getComparator() { return comparator; } // ---------------------------------------------------------------- override /** * Adds an Object to sorted list. Object is inserted at correct place, found * using binary search. If the same item exist, it will be put to the end of * the range. *

* This method breaks original list contract since objects are not * added at the list end, but in sorted manner. */ @Override public boolean add(E o) { int idx = 0; if (!isEmpty()) { idx = findInsertionPoint(o); } super.add(idx, o); return true; } /** * Add all of the elements in the given collection to this list. */ @Override public boolean addAll(Collection c) { Iterator i = c.iterator(); boolean changed = false; while (i.hasNext()) { boolean ret = add(i.next()); if (!changed) { changed = ret; } } return changed; } /** * Finds the index at which object should be inserted. */ public int findInsertionPoint(E o) { return findInsertionPoint(o, 0, size() - 1); } // ---------------------------------------------------------------- unsupported methods /** * @throws UnsupportedOperationException This method not supported. */ @Override @Deprecated public void add(int index, E element) { throw new UnsupportedOperationException(); } /** * @throws UnsupportedOperationException This method not supported. */ @Override @Deprecated public E set(int index, E element) { throw new UnsupportedOperationException(); } /** * @throws UnsupportedOperationException This method not supported. */ @Override @Deprecated public boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } // ---------------------------------------------------------------- sorting /** * Compares two keys using the correct comparison method for this * collection. */ @SuppressWarnings({ "unchecked" }) protected int compare(E k1, E k2) { if (comparator == null) { return ((Comparable) k1).compareTo(k2); } return comparator.compare(k1, k2); } /** * Conducts a binary search to find the index where Object * should be inserted. */ protected int findInsertionPoint(E o, int originalLow, int originalHigh) { int low = originalLow; int high = originalHigh; while (low <= high) { int mid = low + ((high - low) >>> 1); int delta = compare(get(mid), o); if (delta > 0) { high = mid - 1; } else { low = mid + 1; } } return low; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/primitive/IntObjectHashMap.java ================================================ /* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.vip.vjtools.vjkit.collection.type.primitive; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * 移植Netty 4.1.9的Key为原子类型的集合类, 在数据结构上与HashMap不一样,空间占用与读写性能俱比原来更优. * * 原子类型集合类有多个实现,选择Netty是因为有在实战中使用. * * https://github.com/netty/netty/blob/4.1/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template * * A hash map implementation of {@link IntObjectMap} that uses open addressing for keys. To minimize the memory * footprint, this class uses open addressing rather than chaining. Collisions are resolved using linear probing. * Deletions implement compaction, so cost of remove can approach O(N) for full maps, which makes a small loadFactor * recommended. * * @param The value type stored in the map. */ public class IntObjectHashMap implements IntObjectMap { /** Default initial capacity. Used if not specified in the constructor */ public static final int DEFAULT_CAPACITY = 8; /** Default load factor. Used if not specified in the constructor */ public static final float DEFAULT_LOAD_FACTOR = 0.5f; /** * Placeholder for null values, so we can use the actual null to mean available. (Better than using a placeholder * for available: less references for GC processing.) */ private static final Object NULL_VALUE = new Object(); /** The maximum number of elements allowed without allocating more space. */ private int maxSize; /** The load factor for the map. Used to calculate {@link #maxSize}. */ private final float loadFactor; private int[] keys; private V[] values; private int size; private int mask; private final Set keySet = new KeySet(); private final Set> entrySet = new EntrySet(); private final Iterable> entries = new Iterable>() { @Override public Iterator> iterator() { return new PrimitiveIterator(); } }; public IntObjectHashMap() { this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); } public IntObjectHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public IntObjectHashMap(int initialCapacity, float loadFactor) { if (loadFactor <= 0.0f || loadFactor > 1.0f) { // Cannot exceed 1 because we can never store more than capacity elements; // using a bigger loadFactor would trigger rehashing before the desired load is reached. throw new IllegalArgumentException("loadFactor must be > 0 and <= 1"); } this.loadFactor = loadFactor; // Adjust the initial capacity if necessary. int capacity = safeFindNextPositivePowerOfTwo(initialCapacity); mask = capacity - 1; // Allocate the arrays. keys = new int[capacity]; @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" }) V[] temp = (V[]) new Object[capacity]; values = temp; // Initialize the maximum size value. maxSize = calcMaxSize(capacity); } private static T toExternal(T value) { assert value != null : "null is not a legitimate internal value. Concurrent Modification?"; return value == NULL_VALUE ? null : value; } @SuppressWarnings("unchecked") private static T toInternal(T value) { return value == null ? (T) NULL_VALUE : value; } @Override public V get(int key) { int index = indexOf(key); return index == -1 ? null : toExternal(values[index]); } @Override public V put(int key, V value) { int startIndex = hashIndex(key); int index = startIndex; for (;;) { if (values[index] == null) { // Found empty slot, use it. keys[index] = key; values[index] = toInternal(value); growSize(); return null; } if (keys[index] == key) { // Found existing entry with this key, just replace the value. V previousValue = values[index]; values[index] = toInternal(value); return toExternal(previousValue); } // Conflict, keep probing ... if ((index = probeNext(index)) == startIndex) { // Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow. throw new IllegalStateException("Unable to insert"); } } } @Override public void putAll(Map sourceMap) { if (sourceMap instanceof IntObjectHashMap) { // Optimization - iterate through the arrays. @SuppressWarnings("unchecked") IntObjectHashMap source = (IntObjectHashMap) sourceMap; for (int i = 0; i < source.values.length; ++i) { V sourceValue = source.values[i]; if (sourceValue != null) { put(source.keys[i], sourceValue); } } return; } // Otherwise, just add each entry. for (Entry entry : sourceMap.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public V remove(int key) { int index = indexOf(key); if (index == -1) { return null; } V prev = values[index]; removeAt(index); return toExternal(prev); } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public void clear() { Arrays.fill(keys, 0); Arrays.fill(values, null); size = 0; } @Override public boolean containsKey(int key) { return indexOf(key) >= 0; } @Override public boolean containsValue(Object value) { @SuppressWarnings("unchecked") V v1 = toInternal((V) value); for (V v2 : values) { // The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE). if (v2 != null && v2.equals(v1)) { return true; } } return false; } @Override public Iterable> entries() { return entries; } @Override public Collection values() { return new AbstractCollection() { @Override public Iterator iterator() { return new Iterator() { final PrimitiveIterator iter = new PrimitiveIterator(); @Override public boolean hasNext() { return iter.hasNext(); } @Override public V next() { return iter.next().value(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { return size; } }; } @Override public int hashCode() { // Hashcode is based on all non-zero, valid keys. We have to scan the whole keys // array, which may have different lengths for two maps of same size(), so the // capacity cannot be used as input for hashing but the size can. int hash = size; for (int key : keys) { // 0 can be a valid key or unused slot, but won't impact the hashcode in either case. // This way we can use a cheap loop without conditionals, or hard-to-unroll operations, // or the devastatingly bad memory locality of visiting value objects. // Also, it's important to use a hash function that does not depend on the ordering // of terms, only their values; since the map is an unordered collection and // entries can end up in different positions in different maps that have the same // elements, but with different history of puts/removes, due to conflicts. hash ^= hashCode(key); } return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof IntObjectMap)) { return false; } @SuppressWarnings("rawtypes") IntObjectMap other = (IntObjectMap) obj; if (size != other.size()) { return false; } for (int i = 0; i < values.length; ++i) { V value = values[i]; if (value != null) { int key = keys[i]; Object otherValue = other.get(key); if (value == NULL_VALUE) { if (otherValue != null) { return false; } } else if (!value.equals(otherValue)) { return false; } } } return true; } @Override public boolean containsKey(Object key) { return containsKey(objectToKey(key)); } @Override public V get(Object key) { return get(objectToKey(key)); } @Override public V put(Integer key, V value) { return put(objectToKey(key), value); } @Override public V remove(Object key) { return remove(objectToKey(key)); } @Override public Set keySet() { return keySet; } @Override public Set> entrySet() { return entrySet; } private int objectToKey(Object key) { return ((Integer) key).intValue(); } /** * Locates the index for the given key. This method probes using double hashing. * * @param key the key for an entry in the map. * @return the index where the key was found, or {@code -1} if no entry is found for that key. */ private int indexOf(int key) { int startIndex = hashIndex(key); int index = startIndex; for (;;) { if (values[index] == null) { // It's available, so no chance that this value exists anywhere in the map. return -1; } if (key == keys[index]) { return index; } // Conflict, keep probing ... if ((index = probeNext(index)) == startIndex) { return -1; } } } /** * Returns the hashed index for the given key. */ private int hashIndex(int key) { // The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds. return hashCode(key) & mask; } /** * Returns the hash code for the key. */ private static int hashCode(int key) { return key; } /** * Get the next sequential index after {@code index} and wraps if necessary. */ private int probeNext(int index) { // The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds. return (index + 1) & mask; } /** * Grows the map size after an insertion. If necessary, performs a rehash of the map. */ private void growSize() { size++; if (size > maxSize) { if (keys.length == Integer.MAX_VALUE) { throw new IllegalStateException("Max capacity reached at size=" + size); } // Double the capacity. rehash(keys.length << 1); } } /** * Removes entry at the given index position. Also performs opportunistic, incremental rehashing if necessary to not * break conflict chains. * * @param index the index position of the element to remove. * @return {@code true} if the next item was moved back. {@code false} otherwise. */ private boolean removeAt(final int index) { --size; // Clearing the key is not strictly necessary (for GC like in a regular collection), // but recommended for security. The memory location is still fresh in the cache anyway. keys[index] = 0; values[index] = null; // In the interval from index to the next available entry, the arrays may have entries // that are displaced from their base position due to prior conflicts. Iterate these // entries and move them back if possible, optimizing future lookups. // Knuth Section 6.4 Algorithm R, also used by the JDK's IdentityHashMap. int nextFree = index; int i = probeNext(index); for (V value = values[i]; value != null; value = values[i = probeNext(i)]) { int key = keys[i]; int bucket = hashIndex(key); if (i < bucket && (bucket <= nextFree || nextFree <= i) || bucket <= nextFree && nextFree <= i) { // Move the displaced entry "back" to the first available position. keys[nextFree] = key; values[nextFree] = value; // Put the first entry after the displaced entry keys[i] = 0; values[i] = null; nextFree = i; } } return nextFree != index; } /** * Calculates the maximum size allowed before rehashing. */ private int calcMaxSize(int capacity) { // Clip the upper bound so that there will always be at least one available slot. int upperBound = capacity - 1; return Math.min(upperBound, (int) (capacity * loadFactor)); } /** * Rehashes the map for the given capacity. * * @param newCapacity the new capacity for the map. */ private void rehash(int newCapacity) { int[] oldKeys = keys; V[] oldVals = values; keys = new int[newCapacity]; @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" }) V[] temp = (V[]) new Object[newCapacity]; values = temp; maxSize = calcMaxSize(newCapacity); mask = newCapacity - 1; // Insert to the new arrays. for (int i = 0; i < oldVals.length; ++i) { V oldVal = oldVals[i]; if (oldVal != null) { // Inlined put(), but much simpler: we don't need to worry about // duplicated keys, growing/rehashing, or failing to insert. int oldKey = oldKeys[i]; int index = hashIndex(oldKey); for (;;) { if (values[index] == null) { keys[index] = oldKey; values[index] = oldVal; break; } // Conflict, keep probing. Can wrap around, but never reaches startIndex again. index = probeNext(index); } } } } @Override public String toString() { if (isEmpty()) { return "{}"; } StringBuilder sb = new StringBuilder(4 * size); sb.append('{'); boolean first = true; for (int i = 0; i < values.length; ++i) { V value = values[i]; if (value != null) { if (!first) { sb.append(", "); } sb.append(keyToString(keys[i])).append('=').append(value == this ? "(this Map)" : toExternal(value)); first = false; } } return sb.append('}').toString(); } /** * Helper method called by {@link #toString()} in order to convert a single map key into a string. This is protected * to allow subclasses to override the appearance of a given key. */ protected String keyToString(int key) { return Integer.toString(key); } /** * Set implementation for iterating over the entries of the map. */ private final class EntrySet extends AbstractSet> { @Override public Iterator> iterator() { return new MapIterator(); } @Override public int size() { return IntObjectHashMap.this.size(); } } /** * Set implementation for iterating over the keys. */ private final class KeySet extends AbstractSet { @Override public int size() { return IntObjectHashMap.this.size(); } @Override public boolean contains(Object o) { return IntObjectHashMap.this.containsKey(o); } @Override public boolean remove(Object o) { return IntObjectHashMap.this.remove(o) != null; } @Override public boolean retainAll(Collection retainedKeys) { boolean changed = false; for (Iterator> iter = entries().iterator(); iter.hasNext();) { PrimitiveEntry entry = iter.next(); if (!retainedKeys.contains(entry.key())) { changed = true; iter.remove(); } } return changed; } @Override public void clear() { IntObjectHashMap.this.clear(); } @Override public Iterator iterator() { return new Iterator() { private final Iterator> iter = entrySet.iterator(); @Override public boolean hasNext() { return iter.hasNext(); } @Override public Integer next() { return iter.next().getKey(); } @Override public void remove() { iter.remove(); } }; } } /** * Iterator over primitive entries. Entry key/values are overwritten by each call to {@link #next()}. */ private final class PrimitiveIterator implements Iterator>, PrimitiveEntry { private int prevIndex = -1; private int nextIndex = -1; private int entryIndex = -1; private void scanNext() { while (++nextIndex != values.length && values[nextIndex] == null) { } } @Override public boolean hasNext() { if (nextIndex == -1) { scanNext(); } return nextIndex != values.length; } @Override public PrimitiveEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } prevIndex = nextIndex; scanNext(); // Always return the same Entry object, just change its index each time. entryIndex = prevIndex; return this; } @Override public void remove() { if (prevIndex == -1) { throw new IllegalStateException("next must be called before each remove."); } if (removeAt(prevIndex)) { // removeAt may move elements "back" in the array if they have been displaced because their spot in the // array was occupied when they were inserted. If this occurs then the nextIndex is now invalid and // should instead point to the prevIndex which now holds an element which was "moved back". nextIndex = prevIndex; } prevIndex = -1; } // Entry implementation. Since this implementation uses a single Entry, we coalesce that // into the Iterator object (potentially making loop optimization much easier). @Override public int key() { return keys[entryIndex]; } @Override public V value() { return toExternal(values[entryIndex]); } @Override public void setValue(V value) { values[entryIndex] = toInternal(value); } } /** * Iterator used by the {@link Map} interface. */ private final class MapIterator implements Iterator> { private final PrimitiveIterator iter = new PrimitiveIterator(); @Override public boolean hasNext() { return iter.hasNext(); } @Override public Entry next() { if (!hasNext()) { throw new NoSuchElementException(); } iter.next(); return new MapEntry(iter.entryIndex); } @Override public void remove() { iter.remove(); } } /** * A single entry in the map. */ final class MapEntry implements Entry { private final int entryIndex; MapEntry(int entryIndex) { this.entryIndex = entryIndex; } @Override public Integer getKey() { verifyExists(); return keys[entryIndex]; } @Override public V getValue() { verifyExists(); return toExternal(values[entryIndex]); } @Override public V setValue(V value) { verifyExists(); V prevValue = toExternal(values[entryIndex]); values[entryIndex] = toInternal(value); return prevValue; } private void verifyExists() { if (values[entryIndex] == null) { throw new IllegalStateException("The map entry has been removed"); } } } public static int safeFindNextPositivePowerOfTwo(final int value) { return value <= 0 ? 1 : value >= 0x40000000 ? 0x40000000 : findNextPositivePowerOfTwo(value); } public static int findNextPositivePowerOfTwo(final int value) { assert value > Integer.MIN_VALUE && value < 0x40000000; return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/primitive/IntObjectMap.java ================================================ /* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.vip.vjtools.vjkit.collection.type.primitive; import java.util.Map; /** * Interface for a primitive map that uses {@code int}s as keys. * * @param the value type stored in the map. */ public interface IntObjectMap extends Map { /** * A primitive entry in the map, provided by the iterator from {@link #entries()} * * @param the value type stored in the map. */ interface PrimitiveEntry { /** * Gets the key for this entry. */ int key(); /** * Gets the value for this entry. */ V value(); /** * Sets the value for this entry. */ void setValue(V value); } /** * Gets the value in the map with the specified key. * * @param key the key whose associated value is to be returned. * @return the value or {@code null} if the key was not found in the map. */ V get(int key); /** * Puts the given entry into the map. * * @param key the key of the entry. * @param value the value of the entry. * @return the previous value for this key or {@code null} if there was no previous mapping. */ V put(int key, V value); /** * Removes the entry with the specified key. * * @param key the key for the entry to be removed from this map. * @return the previous value for the key, or {@code null} if there was no mapping. */ V remove(int key); /** * Gets an iterable to traverse over the primitive entries contained in this map. As an optimization, the * {@link PrimitiveEntry}s returned by the {@link Iterator} may change as the {@link Iterator} progresses. The * caller should not rely on {@link PrimitiveEntry} key/value stability. */ Iterable> entries(); /** * Indicates whether or not this map contains a value for the specified key. */ boolean containsKey(int key); } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/primitive/LongObjectHashMap.java ================================================ /* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.vip.vjtools.vjkit.collection.type.primitive; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * 移植Netty 4.1.6的Key为原子类型的集合类, 在数据结构上与HashMap不一样,空间占用与读写性能俱比原来更优. * * 原子类型集合类有多个实现,选择Netty是因为有在实战中使用. * * A hash map implementation of {@link LongObjectMap} that uses open addressing for keys. To minimize the memory * footprint, this class uses open addressing rather than chaining. Collisions are resolved using linear probing. * Deletions implement compaction, so cost of remove can approach O(N) for full maps, which makes a small loadFactor * recommended. * * @param The value type stored in the map. */ public class LongObjectHashMap implements LongObjectMap { /** Default initial capacity. Used if not specified in the constructor */ public static final int DEFAULT_CAPACITY = 8; /** Default load factor. Used if not specified in the constructor */ public static final float DEFAULT_LOAD_FACTOR = 0.5f; /** * Placeholder for null values, so we can use the actual null to mean available. (Better than using a placeholder * for available: less references for GC processing.) */ private static final Object NULL_VALUE = new Object(); /** The maximum number of elements allowed without allocating more space. */ private int maxSize; /** The load factor for the map. Used to calculate {@link #maxSize}. */ private final float loadFactor; private long[] keys; private V[] values; private int size; private int mask; private final Set keySet = new KeySet(); private final Set> entrySet = new EntrySet(); private final Iterable> entries = new Iterable>() { @Override public Iterator> iterator() { return new PrimitiveIterator(); } }; public LongObjectHashMap() { this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); } public LongObjectHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public LongObjectHashMap(int initialCapacity, float loadFactor) { if (loadFactor <= 0.0f || loadFactor > 1.0f) { // Cannot exceed 1 because we can never store more than capacity elements; // using a bigger loadFactor would trigger rehashing before the desired load is reached. throw new IllegalArgumentException("loadFactor must be > 0 and <= 1"); } this.loadFactor = loadFactor; // Adjust the initial capacity if necessary. int capacity = safeFindNextPositivePowerOfTwo(initialCapacity); mask = capacity - 1; // Allocate the arrays. keys = new long[capacity]; @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" }) V[] temp = (V[]) new Object[capacity]; values = temp; // Initialize the maximum size value. maxSize = calcMaxSize(capacity); } private static T toExternal(T value) { assert value != null : "null is not a legitimate internal value. Concurrent Modification?"; return value == NULL_VALUE ? null : value; } @SuppressWarnings("unchecked") private static T toInternal(T value) { return value == null ? (T) NULL_VALUE : value; } @Override public V get(long key) { int index = indexOf(key); return index == -1 ? null : toExternal(values[index]); } @Override public V put(long key, V value) { int startIndex = hashIndex(key); int index = startIndex; for (;;) { if (values[index] == null) { // Found empty slot, use it. keys[index] = key; values[index] = toInternal(value); growSize(); return null; } if (keys[index] == key) { // Found existing entry with this key, just replace the value. V previousValue = values[index]; values[index] = toInternal(value); return toExternal(previousValue); } // Conflict, keep probing ... if ((index = probeNext(index)) == startIndex) { // Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow. throw new IllegalStateException("Unable to insert"); } } } @Override public void putAll(Map sourceMap) { if (sourceMap instanceof LongObjectHashMap) { // Optimization - iterate through the arrays. @SuppressWarnings("unchecked") LongObjectHashMap source = (LongObjectHashMap) sourceMap; for (int i = 0; i < source.values.length; ++i) { V sourceValue = source.values[i]; if (sourceValue != null) { put(source.keys[i], sourceValue); } } return; } // Otherwise, just add each entry. for (Entry entry : sourceMap.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public V remove(long key) { int index = indexOf(key); if (index == -1) { return null; } V prev = values[index]; removeAt(index); return toExternal(prev); } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public void clear() { Arrays.fill(keys, 0); Arrays.fill(values, null); size = 0; } @Override public boolean containsKey(long key) { return indexOf(key) >= 0; } @Override public boolean containsValue(Object value) { @SuppressWarnings("unchecked") V v1 = toInternal((V) value); for (V v2 : values) { // The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE). if (v2 != null && v2.equals(v1)) { return true; } } return false; } @Override public Iterable> entries() { return entries; } @Override public Collection values() { return new AbstractCollection() { @Override public Iterator iterator() { return new Iterator() { final PrimitiveIterator iter = new PrimitiveIterator(); @Override public boolean hasNext() { return iter.hasNext(); } @Override public V next() { return iter.next().value(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { return size; } }; } @Override public int hashCode() { // Hashcode is based on all non-zero, valid keys. We have to scan the whole keys // array, which may have different lengths for two maps of same size(), so the // capacity cannot be used as input for hashing but the size can. int hash = size; for (long key : keys) { // 0 can be a valid key or unused slot, but won't impact the hashcode in either case. // This way we can use a cheap loop without conditionals, or hard-to-unroll operations, // or the devastatingly bad memory locality of visiting value objects. // Also, it's important to use a hash function that does not depend on the ordering // of terms, only their values; since the map is an unordered collection and // entries can end up in different positions in different maps that have the same // elements, but with different history of puts/removes, due to conflicts. hash ^= hashCode(key); } return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof LongObjectMap)) { return false; } @SuppressWarnings("rawtypes") LongObjectMap other = (LongObjectMap) obj; if (size != other.size()) { return false; } for (int i = 0; i < values.length; ++i) { V value = values[i]; if (value != null) { long key = keys[i]; Object otherValue = other.get(key); if (value == NULL_VALUE) { if (otherValue != null) { return false; } } else if (!value.equals(otherValue)) { return false; } } } return true; } @Override public boolean containsKey(Object key) { return containsKey(objectToKey(key)); } @Override public V get(Object key) { return get(objectToKey(key)); } @Override public V put(Long key, V value) { return put(objectToKey(key), value); } @Override public V remove(Object key) { return remove(objectToKey(key)); } @Override public Set keySet() { return keySet; } @Override public Set> entrySet() { return entrySet; } private long objectToKey(Object key) { return ((Long) key).longValue(); } /** * Locates the index for the given key. This method probes using double hashing. * * @param key the key for an entry in the map. * @return the index where the key was found, or {@code -1} if no entry is found for that key. */ private int indexOf(long key) { int startIndex = hashIndex(key); int index = startIndex; for (;;) { if (values[index] == null) { // It's available, so no chance that this value exists anywhere in the map. return -1; } if (key == keys[index]) { return index; } // Conflict, keep probing ... if ((index = probeNext(index)) == startIndex) { return -1; } } } /** * Returns the hashed index for the given key. */ private int hashIndex(long key) { // The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds. return hashCode(key) & mask; } /** * Returns the hash code for the key. */ private static int hashCode(long key) { return (int) (key ^ (key >>> 32)); } /** * Get the next sequential index after {@code index} and wraps if necessary. */ private int probeNext(int index) { // The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds. return (index + 1) & mask; } /** * Grows the map size after an insertion. If necessary, performs a rehash of the map. */ private void growSize() { size++; if (size > maxSize) { if (keys.length == Integer.MAX_VALUE) { throw new IllegalStateException("Max capacity reached at size=" + size); } // Double the capacity. rehash(keys.length << 1); } } /** * Removes entry at the given index position. Also performs opportunistic, incremental rehashing if necessary to not * break conflict chains. * * @param index the index position of the element to remove. * @return {@code true} if the next item was moved back. {@code false} otherwise. */ private boolean removeAt(final int index) { --size; // Clearing the key is not strictly necessary (for GC like in a regular collection), // but recommended for security. The memory location is still fresh in the cache anyway. keys[index] = 0; values[index] = null; // In the interval from index to the next available entry, the arrays may have entries // that are displaced from their base position due to prior conflicts. Iterate these // entries and move them back if possible, optimizing future lookups. // Knuth Section 6.4 Algorithm R, also used by the JDK's IdentityHashMap. int nextFree = index; int i = probeNext(index); for (V value = values[i]; value != null; value = values[i = probeNext(i)]) { long key = keys[i]; int bucket = hashIndex(key); if (i < bucket && (bucket <= nextFree || nextFree <= i) || bucket <= nextFree && nextFree <= i) { // Move the displaced entry "back" to the first available position. keys[nextFree] = key; values[nextFree] = value; // Put the first entry after the displaced entry keys[i] = 0; values[i] = null; nextFree = i; } } return nextFree != index; } /** * Calculates the maximum size allowed before rehashing. */ private int calcMaxSize(int capacity) { // Clip the upper bound so that there will always be at least one available slot. int upperBound = capacity - 1; return Math.min(upperBound, (int) (capacity * loadFactor)); } /** * Rehashes the map for the given capacity. * * @param newCapacity the new capacity for the map. */ private void rehash(int newCapacity) { long[] oldKeys = keys; V[] oldVals = values; keys = new long[newCapacity]; @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" }) V[] temp = (V[]) new Object[newCapacity]; values = temp; maxSize = calcMaxSize(newCapacity); mask = newCapacity - 1; // Insert to the new arrays. for (int i = 0; i < oldVals.length; ++i) { V oldVal = oldVals[i]; if (oldVal != null) { // Inlined put(), but much simpler: we don't need to worry about // duplicated keys, growing/rehashing, or failing to insert. long oldKey = oldKeys[i]; int index = hashIndex(oldKey); for (;;) { if (values[index] == null) { keys[index] = oldKey; values[index] = oldVal; break; } // Conflict, keep probing. Can wrap around, but never reaches startIndex again. index = probeNext(index); } } } } @Override public String toString() { if (isEmpty()) { return "{}"; } StringBuilder sb = new StringBuilder(4 * size); sb.append('{'); boolean first = true; for (int i = 0; i < values.length; ++i) { V value = values[i]; if (value != null) { if (!first) { sb.append(", "); } sb.append(keyToString(keys[i])).append('=').append(value == this ? "(this Map)" : toExternal(value)); first = false; } } return sb.append('}').toString(); } /** * Helper method called by {@link #toString()} in order to convert a single map key into a string. This is protected * to allow subclasses to override the appearance of a given key. */ protected String keyToString(long key) { return Long.toString(key); } /** * Set implementation for iterating over the entries of the map. */ private final class EntrySet extends AbstractSet> { @Override public Iterator> iterator() { return new MapIterator(); } @Override public int size() { return LongObjectHashMap.this.size(); } } /** * Set implementation for iterating over the keys. */ private final class KeySet extends AbstractSet { @Override public int size() { return LongObjectHashMap.this.size(); } @Override public boolean contains(Object o) { return LongObjectHashMap.this.containsKey(o); } @Override public boolean remove(Object o) { return LongObjectHashMap.this.remove(o) != null; } @Override public boolean retainAll(Collection retainedKeys) { boolean changed = false; for (Iterator> iter = entries().iterator(); iter.hasNext();) { PrimitiveEntry entry = iter.next(); if (!retainedKeys.contains(entry.key())) { changed = true; iter.remove(); } } return changed; } @Override public void clear() { LongObjectHashMap.this.clear(); } @Override public Iterator iterator() { return new Iterator() { private final Iterator> iter = entrySet.iterator(); @Override public boolean hasNext() { return iter.hasNext(); } @Override public Long next() { return iter.next().getKey(); } @Override public void remove() { iter.remove(); } }; } } /** * Iterator over primitive entries. Entry key/values are overwritten by each call to {@link #next()}. */ private final class PrimitiveIterator implements Iterator>, PrimitiveEntry { private int prevIndex = -1; private int nextIndex = -1; private int entryIndex = -1; private void scanNext() { while (++nextIndex != values.length && values[nextIndex] == null) { } } @Override public boolean hasNext() { if (nextIndex == -1) { scanNext(); } return nextIndex != values.length; } @Override public PrimitiveEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } prevIndex = nextIndex; scanNext(); // Always return the same Entry object, just change its index each time. entryIndex = prevIndex; return this; } @Override public void remove() { if (prevIndex == -1) { throw new IllegalStateException("next must be called before each remove."); } if (removeAt(prevIndex)) { // removeAt may move elements "back" in the array if they have been displaced because their spot in the // array was occupied when they were inserted. If this occurs then the nextIndex is now invalid and // should instead point to the prevIndex which now holds an element which was "moved back". nextIndex = prevIndex; } prevIndex = -1; } // Entry implementation. Since this implementation uses a single Entry, we coalesce that // into the Iterator object (potentially making loop optimization much easier). @Override public long key() { return keys[entryIndex]; } @Override public V value() { return toExternal(values[entryIndex]); } @Override public void setValue(V value) { values[entryIndex] = toInternal(value); } } /** * Iterator used by the {@link Map} interface. */ private final class MapIterator implements Iterator> { private final PrimitiveIterator iter = new PrimitiveIterator(); @Override public boolean hasNext() { return iter.hasNext(); } @Override public Entry next() { if (!hasNext()) { throw new NoSuchElementException(); } iter.next(); return new MapEntry(iter.entryIndex); } @Override public void remove() { iter.remove(); } } /** * A single entry in the map. */ final class MapEntry implements Entry { private final int entryIndex; MapEntry(int entryIndex) { this.entryIndex = entryIndex; } @Override public Long getKey() { verifyExists(); return keys[entryIndex]; } @Override public V getValue() { verifyExists(); return toExternal(values[entryIndex]); } @Override public V setValue(V value) { verifyExists(); V prevValue = toExternal(values[entryIndex]); values[entryIndex] = toInternal(value); return prevValue; } private void verifyExists() { if (values[entryIndex] == null) { throw new IllegalStateException("The map entry has been removed"); } } } public static int safeFindNextPositivePowerOfTwo(final int value) { return value <= 0 ? 1 : value >= 0x40000000 ? 0x40000000 : findNextPositivePowerOfTwo(value); } public static int findNextPositivePowerOfTwo(final int value) { assert value > Integer.MIN_VALUE && value < 0x40000000; return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/primitive/LongObjectMap.java ================================================ /* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.vip.vjtools.vjkit.collection.type.primitive; import java.util.Map; /** * Interface for a primitive map that uses {@code long}s as keys. * * @param the value type stored in the map. */ public interface LongObjectMap extends Map { /** * A primitive entry in the map, provided by the iterator from {@link #entries()} * * @param the value type stored in the map. */ interface PrimitiveEntry { /** * Gets the key for this entry. */ long key(); /** * Gets the value for this entry. */ V value(); /** * Sets the value for this entry. */ void setValue(V value); } /** * Gets the value in the map with the specified key. * * @param key the key whose associated value is to be returned. * @return the value or {@code null} if the key was not found in the map. */ V get(long key); /** * Puts the given entry into the map. * * @param key the key of the entry. * @param value the value of the entry. * @return the previous value for this key or {@code null} if there was no previous mapping. */ V put(long key, V value); /** * Removes the entry with the specified key. * * @param key the key for the entry to be removed from this map. * @return the previous value for the key, or {@code null} if there was no mapping. */ V remove(long key); /** * Gets an iterable to traverse over the primitive entries contained in this map. As an optimization, the * {@link PrimitiveEntry}s returned by the {@link Iterator} may change as the {@link Iterator} progresses. The * caller should not rely on {@link PrimitiveEntry} key/value stability. */ Iterable> entries(); /** * Indicates whether or not this map contains a value for the specified key. */ boolean containsKey(long key); } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/Concurrents.java ================================================ package com.vip.vjtools.vjkit.concurrent; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import com.google.common.util.concurrent.RateLimiter; import com.vip.vjtools.vjkit.concurrent.jsr166e.LongAdder; import com.vip.vjtools.vjkit.concurrent.limiter.RateLimiterUtil; import com.vip.vjtools.vjkit.concurrent.limiter.Sampler; import com.vip.vjtools.vjkit.concurrent.limiter.TimeIntervalLimiter; /** * 并发常用工具类 */ public class Concurrents { /** * 返回没有激烈CAS冲突的LongAdder, 并发的+1将在不同的Counter里进行,只在取值时将多个Counter求和. * * 为了保持JDK版本兼容性,统一采用移植版 */ public static LongAdder longAdder() { return new LongAdder(); } /** * 返回CountDownLatch, 每条线程减1,减到0时正在latch.wait()的进程继续进行 */ public static CountDownLatch countDownLatch(int count) { return new CountDownLatch(count); } /** * 返回CyclicBarrier,每条线程减1并等待,减到0时,所有线程继续运行 */ public static CyclicBarrier cyclicBarrier(int count) { return new CyclicBarrier(count); } /** * 返回默认的非公平信号量,先请求的线程不一定先拿到信号量 */ public static Semaphore nonFairSemaphore(int permits) { return new Semaphore(permits); } /** * 返回公平的信号量,先请求的线程先拿到信号量 */ public static Semaphore fairSemaphore(int permits) { return new Semaphore(permits, true); } /////////// 限流采样 ////// /** * 返回令牌桶算法的RateLimiter默认版,默认令牌桶大小等于期望的QPS,且刚启动时桶为空。 * * @param permitsPerSecond 每秒允许的请求数,可看成QPS,同时将QPS平滑到毫秒级别上,请求到达速度不平滑时依赖缓冲能力. */ public static RateLimiter rateLimiter(int permitsPerSecond) { return RateLimiter.create(permitsPerSecond); } /** * 返回令牌桶算法的RateLimiter定制版,可定制令牌桶的大小,且刚启动时桶已装满。 * * @param permitsPerSecond 每秒允许的请求数,可看成QPS,同时将QPS平滑到毫秒级别上,请求到达速度不平滑时依赖缓冲能力. * @param maxBurstSeconds 可看成桶的容量,Guava中最大的突发流量缓冲时间,默认是1s, permitsPerSecond * maxBurstSeconds,就是闲时能累积的缓冲token最大数量。 */ public static RateLimiter rateLimiter(int permitsPerSecond, int maxBurstSeconds) throws ReflectiveOperationException { return RateLimiterUtil.create(permitsPerSecond, maxBurstSeconds); } /** * 返回采样器. * * @param selectPercent 采样率,在0-100 之间,可以有小数位 */ public static Sampler sampler(double selectPercent) { return Sampler.create(selectPercent); } /** * 返回时间间隔限制器. * @param interval 间隔时间 * @param timeUnit 间隔时间单位 */ public static TimeIntervalLimiter timeIntervalLimiter(long interval, TimeUnit timeUnit) { return new TimeIntervalLimiter(interval, timeUnit); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/ThreadDumpper.java ================================================ package com.vip.vjtools.vjkit.concurrent; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vip.vjtools.vjkit.concurrent.limiter.TimeIntervalLimiter; /** * 由程序触发的ThreadDump,打印到日志中. * * 因为ThreadDump本身会造成JVM停顿,所以加上了开关和最少间隔时间的选项(默认不限制) * * 因为ThreadInfo的toString()最多只会打印8层的StackTrace,所以加上了最大打印层数的选项.(默认为8) */ public class ThreadDumpper { private static final int DEFAULT_MAX_STACK_LEVEL = 8; private static final int DEFAULT_MIN_INTERVAL = 1000 * 60 * 10; // 10分钟 private static Logger logger = LoggerFactory.getLogger(ThreadDumpper.class); private int maxStackLevel; // 打印StackTrace的最大深度 private TimeIntervalLimiter timeIntervalLimiter; public ThreadDumpper() { this(DEFAULT_MIN_INTERVAL, DEFAULT_MAX_STACK_LEVEL); } public ThreadDumpper(long leastIntervalMills, int maxStackLevel) { this.maxStackLevel = maxStackLevel; timeIntervalLimiter = new TimeIntervalLimiter(leastIntervalMills, TimeUnit.MILLISECONDS); } /** * 符合条件则打印线程栈. */ public void tryThreadDump() { tryThreadDump(null); } /** * 符合条件则打印线程栈. * * @param reasonMsg 发生ThreadDump的原因 */ public void tryThreadDump(String reasonMsg) { if (timeIntervalLimiter.tryAcquire()) { threadDump(reasonMsg); } } /** * 强行打印ThreadDump,使用最轻量的采集方式,不打印锁信息 */ public void threadDump(String reasonMsg) { logger.info("Thread dump by ThreadDumpper" + (reasonMsg != null ? (" for " + reasonMsg) : "")); Map threads = Thread.getAllStackTraces(); // 两条日志间的时间间隔,是VM被thread dump堵塞的时间. logger.info("Finish the threads snapshot"); StringBuilder sb = new StringBuilder(8192 * 20).append('\n'); for (Entry entry : threads.entrySet()) { dumpThreadInfo(entry.getKey(), entry.getValue(), sb); } logger.info(sb.toString()); } /** * 打印全部的stack,重新实现threadInfo的toString()函数,因为默认最多只打印8层的stack. 同时,不再打印lockedMonitors和lockedSynchronizers. */ private String dumpThreadInfo(Thread thread, StackTraceElement[] stackTrace, StringBuilder sb) { sb.append('\"').append(thread.getName()).append("\" Id=").append(thread.getId()).append(' ') .append(thread.getState()); sb.append('\n'); int i = 0; for (; i < Math.min(maxStackLevel, stackTrace.length); i++) { StackTraceElement ste = stackTrace[i]; sb.append("\tat ").append(ste).append('\n'); } if (i < stackTrace.length) { sb.append("\t...").append('\n'); } sb.append('\n'); return sb.toString(); } /** * 打印ThreadDump的最小时间间隔,单位为秒,默认为0不限制. */ public void setLeastInterval(int leastIntervalSeconds) { this.timeIntervalLimiter = new TimeIntervalLimiter(leastIntervalSeconds, TimeUnit.MILLISECONDS); } /** * 打印StackTrace的最大深度, 默认为8. */ public void setMaxStackLevel(int maxStackLevel) { this.maxStackLevel = maxStackLevel; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/ThreadUtil.java ================================================ package com.vip.vjtools.vjkit.concurrent; import java.util.concurrent.TimeUnit; /** * 线程相关工具类. * * 1. 处理了InterruptedException的sleep * * 2. 正确的InterruptedException处理方法 */ public class ThreadUtil { /** * sleep等待, 单位为毫秒, 已捕捉并处理InterruptedException. */ public static void sleep(long durationMillis) { try { Thread.sleep(durationMillis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * sleep等待,已捕捉并处理InterruptedException. */ public static void sleep(long duration, TimeUnit unit) { try { Thread.sleep(unit.toMillis(duration)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * 纯粹为了提醒下处理InterruptedException的正确方式,除非你是在写不可中断的任务. */ public static void handleInterruptedException() { Thread.currentThread().interrupt(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/jsr166e/LongAdder.java ================================================ /* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ package com.vip.vjtools.vjkit.concurrent.jsr166e; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.concurrent.atomic.AtomicLong; /** * 移植 * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java Revision.1.17 * * One or more variables that together maintain an initially zero * {@code long} sum. When updates (method {@link #add}) are contended * across threads, the set of variables may grow dynamically to reduce * contention. Method {@link #sum} (or, equivalently, {@link * #longValue}) returns the current total combined across the * variables maintaining the sum. * *

This class is usually preferable to {@link AtomicLong} when * multiple threads update a common sum that is used for purposes such * as collecting statistics, not for fine-grained synchronization * control. Under low update contention, the two classes have similar * characteristics. But under high contention, expected throughput of * this class is significantly higher, at the expense of higher space * consumption. * *

This class extends {@link Number}, but does not define * methods such as {@code equals}, {@code hashCode} and {@code * compareTo} because instances are expected to be mutated, and so are * not useful as collection keys. * *

jsr166e note: This class is targeted to be placed in * java.util.concurrent.atomic. * * @since 1.8 * @author Doug Lea */ public class LongAdder extends Striped64 implements Serializable { private static final long serialVersionUID = 7249069246863182397L; /** * Version of plus for use in retryUpdate */ final long fn(long v, long x) { return v + x; } /** * Creates a new adder with initial sum of zero. */ public LongAdder() { // empty } /** * Adds the given value. * * @param x the value to add */ public void add(long x) { Cell[] as; long b, v; int[] hc; Cell a; int n; if ((as = cells) != null || !casBase(b = base, b + x)) { boolean uncontended = true; if ((hc = threadHashCode.get()) == null || as == null || (n = as.length) < 1 || (a = as[(n - 1) & hc[0]]) == null || !(uncontended = a.cas(v = a.value, v + x))) retryUpdate(x, hc, uncontended); } } /** * Equivalent to {@code add(1)}. */ public void increment() { add(1L); } /** * Equivalent to {@code add(-1)}. */ public void decrement() { add(-1L); } /** * Returns the current sum. The returned value is NOT an * atomic snapshot; invocation in the absence of concurrent * updates returns an accurate result, but concurrent updates that * occur while the sum is being calculated might not be * incorporated. * * @return the sum */ public long sum() { long sum = base; Cell[] as = cells; if (as != null) { int n = as.length; for (int i = 0; i < n; ++i) { Cell a = as[i]; if (a != null) sum += a.value; } } return sum; } /** * Resets variables maintaining the sum to zero. This method may * be a useful alternative to creating a new adder, but is only * effective if there are no concurrent updates. Because this * method is intrinsically racy, it should only be used when it is * known that no threads are concurrently updating. */ public void reset() { internalReset(0L); } /** * Equivalent in effect to {@link #sum} followed by {@link * #reset}. This method may apply for example during quiescent * points between multithreaded computations. If there are * updates concurrent with this method, the returned value is * not guaranteed to be the final value occurring before * the reset. * * @return the sum */ public long sumThenReset() { long sum = base; Cell[] as = cells; base = 0L; if (as != null) { int n = as.length; for (int i = 0; i < n; ++i) { Cell a = as[i]; if (a != null) { sum += a.value; a.value = 0L; } } } return sum; } /** * Returns the String representation of the {@link #sum}. * @return the String representation of the {@link #sum} */ public String toString() { return Long.toString(sum()); } /** * Equivalent to {@link #sum}. * * @return the sum */ public long longValue() { return sum(); } /** * Returns the {@link #sum} as an {@code int} after a narrowing * primitive conversion. */ public int intValue() { return (int) sum(); } /** * Returns the {@link #sum} as a {@code float} * after a widening primitive conversion. */ public float floatValue() { return (float) sum(); } /** * Returns the {@link #sum} as a {@code double} after a widening * primitive conversion. */ public double doubleValue() { return (double) sum(); } private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeLong(sum()); } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); busy = 0; cells = null; base = s.readLong(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/jsr166e/Striped64.java ================================================ /* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ package com.vip.vjtools.vjkit.concurrent.jsr166e; import java.util.Random; /** * 移植 * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java Revision 1.10 * * A package-local class holding common representation and mechanics * for classes supporting dynamic striping on 64bit values. The class * extends Number so that concrete subclasses must publicly do so. */ public abstract class Striped64 extends Number { /* * This class maintains a lazily-initialized table of atomically updated variables, plus an extra "base" field. The * table size is a power of two. Indexing uses masked per-thread hash codes. Nearly all declarations in this class * are package-private, accessed directly by subclasses. * * Table entries are of class Cell; a variant of AtomicLong padded to reduce cache contention on most processors. * Padding is overkill for most Atomics because they are usually irregularly scattered in memory and thus don't * interfere much with each other. But Atomic objects residing in arrays will tend to be placed adjacent to each * other, and so will most often share cache lines (with a huge negative performance impact) without this * precaution. * * In part because Cells are relatively large, we avoid creating them until they are needed. When there is no * contention, all updates are made to the base field. Upon first contention (a failed CAS on base update), the * table is initialized to size 2. The table size is doubled upon further contention until reaching the nearest * power of two greater than or equal to the number of CPUS. Table slots remain empty (null) until they are needed. * * A single spinlock ("busy") is used for initializing and resizing the table, as well as populating slots with new * Cells. There is no need for a blocking lock; when the lock is not available, threads try other slots (or the * base). During these retries, there is increased contention and reduced locality, which is still better than * alternatives. * * Per-thread hash codes are initialized to random values. Contention and/or table collisions are indicated by * failed CASes when performing an update operation (see method retryUpdate). Upon a collision, if the table size is * less than the capacity, it is doubled in size unless some other thread holds the lock. If a hashed slot is empty, * and lock is available, a new Cell is created. Otherwise, if the slot exists, a CAS is tried. Retries proceed by * "double hashing", using a secondary hash (Marsaglia XorShift) to try to find a free slot. * * The table size is capped because, when there are more threads than CPUs, supposing that each thread were bound to * a CPU, there would exist a perfect hash function mapping threads to slots that eliminates collisions. When we * reach capacity, we search for this mapping by randomly varying the hash codes of colliding threads. Because * search is random, and collisions only become known via CAS failures, convergence can be slow, and because threads * are typically not bound to CPUS forever, may not occur at all. However, despite these limitations, observed * contention rates are typically low in these cases. * * It is possible for a Cell to become unused when threads that once hashed to it terminate, as well as in the case * where doubling the table causes no thread to hash to it under expanded mask. We do not try to detect or remove * such cells, under the assumption that for long-running instances, observed contention levels will recur, so the * cells will eventually be needed again; and for short-lived ones, it does not matter. */ /** * Padded variant of AtomicLong supporting only raw accesses plus CAS. * The value field is placed between pads, hoping that the JVM doesn't * reorder them. * * JVM intrinsics note: It would be possible to use a release-only * form of CAS here, if it were provided. */ static final class Cell { volatile long p0, p1, p2, p3, p4, p5, p6; volatile long value; volatile long q0, q1, q2, q3, q4, q5, q6; Cell(long x) { value = x; } final boolean cas(long cmp, long val) { return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long valueOffset; static { try { UNSAFE = getUnsafe(); Class ak = Cell.class; valueOffset = UNSAFE.objectFieldOffset(ak.getDeclaredField("value")); } catch (Exception e) { throw new Error(e); } } } /** * ThreadLocal holding a single-slot int array holding hash code. * Unlike the JDK8 version of this class, we use a suboptimal * int[] representation to avoid introducing a new type that can * impede class-unloading when ThreadLocals are not removed. */ static final ThreadLocal threadHashCode = new ThreadLocal(); /** * Generator of new random hash codes */ static final Random rng = new Random(); /** Number of CPUS, to place bound on table size */ static final int NCPU = Runtime.getRuntime().availableProcessors(); /** * Table of cells. When non-null, size is a power of 2. */ transient volatile Cell[] cells; /** * Base value, used mainly when there is no contention, but also as * a fallback during table initialization races. Updated via CAS. */ transient volatile long base; /** * Spinlock (locked via CAS) used when resizing and/or creating Cells. */ transient volatile int busy; /** * Package-private default constructor */ Striped64() { } /** * CASes the base field. */ final boolean casBase(long cmp, long val) { return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); } /** * CASes the busy field from 0 to 1 to acquire lock. */ final boolean casBusy() { return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); } /** * Computes the function of current and new value. Subclasses * should open-code this update function for most uses, but the * virtualized form is needed within retryUpdate. * * @param currentValue the current value (of either base or a cell) * @param newValue the argument from a user update call * @return result of the update function */ abstract long fn(long currentValue, long newValue); /** * Handles cases of updates involving initialization, resizing, * creating new Cells, and/or contention. See above for * explanation. This method suffers the usual non-modularity * problems of optimistic retry code, relying on rechecked sets of * reads. * * @param x the value * @param hc the hash code holder * @param wasUncontended false if CAS failed before call */ final void retryUpdate(long x, int[] hc, boolean wasUncontended) { int h; if (hc == null) { threadHashCode.set(hc = new int[1]); // Initialize randomly int r = rng.nextInt(); // Avoid zero to allow xorShift rehash h = hc[0] = (r == 0) ? 1 : r; } else h = hc[0]; boolean collide = false; // True if last slot nonempty for (;;) { Cell[] as; Cell a; int n; long v; if ((as = cells) != null && (n = as.length) > 0) { if ((a = as[(n - 1) & h]) == null) { if (busy == 0) { // Try to attach new Cell Cell r = new Cell(x); // Optimistically create if (busy == 0 && casBusy()) { boolean created = false; try { // Recheck under lock Cell[] rs; int m, j; if ((rs = cells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) { rs[j] = r; created = true; } } finally { busy = 0; } if (created) break; continue; // Slot is now non-empty } } collide = false; } else if (!wasUncontended) // CAS already known to fail wasUncontended = true; // Continue after rehash else if (a.cas(v = a.value, fn(v, x))) break; else if (n >= NCPU || cells != as) collide = false; // At max size or stale else if (!collide) collide = true; else if (busy == 0 && casBusy()) { try { if (cells == as) { // Expand table unless stale Cell[] rs = new Cell[n << 1]; for (int i = 0; i < n; ++i) rs[i] = as[i]; cells = rs; } } finally { busy = 0; } collide = false; continue; // Retry with expanded table } h ^= h << 13; // Rehash h ^= h >>> 17; h ^= h << 5; hc[0] = h; // Record index for next time } else if (busy == 0 && cells == as && casBusy()) { boolean init = false; try { // Initialize table if (cells == as) { Cell[] rs = new Cell[2]; rs[h & 1] = new Cell(x); cells = rs; init = true; } } finally { busy = 0; } if (init) break; } else if (casBase(v = base, fn(v, x))) break; // Fall back on using base } } /** * Sets base and all cells to the given value. */ final void internalReset(long initialValue) { Cell[] as = cells; base = initialValue; if (as != null) { int n = as.length; for (int i = 0; i < n; ++i) { Cell a = as[i]; if (a != null) a.value = initialValue; } } } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long baseOffset; private static final long busyOffset; static { try { UNSAFE = getUnsafe(); Class sk = Striped64.class; baseOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField("base")); busyOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField("busy")); } catch (Exception e) { throw new Error(e); } } /** * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. * Replace with a simple call to Unsafe.getUnsafe when integrating * into a jdk. * * @return a sun.misc.Unsafe */ private static sun.misc.Unsafe getUnsafe() { try { return sun.misc.Unsafe.getUnsafe(); } catch (SecurityException tryReflectionInstead) { } try { return java.security.AccessController .doPrivileged(new java.security.PrivilegedExceptionAction() { public sun.misc.Unsafe run() throws Exception { Class k = sun.misc.Unsafe.class; for (java.lang.reflect.Field f : k.getDeclaredFields()) { f.setAccessible(true); Object x = f.get(null); if (k.isInstance(x)) return k.cast(x); } throw new NoSuchFieldError("the Unsafe"); } }); } catch (java.security.PrivilegedActionException e) { throw new RuntimeException("Could not initialize intrinsics", e.getCause()); } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/limiter/RateLimiterUtil.java ================================================ package com.vip.vjtools.vjkit.concurrent.limiter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import com.google.common.util.concurrent.RateLimiter; public class RateLimiterUtil { /** * 一个用来定制RateLimiter的方法,默认一开始就桶里就装满token。 * * @param permitsPerSecond 每秒允许的请求数,可看成QPS。 * @param maxBurstSeconds 可看成桶的容量,Guava中最大的突发流量缓冲时间,默认是1s, permitsPerSecond * maxBurstSeconds,就是闲置时累积的缓冲token最大值。 */ public static RateLimiter create(double permitsPerSecond, double maxBurstSeconds) throws ReflectiveOperationException { return create(permitsPerSecond, maxBurstSeconds, true); } /** * 一个用来定制RateLimiter的方法。 * * @param permitsPerSecond 每秒允许的请求书,可看成QPS * @param maxBurstSeconds 最大的突发缓冲时间。用来应对突发流量。Guava的实现默认是1s。permitsPerSecond * maxBurstSeconds的数量,就是闲置时预留的缓冲token数量 * @param filledWithToken 是否需要创建时就保留有permitsPerSecond * maxBurstSeconds的token */ public static RateLimiter create(double permitsPerSecond, double maxBurstSeconds, boolean filledWithToken) throws ReflectiveOperationException { Class sleepingStopwatchClass = Class .forName("com.google.common.util.concurrent.RateLimiter$SleepingStopwatch"); Method createStopwatchMethod = sleepingStopwatchClass.getDeclaredMethod("createFromSystemTimer"); createStopwatchMethod.setAccessible(true); Object stopwatch = createStopwatchMethod.invoke(null); Class burstyRateLimiterClass = Class .forName("com.google.common.util.concurrent.SmoothRateLimiter$SmoothBursty"); Constructor burstyRateLimiterConstructor = burstyRateLimiterClass.getDeclaredConstructors()[0]; burstyRateLimiterConstructor.setAccessible(true); // set maxBurstSeconds RateLimiter rateLimiter = (RateLimiter) burstyRateLimiterConstructor.newInstance(stopwatch, maxBurstSeconds); rateLimiter.setRate(permitsPerSecond); if (filledWithToken) { // set storedPermits setField(rateLimiter, "storedPermits", permitsPerSecond * maxBurstSeconds); } return rateLimiter; } private static boolean setField(Object targetObject, String fieldName, Object fieldValue) { Field field; try { field = targetObject.getClass().getDeclaredField(fieldName); } catch (NoSuchFieldException e) { field = null; } Class superClass = targetObject.getClass().getSuperclass(); while (field == null && superClass != null) { try { field = superClass.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { superClass = superClass.getSuperclass(); } } if (field == null) { return false; } field.setAccessible(true); try { field.set(targetObject, fieldValue); return true; } catch (IllegalAccessException e) { return false; } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/limiter/Sampler.java ================================================ package com.vip.vjtools.vjkit.concurrent.limiter; import org.apache.commons.lang3.Validate; import com.vip.vjtools.vjkit.number.RandomUtil; /** * 采样器 * * 移植 Twitter Common, 优化使用ThreadLocalRandom * * https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/util/Sampler.java * */ public class Sampler { private static final Double ALWAYS = Double.valueOf(100); private static final Double NEVER = Double.valueOf(0); private double threshold; protected Sampler() { } /** * @param selectPercent 采样率,在0-100 之间,可以有小数位 */ protected Sampler(double selectPercent) { Validate.isTrue((selectPercent >= 0) && (selectPercent <= 100), "Invalid selectPercent value: " + selectPercent); this.threshold = selectPercent / 100; } /** * 优化的创建函数,如果为0或100时,返回更直接的采样器 */ public static Sampler create(Double selectPercent) { if (selectPercent.equals(ALWAYS)) { return new AlwaysSampler(); } else if (selectPercent.equals(NEVER)) { return new NeverSampler(); } else { return new Sampler(selectPercent); } } /** * 判断当前请求是否命中采样 */ public boolean select() { return RandomUtil.threadLocalRandom().nextDouble() < threshold; } /** * 采样率为100时,总是返回true */ protected static class AlwaysSampler extends Sampler { @Override public boolean select() { return true; } } /** * 采样率为0时,总是返回false */ protected static class NeverSampler extends Sampler { @Override public boolean select() { return false; } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/limiter/TimeIntervalLimiter.java ================================================ package com.vip.vjtools.vjkit.concurrent.limiter; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; public class TimeIntervalLimiter { private final AtomicLong lastTimeAtom = new AtomicLong(0); private long windowSizeMillis; public TimeIntervalLimiter(long interval, TimeUnit timeUnit) { this.windowSizeMillis = timeUnit.toMillis(interval); } public boolean tryAcquire() { long currentTime = System.currentTimeMillis(); long lastTime = lastTimeAtom.get(); return currentTime - lastTime >= windowSizeMillis && lastTimeAtom.compareAndSet(lastTime, currentTime); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/threadpool/AbortPolicyWithReport.java ================================================ package com.vip.vjtools.vjkit.concurrent.threadpool; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vip.vjtools.vjkit.concurrent.ThreadDumpper; /** * Abort Policy. * 如果线程池已满,退出申请并打印Thread Dump(会有一定的最少间隔,默认为10分钟) */ public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy { protected static final Logger logger = LoggerFactory.getLogger(AbortPolicyWithReport.class); private final String threadName; private ThreadDumpper dumpper = new ThreadDumpper(); public AbortPolicyWithReport(String threadName) { this.threadName = threadName; } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { String msg = String.format( "Thread pool is EXHAUSTED!" + " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d)," + " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s)!", threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(), e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating()); logger.warn(msg); dumpper.tryThreadDump(null); throw new RejectedExecutionException(msg); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/threadpool/QueuableCachedThreadPool.java ================================================ package com.vip.vjtools.vjkit.concurrent.threadpool; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * From Tomcat 8.5.6, 传统的FixedThreadPool有Queue但线程数量不变,而CachedThreadPool线程数可变但没有Queue * * Tomcat的线程池,通过控制TaskQueue,线程数,但线程数到达最大时会进入Queue中. * * 代码从Tomcat复制,主要修改包括: * * 1. 删除定期重启线程避免内存泄漏的功能, * * 2. TaskQueue中可能3次有锁的读取线程数量,改为只读取1次,这把锁也是这个实现里的唯一遗憾了。 * * https://github.com/apache/tomcat/blob/trunk/java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java */ public final class QueuableCachedThreadPool extends java.util.concurrent.ThreadPoolExecutor { /** * The number of tasks submitted but not yet finished. This includes tasks in the queue and tasks that have been * handed to a worker thread but the latter did not start executing the task yet. This number is always greater or * equal to {@link #getActiveCount()}. */ private final AtomicInteger submittedCount = new AtomicInteger(0); public QueuableCachedThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ControllableQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); workQueue.setParent(this); prestartAllCoreThreads(); // NOSOANR } @Override protected void afterExecute(Runnable r, Throwable t) { submittedCount.decrementAndGet(); } public int getSubmittedCount() { return submittedCount.get(); } /** * {@inheritDoc} */ @Override public void execute(Runnable command) { execute(command, 0, TimeUnit.MILLISECONDS); } /** * Executes the given command at some time in the future. The command may execute in a new thread, in a pooled * thread, or in the calling thread, at the discretion of the Executor implementation. If no threads are * available, it will be added to the work queue. If the work queue is full, the system will wait for the specified * time and it throw a RejectedExecutionException if the queue is still full after that. * * @param command the runnable task * @param timeout A timeout for the completion of the task * @param unit The timeout time unit * @throws RejectedExecutionException if this task cannot be accepted for execution - the queue is full * @throws NullPointerException if command or unit is null */ public void execute(Runnable command, long timeout, TimeUnit unit) { submittedCount.incrementAndGet(); try { super.execute(command); } catch (RejectedExecutionException rx) { // NOSONAR // not to re-throw this exception because this is only used to find out whether the pool is full, not for a // exception purpose final ControllableQueue queue = (ControllableQueue) super.getQueue(); try { if (!queue.force(command, timeout, unit)) { submittedCount.decrementAndGet(); throw new RejectedExecutionException("Queue capacity is full."); } } catch (InterruptedException ignore) { submittedCount.decrementAndGet(); throw new RejectedExecutionException(ignore); } } } /** * https://github.com/apache/tomcat/blob/trunk/java/org/apache/tomcat/util/threads/TaskQueue.java */ protected static class ControllableQueue extends LinkedBlockingQueue { private static final long serialVersionUID = 5044057462066661171L; private transient volatile QueuableCachedThreadPool parent = null; public ControllableQueue(int capacity) { super(capacity); } public void setParent(QueuableCachedThreadPool tp) { parent = tp; } public boolean force(Runnable o) { if (parent.isShutdown()) { throw new RejectedExecutionException("Executor not running, can't force a command into the queue"); } return super.offer(o); // forces the item onto the queue, to be used if the task is rejected } public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException { if (parent.isShutdown()) { throw new RejectedExecutionException("Executor not running, can't force a command into the queue"); } return super.offer(o, timeout, unit); // forces the item onto the queue, to be used if the task is rejected } @Override public boolean offer(Runnable o) { // springside: threadPool.getPoolSize() 是个有锁的操作,所以尽量减少 int currentPoolSize = parent.getPoolSize(); // we are maxed out on threads, simply queue the object if (currentPoolSize >= parent.getMaximumPoolSize()) { return super.offer(o); } // we have idle threads, just add it to the queue if (parent.getSubmittedCount() < currentPoolSize) { return super.offer(o); } // if we have less threads than maximum force creation of a new thread if (currentPoolSize < parent.getMaximumPoolSize()) { return false; } // if we reached here, we need to add it to the queue return super.offer(o); } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/threadpool/ThreadPoolBuilder.java ================================================ package com.vip.vjtools.vjkit.concurrent.threadpool; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor.AbortPolicy; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.Validate; import com.vip.vjtools.vjkit.concurrent.threadpool.QueuableCachedThreadPool.ControllableQueue; /** * ThreadPool创建的工具类. * * 对比JDK Executors中的newFixedThreadPool(), newCachedThreadPool(),newScheduledThreadPool, 提供更多有用的配置项. * * 另包含了移植自Tomcat的QueuableCachedPool. * * 使用示例如下: * *

 * ExecutorService ExecutorService = new FixedThreadPoolBuilder().setPoolSize(10).build();
 * 
* * 参考文章 《Java ThreadPool的正确打开方式》http://calvin1978.blogcn.com/articles/java-threadpool.html */ public class ThreadPoolBuilder { private static RejectedExecutionHandler defaultRejectHandler = new AbortPolicy(); /** * @see FixedThreadPoolBuilder */ public static FixedThreadPoolBuilder fixedPool() { return new FixedThreadPoolBuilder(); } /** * @see CachedThreadPoolBuilder */ public static CachedThreadPoolBuilder cachedPool() { return new CachedThreadPoolBuilder(); } /** * @see ScheduledThreadPoolBuilder */ public static ScheduledThreadPoolBuilder scheduledPool() { return new ScheduledThreadPoolBuilder(); } /** * @see QueuableCachedThreadPoolBuilder */ public static QueuableCachedThreadPoolBuilder queuableCachedPool() { return new QueuableCachedThreadPoolBuilder(); } /** * 创建FixedThreadPool.建议必须设置queueSize保证有界。 * * 1. 任务提交时, 如果线程数还没达到poolSize即创建新线程并绑定任务(即poolSize次提交后线程总数必达到poolSize,不会重用之前的线程) * * poolSize默认为1,即singleThreadPool. * * 2. 第poolSize次任务提交后, 新增任务放入Queue中, Pool中的所有线程从Queue中take任务执行. * * Queue默认为无限长的LinkedBlockingQueue, 但建议设置queueSize换成有界的队列. * * 如果使用有界队列, 当队列满了之后,会调用RejectHandler进行处理, 默认为AbortPolicy,抛出RejectedExecutionException异常. * 其他可选的Policy包括静默放弃当前任务(Discard),放弃Queue里最老的任务(DiscardOldest),或由主线程来直接执行(CallerRuns). * * 3. 因为线程全部为core线程,所以不会在空闲时回收. */ public static class FixedThreadPoolBuilder { private int poolSize = 1; private int queueSize = -1; private ThreadFactory threadFactory; private String threadNamePrefix; private Boolean daemon; private RejectedExecutionHandler rejectHandler; /** * Pool大小,默认为1,即singleThreadPool */ public FixedThreadPoolBuilder setPoolSize(int poolSize) { Validate.isTrue(poolSize >= 1); this.poolSize = poolSize; return this; } /** * 不设置时默认为-1, 使用无限长的LinkedBlockingQueue. * * 为正数时使用ArrayBlockingQueue. */ public FixedThreadPoolBuilder setQueueSize(int queueSize) { this.queueSize = queueSize; return this; } /** * 与threadNamePrefix互斥, 优先使用ThreadFactory */ public FixedThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; return this; } /** * 与ThreadFactory互斥, 优先使用ThreadFactory */ public FixedThreadPoolBuilder setThreadNamePrefix(String threadNamePrefix) { this.threadNamePrefix = threadNamePrefix; return this; } /** * 与threadFactory互斥, 优先使用ThreadFactory * * 默认为NULL,不进行设置,使用JDK的默认值. */ public FixedThreadPoolBuilder setDaemon(Boolean daemon) { this.daemon = daemon; return this; } public FixedThreadPoolBuilder setRejectHandler(RejectedExecutionHandler rejectHandler) { this.rejectHandler = rejectHandler; return this; } public ThreadPoolExecutor build() { BlockingQueue queue = null; if (queueSize < 1) { queue = new LinkedBlockingQueue(); } else { queue = new ArrayBlockingQueue(queueSize); } threadFactory = createThreadFactory(threadFactory, threadNamePrefix, daemon); if (rejectHandler == null) { rejectHandler = defaultRejectHandler; } return new ThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.MILLISECONDS, queue, threadFactory, rejectHandler); } } /** * 创建CachedThreadPool, maxSize建议设置 * * 1. 任务提交时, 如果线程数还没达到minSize即创建新线程并绑定任务(即minSize次提交后线程总数必达到minSize, 不会重用之前的线程) * * minSize默认为0, 可设置保证有基本的线程处理请求不被回收. * * 2. 第minSize次任务提交后, 新增任务提交进SynchronousQueue后,如果没有空闲线程立刻处理,则会创建新的线程, 直到总线程数达到上限. * * maxSize默认为Integer.Max, 可以进行设置. * * 如果设置了maxSize, 当总线程数达到上限, 会调用RejectHandler进行处理, 默认为AbortPolicy, 抛出RejectedExecutionException异常. * 其他可选的Policy包括静默放弃当前任务(Discard),或由主线程来直接执行(CallerRuns). * * 3. minSize以上, maxSize以下的线程, 如果在keepAliveTime中都poll不到任务执行将会被结束掉, keeAliveTimeJDK默认为10秒. * JDK默认值60秒太高,如高达1000线程时,要低于16QPS时才会开始回收线程, 因此改为默认10秒. */ public static class CachedThreadPoolBuilder { private int minSize = 0; private int maxSize = Integer.MAX_VALUE; private int keepAliveSecs = 10; private ThreadFactory threadFactory; private String threadNamePrefix; private Boolean daemon; private RejectedExecutionHandler rejectHandler; public CachedThreadPoolBuilder setMinSize(int minSize) { this.minSize = minSize; return this; } /** * Max默认Integer.MAX_VALUE的,建议设置 */ public CachedThreadPoolBuilder setMaxSize(int maxSize) { this.maxSize = maxSize; return this; } /** * JDK默认值60秒太高,如高达1000线程时,要低于16QPS时才会开始回收线程, 因此改为默认10秒. */ public CachedThreadPoolBuilder setKeepAliveSecs(int keepAliveSecs) { this.keepAliveSecs = keepAliveSecs; return this; } /** * 与threadNamePrefix互斥, 优先使用ThreadFactory */ public CachedThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; return this; } /** * 与threadFactory互斥, 优先使用ThreadFactory */ public CachedThreadPoolBuilder setThreadNamePrefix(String threadNamePrefix) { this.threadNamePrefix = threadNamePrefix; return this; } /** * 与threadFactory互斥, 优先使用ThreadFactory * * 默认为NULL,不进行设置,使用JDK的默认值. */ public CachedThreadPoolBuilder setDaemon(Boolean daemon) { this.daemon = daemon; return this; } public CachedThreadPoolBuilder setRejectHandler(RejectedExecutionHandler rejectHandler) { this.rejectHandler = rejectHandler; return this; } public ThreadPoolExecutor build() { threadFactory = createThreadFactory(threadFactory, threadNamePrefix, daemon); if (rejectHandler == null) { rejectHandler = defaultRejectHandler; } return new ThreadPoolExecutor(minSize, maxSize, keepAliveSecs, TimeUnit.SECONDS, new SynchronousQueue(), threadFactory, rejectHandler); } } /* * 创建ScheduledPool. */ public static class ScheduledThreadPoolBuilder { private int poolSize = 1; private ThreadFactory threadFactory; private String threadNamePrefix; /** * 默认为1 */ public ScheduledThreadPoolBuilder setPoolSize(int poolSize) { this.poolSize = poolSize; return this; } /** * 与threadNamePrefix互斥, 优先使用ThreadFactory */ public ScheduledThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; return this; } public ScheduledThreadPoolBuilder setThreadNamePrefix(String threadNamePrefix) { this.threadNamePrefix = threadNamePrefix; return this; } public ScheduledThreadPoolExecutor build() { threadFactory = createThreadFactory(threadFactory, threadNamePrefix, Boolean.TRUE); return new ScheduledThreadPoolExecutor(poolSize, threadFactory); } } /** * 从Tomcat移植过来的可扩展可用Queue缓存任务的ThreadPool * * @see QueuableCachedThreadPool */ public static class QueuableCachedThreadPoolBuilder { private int minSize = 0; private int maxSize = Integer.MAX_VALUE; private int keepAliveSecs = 10; private int queueSize = 100; private ThreadFactory threadFactory; private String threadNamePrefix; private Boolean daemon; private RejectedExecutionHandler rejectHandler; public QueuableCachedThreadPoolBuilder setMinSize(int minSize) { this.minSize = minSize; return this; } public QueuableCachedThreadPoolBuilder setMaxSize(int maxSize) { this.maxSize = maxSize; return this; } /** * LinkedQueue长度, 默认100 */ public QueuableCachedThreadPoolBuilder setQueueSize(int queueSize) { this.queueSize = queueSize; return this; } public QueuableCachedThreadPoolBuilder setKeepAliveSecs(int keepAliveSecs) { this.keepAliveSecs = keepAliveSecs; return this; } /** * 与threadNamePrefix互斥, 优先使用ThreadFactory */ public QueuableCachedThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; return this; } /** * 与threadFactory互斥, 优先使用ThreadFactory */ public QueuableCachedThreadPoolBuilder setThreadNamePrefix(String threadNamePrefix) { this.threadNamePrefix = threadNamePrefix; return this; } /** * 与threadFactory互斥, 优先使用ThreadFactory * * 默认为NULL,不进行设置,使用JDK的默认值. */ public QueuableCachedThreadPoolBuilder setDaemon(Boolean daemon) { this.daemon = daemon; return this; } public QueuableCachedThreadPoolBuilder setRejectHandler(RejectedExecutionHandler rejectHandler) { this.rejectHandler = rejectHandler; return this; } public QueuableCachedThreadPool build() { threadFactory = createThreadFactory(threadFactory, threadNamePrefix, daemon); if (rejectHandler == null) { rejectHandler = defaultRejectHandler; } return new QueuableCachedThreadPool(minSize, maxSize, keepAliveSecs, TimeUnit.SECONDS, new ControllableQueue(queueSize), threadFactory, rejectHandler); } } /** * 优先使用threadFactory,否则如果threadNamePrefix不为空则使用自建ThreadFactory,否则使用defaultThreadFactory */ private static ThreadFactory createThreadFactory(ThreadFactory threadFactory, String threadNamePrefix, Boolean daemon) { if (threadFactory != null) { return threadFactory; } if (threadNamePrefix != null) { if (daemon != null) { return ThreadPoolUtil.buildThreadFactory(threadNamePrefix, daemon); } else { return ThreadPoolUtil.buildThreadFactory(threadNamePrefix); } } return Executors.defaultThreadFactory(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/threadpool/ThreadPoolUtil.java ================================================ package com.vip.vjtools.vjkit.concurrent.threadpool; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.vip.vjtools.vjkit.base.annotation.NotNull; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 线程池工具集 * * 1. 优雅关闭线程池的(via guava) * * 2. 创建可自定义线程名的ThreadFactory(via guava) * * 3. 防止第三方Runnable未捕捉异常导致线程跑飞 * * @author calvin * */ public class ThreadPoolUtil { /** * 按照ExecutorService JavaDoc示例代码编写的Graceful Shutdown方法. * * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. * * 如果1/2超时时间后, 则调用shutdownNow,取消在workQueue中Pending的任务,并中断所有阻塞函数. * * 如果1/2超时仍然超時,則強制退出. * * 另对在shutdown时线程本身被调用中断做了处理. * * 返回线程最后是否被中断. * * 使用了Guava的工具类 * @see MoreExecutors#shutdownAndAwaitTermination(ExecutorService, long, TimeUnit) */ public static boolean gracefulShutdown(@Nullable ExecutorService threadPool, int shutdownTimeoutMills) { return threadPool == null || MoreExecutors.shutdownAndAwaitTermination(threadPool, shutdownTimeoutMills, TimeUnit.MILLISECONDS); } /** * @see #gracefulShutdown */ public static boolean gracefulShutdown(@Nullable ExecutorService threadPool, int shutdownTimeout, TimeUnit timeUnit) { return threadPool == null || MoreExecutors.shutdownAndAwaitTermination(threadPool, shutdownTimeout, timeUnit); } /** * 创建ThreadFactory,使得创建的线程有自己的名字而不是默认的"pool-x-thread-y" * * 使用了Guava的工具类 * * @see ThreadFactoryBuilder#build() */ public static ThreadFactory buildThreadFactory(@NotNull String threadNamePrefix) { return new ThreadFactoryBuilder().setNameFormat(threadNamePrefix + "-%d").build(); } /** * 可设定是否daemon, daemon线程在主线程已执行完毕时, 不会阻塞应用不退出, 而非daemon线程则会阻塞. * * @see #buildThreadFactory */ public static ThreadFactory buildThreadFactory(@NotNull String threadNamePrefix, @NotNull boolean daemon) { return new ThreadFactoryBuilder().setNameFormat(threadNamePrefix + "-%d").setDaemon(daemon).build(); } /** * 防止用户没有捕捉异常导致中断了线程池中的线程, 使得SchedulerService无法继续执行. * * 在无法控制第三方包的Runnable实现时,调用本函数进行包裹. */ public static Runnable safeRunnable(@NotNull Runnable runnable) { return new SafeRunnable(runnable); } /** * 保证不会有Exception抛出到线程池的Runnable包裹类,防止用户没有捕捉异常导致中断了线程池中的线程, 使得SchedulerService无法执行. 在无法控制第三方包的Runnable实现时,使用本类进行包裹. */ private static class SafeRunnable implements Runnable { private static Logger logger = LoggerFactory.getLogger(SafeRunnable.class); private Runnable runnable; public SafeRunnable(Runnable runnable) { Validate.notNull(runnable); this.runnable = runnable; } @Override public void run() { try { runnable.run(); } catch (Throwable e) { // catch any exception, because the scheduled thread will break if the exception thrown to outside. logger.error("Unexpected error occurred in task", e); } } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/type/BasicFuture.java ================================================ /* * ==================================================================== Licensed to the Apache Software Foundation (ASF) * under one or more contributor license agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the * License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * ==================================================================== * * This software consists of voluntary contributions made by many individuals on behalf of the Apache Software * Foundation. For more information on the Apache Software Foundation, please see . * */ package com.vip.vjtools.vjkit.concurrent.type; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.lang3.Validate; /** * 从Apache HttpClient 移植(2017.4),一个Future实现类的基本框架. * * https://github.com/apache/httpcomponents-core/blob/master/httpcore5/src/main/java/org/apache/hc/core5/concurrent/BasicFuture.java * * 不过HC用的是callback,这里用的是继承 */ public abstract class BasicFuture implements Future { private volatile boolean completed; // NOSONAR private volatile boolean cancelled; private volatile T result; private volatile Exception ex; @Override public boolean isCancelled() { return this.cancelled; } @Override public boolean isDone() { return this.completed; } @Override public synchronized T get() throws InterruptedException, ExecutionException { while (!this.completed) { wait(); } return getResult(); } @Override public synchronized T get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { Validate.notNull(unit, "Time unit"); final long msecs = unit.toMillis(timeout); final long startTime = (msecs <= 0) ? 0 : System.currentTimeMillis(); long waitTime = msecs; if (this.completed) { return getResult(); } else if (waitTime <= 0) { throw new TimeoutException(); } else { for (;;) { wait(waitTime); if (this.completed) { return getResult(); } else { waitTime = msecs - (System.currentTimeMillis() - startTime); if (waitTime <= 0) { throw new TimeoutException(); } } } } } private T getResult() throws ExecutionException { if (this.ex != null) { throw new ExecutionException(this.ex); } if (cancelled) { throw new CancellationException(); } return this.result; } public boolean completed(final T result) { synchronized (this) { if (this.completed) { return false; } this.completed = true; this.result = result; notifyAll(); } onCompleted(result); return true; } public boolean failed(final Exception exception) { synchronized (this) { if (this.completed) { return false; } this.completed = true; this.ex = exception; notifyAll(); } onFailed(exception); return true; } @Override public boolean cancel(final boolean mayInterruptIfRunning) { synchronized (this) { if (this.completed) { return false; } this.completed = true; this.cancelled = true; notifyAll(); } onCancelled(); return true; } protected abstract void onCompleted(T result); protected abstract void onFailed(Exception ex); protected abstract void onCancelled(); } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/type/ThreadLocalContext.java ================================================ package com.vip.vjtools.vjkit.concurrent.type; import java.util.HashMap; import java.util.Map; /** * 存储于ThreadLocal的Map, 用于存储上下文.
* * 但HashMap的存储其实较为低效,在高性能场景下可改为EnumMap
* * 1.先定义枚举类,列举所有可能的Key
* 2.替换contextMap的创建语句,见下例
* 3.修改put()/get()中key的类型
* *
 * private static ThreadLocal> contextMap = new ThreadLocal>() {
 * 	@Override
 * 	protected Map initialValue() {
 * 		return new EnumMap(MyEnum.class);
 * 	}
 * };
 * 
*/ public class ThreadLocalContext { private static ThreadLocal> contextMap = new ThreadLocal>() { @Override protected Map initialValue() { // 降低loadFactory减少冲突 return new HashMap(16, 0.5f); } }; /** * 放入ThreadLocal的上下文信息. */ public static void put(String key, Object value) { contextMap.get().put(key, value); } /** * 取出ThreadLocal的上下文信息. */ @SuppressWarnings("unchecked") public static T get(String key) { return (T) (contextMap.get().get(key)); } /** * 清理ThreadLocal的Context内容. */ public static void reset() { contextMap.get().clear(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/DataMask.java ================================================ package com.vip.vjtools.vjkit.datamasking; import com.alibaba.fastjson.JSON; import com.vip.vjtools.vjkit.datamasking.strategy.HashMask; /** * 脱敏工具类 */ public class DataMask { private static DataMaskJsonFilter jsonFilter = new DataMaskJsonFilter(); /** * 设置Hash脱敏的salt */ public static void setHashSalt(String salt){ HashMask.setSalt(salt); } /** * 默认的脱敏方式,只显示第一个字符串 * @param source 源字符串 * @return 脱敏后的字符串 */ public static String mask(String source) { try { return mask(source, SensitiveType.Default); } catch (Exception e) { return source; } } /** * 根据类型来脱敏 * @param source 源字符串 * @param type 类型 * @return 掩码后的字符串 */ public static String mask(String source, SensitiveType type) { try { return type.getStrategy().mask(source, type.getParams()); } catch (Exception e) { return source; } } /** * 自定义脱敏策略来脱敏 * @param source 源字符串 * @param strategy 策略 * @param param 策略参数 * @return 脱敏后的字符串 */ public static String mask(String source,MaskStrategy strategy,int...param){ try { return strategy.mask(source,param); } catch (Exception e) { return source; } } /** * 将需要脱敏的字段先进行脱敏操作,最后转成json格式 * @param object 需要序列化的对象 * @return 脱敏后的json格式 */ public static String toJSONString(Object object) { if (object == null) { return null; } try { return JSON.toJSONString(object, jsonFilter); } catch (Exception e) { return JSON.toJSONString(object); } } /** * 对脱敏字段进行处理,然后调用对象原来的toString() * 注意,如果需要脱敏的字段比较多,性能会被toJson差,建议优先使用toJSON * @param object 需要序列化的对象 * @return 脱敏后的toString()格式 */ public static String toString(Object object) { if (object == null) { return null; } try { String json = toJSONString(object); Object cloneObj = JSON.parseObject(json, object.getClass()); return cloneObj.toString(); } catch (Exception e) { return object.toString(); } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/DataMaskJsonFilter.java ================================================ package com.vip.vjtools.vjkit.datamasking; import com.alibaba.fastjson.serializer.BeanContext; import com.alibaba.fastjson.serializer.ContextValueFilter; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; /** * 对每个json字段进行脱敏处理 * 处理String,String[] Collection */ public class DataMaskJsonFilter implements ContextValueFilter { @Override public Object process(BeanContext context, Object object, String name, Object value) { if (null == value) { return null; } try { Field field = context.getField(); //处理字符串,只处理字符串相关类型的字段 if (field.getType() == String.class) { if (((String) value).length() == 0) { return value; } Sensitive sensitive = field.getAnnotation(Sensitive.class); SensitiveType sensitiveType = getSensitiveType(field, sensitive); return mask((String) value, sensitive, sensitiveType); } else if (field.getType() == String[].class) { //处理数组String[] Sensitive sensitive = field.getAnnotation(Sensitive.class); SensitiveType sensitiveType = getSensitiveType(field, sensitive); if (sensitiveType == null) { return value; } String[] strArr = (String[]) value; for (int i = 0; i < strArr.length; i++) { strArr[i] = mask(strArr[i], sensitive, sensitiveType); } return strArr; } else if (Collection.class.isAssignableFrom(field.getType())) { //处理Collection Sensitive sensitive = field.getAnnotation(Sensitive.class); SensitiveType sensitiveType = getSensitiveType(field, sensitive); if (sensitiveType == null) { return value; } if(!isStringCollection(field)){ return value; } //没有set()的接口,重新构造一个 Collection newValue = (Collection) value.getClass().newInstance(); for (String item : (Collection) value) { newValue.add(mask(item, sensitive, sensitiveType)); } return newValue; } else { return value; } } catch (Exception e) { return value; } } /** * 是否Collection * @param field * @return */ private boolean isStringCollection(Field field){ Type type = field.getGenericType(); if (!(type instanceof ParameterizedType)) { return false; } Class parameterizedType = (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; if (parameterizedType != String.class) { return false; } return true; } /** * 对字段进行mask * @param value 值 * @return 脱敏后的值 */ private String mask(String value, Sensitive sensitive, SensitiveType type) { if (value == null || value.isEmpty()) { return value; } if (sensitive == null) { if (type != null) { return DataMask.mask(value, type); } else { return value; } } else { return sensitive.type().getStrategy().mask(value, sensitive.keepChars()); } } private SensitiveType getSensitiveType(Field field, Sensitive sensitive) { SensitiveType type = null; if (sensitive == null) { String fieldName = field.getName(); //没有@Sensitive,但是有mapping命中 type = MaskMapping.getMaskTypeMapping(fieldName); } else { type = sensitive.type(); } return type; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/MaskMapping.java ================================================ package com.vip.vjtools.vjkit.datamasking; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 加载系统定义的和用户定义的mapping配置 */ public class MaskMapping { private static final Logger logger = LoggerFactory.getLogger(MaskMapping.class); private static final String SYS_MASK_MAPPING = "sys_data_mask.properties";//系统定义的 private static final String USER_MASK_MAPPING = "data_mask.properties";//用户自定义的 private static final Map mappings = new HashMap<>(); static { init(); } /** * 初始化 * 加载系统定义的和用户自定义的掩码字段映射 */ private static void init() { loadPropertyFromFile(SYS_MASK_MAPPING); loadPropertyFromFile(USER_MASK_MAPPING); } /** * 加载掩码映射文件 */ private static void loadPropertyFromFile(String resourcePath) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Properties props = new Properties(); boolean isUserMapping = resourcePath.equals(USER_MASK_MAPPING); try { //加载所有的用户自定义mapping配置 Enumeration paths = loader.getResources(resourcePath); //没有配置文件 if (paths == null || !paths.hasMoreElements()) { logger.info("data-masking property file don't exist:{}", resourcePath); return; } while (paths.hasMoreElements()) { URL path = paths.nextElement(); //打印路径 logger.info("load data-masking property file:{},path is {}", resourcePath, path); try (InputStream resourceStream = path.openStream()) { if (resourceStream == null) { logger.info("data-masking property file's stream is null:{},{}", path, resourcePath); return; } props.load(resourceStream); for (Map.Entry prop : props.entrySet()) { String type = prop.getKey().toString().trim(); String values = prop.getValue().toString().trim(); String[] maps = values.split(","); SensitiveType sensitiveType = SensitiveType.valueOf(type); for (String map : maps) { mappings.put(map.toLowerCase(), sensitiveType); if (isUserMapping) { logger.info("load user mask mapping {}={},from path:{}", sensitiveType, map, path); } } } } catch (IOException e) { logger.warn("load data-masking property file error!,{}", path, e); } } } catch (IOException e) { logger.warn("load data-masking property file error!", e); } } public static SensitiveType getMaskTypeMapping(String fieldName) { return mappings.get(fieldName.toLowerCase()); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/MaskStrategy.java ================================================ package com.vip.vjtools.vjkit.datamasking; /** * 脱敏策略接口 */ public interface MaskStrategy { String mask(String source, int[] params); } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/Sensitive.java ================================================ package com.vip.vjtools.vjkit.datamasking; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 掩码类型标记 * 在Java类字段中标记,对象在被DataMask序列化的时候,就可以自动被掩码 */ @Target(ElementType.FIELD) @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface Sensitive { SensitiveType type() default SensitiveType.Default; /** * 如果首尾保留数量一样的,可以只用一个数字 {2} 表示首尾各保留2个字符 {1,2} 表示头部保留1个字符串,尾部保留2个字符串 */ int[] keepChars() default {1, 0}; } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/SensitiveType.java ================================================ package com.vip.vjtools.vjkit.datamasking; import com.vip.vjtools.vjkit.datamasking.strategy.EmailMask; import com.vip.vjtools.vjkit.datamasking.strategy.HashMask; import com.vip.vjtools.vjkit.datamasking.strategy.NameMask; import com.vip.vjtools.vjkit.datamasking.strategy.PartMask; /** * 脱敏类型 */ public enum SensitiveType { Name(new NameMask()),//中文名 Phone(new PartMask(), 3),//电话 IDCard(new PartMask(), 5, 2),//身份证号 BankCard(new PartMask(), 4, 2),//银行卡号 Address(new PartMask(), 9, 0),//地址 Email(new EmailMask()),//电子邮件 Captcha(new PartMask(), 1),//验证码 Passport(new PartMask(), 2),//护照/军官证 Account(new PartMask(), 1),//账号 Password(new PartMask(), 0),//密码 /** * 散列,这种掩码方式,用户可以手工计算Hash值来精确查询日志。 */ Hash(new HashMask()), Default(new PartMask(), 1, 0); //缺省,只显示第一个字符串 private MaskStrategy strategy; private int[] params; SensitiveType(MaskStrategy strategy, int... params) { this.strategy = strategy; this.params = params; } public MaskStrategy getStrategy() { return strategy; } public int[] getParams() { return params; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/strategy/EmailMask.java ================================================ package com.vip.vjtools.vjkit.datamasking.strategy; import com.vip.vjtools.vjkit.datamasking.MaskStrategy; /** * 电子邮件掩码 * 规则: * '@' 前面的字符串,首尾保留1位 */ public class EmailMask extends PartMask implements MaskStrategy { @Override public String mask(String source, int[] params) { if (source == null || source.length() == 0) { return source; } int index = source.indexOf("@"); //不是电子邮件格式,直接回退到默认的模式 if (index <= 0) { return super.mask(source, new int[]{1, 0}); } return super.mask(source.substring(0, index), new int[]{1}).concat(source.substring(index)); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/strategy/HashMask.java ================================================ package com.vip.vjtools.vjkit.datamasking.strategy; import com.vip.vjtools.vjkit.datamasking.MaskStrategy; import com.vip.vjtools.vjkit.text.EncodeUtil; import com.vip.vjtools.vjkit.text.HashUtil; /** * Hash掩码,主要用于一些敏感信息掩码后查询。 * 算法:sha1(source+salt) */ public class HashMask implements MaskStrategy { private static String salt="default_salt"; @Override public String mask(String source, int[] params) { if (source == null || source.isEmpty()) { return source; } return EncodeUtil.encodeHex(HashUtil.sha1(source+getSalt())); } /** * 设置salt */ public static void setSalt(String salt){ HashMask.salt = salt; } /** * 默认用 default_salt * 可以通过DataMask来设置 */ public static String getSalt() { return salt; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/strategy/NameMask.java ================================================ package com.vip.vjtools.vjkit.datamasking.strategy; import com.vip.vjtools.vjkit.datamasking.MaskStrategy; /** * 姓名掩码 * 规则: * 长度小于3,显示最后一位 * 长度大于3,显示最后两位 */ public class NameMask extends PartMask implements MaskStrategy { @Override public String mask(String source, int[] params) { if (source == null || source.length() == 0) { return source; } if (source.length() <= 3) { return super.mask(source, new int[]{0, 1}); } else { return super.mask(source, new int[]{0, 2}); } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/strategy/PartMask.java ================================================ package com.vip.vjtools.vjkit.datamasking.strategy; import com.vip.vjtools.vjkit.datamasking.MaskStrategy; import java.util.HashMap; import java.util.Map; /** * 部分掩码 * 可以支持前后保留n个字符串 */ public class PartMask implements MaskStrategy { private static Map maskCodes = new HashMap<>(); static { //预生成指定长度的掩码星号 for (int i = 1; i <= 10; i++) { maskCodes.put(i, getMaskCodeByNum(i)); } } /** * 生成指定长度的* */ private static String getMaskCodeByNum(int num) { String maskCode = maskCodes.get(num); if (maskCode != null) { return maskCode; } StringBuilder mask = new StringBuilder(); for (int j = 0; j < num; j++) { mask.append('*'); } return mask.toString(); } @Override public String mask(String source, int[] params) { //空数据直接返回 if (source == null || source.isEmpty()) { return source; } if (source.length() == 1) { return "*"; } int leftKeep = Math.max(0, params[0]); int rightKeep = leftKeep; //左右保留不一样,可以配置两个参数 if (params.length > 1) { rightKeep = Math.max(0, params[1]); } //各种不符合条件的(leftKeep,rightKeep过界),都回退到只保留第一位的情况 boolean useDefault = leftKeep >= source.length() || rightKeep >= source.length() || (leftKeep + rightKeep) >= source .length(); if (useDefault) { return source.substring(0, 1).concat(getMaskCodeByNum(source.length() - 1)); } return source.substring(0, leftKeep).concat(getMaskCodeByNum(source.length() - leftKeep - rightKeep)) .concat(source.substring(source.length() - rightKeep, source.length())); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/id/IdUtil.java ================================================ package com.vip.vjtools.vjkit.id; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; public class IdUtil { /* * 返回使用ThreadLocalRandom的UUID,比默认的UUID性能更优 */ public static UUID fastUUID() { ThreadLocalRandom random = ThreadLocalRandom.current(); return new UUID(random.nextLong(), random.nextLong()); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/io/FilePathUtil.java ================================================ package com.vip.vjtools.vjkit.io; import org.apache.commons.lang3.StringUtils; import com.google.common.io.Files; import com.vip.vjtools.vjkit.base.Platforms; import com.vip.vjtools.vjkit.text.MoreStringUtil; /** * 关于文件路径的工具集. 这个类只适合处理纯字符串的路径,如果是File对象或者Path对象的路径处理,建议直接使用Path类的方法。 * * @see java.nio.file.Path */ public class FilePathUtil { /** * 在Windows环境里,兼容Windows上的路径分割符,将 '/' 转回 '\' */ public static String normalizePath(String path) { if (Platforms.FILE_PATH_SEPARATOR_CHAR == Platforms.WINDOWS_FILE_PATH_SEPARATOR_CHAR && StringUtils.indexOf(path, Platforms.LINUX_FILE_PATH_SEPARATOR_CHAR) != -1) { return StringUtils.replaceChars(path, Platforms.LINUX_FILE_PATH_SEPARATOR_CHAR, Platforms.WINDOWS_FILE_PATH_SEPARATOR_CHAR); } return path; } /** * 将路径整理,如 "a/../b",整理成 "b" */ public static String simplifyPath(String path) { return Files.simplifyPath(path); } /** * 以拼接路径名 */ public static String concat(String baseName, String... appendName) { if (appendName.length == 0) { return baseName; } StringBuilder concatName = new StringBuilder(); if (MoreStringUtil.endWith(baseName, Platforms.FILE_PATH_SEPARATOR_CHAR)) { concatName.append(baseName).append(appendName[0]); } else { concatName.append(baseName).append(Platforms.FILE_PATH_SEPARATOR_CHAR).append(appendName[0]); } if (appendName.length > 1) { for (int i = 1; i < appendName.length; i++) { concatName.append(Platforms.FILE_PATH_SEPARATOR_CHAR).append(appendName[i]); } } return concatName.toString(); } /** * 获得上层目录的路径 */ public static String getParentPath(String path) { String parentPath = path; if (Platforms.FILE_PATH_SEPARATOR.equals(parentPath)) { return parentPath; } parentPath = MoreStringUtil.removeEnd(parentPath, Platforms.FILE_PATH_SEPARATOR_CHAR); int idx = parentPath.lastIndexOf(Platforms.FILE_PATH_SEPARATOR_CHAR); if (idx >= 0) { parentPath = parentPath.substring(0, idx + 1); } else { parentPath = Platforms.FILE_PATH_SEPARATOR; } return parentPath; } /** * 获得参数clazz所在的Jar文件的绝对路径 */ public static String getJarPath(Class clazz) { return clazz.getProtectionDomain().getCodeSource().getLocation().getFile(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/io/FileTreeWalker.java ================================================ package com.vip.vjtools.vjkit.io; import java.io.File; import java.util.List; import java.util.regex.Pattern; import com.google.common.base.Predicate; import com.google.common.collect.TreeTraverser; import com.google.common.io.Files; import com.vip.vjtools.vjkit.text.WildcardMatcher; public class FileTreeWalker { /** * 前序递归列出所有文件, 包含文件与目录,及根目录本身. * * 前序即先列出父目录,在列出子目录. 如要后序遍历, 直接使用Files.fileTreeTraverser() */ public static List listAll(File rootDir) { return Files.fileTreeTraverser().preOrderTraversal(rootDir).toList(); } /** * 前序递归列出所有文件, 只包含文件. */ public static List listFile(File rootDir) { return Files.fileTreeTraverser().preOrderTraversal(rootDir).filter(Files.isFile()).toList(); } /** * 前序递归列出所有文件, 列出后缀名匹配的文件. (后缀名不包含.) */ public static List listFileWithExtension(final File rootDir, final String extension) { return Files.fileTreeTraverser().preOrderTraversal(rootDir).filter(new FileExtensionFilter(extension)).toList(); } /** * 前序递归列出所有文件, 列出文件名匹配通配符的文件 * * 如 ("/a/b/hello.txt", "he*") 将被返回 */ public static List listFileWithWildcardFileName(final File rootDir, final String fileNamePattern) { return Files.fileTreeTraverser().preOrderTraversal(rootDir).filter(new WildcardFileNameFilter(fileNamePattern)) .toList(); } /** * 前序递归列出所有文件, 列出文件名匹配正则表达式的文件 * * 如 ("/a/b/hello.txt", "he.*\.txt") 将被返回 */ public static List listFileWithRegexFileName(final File rootDir, final String regexFileNamePattern) { return Files.fileTreeTraverser().preOrderTraversal(rootDir) .filter(new RegexFileNameFilter(regexFileNamePattern)).toList(); } /** * 前序递归列出所有文件, 列出符合ant path风格表达式的文件 * * 如 ("/a/b/hello.txt", "he.*\.txt") 将被返回 */ public static List listFileWithAntPath(final File rootDir, final String antPathPattern) { return Files.fileTreeTraverser().preOrderTraversal(rootDir) .filter(new AntPathFilter(FilePathUtil.concat(rootDir.getAbsolutePath(), antPathPattern))).toList(); } /** * 直接使用Guava的TreeTraverser,获得更大的灵活度, 比如加入各类filter,前序/后序的选择,一边遍历一边操作 * *
	 * FileUtil.fileTreeTraverser().preOrderTraversal(root).iterator();
	 * 
*/ public static TreeTraverser fileTreeTraverser() { return Files.fileTreeTraverser(); } /** * 以文件名正则表达式为filter,配合fileTreeTraverser使用 */ public static final class RegexFileNameFilter implements Predicate { private final Pattern pattern; private RegexFileNameFilter(String pattern) { this.pattern = Pattern.compile(pattern); } @Override public boolean apply(File input) { return input.isFile() && pattern.matcher(input.getName()).matches(); } } /** * 以文件名通配符为filter,配合fileTreeTraverser使用. * * @param pattern 支持*与?的通配符,如hello*.txt 匹配 helloworld.txt */ public static final class WildcardFileNameFilter implements Predicate { private final String pattern; private WildcardFileNameFilter(String pattern) { this.pattern = pattern; } @Override public boolean apply(File input) { return input.isFile() && WildcardMatcher.match(input.getName(), pattern); } } /** * 以文件名后缀做filter,配合fileTreeTraverser使用 */ public static final class FileExtensionFilter implements Predicate { private final String extension; private FileExtensionFilter(String extension) { this.extension = extension; } @Override public boolean apply(File input) { return input.isFile() && extension.equals(FileUtil.getFileExtension(input)); } } /** * 以ant风格的path为filter,配合fileTreeTraverser使用. * * @param pattern 支持ant风格的通配符,如/var/?/a?.txt 匹配 /var/b/ab.txt, 其他通配符包括**,* */ public static final class AntPathFilter implements Predicate { private final String pattern; private AntPathFilter(String pattern) { this.pattern = pattern; } @Override public boolean apply(File input) { return input.isFile() && WildcardMatcher.matchPath(input.getAbsolutePath(), pattern); } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/io/FileUtil.java ================================================ package com.vip.vjtools.vjkit.io; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.DirectoryStream; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.List; import org.apache.commons.lang3.Validate; import com.vip.vjtools.vjkit.base.Platforms; import com.vip.vjtools.vjkit.base.annotation.NotNull; import com.vip.vjtools.vjkit.base.annotation.Nullable; import com.vip.vjtools.vjkit.text.Charsets; /** * 关于文件的工具集. * * 主要是调用JDK自带的Files工具类,少量代码调用Guava Files。 固定encoding为UTF8. * * 1.文件读写 * * 2.文件及目录操作 */ public class FileUtil { // fileVisitor for file deletion on Files.walkFileTree private static FileVisitor deleteFileVisitor = new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }; //////// 文件读写////// /** * 读取文件到byte[]. * * @see Files#readAllBytes */ public static byte[] toByteArray(final File file) throws IOException { return Files.readAllBytes(file.toPath()); } /** * 读取文件到String. */ public static String toString(final File file) throws IOException { return com.google.common.io.Files.toString(file, Charsets.UTF_8); } /** * 读取文件的每行内容到List. * * @see Files#readAllLines */ public static List toLines(final File file) throws IOException { return Files.readAllLines(file.toPath(), Charsets.UTF_8); } /** * 简单写入String到File. */ public static void write(final CharSequence data, final File file) throws IOException { Validate.notNull(file); Validate.notNull(data); try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), Charsets.UTF_8)) { writer.append(data); } } /** * 追加String到File. */ public static void append(final CharSequence data, final File file) throws IOException { Validate.notNull(file); Validate.notNull(data); try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), Charsets.UTF_8, StandardOpenOption.APPEND)) { writer.append(data); } } /** * 打开文件为InputStream. * * @see Files#newInputStream */ public static InputStream asInputStream(String fileName) throws IOException { return asInputStream(getPath(fileName)); } /** * 打开文件为InputStream. * * @see Files#newInputStream */ public static InputStream asInputStream(File file) throws IOException { Validate.notNull(file, "file is null"); return asInputStream(file.toPath()); } /** * 打开文件为InputStream. * * @see Files#newInputStream */ public static InputStream asInputStream(Path path) throws IOException { Validate.notNull(path, "path is null"); return Files.newInputStream(path); } /** * 打开文件为OutputStream. * * @see Files#newOutputStream */ public static OutputStream asOutputStream(String fileName) throws IOException { return asOutputStream(getPath(fileName)); } /** * 打开文件为OutputStream. * * @see Files#newOutputStream */ public static OutputStream asOutputStream(File file) throws IOException { Validate.notNull(file, "file is null"); return asOutputStream(file.toPath()); } /** * 打开文件为OutputStream. * * @see Files#newOutputStream */ public static OutputStream asOutputStream(Path path) throws IOException { Validate.notNull(path, "path is null"); return Files.newOutputStream(path); } /** * 获取File的BufferedReader. * * @see Files#newBufferedReader */ public static BufferedReader asBufferedReader(String fileName) throws IOException { Validate.notBlank(fileName, "filename is blank"); return asBufferedReader(getPath(fileName)); } public static BufferedReader asBufferedReader(Path path) throws IOException { Validate.notNull(path, "path is null"); return Files.newBufferedReader(path, Charsets.UTF_8); } /** * 获取File的BufferedWriter. * * @see Files#newBufferedWriter */ public static BufferedWriter asBufferedWriter(String fileName) throws IOException { Validate.notBlank(fileName, "filename is blank"); return Files.newBufferedWriter(getPath(fileName), Charsets.UTF_8); } /** * 获取File的BufferedWriter. * * @see Files#newBufferedWriter */ public static BufferedWriter asBufferedWriter(Path path) throws IOException { Validate.notNull(path, "path is null"); return Files.newBufferedWriter(path, Charsets.UTF_8); } ///// 文件操作 ///// /** * 复制文件或目录, not following links. * * @param from 如果为null,或者是不存在的文件或目录,抛出异常. * @param to 如果为null,或者from是目录而to是已存在文件,或相反 */ public static void copy(@NotNull File from, @NotNull File to) throws IOException { Validate.notNull(from); Validate.notNull(to); copy(from.toPath(), to.toPath()); } /** * 复制文件或目录, not following links. * * @param from 如果为null,或者是不存在的文件或目录,抛出异常. * @param to 如果为null,或者from是目录而to是已存在文件,或相反 */ public static void copy(@NotNull Path from, @NotNull Path to) throws IOException { Validate.notNull(from); Validate.notNull(to); if (Files.isDirectory(from)) { copyDir(from, to); } else { copyFile(from, to); } } /** * 文件复制. {@link Files#copy} * * @param from 如果为null,或文件不存在或者是目录,,抛出异常 * @param to 如果to为null,或文件存在但是一个目录,抛出异常 */ public static void copyFile(@NotNull File from, @NotNull File to) throws IOException { Validate.notNull(from); Validate.notNull(to); copyFile(from.toPath(), to.toPath()); } /** * 文件复制. {@link Files#copy} * * @param from 如果为null,或文件不存在或者是目录,,抛出异常 * @param to 如果to为null,或文件存在但是一个目录,抛出异常 */ public static void copyFile(@NotNull Path from, @NotNull Path to) throws IOException { Validate.isTrue(Files.exists(from), "%s is not exist or not a file", from); Validate.notNull(to); Validate.isTrue(!FileUtil.isDirExists(to), "%s is exist but it is a dir", to); Files.copy(from, to); } /** * 复制目录 */ public static void copyDir(@NotNull File from, @NotNull File to) throws IOException { Validate.isTrue(isDirExists(from), "%s is not exist or not a dir", from); Validate.notNull(to); copyDir(from.toPath(), to.toPath()); } /** * 复制目录 */ public static void copyDir(@NotNull Path from, @NotNull Path to) throws IOException { Validate.isTrue(isDirExists(from), "%s is not exist or not a dir", from); Validate.notNull(to); makesureDirExists(to); try (DirectoryStream dirStream = Files.newDirectoryStream(from)) { for (Path path : dirStream) { copy(path, to.resolve(path.getFileName())); } } } /** * 文件移动/重命名. * * @see Files#move */ public static void moveFile(@NotNull File from, @NotNull File to) throws IOException { Validate.notNull(from); Validate.notNull(to); moveFile(from.toPath(), to.toPath()); } /** * 文件移动/重命名. * * @see Files#move */ public static void moveFile(@NotNull Path from, @NotNull Path to) throws IOException { Validate.isTrue(isFileExists(from), "%s is not exist or not a file", from); Validate.notNull(to); Validate.isTrue(!isDirExists(to), "%s is exist but it is a dir", to); Files.move(from, to); } /** * 目录移动/重命名 */ public static void moveDir(@NotNull File from, @NotNull File to) throws IOException { Validate.isTrue(isDirExists(from), "%s is not exist or not a dir", from); Validate.notNull(to); Validate.isTrue(!isFileExists(to), "%s is exist but it is a file", to); final boolean rename = from.renameTo(to); if (!rename) { if (to.getCanonicalPath().startsWith(from.getCanonicalPath() + File.separator)) { throw new IOException("Cannot move directory: " + from + " to a subdirectory of itself: " + to); } copyDir(from, to); deleteDir(from); if (from.exists()) { throw new IOException("Failed to delete original directory '" + from + "' after copy to '" + to + '\''); } } } /** * 创建文件或更新时间戳. * * @see com.google.common.io.Files#touch */ public static void touch(String filePath) throws IOException { touch(new File(filePath)); } /** * 创建文件或更新时间戳. * * @see com.google.common.io.Files#touch */ public static void touch(File file) throws IOException { com.google.common.io.Files.touch(file); } /** * 删除文件. * * 如果文件不存在或者是目录,则不做修改 */ public static void deleteFile(@Nullable File file) throws IOException { Validate.isTrue(isFileExists(file), "%s is not exist or not a file", file); deleteFile(file.toPath()); } /** * 删除文件. * * 如果文件不存在或者是目录,则不做修改 */ public static void deleteFile(@Nullable Path path) throws IOException { Validate.isTrue(isFileExists(path), "%s is not exist or not a file", path); Files.delete(path); } /** * 删除目录及所有子目录/文件 * * @see Files#walkFileTree */ public static void deleteDir(Path dir) throws IOException { Validate.isTrue(isDirExists(dir), "%s is not exist or not a dir", dir); // 后序遍历,先删掉子目录中的文件/目录 Files.walkFileTree(dir, deleteFileVisitor); } /** * 删除目录及所有子目录/文件 */ public static void deleteDir(File dir) throws IOException { Validate.isTrue(isDirExists(dir), "%s is not exist or not a dir", dir); deleteDir(dir.toPath()); } /** * 判断目录是否存在, from Jodd */ public static boolean isDirExists(String dirPath) { if (dirPath == null) { return false; } return isDirExists(getPath(dirPath)); } public static boolean isDirExists(Path dirPath) { return dirPath != null && Files.exists(dirPath) && Files.isDirectory(dirPath); } /** * 判断目录是否存在, from Jodd */ public static boolean isDirExists(File dir) { if (dir == null) { return false; } return isDirExists(dir.toPath()); } /** * 确保目录存在, 如不存在则创建 */ public static void makesureDirExists(String dirPath) throws IOException { makesureDirExists(getPath(dirPath)); } /** * 确保目录存在, 如不存在则创建 */ public static void makesureDirExists(File file) throws IOException { Validate.notNull(file); makesureDirExists(file.toPath()); } /** * 确保目录存在, 如不存在则创建. * * @see Files#createDirectories * */ public static void makesureDirExists(Path dirPath) throws IOException { Validate.notNull(dirPath); Files.createDirectories(dirPath); } /** * 确保父目录及其父目录直到根目录都已经创建. * */ public static void makesureParentDirExists(File file) throws IOException { Validate.notNull(file); makesureDirExists(file.getParentFile()); } /** * 判断文件是否存在, from Jodd. * * @see Files#exists * @see Files#isRegularFile */ public static boolean isFileExists(String fileName) { if (fileName == null) { return false; } return isFileExists(getPath(fileName)); } /** * 判断文件是否存在, from Jodd. * * @see Files#exists * @see Files#isRegularFile */ public static boolean isFileExists(File file) { if (file == null) { return false; } return isFileExists(file.toPath()); } /** * 判断文件是否存在, from Jodd. * * @see Files#exists * @see Files#isRegularFile */ public static boolean isFileExists(Path path) { if (path == null) { return false; } return Files.exists(path) && Files.isRegularFile(path); } /** * 在临时目录创建临时目录,命名为${毫秒级时间戳}-${同一毫秒内的随机数}. * * @see Files#createTempDirectory */ public static Path createTempDir() throws IOException { return Files.createTempDirectory(System.currentTimeMillis() + "-"); } /** * 在临时目录创建临时文件,命名为tmp-${random.nextLong()}.tmp * * @see Files#createTempFile */ public static Path createTempFile() throws IOException { return Files.createTempFile("tmp-", ".tmp"); } /** * 在临时目录创建临时文件,命名为${prefix}${random.nextLong()}${suffix} * * @see Files#createTempFile */ public static Path createTempFile(String prefix, String suffix) throws IOException { return Files.createTempFile(prefix, suffix); } private static Path getPath(String filePath) { return Paths.get(filePath); } /** * 获取文件名(不包含路径) */ public static String getFileName(@NotNull String fullName) { Validate.notEmpty(fullName); int last = fullName.lastIndexOf(Platforms.FILE_PATH_SEPARATOR_CHAR); return fullName.substring(last + 1); } /** * 获取文件名的扩展名部分(不包含.) * * @see com.google.common.io.Files#getFileExtension */ public static String getFileExtension(File file) { return com.google.common.io.Files.getFileExtension(file.getName()); } /** * 获取文件名的扩展名部分(不包含.) * * @see com.google.common.io.Files#getFileExtension */ public static String getFileExtension(String fullName) { return com.google.common.io.Files.getFileExtension(fullName); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/io/IOUtil.java ================================================ package com.vip.vjtools.vjkit.io; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; import com.google.common.io.Closeables; import com.vip.vjtools.vjkit.text.Charsets; /** * IO Stream/Reader相关工具集. 固定encoding为UTF8. * * 建议使用Apache Commons IO和Guava关于IO的工具类(com.google.common.io.*), 在未引入Commons IO时可以用本类做最基本的事情. * * 1. 安静关闭Closeable对象 * * 2. 读出InputStream/Reader全部内容到String 或 List * * 3. 读出InputStream/Reader一行内容到String * * 4. 将String写到OutputStream/Writer * * 5. 将String 转换为InputStream/Reader * * 5. InputStream/Reader与OutputStream/Writer之间复制的copy * */ public class IOUtil { private static Logger logger = LoggerFactory.getLogger(IOUtil.class); /** * 在final中安静的关闭, 不再往外抛出异常避免影响原有异常,最常用函数. 同时兼容Closeable为空未实际创建的情况. * * @see Closeables#close */ public static void closeQuietly(Closeable closeable) { if (closeable == null) { return; } try { closeable.close(); } catch (IOException e) { logger.warn("IOException thrown while closing Closeable.", e); } } /** * 简单读取InputStream到String. */ public static String toString(InputStream input) throws IOException { InputStreamReader reader = new InputStreamReader(input, Charsets.UTF_8); return toString(reader); } /** * 简单读取Reader到String * * @see CharStreams#toString */ public static String toString(Reader input) throws IOException { return CharStreams.toString(input); } /** * 简单读取Reader的每行内容到List */ public static List toLines(final InputStream input) throws IOException { return CharStreams.readLines(new BufferedReader(new InputStreamReader(input, Charsets.UTF_8))); } /** * 简单读取Reader的每行内容到List * * @see CharStreams#readLines */ public static List toLines(final Reader input) throws IOException { return CharStreams.readLines(toBufferedReader(input)); } /** * 读取一行数据,比如System.in的用户输入 */ public static String readLine(final InputStream input) throws IOException { return new BufferedReader(new InputStreamReader(input, Charsets.UTF_8)).readLine(); } /** * 读取一行数据 */ public static String readLine(final Reader reader) throws IOException { return toBufferedReader(reader).readLine(); } /** * 简单写入String到OutputStream. */ public static void write(final String data, final OutputStream output) throws IOException { if (data != null) { output.write(data.getBytes(Charsets.UTF_8)); } } /** * 简单写入String到Writer. */ public static void write(final String data, final Writer output) throws IOException { if (data != null) { output.write(data); } } /** * 字符串转换成InputStream */ public static InputStream toInputStream(String input) { return new ByteArrayInputStream(input.getBytes(Charsets.UTF_8)); } /** * 字符串转换成Reader */ public static Reader toInputStreamReader(String input) { return new InputStreamReader(toInputStream(input), Charsets.UTF_8); } /** * 在Reader与Writer间复制内容 * * @see CharStreams#copy */ public static long copy(final Reader input, final Writer output) throws IOException { return CharStreams.copy(input, output); } /** * 在InputStream与OutputStream间复制内容 * * @see ByteStreams#copy */ public static long copy(final InputStream input, final OutputStream output) throws IOException { return ByteStreams.copy(input, output); } public static BufferedReader toBufferedReader(final Reader reader) { return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/io/ResourceUtil.java ================================================ package com.vip.vjtools.vjkit.io; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import com.google.common.io.Resources; import com.vip.vjtools.vjkit.collection.ListUtil; import com.vip.vjtools.vjkit.reflect.ClassLoaderUtil; import com.vip.vjtools.vjkit.text.Charsets; /** * 针对Jar包内的文件的工具类. *

* 1.ClassLoader * * 不指定contextClass时,优先使用Thread.getContextClassLoader(), 如果ContextClassLoader未设置则使用Guava Resources类的ClassLoader * * 指定contextClass时,则直接使用该contextClass的ClassLoader. *

* 2.路径 * * 不指定contextClass时,按URLClassLoader的实现, 从jar file中查找resourceName, * * 所以resourceName无需以"/"打头即表示jar file中的根目录,带了"/" 反而导致JarFile.getEntry(resourceName)时没有返回. * * 指定contextClass时,class.getResource()会先对name进行处理再交给classLoader,打头的"/"的会被去除,不以"/"打头则表示与该contextClass package的相对路径, * 会先转为绝对路径. *

* 3.同名资源 * * 如果有多个同名资源,除非调用getResources()获取全部资源,否则在URLClassLoader中按ClassPath顺序打开第一个命中的Jar文件. */ public class ResourceUtil { // 打开单个文件//// /** * 读取规则见本类注释. */ public static URL asUrl(String resourceName) { return Resources.getResource(resourceName); } /** * 读取规则见本类注释. */ public static URL asUrl(Class contextClass, String resourceName) { return Resources.getResource(contextClass, resourceName); } /** * 读取规则见本类注释. */ public static InputStream asStream(String resourceName) throws IOException { return Resources.getResource(resourceName).openStream(); } /** * 读取文件的每一行,读取规则见本类注释. */ public static InputStream asStream(Class contextClass, String resourceName) throws IOException { return Resources.getResource(contextClass, resourceName).openStream(); } ////// 读取单个文件内容///// /** * 读取文件的每一行,读取规则见本类注释. */ public static String toString(String resourceName) throws IOException { return Resources.toString(Resources.getResource(resourceName), Charsets.UTF_8); } /** * 读取文件的每一行,读取规则见本类注释. */ public static String toString(Class contextClass, String resourceName) throws IOException { return Resources.toString(Resources.getResource(contextClass, resourceName), Charsets.UTF_8); } /** * 读取文件的每一行,读取规则见本类注释. */ public static List toLines(String resourceName) throws IOException { return Resources.readLines(Resources.getResource(resourceName), Charsets.UTF_8); } /** * 读取文件的每一行,读取规则见本类注释. */ public static List toLines(Class contextClass, String resourceName) throws IOException { return Resources.readLines(Resources.getResource(contextClass, resourceName), Charsets.UTF_8); } ///////////// 打开所有同名文件/////// public static List getResourcesQuietly(String resourceName) { return getResourcesQuietly(resourceName, ClassLoaderUtil.getDefaultClassLoader()); } public static List getResourcesQuietly(String resourceName, ClassLoader contextClassLoader) { try { Enumeration urls = contextClassLoader.getResources(resourceName); List list = new ArrayList(10); while (urls.hasMoreElements()) { list.add(urls.nextElement()); } return list; } catch (IOException e) {// NOSONAR return ListUtil.emptyList(); } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/io/URLResourceUtil.java ================================================ package com.vip.vjtools.vjkit.io; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; /** * 兼容文件url为无前缀, classpath:, file:// 三种方式的Resource读取工具集 * * e.g: classpath:com/myapp/config.xml, file:///data/config.xml, /data/config.xml * * 参考Spring ResourceUtils */ public class URLResourceUtil { private static final String CLASSPATH_PREFIX = "classpath:"; private static final String URL_PROTOCOL_FILE = "file"; /** * 兼容无前缀, classpath:, file:// 的情况获取文件 * * 如果以classpath: 定义的文件不存在会抛出IllegalArgumentException异常,以file://定义的则不会 */ public static File asFile(String generalPath) throws IOException { if (StringUtils.startsWith(generalPath, CLASSPATH_PREFIX)) { String resourceName = StringUtils.substringAfter(generalPath, CLASSPATH_PREFIX); return getFileByURL(ResourceUtil.asUrl(resourceName)); } try { // try URL return getFileByURL(new URL(generalPath)); } catch (MalformedURLException ex) { // no URL -> treat as file path return new File(generalPath); } } /** * 兼容无前缀, classpath:, file:// 的情况打开文件成Stream */ public static InputStream asStream(String generalPath) throws IOException { if (StringUtils.startsWith(generalPath, CLASSPATH_PREFIX)) { String resourceName = StringUtils.substringAfter(generalPath, CLASSPATH_PREFIX); return ResourceUtil.asStream(resourceName); } try { // try URL return FileUtil.asInputStream(getFileByURL(new URL(generalPath))); } catch (MalformedURLException ex) { // no URL -> treat as file path return FileUtil.asInputStream(generalPath); } } private static File getFileByURL(URL fileUrl) throws FileNotFoundException { Validate.notNull(fileUrl, "Resource URL must not be null"); if (!URL_PROTOCOL_FILE.equals(fileUrl.getProtocol())) { throw new FileNotFoundException("URL cannot be resolved to absolute file path " + "because it does not reside in the file system: " + fileUrl); } try { return new File(toURI(fileUrl.toString()).getSchemeSpecificPart()); } catch (URISyntaxException ex) { // NOSONAR // Fallback for URLs that are not valid URIs (should hardly ever happen). return new File(fileUrl.getFile()); } } public static URI toURI(String location) throws URISyntaxException { return new URI(StringUtils.replace(location, " ", "%20")); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/io/type/StringBuilderWriter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.vip.vjtools.vjkit.io.type; import java.io.Serializable; import java.io.Writer; /** * JDK的java.io.StringWriter使用StringBuffer,移植Commons IO使用StringBuilder的版本. * * https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java * * {@link Writer} implementation that outputs to a {@link StringBuilder}. *

* NOTE: This implementation, as an alternative to java.io.StringWriter, provides an * un-synchronized (i.e. for use in a single thread) implementation for better performance. For safe usage with * multiple {@link Thread}s then java.io.StringWriter should be used. * * @since 2.0 */ public class StringBuilderWriter extends Writer implements Serializable { private static final long serialVersionUID = -146927496096066153L; private final StringBuilder builder; /** * Constructs a new {@link StringBuilder} instance with default capacity. */ public StringBuilderWriter() { this.builder = new StringBuilder(); } /** * Constructs a new {@link StringBuilder} instance with the specified capacity. * * @param capacity The initial capacity of the underlying {@link StringBuilder} */ public StringBuilderWriter(final int capacity) { this.builder = new StringBuilder(capacity); } /** * Constructs a new instance with the specified {@link StringBuilder}. * *

* If {@code builder} is null a new instance with default capacity will be created. *

* * @param builder The String builder. May be null. */ public StringBuilderWriter(final StringBuilder builder) { this.builder = builder != null ? builder : new StringBuilder(); } /** * Appends a single character to this Writer. * * @param value The character to append * @return This writer instance */ @Override public Writer append(final char value) { builder.append(value); return this; } /** * Appends a character sequence to this Writer. * * @param value The character to append * @return This writer instance */ @Override public Writer append(final CharSequence value) { builder.append(value); return this; } /** * Appends a portion of a character sequence to the {@link StringBuilder}. * * @param value The character to append * @param start The index of the first character * @param end The index of the last character + 1 * @return This writer instance */ @Override public Writer append(final CharSequence value, final int start, final int end) { builder.append(value, start, end); return this; } /** * Closing this writer has no effect. */ @Override public void close() { // no-op } /** * Flushing this writer has no effect. */ @Override public void flush() { // no-op } /** * Writes a String to the {@link StringBuilder}. * * @param value The value to write */ @Override public void write(final String value) { if (value != null) { builder.append(value); } } /** * Writes a portion of a character array to the {@link StringBuilder}. * * @param value The value to write * @param offset The index of the first character * @param length The number of characters to write */ @Override public void write(final char[] value, final int offset, final int length) { if (value != null) { builder.append(value, offset, length); } } /** * Returns the underlying builder. * * @return The underlying builder */ public StringBuilder getBuilder() { return builder; } /** * Returns {@link StringBuilder#toString()}. * * @return The contents of the String builder. */ @Override public String toString() { return builder.toString(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/logging/PerformanceUtil.java ================================================ package com.vip.vjtools.vjkit.logging; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; /** * 性能日志工具 */ public class PerformanceUtil { private PerformanceUtil() { } // 全局共享ThreadLocal Timer private static ThreadLocal localTimer = new ThreadLocal() { @Override protected Timer initialValue() { return new Timer(); } }; // 按Key定义多个ThreadLocal Timer private static ThreadLocal> localTimerMap = new ThreadLocal>() { @Override protected Map initialValue() { return new HashMap(); } }; /** * 记录开始时间 */ public static void start() { localTimer.get().start(); } /** * 返回开始到现在的时间 */ public static long duration() { return localTimer.get().duration(); } /** * 记录结束时间 */ public static long end() { long duration = localTimer.get().duration(); localTimer.remove(); return duration; } /** * 记录特定Timer的开始时间 */ public static void start(String key) { getTimer(key).start(); } /** * 记录特定Timer的开始时间 */ public static long duration(String key) { return getTimer(key).duration(); } /** * 记录特定Timer结束时间,返回耗时 */ public static long end(String key) { long duration = getTimer(key).duration(); localTimerMap.get().remove(key); return duration; } /** * 清除所有ThreadLocal Timer */ public static void removeAll() { localTimer.remove(); localTimerMap.remove(); } /** * 当处理时间超过预定的阈值时发出警告信息 * @param logger * @param duration * @param threshold 阈值(单位:ms) */ public static void slowLog(Logger logger, long duration, long threshold) { if (duration > threshold) { logger.warn("[Performance Warning] use {}ms, slow than {}ms", duration, threshold); } } /** * 当处理时间超过预定的阈值时发出警告信息 * @param logger * @param key * @param threshold 阈值(单位:ms) */ public static void slowLog(Logger logger, String key, long duration, long threshold) { if (duration > threshold) { logger.warn("[Performance Warning] task {} use {}ms, slow than {}ms", key, duration, threshold); } } /** * 当处理时间超过预定的阈值时发出警告信息 * * @param logger 写日志的logger * @param threshold 阈值(单位:ms) * @param context 需要记录的context信息,如请求的json等 */ public static void slowLog(Logger logger, long duration, long threshold, String context) { if (duration > threshold) { logger.warn("[Performance Warning] use {}ms, slow than {}ms, context={}", duration, threshold, context); } } /** * 当处理时间超过预定的阈值时发出警告信息 * @param logger * @param key * @param threshold 阈值(单位:ms) * @param context 需要记录的context信息,如请求的json等 */ public static void slowLog(Logger logger, String key, long duration, long threshold, String context) { if (duration > threshold) { logger.warn("[Performance Warning] task {} use {}ms, slow than {}ms, contxt={}", key, duration, threshold, context); } } /** * 记录结束时间并当处理时间超过预定的阈值时发出警告信息,最后清除 * @param logger * @param threshold 阈值(单位:ms) */ public static void endWithSlowLog(Logger logger, long threshold) { slowLog(logger, end(), threshold); } /** * 记录结束时间并当处理时间超过预定的阈值时发出警告信息,最后清除 * @param logger * @param key * @param threshold 阈值(单位:ms) */ public static void endWithSlowLog(Logger logger, String key, long threshold) { slowLog(logger, key, end(key), threshold); } /** * 记录结束时间并当处理时间超过预定的阈值时发出警告信息,最后清除 * @param logger * @param threshold 阈值(单位:ms) * @param context 需要记录的context信息,如请求的json等 */ public static void endWithSlowLog(Logger logger, long threshold, String context) { slowLog(logger, end(), threshold, context); } /** * 记录结束时间并当处理时间超过预定的阈值时发出警告信息,最后清除 * @param logger * @param key * @param threshold 阈值(单位:ms) * @param context 需要记录的context信息,如请求的json等 */ public static void endWithSlowLog(Logger logger, String key, long threshold, String context) { slowLog(logger, key, end(key), threshold, context); } private static Timer getTimer(String key) { Map map = localTimerMap.get(); Timer timer = map.get(key); if (timer == null) { timer = new Timer(); map.put(key, timer); } return timer; } static class Timer { private long start; public void start() { start = System.currentTimeMillis(); } public long duration() { return System.currentTimeMillis() - start; } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/mapper/BeanMapper.java ================================================ package com.vip.vjtools.vjkit.mapper; import java.util.ArrayList; import java.util.List; import org.dozer.DozerBeanMapper; import org.dozer.Mapper; import com.vip.vjtools.vjkit.collection.ArrayUtil; /** * 实现深度的BeanOfClassA<->BeanOfClassB复制 * * 不要使用Apache Common BeanUtils进行类复制,每次就行反射查询对象的属性列表, 非常缓慢. * * orika性能比Dozer快近十倍,也不需要Getter函数与无参构造函数 * * 但我们内部修复了的bug,社区版没有修复: https://github.com/orika-mapper/orika/issues/252 * * 如果应用启动时有并发流量进入,可能导致两个不同类型的同名属性间(如Order的User user属性,与OrderVO的UserVO user)的复制失败,只有重启才能解决。 * * 因此安全起见,在vjkit的开源版本中仍然使用Dozer。 * * Dozer最新是6.x版,但only for JDK8,为兼容JDK7这里仍使用5.x版本。 * * 注意: 需要参考POM文件,显式引用Dozer. */ public class BeanMapper { private static Mapper mapper = new DozerBeanMapper(); /** * 简单的复制出新类型对象. */ public static D map(S source, Class destinationClass) { return mapper.map(source, destinationClass); } /** * 简单的复制出新对象ArrayList */ public static List mapList(Iterable sourceList, Class destinationClass) { List destinationList = new ArrayList(); for (S source : sourceList) { if (source != null) { destinationList.add(mapper.map(source, destinationClass)); } } return destinationList; } /** * 简单复制出新对象数组 */ public static D[] mapArray(final S[] sourceArray, final Class destinationClass) { D[] destinationArray = ArrayUtil.newArray(destinationClass, sourceArray.length); int i = 0; for (S source : sourceArray) { if (source != null) { destinationArray[i] = mapper.map(sourceArray[i], destinationClass); i++; } } return destinationArray; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/mapper/JsonMapper.java ================================================ package com.vip.vjtools.vjkit.mapper; import java.io.IOException; import java.util.Collection; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.util.JSONPObject; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 简单封装Jackson,实现JSON String<->Java Object转换的Mapper. * * 可以直接使用公共示例JsonMapper.INSTANCE, 也可以使用不同的builder函数创建实例,封装不同的输出风格, * * 不要使用GSON, 在对象稍大时非常缓慢. * * 注意: 需要参考本模块的POM文件,显式引用jackson. * * @author calvin */ public class JsonMapper { private static Logger logger = LoggerFactory.getLogger(JsonMapper.class); public static final JsonMapper INSTANCE = new JsonMapper(); private ObjectMapper mapper; public JsonMapper() { this(null); } public JsonMapper(Include include) { mapper = new ObjectMapper(); // 设置输出时包含属性的风格 if (include != null) { mapper.setSerializationInclusion(include); } // 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性 mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); } /** * 创建只输出非Null的属性到Json字符串的Mapper. */ public static JsonMapper nonNullMapper() { return new JsonMapper(Include.NON_NULL); } /** * 创建只输出非Null且非Empty(如List.isEmpty)的属性到Json字符串的Mapper. * * 注意,要小心使用, 特别留意empty的情况. */ public static JsonMapper nonEmptyMapper() { return new JsonMapper(Include.NON_EMPTY); } /** * 默认的全部输出的Mapper, 区别于INSTANCE,可以做进一步的配置 */ public static JsonMapper defaultMapper() { return new JsonMapper(); } /** * Object可以是POJO,也可以是Collection或数组。 如果对象为Null, 返回"null". 如果集合为空集合, 返回"[]". */ public String toJson(Object object) { try { return mapper.writeValueAsString(object); } catch (IOException e) { logger.warn("write to json string error:" + object, e); return null; } } /** * 反序列化POJO或简单Collection如List. * * 如果JSON字符串为Null或"null"字符串, 返回Null. 如果JSON字符串为"[]", 返回空集合. * * 如需反序列化复杂Collection如List, 请使用fromJson(String, JavaType) * * @see #fromJson(String, JavaType) */ public T fromJson(@Nullable String jsonString, Class clazz) { if (StringUtils.isEmpty(jsonString)) { return null; } try { return mapper.readValue(jsonString, clazz); } catch (IOException e) { logger.warn("parse json string error:" + jsonString, e); return null; } } /** * 反序列化复杂Collection如List, constructCollectionType()或constructMapType()构造类型, 然后调用本函数. * * @see #createCollectionType(Class, Class...) */ public T fromJson(@Nullable String jsonString, JavaType javaType) { if (StringUtils.isEmpty(jsonString)) { return null; } try { return (T) mapper.readValue(jsonString, javaType); } catch (IOException e) { logger.warn("parse json string error:" + jsonString, e); return null; } } /** * 构造Collection类型. */ public JavaType buildCollectionType(Class collectionClass, Class elementClass) { return mapper.getTypeFactory().constructCollectionType(collectionClass, elementClass); } /** * 构造Map类型. */ public JavaType buildMapType(Class mapClass, Class keyClass, Class valueClass) { return mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass); } /** * 当JSON里只含有Bean的部分属性時,更新一個已存在Bean,只覆盖該部分的属性. */ public void update(String jsonString, Object object) { try { mapper.readerForUpdating(object).readValue(jsonString); } catch (JsonProcessingException e) { logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e); } catch (IOException e) { logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e); } } /** * 輸出JSONP格式數據. */ public String toJsonP(String functionName, Object object) { return toJson(new JSONPObject(functionName, object)); } /** * 設定是否使用Enum的toString函數來讀寫Enum, 為False時時使用Enum的name()函數來讀寫Enum, 默認為False. 注意本函數一定要在Mapper創建後, 所有的讀寫動作之前調用. */ public void enableEnumUseToString() { mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); } /** * 取出Mapper做进一步的设置或使用其他序列化API. */ public ObjectMapper getMapper() { return mapper; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/mapper/XmlMapper.java ================================================ package com.vip.vjtools.vjkit.mapper; import java.io.StringReader; import java.io.StringWriter; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlAnyElement; import javax.xml.namespace.QName; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import com.vip.vjtools.vjkit.base.ExceptionUtil; import com.vip.vjtools.vjkit.reflect.ClassUtil; /** * 使用Jaxb2.0实现XML<->Java Object的Mapper. * * 在创建时需要设定所有需要序列化的Root对象的Class. * 特别支持Root对象是Collection的情形. * * @author calvin */ public class XmlMapper { private static ConcurrentMap jaxbContexts = new ConcurrentHashMap(); /** * Java Object->Xml without encoding. */ public static String toXml(Object root) { Class clazz = ClassUtil.unwrapCglib(root); return toXml(root, clazz, null); } /** * Java Object->Xml with encoding. */ public static String toXml(Object root, String encoding) { Class clazz = ClassUtil.unwrapCglib(root); return toXml(root, clazz, encoding); } /** * Java Object->Xml with encoding. */ public static String toXml(Object root, Class clazz, String encoding) { try { StringWriter writer = new StringWriter(); createMarshaller(clazz, encoding).marshal(root, writer); return writer.toString(); } catch (JAXBException e) { throw ExceptionUtil.unchecked(e); } } /** * Java Collection->Xml without encoding, 特别支持Root Element是Collection的情形. */ public static String toXml(Collection root, String rootName, Class clazz) { return toXml(root, rootName, clazz, null); } /** * Java Collection->Xml with encoding, 特别支持Root Element是Collection的情形. */ public static String toXml(Collection root, String rootName, Class clazz, String encoding) { try { CollectionWrapper wrapper = new CollectionWrapper(); wrapper.collection = root; JAXBElement wrapperElement = new JAXBElement(new QName(rootName), CollectionWrapper.class, wrapper); StringWriter writer = new StringWriter(); createMarshaller(clazz, encoding).marshal(wrapperElement, writer); return writer.toString(); } catch (JAXBException e) { throw ExceptionUtil.unchecked(e); } } /** * Xml->Java Object. */ public static T fromXml(String xml, Class clazz) { try { StringReader reader = new StringReader(xml); return (T) createUnmarshaller(clazz).unmarshal(reader); } catch (JAXBException e) { throw ExceptionUtil.unchecked(e); } } /** * 创建Marshaller并设定encoding(可为null). * 线程不安全,需要每次创建或pooling。 */ public static Marshaller createMarshaller(Class clazz, String encoding) { try { JAXBContext jaxbContext = getJaxbContext(clazz); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); if (StringUtils.isNotBlank(encoding)) { marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding); } return marshaller; } catch (JAXBException e) { throw ExceptionUtil.unchecked(e); } } /** * 创建UnMarshaller. * 线程不安全,需要每次创建或pooling。 */ public static Unmarshaller createUnmarshaller(Class clazz) { try { JAXBContext jaxbContext = getJaxbContext(clazz); return jaxbContext.createUnmarshaller(); } catch (JAXBException e) { throw ExceptionUtil.unchecked(e); } } protected static JAXBContext getJaxbContext(Class clazz) { Validate.notNull(clazz, "'clazz' must not be null"); JAXBContext jaxbContext = jaxbContexts.get(clazz); if (jaxbContext == null) { try { jaxbContext = JAXBContext.newInstance(clazz, CollectionWrapper.class); jaxbContexts.putIfAbsent(clazz, jaxbContext); } catch (JAXBException ex) { throw new RuntimeException( "Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); } } return jaxbContext; } /** * 封装Root Element 是 Collection的情况. */ public static class CollectionWrapper { @XmlAnyElement protected Collection collection; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/net/IPUtil.java ================================================ package com.vip.vjtools.vjkit.net; import java.net.Inet4Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.List; import com.google.common.net.InetAddresses; import com.vip.vjtools.vjkit.number.NumberUtil; import com.vip.vjtools.vjkit.text.MoreStringUtil; /** * InetAddress工具类,基于Guava的InetAddresses. * * 主要包含int, String/IPV4String, InetAdress/Inet4Address之间的互相转换 * * 先将字符串传换为byte[]再用InetAddress.getByAddress(byte[]),避免了InetAddress.getByName(ip)可能引起的DNS访问. * * InetAddress与String的转换其实消耗不小,如果是有限的地址,建议进行缓存. */ public class IPUtil { /** * 从InetAddress转化到int, 传输和存储时, 用int代表InetAddress是最小的开销. * * InetAddress可以是IPV4或IPV6,都会转成IPV4. * * @see com.google.common.net.InetAddresses#coerceToInteger(InetAddress) */ public static int toInt(InetAddress address) { return InetAddresses.coerceToInteger(address); } /** * InetAddress转换为String. * * InetAddress可以是IPV4或IPV6. 其中IPV4直接调用getHostAddress() * * @see com.google.common.net.InetAddresses#toAddrString(InetAddress) */ public static String toIpString(InetAddress address) { return InetAddresses.toAddrString(address); } /** * 从int转换为Inet4Address(仅支持IPV4) */ public static Inet4Address fromInt(int address) { return InetAddresses.fromInteger(address); } /** * 从String转换为InetAddress. * * IpString可以是ipv4 或 ipv6 string, 但不可以是域名. * * 先字符串传换为byte[]再调getByAddress(byte[]),避免了调用getByName(ip)可能引起的DNS访问. */ public static InetAddress fromIpString(String address) { return InetAddresses.forString(address); } /** * 从IPv4String转换为InetAddress. * * IpString如果确定ipv4, 使用本方法减少字符分析消耗 . * * 先字符串传换为byte[]再调getByAddress(byte[]),避免了调用getByName(ip)可能引起的DNS访问. */ public static Inet4Address fromIpv4String(String address) { byte[] bytes = ip4StringToBytes(address); if (bytes == null) { return null; } else { try { return (Inet4Address) Inet4Address.getByAddress(bytes); } catch (UnknownHostException e) { throw new AssertionError(e); } } } /** * int转换到IPV4 String, from Netty NetUtil */ public static String intToIpv4String(int i) { return new StringBuilder(15).append((i >> 24) & 0xff).append('.').append((i >> 16) & 0xff).append('.') .append((i >> 8) & 0xff).append('.').append(i & 0xff).toString(); } /** * Ipv4 String 转换到int */ public static int ipv4StringToInt(String ipv4Str) { byte[] byteAddress = ip4StringToBytes(ipv4Str); if (byteAddress == null) { return 0; } else { return NumberUtil.toInt(byteAddress); } } /** * Ipv4 String 转换到byte[] */ private static byte[] ip4StringToBytes(String ipv4Str) { if (ipv4Str == null) { return null; } List it = MoreStringUtil.split(ipv4Str, '.', 4); if (it.size() != 4) { return null; } byte[] byteAddress = new byte[4]; for (int i = 0; i < 4; i++) { int tempInt = Integer.parseInt(it.get(i)); if (tempInt > 255) { return null; } byteAddress[i] = (byte) tempInt; } return byteAddress; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/net/NetUtil.java ================================================ package com.vip.vjtools.vjkit.net; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.ServerSocket; import java.net.SocketException; import java.net.UnknownHostException; import java.util.Enumeration; import java.util.Map; import java.util.Random; import javax.net.ServerSocketFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.vip.vjtools.vjkit.base.Platforms; import com.vip.vjtools.vjkit.base.SystemPropertiesUtil; import com.vip.vjtools.vjkit.collection.MapUtil; /** * 关于网络的工具类. * * 1. 获取本机IP地址与HostName * * 2. 查找空闲端口 */ @Beta public class NetUtil { private static Logger logger = LoggerFactory.getLogger(NetUtil.class); public static final int PORT_RANGE_MIN = 1024; public static final int PORT_RANGE_MAX = 65535; private static Random random = new Random(); /////// LocalAddress ////// /** * 获得本地地址 */ public static InetAddress getLocalAddress() { return LocalAddressHoler.INSTANCE.localInetAddress; } /** * 获得本地Ip地址 */ public static String getLocalHost() { return LocalAddressHoler.INSTANCE.localHost; } /** * 获得本地HostName */ public static String getHostName() { return LocalAddressHoler.INSTANCE.hostName; } /** * 懒加载进行探测 */ private static class LocalAddressHoler { static final LocalAddress INSTANCE = new LocalAddress(); } private static class LocalAddress { private InetAddress localInetAddress; private String localHost; private String hostName; public LocalAddress() { initLocalAddress(); // from Common Lang SystemUtils hostName = Platforms.IS_WINDOWS ? System.getenv("COMPUTERNAME") : System.getenv("HOSTNAME"); } /** * 初始化本地地址 */ private void initLocalAddress() { NetworkInterface nic = null; // 根据命令行执行hostname获得本机hostname, 与/etc/hosts 中该hostname的第一条ip配置,获得ip地址 try { localInetAddress = InetAddress.getLocalHost(); nic = NetworkInterface.getByInetAddress(localInetAddress); } catch (Exception ignored) { // NOSONAR } // 如果结果为空,或是一个loopback地址(127.0.0.1), 或是ipv6地址,再遍历网卡尝试获取 if (localInetAddress == null || nic == null || localInetAddress.isLoopbackAddress() || localInetAddress instanceof Inet6Address) { InetAddress lookedUpAddr = findLocalAddressViaNetworkInterface(); // 仍然不符合要求,只好使用127.0.0.1 try { localInetAddress = lookedUpAddr != null ? lookedUpAddr : InetAddress.getByName("127.0.0.1"); } catch (UnknownHostException ignored) {// NOSONAR } } localHost = IPUtil.toIpString(localInetAddress); logger.info("localhost is {}", localHost); } /** * 根据preferNamePrefix 与 defaultNicList的配置网卡,找出合适的网卡 */ private static InetAddress findLocalAddressViaNetworkInterface() { // 如果hostname +/etc/hosts 得到的是127.0.0.1, 则首选这块网卡 String preferNamePrefix = SystemPropertiesUtil.getString("localhost.prefer.nic.prefix", "LOCALHOST_PREFER_NIC_PREFIX", "bond0."); // 如果hostname +/etc/hosts 得到的是127.0.0.1, 和首选网卡都不符合要求,则按顺序遍历下面的网卡 String defaultNicList = SystemPropertiesUtil.getString("localhost.default.nic.list", "LOCALHOST_DEFAULT_NIC_LIST", "bond0,eth0,em0,br0"); InetAddress resultAddress = null; Map candidateInterfaces = MapUtil.newHashMap(); // 遍历所有网卡,找出所有可用网卡,尝试找出符合prefer前缀的网卡 try { for (Enumeration allInterfaces = NetworkInterface .getNetworkInterfaces(); allInterfaces.hasMoreElements();) { NetworkInterface nic = allInterfaces.nextElement(); // 检查网卡可用并支持广播 try { if (!nic.isUp() || !nic.supportsMulticast()) { continue; } } catch (SocketException ignored) { // NOSONAR continue; } // 检查是否符合prefer前缀 String name = nic.getName(); if (name.startsWith(preferNamePrefix)) { // 检查有否非ipv6 非127.0.0.1的inetaddress resultAddress = findAvailableInetAddress(nic); if (resultAddress != null) { return resultAddress; } } else { // 不是Prefer前缀,先放入可选列表 candidateInterfaces.put(name, nic); } } for (String nifName : defaultNicList.split(",")) { NetworkInterface nic = candidateInterfaces.get(nifName); if (nic != null) { resultAddress = findAvailableInetAddress(nic); if (resultAddress != null) { return resultAddress; } } } } catch (SocketException e) {// NOSONAR return null; } return null; } /** * 检查有否非ipv6,非127.0.0.1的inetaddress */ private static InetAddress findAvailableInetAddress(NetworkInterface nic) { for (Enumeration indetAddresses = nic.getInetAddresses(); indetAddresses.hasMoreElements();) { InetAddress inetAddress = indetAddresses.nextElement(); if (!(inetAddress instanceof Inet6Address) && !inetAddress.isLoopbackAddress()) { return inetAddress; } } return null; } } /////////// 查找空闲端口 ///////// /** * 测试端口是否空闲可用, from Spring SocketUtils */ public static boolean isPortAvailable(int port) { try { ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost")); serverSocket.close(); return true; } catch (Exception ex) { // NOSONAR return false; } } /** * 从1024到65535, 随机找一个空闲端口 from Spring SocketUtils */ public static int findRandomAvailablePort() { return findRandomAvailablePort(PORT_RANGE_MIN, PORT_RANGE_MAX); } /** * 在范围里随机找一个空闲端口,from Spring SocketUtils. * * @throws IllegalStateException 最多尝试(maxPort-minPort)次,如无空闲端口,抛出此异常. */ public static int findRandomAvailablePort(int minPort, int maxPort) { int portRange = maxPort - minPort; int candidatePort; int searchCounter = 0; do { if (++searchCounter > portRange) { throw new IllegalStateException( String.format("Could not find an available tcp port in the range [%d, %d] after %d attempts", minPort, maxPort, searchCounter)); } candidatePort = minPort + random.nextInt(portRange + 1); } while (!isPortAvailable(candidatePort)); return candidatePort; } /** * 从某个端口开始,递增直到65535,找一个空闲端口. * * @throws IllegalStateException 范围内如无空闲端口,抛出此异常 */ public static int findAvailablePortFrom(int minPort) { for (int port = minPort; port < PORT_RANGE_MAX; port++) { if (isPortAvailable(port)) { return port; } } throw new IllegalStateException( String.format("Could not find an available tcp port in the range [%d, %d]", minPort, PORT_RANGE_MAX)); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/number/MathUtil.java ================================================ package com.vip.vjtools.vjkit.number; import java.math.RoundingMode; import com.google.common.math.IntMath; import com.google.common.math.LongMath; /** * 数学相关工具类.包括 * * 1. 2的倍数的计算 * * 2. 其他函数如安全的取模,可控制取整方向的相除,乘方,开方等。 */ public class MathUtil { /////// 2 的倍数的计算//// /** * 往上找出最接近的2的倍数,比如15返回16, 17返回32. * * @param value 必须为正数,否则抛出异常. */ public static int nextPowerOfTwo(int value) { return IntMath.ceilingPowerOfTwo(value); } /** * 往上找出最接近的2的倍数,比如15返回16, 17返回32. * * @param value 必须为正数,否则抛出异常. */ public static long nextPowerOfTwo(long value) { return LongMath.ceilingPowerOfTwo(value); } /** * 往下找出最接近2的倍数,比如15返回8, 17返回16. * * @param value 必须为正数,否则抛出异常. */ public static int previousPowerOfTwo(int value) { return IntMath.floorPowerOfTwo(value); } /** * 往下找出最接近2的倍数,比如15返回8, 17返回16. * * @param value 必须为正数,否则抛出异常. */ public static long previousPowerOfTwo(long value) { return LongMath.floorPowerOfTwo(value); } /** * 是否2的倍数 * * @param value 不是正数时总是返回false */ public static boolean isPowerOfTwo(int value) { return IntMath.isPowerOfTwo(value); } /** * 是否2的倍数 * * @param value <=0 时总是返回false */ public static boolean isPowerOfTwo(long value) { return LongMath.isPowerOfTwo(value); } /** * 当模为2的倍数时,用比取模块更快的方式计算. * * @param value 可以为负数,比如 -1 mod 16 = 15 */ public static int modByPowerOfTwo(int value, int mod) { return value & (mod - 1); } ////////////// 其他函数////////// /** * 保证结果为正数的取模. * * 如果(v = x/m) <0,v+=m. */ public static int mod(int x, int m) { return IntMath.mod(x, m); } /** * 保证结果为正数的取模. * * 如果(v = x/m) <0,v+=m. */ public static long mod(long x, long m) { return LongMath.mod(x, m); } /** * 保证结果为正数的取模 */ public static int mod(long x, int m) { return LongMath.mod(x, m); } /** * 能控制rounding方向的int相除. * * jdk的'/'运算符,直接向下取整 */ public static int divide(int p, int q, RoundingMode mode) { return IntMath.divide(p, q, mode); } /** * 能控制rounding方向的long相除 * * jdk的'/'运算符,直接向下取整 */ public static long divide(long p, long q, RoundingMode mode) { return LongMath.divide(p, q, mode); } /** * 平方 * * @param k 平方次数, 不能为负数, k=0时返回1. */ public static int pow(int b, int k) { return IntMath.pow(b, k); } /** * 平方 * * @param k 平方次数,不能为负数, k=0时返回1. */ public static long pow(long b, int k) { return LongMath.pow(b, k); } /** * 开方 */ public static int sqrt(int x, RoundingMode mode) { return IntMath.sqrt(x, mode); } /** * 开方 */ public static long sqrt(long x, RoundingMode mode) { return LongMath.sqrt(x, mode); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/number/MoneyUtil.java ================================================ package com.vip.vjtools.vjkit.number; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.ParseException; import org.apache.commons.lang3.StringUtils; /** * 货币工具类. * * 1.元和分之间的转换 * 2.货币格式化成字符串 * 3.字符串转换成货币 * */ public class MoneyUtil { private static final ThreadLocal DEFAULT_FORMAT = createThreadLocalNumberformat("0.00"); private static final ThreadLocal PRETTY_FORMAT = createThreadLocalNumberformat("#,##0.00"); // ThreadLocal重用MessageDigest private static ThreadLocal createThreadLocalNumberformat(final String pattern) { return new ThreadLocal() { @Override protected DecimalFormat initialValue() { DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(); df.applyPattern(pattern); return df; } }; } /////////////////// 元和分的转换 /////////////////// /** * 人民币金额单位转换,分转换成元,取两位小数 例如:150 => 1.5 */ public static BigDecimal fen2yuan(BigDecimal num) { return num.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP); } /** * 人民币金额单位转换,分转换成元,取两位小数 例如:150 => 1.5 */ public static BigDecimal fen2yuan(long num) { return fen2yuan(new BigDecimal(num)); } /** * 人民币金额单位转换,分转换成元,取两位小数 例如:150 => 1.5 */ public static BigDecimal fen2yuan(String num) { if (StringUtils.isEmpty(num)) { return new BigDecimal(0); } return fen2yuan(new BigDecimal(num)); } /** * 人民币金额单位转换,元转换成分,例如:1 => 100 */ public static BigDecimal yuan2fen(String y) { return new BigDecimal(Math.round(new BigDecimal(y).multiply(new BigDecimal(100)).doubleValue())); } /** * 人民币金额单位转换,元转换成分,例如:1 => 100 */ public static BigDecimal yuan2fen(double y) { return yuan2fen(String.valueOf(y)); } /** * 人民币金额单位转换,元转换成分,例如:1 => 100 */ public static BigDecimal yuan2fen(BigDecimal y) { if (y != null) { return yuan2fen(y.toString()); } else { return new BigDecimal(0); } } ////////////////// 格式化输出 ////////////////// /** * 格式化金额,例如:1=>1.00 */ public static String format(BigDecimal number) { return format(number.doubleValue()); } /** * 格式化金额,默认格式:00.0 ,例如:1=>1.00 */ public static String format(double number) { return DEFAULT_FORMAT.get().format(number); } /** * 格式化金额,默认格式:#,##0.00 ,例如:33999999932.3333d 输出:33,999,999,932.33 */ public static String prettyFormat(BigDecimal number) { return prettyFormat(number.doubleValue()); } /** * 格式化金额,默认格式:#,##0.00 ,例如:33999999932.3333d 输出:33,999,999,932.33 */ public static String prettyFormat(double number) { return PRETTY_FORMAT.get().format(number); } /** * 格式化金额,当pattern为空时,pattern默认为#,##0.00 */ public static String format(BigDecimal number, String pattern) { return format(number.doubleValue(), pattern); } /** * 格式化金额,当pattern为空时,pattern默认为#,##0.00 */ public static String format(double number, String pattern) { DecimalFormat df = null; if (StringUtils.isEmpty(pattern)) { df = PRETTY_FORMAT.get(); } else { df = (DecimalFormat) DecimalFormat.getInstance(); df.applyPattern(pattern); } return df.format(number); } /////////////// 转换金额字符串为金额////////// /** * 分析格式为0.00格式的字符串 */ public static BigDecimal parseString(String numberStr) throws ParseException { return new BigDecimal(DEFAULT_FORMAT.get().parse(numberStr).doubleValue()); } /** * 分析格式为#,##0.00格式的字符串 */ public static BigDecimal parsePrettyString(String numberStr) throws ParseException { return new BigDecimal(PRETTY_FORMAT.get().parse(numberStr).doubleValue()); } /** * 按格式分析字符串,当pattern为空时,pattern默认为#,##0.00 */ public static BigDecimal parseString(String numberStr, String pattern) throws ParseException { DecimalFormat df = null; if (StringUtils.isEmpty(pattern)) { df = PRETTY_FORMAT.get(); } else { df = (DecimalFormat) DecimalFormat.getInstance(); df.applyPattern(pattern); } return new BigDecimal(df.parse(numberStr).doubleValue()); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/number/NumberUtil.java ================================================ package com.vip.vjtools.vjkit.number; import java.util.Locale; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.vip.vjtools.vjkit.base.annotation.NotNull; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 数字的工具类. * * 1.原始类型数字与byte[]的双向转换(via Guava) * * 2.判断字符串是否数字, 是否16进制字符串(via Common Lang) * * 3.10机制/16进制字符串 与 原始类型数字/数字对象 的双向转换(参考Common Lang自写) */ public class NumberUtil { private static final double DEFAULT_DOUBLE_EPSILON = 0.00001d; /** * 因为double的精度问题, 允许两个double在0.00001内的误差为相等。 */ public static boolean equalsWithin(double d1, double d2) { return Math.abs(d1 - d2) < DEFAULT_DOUBLE_EPSILON; } /** * 因为double的精度问题, 允许两个double在epsilon内的误差为相等 */ public static boolean equalsWithin(double d1, double d2, double epsilon) { return Math.abs(d1 - d2) < epsilon; } ///////////// bytes[] 与原始类型数字转换 /////// public static byte[] toBytes(int value) { return Ints.toByteArray(value); } public static byte[] toBytes(long value) { return Longs.toByteArray(value); } /** * copy from ElasticSearch Numbers */ public static byte[] toBytes(double val) { return toBytes(Double.doubleToRawLongBits(val)); } public static int toInt(byte[] bytes) { return Ints.fromByteArray(bytes); } public static long toLong(byte[] bytes) { return Longs.fromByteArray(bytes); } /** * copy from ElasticSearch Numbers */ public static double toDouble(byte[] bytes) { return Double.longBitsToDouble(toLong(bytes)); } /////// 判断字符串类型////////// /** * 判断字符串是否合法数字 */ public static boolean isNumber(@Nullable String str) { return NumberUtils.isCreatable(str); } /** * 判断字符串是否16进制 */ public static boolean isHexNumber(@Nullable String value) { if (StringUtils.isEmpty(value)) { return false; } int index = value.startsWith("-") ? 1 : 0; return value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index); } /////////// 将字符串转化为原始类型数字///////// /** * 将10进制的String转化为int. * * 当str为空或非数字字符串时抛NumberFormatException */ public static int toInt(@NotNull String str) { return Integer.parseInt(str); } /** * 将10进制的String安全的转化为int. * * 当str为空或非数字字符串时,返回default值. */ public static int toInt(@Nullable String str, int defaultValue) { return NumberUtils.toInt(str, defaultValue); } /** * 将10进制的String安全的转化为long. * * 当str或非数字字符串时抛NumberFormatException */ public static long toLong(@NotNull String str) { return Long.parseLong(str); } /** * 将10进制的String安全的转化为long. * * 当str为空或非数字字符串时,返回default值 */ public static long toLong(@Nullable String str, long defaultValue) { return NumberUtils.toLong(str, defaultValue); } /** * 将10进制的String安全的转化为double. * * 当str为空或非数字字符串时抛NumberFormatException */ public static double toDouble(@NotNull String str) { // 统一行为,不要有时候抛NPE,有时候抛NumberFormatException if (str == null) { throw new NumberFormatException("null"); } return Double.parseDouble(str); } /** * 将10进制的String安全的转化为double. * * 当str为空或非数字字符串时,返回default值 */ public static double toDouble(@Nullable String str, double defaultValue) { return NumberUtils.toDouble(str, defaultValue); } ////////////// 10进制字符串 转换对象类型数字///////////// /** * 将10进制的String安全的转化为Integer. * * 当str为空或非数字字符串时抛NumberFormatException */ public static Integer toIntObject(@NotNull String str) { return Integer.valueOf(str); } /** * 将10进制的String安全的转化为Integer. * * 当str为空或非数字字符串时,返回default值 */ public static Integer toIntObject(@Nullable String str, Integer defaultValue) { if (StringUtils.isEmpty(str)) { return defaultValue; } try { return Integer.valueOf(str); } catch (final NumberFormatException nfe) { return defaultValue; } } /** * 将10进制的String安全的转化为Long. * * 当str为空或非数字字符串时抛NumberFormatException */ public static Long toLongObject(@NotNull String str) { return Long.valueOf(str); } /** * 将10进制的String安全的转化为Long. * * 当str为空或非数字字符串时,返回default值 */ public static Long toLongObject(@Nullable String str, Long defaultValue) { if (StringUtils.isEmpty(str)) { return defaultValue; } try { return Long.valueOf(str); } catch (final NumberFormatException nfe) { return defaultValue; } } /** * 将10进制的String安全的转化为Double. * * 当str为空或非数字字符串时抛NumberFormatException */ public static Double toDoubleObject(@NotNull String str) { // 统一行为,不要有时候抛NPE,有时候抛NumberFormatException if (str == null) { throw new NumberFormatException("null"); } return Double.valueOf(str); } /** * 将10进制的String安全的转化为Long. * * 当str为空或非数字字符串时,返回default值 */ public static Double toDoubleObject(@Nullable String str, Double defaultValue) { if (StringUtils.isEmpty(str)) { return defaultValue; } try { return Double.valueOf(str); } catch (final NumberFormatException nfe) { return defaultValue; } } //////////// 16进制 字符串转换为数字对象////////// /** * 将16进制的String转化为Integer. * * 当str为空或非数字字符串时抛NumberFormatException */ public static Integer hexToIntObject(@NotNull String str) { // 统一行为,不要有时候抛NPE,有时候抛NumberFormatException if (str == null) { throw new NumberFormatException("null"); } return Integer.decode(str); } /** * 将16进制的String转化为Integer,出错时返回默认值. */ public static Integer hexToIntObject(@Nullable String str, Integer defaultValue) { if (StringUtils.isEmpty(str)) { return defaultValue; } try { return Integer.decode(str); } catch (final NumberFormatException nfe) { return defaultValue; } } /** * 将16进制的String转化为Long * * 当str为空或非数字字符串时抛NumberFormatException */ public static Long hexToLongObject(@NotNull String str) { // 统一行为,不要有时候抛NPE,有时候抛NumberFormatException if (str == null) { throw new NumberFormatException("null"); } return Long.decode(str); } /** * 将16进制的String转化为Long,出错时返回默认值. */ public static Long hexToLongObject(@Nullable String str, Long defaultValue) { if (StringUtils.isEmpty(str)) { return defaultValue; } try { return Long.decode(str); } catch (final NumberFormatException nfe) { return defaultValue; } } /////// toString /////// // 定义了原子类型与对象类型的参数,保证不会用错函数会导致额外AutoBoxing转换// public static String toString(int i) { return Integer.toString(i); } public static String toString(@NotNull Integer i) { return i.toString(); } public static String toString(long l) { return Long.toString(l); } public static String toString(@NotNull Long l) { return l.toString(); } public static String toString(double d) { return Double.toString(d); } public static String toString(@NotNull Double d) { return d.toString(); } /** * 输出格式化为小数后两位的double字符串 */ public static String to2DigitString(double d) { return String.format(Locale.ROOT, "%.2f", d); } /////////// 杂项 /////// /** * 安全的将小于Integer.MAX的long转为int,否则抛出IllegalArgumentException异常 */ public static int toInt32(long x) { if ((int) x == x) { return (int) x; } throw new IllegalArgumentException("Int " + x + " out of range"); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/number/RandomUtil.java ================================================ package com.vip.vjtools.vjkit.number; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.Validate; import com.vip.vjtools.vjkit.base.MoreValidate; /** * 随机数工具集. * * 1. 获取无锁的ThreadLocalRandom, 性能较佳的SecureRandom * * 2. 保证没有负数陷阱,也能更精确设定范围的nextInt/nextLong/nextDouble * (copy from Common Lang RandomUtils,但默认使用性能较优的ThreadLocalRandom,并可配置其他的Random) * * 3. 随机字符串 (via Common Lang RandomStringUtils) * * @author calvin */ public class RandomUtil { /////////////////// 获取Random实例////////////// /** * 返回无锁的ThreadLocalRandom */ public static Random threadLocalRandom() { return ThreadLocalRandom.current(); } /** * SecureRandom使用性能更好的SHA1PRNG, Tomcat的sessionId生成也用此算法. * * 但JDK7中,需要在启动参数加入 -Djava.security=file:/dev/./urandom (中间那个点很重要) * * 详见:《SecureRandom的江湖偏方与真实效果》http://calvin1978.blogcn.com/articles/securerandom.html */ public static SecureRandom secureRandom() { try { return SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) {// NOSONAR return new SecureRandom(); } } ////////////////// nextInt 相关///////// /** * 返回0到Intger.MAX_VALUE的随机Int, 使用ThreadLocalRandom. */ public static int nextInt() { return nextInt(ThreadLocalRandom.current()); } /** * 返回0到Intger.MAX_VALUE的随机Int, 可传入ThreadLocalRandom或SecureRandom */ public static int nextInt(Random random) { int n = random.nextInt(); if (n == Integer.MIN_VALUE) { n = 0; // corner case } else { n = Math.abs(n); } return n; } /** * 返回0到max的随机Int, 使用ThreadLocalRandom. */ public static int nextInt(int max) { return nextInt(ThreadLocalRandom.current(), max); } /** * 返回0到max的随机Int, 可传入SecureRandom或ThreadLocalRandom */ public static int nextInt(Random random, int max) { return random.nextInt(max); } /** * 返回min到max的随机Int, 使用ThreadLocalRandom. * * min必须大于0. */ public static int nextInt(int min, int max) { return nextInt(ThreadLocalRandom.current(), min, max); } /** * 返回min到max的随机Int,可传入SecureRandom或ThreadLocalRandom. * * min必须大于0. * * JDK本身不具有控制两端范围的nextInt,因此参考Commons Lang RandomUtils的实现, 不直接复用是因为要传入Random实例 * * @see org.apache.commons.lang3.RandomUtils#nextInt(int, int) */ public static int nextInt(Random random, int min, int max) { Validate.isTrue(max >= min, "Start value must be smaller or equal to end value."); MoreValidate.nonNegative("min", min); if (min == max) { return min; } return min + random.nextInt(max - min); } ////////////////// long 相关///////// /** * 返回0-Long.MAX_VALUE间的随机Long, 使用ThreadLocalRandom. */ public static long nextLong() { return nextLong(ThreadLocalRandom.current()); } /** * 返回0-Long.MAX_VALUE间的随机Long, 可传入SecureRandom或ThreadLocalRandom */ public static long nextLong(Random random) { long n = random.nextLong(); if (n == Long.MIN_VALUE) { n = 0; // corner case } else { n = Math.abs(n); } return n; } /** * 返回0-max间的随机Long, 使用ThreadLocalRandom. */ public static long nextLong(long max) { return nextLong(ThreadLocalRandom.current(), 0, max); } /** * 返回0-max间的随机Long, 可传入SecureRandom或ThreadLocalRandom */ public static long nextLong(Random random, long max) { return nextLong(random, 0, max); } /** * 返回min-max间的随机Long, 使用ThreadLocalRandom. * * min必须大于0. */ public static long nextLong(long min, long max) { return nextLong(ThreadLocalRandom.current(), min, max); } /** * 返回min-max间的随机Long,可传入SecureRandom或ThreadLocalRandom. * * min必须大于0. * * JDK本身不具有控制两端范围的nextLong,因此参考Commons Lang RandomUtils的实现, 不直接复用是因为要传入Random实例 * * @see org.apache.commons.lang3.RandomUtils#nextLong(long, long) */ public static long nextLong(Random random, long min, long max) { Validate.isTrue(max >= min, "Start value must be smaller or equal to end value."); MoreValidate.nonNegative("min", min); if (min == max) { return min; } return (long) (min + ((max - min) * random.nextDouble())); } ///////// Double ////// /** * 返回0-之间的double, 使用ThreadLocalRandom */ public static double nextDouble() { return nextDouble(ThreadLocalRandom.current(), 0, Double.MAX_VALUE); } /** * 返回0-Double.MAX之间的double */ public static double nextDouble(Random random) { return nextDouble(random, 0, Double.MAX_VALUE); } /** * 返回0-max之间的double, 使用ThreadLocalRandom * * 注意:与JDK默认返回0-1的行为不一致. */ public static double nextDouble(double max) { return nextDouble(ThreadLocalRandom.current(), 0, max); } /** * 返回0-max之间的double */ public static double nextDouble(Random random, double max) { return nextDouble(random, 0, max); } /** * 返回min-max之间的double,ThreadLocalRandom */ public static double nextDouble(final double min, final double max) { return nextDouble(ThreadLocalRandom.current(), min, max); } /** * 返回min-max之间的double */ public static double nextDouble(Random random, final double min, final double max) { Validate.isTrue(max >= min, "Start value must be smaller or equal to end value."); MoreValidate.nonNegative("min", min); if (Double.compare(min, max) == 0) { return min; } return min + ((max - min) * random.nextDouble()); } //////////////////// String///////// /** * 随机字母或数字,固定长度 */ public static String randomStringFixLength(int length) { return RandomStringUtils.random(length, 0, 0, true, true, null, threadLocalRandom()); } /** * 随机字母或数字,固定长度 */ public static String randomStringFixLength(Random random, int length) { return RandomStringUtils.random(length, 0, 0, true, true, null, random); } /** * 随机字母或数字,随机长度 */ public static String randomStringRandomLength(int minLength, int maxLength) { return RandomStringUtils.random(nextInt(minLength, maxLength), 0, 0, true, true, null, threadLocalRandom()); } /** * 随机字母或数字,随机长度 */ public static String randomStringRandomLength(Random random, int minLength, int maxLength) { return RandomStringUtils.random(nextInt(random, minLength, maxLength), 0, 0, true, true, null, random); } /** * 随机字母,固定长度 */ public static String randomLetterFixLength(int length) { return RandomStringUtils.random(length, 0, 0, true, false, null, threadLocalRandom()); } /** * 随机字母,固定长度 */ public static String randomLetterFixLength(Random random, int length) { return RandomStringUtils.random(length, 0, 0, true, false, null, random); } /** * 随机字母,随机长度 */ public static String randomLetterRandomLength(int minLength, int maxLength) { return RandomStringUtils.random(nextInt(minLength, maxLength), 0, 0, true, false, null, threadLocalRandom()); } /** * 随机字母,随机长度 */ public static String randomLetterRandomLength(Random random, int minLength, int maxLength) { return RandomStringUtils.random(nextInt(random, minLength, maxLength), 0, 0, true, false, null, random); } /** * 随机ASCII字符(含字母,数字及其他符号),固定长度 */ public static String randomAsciiFixLength(int length) { return RandomStringUtils.random(length, 32, 127, false, false, null, threadLocalRandom()); } /** * 随机ASCII字符(含字母,数字及其他符号),固定长度 */ public static String randomAsciiFixLength(Random random, int length) { return RandomStringUtils.random(length, 32, 127, false, false, null, random); } /** * 随机ASCII字符(含字母,数字及其他符号),随机长度 */ public static String randomAsciiRandomLength(int minLength, int maxLength) { return RandomStringUtils.random(nextInt(minLength, maxLength), 32, 127, false, false, null, threadLocalRandom()); } /** * 随机ASCII字符(含字母,数字及其他符号),随机长度 */ public static String randomAsciiRandomLength(Random random, int minLength, int maxLength) { return RandomStringUtils.random(nextInt(random, minLength, maxLength), 32, 127, false, false, null, random); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/number/SizeUnit.java ================================================ package com.vip.vjtools.vjkit.number; /** * Representation of basic size units,just like TimeUnit. * * Usage example: * assertTrue(SizeUnit.BYTES.toMegaBytes(1024 * 1024) == 1.0);
* assertTrue(SizeUnit.GIGABYTES.toBytes(1) == 1024.0 * 1024.0 * 1024.0); */ public enum SizeUnit { /** Smallest memory unit. */ BYTES, /** "One thousand" (1024) bytes. */ KILOBYTES, /** "One million" (1024x1024) bytes. */ MEGABYTES, /** "One billion" (1024x1024x1024) bytes. */ GIGABYTES; /** Number of bytes in a kilobyte. */ private final double BYTES_PER_KILOBYTE = 1024.0; /** Number of kilobytes in a megabyte. */ private final double KILOBYTES_PER_MEGABYTE = 1024.0; /** Number of megabytes per gigabyte. */ private final double MEGABYTES_PER_GIGABYTE = 1024.0; /** * Returns the number of bytes corresponding to the provided input for a particular unit of memory. * * @param input Number of units of memory. * @return Number of bytes corresponding to the provided number of particular memory units. */ public double toBytes(final long input) { double bytes; switch (this) { case BYTES: bytes = input; break; case KILOBYTES: bytes = input * BYTES_PER_KILOBYTE; break; case MEGABYTES: bytes = input * BYTES_PER_KILOBYTE * KILOBYTES_PER_MEGABYTE; break; case GIGABYTES: bytes = input * BYTES_PER_KILOBYTE * KILOBYTES_PER_MEGABYTE * MEGABYTES_PER_GIGABYTE; break; default: throw new RuntimeException("No value '" + this + "' recognized for enum MemoryUnit."); } return bytes; } /** * Returns the number of kilobytes corresponding to the provided input for a particular unit of memory. * * @param input Number of units of memory. * @return Number of kilobytes corresponding to the provided number of particular memory units. */ public double toKiloBytes(final long input) { double kilobytes; switch (this) { case BYTES: kilobytes = input / BYTES_PER_KILOBYTE; break; case KILOBYTES: kilobytes = input; break; case MEGABYTES: kilobytes = input * KILOBYTES_PER_MEGABYTE; break; case GIGABYTES: kilobytes = input * KILOBYTES_PER_MEGABYTE * MEGABYTES_PER_GIGABYTE; break; default: throw new RuntimeException("No value '" + this + "' recognized for enum MemoryUnit."); } return kilobytes; } /** * Returns the number of megabytes corresponding to the provided input for a particular unit of memory. * * @param input Number of units of memory. * @return Number of megabytes corresponding to the provided number of particular memory units. */ public double toMegaBytes(final long input) { double megabytes; switch (this) { case BYTES: megabytes = input / BYTES_PER_KILOBYTE / KILOBYTES_PER_MEGABYTE; break; case KILOBYTES: megabytes = input / KILOBYTES_PER_MEGABYTE; break; case MEGABYTES: megabytes = input; break; case GIGABYTES: megabytes = input * MEGABYTES_PER_GIGABYTE; break; default: throw new RuntimeException("No value '" + this + "' recognized for enum MemoryUnit."); } return megabytes; } /** * Returns the number of gigabytes corresponding to the provided input for a particular unit of memory. * * @param input Number of units of memory. * @return Number of gigabytes corresponding to the provided number of particular memory units. */ public double toGigaBytes(final long input) { double gigabytes; switch (this) { case BYTES: gigabytes = input / BYTES_PER_KILOBYTE / KILOBYTES_PER_MEGABYTE / MEGABYTES_PER_GIGABYTE; break; case KILOBYTES: gigabytes = input / KILOBYTES_PER_MEGABYTE / MEGABYTES_PER_GIGABYTE; break; case MEGABYTES: gigabytes = input / MEGABYTES_PER_GIGABYTE; break; case GIGABYTES: gigabytes = input; break; default: throw new RuntimeException("No value '" + this + "' recognized for enum MemoryUnit."); } return gigabytes; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/number/UnitConverter.java ================================================ /* * Copyright (C) 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.vip.vjtools.vjkit.number; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 1.将带单位的时间,大小字符串转换为数字. copy from Facebook * https://github.com/facebook/jcommon/blob/master/config/src/main/java/com/facebook/config/ConfigUtil.java * * 2.将数字转为带单位的字符串 */ public class UnitConverter { private static final long K = 1024L; private static final long M = K * 1024; private static final long G = M * 1024; private static final long T = G * 1024; private static final long MILLIS_PER_SECOND = 1000L; private static final long MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60; private static final long MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60; private static final long MILLIS_PER_DAY = MILLIS_PER_HOUR * 24; private static final Pattern NUMBER_AND_UNIT = Pattern.compile("(\\d+)([a-zA-Z]+)?"); /** * 将带单位的时间字符串转化为毫秒数. * * 单位包括不分大小写的ms(毫秒),s(秒),m(分钟),h(小时),d(日),y(年) * * 不带任何单位的话,默认单位是毫秒 */ public static long toDurationMillis(String duration) { Matcher matcher = NUMBER_AND_UNIT.matcher(duration); if (!matcher.matches()) { throw new IllegalArgumentException("malformed duration string: " + duration); } long number = Long.parseLong(matcher.group(1)); String unitStr = matcher.group(2); if (unitStr == null) { return number; } char unit = unitStr.toLowerCase().charAt(0); switch (unit) { case 's': return number * MILLIS_PER_SECOND; case 'm': // if it's an m, could be 'minutes' or 'millis'. default minutes if (unitStr.length() >= 2 && unitStr.charAt(1) == 's') { return number; } return number * MILLIS_PER_MINUTE; case 'h': return number * MILLIS_PER_HOUR; case 'd': return number * MILLIS_PER_DAY; default: throw new IllegalArgumentException("unknown time unit :" + unit); } } /** * 将带单位的大小字符串转化为字节数. * * 单位包括不分大小写的b(b),k(kb),m(mb),g(gb),t(tb) * * 不带任何单位的话,默认单位是b */ public static long toBytes(String size) { Matcher matcher = NUMBER_AND_UNIT.matcher(size); if (matcher.matches()) { long number = Long.parseLong(matcher.group(1)); String unitStr = matcher.group(2); if (unitStr != null) { char unit = unitStr.toLowerCase().charAt(0); switch (unit) { case 'b': return number; case 'k': return number * K; case 'm': return number * M; case 'g': return number * G; case 't': return number * T; default: throw new IllegalArgumentException("unknown size unit :" + unit); } } else { return number; } } else { throw new IllegalArgumentException("malformed size string: " + size); } } /** * 从bytes转换为带单位的字符串, 单位最大只支持到G级别,四舍五入 * * @param scale 小数后的精度 */ public static String toSizeUnit(Long bytes, int scale) { if (bytes == null) { return "n/a"; } if (bytes < K) { return String.format("%4d", bytes); } if (bytes < M) { return String.format("%" + (scale == 0 ? 4 : 5 + scale) + '.' + scale + "fk", bytes * 1d / K); } if (bytes < G) { return String.format("%" + (scale == 0 ? 4 : 5 + scale) + '.' + scale + "fm", bytes * 1d / M); } if (bytes < T) { return String.format("%" + (scale == 0 ? 4 : 5 + scale) + '.' + scale + "fg", bytes * 1d / G); } return String.format("%" + (scale == 0 ? 4 : 5 + scale) + '.' + scale + "ft", bytes * 1d / T); } /** * 转换毫秒为带时间单位的字符串,单位最大到day级别,四舍五入 * * @param scale 小数后的精度 */ public static String toTimeUnit(long millis, int scale) { if (millis < MILLIS_PER_SECOND) { return String.format("%4dms", millis); } if (millis < MILLIS_PER_MINUTE) { return String.format("%" + (scale == 0 ? 2 : 3 + scale) + '.' + scale + "fs", millis * 1d / MILLIS_PER_SECOND); } if (millis < MILLIS_PER_HOUR) { return String.format("%" + (scale == 0 ? 2 : 3 + scale) + '.' + scale + "fm", millis * 1d / MILLIS_PER_MINUTE); } if (millis < MILLIS_PER_DAY) { return String.format("%" + (scale == 0 ? 2 : 3 + scale) + '.' + scale + "fh", millis * 1d / MILLIS_PER_HOUR); } return String.format("%" + (scale == 0 ? 2 : 3 + scale) + '.' + scale + "fd", millis * 1d / MILLIS_PER_DAY); } /** * 转换毫秒为带时间单位的字符串,会同时带下一级的单位,四舍五入 */ public static String toTimeWithMinorUnit(long millis) { if (millis < MILLIS_PER_SECOND) { return String.format("%4dms", millis); } if (millis < MILLIS_PER_MINUTE) { return String.format("%02ds", millis / MILLIS_PER_SECOND); } if (millis < MILLIS_PER_HOUR) { return String.format("%02dm%02ds", millis / MILLIS_PER_MINUTE, (millis / MILLIS_PER_SECOND) % 60); } if (millis < MILLIS_PER_DAY) { return String.format("%02dh%02dm", millis / MILLIS_PER_HOUR, (millis / MILLIS_PER_MINUTE) % 60); } return String.format("%dd%02dh", millis / MILLIS_PER_DAY, (millis / MILLIS_PER_HOUR) % 24); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/AnnotationUtil.java ================================================ package com.vip.vjtools.vjkit.reflect; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang3.ClassUtils; /** * Annotation的工具类 * * 1.获得类的全部Annotation * * 2.获取类的标注了annotation的所有属性和方法 */ public class AnnotationUtil { /** * 递归Class所有的Annotation,一个最彻底的实现. * * 包括所有基类,所有接口的Annotation,同时支持Spring风格的Annotation继承的父Annotation, */ public static Set getAllAnnotations(final Class cls) { List> allTypes = ClassUtil.getAllSuperclasses(cls); allTypes.addAll(ClassUtil.getAllInterfaces(cls)); allTypes.add(cls); Set anns = new HashSet(); for (Class type : allTypes) { anns.addAll(Arrays.asList(type.getDeclaredAnnotations())); } Set superAnnotations = new HashSet(); for (Annotation ann : anns) { getSuperAnnotations(ann.annotationType(), superAnnotations); } anns.addAll(superAnnotations); return anns; } private static void getSuperAnnotations(Class annotationType, Set visited) { Annotation[] anns = annotationType.getDeclaredAnnotations(); for (Annotation ann : anns) { if (!ann.annotationType().getName().startsWith("java.lang") && visited.add(ann)) { getSuperAnnotations(ann.annotationType(), visited); } } } /** * 找出所有标注了该annotation的公共属性,循环遍历父类. * * 暂未支持Spring风格Annotation继承Annotation * * copy from org.unitils.util.AnnotationUtils */ public static Set getAnnotatedPublicFields(Class clazz, Class annotation) { if (Object.class.equals(clazz)) { return Collections.emptySet(); } Set annotatedFields = new HashSet(); Field[] fields = clazz.getFields(); for (Field field : fields) { if (field.getAnnotation(annotation) != null) { annotatedFields.add(field); } } return annotatedFields; } /** * 找出所有标注了该annotation的属性,循环遍历父类,包含private属性. * * 暂未支持Spring风格Annotation继承Annotation * * copy from org.unitils.util.AnnotationUtils */ public static Set getAnnotatedFields(Class clazz, Class annotation) { if (Object.class.equals(clazz)) { return Collections.emptySet(); } Set annotatedFields = new HashSet(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.getAnnotation(annotation) != null) { annotatedFields.add(field); } } annotatedFields.addAll(getAnnotatedFields(clazz.getSuperclass(), annotation)); return annotatedFields; } /** * 找出所有标注了该annotation的公共方法(含父类的公共函数),循环其接口. * * 暂未支持Spring风格Annotation继承Annotation * * 另,如果子类重载父类的公共函数,父类函数上的annotation不会继承,只有接口上的annotation会被继承. */ public static Set getAnnotatedPublicMethods(Class clazz, Class annotation) { // 已递归到Objebt.class, 停止递归 if (Object.class.equals(clazz)) { return Collections.emptySet(); } List> ifcs = ClassUtils.getAllInterfaces(clazz); Set annotatedMethods = new HashSet(); // 遍历当前类的所有公共方法 Method[] methods = clazz.getMethods(); for (Method method : methods) { // 如果当前方法有标注,或定义了该方法的所有接口有标注 if (method.getAnnotation(annotation) != null || searchOnInterfaces(method, annotation, ifcs)) { annotatedMethods.add(method); } } return annotatedMethods; } private static boolean searchOnInterfaces(Method method, Class annotationType, List> ifcs) { for (Class iface : ifcs) { try { Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes()); if (equivalentMethod.getAnnotation(annotationType) != null) { return true; } } catch (NoSuchMethodException ex) { // NOSONAR // Skip this interface - it doesn't have the method... } } return false; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/ClassLoaderUtil.java ================================================ package com.vip.vjtools.vjkit.reflect; public class ClassLoaderUtil { /** * Copy from Spring, 按顺序获取默认ClassLoader * * 1. Thread.currentThread().getContextClassLoader() * * 2. ClassLoaderUtil的加载ClassLoader * * 3. SystemClassLoader */ public static ClassLoader getDefaultClassLoader() { ClassLoader cl = null; try { cl = Thread.currentThread().getContextClassLoader(); } catch (Throwable ex) { // NOSONAR // Cannot access thread context ClassLoader - falling back... } if (cl == null) { // No thread context class loader -> use class loader of this class. cl = ClassLoaderUtil.class.getClassLoader(); if (cl == null) { // getClassLoader() returning null indicates the bootstrap ClassLoader try { cl = ClassLoader.getSystemClassLoader(); } catch (Throwable ex) { // NOSONAR // Cannot access system ClassLoader - oh well, maybe the caller can live with null... } } } return cl; } /** * 探测类是否存在classpath中 */ public static boolean isPresent(String className, ClassLoader classLoader) { try { classLoader.loadClass(className); return true; } catch (Throwable ex) { // NOSONAR // Class or one of its dependencies is not present... return false; } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/ClassUtil.java ================================================ package com.vip.vjtools.vjkit.reflect; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 获取Class信息的工具类 * * 1. 获取类名,包名,循环向上的全部父类,全部接口 * * 2. 其他便捷函数 */ public class ClassUtil { private static final String CGLIB_CLASS_SEPARATOR = "$$"; private static Logger logger = LoggerFactory.getLogger(ClassUtil.class); ////// Short class and Package Name ////... /** * 返回短Class名, 不包含PackageName. * * 内部类的话,返回"主类.内部类" */ public static String getShortClassName(final Class cls) { return ClassUtils.getShortClassName(cls); } /** * 返回Class名,不包含PackageName * * 内部类的话,返回"主类.内部类" */ public static String getShortClassName(final String className) { return ClassUtils.getShortClassName(className); } /** * 返回PackageName */ public static String getPackageName(final Class cls) { return ClassUtils.getPackageName(cls); } /** * 返回PackageName */ public static String getPackageName(final String className) { return ClassUtils.getPackageName(className); } ////////// 获取全部父类,全部接口////////// /** * 递归返回所有的SupperClasses,包含Object.class */ public static List> getAllSuperclasses(final Class cls) { return ClassUtils.getAllSuperclasses(cls); } /** * 递归返回本类及所有基类继承的接口,及接口继承的接口,比Spring中的相同实现完整 */ public static List> getAllInterfaces(final Class cls) { return ClassUtils.getAllInterfaces(cls); } /////////// 杂项 ///////// /** * https://github.com/linkedin/linkedin-utils/blob/master/org.linkedin.util-core/src/main/java/org/linkedin/util/reflect/ReflectUtils.java * * The purpose of this method is somewhat to provide a better naming / documentation than the javadoc of * Class.isAssignableFrom method. * * @return true if subclass is a subclass or sub interface of superclass */ public static boolean isSubClassOrInterfaceOf(Class subclass, Class superclass) { return superclass.isAssignableFrom(subclass); } /** * 获取CGLib处理过后的实体的原Class. */ public static Class unwrapCglib(Object instance) { Validate.notNull(instance, "Instance must not be null"); Class clazz = instance.getClass(); if ((clazz != null) && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { Class superClass = clazz.getSuperclass(); if ((superClass != null) && !Object.class.equals(superClass)) { return superClass; } } return clazz; } /** * 通过反射, 获得Class定义中声明的泛型参数的类型, * * 注意泛型必须定义在父类处. 这是唯一可以通过反射从泛型获得Class实例的地方. * * 如无法找到, 返回Object.class. * * eg. public UserDao extends HibernateDao * * @param clazz The class to introspect * @return the first generic declaration, or Object.class if cannot be determined */ public static Class getClassGenericType(final Class clazz) { return getClassGenericType(clazz, 0); } /** * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. * * 注意泛型必须定义在父类处. 这是唯一可以通过反射从泛型获得Class实例的地方. * * 如无法找到, 返回Object.class. * * 如public UserDao extends HibernateDao * * @param clazz clazz The class to introspect * @param index the Index of the generic declaration, start from 0. * @return the index generic declaration, or Object.class if cannot be determined */ public static Class getClassGenericType(final Class clazz, final int index) { Type genType = clazz.getGenericSuperclass(); if (!(genType instanceof ParameterizedType)) { logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType"); return Object.class; } Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); if ((index >= params.length) || (index < 0)) { logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + params.length); return Object.class; } if (!(params[index] instanceof Class)) { logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); return Object.class; } return (Class) params[index]; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/ReflectionUtil.java ================================================ package com.vip.vjtools.vjkit.reflect; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.reflect.ConstructorUtils; import org.apache.commons.lang3.reflect.MethodUtils; import com.vip.vjtools.vjkit.base.ExceptionUtil; import com.vip.vjtools.vjkit.base.ObjectUtil; import com.vip.vjtools.vjkit.base.type.UncheckedException; /** * 反射工具类. * * 所有反射均无视modifier的范围限制,同时将反射的Checked异常转为UnChecked异常。 * * 需要平衡性能较差的一次性调用,以及高性能的基于预先获取的Method/Filed对象反复调用两种用法 * * 1. 获取方法与属性 (兼容了原始类型/接口/抽象类的参数, 并默认将方法与属性设为可访问) * * 2. 方法调用. * * 3. 构造函数. */ @SuppressWarnings("unchecked") public class ReflectionUtil { private static final String SETTER_PREFIX = "set"; private static final String GETTER_PREFIX = "get"; private static final String IS_PREFIX = "is"; ///////// 获取方法对象 //////// /** * 循环遍历,按属性名获取前缀为set的函数,并设为可访问 */ public static Method getSetterMethod(Class clazz, String propertyName, Class parameterType) { String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(propertyName); return getMethod(clazz, setterMethodName, parameterType); } /** * 循环遍历,按属性名获取前缀为get或is的函数,并设为可访问 */ public static Method getGetterMethod(Class clazz, String propertyName) { String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(propertyName); Method method = getMethod(clazz, getterMethodName); // retry on another name if (method == null) { getterMethodName = IS_PREFIX + StringUtils.capitalize(propertyName); method = getMethod(clazz, getterMethodName); } return method; } /** * 循环向上转型, 获取对象的DeclaredMethod, 并强制设置为可访问. * * 如向上转型到Object仍无法找到, 返回null. * * 匹配函数名+参数类型. * * 方法需要被多次调用时,先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) * * 因为getMethod() 不能获取父类的private函数, 因此采用循环向上的getDeclaredMethod(); */ public static Method getMethod(final Class clazz, final String methodName, Class... parameterTypes) { Method method = MethodUtils.getMatchingMethod(clazz, methodName, parameterTypes); if (method != null) { makeAccessible(method); } return method; } /** * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. * * 如向上转型到Object仍无法找到, 返回null. * * 只匹配函数名, 如果有多个同名函数返回第一个 * * 方法需要被多次调用时,先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) * * 因为getMethod() 不能获取父类的private函数, 因此采用循环向上的getDeclaredMethods() */ public static Method getAccessibleMethodByName(final Class clazz, final String methodName) { Validate.notNull(clazz, "clazz can't be null"); Validate.notEmpty(methodName, "methodName can't be blank"); for (Class searchType = clazz; searchType != Object.class; searchType = searchType.getSuperclass()) { Method[] methods = searchType.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { makeAccessible(method); return method; } } } return null; } //////////// 获取Field对象/////////// /** * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. * * 如向上转型到Object仍无法找到, 返回null. * * 因为getFiled()不能获取父类的private属性, 因此采用循环向上的getDeclaredField(); */ public static Field getField(final Class clazz, final String fieldName) { Validate.notNull(clazz, "clazz can't be null"); Validate.notEmpty(fieldName, "fieldName can't be blank"); for (Class superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) { try { Field field = superClass.getDeclaredField(fieldName); makeAccessible(field); return field; } catch (NoSuchFieldException e) {// NOSONAR // Field不在当前类定义,继续向上转型 } } return null; } /////////// 获取或设置属性相关函数 /////////// /** * 调用Getter方法, 无视private/protected修饰符. * * 性能较差, 用于单次调用的场景 */ public static T invokeGetter(Object obj, String propertyName) { Method method = getGetterMethod(obj.getClass(), propertyName); if (method == null) { throw new IllegalArgumentException( "Could not find getter method [" + propertyName + "] on target [" + obj + ']'); } return invokeMethod(obj, method); } /** * 调用Setter方法, 无视private/protected修饰符, 按传入value的类型匹配函数. * * 性能较差, 用于单次调用的场景 */ public static void invokeSetter(Object obj, String propertyName, Object value) { Method method = getSetterMethod(obj.getClass(), propertyName, value.getClass()); if (method == null) { throw new IllegalArgumentException( "Could not find getter method [" + propertyName + "] on target [" + obj + ']'); } invokeMethod(obj, method, value); } /** * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. * * 性能较差, 用于单次调用的场景 */ public static T getFieldValue(final Object obj, final String fieldName) { Field field = getField(obj.getClass(), fieldName); if (field == null) { throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + ']'); } return getFieldValue(obj, field); } /** * 使用已获取的Field, 直接读取对象属性值, 不经过getter函数. * * 用于反复调用的场景. */ public static T getFieldValue(final Object obj, final Field field) { try { return (T) field.get(obj); } catch (Exception e) { throw convertReflectionExceptionToUnchecked(e); } } /** * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. * * 性能较差, 用于单次调用的场景 */ public static void setFieldValue(final Object obj, final String fieldName, final Object value) { Field field = getField(obj.getClass(), fieldName); if (field == null) { throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + ']'); } setField(obj, field, value); } /** * 使用预先获取的Field, 直接读取对象属性值, 不经过setter函数. * * 用于反复调用的场景. */ public static void setField(final Object obj, Field field, final Object value) { try { field.set(obj, value); } catch (Exception e) { throw convertReflectionExceptionToUnchecked(e); } } /** * 先尝试用Getter函数读取, 如果不存在则直接读取变量. * * 性能较差, 用于单次调用的场景 */ public static T getProperty(Object obj, String propertyName) { Method method = getGetterMethod(obj.getClass(), propertyName); if (method != null) { return invokeMethod(obj, method); } else { return getFieldValue(obj, propertyName); } } /** * 先尝试用Setter函数写入, 如果不存在则直接写入变量, 按传入value的类型匹配函数. * * 性能较差, 用于单次调用的场景 */ public static void setProperty(Object obj, String propertyName, final Object value) { Method method = getSetterMethod(obj.getClass(), propertyName, value.getClass()); if (method != null) { invokeMethod(obj, method, value); } else { setFieldValue(obj, propertyName, value); } } /////////// 方法相关函数 //////////// /** * 反射调用对象方法, 无视private/protected修饰符. * * 根据传入参数的实际类型进行匹配, 支持方法参数定义是接口,父类,原子类型等情况 * * 性能较差,仅用于单次调用. */ public static T invokeMethod(Object obj, String methodName, Object... args) { Object[] theArgs = ArrayUtils.nullToEmpty(args); final Class[] parameterTypes = ClassUtils.toClass(theArgs); return invokeMethod(obj, methodName, theArgs, parameterTypes); } /** * 反射调用对象方法, 无视private/protected修饰符. * * 根据参数类型参数进行匹配, 支持方法参数定义是接口,父类,原子类型等情况 * * 性能较低,仅用于单次调用. */ public static T invokeMethod(final Object obj, final String methodName, final Object[] args, final Class[] parameterTypes) { Method method = getMethod(obj.getClass(), methodName, parameterTypes); if (method == null) { throw new IllegalArgumentException("Could not find method [" + methodName + "] with parameter types:" + ObjectUtil.toPrettyString(parameterTypes) + " on class [" + obj.getClass() + ']'); } return invokeMethod(obj, method, args); } /** * 反射调用对象方法, 无视private/protected修饰符 * * 只匹配函数名,如果有多个同名函数调用第一个. 用于确信只有一个同名函数, 但参数类型不确定的情况. * * 性能较低,仅用于单次调用. */ public static T invokeMethodByName(final Object obj, final String methodName, final Object[] args) { Method method = getAccessibleMethodByName(obj.getClass(), methodName); if (method == null) { throw new IllegalArgumentException( "Could not find method [" + methodName + "] on class [" + obj.getClass() + ']'); } return invokeMethod(obj, method, args); } /** * 调用预先获取的Method,用于反复调用的场景 */ public static T invokeMethod(final Object obj, Method method, Object... args) { try { return (T) method.invoke(obj, args); } catch (Exception e) { throw ExceptionUtil.unwrapAndUnchecked(e); } } ////////// 构造函数 //////// // TODO:更多函数的封装 /** * 调用构造函数. */ public static T invokeConstructor(final Class cls, Object... args) { try { return ConstructorUtils.invokeConstructor(cls, args); } catch (Exception e) { throw ExceptionUtil.unwrapAndUnchecked(e); } } /////// 辅助函数 //////// /** * 改变private/protected的方法为可访问,尽量不进行改变,避免JDK的SecurityManager抱怨。 */ public static void makeAccessible(Method method) { if (!method.isAccessible() && (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))) { method.setAccessible(true); } } /** * 改变private/protected的成员变量为可访问,尽量不进行改变,避免JDK的SecurityManager抱怨。 */ public static void makeAccessible(Field field) { if (!field.isAccessible() && (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier.isFinal(field.getModifiers()))) { field.setAccessible(true); } } /** * 将反射时的checked exception转换为unchecked exception. */ public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) { if ((e instanceof IllegalAccessException) || (e instanceof NoSuchMethodException)) { return new IllegalArgumentException(e); } else if (e instanceof InvocationTargetException) { return new RuntimeException(((InvocationTargetException) e).getTargetException()); } else if (e instanceof RuntimeException) { return (RuntimeException) e; } return new UncheckedException(e); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/security/CryptoUtil.java ================================================ package com.vip.vjtools.vjkit.security; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import com.vip.vjtools.vjkit.base.ExceptionUtil; import com.vip.vjtools.vjkit.number.RandomUtil; import com.vip.vjtools.vjkit.text.Charsets; /** * 支持HMAC-SHA1消息签名 及 DES/AES对称加密的工具类. * * 支持Hex与Base64两种编码方式. */ public class CryptoUtil { private static final String AES_ALG = "AES"; private static final String AES_CBC_ALG = "AES/CBC/PKCS5Padding"; private static final String HMACSHA1_ALG = "HmacSHA1"; private static final int DEFAULT_HMACSHA1_KEYSIZE = 160; // RFC2401 private static final int DEFAULT_AES_KEYSIZE = 128; private static final int DEFAULT_IVSIZE = 16; private static SecureRandom random = RandomUtil.secureRandom(); // -- HMAC-SHA1 funciton --// /** * 使用HMAC-SHA1进行消息签名, 返回字节数组,长度为20字节. * * @param input 原始输入字符数组 * @param key HMAC-SHA1密钥 */ public static byte[] hmacSha1(byte[] input, byte[] key) { try { SecretKey secretKey = new SecretKeySpec(key, HMACSHA1_ALG); Mac mac = Mac.getInstance(HMACSHA1_ALG); mac.init(secretKey); return mac.doFinal(input); } catch (GeneralSecurityException e) { throw ExceptionUtil.unchecked(e); } } /** * 校验HMAC-SHA1签名是否正确. * * @param expected 已存在的签名 * @param input 原始输入字符串 * @param key 密钥 */ public static boolean isMacValid(byte[] expected, byte[] input, byte[] key) { byte[] actual = hmacSha1(input, key); return Arrays.equals(expected, actual); } /** * 生成HMAC-SHA1密钥,返回字节数组,长度为160位(20字节). HMAC-SHA1算法对密钥无特殊要求, RFC2401建议最少长度为160位(20字节). */ public static byte[] generateHmacSha1Key() { try { KeyGenerator keyGenerator = KeyGenerator.getInstance(HMACSHA1_ALG); keyGenerator.init(DEFAULT_HMACSHA1_KEYSIZE); SecretKey secretKey = keyGenerator.generateKey(); return secretKey.getEncoded(); } catch (GeneralSecurityException e) { throw ExceptionUtil.unchecked(e); } } ///////////// -- AES funciton --////////// /** * 使用AES加密原始字符串. * * @param input 原始输入字符数组 * @param key 符合AES要求的密钥 */ public static byte[] aesEncrypt(byte[] input, byte[] key) { return aes(input, key, Cipher.ENCRYPT_MODE); } /** * 使用AES加密原始字符串. * * @param input 原始输入字符数组 * @param key 符合AES要求的密钥 * @param iv 初始向量 */ public static byte[] aesEncrypt(byte[] input, byte[] key, byte[] iv) { return aes(input, key, iv, Cipher.ENCRYPT_MODE); } /** * 使用AES解密字符串, 返回原始字符串. * * @param input Hex编码的加密字符串 * @param key 符合AES要求的密钥 */ public static String aesDecrypt(byte[] input, byte[] key) { byte[] decryptResult = aes(input, key, Cipher.DECRYPT_MODE); return new String(decryptResult, Charsets.UTF_8); } /** * 使用AES解密字符串, 返回原始字符串. * * @param input Hex编码的加密字符串 * @param key 符合AES要求的密钥 * @param iv 初始向量 */ public static String aesDecrypt(byte[] input, byte[] key, byte[] iv) { byte[] decryptResult = aes(input, key, iv, Cipher.DECRYPT_MODE); return new String(decryptResult, Charsets.UTF_8); } /** * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果. * * @param input 原始字节数组 * @param key 符合AES要求的密钥 * @param mode Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE */ private static byte[] aes(byte[] input, byte[] key, int mode) { try { SecretKey secretKey = new SecretKeySpec(key, AES_ALG); Cipher cipher = Cipher.getInstance(AES_ALG); cipher.init(mode, secretKey); return cipher.doFinal(input); } catch (GeneralSecurityException e) { throw ExceptionUtil.unchecked(e); } } /** * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果. * * @param input 原始字节数组 * @param key 符合AES要求的密钥 * @param iv 初始向量 * @param mode Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE */ private static byte[] aes(byte[] input, byte[] key, byte[] iv, int mode) { try { SecretKey secretKey = new SecretKeySpec(key, AES_ALG); IvParameterSpec ivSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance(AES_CBC_ALG); cipher.init(mode, secretKey, ivSpec); return cipher.doFinal(input); } catch (GeneralSecurityException e) { throw ExceptionUtil.unchecked(e); } } /** * 生成AES密钥,返回字节数组, 默认长度为128位(16字节). */ public static byte[] generateAesKey() { return generateAesKey(DEFAULT_AES_KEYSIZE); } /** * 生成AES密钥,可选长度为128,192,256位. */ public static byte[] generateAesKey(int keysize) { try { KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALG); keyGenerator.init(keysize); SecretKey secretKey = keyGenerator.generateKey(); return secretKey.getEncoded(); } catch (GeneralSecurityException e) { throw ExceptionUtil.unchecked(e); } } /** * 生成随机向量,默认大小为cipher.getBlockSize(), 16字节. */ public static byte[] generateIV() { byte[] bytes = new byte[DEFAULT_IVSIZE]; random.nextBytes(bytes); return bytes; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/text/Charsets.java ================================================ package com.vip.vjtools.vjkit.text; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * * 尽量使用Charsets.UTF8而不是"UTF-8",减少JDK里的Charset查找消耗. * * 使用JDK7的StandardCharsets,同时留了标准名称的字符串 * * @author calvin */ public class Charsets { public static final Charset UTF_8 = StandardCharsets.UTF_8; public static final Charset US_ASCII = StandardCharsets.US_ASCII; public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; public static final String UTF_8_NAME = StandardCharsets.UTF_8.name(); public static final String ASCII_NAME = StandardCharsets.US_ASCII.name(); public static final String ISO_8859_1_NAME = StandardCharsets.ISO_8859_1.name(); } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/text/CsvUtil.java ================================================ // Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package com.vip.vjtools.vjkit.text; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; /** * 从Jodd移植 * * https://github.com/oblac/jodd/blob/master/jodd-core/src/main/java/jodd/util/CsvUtil.java * * Helps with CSV strings. See: http://en.wikipedia.org/wiki/Comma-separated_values */ public class CsvUtil { protected static final char FIELD_SEPARATOR = ','; protected static final char FIELD_QUOTE = '"'; protected static final String DOUBLE_QUOTE = "\"\""; protected static final String SPECIAL_CHARS = "\r\n"; protected static final String SPACE = " "; protected static final String QUOTE = "\""; /** * Parse fields as csv string, */ public static String toCsvString(Object... elements) { StringBuilder line = new StringBuilder(); int last = elements.length - 1; for (int i = 0; i < elements.length; i++) { if (elements[i] == null) { if (i != last) { line.append(FIELD_SEPARATOR); } continue; } String field = elements[i].toString(); // check for special cases int ndx = field.indexOf(FIELD_SEPARATOR); if (ndx == -1) { ndx = field.indexOf(FIELD_QUOTE); } if (ndx == -1 && (field.startsWith(SPACE) || field.endsWith(SPACE))) { ndx = 1; } if (ndx == -1) { ndx = StringUtils.indexOf(field, SPECIAL_CHARS); } // add field if (ndx != -1) { line.append(FIELD_QUOTE); } field = StringUtils.replace(field, QUOTE, DOUBLE_QUOTE); line.append(field); if (ndx != -1) { line.append(FIELD_QUOTE); } // last if (i != last) { line.append(FIELD_SEPARATOR); } } return line.toString(); } /** * Converts CSV line to string array. */ public static String[] fromCsvString(String line) { List row = new ArrayList(); boolean inQuotedField = false; int fieldStart = 0; final int len = line.length(); for (int i = 0; i < len; i++) { char c = line.charAt(i); if (c == FIELD_SEPARATOR) { if (!inQuotedField) { // ignore we are quoting addField(row, line, fieldStart, i, inQuotedField); fieldStart = i + 1; } } else if (c == FIELD_QUOTE) { if (inQuotedField) { if (i + 1 == len || line.charAt(i + 1) == FIELD_SEPARATOR) { // we are already quoting - peek to see // if this is the end of the field addField(row, line, fieldStart, i, inQuotedField); fieldStart = i + 2; i++; // and skip the comma inQuotedField = false; } } else if (fieldStart == i) { inQuotedField = true; // this is a beginning of a quote fieldStart++; // move field start } } } // add last field - but only if string was not empty if (len > 0 && fieldStart <= len) { addField(row, line, fieldStart, len, inQuotedField); } return row.toArray(new String[row.size()]); } private static void addField(List row, String line, int startIndex, int endIndex, boolean inQuoted) { String field = line.substring(startIndex, endIndex); if (inQuoted) { field = StringUtils.replace(field, DOUBLE_QUOTE, "\""); } row.add(field); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/text/EncodeUtil.java ================================================ package com.vip.vjtools.vjkit.text; import com.google.common.io.BaseEncoding; /** * string/url -> hex/base64 编解码工具集(via guava BaseEncoding) */ public class EncodeUtil { /** * Hex编码, 将byte[]编码为String,默认为ABCDEF为大写字母. */ public static String encodeHex(byte[] input) { return BaseEncoding.base16().encode(input); } /** * Hex解码, 将String解码为byte[]. * * 字符串有异常时抛出IllegalArgumentException. */ public static byte[] decodeHex(CharSequence input) { return BaseEncoding.base16().decode(input); } /** * Base64编码. */ public static String encodeBase64(byte[] input) { return BaseEncoding.base64().encode(input); } /** * Base64解码. * * 如果字符不合法,抛出IllegalArgumentException */ public static byte[] decodeBase64(CharSequence input) { return BaseEncoding.base64().decode(input); } /** * Base64编码, URL安全.(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548). */ public static String encodeBase64UrlSafe(byte[] input) { return BaseEncoding.base64Url().encode(input); } /** * Base64解码, URL安全(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548). * * 如果字符不合法,抛出IllegalArgumentException */ public static byte[] decodeBase64UrlSafe(CharSequence input) { return BaseEncoding.base64Url().decode(input); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/text/EscapeUtil.java ================================================ package com.vip.vjtools.vjkit.text; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import org.apache.commons.lang3.StringEscapeUtils; /** * 转义工具集. * * 1.URL 转义,转义后的URL可作为URL中的参数 (via JDK) * * 2.xml/html 转义(via Commons-Lang StringEscapeUtils ,但已被废弃, 建议用Common-Text) * * 比如 "bread" & "butter" 转化为 "bread" & "butter" */ public class EscapeUtil { /** * URL 编码, Encode默认为UTF-8. * * 转义后的URL可作为URL中的参数 */ public static String urlEncode(String part) { try { return URLEncoder.encode(part, Charsets.UTF_8_NAME); } catch (UnsupportedEncodingException ignored) { // NOSONAR // this exception is only for detecting and handling invalid inputs return null; } } /** * URL 解码, Encode默认为UTF-8. 转义后的URL可作为URL中的参数 */ public static String urlDecode(String part) { try { return URLDecoder.decode(part, Charsets.UTF_8_NAME); } catch (UnsupportedEncodingException e) { // NOSONAR // this exception is only for detecting and handling invalid inputs return null; } } /** * Xml转码,将字符串转码为符合XML1.1格式的字符串. * * 比如 "bread" & "butter" 转化为 "bread" & "butter" */ public static String escapeXml(String xml) { return StringEscapeUtils.escapeXml11(xml); } /** * Xml转码,XML格式的字符串解码为普通字符串. * * 比如 "bread" & "butter" 转化为"bread" & "butter" */ public static String unescapeXml(String xml) { return StringEscapeUtils.unescapeXml(xml); } /** * Html转码,将字符串转码为符合HTML4格式的字符串. * * 比如 "bread" & "butter" 转化为 "bread" & "butter" */ public static String escapeHtml(String html) { return StringEscapeUtils.escapeHtml4(html); } /** * Html解码,将HTML4格式的字符串转码解码为普通字符串. * * 比如 "bread" & "butter"转化为"bread" & "butter" */ public static String unescapeHtml(String html) { return StringEscapeUtils.unescapeHtml4(html); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/text/HashUtil.java ================================================ package com.vip.vjtools.vjkit.text; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.zip.CRC32; import org.apache.commons.lang3.Validate; import com.google.common.hash.Hashing; import com.vip.vjtools.vjkit.base.annotation.NotNull; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 封装各种Hash算法的工具类 * * 1.SHA-1, 安全性较高, 返回byte[](可用Encodes进一步被编码为Hex, Base64) * * 性能优化,使用ThreadLocal的MessageDigest(from ElasticSearch) * * 支持带salt并且进行迭代达到更高的安全性. * * MD5的安全性较低, 只在文件Checksum时支持. * * 2.crc32, murmur32这些不追求安全性, 性能较高, 返回int. * * 其中crc32基于JDK, murmurhash基于guava */ public class HashUtil { public static final int MURMUR_SEED = 1_318_007_700; private static final ThreadLocal MD5_DIGEST = createThreadLocalMessageDigest("MD5"); private static final ThreadLocal SHA_1_DIGEST = createThreadLocalMessageDigest("SHA-1"); private static SecureRandom random = new SecureRandom(); // ThreadLocal重用MessageDigest private static ThreadLocal createThreadLocalMessageDigest(final String digest) { return new ThreadLocal() { @Override protected MessageDigest initialValue() { try { return MessageDigest.getInstance(digest); } catch (NoSuchAlgorithmException e) { throw new RuntimeException( "unexpected exception creating MessageDigest instance for [" + digest + ']', e); } } }; } ////////////////// SHA1 /////////////////// /** * 对输入字符串进行sha1散列. */ public static byte[] sha1(@NotNull byte[] input) { return digest(input, get(SHA_1_DIGEST), null, 1); } /** * 对输入字符串进行sha1散列, 编码默认为UTF8. */ public static byte[] sha1(@NotNull String input) { return digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), null, 1); } /** * 对输入字符串进行sha1散列,带salt达到更高的安全性. */ public static byte[] sha1(@NotNull byte[] input, @Nullable byte[] salt) { return digest(input, get(SHA_1_DIGEST), salt, 1); } /** * 对输入字符串进行sha1散列,带salt达到更高的安全性. */ public static byte[] sha1(@NotNull String input, @Nullable byte[] salt) { return digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), salt, 1); } /** * 对输入字符串进行sha1散列,带salt而且迭代达到更高更高的安全性. * * @see #generateSalt(int) */ public static byte[] sha1(@NotNull byte[] input, @Nullable byte[] salt, int iterations) { return digest(input, get(SHA_1_DIGEST), salt, iterations); } /** * 对输入字符串进行sha1散列,带salt而且迭代达到更高更高的安全性. * * @see #generateSalt(int) */ public static byte[] sha1(@NotNull String input, @Nullable byte[] salt, int iterations) { return digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), salt, iterations); } private static MessageDigest get(ThreadLocal messageDigest) { MessageDigest instance = messageDigest.get(); instance.reset(); return instance; } /** * 对字符串进行散列, 支持md5与sha1算法. */ private static byte[] digest(@NotNull byte[] input, MessageDigest digest, byte[] salt, int iterations) { // 带盐 if (salt != null) { digest.update(salt); } // 第一次散列 byte[] result = digest.digest(input); // 如果迭代次数>1,进一步迭代散列 for (int i = 1; i < iterations; i++) { digest.reset(); result = digest.digest(result); } return result; } /** * 用SecureRandom生成随机的byte[]作为salt. * * @param numBytes salt数组的大小 */ public static byte[] generateSalt(int numBytes) { Validate.isTrue(numBytes > 0, "numBytes argument must be a positive integer (1 or larger)", numBytes); byte[] bytes = new byte[numBytes]; random.nextBytes(bytes); return bytes; } /** * 对文件进行sha1散列. */ public static byte[] sha1File(InputStream input) throws IOException { return digestFile(input, get(SHA_1_DIGEST)); } /** * 对文件进行md5散列,被破解后MD5已较少人用. */ public static byte[] md5File(InputStream input) throws IOException { return digestFile(input, get(MD5_DIGEST)); } private static byte[] digestFile(InputStream input, MessageDigest messageDigest) throws IOException { int bufferLength = 8 * 1024; byte[] buffer = new byte[bufferLength]; int read = input.read(buffer, 0, bufferLength); while (read > -1) { messageDigest.update(buffer, 0, read); read = input.read(buffer, 0, bufferLength); } return messageDigest.digest(); } ////////////////// 基于JDK的CRC32 /////////////////// /** * 对输入字符串进行crc32散列返回int, 返回值有可能是负数. * * Guava也有crc32实现, 但返回值无法返回long,所以统一使用JDK默认实现 */ public static int crc32AsInt(@NotNull String input) { return crc32AsInt(input.getBytes(Charsets.UTF_8)); } /** * 对输入字符串进行crc32散列返回int, 返回值有可能是负数. * * Guava也有crc32实现, 但返回值无法返回long,所以统一使用JDK默认实现 */ public static int crc32AsInt(@NotNull byte[] input) { CRC32 crc32 = new CRC32(); crc32.update(input); // CRC32 只是 32bit int,为了CheckSum接口强转成long,此处再次转回来 return (int) crc32.getValue(); } /** * 对输入字符串进行crc32散列,与php兼容,在64bit系统下返回永远是正数的long * * Guava也有crc32实现, 但返回值无法返回long,所以统一使用JDK默认实现 */ public static long crc32AsLong(@NotNull String input) { return crc32AsLong(input.getBytes(Charsets.UTF_8)); } /** * 对输入字符串进行crc32散列,与php兼容,在64bit系统下返回永远是正数的long * * Guava也有crc32实现, 但返回值无法返回long,所以统一使用JDK默认实现 */ public static long crc32AsLong(@NotNull byte[] input) { CRC32 crc32 = new CRC32(); crc32.update(input); return crc32.getValue(); } ////////////////// 基于Guava的MurMurHash /////////////////// /** * 对输入字符串进行murmur32散列, 返回值可能是负数 */ public static int murmur32AsInt(@NotNull byte[] input) { return Hashing.murmur3_32(MURMUR_SEED).hashBytes(input).asInt(); } /** * 对输入字符串进行murmur32散列, 返回值可能是负数 */ public static int murmur32AsInt(@NotNull String input) { return Hashing.murmur3_32(MURMUR_SEED).hashString(input, Charsets.UTF_8).asInt(); } /** * 对输入字符串进行murmur128散列, 返回值可能是负数 */ public static long murmur128AsLong(@NotNull byte[] input) { return Hashing.murmur3_128(MURMUR_SEED).hashBytes(input).asLong(); } /** * 对输入字符串进行murmur128散列, 返回值可能是负数 */ public static long murmur128AsLong(@NotNull String input) { return Hashing.murmur3_128(MURMUR_SEED).hashString(input, Charsets.UTF_8).asLong(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/text/MoreStringUtil.java ================================================ package com.vip.vjtools.vjkit.text; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; import com.google.common.base.Utf8; import com.vip.vjtools.vjkit.base.annotation.Nullable; import com.vip.vjtools.vjkit.collection.ListUtil; /** * 尽量使用Common Lang StringUtils, 基本覆盖了所有类库的StringUtils * * 本类仅补充少量额外方法, 尤其是针对char的运算 * * 1. split char/chars * * 2. 针对char的replace first/last, startWith,endWith 等 * * @author calvin */ public class MoreStringUtil { /////////// split char 相关 //////// /** * 高性能的Split,针对char的分隔符号,比JDK String自带的高效. * * copy from Commons Lang 3.5 StringUtils 并做优化 * * @see #split(String, char, int) */ public static List split(@Nullable final String str, final char separatorChar) { return split(str, separatorChar, 10); } /** * 高性能的Split,针对char的分隔符号,比JDK String自带的高效. * * copy from Commons Lang 3.5 StringUtils, 做如下优化: * * 1. 最后不做数组转换,直接返回List. * * 2. 可设定List初始大小. * * 3. preserveAllTokens 取默认值false * * @param expectParts 预估分割后的List大小,初始化数据更精准 * * @return 如果为null返回null, 如果为""返回空数组 */ public static List split(@Nullable final String str, final char separatorChar, int expectParts) { if (str == null) { return null; } final int len = str.length(); if (len == 0) { return ListUtil.emptyList(); } final List list = new ArrayList(expectParts); int i = 0; int start = 0; boolean match = false; while (i < len) { if (str.charAt(i) == separatorChar) { if (match) { list.add(str.substring(start, i)); match = false; } start = ++i; continue; } match = true; i++; } if (match) { list.add(str.substring(start, i)); } return list; } /** * 使用多个可选的char作为分割符, 还可以设置omitEmptyStrings,trimResults等配置 * * 设置后的Splitter进行重用,不要每次创建 * * @param separatorChars 比如Unix/Windows的路径分割符 "/\\" * * @see com.google.common.base.Splitter */ public static Splitter charsSplitter(final String separatorChars) { return Splitter.on(CharMatcher.anyOf(separatorChars)); } ////////// 其他 char 相关 /////////// /** * String 有replace(char,char),但缺少单独replace first/last的 */ public static String replaceFirst(@Nullable String s, char sub, char with) { if (s == null) { return null; } int index = s.indexOf(sub); if (index == -1) { return s; } char[] str = s.toCharArray(); str[index] = with; return new String(str); } /** * String 有replace(char,char)替换全部char,但缺少单独replace first/last */ public static String replaceLast(@Nullable String s, char sub, char with) { if (s == null) { return null; } int index = s.lastIndexOf(sub); if (index == -1) { return s; } char[] str = s.toCharArray(); str[index] = with; return new String(str); } /** * 判断字符串是否以字母开头 * * 如果字符串为Null或空,返回false */ public static boolean startWith(@Nullable CharSequence s, char c) { if (StringUtils.isEmpty(s)) { return false; } return s.charAt(0) == c; } /** * 判断字符串是否以字母结尾 * * 如果字符串为Null或空,返回false */ public static boolean endWith(@Nullable CharSequence s, char c) { if (StringUtils.isEmpty(s)) { return false; } return s.charAt(s.length() - 1) == c; } /** * 如果结尾字符为c, 去除掉该字符. */ public static String removeEnd(final String s, final char c) { if (endWith(s, c)) { return s.substring(0, s.length() - 1); } return s; } ///////////// 其他 //////////// /** * 计算字符串被UTF8编码后的字节数 via guava * * @see Utf8#encodedLength(CharSequence) */ public static int utf8EncodedLength(@Nullable CharSequence sequence) { if (StringUtils.isEmpty(sequence)) { return 0; } return Utf8.encodedLength(sequence); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/text/StringBuilderHolder.java ================================================ package com.vip.vjtools.vjkit.text; /** * 参考Netty的InternalThreadLocalMap 与 BigDecimal, 放在threadLocal中重用的StringBuilder, 节约StringBuilder内部的char[] * * 参考文章:《StringBuilder在高性能场景下的正确用法》http://calvin1978.blogcn.com/articles/stringbuilder.html * * 不过仅在String对象较大时才有明显效果,否则抵不上访问ThreadLocal的消耗. * * 当StringBuilder在使用过程中,会调用其他可能也使用StringBuilderHolder的子函数时,需要创建独立的Holder, 否则会共同使用公共的Holder * * 注意:在Netty环境中,使用Netty提供的基于FastThreadLocal的版本 */ public class StringBuilderHolder { // 全局公共的ThreadLocal StringBuilder private static ThreadLocal globalStringBuilder = new ThreadLocal() { @Override protected StringBuilder initialValue() { return new StringBuilder(512); } }; // 独立创建的ThreadLocal的StringBuilder private ThreadLocal stringBuilder = new ThreadLocal() { @Override protected StringBuilder initialValue() { return new StringBuilder(initSize); } }; private int initSize; /** * 创建独立的Holder. * * 用于StringBuilder在使用过程中,会调用其他可能也使用StringBuilderHolder的子函数. * * @param initSize StringBuilder的初始大小, 建议512,如果容量不足将进行扩容,扩容后的数组将一直保留. */ public StringBuilderHolder(int initSize) { this.initSize = initSize; } /** * 获取公共Holder的StringBuilder. * * 当StringBuilder会被连续使用,期间不会调用其他可能也使用StringBuilderHolder的子函数时使用. * * 重置StringBuilder内部的writerIndex, 而char[]保留不动. */ public static StringBuilder getGlobal() { StringBuilder sb = globalStringBuilder.get(); sb.setLength(0); return sb; } /** * 获取独立Holder的StringBuilder. * * 重置StringBuilder内部的writerIndex, 而char[]保留不动. */ public StringBuilder get() { StringBuilder sb = stringBuilder.get(); sb.setLength(0); return sb; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/text/TextValidator.java ================================================ package com.vip.vjtools.vjkit.text; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import com.vip.vjtools.vjkit.base.annotation.Nullable; /** * 通过正则表达判断是否正确的URL, 邮箱,手机号,固定电话,身份证,邮箱等. * * 从AndroidUtilCode的RegexUtils移植, 性能优化将正则表达式为预编译, 并修改了TEL的正则表达式. * * https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/src/main/java/com/blankj/utilcode/util/RegexUtils.java * https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/src/main/java/com/blankj/utilcode/constant/RegexConstants.java */ public class TextValidator { /** * 正则:手机号(简单), 1字头+10位数字即可. */ private static final String REGEX_MOBILE_SIMPLE = "^[1]\\d{10}$"; private static final Pattern PATTERN_REGEX_MOBILE_SIMPLE = Pattern.compile(REGEX_MOBILE_SIMPLE); /** * 正则:手机号(精确), 已知3位前缀+8位数字 *

* 移动:134(0-8)、135、136、137、138、139、147、150、151、152、157、158、159、178、182、183、184、187、188、198 *

*

* 联通:130、131、132、145、155、156、166、171、175、176、185、186 *

*

* 电信:133、153、173、177、180、181、189、199 *

*

* 全球星:1349 *

*

* 虚拟运营商:170 *

*/ public static final String REGEX_MOBILE_EXACT = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(16[6])|(17[0,1,3,5-8])|(18[0-9])|(19[8,9]))\\d{8}$"; private static final Pattern PATTERN_REGEX_MOBILE_EXACT = Pattern.compile(REGEX_MOBILE_EXACT); /** * 正则:固定电话号码,可带区号,然后6至少8位数字 */ private static final String REGEX_TEL = "^(\\d{3,4}-)?\\d{6,8}$"; private static final Pattern PATTERN_REGEX_TEL = Pattern.compile(REGEX_TEL); /** * 正则:身份证号码15位, 数字且关于生日的部分必须正确 */ private static final String REGEX_ID_CARD15 = "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$"; private static final Pattern PATTERN_REGEX_ID_CARD15 = Pattern.compile(REGEX_ID_CARD15); /** * 正则:身份证号码18位, 数字且关于生日的部分必须正确 */ private static final String REGEX_ID_CARD18 = "^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9Xx])$"; private static final Pattern PATTERN_REGEX_ID_CARD18 = Pattern.compile(REGEX_ID_CARD18); /** * 正则:邮箱, 有效字符(不支持中文), 且中间必须有@,后半部分必须有. */ private static final String REGEX_EMAIL = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; private static final Pattern PATTERN_REGEX_EMAIL = Pattern.compile(REGEX_EMAIL); /** * 正则:URL, 必须有"://",前面必须是英文,后面不能有空格 */ private static final String REGEX_URL = "[a-zA-z]+://[^\\s]*"; private static final Pattern PATTERN_REGEX_URL = Pattern.compile(REGEX_URL); /** * 正则:yyyy-MM-dd格式的日期校验,已考虑平闰年 */ private static final String REGEX_DATE = "^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$"; private static final Pattern PATTERN_REGEX_DATE = Pattern.compile(REGEX_DATE); /** * 正则:IP地址 */ private static final String REGEX_IP = "((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)"; private static final Pattern PATTERN_REGEX_IP = Pattern.compile(REGEX_IP); ///////////////// /** * 验证手机号(简单) */ public static boolean isMobileSimple(@Nullable CharSequence input) { return isMatch(PATTERN_REGEX_MOBILE_SIMPLE, input); } /** * 验证手机号(精确) */ public static boolean isMobileExact(@Nullable CharSequence input) { return isMatch(PATTERN_REGEX_MOBILE_EXACT, input); } /** * 验证固定电话号码 */ public static boolean isTel(@Nullable CharSequence input) { return isMatch(PATTERN_REGEX_TEL, input); } /** * 验证15或18位身份证号码 */ public static boolean isIdCard(@Nullable CharSequence input) { return isMatch(PATTERN_REGEX_ID_CARD15, input) || isMatch(PATTERN_REGEX_ID_CARD18, input); } /** * 验证邮箱 */ public static boolean isEmail(@Nullable CharSequence input) { return isMatch(PATTERN_REGEX_EMAIL, input); } /** * 验证URL */ public static boolean isUrl(@Nullable CharSequence input) { return isMatch(PATTERN_REGEX_URL, input); } /** * 验证yyyy-MM-dd格式的日期校验,已考虑平闰年 */ public static boolean isDate(@Nullable CharSequence input) { return isMatch(PATTERN_REGEX_DATE, input); } /** * 验证IP地址 */ public static boolean isIp(@Nullable CharSequence input) { return isMatch(PATTERN_REGEX_IP, input); } public static boolean isMatch(Pattern pattern, CharSequence input) { return StringUtils.isNotEmpty(input) && pattern.matcher(input).matches(); } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/text/WildcardMatcher.java ================================================ // Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package com.vip.vjtools.vjkit.text; import java.util.List; import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; /** * * 从Jodd移植,匹配以通配符比较字符串(比正则表达式简单),以及Ant Path风格如比较目录Path * * https://github.com/oblac/jodd/blob/master/jodd-core/src/main/java/jodd/util/Wildcard.java * * Checks whether a string or path matches a given wildcard pattern. Possible patterns allow to match single characters * ('?') or any count of characters ('*'). Wildcard characters can be escaped (by an '\'). When matching path, deep tree * wildcard also can be used ('**'). *

* This method uses recursive matching, as in linux or windows. regexp works the same. This method is very fast, * comparing to similar implementations. */ public class WildcardMatcher { /** * Checks whether a string matches a given wildcard pattern. * * @param string input string * @param pattern pattern to match * @return true if string matches the pattern, otherwise false */ public static boolean match(CharSequence string, CharSequence pattern) { return match(string, pattern, 0, 0); } /** * Internal matching recursive function. */ private static boolean match(CharSequence string, CharSequence pattern, final int sNdxConst, final int pNdxConst) { int pLen = pattern.length(); if (pLen == 1) { if (pattern.charAt(0) == '*') { // speed-up return true; } } int sLen = string.length(); boolean nextIsNotWildcard = false; int sNdx = sNdxConst; int pNdx = pNdxConst; while (true) { // check if end of string and/or pattern occurred if ((sNdx >= sLen)) { // end of string still may have pending '*' in pattern while ((pNdx < pLen) && (pattern.charAt(pNdx) == '*')) { pNdx++; } return pNdx >= pLen; } if (pNdx >= pLen) { // end of pattern, but not end of the string return false; } char p = pattern.charAt(pNdx); // pattern char // perform logic if (!nextIsNotWildcard) { if (p == '\\') { pNdx++; nextIsNotWildcard = true; continue; } if (p == '?') { sNdx++; pNdx++; continue; } if (p == '*') { char pNext = 0; // next pattern char if (pNdx + 1 < pLen) { pNext = pattern.charAt(pNdx + 1); } if (pNext == '*') { // double '*' have the same effect as one '*' pNdx++; continue; } int i; pNdx++; // find recursively if there is any substring from the end of the // line that matches the rest of the pattern !!! for (i = string.length(); i >= sNdx; i--) { if (match(string, pattern, i, pNdx)) { return true; } } return false; } } else { nextIsNotWildcard = false; } // check if pattern char and string char are equals if (p != string.charAt(sNdx)) { return false; } // everything matches for now, continue sNdx++; pNdx++; } } // ---------------------------------------------------------------- utilities /** * Matches string to at least one pattern. Returns index of matched pattern, or -1 otherwise. * @see #match(CharSequence, CharSequence) */ public static int matchOne(String src, String... patterns) { for (int i = 0; i < patterns.length; i++) { if (match(src, patterns[i])) { return i; } } return -1; } // ---------------------------------------------------------------- path protected static final String PATH_MATCH = "**"; protected static final Splitter PATH_SPLITTER = Splitter.on(CharMatcher.anyOf("/\\")); /** * Matches path to at least one pattern. Returns index of matched pattern or -1 otherwise. * @see #matchPath */ public static int matchPathOne(String platformDependentPath, String... patterns) { for (int i = 0; i < patterns.length; i++) { if (matchPath(platformDependentPath, patterns[i])) { return i; } } return -1; } /** * Matches path against pattern using *, ? and ** wildcards. Both path and the pattern are tokenized on path * separators (both \ and /). '**' represents deep tree wildcard, as in Ant. The separator should match the * corresponding path */ public static boolean matchPath(String path, String pattern) { List pathElements = PATH_SPLITTER.splitToList(path); List patternElements = PATH_SPLITTER.splitToList(pattern); return matchTokens(pathElements.toArray(new String[0]), patternElements.toArray(new String[0])); } /** * Match tokenized string and pattern. */ protected static boolean matchTokens(String[] tokens, String[] patterns) { int patNdxStart = 0; int patNdxEnd = patterns.length - 1; int tokNdxStart = 0; int tokNdxEnd = tokens.length - 1; while ((patNdxStart <= patNdxEnd) && (tokNdxStart <= tokNdxEnd)) { // find first ** String patDir = patterns[patNdxStart]; if (patDir.equals(PATH_MATCH)) { break; } if (!match(tokens[tokNdxStart], patDir)) { return false; } patNdxStart++; tokNdxStart++; } if (tokNdxStart > tokNdxEnd) { for (int i = patNdxStart; i <= patNdxEnd; i++) { // string is finished if (!patterns[i].equals(PATH_MATCH)) { return false; } } return true; } if (patNdxStart > patNdxEnd) { return false; // string is not finished, but pattern is } while ((patNdxStart <= patNdxEnd) && (tokNdxStart <= tokNdxEnd)) { // to the last ** String patDir = patterns[patNdxEnd]; if (patDir.equals(PATH_MATCH)) { break; } if (!match(tokens[tokNdxEnd], patDir)) { return false; } patNdxEnd--; tokNdxEnd--; } if (tokNdxStart > tokNdxEnd) { for (int i = patNdxStart; i <= patNdxEnd; i++) { // string is finished if (!patterns[i].equals(PATH_MATCH)) { return false; } } return true; } while ((patNdxStart != patNdxEnd) && (tokNdxStart <= tokNdxEnd)) { int patIdxTmp = -1; for (int i = patNdxStart + 1; i <= patNdxEnd; i++) { if (patterns[i].equals(PATH_MATCH)) { patIdxTmp = i; break; } } if (patIdxTmp == patNdxStart + 1) { patNdxStart++; // skip **/** situation continue; } // find the pattern between padIdxStart & padIdxTmp in str between strIdxStart & strIdxEnd int patLength = (patIdxTmp - patNdxStart - 1); int strLength = (tokNdxEnd - tokNdxStart + 1); int ndx = -1; strLoop: for (int i = 0; i <= strLength - patLength; i++) { for (int j = 0; j < patLength; j++) { String subPat = patterns[patNdxStart + j + 1]; String subStr = tokens[tokNdxStart + i + j]; if (!match(subStr, subPat)) { continue strLoop; } } ndx = tokNdxStart + i; break; // this is a double-loop, cannot be refactor to break statement directly } if (ndx == -1) { return false; } patNdxStart = patIdxTmp; tokNdxStart = ndx + patLength; } for (int i = patNdxStart; i <= patNdxEnd; i++) { if (!patterns[i].equals(PATH_MATCH)) { return false; } } return true; } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/time/CachingDateFormatter.java ================================================ package com.vip.vjtools.vjkit.time; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.time.FastDateFormat; /** * DateFormat.format()消耗较大,如果时间戳是递增的,而且同一单位内有多次format(),使用用本类减少重复调用. * * copy from Log4j2 DatePatternConverter,进行了优化,根据输出格式是否毫秒级,决定缓存在秒级还是毫秒级. * * 注意如果输出格式为毫秒级的话,根据QPS决定性价比 * * see https://github.com/apache/logging-log4j2/blob/master/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java#L272 * * @author calvin */ public class CachingDateFormatter { private FastDateFormat fastDateFormat; private AtomicReference cachedTime; private boolean onSecond;// 根据时间格式,决定缓存在秒级还是毫秒级 public CachingDateFormatter(String pattern) { this(FastDateFormat.getInstance(pattern)); } public CachingDateFormatter(FastDateFormat fastDateFormat) { this.fastDateFormat = fastDateFormat; onSecond = fastDateFormat.getPattern().indexOf("SSS") == -1; long current = System.currentTimeMillis(); this.cachedTime = new AtomicReference(new CachedTime(current, fastDateFormat.format(current))); } public String format(final long timestampMillis) { CachedTime cached = cachedTime.get(); long timestamp = onSecond ? timestampMillis / 1000 : timestampMillis; if (timestamp != cached.timestamp) { final CachedTime newCachedTime = new CachedTime(timestamp, fastDateFormat.format(timestampMillis)); // 尝试放入cachedTime cachedTime.compareAndSet(cached, newCachedTime); // 与log4j2做法不同,无论是否放入成功,都使用自己的值 cached = newCachedTime; } return cached.formatted; } static final class CachedTime { public long timestamp; public String formatted; public CachedTime(final long timestamp, String formatted) { this.timestamp = timestamp; this.formatted = formatted; } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/time/ClockUtil.java ================================================ package com.vip.vjtools.vjkit.time; import java.util.Date; /** * 日期提供者, 使用它而不是直接取得系统时间, 方便测试. * * 平时使用DEFAULT,测试时替换为DummyClock,可准确控制时间变化而不用Thread.sleep()等待时间流逝. */ public class ClockUtil { private static Clock instance = new DefaultClock(); /** * 计算流逝的时间 */ public static long elapsedTime(long beginTime) { return currentTimeMillis() - beginTime; } /** * 切换为DummyClock,使用系统时间为初始时间, 单个测试完成后需要调用useDefaultClock()切换回去. */ public static synchronized DummyClock useDummyClock() { instance = new DummyClock(); return (DummyClock) instance; } /** * 切换为DummyClock,单个测试完成后需要调用useDefaultClock()切换回去. */ public static synchronized DummyClock useDummyClock(long timeStampMills) { instance = new DummyClock(timeStampMills); return (DummyClock) instance; } /** * 切换为DummyClock,单个测试完成后需要调用useDefaultClock()切换回去. */ public static synchronized DummyClock useDummyClock(Date date) { instance = new DummyClock(date); return (DummyClock) instance; } /** * 重置为默认Clock */ public static synchronized void useDefaultClock() { instance = new DefaultClock(); } /** * 系统当前时间 */ public static Date currentDate() { return instance.currentDate(); } /** * 系统当前时间戳 */ public static long currentTimeMillis() { return instance.currentTimeMillis(); } /** * 操作系统启动到现在的纳秒数,与系统时间是完全独立的两个时间体系 */ public static long nanoTime() { return instance.nanoTime(); } public interface Clock { /** * 系统当前时间 */ Date currentDate(); /** * 系统当前时间戳 */ long currentTimeMillis(); /** * 操作系统启动到现在的纳秒数,与系统时间是完全独立的两个时间体系 */ long nanoTime(); } /** * 默认时间提供者,返回当前的时间,线程安全。 */ public static class DefaultClock implements Clock { @Override public Date currentDate() { return new Date(); } @Override public long currentTimeMillis() { return System.currentTimeMillis(); } @Override public long nanoTime() { return System.nanoTime(); } } /** * 可配置的时间提供者,用于测试. */ public static class DummyClock implements Clock { private long time; private long nanoTme; public DummyClock() { this(System.currentTimeMillis()); } public DummyClock(Date date) { this(date.getTime()); } public DummyClock(long time) { this.time = time; this.nanoTme = System.nanoTime(); } @Override public Date currentDate() { return new Date(time); } @Override public long currentTimeMillis() { return time; } /** * 获取nanoTime */ @Override public long nanoTime() { return nanoTme; } /** * 重新设置日期. */ public void updateNow(Date newDate) { time = newDate.getTime(); } /** * 重新设置时间. */ public void updateNow(long newTime) { this.time = newTime; } /** * 滚动时间. */ public void increaseTime(int millis) { time += millis; } /** * 滚动时间. */ public void decreaseTime(int millis) { time -= millis; } /** * 设置nanoTime. */ public void setNanoTime(long nanoTime) { this.nanoTme = nanoTime; } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/time/DateFormatUtil.java ================================================ package com.vip.vjtools.vjkit.time; import java.text.ParseException; import java.util.Date; import org.apache.commons.lang3.time.DurationFormatUtils; import org.apache.commons.lang3.time.FastDateFormat; import com.vip.vjtools.vjkit.base.annotation.NotNull; /** * Date的parse()与format(), 采用Apache Common Lang中线程安全, 性能更佳的FastDateFormat * * 注意Common Lang版本,3.5版才使用StringBuilder,3.4及以前使用StringBuffer. * * 1. 常用格式的FastDateFormat定义, 常用格式直接使用这些FastDateFormat * * 2. 日期格式不固定时的String<->Date 转换函数. * * 3. 打印时间间隔,如"01:10:10",以及用户友好的版本,比如"刚刚","10分钟前" * * @see FastDateFormat#parse(String) * @see FastDateFormat#format(java.util.Date) * @see FastDateFormat#format(long) */ public class DateFormatUtil { // 以T分隔日期和时间,并带时区信息,符合ISO8601规范 public static final String PATTERN_ISO = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"; public static final String PATTERN_ISO_ON_SECOND = "yyyy-MM-dd'T'HH:mm:ssZZ"; public static final String PATTERN_ISO_ON_DATE = "yyyy-MM-dd"; // 以空格分隔日期和时间,不带时区信息 public static final String PATTERN_DEFAULT = "yyyy-MM-dd HH:mm:ss.SSS"; public static final String PATTERN_DEFAULT_ON_SECOND = "yyyy-MM-dd HH:mm:ss"; // 使用工厂方法FastDateFormat.getInstance(), 从缓存中获取实例 // 以T分隔日期和时间,并带时区信息,符合ISO8601规范 public static final FastDateFormat ISO_FORMAT = FastDateFormat.getInstance(PATTERN_ISO); public static final FastDateFormat ISO_ON_SECOND_FORMAT = FastDateFormat.getInstance(PATTERN_ISO_ON_SECOND); public static final FastDateFormat ISO_ON_DATE_FORMAT = FastDateFormat.getInstance(PATTERN_ISO_ON_DATE); // 以空格分隔日期和时间,不带时区信息 public static final FastDateFormat DEFAULT_FORMAT = FastDateFormat.getInstance(PATTERN_DEFAULT); public static final FastDateFormat DEFAULT_ON_SECOND_FORMAT = FastDateFormat.getInstance(PATTERN_DEFAULT_ON_SECOND); /** * 分析日期字符串, 仅用于pattern不固定的情况. * * 否则直接使用DateFormats中封装好的FastDateFormat. * * FastDateFormat.getInstance()已经做了缓存,不会每次创建对象,但直接使用对象仍然能减少在缓存中的查找. */ public static Date parseDate(@NotNull String pattern, @NotNull String dateString) throws ParseException { return FastDateFormat.getInstance(pattern).parse(dateString); } /** * 格式化日期, 仅用于pattern不固定的情况. * * 否则直接使用本类中封装好的FastDateFormat. * * FastDateFormat.getInstance()已经做了缓存,不会每次创建对象,但直接使用对象仍然能减少在缓存中的查找. */ public static String formatDate(@NotNull String pattern, @NotNull Date date) { return FastDateFormat.getInstance(pattern).format(date); } /** * 格式化日期, 仅用于不固定pattern不固定的情况. * * 否否则直接使用本类中封装好的FastDateFormat. * * FastDateFormat.getInstance()已经做了缓存,不会每次创建对象,但直接使用对象仍然能减少在缓存中的查找. */ public static String formatDate(@NotNull String pattern, long date) { return FastDateFormat.getInstance(pattern).format(date); } /////// 格式化间隔时间///////// /** * 按HH:mm:ss.SSS格式,格式化时间间隔. * * endDate必须大于startDate,间隔可大于1天, * * @see DurationFormatUtils */ public static String formatDuration(@NotNull Date startDate, @NotNull Date endDate) { return DurationFormatUtils.formatDurationHMS(endDate.getTime() - startDate.getTime()); } /** * 按HH:mm:ss.SSS格式,格式化时间间隔 * * 单位为毫秒,必须大于0,可大于1天 * * @see DurationFormatUtils */ public static String formatDuration(long durationMillis) { return DurationFormatUtils.formatDurationHMS(durationMillis); } /** * 按HH:mm:ss格式,格式化时间间隔 * * endDate必须大于startDate,间隔可大于1天 * * @see DurationFormatUtils */ public static String formatDurationOnSecond(@NotNull Date startDate, @NotNull Date endDate) { return DurationFormatUtils.formatDuration(endDate.getTime() - startDate.getTime(), "HH:mm:ss"); } /** * 按HH:mm:ss格式,格式化时间间隔 * * 单位为毫秒,必须大于0,可大于1天 * * @see DurationFormatUtils */ public static String formatDurationOnSecond(long durationMillis) { return DurationFormatUtils.formatDuration(durationMillis, "HH:mm:ss"); } //////// 打印用于页面显示的用户友好,与当前时间比的时间差 /** * 打印用户友好的,与当前时间相比的时间差,如刚刚,5分钟前,今天XXX,昨天XXX * * copy from AndroidUtilCode */ public static String formatFriendlyTimeSpanByNow(@NotNull Date date) { return formatFriendlyTimeSpanByNow(date.getTime()); } /** * 打印用户友好的,与当前时间相比的时间差,如刚刚,5分钟前,今天XXX,昨天XXX * * copy from AndroidUtilCode */ public static String formatFriendlyTimeSpanByNow(long timeStampMillis) { long now = ClockUtil.currentTimeMillis(); long span = now - timeStampMillis; if (span < 0) { // 'c' 日期和时间,被格式化为 "%ta %tb %td %tT %tZ %tY",例如 "Sun Jul 20 16:17:00 EDT 1969"。 return String.format("%tc", timeStampMillis); } if (span < DateUtil.MILLIS_PER_SECOND) { return "刚刚"; } else if (span < DateUtil.MILLIS_PER_MINUTE) { return String.format("%d秒前", span / DateUtil.MILLIS_PER_SECOND); } else if (span < DateUtil.MILLIS_PER_HOUR) { return String.format("%d分钟前", span / DateUtil.MILLIS_PER_MINUTE); } // 获取当天00:00 long wee = DateUtil.beginOfDate(new Date(now)).getTime(); if (timeStampMillis >= wee) { // 'R' 24 小时制的时间,被格式化为 "%tH:%tM" return String.format("今天%tR", timeStampMillis); } else if (timeStampMillis >= wee - DateUtil.MILLIS_PER_DAY) { return String.format("昨天%tR", timeStampMillis); } else { // 'F' ISO 8601 格式的完整日期,被格式化为 "%tY-%tm-%td"。 return String.format("%tF", timeStampMillis); } } } ================================================ FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/time/DateUtil.java ================================================ package com.vip.vjtools.vjkit.time; import java.util.Calendar; import java.util.Date; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import com.vip.vjtools.vjkit.base.annotation.NotNull; /** * 日期工具类. * * 在不方便使用joda-time时,使用本类降低Date处理的复杂度与性能消耗, 封装Common Lang及移植Jodd的最常用日期方法 */ public class DateUtil { public static final long MILLIS_PER_SECOND = 1000; // Number of milliseconds in a standard second. public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND; // Number of milliseconds in a standard minute. public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE; // Number of milliseconds in a standard hour. public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR; // Number of milliseconds in a standard day. private static final int[] MONTH_LENGTH = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; //////// 日期比较 /////////// /** * 是否同一天. * * @see DateUtils#isSameDay(Date, Date) */ public static boolean isSameDay(@NotNull final Date date1, @NotNull final Date date2) { return DateUtils.isSameDay(date1, date2); } /** * 是否同一时刻. */ public static boolean isSameTime(@NotNull final Date date1, @NotNull final Date date2) { // date.getMillisOf() 比date.getTime()快 return date1.compareTo(date2) == 0; } /** * 判断日期是否在范围内,包含相等的日期 */ public static boolean isBetween(@NotNull final Date date, @NotNull final Date start, @NotNull final Date end) { if (date == null || start == null || end == null || start.after(end)) { throw new IllegalArgumentException("some date parameters is null or dateBein after dateEnd"); } return !date.before(start) && !date.after(end); } //////////// 往前往后滚动时间////////////// /** * 加一月 */ public static Date addMonths(@NotNull final Date date, int amount) { return DateUtils.addMonths(date, amount); } /** * 减一月 */ public static Date subMonths(@NotNull final Date date, int amount) { return DateUtils.addMonths(date, -amount); } /** * 加一周 */ public static Date addWeeks(@NotNull final Date date, int amount) { return DateUtils.addWeeks(date, amount); } /** * 减一周 */ public static Date subWeeks(@NotNull final Date date, int amount) { return DateUtils.addWeeks(date, -amount); } /** * 加一天 */ public static Date addDays(@NotNull final Date date, final int amount) { return DateUtils.addDays(date, amount); } /** * 减一天 */ public static Date subDays(@NotNull final Date date, int amount) { return DateUtils.addDays(date, -amount); } /** * 加一小时 */ public static Date addHours(@NotNull final Date date, int amount) { return DateUtils.addHours(date, amount); } /** * 减一小时 */ public static Date subHours(@NotNull final Date date, int amount) { return DateUtils.addHours(date, -amount); } /** * 加一分钟 */ public static Date addMinutes(@NotNull final Date date, int amount) { return DateUtils.addMinutes(date, amount); } /** * 减一分钟 */ public static Date subMinutes(@NotNull final Date date, int amount) { return DateUtils.addMinutes(date, -amount); } /** * 终于到了,续一秒. */ public static Date addSeconds(@NotNull final Date date, int amount) { return DateUtils.addSeconds(date, amount); } /** * 减一秒. */ public static Date subSeconds(@NotNull final Date date, int amount) { return DateUtils.addSeconds(date, -amount); } //////////// 直接设置时间////////////// /** * 设置年份, 公元纪年. */ public static Date setYears(@NotNull final Date date, int amount) { return DateUtils.setYears(date, amount); } /** * 设置月份, 1-12. */ public static Date setMonths(@NotNull final Date date, int amount) { if (amount < 1 || amount > 12) { throw new IllegalArgumentException("monthOfYear must be in the range[ 1, 12]"); } return DateUtils.setMonths(date, amount - 1); } /** * 设置日期, 1-31. */ public static Date setDays(@NotNull final Date date, int amount) { return DateUtils.setDays(date, amount); } /** * 设置小时, 0-23. */ public static Date setHours(@NotNull final Date date, int amount) { return DateUtils.setHours(date, amount); } /** * 设置分钟, 0-59. */ public static Date setMinutes(@NotNull final Date date, int amount) { return DateUtils.setMinutes(date, amount); } /** * 设置秒, 0-59. */ public static Date setSeconds(@NotNull final Date date, int amount) { return DateUtils.setSeconds(date, amount); } /** * 设置毫秒. */ public static Date setMilliseconds(@NotNull final Date date, int amount) { return DateUtils.setMilliseconds(date, amount); } ///// 获取日期的位置////// /** * 获得日期是一周的第几天. 已改为中国习惯,1 是Monday,而不是Sunday. */ public static int getDayOfWeek(@NotNull final Date date) { int result = getWithMondayFirst(date, Calendar.DAY_OF_WEEK); return result == 1 ? 7 : result - 1; } /** * 获得日期是一年的第几天,返回值从1开始 */ public static int getDayOfYear(@NotNull final Date date) { return get(date, Calendar.DAY_OF_YEAR); } /** * 获得日期是一月的第几周,返回值从1开始. * * 开始的一周,只要有一天在那个月里都算. 已改为中国习惯,1 是Monday,而不是Sunday */ public static int getWeekOfMonth(@NotNull final Date date) { return getWithMondayFirst(date, Calendar.WEEK_OF_MONTH); } /** * 获得日期是一年的第几周,返回值从1开始. * * 开始的一周,只要有一天在那一年里都算.已改为中国习惯,1 是Monday,而不是Sunday */ public static int getWeekOfYear(@NotNull final Date date) { return getWithMondayFirst(date, Calendar.WEEK_OF_YEAR); } private static int get(final Date date, int field) { Validate.notNull(date, "The date must not be null"); Calendar cal = Calendar.getInstance(); cal.setTime(date); return cal.get(field); } private static int getWithMondayFirst(final Date date, int field) { Validate.notNull(date, "The date must not be null"); Calendar cal = Calendar.getInstance(); cal.setFirstDayOfWeek(Calendar.MONDAY); cal.setTime(date); return cal.get(field); } ///// 获得往前往后的日期////// /** * 2016-11-10 07:33:23, 则返回2016-1-1 00:00:00 */ public static Date beginOfYear(@NotNull final Date date) { return DateUtils.truncate(date, Calendar.YEAR); } /** * 2016-11-10 07:33:23, 则返回2016-12-31 23:59:59.999 */ public static Date endOfYear(@NotNull final Date date) { return new Date(nextYear(date).getTime() - 1); } /** * 2016-11-10 07:33:23, 则返回2017-1-1 00:00:00 */ public static Date nextYear(@NotNull final Date date) { return DateUtils.ceiling(date, Calendar.YEAR); } /** * 2016-11-10 07:33:23, 则返回2016-11-1 00:00:00 */ public static Date beginOfMonth(@NotNull final Date date) { return DateUtils.truncate(date, Calendar.MONTH); } /** * 2016-11-10 07:33:23, 则返回2016-11-30 23:59:59.999 */ public static Date endOfMonth(@NotNull final Date date) { return new Date(nextMonth(date).getTime() - 1); } /** * 2016-11-10 07:33:23, 则返回2016-12-1 00:00:00 */ public static Date nextMonth(@NotNull final Date date) { return DateUtils.ceiling(date, Calendar.MONTH); } /** * 2017-1-20 07:33:23, 则返回2017-1-16 00:00:00 */ public static Date beginOfWeek(@NotNull final Date date) { return DateUtils.truncate(DateUtil.subDays(date, DateUtil.getDayOfWeek(date) - 1), Calendar.DATE); } /** * 2017-1-20 07:33:23, 则返回2017-1-22 23:59:59.999 */ public static Date endOfWeek(@NotNull final Date date) { return new Date(nextWeek(date).getTime() - 1); } /** * 2017-1-23 07:33:23, 则返回2017-1-22 00:00:00 */ public static Date nextWeek(@NotNull final Date date) { return DateUtils.truncate(DateUtil.addDays(date, 8 - DateUtil.getDayOfWeek(date)), Calendar.DATE); } /** * 2016-11-10 07:33:23, 则返回2016-11-10 00:00:00 */ public static Date beginOfDate(@NotNull final Date date) { return DateUtils.truncate(date, Calendar.DATE); } /** * 2017-1-23 07:33:23, 则返回2017-1-23 23:59:59.999 */ public static Date endOfDate(@NotNull final Date date) { return new Date(nextDate(date).getTime() - 1); } /** * 2016-11-10 07:33:23, 则返回2016-11-11 00:00:00 */ public static Date nextDate(@NotNull final Date date) { return DateUtils.ceiling(date, Calendar.DATE); } /** * 2016-12-10 07:33:23, 则返回2016-12-10 07:00:00 */ public static Date beginOfHour(@NotNull final Date date) { return DateUtils.truncate(date, Calendar.HOUR_OF_DAY); } /** * 2017-1-23 07:33:23, 则返回2017-1-23 07:59:59.999 */ public static Date endOfHour(@NotNull final Date date) { return new Date(nextHour(date).getTime() - 1); } /** * 2016-12-10 07:33:23, 则返回2016-12-10 08:00:00 */ public static Date nextHour(@NotNull final Date date) { return DateUtils.ceiling(date, Calendar.HOUR_OF_DAY); } /** * 2016-12-10 07:33:23, 则返回2016-12-10 07:33:00 */ public static Date beginOfMinute(@NotNull final Date date) { return DateUtils.truncate(date, Calendar.MINUTE); } /** * 2017-1-23 07:33:23, 则返回2017-1-23 07:33:59.999 */ public static Date endOfMinute(@NotNull final Date date) { return new Date(nextMinute(date).getTime() - 1); } /** * 2016-12-10 07:33:23, 则返回2016-12-10 07:34:00 */ public static Date nextMinute(@NotNull final Date date) { return DateUtils.ceiling(date, Calendar.MINUTE); } ////// 闰年及每月天数/////// /** * 是否闰年. */ public static boolean isLeapYear(@NotNull final Date date) { return isLeapYear(get(date, Calendar.YEAR)); } /** * 是否闰年,copy from Jodd Core的TimeUtil * * 参数是公元计数, 如2016 */ public static boolean isLeapYear(int y) { boolean result = false; if (((y % 4) == 0) && // must be divisible by 4... ((y < 1582) || // and either before reform year... ((y % 100) != 0) || // or not a century... ((y % 400) == 0))) { // or a multiple of 400... result = true; // for leap year. } return result; } /** * 获取某个月有多少天, 考虑闰年等因数, 移植Jodd Core的TimeUtil */ public static int getMonthLength(@NotNull final Date date) { int year = get(date, Calendar.YEAR); int month = get(date, Calendar.MONTH); return getMonthLength(year, month); } /** * 获取某个月有多少天, 考虑闰年等因数, 移植Jodd Core的TimeUtil */ public static int getMonthLength(int year, int month) { if ((month < 1) || (month > 12)) { throw new IllegalArgumentException("Invalid month: " + month); } if (month == 2) { return isLeapYear(year) ? 29 : 28; } return MONTH_LENGTH[month]; } } ================================================ FILE: vjkit/src/main/resources/sys_data_mask.properties ================================================ Name=chineseName,userName Phone=phone,phoneNum,mobile,tel,telephone IDCard=IDCard,IdNo BankCard=bankCard Address=address,addr Email=mail,email Captcha=captcha Passport=passport Account=account Password=password,passwd ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/test/data/RandomData.java ================================================ package com.vip.vjtools.test.data; import java.util.Collections; import java.util.List; import java.util.Random; /** * 随机测试数据生成工具类. */ public class RandomData { private static Random random = new Random(); /** * 返回随机ID. */ public static long randomId() { return random.nextLong(); } /** * 返回随机名称, prefix字符串+5位随机数字. */ public static String randomName(String prefix) { return prefix + random.nextInt(10000); } /** * 从输入list中随机返回一个对象. */ public static T randomOne(List list) { Collections.shuffle(list); return list.get(0); } /** * 从输入list中随机返回n个对象. */ public static List randomSome(List list, int n) { Collections.shuffle(list); return list.subList(0, n); } /** * 从输入list中随机返回随机个对象. */ public static List randomSome(List list) { int size = random.nextInt(list.size()); if (size == 0) { size = 1; } return randomSome(list, size); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/test/log/LogbackListAppender.java ================================================ package com.vip.vjtools.test.log; import java.util.List; import org.slf4j.LoggerFactory; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.UnsynchronizedAppenderBase; /** * 在List中保存日志的Appender, 用于测试Logback的日志输出. * * 在测试开始前, 使用任意一种addToLogger()方法将此appender添加到需要侦听的logger中. */ public class LogbackListAppender extends UnsynchronizedAppenderBase { private final List logs = Lists.newArrayList(); public LogbackListAppender() { start(); } public static LogbackListAppender create(Class loggerClass) { LogbackListAppender appender = new LogbackListAppender(); appender.addToLogger(loggerClass); return appender; } public static LogbackListAppender create(String loggerName) { LogbackListAppender appender = new LogbackListAppender(); appender.addToLogger(loggerName); return appender; } @Override protected void append(ILoggingEvent e) { logs.add(e); } /** * 返回之前append的第一个log. */ public ILoggingEvent getFirstLog() { if (logs.isEmpty()) { return null; } return logs.get(0); } /** * 返回之前append的第一个log的内容. */ public String getFirstMessage() { if (logs.isEmpty()) { return null; } return getFirstLog().getFormattedMessage(); } /** * 返回之前append的最后一个log. */ public ILoggingEvent getLastLog() { if (logs.isEmpty()) { return null; } return Iterables.getLast(logs); } /** * 返回之前append的最后一个log的内容. */ public String getLastMessage() { if (logs.isEmpty()) { return null; } return getLastLog().getFormattedMessage(); } /** * 返回之前append的所有log. */ public List getAllLogs() { return logs; } /** * 返回Log的数量。 */ public int getLogsCount() { return logs.size(); } /** * 判断是否有log. */ public boolean isEmpty() { return logs.isEmpty(); } /** * 清除之前append的所有log. */ public void clearLogs() { logs.clear(); } /** * 将此appender添加到logger中. */ public void addToLogger(String loggerName) { Logger logger = (Logger) LoggerFactory.getLogger(loggerName); logger.addAppender(this); } /** * 将此appender添加到logger中. */ public void addToLogger(Class loggerClass) { Logger logger = (Logger) LoggerFactory.getLogger(loggerClass); logger.addAppender(this); } /** * 将此appender添加到root logger中. */ public void addToRootLogger() { Logger logger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); logger.addAppender(this); } /** * 将此appender从logger中移除. */ public void removeFromLogger(String loggerName) { Logger logger = (Logger) LoggerFactory.getLogger(loggerName); logger.detachAppender(this); } /** * 将此appender从logger中移除. */ public void removeFromLogger(Class loggerClass) { Logger logger = (Logger) LoggerFactory.getLogger(loggerClass); logger.detachAppender(this); } /** * 将此appender从root logger中移除. */ public void removeFromRootLogger() { Logger logger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); logger.detachAppender(this); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/test/log/LogbackListAppenderTest.java ================================================ package com.vip.vjtools.test.log; import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogbackListAppenderTest { @Test public void normal() { String testString1 = "Hello"; String testString2 = "World"; LogbackListAppender appender = new LogbackListAppender(); appender.addToLogger(LogbackListAppenderTest.class); // null assertThat(appender.getFirstLog()).isNull(); assertThat(appender.getLastLog()).isNull(); assertThat(appender.getFirstMessage()).isNull(); assertThat(appender.getFirstMessage()).isNull(); Logger logger = LoggerFactory.getLogger(LogbackListAppenderTest.class); logger.warn(testString1); logger.warn(testString2); // getFirstLog/getLastLog assertThat(appender.getFirstLog().getMessage()).isEqualTo(testString1); assertThat(appender.getLastLog().getMessage()).isEqualTo(testString2); assertThat(appender.getFirstMessage()).isEqualTo(testString1); assertThat(appender.getLastMessage()).isEqualTo(testString2); // getAllLogs assertThat(appender.getLogsCount()).isEqualTo(2); assertThat(appender.getAllLogs()).hasSize(2); assertThat(appender.getAllLogs().get(1).getMessage()).isEqualTo(testString2); // clearLogs appender.clearLogs(); assertThat(appender.getFirstLog()).isNull(); assertThat(appender.getLastLog()).isNull(); } @Test public void addAndRemoveAppender() { String testString = "Hello"; Logger logger = LoggerFactory.getLogger(LogbackListAppenderTest.class); LogbackListAppender appender = new LogbackListAppender(); // class appender.addToLogger(LogbackListAppenderTest.class); logger.warn(testString); assertThat(appender.getFirstLog()).isNotNull(); appender.clearLogs(); appender.removeFromLogger(LogbackListAppenderTest.class); logger.warn(testString); assertThat(appender.getFirstLog()).isNull(); // name appender.clearLogs(); appender.addToLogger("com.vip.vjtools.test.log"); logger.warn(testString); assertThat(appender.getFirstLog()).isNotNull(); appender.clearLogs(); appender.removeFromLogger("com.vip.vjtools.test.log"); logger.warn(testString); assertThat(appender.getFirstLog()).isNull(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/test/rule/TestProgress.java ================================================ package com.vip.vjtools.test.rule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; /** * 在Console里打印Case的开始与结束,更容易分清Console里的日志归属于哪个Case. * * @author calvin */ public class TestProgress extends TestWatcher { @Override protected void starting(Description description) { System.out.println("\n[Test Case starting] " + description.getTestClass().getSimpleName() + "." + description.getMethodName() + "()\n"); } @Override protected void finished(Description description) { System.out.println("\n[Test Case finished] " + description.getTestClass().getSimpleName() + "." + description.getMethodName() + "()\n"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/BooleanUtilTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class BooleanUtilTest { @Test public void test() { assertThat(BooleanUtil.toBoolean("True")).isTrue(); assertThat(BooleanUtil.toBoolean("tre")).isFalse(); assertThat(BooleanUtil.toBoolean(null)).isFalse(); assertThat(BooleanUtil.toBooleanObject("True")).isTrue(); assertThat(BooleanUtil.toBooleanObject("tre")).isFalse(); assertThat(BooleanUtil.toBooleanObject(null)).isNull(); assertThat(BooleanUtil.parseGeneralString("1", false)).isFalse(); assertThat(BooleanUtil.parseGeneralString("y", false)).isTrue(); assertThat(BooleanUtil.parseGeneralString("y")).isTrue(); assertThat(BooleanUtil.parseGeneralString("x")).isNull(); } @Test public void logic() { assertThat(BooleanUtil.negate(Boolean.TRUE)).isFalse(); assertThat(BooleanUtil.negate(Boolean.FALSE)).isTrue(); assertThat(BooleanUtil.negate(true)).isFalse(); assertThat(BooleanUtil.negate(false)).isTrue(); assertThat(BooleanUtil.or(Boolean.TRUE, Boolean.TRUE, Boolean.FALSE)).isTrue(); assertThat(BooleanUtil.or(false, false, false)).isFalse(); assertThat(BooleanUtil.and(Boolean.TRUE, Boolean.TRUE, Boolean.FALSE)).isFalse(); assertThat(BooleanUtil.and(true, true, true)).isTrue(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/EnumUtilTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.vip.vjtools.vjkit.collection.ListUtil; public class EnumUtilTest { public enum Options { A, B, C, D; } @Test public void testBits() { assertThat(EnumUtil.generateBits(Options.class, Options.A)).isEqualTo(1); assertThat(EnumUtil.generateBits(Options.class, Options.A, Options.B)).isEqualTo(3); assertThat(EnumUtil.generateBits(Options.class, ListUtil.newArrayList(Options.A))).isEqualTo(1); assertThat(EnumUtil.generateBits(Options.class, ListUtil.newArrayList(Options.A, Options.B))).isEqualTo(3); assertThat(EnumUtil.processBits(Options.class, 3)).hasSize(2).containsExactly(Options.A, Options.B); assertThat(EnumUtil.processBits(Options.class, EnumUtil.generateBits(Options.class, Options.A, Options.C, Options.D))).hasSize(3) .containsExactly(Options.A, Options.C, Options.D); } @Test public void testString() { assertThat(EnumUtil.toString(Options.A)).isEqualTo("A"); assertThat(EnumUtil.fromString(Options.class, "B")).isEqualTo(Options.B); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/ExceptionUtilTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.*; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.concurrent.ExecutionException; import org.junit.Test; import com.vip.vjtools.vjkit.base.type.CloneableException; import com.vip.vjtools.vjkit.base.type.CloneableRuntimeException; import com.vip.vjtools.vjkit.base.type.UncheckedException; public class ExceptionUtilTest { private static RuntimeException TIMEOUT_EXCEPTION = ExceptionUtil.setStackTrace(new RuntimeException("Timeout"), ExceptionUtilTest.class, "hello"); private static CloneableException TIMEOUT_EXCEPTION2 = new CloneableException("Timeout") .setStackTrace(ExceptionUtilTest.class, "hello"); private static CloneableRuntimeException TIMEOUT_EXCEPTION3 = new CloneableRuntimeException("Timeout") .setStackTrace(ExceptionUtilTest.class, "hello"); @Test public void unchecked() { // convert Exception to RuntimeException with cause Exception exception = new Exception("my exception"); try { ExceptionUtil.unchecked(exception); fail("should fail before"); } catch (Throwable t) { assertThat(t.getCause()).isSameAs(exception); } // do nothing of Error Error error = new LinkageError(); try { ExceptionUtil.unchecked(error); fail("should fail before"); } catch (Throwable t) { assertThat(t).isSameAs(error); } // do nothing of RuntimeException RuntimeException runtimeException = new RuntimeException("haha"); try { ExceptionUtil.unchecked(runtimeException); fail("should fail before"); } catch (Throwable t) { assertThat(t).isSameAs(runtimeException); } } @Test public void unwrap() { RuntimeException re = new RuntimeException("my runtime"); assertThat(ExceptionUtil.unwrap(re)).isSameAs(re); ExecutionException ee = new ExecutionException(re); assertThat(ExceptionUtil.unwrap(ee)).isSameAs(re); InvocationTargetException ie = new InvocationTargetException(re); assertThat(ExceptionUtil.unwrap(ie)).isSameAs(re); Exception e = new Exception("my exception"); ExecutionException ee2 = new ExecutionException(e); try { ExceptionUtil.unwrapAndUnchecked(ee2); } catch (Throwable t) { assertThat(t).isInstanceOf(UncheckedException.class).hasCauseExactlyInstanceOf(Exception.class); } } @Test public void getStackTraceAsString() { Exception exception = new Exception("my exception"); RuntimeException runtimeException = new RuntimeException(exception); String stack = ExceptionUtil.stackTraceText(runtimeException); System.out.println(stack); } @Test public void cause() { IOException ioexception = new IOException("my exception"); IllegalStateException illegalStateException = new IllegalStateException(ioexception); RuntimeException runtimeException = new RuntimeException(illegalStateException); assertThat(ExceptionUtil.isCausedBy(runtimeException, IOException.class)).isTrue(); assertThat(ExceptionUtil.isCausedBy(runtimeException, IllegalStateException.class, IOException.class)).isTrue(); assertThat(ExceptionUtil.isCausedBy(runtimeException, Exception.class)).isTrue(); assertThat(ExceptionUtil.isCausedBy(runtimeException, IllegalAccessException.class)).isFalse(); assertThat(ExceptionUtil.findCause(runtimeException, IllegalStateException.class)) .isSameAs(illegalStateException); assertThat(ExceptionUtil.findCause(runtimeException, IOException.class)).isSameAs(ioexception); assertThat(ExceptionUtil.findCause(runtimeException, UncheckedException.class)).isNull(); } @Test public void getRootCause() { IOException ioexception = new IOException("my exception"); IllegalStateException illegalStateException = new IllegalStateException(ioexception); RuntimeException runtimeException = new RuntimeException(illegalStateException); assertThat(ExceptionUtil.getRootCause(runtimeException)).isSameAs(ioexception); // 无cause assertThat(ExceptionUtil.getRootCause(ioexception)).isSameAs(ioexception); } @Test public void buildMessage() { IOException ioexception = new IOException("my exception"); assertThat(ExceptionUtil.toStringWithShortName(ioexception)).isEqualTo("IOException: my exception"); assertThat(ExceptionUtil.toStringWithShortName(null)).isEqualTo(""); RuntimeException runtimeExcetpion = new RuntimeException("my runtimeException", ioexception); assertThat(ExceptionUtil.toStringWithRootCause(runtimeExcetpion)) .isEqualTo("RuntimeException: my runtimeException; <---IOException: my exception"); assertThat(ExceptionUtil.toStringWithRootCause(null)).isEqualTo(""); // 无cause assertThat(ExceptionUtil.toStringWithRootCause(ioexception)).isEqualTo("IOException: my exception"); } @Test public void clearStackTrace() { IOException ioexception = new IOException("my exception"); RuntimeException runtimeException = new RuntimeException(ioexception); System.out.println(ExceptionUtil.stackTraceText(ExceptionUtil.clearStackTrace(runtimeException))); } @Test public void staticException() { assertThat(ExceptionUtil.stackTraceText(TIMEOUT_EXCEPTION)).hasLineCount(2) .contains("java.lang.RuntimeException: Timeout") .contains("at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)"); assertThat(ExceptionUtil.stackTraceText(TIMEOUT_EXCEPTION2)).hasLineCount(2) .contains("com.vip.vjtools.vjkit.base.type.CloneableException: Timeout") .contains("at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)"); CloneableException timeoutException = TIMEOUT_EXCEPTION2.clone("Timeout for 30ms"); assertThat(ExceptionUtil.stackTraceText(timeoutException)).hasLineCount(2) .contains("com.vip.vjtools.vjkit.base.type.CloneableException: Timeout for 30ms") .contains("at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)"); assertThat(ExceptionUtil.stackTraceText(TIMEOUT_EXCEPTION3)).hasLineCount(2) .contains("com.vip.vjtools.vjkit.base.type.CloneableRuntimeException: Timeout") .contains("at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)"); CloneableRuntimeException timeoutRuntimeException = TIMEOUT_EXCEPTION3.clone("Timeout for 40ms"); assertThat(ExceptionUtil.stackTraceText(timeoutRuntimeException)).hasLineCount(2) .contains("com.vip.vjtools.vjkit.base.type.CloneableRuntimeException: Timeout for 40ms") .contains("at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/MoreValidateTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import org.junit.Test; public class MoreValidateTest { @Test public void test() { // int int a = MoreValidate.nonNegative("x", 0); assertThat(a).isEqualTo(0); a = MoreValidate.nonNegative("x", 1); assertThat(a).isEqualTo(1); a = MoreValidate.positive("x", 1); assertThat(a).isEqualTo(1); // Integer Integer c = MoreValidate.nonNegative("x", Integer.valueOf(0)); assertThat(c).isEqualTo(0); c = MoreValidate.nonNegative("x", Integer.valueOf(21)); assertThat(c).isEqualTo(21); c = MoreValidate.positive("x", Integer.valueOf(1)); assertThat(c).isEqualTo(1); // long long b = MoreValidate.nonNegative("x", 0l); assertThat(b).isEqualTo(0); b = MoreValidate.nonNegative("x", 11l); assertThat(b).isEqualTo(11); b = MoreValidate.positive("x", 1l); assertThat(b).isEqualTo(1); double e = MoreValidate.nonNegative("x", 0l); assertThat(e).isEqualTo(0); e = MoreValidate.nonNegative("x", 11d); assertThat(e).isEqualTo(11); e = MoreValidate.positive("x", 1.1d); assertThat(e).isEqualTo(1.1); // Long Long d = MoreValidate.nonNegative("x", Long.valueOf(0)); assertThat(d).isEqualTo(0); d = MoreValidate.positive("x", Long.valueOf(1)); assertThat(d).isEqualTo(1); d = MoreValidate.nonNegative("x", Long.valueOf(11)); assertThat(d).isEqualTo(11); // int try { MoreValidate.nonNegative("x", -1); fail("fail"); } catch (Throwable t) { assertThat(t).hasMessage("x (-1) must be >= 0"); assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.nonNegative(null, -1); fail("fail"); } catch (Throwable t) { assertThat(t).hasMessage("null (-1) must be >= 0"); assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", -1); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", 0); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } // long try { MoreValidate.nonNegative("x", -1l); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", -1l); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", 0l); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } // Long try { MoreValidate.nonNegative("x", Long.valueOf(-1)); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", Long.valueOf(-1)); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", Long.valueOf(0)); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } // Integer try { MoreValidate.nonNegative("x", Integer.valueOf(-1)); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", Integer.valueOf(-1)); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", Integer.valueOf(0)); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } // double try { MoreValidate.nonNegative("x", -9999.2d); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", -1.2d); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { MoreValidate.positive("x", 0d); fail("fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/ObjectUtilTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.vip.vjtools.vjkit.collection.ListUtil; public class ObjectUtilTest { @Test public void hashCodeTest() { assertThat(ObjectUtil.hashCode("a", "b") - ObjectUtil.hashCode("a", "a")).isEqualTo(1); } @Test public void toPrettyString() { assertThat(ObjectUtil.toPrettyString(null)).isEqualTo("null"); assertThat(ObjectUtil.toPrettyString(1)).isEqualTo("1"); assertThat(ObjectUtil.toPrettyString(new int[] { 1, 2 })).isEqualTo("[1, 2]"); assertThat(ObjectUtil.toPrettyString(new long[] { 1, 2 })).isEqualTo("[1, 2]"); assertThat(ObjectUtil.toPrettyString(new double[] { 1.1d, 2.2d })).isEqualTo("[1.1, 2.2]"); assertThat(ObjectUtil.toPrettyString(new float[] { 1.1f, 2.2f })).isEqualTo("[1.1, 2.2]"); assertThat(ObjectUtil.toPrettyString(new boolean[] { true, false })).isEqualTo("[true, false]"); assertThat(ObjectUtil.toPrettyString(new short[] { 1, 2 })).isEqualTo("[1, 2]"); assertThat(ObjectUtil.toPrettyString(new byte[] { 1, 2 })).isEqualTo("[1, 2]"); assertThat(ObjectUtil.toPrettyString(new Integer[] { 1, 2 })).isEqualTo("[1, 2]"); assertThat(ObjectUtil.toPrettyString(ListUtil.newArrayList("1", "2"))).isEqualTo("{1,2}"); System.out.println(new Integer[] { 1, 2 }.toString()); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/PairTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.vip.vjtools.vjkit.base.type.Pair; import com.vip.vjtools.vjkit.base.type.Triple; public class PairTest { @Test public void pairTest() { Pair pair = Pair.of("haha", 1); Pair pair2 = Pair.of("haha", 2); Pair pair3 = Pair.of("kaka", 1); assertThat(pair.equals(pair2)).isFalse(); assertThat(pair.equals(pair3)).isFalse(); assertThat(pair.hashCode() != pair2.hashCode()).isTrue(); assertThat(pair.toString()).isEqualTo("Pair [left=haha, right=1]"); assertThat(pair.getLeft()).isEqualTo("haha"); assertThat(pair.getRight()).isEqualTo(1); } @Test public void tripleTest() { Triple triple = Triple.of("haha", "hehe", 1); Triple triple2 = Triple.of("haha", "hehe", 2); Triple triple3 = Triple.of("haha", "lala", 2); Triple triple4 = Triple.of("kaka", "lala", 2); Pair pair = Pair.of("haha", 1); assertThat(triple.equals(triple2)).isFalse(); assertThat(triple.equals(triple3)).isFalse(); assertThat(triple.equals(triple4)).isFalse(); assertThat(triple.equals(pair)).isFalse(); assertThat(triple.hashCode() != triple2.hashCode()).isTrue(); assertThat(triple.toString()).isEqualTo("Triple [left=haha, middle=hehe, right=1]"); assertThat(triple.getLeft()).isEqualTo("haha"); assertThat(triple.getMiddle()).isEqualTo("hehe"); assertThat(triple.getRight()).isEqualTo(1); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/PlatformsTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class PlatformsTest { @Test public void PlatformTest() { if (Platforms.IS_WINDOWS) { assertThat(Platforms.FILE_PATH_SEPARATOR).isEqualTo("\\"); assertThat(Platforms.FILE_PATH_SEPARATOR_CHAR).isEqualTo('\\'); } else { assertThat(Platforms.FILE_PATH_SEPARATOR).isEqualTo("/"); assertThat(Platforms.FILE_PATH_SEPARATOR_CHAR).isEqualTo('/'); } System.out.println("OS_NAME:" + Platforms.OS_NAME); System.out.println("OS_VERSION:" + Platforms.OS_VERSION); System.out.println("OS_ARCH:" + Platforms.OS_ARCH); System.out.println("JAVA_SPECIFICATION_VERSION:" + Platforms.JAVA_SPECIFICATION_VERSION); System.out.println("JAVA_VERSION:" + Platforms.JAVA_VERSION); System.out.println("JAVA_HOME:" + Platforms.JAVA_HOME); System.out.println("USER_HOME:" + Platforms.USER_HOME); System.out.println("TMP_DIR:" + Platforms.TMP_DIR); System.out.println("WORKING_DIR:" + Platforms.WORKING_DIR); if (Platforms.IS_JAVA7) { assertThat(Platforms.IS_ATLEASET_JAVA7).isTrue(); assertThat(Platforms.IS_ATLEASET_JAVA8).isFalse(); } if (Platforms.IS_JAVA8) { assertThat(Platforms.IS_ATLEASET_JAVA7).isTrue(); assertThat(Platforms.IS_ATLEASET_JAVA8).isTrue(); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/PropertiesUtilTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.*; import java.util.Properties; import org.junit.Test; public class PropertiesUtilTest { @Test public void loadProperties() { Properties p1 = PropertiesUtil.loadFromFile("classpath:application.properties"); assertThat(p1.get("springside.min")).isEqualTo("1"); assertThat(p1.get("springside.max")).isEqualTo("10"); Properties p2 = PropertiesUtil.loadFromString("springside.min=1\nspringside.max=10\nisOpen=true"); assertThat(PropertiesUtil.getInt(p2, "springside.min", 0)).isEqualTo(1); assertThat(PropertiesUtil.getInt(p2, "springside.max", 0)).isEqualTo(10); assertThat(PropertiesUtil.getInt(p2, "springside.maxA", 0)).isEqualTo(0); assertThat(PropertiesUtil.getLong(p2, "springside.min", 0L)).isEqualTo(1); assertThat(PropertiesUtil.getLong(p2, "springside.max", 0L)).isEqualTo(10); assertThat(PropertiesUtil.getLong(p2, "springside.maxA", 0L)).isEqualTo(0); assertThat(PropertiesUtil.getDouble(p2, "springside.min", 0d)).isEqualTo(1); assertThat(PropertiesUtil.getDouble(p2, "springside.max", 0d)).isEqualTo(10); assertThat(PropertiesUtil.getDouble(p2, "springside.maxA", 0d)).isEqualTo(0); assertThat(PropertiesUtil.getString(p2, "springside.min", "")).isEqualTo("1"); assertThat(PropertiesUtil.getString(p2, "springside.max", "")).isEqualTo("10"); assertThat(PropertiesUtil.getString(p2, "springside.maxA", "")).isEqualTo(""); assertThat(PropertiesUtil.getBoolean(p2, "isOpen", false)).isTrue(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/RuntimeUtilTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class RuntimeUtilTest { @Test public void testRuntime() { System.out.println("pid:" + RuntimeUtil.getPid()); assertThat(RuntimeUtil.getPid()).isNotEqualTo(-1); System.out.println("vmargs:" + RuntimeUtil.getVmArguments()); RuntimeUtil.addShutdownHook(new Runnable() { @Override public void run() { System.out.println("systemShutdowning"); } }); assertThat(RuntimeUtil.getCores()).isGreaterThan(1); System.out.println("uptime" + RuntimeUtil.getUpTime()); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/SystemPropertiesUtilTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import org.junit.Test; import com.vip.vjtools.vjkit.base.SystemPropertiesUtil.PropertiesListener; import com.vip.vjtools.vjkit.number.RandomUtil; public class SystemPropertiesUtilTest { @Test public void systemProperty() { String name = "ss.test" + RandomUtil.nextInt(); Boolean result0 = SystemPropertiesUtil.getBoolean(name); assertThat(result0).isNull(); Boolean result1 = SystemPropertiesUtil.getBoolean(name, null); assertThat(result1).isNull(); Boolean result3 = SystemPropertiesUtil.getBoolean(name, Boolean.TRUE); assertThat(result3).isTrue(); System.setProperty(name, "true"); Boolean result5 = SystemPropertiesUtil.getBoolean(name, Boolean.FALSE); assertThat(result5).isTrue(); System.clearProperty(name); /// int Integer result6 = SystemPropertiesUtil.getInteger(name); assertThat(result6).isNull(); result6 = SystemPropertiesUtil.getInteger(name, 1); assertThat(result6).isEqualTo(1); System.setProperty(name, "2"); result6 = SystemPropertiesUtil.getInteger(name, 1); assertThat(result6).isEqualTo(2); System.clearProperty(name); ///// long Long result7 = SystemPropertiesUtil.getLong(name); assertThat(result7).isNull(); result7 = SystemPropertiesUtil.getLong(name, 1L); assertThat(result7).isEqualTo(1L); System.setProperty(name, "2"); result7 = SystemPropertiesUtil.getLong(name, 1L); assertThat(result7).isEqualTo(2L); System.clearProperty(name); ///// doulbe Double result8 = SystemPropertiesUtil.getDouble(name); assertThat(result8).isNull(); result8 = SystemPropertiesUtil.getDouble(name, 1.1); assertThat(result8).isEqualTo(1.1); System.setProperty(name, "2.1"); result8 = SystemPropertiesUtil.getDouble(name, 1.1); assertThat(result8).isEqualTo(2.1); System.clearProperty(name); ///// String String result9 = SystemPropertiesUtil.getString(name); assertThat(result9).isNull(); result9 = SystemPropertiesUtil.getString(name, "1.1"); assertThat(result9).isEqualTo("1.1"); System.setProperty(name, "2.1"); result9 = SystemPropertiesUtil.getString(name, "1.1"); assertThat(result9).isEqualTo("2.1"); System.clearProperty(name); } @Test public void stringSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 String result = SystemPropertiesUtil.getString(name, envName, "123"); assertThat(result).isEqualTo("123"); // env值 String result2 = SystemPropertiesUtil.getString(name, "PATH", "123"); assertThat(result2).isNotEqualTo("123"); // system properties值 System.setProperty(name, "456"); String result3 = SystemPropertiesUtil.getString(name, envName, "123"); assertThat(result3).isEqualTo("456"); try { // 非法字符 String result4 = SystemPropertiesUtil.getString(name, name, "123"); fail("should fail before"); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); } System.clearProperty(name); } @Test public void intSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 int result = SystemPropertiesUtil.getInteger(name, envName, 123); assertThat(result).isEqualTo(123); // env值没有数字类型的,忽略 // system properties值 System.setProperty(name, "456"); int result3 = SystemPropertiesUtil.getInteger(name, envName, 123); assertThat(result3).isEqualTo(456); System.clearProperty(name); } @Test public void longSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 long result = SystemPropertiesUtil.getLong(name, envName, 123L); assertThat(result).isEqualTo(123L); // env值没有数字类型的,忽略 // system properties值 System.setProperty(name, "456"); long result3 = SystemPropertiesUtil.getLong(name, envName, 123L); assertThat(result3).isEqualTo(456L); System.clearProperty(name); } @Test public void doubleSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 double result = SystemPropertiesUtil.getDouble(name, envName, 123d); assertThat(result).isEqualTo(123d); // env值没有数字类型的,忽略 // system properties值 System.setProperty(name, "456"); double result3 = SystemPropertiesUtil.getDouble(name, envName, 123d); assertThat(result3).isEqualTo(456d); System.clearProperty(name); } @Test public void booleanSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 boolean result = SystemPropertiesUtil.getBoolean(name, envName, true); assertThat(result).isTrue(); // env值没有boolean类型的,忽略 // system properties值 System.setProperty(name, "true"); boolean result3 = SystemPropertiesUtil.getBoolean(name, envName, false); assertThat(result3).isTrue(); System.clearProperty(name); } @Test public void listenableProperties() { String name = "ss.test" + RandomUtil.nextInt(); TestPropertiesListener listener = new TestPropertiesListener(name); SystemPropertiesUtil.registerSystemPropertiesListener(listener); System.setProperty(name, "haha"); assertThat(listener.newValue).isEqualTo("haha"); } public static class TestPropertiesListener extends PropertiesListener { public TestPropertiesListener(String propertyName) { super(propertyName); } public String newValue; @Override public void onChange(String propertyName, String value) { newValue = value; } }; } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/base/ValueValidatorTest.java ================================================ package com.vip.vjtools.vjkit.base; import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; import com.vip.vjtools.vjkit.base.ValueValidator.Validator; public class ValueValidatorTest { @Test public void testValidator() { assertThat(ValueValidator.checkAndGet(-1, 1, Validator.INTEGER_GT_ZERO_VALIDATOR)).isEqualTo(1); assertThat(ValueValidator.checkAndGet("testUnEmpty", "isEmpty", Validator.STRING_EMPTY_VALUE_VALIDATOR)) .isEqualTo("testUnEmpty"); assertThat(ValueValidator.checkAndGet("flase", "true", Validator.STRICT_BOOL_VALUE_VALIDATOR)) .isEqualTo("true"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/collection/ArrayUtilTest.java ================================================ package com.vip.vjtools.vjkit.collection; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.junit.Assert.assertFalse; import java.util.Arrays; import java.util.List; import org.junit.Test; import com.vip.vjtools.vjkit.number.RandomUtil; public class ArrayUtilTest { @Test public void shuffle() { String[] arrays = new String[] { "d", "a", "c", "b", "e", "i", "g" }; String[] arraysClone = Arrays.copyOf(arrays, arrays.length); Arrays.sort(arrays); assertThat(arrays).containsExactly("a", "b", "c", "d", "e", "g", "i"); ArrayUtil.shuffle(arrays); assertFalse("should not be equal to origin array", Arrays.equals(arrays, arraysClone)); // System.out.println(Arrays.toString(arrays)); Arrays.sort(arrays); ArrayUtil.shuffle(arrays, RandomUtil.secureRandom()); assertFalse("should not be equal to origin array", Arrays.equals(arrays, arraysClone)); } @Test public void asList() { List list = ArrayUtil.asList("d", "a", "c", "b", "e", "i", "g"); assertThat(list).hasSize(7).containsExactly("d", "a", "c", "b", "e", "i", "g"); try { list.add("a"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } List list3 = ArrayUtil.intAsList(1, 2, 3); assertThat(list3).hasSize(3).containsExactly(1, 2, 3); List list4 = ArrayUtil.longAsList(1L, 2L, 3L); assertThat(list4).hasSize(3).containsExactly(1L, 2L, 3L); List list5 = ArrayUtil.doubleAsList(1.1d, 2.2d, 3.3d); assertThat(list5).hasSize(3).containsExactly(1.1d, 2.2d, 3.3d); } @Test public void contact() { String[] array = new String[] { "d", "a", "c" }; assertThat(ArrayUtil.concat("z", array)).containsExactly("z", "d", "a", "c"); assertThat(ArrayUtil.concat(array, "z")).containsExactly("d", "a", "c", "z"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/collection/CollectionUtilTest.java ================================================ package com.vip.vjtools.vjkit.collection; import static org.assertj.core.api.Assertions.*; import java.util.List; import java.util.Set; import org.junit.Test; import com.google.common.collect.Ordering; public class CollectionUtilTest { @Test public void test() { List list1 = ListUtil.newArrayList(); List list2 = ListUtil.newArrayList("a", "b", "c"); List list3 = ListUtil.newArrayList("a"); Set set1 = SetUtil.newSortedSet(); set1.add("a"); set1.add("b"); set1.add("c"); Set set2 = SetUtil.newSortedSet(); set2.add("a"); assertThat(CollectionUtil.isEmpty(list1)).isTrue(); assertThat(CollectionUtil.isEmpty(null)).isTrue(); assertThat(CollectionUtil.isEmpty(list2)).isFalse(); assertThat(CollectionUtil.isNotEmpty(list1)).isFalse(); assertThat(CollectionUtil.isNotEmpty(null)).isFalse(); assertThat(CollectionUtil.isNotEmpty(list2)).isTrue(); assertThat(CollectionUtil.getFirst(list2)).isEqualTo("a"); assertThat(CollectionUtil.getLast(list2)).isEqualTo("c"); assertThat(CollectionUtil.getFirst(set1)).isEqualTo("a"); assertThat(CollectionUtil.getLast(set1)).isEqualTo("c"); assertThat(CollectionUtil.getFirst(list3)).isEqualTo("a"); assertThat(CollectionUtil.getLast(list3)).isEqualTo("a"); assertThat(CollectionUtil.getFirst(set2)).isEqualTo("a"); assertThat(CollectionUtil.getLast(set2)).isEqualTo("a"); assertThat(CollectionUtil.getFirst(list1)).isNull(); assertThat((List) CollectionUtil.getFirst(null)).isNull(); assertThat(CollectionUtil.getLast(list1)).isNull(); assertThat((List) CollectionUtil.getLast(null)).isNull(); } @Test public void minAndMax() { List list = ListUtil.newArrayList(4, 1, 9, 100, 20, 101, 40); assertThat(CollectionUtil.min(list)).isEqualTo(1); assertThat(CollectionUtil.min(list, Ordering.natural())).isEqualTo(1); assertThat(CollectionUtil.max(list)).isEqualTo(101); assertThat(CollectionUtil.max(list, Ordering.natural())).isEqualTo(101); assertThat(CollectionUtil.minAndMax(list).getLeft()).isEqualTo(1); assertThat(CollectionUtil.minAndMax(list).getRight()).isEqualTo(101); assertThat(CollectionUtil.minAndMax(list, Ordering.natural()).getLeft()).isEqualTo(1); assertThat(CollectionUtil.minAndMax(list, Ordering.natural()).getRight()).isEqualTo(101); } @Test public void listCompare() { List list1 = ArrayUtil.asList("d", "a", "c", "b", "e", "i", "g"); List list2 = ArrayUtil.asList("d", "a", "c", "b", "e", "i", "g"); List list3 = ArrayUtil.asList("d", "c", "a", "b", "e", "i", "g"); List list4 = ArrayUtil.asList("d", "a", "c", "b", "e"); List list5 = ArrayUtil.asList("d", "a", "c", "b", "e", "i", "g", "x"); assertThat(CollectionUtil.elementsEqual(list1, list1)).isTrue(); assertThat(CollectionUtil.elementsEqual(list1, list2)).isTrue(); assertThat(CollectionUtil.elementsEqual(list1, list3)).isFalse(); assertThat(CollectionUtil.elementsEqual(list1, list4)).isFalse(); assertThat(CollectionUtil.elementsEqual(list1, list5)).isFalse(); } @Test public void topNAndBottomN() { List list = ArrayUtil.asList(3, 5, 7, 4, 2, 6, 9); assertThat(CollectionUtil.topN(list, 3)).containsExactly(9, 7, 6); assertThat(CollectionUtil.topN(list, 3, Ordering.natural().reverse())).containsExactly(2, 3, 4); assertThat(CollectionUtil.bottomN(list, 3)).containsExactly(2, 3, 4); assertThat(CollectionUtil.bottomN(list, 3, Ordering.natural().reverse())).containsExactly(9, 7, 6); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/collection/ListUtilTest.java ================================================ package com.vip.vjtools.vjkit.collection; import static org.assertj.core.api.Assertions.*; import java.util.List; import java.util.Random; import org.junit.Test; import com.google.common.collect.Ordering; public class ListUtilTest { @Test public void guavaBuildList() { List list1 = ListUtil.newArrayList(); List list2 = ListUtil.newArrayList("a", "b"); assertThat(list2).hasSize(2).containsExactly("a", "b"); List list3 = ListUtil.newArrayList(SetUtil.newHashSet("a", "b")); assertThat(list2).hasSize(2).containsExactly("a", "b"); List list4 = ListUtil.newArrayListWithCapacity(10); List list5 = ListUtil.newCopyOnWriteArrayList(); List list6 = ListUtil.newCopyOnWriteArrayList("a", "b"); assertThat(list6).hasSize(2).containsExactly("a", "b"); List list7 = ListUtil.newLinkedList(); } @Test public void jdkBuild() { List list1 = ListUtil.emptyList(); assertThat(list1).hasSize(0); List list2 = ListUtil.emptyListIfNull(null); assertThat(list2).isNotNull().hasSize(0); List list3 = ListUtil.emptyListIfNull(list1); assertThat(list3).isSameAs(list1); try { list1.add("a"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } List list4 = ListUtil.singletonList("1"); assertThat(list4).hasSize(1).contains("1"); try { list4.add("a"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } List list5 = ListUtil.newArrayList(); List list6 = ListUtil.unmodifiableList(list5); try { list6.add("a"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } List list7 = ListUtil.synchronizedList(list6); } @Test public void general() { List list1 = ListUtil.newArrayList(); List list2 = ListUtil.newArrayList("a", "b", "c"); List list3 = ListUtil.newArrayList("a"); assertThat(ListUtil.isEmpty(list1)).isTrue(); assertThat(ListUtil.isEmpty(null)).isTrue(); assertThat(ListUtil.isEmpty(list2)).isFalse(); assertThat(ListUtil.isNotEmpty(list1)).isFalse(); assertThat(ListUtil.isNotEmpty(null)).isFalse(); assertThat(ListUtil.isNotEmpty(list2)).isTrue(); assertThat(ListUtil.getFirst(list2)).isEqualTo("a"); assertThat(ListUtil.getLast(list2)).isEqualTo("c"); assertThat(ListUtil.getFirst(list3)).isEqualTo("a"); assertThat(ListUtil.getLast(list3)).isEqualTo("a"); assertThat(ListUtil.getFirst(list1)).isNull(); assertThat((List) ListUtil.getFirst(null)).isNull(); } @Test public void sortAndSearch() { List list = ListUtil.newArrayList("d", "a", "c", "b", "e", "i", "g"); ListUtil.sort(list); assertThat(list).hasSize(7).containsExactly("a", "b", "c", "d", "e", "g", "i"); ListUtil.shuffle(list); ListUtil.shuffle(list, new Random()); System.out.println("shuffle list:" + list); ListUtil.sort(list, Ordering.natural()); assertThat(list).hasSize(7).containsExactly("a", "b", "c", "d", "e", "g", "i"); assertThat(ListUtil.binarySearch(list, "b")).isEqualTo(1); assertThat(ListUtil.binarySearch(list, "b", Ordering.natural())).isEqualTo(1); assertThat(ListUtil.binarySearch(list, "x")).isEqualTo(-8); // reverse List list8 = ListUtil.reverse(list); assertThat(list8).hasSize(7).containsExactly("i", "g", "e", "d", "c", "b", "a"); // sortReverse ListUtil.shuffle(list8); ListUtil.sortReverse(list8); assertThat(list8).hasSize(7).containsExactly("i", "g", "e", "d", "c", "b", "a"); ListUtil.shuffle(list8); ListUtil.sortReverse(list8, Ordering.natural()); assertThat(list8).hasSize(7).containsExactly("i", "g", "e", "d", "c", "b", "a"); } @Test public void collectionCalc() { List list1 = ListUtil.newArrayList("1", "2", "3", "6", "6"); List list2 = ListUtil.newArrayList("4", "5", "6", "7", "6", "6"); List result = ListUtil.union(list1, list2); assertThat(result).containsExactly("1", "2", "3", "6", "6", "4", "5", "6", "7", "6", "6"); List result2 = ListUtil.intersection(list1, list2); assertThat(result2).containsExactly("6", "6"); List result3 = ListUtil.difference(list2, list1); assertThat(result3).containsExactly("4", "5", "7", "6"); List result4 = ListUtil.disjoint(list1, list2); assertThat(result4).containsExactly("1", "2", "3", "4", "5", "7", "6"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/collection/MapUtilTest.java ================================================ package com.vip.vjtools.vjkit.collection; import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Fail.fail; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; import org.junit.Test; import com.google.common.collect.Ordering; import com.vip.vjtools.vjkit.collection.MapUtil.ValueCreator; import com.vip.vjtools.vjkit.collection.type.MoreMaps; public class MapUtilTest { @Test public void buildMap() { try { HashMap map4 = MapUtil.newHashMap(new String[] { "1", "2" }, new Integer[] { 1 }); fail("should fail here"); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); assertThat(e).hasMessage("keys.length is 2 but values.length is 1"); } } @Test public void generalMethod() { HashMap map = MapUtil.newHashMap(); assertThat(MapUtil.isEmpty(map)).isTrue(); assertThat(MapUtil.isEmpty(null)).isTrue(); assertThat(MapUtil.isNotEmpty(map)).isFalse(); assertThat(MapUtil.isNotEmpty(null)).isFalse(); map.put("haha", 1); assertThat(MapUtil.isEmpty(map)).isFalse(); assertThat(MapUtil.isNotEmpty(map)).isTrue(); ////////// ConcurrentMap map2 = new ConcurrentHashMap(); assertThat(MapUtil.putIfAbsentReturnLast(map2, "haha", 3)).isEqualTo(3); assertThat(MapUtil.putIfAbsentReturnLast(map2, "haha", 4)).isEqualTo(3); MapUtil.createIfAbsentReturnLast(map2, "haha", new ValueCreator() { @Override public Integer get() { return 5; } }); assertThat(map2).hasSize(1).containsEntry("haha", 3); MapUtil.createIfAbsentReturnLast(map2, "haha2", new ValueCreator() { @Override public Integer get() { return 5; } }); assertThat(map2).hasSize(2).containsEntry("haha2", 5); } @Test public void guavaBuildMap() { HashMap map1 = MapUtil.newHashMap(); HashMap map2 = MapUtil.newHashMapWithCapacity(10, 0.5f); map2 = MapUtil.newHashMapWithCapacity(10, 0.5f); HashMap map3 = MapUtil.newHashMap("1", 1); assertThat(map3).hasSize(1).containsEntry("1", 1); HashMap map4 = MapUtil.newHashMap(new String[] { "1", "2" }, new Integer[] { 1, 2 }); assertThat(map4).hasSize(2).containsEntry("1", 1).containsEntry("2", 2); HashMap map5 = MapUtil.newHashMap(ArrayUtil.asList("1", "2", "3"), ArrayUtil.asList(1, 2, 3)); assertThat(map5).hasSize(3).containsEntry("1", 1).containsEntry("2", 2).containsEntry("3", 3); TreeMap map6 = MapUtil.newSortedMap(); TreeMap map7 = MapUtil.newSortedMap(Ordering.natural()); ConcurrentSkipListMap map10 = MapUtil.newConcurrentSortedMap(); EnumMap map11 = MapUtil.newEnumMap(EnumA.class); } @Test public void jdkBuildMap() { Map map1 = MapUtil.emptyMap(); assertThat(map1).hasSize(0); Map map2 = MapUtil.emptyMapIfNull(null); assertThat(map2).isNotNull().hasSize(0); Map map3 = MapUtil.emptyMapIfNull(map1); assertThat(map3).isSameAs(map1); Map map4 = MapUtil.singletonMap("haha", 1); assertThat(map4).hasSize(1).containsEntry("haha", 1); try { map4.put("dada", 2); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } Map map5 = MapUtil.newHashMap(); Map map6 = MapUtil.unmodifiableMap(map5); try { map6.put("a", 2); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } } @Test public void weakMap() { ConcurrentMap weakKeyMap = MoreMaps.createWeakKeyConcurrentMap(10, 1); initExpireAllMap(weakKeyMap); System.gc(); assertThat(weakKeyMap.get(new MyBean("A"))).isNull(); assertThat(weakKeyMap).hasSize(1); // key仍然在 ConcurrentMap weakKeyMap2 = MoreMaps.createWeakKeyConcurrentMap(10, 1); MyBean value = new MyBean("B"); initExpireKeyMap(weakKeyMap2, value); System.gc(); assertThat(weakKeyMap2.get(new MyBean("A"))).isNull(); ConcurrentMap weakKeyMap3 = MoreMaps.createWeakKeyConcurrentMap(10, 1); MyBean key = new MyBean("A"); initExpireValueMap(weakKeyMap3, key); System.gc(); assertThat(weakKeyMap3.get(key)).isEqualTo(new MyBean("B")); // weak value ConcurrentMap weakValueMap = MoreMaps.createWeakValueConcurrentMap(10, 1); initExpireAllMap(weakValueMap); System.gc(); assertThat(weakValueMap.get(new MyBean("A"))).isNull(); ConcurrentMap weakValueMap2 = MoreMaps.createWeakValueConcurrentMap(10, 1); MyBean value2 = new MyBean("B"); initExpireKeyMap(weakValueMap2, value2); System.gc(); assertThat(weakValueMap2.get(new MyBean("A"))).isEqualTo(new MyBean("B")); ConcurrentMap weakValueMap3 = MoreMaps.createWeakValueConcurrentMap(10, 1); MyBean key3 = new MyBean("A"); initExpireValueMap(weakValueMap3, key3); System.gc(); assertThat(weakValueMap3.get(new MyBean("A"))).isNull(); } // 抽出子函数,使得Key/Value的生命周期过期 private void initExpireAllMap(ConcurrentMap weakKeyMap) { MyBean key = new MyBean("A"); MyBean value = new MyBean("B"); weakKeyMap.put(key, value); assertThat(weakKeyMap.get(key)).isEqualTo(value); } // 抽出子函数,使得key过期,value不过期 private void initExpireKeyMap(ConcurrentMap weakKeyMap, MyBean value) { MyBean key = new MyBean("A"); weakKeyMap.put(key, value); assertThat(weakKeyMap.get(key)).isEqualTo(value); } // 抽出子函数,使得key不过期,value过期 private void initExpireValueMap(ConcurrentMap weakKeyMap, MyBean key) { MyBean value = new MyBean("B"); weakKeyMap.put(key, value); assertThat(weakKeyMap.get(key)).isEqualTo(value); } // 抽出子函数,使得Key/Value的生命周琦过期 private void initWeakValue(ConcurrentMap weakKeyMap) { MyBean key = new MyBean("A"); MyBean value = new MyBean("B"); weakKeyMap.put(key, value); assertThat(weakKeyMap.get(new MyBean("A"))).isEqualTo(value); } public static class MyBean { String name; public MyBean(String name) { this.name = name; } @Override public int hashCode() { final int prime = 31; int result = 1; 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; MyBean other = (MyBean) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } public enum EnumA { A, B, C } @Test public void sortAndTop() { Map map = MapUtil.newHashMap(new String[] { "A", "B", "C" }, new Integer[] { 3, 1, 2 }); // sort Map resultMap = MapUtil.sortByValue(map, false); assertThat(resultMap.toString()).isEqualTo("{B=1, C=2, A=3}"); resultMap = MapUtil.sortByValue(map, true); assertThat(resultMap.toString()).isEqualTo("{A=3, C=2, B=1}"); resultMap = MapUtil.sortByValue(map, Ordering.natural()); assertThat(resultMap.toString()).isEqualTo("{B=1, C=2, A=3}"); resultMap = MapUtil.sortByValue(map, Ordering.natural().reverse()); assertThat(resultMap.toString()).isEqualTo("{A=3, C=2, B=1}"); // Top n resultMap = MapUtil.topNByValue(map, false, 2); assertThat(resultMap.toString()).isEqualTo("{B=1, C=2}"); resultMap = MapUtil.topNByValue(map, true, 2); assertThat(resultMap.toString()).isEqualTo("{A=3, C=2}"); resultMap = MapUtil.topNByValue(map, Ordering.natural(), 2); assertThat(resultMap.toString()).isEqualTo("{B=1, C=2}"); resultMap = MapUtil.topNByValue(map, Ordering.natural().reverse(), 2); assertThat(resultMap.toString()).isEqualTo("{A=3, C=2}"); // top Size > array Size resultMap = MapUtil.topNByValue(map, false, 4); assertThat(resultMap.toString()).isEqualTo("{B=1, C=2, A=3}"); resultMap = MapUtil.topNByValue(map, true, 4); assertThat(resultMap.toString()).isEqualTo("{A=3, C=2, B=1}"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/collection/QueueUtilTest.java ================================================ package com.vip.vjtools.vjkit.collection; import static org.assertj.core.api.Assertions.*; import java.util.ArrayDeque; import java.util.Deque; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import org.junit.Test; import com.vip.vjtools.vjkit.collection.type.MoreQueues; public class QueueUtilTest { @Test public void guavaBuildSet() { ArrayDeque queue1 = QueueUtil.newArrayDeque(16); LinkedList queue2 = QueueUtil.newLinkedDeque(); ConcurrentLinkedQueue queue3 = QueueUtil.newConcurrentNonBlockingQueue(); Deque queue7 = QueueUtil.newConcurrentNonBlockingDeque(); LinkedBlockingQueue queue4 = QueueUtil.newBlockingUnlimitQueue(); LinkedBlockingDeque queue8 = QueueUtil.newBlockingUnlimitDeque(); LinkedBlockingQueue queue5 = QueueUtil.newLinkedBlockingQueue(100); ArrayBlockingQueue queue6 = QueueUtil.newArrayBlockingQueue(100); LinkedBlockingDeque queue9 = QueueUtil.newBlockingDeque(100); } @Test public void stack() { Queue stack = MoreQueues.createStack(10); Queue stack2 = MoreQueues.createConcurrentStack(); stack.offer("1"); stack.offer("2"); assertThat(stack.poll()).isEqualTo("2"); assertThat(stack.poll()).isEqualTo("1"); stack2.offer("1"); stack2.offer("2"); assertThat(stack2.poll()).isEqualTo("2"); assertThat(stack2.poll()).isEqualTo("1"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/collection/SetUtilTest.java ================================================ package com.vip.vjtools.vjkit.collection; import static org.assertj.core.api.Assertions.*; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import org.junit.Test; import com.google.common.collect.Ordering; import com.vip.vjtools.vjkit.collection.type.ConcurrentHashSet; public class SetUtilTest { @Test public void guavaBuildSet() { HashSet set1 = SetUtil.newHashSet(); HashSet set2 = SetUtil.newHashSetWithCapacity(10); HashSet set3 = SetUtil.newHashSet("1", "2", "2"); assertThat(set3).hasSize(2).contains("1", "2"); HashSet set4 = SetUtil.newHashSet(ListUtil.newArrayList("1", "2", "2")); assertThat(set4).hasSize(2).contains("1", "2"); TreeSet set5 = SetUtil.newSortedSet(); TreeSet set6 = SetUtil.newSortedSet(Ordering.natural()); ConcurrentHashSet set7 = SetUtil.newConcurrentHashSet(); } @Test public void jdkBuildSet() { Set set1 = SetUtil.emptySet(); assertThat(set1).hasSize(0); Set set2 = SetUtil.emptySetIfNull(null); assertThat(set2).isNotNull().hasSize(0); Set set3 = SetUtil.emptySetIfNull(set1); assertThat(set3).isSameAs(set1); try { set1.add("a"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } Set set4 = SetUtil.singletonSet("1"); assertThat(set4).hasSize(1).contains("1"); try { set4.add("a"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } Set set5 = SetUtil.newHashSet(); Set set6 = SetUtil.unmodifiableSet(set5); try { set6.add("a"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } Set set7 = SetUtil.newSetFromMap(MapUtil.newConcurrentSortedMap()); } @Test public void collectionCaculate() { HashSet set1 = SetUtil.newHashSet("1", "2", "3", "6"); HashSet set2 = SetUtil.newHashSet("4", "5", "6", "7"); Set set3 = SetUtil.unionView(set1, set2); assertThat(set3).hasSize(7).contains("1", "2", "3", "4", "5", "6", "7"); Set set4 = SetUtil.intersectionView(set1, set2); assertThat(set4).hasSize(1).contains("6"); Set set5 = SetUtil.differenceView(set1, set2); assertThat(set5).hasSize(3).contains("1", "2", "3"); Set set6 = SetUtil.disjointView(set1, set2); assertThat(set6).hasSize(6).contains("1", "2", "3", "4", "5", "7"); try { set6.add("a"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/collection/type/ConcurrentHashSetTest.java ================================================ package com.vip.vjtools.vjkit.collection.type; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.vip.vjtools.vjkit.collection.SetUtil; public class ConcurrentHashSetTest { @Test public void concurrentHashSet() { ConcurrentHashSet conrrentHashSet = SetUtil.newConcurrentHashSet(); conrrentHashSet.add("a"); conrrentHashSet.add("b"); conrrentHashSet.add("c"); assertThat(conrrentHashSet.isEmpty()).isFalse(); assertThat(conrrentHashSet.contains("a")).isTrue(); assertThat(conrrentHashSet.contains("d")).isFalse(); assertThat(conrrentHashSet).hasSize(3).contains("a", "b", "c"); for (String key : conrrentHashSet) { System.out.print(key + ","); } conrrentHashSet.remove("c"); assertThat(conrrentHashSet).hasSize(2); Object[] strings = conrrentHashSet.toArray(); assertThat(strings).hasSize(2).contains("a", "b"); conrrentHashSet.toArray(new String[conrrentHashSet.size()]); conrrentHashSet.hashCode(); conrrentHashSet.toString(); ConcurrentHashSet conrrentHashSet2 = SetUtil.newConcurrentHashSet(); conrrentHashSet2.add("a"); assertThat(conrrentHashSet.equals(conrrentHashSet)).isTrue(); assertThat(conrrentHashSet.equals(conrrentHashSet2)).isFalse(); assertThat(conrrentHashSet.containsAll(conrrentHashSet2)).isTrue(); conrrentHashSet.retainAll(conrrentHashSet2); assertThat(conrrentHashSet).hasSize(1).contains("a"); assertThat(conrrentHashSet.equals(conrrentHashSet2)).isTrue(); conrrentHashSet.removeAll(conrrentHashSet2); assertThat(conrrentHashSet.isEmpty()).isTrue(); conrrentHashSet2.clear(); assertThat(conrrentHashSet2.isEmpty()).isTrue(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/collection/type/SortedArrayListTest.java ================================================ package com.vip.vjtools.vjkit.collection.type; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.google.common.collect.Ordering; import com.vip.vjtools.vjkit.collection.ListUtil; public class SortedArrayListTest { @Test public void sortedArrayList() { SortedArrayList list = MoreLists.createSortedArrayList(); list.add("9"); list.add("1"); list.add("6"); list.add("9"); list.add("3"); assertThat(list).containsExactly("1", "3", "6", "9", "9"); list.remove(2); assertThat(list).containsExactly("1", "3", "9", "9"); assertThat(list.contains("3")).isTrue(); assertThat(list.contains("2")).isFalse(); try { list.add(1, "2"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } try { list.set(1, "2"); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(UnsupportedOperationException.class); } SortedArrayList list2 = MoreLists.createSortedArrayList(Ordering.natural()); list2.addAll(ListUtil.newArrayList("3", "1", "2")); assertThat(list2).containsExactly("1", "2", "3"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/ConcurrentsTest.java ================================================ package com.vip.vjtools.vjkit.concurrent; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.vip.vjtools.vjkit.concurrent.jsr166e.LongAdder; public class ConcurrentsTest { @Test public void longAdder() { LongAdder counter = Concurrents.longAdder(); counter.increment(); counter.add(2); assertThat(counter.longValue()).isEqualTo(3L); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/ThreadDumpperTest.java ================================================ package com.vip.vjtools.vjkit.concurrent; import static org.assertj.core.api.Assertions.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.vip.vjtools.test.log.LogbackListAppender; import com.vip.vjtools.vjkit.concurrent.threadpool.ThreadPoolBuilder; public class ThreadDumpperTest { public static class LongRunTask implements Runnable { private CountDownLatch countDownLatch; public LongRunTask(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { countDownLatch.countDown(); ThreadUtil.sleep(5, TimeUnit.SECONDS); } } @Test public void test() throws InterruptedException { ExecutorService executor = ThreadPoolBuilder.fixedPool().setPoolSize(10).build(); CountDownLatch countDownLatch = Concurrents.countDownLatch(10); for (int i = 0; i < 10; i++) { executor.execute(new LongRunTask(countDownLatch)); } countDownLatch.await(); ThreadDumpper dumpper = new ThreadDumpper(); dumpper.tryThreadDump(); LogbackListAppender appender = new LogbackListAppender(); appender.addToLogger(ThreadDumpper.class); // 设置最少间隔,不输出 dumpper.setLeastInterval(1800); dumpper.tryThreadDump(); // 重置间隔会重置上一次写日志的时间,因此要调一次把新增的次数用完 dumpper.tryThreadDump(); assertThat(appender.getAllLogs()).hasSize(3); executor.shutdownNow(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/ThreadUtilTest.java ================================================ package com.vip.vjtools.vjkit.concurrent; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.vip.vjtools.vjkit.base.ObjectUtil; import com.vip.vjtools.vjkit.base.RuntimeUtil; public class ThreadUtilTest { @Test public void testCaller() { hello(); new MyClass().hello(); assertThat(RuntimeUtil.getCurrentClass()).isEqualTo("com.vip.vjtools.vjkit.concurrent.ThreadUtilTest"); assertThat(RuntimeUtil.getCurrentMethod()) .isEqualTo("com.vip.vjtools.vjkit.concurrent.ThreadUtilTest.testCaller()"); } private void hello() { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); System.out.println(ObjectUtil.toPrettyString(stacktrace)); assertThat(RuntimeUtil.getCallerClass()).isEqualTo("com.vip.vjtools.vjkit.concurrent.ThreadUtilTest"); assertThat(RuntimeUtil.getCallerMethod()) .isEqualTo("com.vip.vjtools.vjkit.concurrent.ThreadUtilTest.testCaller()"); } public static class MyClass { public void hello() { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); System.out.println(ObjectUtil.toPrettyString(stacktrace)); assertThat(RuntimeUtil.getCallerClass()).isEqualTo("com.vip.vjtools.vjkit.concurrent.ThreadUtilTest"); assertThat(RuntimeUtil.getCallerMethod()) .isEqualTo("com.vip.vjtools.vjkit.concurrent.ThreadUtilTest.testCaller()"); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/limiter/RateLimiterUtilTest.java ================================================ package com.vip.vjtools.vjkit.concurrent.limiter; import java.lang.reflect.Field; import org.junit.Assert; import org.junit.Test; import com.google.common.util.concurrent.RateLimiter; import com.vip.vjtools.vjkit.concurrent.limiter.RateLimiterUtil; public class RateLimiterUtilTest { @Test public void testCreate() throws Exception { RateLimiter rateLimiter = RateLimiterUtil.create(20000, 0.1); Class superClass = rateLimiter.getClass().getSuperclass(); Field field = superClass.getDeclaredField("storedPermits"); field.setAccessible(true); Assert.assertEquals(2000, (int) field.getDouble(rateLimiter)); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/limiter/SamplerTest.java ================================================ package com.vip.vjtools.vjkit.concurrent.limiter; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.vip.vjtools.vjkit.concurrent.limiter.Sampler; import com.vip.vjtools.vjkit.concurrent.limiter.Sampler.AlwaysSampler; import com.vip.vjtools.vjkit.concurrent.limiter.Sampler.NeverSampler; public class SamplerTest { @Test public void test() { Sampler sampler = Sampler.create(10.5); int hits = 0; for (int i = 0; i < 10000; i++) { if (sampler.select()) { hits++; } } System.out.println("sample 10.5% in 10000 hits should close to 1050, actual is " + hits); assertThat(hits).isBetween(900, 1200); ////////// Sampler sampler2 = Sampler.create(0.5); hits = 0; for (int i = 0; i < 10000; i++) { if (sampler2.select()) { hits++; } } System.out.println("sample 0.5% in 10000 hits should close to 50, actual is " + hits); assertThat(hits).isBetween(20, 100); } @Test public void always() { Sampler sampler = Sampler.create(0d); assertThat(sampler).isInstanceOf(NeverSampler.class); sampler = Sampler.create(100d); assertThat(sampler).isInstanceOf(AlwaysSampler.class); try { sampler = Sampler.create(101d); fail("shoud fail before"); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); } try { sampler = Sampler.create(-2.2); fail("shoud fail before"); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/limiter/TimeIntervalLimiterTest.java ================================================ package com.vip.vjtools.vjkit.concurrent.limiter; import static org.assertj.core.api.Assertions.assertThat; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.vip.vjtools.vjkit.concurrent.Concurrents; import com.vip.vjtools.vjkit.concurrent.limiter.TimeIntervalLimiter; public class TimeIntervalLimiterTest { @Test public void testTryAcquire() throws Exception { int interval = 100; TimeUnit timeUnit = TimeUnit.MILLISECONDS; TimeIntervalLimiter limiter = Concurrents.timeIntervalLimiter(interval, timeUnit); assertThat(limiter.tryAcquire()).isTrue(); assertThat(limiter.tryAcquire()).isFalse(); timeUnit.sleep(interval); assertThat(limiter.tryAcquire()).isTrue(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/threadpool/AbortPolicyWithReportTest.java ================================================ package com.vip.vjtools.vjkit.concurrent.threadpool; import org.junit.Test; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; public class AbortPolicyWithReportTest { @Test public void jStackDumpTest() throws InterruptedException { AbortPolicyWithReport abortPolicyWithReport = new AbortPolicyWithReport("test"); try { abortPolicyWithReport.rejectedExecution(new Runnable() { @Override public void run() { System.out.println("hello"); } }, (ThreadPoolExecutor) Executors.newFixedThreadPool(1)); } catch (RejectedExecutionException rj) { // ignore } Thread.sleep(1000); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/threadpool/QueuableCachedThreadPoolTest.java ================================================ package com.vip.vjtools.vjkit.concurrent.threadpool; import static org.assertj.core.api.Assertions.*; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.vip.vjtools.vjkit.concurrent.ThreadUtil; public class QueuableCachedThreadPoolTest { public static class LongRunTask implements Runnable { @Override public void run() { ThreadUtil.sleep(5, TimeUnit.SECONDS); } } @Test public void test() { QueuableCachedThreadPool threadPool = null; try { threadPool = ThreadPoolBuilder.queuableCachedPool().setMinSize(0).setMaxSize(10).setQueueSize(10).build(); // 线程满 for (int i = 0; i < 10; i++) { threadPool.submit(new LongRunTask()); } assertThat(threadPool.getActiveCount()).isEqualTo(10); assertThat(threadPool.getQueue().size()).isEqualTo(0); // queue 满 for (int i = 0; i < 10; i++) { threadPool.submit(new LongRunTask()); } assertThat(threadPool.getActiveCount()).isEqualTo(10); assertThat(threadPool.getQueue().size()).isEqualTo(10); // 爆 try { threadPool.submit(new LongRunTask()); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(RejectedExecutionException.class); } } finally { ThreadPoolUtil.gracefulShutdown(threadPool, 1000); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/threadpool/ThreadPoolBuilderTest.java ================================================ package com.vip.vjtools.vjkit.concurrent.threadpool; import static org.assertj.core.api.Assertions.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.vip.vjtools.vjkit.concurrent.threadpool.QueuableCachedThreadPool.ControllableQueue; public class ThreadPoolBuilderTest { @Test public void fixPool() { ThreadPoolExecutor singlePool = ThreadPoolBuilder.fixedPool().build(); assertThat(singlePool.getCorePoolSize()).isEqualTo(1); assertThat(singlePool.getMaximumPoolSize()).isEqualTo(1); assertThat(singlePool.getQueue()).isInstanceOf(LinkedBlockingQueue.class); singlePool.shutdown(); ThreadPoolExecutor fixPoolWithUnlimitQueue = ThreadPoolBuilder.fixedPool().setPoolSize(10).build(); assertThat(fixPoolWithUnlimitQueue.getCorePoolSize()).isEqualTo(10); assertThat(fixPoolWithUnlimitQueue.getMaximumPoolSize()).isEqualTo(10); fixPoolWithUnlimitQueue.shutdown(); ThreadPoolExecutor fixPoolWithlimitQueue = ThreadPoolBuilder.fixedPool().setPoolSize(10).setQueueSize(100) .setThreadFactory(ThreadPoolUtil.buildThreadFactory("kaka")).build(); assertThat(fixPoolWithlimitQueue.getQueue()).isInstanceOf(ArrayBlockingQueue.class); Thread thread = fixPoolWithlimitQueue.getThreadFactory().newThread(new Runnable() { @Override public void run() { } }); assertThat(thread.getName()).startsWith("kaka"); fixPoolWithlimitQueue.shutdown(); ThreadPoolExecutor fixPoolWithNamePrefix = ThreadPoolBuilder.fixedPool().setPoolSize(10) .setThreadNamePrefix("fixPool").build(); Thread thread2 = fixPoolWithNamePrefix.getThreadFactory().newThread(new Runnable() { @Override public void run() { } }); assertThat(thread2.getName()).startsWith("fixPool"); assertThat(thread2.isDaemon()).isFalse(); fixPoolWithNamePrefix.shutdown(); ThreadPoolExecutor fixPoolWithNamePrefixAndDaemon = ThreadPoolBuilder.fixedPool().setPoolSize(10) .setThreadNamePrefix("fixPoolDaemon").setDaemon(true).build(); Thread thread3 = fixPoolWithNamePrefixAndDaemon.getThreadFactory().newThread(new Runnable() { @Override public void run() { } }); assertThat(thread3.getName()).startsWith("fixPoolDaemon"); assertThat(thread3.isDaemon()).isTrue(); fixPoolWithNamePrefixAndDaemon.shutdown(); } @Test public void cachedPool() { ThreadPoolExecutor singlePool = ThreadPoolBuilder.cachedPool().build(); assertThat(singlePool.getCorePoolSize()).isEqualTo(0); assertThat(singlePool.getMaximumPoolSize()).isEqualTo(Integer.MAX_VALUE); assertThat(singlePool.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(10); assertThat(singlePool.getQueue()).isInstanceOf(SynchronousQueue.class); singlePool.shutdown(); ThreadPoolExecutor sizeablePool = ThreadPoolBuilder.cachedPool().setMinSize(10).setMaxSize(100) .setKeepAliveSecs(20).build(); assertThat(sizeablePool.getCorePoolSize()).isEqualTo(10); assertThat(sizeablePool.getMaximumPoolSize()).isEqualTo(100); assertThat(sizeablePool.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(20); sizeablePool.shutdown(); ThreadPoolExecutor fixPoolWithNamePrefix = ThreadPoolBuilder.cachedPool().setThreadNamePrefix("cachedPool") .build(); Thread thread = fixPoolWithNamePrefix.getThreadFactory().newThread(new Runnable() { @Override public void run() { } }); assertThat(thread.getName()).startsWith("cachedPool"); fixPoolWithNamePrefix.shutdown(); } @Test public void scheduledPool() { ScheduledThreadPoolExecutor singlePool = ThreadPoolBuilder.scheduledPool().build(); assertThat(singlePool.getCorePoolSize()).isEqualTo(1); assertThat(singlePool.getMaximumPoolSize()).isEqualTo(Integer.MAX_VALUE); singlePool.shutdown(); ScheduledThreadPoolExecutor sizeablePool = ThreadPoolBuilder.scheduledPool().setPoolSize(2).build(); assertThat(sizeablePool.getCorePoolSize()).isEqualTo(2); assertThat(sizeablePool.getMaximumPoolSize()).isEqualTo(Integer.MAX_VALUE); sizeablePool.shutdown(); ThreadPoolExecutor fixPoolWithNamePrefix = ThreadPoolBuilder.scheduledPool() .setThreadNamePrefix("scheduledPool").build(); Thread thread = fixPoolWithNamePrefix.getThreadFactory().newThread(new Runnable() { @Override public void run() { } }); assertThat(thread.getName()).startsWith("scheduledPool"); fixPoolWithNamePrefix.shutdown(); } @Test public void quequablePool() { ThreadPoolExecutor singlePool = ThreadPoolBuilder.queuableCachedPool().build(); assertThat(singlePool.getCorePoolSize()).isEqualTo(0); assertThat(singlePool.getMaximumPoolSize()).isEqualTo(Integer.MAX_VALUE); assertThat(singlePool.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(10); assertThat(singlePool.getQueue()).isInstanceOf(ControllableQueue.class); singlePool.shutdown(); ThreadPoolExecutor sizeablePool = ThreadPoolBuilder.queuableCachedPool().setMinSize(10).setMaxSize(100) .setKeepAliveSecs(20).build(); assertThat(sizeablePool.getCorePoolSize()).isEqualTo(10); assertThat(sizeablePool.getMaximumPoolSize()).isEqualTo(100); assertThat(sizeablePool.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(20); sizeablePool.shutdown(); ThreadPoolExecutor fixPoolWithNamePrefix = ThreadPoolBuilder.queuableCachedPool() .setThreadNamePrefix("queuableCachedPool").build(); Thread thread = fixPoolWithNamePrefix.getThreadFactory().newThread(new Runnable() { @Override public void run() { } }); assertThat(thread.getName()).startsWith("queuableCachedPool"); fixPoolWithNamePrefix.shutdown(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/threadpool/ThreadPoolUtilTest.java ================================================ package com.vip.vjtools.vjkit.concurrent.threadpool; import static org.assertj.core.api.Assertions.assertThat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vip.vjtools.test.log.LogbackListAppender; import com.vip.vjtools.vjkit.concurrent.ThreadUtil; public class ThreadPoolUtilTest { @Test public void buildThreadFactory() { Runnable testRunnable = new Runnable() { @Override public void run() { } }; // 测试name格式 ThreadFactory threadFactory = ThreadPoolUtil.buildThreadFactory("example"); Thread thread = threadFactory.newThread(testRunnable); assertThat(thread.getName()).isEqualTo("example-0"); assertThat(thread.isDaemon()).isFalse(); // 测试daemon属性设置 threadFactory = ThreadPoolUtil.buildThreadFactory("example", true); Thread thread2 = threadFactory.newThread(testRunnable); assertThat(thread.getName()).isEqualTo("example-0"); assertThat(thread2.isDaemon()).isTrue(); } @Test public void gracefulShutdown() throws InterruptedException { Logger logger = LoggerFactory.getLogger("test"); LogbackListAppender appender = new LogbackListAppender(); appender.addToLogger("test"); // time enough to shutdown ExecutorService pool = Executors.newSingleThreadExecutor(); Runnable task = new Task(logger, 200, 0); pool.execute(task); ThreadPoolUtil.gracefulShutdown(pool, 1000, TimeUnit.MILLISECONDS); assertThat(pool.isTerminated()).isTrue(); assertThat(appender.getFirstLog()).isNull(); // time not enough to shutdown,call shutdownNow appender.clearLogs(); pool = Executors.newSingleThreadExecutor(); task = new Task(logger, 1000, 0); pool.execute(task); ThreadPoolUtil.gracefulShutdown(pool, 500, TimeUnit.MILLISECONDS); assertThat(pool.isTerminated()).isTrue(); assertThat(appender.getFirstLog().getMessage()).isEqualTo("InterruptedException"); // self thread interrupt while calling gracefulShutdown appender.clearLogs(); final ExecutorService self = Executors.newSingleThreadExecutor(); task = new Task(logger, 100000, 0); self.execute(task); final CountDownLatch lock = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { lock.countDown(); ThreadPoolUtil.gracefulShutdown(self, 200000, TimeUnit.MILLISECONDS); } }); thread.start(); lock.await(); thread.interrupt(); ThreadUtil.sleep(500); assertThat(appender.getFirstLog().getMessage()).isEqualTo("InterruptedException"); ThreadPoolUtil.gracefulShutdown(null, 1000); ThreadPoolUtil.gracefulShutdown(null, 1000, TimeUnit.MILLISECONDS); } @Test public void wrapException() { ScheduledThreadPoolExecutor executor = ThreadPoolBuilder.scheduledPool().build(); ExceptionTask task = new ExceptionTask(); executor.scheduleAtFixedRate(task, 0, 100, TimeUnit.MILLISECONDS); ThreadUtil.sleep(500); // 线程第一次跑就被中断 assertThat(task.counter.get()).isEqualTo(1); ThreadPoolUtil.gracefulShutdown(executor, 1000); //////// executor = ThreadPoolBuilder.scheduledPool().build(); ExceptionTask newTask = new ExceptionTask(); Runnable wrapTask = ThreadPoolUtil.safeRunnable(newTask); executor.scheduleAtFixedRate(wrapTask, 0, 100, TimeUnit.MILLISECONDS); ThreadUtil.sleep(500); assertThat(newTask.counter.get()).isGreaterThan(2); System.out.println("-------actual run:" + task.counter.get()); ThreadPoolUtil.gracefulShutdown(executor, 1000); } static class ExceptionTask implements Runnable { public AtomicInteger counter = new AtomicInteger(0); @Override public void run() { counter.incrementAndGet(); throw new RuntimeException("fail"); } } static class Task implements Runnable { private final Logger logger; private int runTime = 0; private final int sleepTime; Task(Logger logger, int sleepTime, int runTime) { this.logger = logger; this.sleepTime = sleepTime; this.runTime = runTime; } @Override public void run() { System.out.println("start task"); if (runTime > 0) { long start = System.currentTimeMillis(); while ((System.currentTimeMillis() - start) < runTime) { } } try { Thread.sleep(sleepTime); } catch (InterruptedException e) { logger.warn("InterruptedException"); } } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/type/BasicFutureTest.java ================================================ package com.vip.vjtools.vjkit.concurrent.type; import static org.assertj.core.api.Assertions.*; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.Test; import com.vip.vjtools.vjkit.base.ExceptionUtil; import com.vip.vjtools.vjkit.concurrent.type.BasicFuture; public class BasicFutureTest { public static class MyFuture extends BasicFuture { @Override protected void onCompleted(T result) { System.out.println("onCompleted:" + result); } @Override protected void onFailed(Exception ex) { System.out.println("onFailed:" + ex.getMessage()); } @Override protected void onCancelled() { System.out.println("onCancelled"); } } private static class Tasks { public static void success(MyFuture future) { future.completed("haha"); } public static void fail(MyFuture future) { future.failed(new RuntimeException("wuwu")); } public static void cancel(MyFuture future) { future.cancel(true); } } @Test public void test() throws InterruptedException, ExecutionException { MyFuture future = new MyFuture(); Tasks.success(future); String result = future.get(); assertThat(result).isEqualTo("haha"); // 无人设置返回值 try { MyFuture future2 = new MyFuture(); future2.get(10, TimeUnit.MILLISECONDS); fail("should fail before"); } catch (TimeoutException e) { assertThat(e).isInstanceOf(TimeoutException.class); } // 失败 try { MyFuture future3 = new MyFuture(); Tasks.fail(future3); future3.get(); fail("should fail before"); } catch (Throwable t) { assertThat(ExceptionUtil.unwrap(t)).hasMessage("wuwu"); } // 取消 MyFuture future4 = new MyFuture(); Tasks.cancel(future4); assertThat(future4.isCancelled()).isTrue(); try { String result4 = future4.get(); fail("should fail here"); } catch (CancellationException cae) { } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/type/ThreadLocalContextTest.java ================================================ package com.vip.vjtools.vjkit.concurrent.type; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import org.junit.Test; import com.vip.vjtools.vjkit.concurrent.Concurrents; import com.vip.vjtools.vjkit.concurrent.ThreadUtil; import com.vip.vjtools.vjkit.concurrent.type.ThreadLocalContext; import com.vip.vjtools.vjkit.number.RandomUtil; public class ThreadLocalContextTest { @Test public void test() throws InterruptedException { final CountDownLatch countdown = Concurrents.countDownLatch(10); final CyclicBarrier barrier = Concurrents.cyclicBarrier(10); Runnable runnable = new Runnable() { @Override public void run() { try { barrier.await(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } ThreadLocalContext.put("myname", Thread.currentThread().getName()); ThreadUtil.sleep(RandomUtil.nextLong(100, 300)); System.out.println((String) ThreadLocalContext.get("myname")); ThreadLocalContext.reset(); System.out.println( "shoud null for " + Thread.currentThread().getName() + ":" + ThreadLocalContext.get("myname")); countdown.countDown(); } }; for (int i = 0; i < 10; i++) { Thread thread = new Thread(runnable); thread.start(); } countdown.await(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/DataMaskJsonFilterTest.java ================================================ package com.vip.vjtools.vjkit.datamasking; import com.alibaba.fastjson.JSON; import com.vip.vjtools.vjkit.datamasking.data.TestData; import com.vip.vjtools.vjkit.datamasking.data.TestUserMapingData; import org.junit.Test; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; /** * * @author ken */ public class DataMaskJsonFilterTest { @Test public void testProcess() { DataMaskJsonFilter filter = new DataMaskJsonFilter(); TestUserMapingData test1 = new TestUserMapingData(); test1.setTel("13590908322"); test1.setTest("test"); //测试String[] 和 List test1.setStrArr(new String[]{"test1", "test2"}); ArrayList strList = new ArrayList<>(); strList.add("test1"); strList.add("test2"); test1.setStrList(strList); //测试set Set setStr = new HashSet<>(); setStr.add("test1"); test1.setSet(setStr); String json = JSON.toJSONString(test1, filter); //没有annotation的 test1 = JSON.parseObject(json, TestUserMapingData.class); assertThat(test1.getTel()).isEqualTo("135*****322"); assertThat(test1.getTest()).isEqualTo("test"); assertThat(test1.getStrArr()).contains("t****"); assertThat(test1.getStrList()).contains("t****"); assertThat(test1.getSet()).contains("t****"); //有annotation的 TestData test2 = new TestData(); test2.setName("name"); test2.setPhone("13655555555"); test2.setAccount("苹果"); json = JSON.toJSONString(test2, filter); test2 = JSON.parseObject(json, TestData.class); assertThat(test2.getName()).isEqualTo("**me"); assertThat(test2.getPhone()).isEqualTo("13*******55"); assertThat(test2.getAccount()).isEqualTo("苹*"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/DataMaskTest.java ================================================ package com.vip.vjtools.vjkit.datamasking; import com.alibaba.fastjson.JSON; import com.vip.vjtools.vjkit.datamasking.data.TestChild; import com.vip.vjtools.vjkit.datamasking.data.TestData; import com.vip.vjtools.vjkit.datamasking.data.TestParent; import com.vip.vjtools.vjkit.datamasking.data.TestUserMapingData; import com.vip.vjtools.vjkit.datamasking.strategy.HashMask; import com.vip.vjtools.vjkit.text.EncodeUtil; import com.vip.vjtools.vjkit.text.HashUtil; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; /** * * @author ken */ public class DataMaskTest { @Test public void testMaskByType() { assertThat(DataMask.mask("王守仁", SensitiveType.Name)).isEqualTo("**仁"); assertThat(DataMask.mask("13599090990", SensitiveType.Phone)).isEqualTo("135*****990"); assertThat(DataMask.mask("441421199902132221", SensitiveType.IDCard)).isEqualTo("44142***********21"); assertThat(DataMask.mask("441421199902132221", SensitiveType.BankCard)).isEqualTo("4414************21"); assertThat(DataMask.mask("广东省广州市荔湾区花地湾1号", SensitiveType.Address)).isEqualTo("广东省广州市荔湾区*****"); assertThat(DataMask.mask("test@vipshop.com", SensitiveType.Email)).isEqualTo("t**t@vipshop.com"); assertThat(DataMask.mask("9527", SensitiveType.Captcha)).isEqualTo("9**7"); assertThat(DataMask.mask("441421199902132221", SensitiveType.Passport)).isEqualTo("44**************21"); assertThat(DataMask.mask("9527", SensitiveType.Password)).isEqualTo("****"); assertThat(DataMask.mask("account", SensitiveType.Account)).isEqualTo("a*****t"); assertThat(DataMask.mask("default", SensitiveType.Default)).isEqualTo("d******"); assertThat(DataMask.mask("test", SensitiveType.Hash)).isEqualTo(new HashMask().mask("test", null)); } @Test public void testMask() { assertThat(DataMask.mask("test")).isEqualTo("t***"); } @Test public void testSha1Mask() throws Exception { String hash = DataMask.mask("test", SensitiveType.Hash); System.out.println(hash); String salt = HashMask.getSalt(); String encrypt =EncodeUtil.encodeHex(HashUtil.sha1("test"+salt)); assertThat(hash).isNotNull().isEqualTo(encrypt); } @Test public void testToJson() { TestData testData = new TestData(); testData.setName("123"); testData.setPhone("1234567"); testData.setAccount("test"); testData.setHash("hash"); testData.setTest("123456"); String mask = DataMask.toJSONString(testData); System.out.println(mask); //直接用json转的 testData.setName("**3"); testData.setPhone("12***67"); testData.setAccount("t***"); testData.setTest("1**456"); testData.setHash(new HashMask().mask("hash", null)); assertThat(mask).isEqualTo(JSON.toJSONString(testData)); } @Test public void testToString() { TestData testData = new TestData(); testData.setName("123"); testData.setPhone("1234567"); testData.setAccount("test"); testData.setHash("hash"); testData.setTest("123456"); String mask = DataMask.toString(testData); System.out.println(mask); testData.setName("**3"); testData.setPhone("12***67"); testData.setAccount("t***"); testData.setTest("1**456"); testData.setHash(new HashMask().mask("hash", null)); assertThat(mask).isEqualTo(testData.toString()); } @Test public void testMapping() { TestUserMapingData data = new TestUserMapingData(); data.setNickName("nick"); data.setTel("13590909090"); String mask = DataMask.toString(data); System.out.println(mask); data.setNickName("**ck"); data.setTel("135*****090"); assertThat(mask).isEqualTo(data.toString()); } //继承嵌套测试 @Test public void testInherit() { TestChild child = new TestChild(); child.setArr(new String[]{"test11111"}); child.setStr("test11111"); List list = new ArrayList<>(); list.add("test11111"); child.setList(list); Set set = new HashSet<>(); set.add("test11111"); child.setSet(set); TestParent parent = new TestParent(); parent.setChild(child); parent.setOther(parent); child = new TestChild(); child.setArr(new String[]{"test11111"}); child.setStr("test11111"); list = new ArrayList<>(); list.add("test11111"); child.setList(list); set = new HashSet<>(); set.add("test11111"); child.setSet(set); parent.setChildren(Arrays.asList(child)); String json = DataMask.toJSONString(parent); System.out.println(json); //普通的子类 parent = JSON.parseObject(json, TestParent.class); assertThat(parent.getChild().getStr()).contains("*"); assertThat(parent.getChild().getArr()).contains("56C082E77E2924421F909BA262AA25BA80626323"); assertThat(parent.getChild().getList()).contains("t********"); assertThat(parent.getChild().getSet()).contains("t********"); //子类list assertThat(parent.getChildren().get(0).getStr()).contains("*"); assertThat(parent.getChildren().get(0).getArr()).contains("56C082E77E2924421F909BA262AA25BA80626323"); assertThat(parent.getChildren().get(0).getList()).contains("t********"); assertThat(parent.getChildren().get(0).getSet()).contains("t********"); //验证下toString parent.setOther(null);//去掉循环 assertThat(DataMask.toString( "TestParent{child=TestChild{str='t********', arr=[5489afe19ca3744d918d2821ed921e7bbc2b824b], list=[t********], set=[t********]}, children=[TestChild{str='t********', arr=[5489afe19ca3744d918d2821ed921e7bbc2b824b], list=[t********], set=[t********]}], other=null}")); System.out.println(DataMask.toString(parent)); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/MaskMappingTest.java ================================================ package com.vip.vjtools.vjkit.datamasking; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** * * @author ken */ public class MaskMappingTest { @Test public void test() { //系统定义的 assertThat(MaskMapping.getMaskTypeMapping("tel")).isNotNull(); //自定义添加的 assertThat(MaskMapping.getMaskTypeMapping("nickName")).isNotNull(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/data/TestChild.java ================================================ package com.vip.vjtools.vjkit.datamasking.data; import com.vip.vjtools.vjkit.datamasking.Sensitive; import com.vip.vjtools.vjkit.datamasking.SensitiveType; import java.util.Arrays; import java.util.List; import java.util.Set; /** * * @author ken */ public class TestChild { @Sensitive private String str; @Sensitive(type = SensitiveType.Hash) private String[] arr; @Sensitive(type = SensitiveType.Address) private List list; @Sensitive(type = SensitiveType.Account) private Set set; public String getStr() { return str; } public void setStr(String str) { this.str = str; } public String[] getArr() { return arr; } public void setArr(String[] arr) { this.arr = arr; } public List getList() { return list; } public void setList(List list) { this.list = list; } public Set getSet() { return set; } public void setSet(Set set) { this.set = set; } @Override public String toString() { return "TestChild{" + "str='" + str + '\'' + ", arr=" + Arrays.toString(arr) + ", list=" + list + ", set=" + set + '}'; } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/data/TestData.java ================================================ package com.vip.vjtools.vjkit.datamasking.data; import com.vip.vjtools.vjkit.datamasking.Sensitive; import com.vip.vjtools.vjkit.datamasking.SensitiveType; /** * * @author ken */ public class TestData { @Sensitive(type = SensitiveType.Name) private String name; @Sensitive(keepChars = 2) private String phone; @Sensitive(type = SensitiveType.Hash) private String hash; @Sensitive private String account; @Sensitive(keepChars = {1, 3}) private String test; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public String getHash() { return hash; } public void setHash(String hash) { this.hash = hash; } public String getTest() { return test; } public void setTest(String test) { this.test = test; } @Override public String toString() { return "TestData{" + "name='" + name + '\'' + ", phone='" + phone + '\'' + ", hash='" + hash + '\'' + ", account='" + account + '\'' + ", test='" + test + '\'' + '}'; } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/data/TestParent.java ================================================ package com.vip.vjtools.vjkit.datamasking.data; import java.util.List; /** * * @author ken */ public class TestParent { private TestChild child; private List children; private TestParent other; public TestChild getChild() { return child; } public void setChild(TestChild child) { this.child = child; } public List getChildren() { return children; } public void setChildren(List children) { this.children = children; } public TestParent getOther() { return other; } public void setOther(TestParent other) { this.other = other; } @Override public String toString() { return "TestParent{" + "child=" + child + ", children=" + children + ", other=" + other + '}'; } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/data/TestUserMapingData.java ================================================ package com.vip.vjtools.vjkit.datamasking.data; import com.vip.vjtools.vjkit.datamasking.Sensitive; import java.util.Arrays; import java.util.List; import java.util.Set; /** * * @author ken */ public class TestUserMapingData { private String nickName;//自定义的 private String tel;//系统配置的 private String test;//默认的 @Sensitive private String[] strArr; @Sensitive private List strList; @Sensitive private Set set; @Sensitive private Set setInt; public String getTel() { return tel; } public void setTel(String tel) { this.tel = tel; } public String getNickName() { return nickName; } public void setNickName(String nickName) { this.nickName = nickName; } public String getTest() { return test; } public void setTest(String test) { this.test = test; } public String[] getStrArr() { return strArr; } public void setStrArr(String[] strArr) { this.strArr = strArr; } public List getStrList() { return strList; } public void setStrList(List strList) { this.strList = strList; } public Set getSet() { return set; } public void setSet(Set set) { this.set = set; } public Set getSetInt() { return setInt; } public void setSetInt(Set setInt) { this.setInt = setInt; } @Override public String toString() { return "TestUserMapingData{" + "nickName='" + nickName + '\'' + ", tel='" + tel + '\'' + ", test='" + test + '\'' + ", strArr=" + Arrays.toString(strArr) + ", strList=" + strList + ", set=" + set + ", setInt=" + setInt + '}'; } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/strategy/EmailMaskTest.java ================================================ package com.vip.vjtools.vjkit.datamasking.strategy; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** * * @author ken */ public class EmailMaskTest { @Test public void testMask() { EmailMask mask = new EmailMask(); assertThat(mask.mask(null, null)).isNull(); assertThat(mask.mask("", null)).isEmpty(); assertThat(mask.mask("test", null)).isEqualTo("t***"); assertThat(mask.mask("@test", null)).isEqualTo("@****"); assertThat(mask.mask("123@test", null)).isEqualTo("1*3@test"); assertThat(mask.mask("13@test", null)).isEqualTo("1*@test"); assertThat(mask.mask("1@test", null)).isEqualTo("*@test"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/strategy/HashMaskTest.java ================================================ package com.vip.vjtools.vjkit.datamasking.strategy; import com.vip.vjtools.vjkit.text.EncodeUtil; import com.vip.vjtools.vjkit.text.HashUtil; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** * * @author ken */ public class HashMaskTest { @Test public void testMask() throws Exception { HashMask mask = new HashMask(); assertThat(mask.mask(null, null)).isNull(); assertThat(mask.mask("", null)).isEmpty(); String salt = HashMask.getSalt(); String encrypt = EncodeUtil.encodeHex(HashUtil.sha1("test" + salt)); assertThat(mask.mask("test", null)).isEqualTo(encrypt); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/strategy/NameMaskTest.java ================================================ package com.vip.vjtools.vjkit.datamasking.strategy; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** * * @author ken */ public class NameMaskTest { @Test public void testMask() { NameMask mask = new NameMask(); assertThat(mask.mask(null, null)).isNull(); assertThat(mask.mask("", null)).isEmpty(); assertThat(mask.mask("1", null)).isEqualTo("*"); assertThat(mask.mask("中文", null)).isEqualTo("*文"); assertThat(mask.mask("中文3", null)).isEqualTo("**3"); assertThat(mask.mask("中文四个", null)).isEqualTo("**四个"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/strategy/PartMaskTest.java ================================================ package com.vip.vjtools.vjkit.datamasking.strategy; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** * * @author ken */ public class PartMaskTest { @Test public void testMask() { PartMask mask = new PartMask(); assertThat(mask.mask(null, new int[]{1})).isNull(); assertThat(mask.mask("", new int[]{1})).isEmpty(); assertThat(mask.mask("1", new int[]{1})).isEqualTo("*"); assertThat(mask.mask("123", new int[]{1})).isEqualTo("1*3"); assertThat(mask.mask("12345", new int[]{2})).isEqualTo("12*45"); assertThat(mask.mask("1234", new int[]{1, 2})).isEqualTo("1*34"); assertThat(mask.mask("1234", new int[]{1, 0})).isEqualTo("1***"); assertThat(mask.mask("1234", new int[]{0, 1})).isEqualTo("***4"); assertThat(mask.mask("1234", new int[]{0, 0})).isEqualTo("****"); //验证不通过的 assertThat(mask.mask("12", new int[]{1, 1})).isEqualTo("1*"); assertThat(mask.mask("1234", new int[]{4, 1})).isEqualTo("1***"); assertThat(mask.mask("1234", new int[]{5, 1})).isEqualTo("1***"); assertThat(mask.mask("1234", new int[]{1, 4})).isEqualTo("1***"); assertThat(mask.mask("1234", new int[]{1, 5})).isEqualTo("1***"); assertThat(mask.mask("1234", new int[]{3, 2})).isEqualTo("1***"); assertThat(mask.mask("1234", new int[]{2, 2})).isEqualTo("1***"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/id/IdUtilTest.java ================================================ package com.vip.vjtools.vjkit.id; import java.util.UUID; import org.junit.Test; public class IdUtilTest { @Test public void normal() { UUID id1 = IdUtil.fastUUID(); UUID id2 = IdUtil.fastUUID(); System.out.println("UUID1:" + id1); System.out.println("UUID2:" + id2); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/io/FilePathUtilTest.java ================================================ package com.vip.vjtools.vjkit.io; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.google.common.io.Files; import com.vip.vjtools.vjkit.base.Platforms; public class FilePathUtilTest { char sep = Platforms.FILE_PATH_SEPARATOR_CHAR; @Test public void pathName() { String filePath = FilePathUtil.concat(sep + "abc", "ef"); assertThat(filePath).isEqualTo(FilePathUtil.normalizePath("/abc/ef")); String filePath2 = FilePathUtil.concat(sep + "stuv" + sep, "xy"); assertThat(filePath2).isEqualTo(FilePathUtil.normalizePath("/stuv/xy")); assertThat(FilePathUtil.simplifyPath("../dd/../abc")).isEqualTo("../abc"); assertThat(FilePathUtil.simplifyPath("../../dd/../abc")).isEqualTo("../../abc"); assertThat(FilePathUtil.simplifyPath("./abc")).isEqualTo("abc"); assertThat(FilePathUtil.getParentPath(FilePathUtil.normalizePath("/abc/dd/efg/"))) .isEqualTo(FilePathUtil.normalizePath("/abc/dd/")); assertThat(FilePathUtil.getParentPath(FilePathUtil.normalizePath("/abc/dd/efg.txt"))) .isEqualTo(FilePathUtil.normalizePath("/abc/dd/")); } @Test public void getJarPath() { System.out.println("the jar file contains Files.class" + FilePathUtil.getJarPath(Files.class)); assertThat(FilePathUtil.getJarPath(Files.class)).endsWith("guava-20.0.jar"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/io/FileTreeWalkerTest.java ================================================ package com.vip.vjtools.vjkit.io; import static org.assertj.core.api.Assertions.*; import java.io.File; import java.io.IOException; import java.util.List; import org.junit.Test; import com.vip.vjtools.vjkit.number.RandomUtil; public class FileTreeWalkerTest { @Test public void listFile() throws IOException { File tmpDir = FileUtil.createTempDir().toFile(); List all = FileTreeWalker.listAll(tmpDir); assertThat(all).hasSize(1); List files = FileTreeWalker.listFile(tmpDir); assertThat(files).hasSize(0); FileUtil.touch(FilePathUtil.concat(tmpDir.getAbsolutePath(), "tmp-" + RandomUtil.nextInt()) + ".tmp"); FileUtil.touch(FilePathUtil.concat(tmpDir.getAbsolutePath(), "tmp-" + RandomUtil.nextInt()) + ".abc"); String childDir = FilePathUtil.concat(tmpDir.getAbsolutePath(), "tmp-" + RandomUtil.nextInt()); FileUtil.makesureDirExists(childDir); FileUtil.touch(FilePathUtil.concat(childDir, "tmp-" + RandomUtil.nextInt()) + ".tmp"); all = FileTreeWalker.listAll(tmpDir); assertThat(all).hasSize(5); files = FileTreeWalker.listFile(tmpDir); assertThat(files).hasSize(3); // extension files = FileTreeWalker.listFileWithExtension(tmpDir, "tmp"); assertThat(files).hasSize(2); files = FileTreeWalker.listFileWithExtension(tmpDir, "tp"); assertThat(files).hasSize(0); // wildcard files = FileTreeWalker.listFileWithWildcardFileName(tmpDir, "*.tmp"); assertThat(files).hasSize(2); files = FileTreeWalker.listFileWithWildcardFileName(tmpDir, "*.tp"); assertThat(files).hasSize(0); // regex files = FileTreeWalker.listFileWithRegexFileName(tmpDir, ".*\\.tmp"); assertThat(files).hasSize(2); files = FileTreeWalker.listFileWithRegexFileName(tmpDir, ".*\\.tp"); assertThat(files).hasSize(0); // antpath files = FileTreeWalker.listFileWithAntPath(tmpDir, "**" + File.separator + "*.tmp"); assertThat(files).hasSize(2); files = FileTreeWalker.listFileWithAntPath(tmpDir, "*" + File.separator + "*.tmp"); assertThat(files).hasSize(1); files = FileTreeWalker.listFileWithAntPath(tmpDir, "*.tp"); assertThat(files).hasSize(0); FileUtil.deleteDir(tmpDir); assertThat(FileUtil.isDirExists(tmpDir)).isFalse(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/io/FileUtilTest.java ================================================ package com.vip.vjtools.vjkit.io; import static org.assertj.core.api.Assertions.assertThat; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Path; import java.util.List; import org.junit.Test; import com.google.common.io.Files; import com.vip.vjtools.vjkit.base.Platforms; import com.vip.vjtools.vjkit.number.RandomUtil; import com.vip.vjtools.vjkit.text.Charsets; public class FileUtilTest { @Test public void readWrite() throws IOException { File file = FileUtil.createTempFile("abc", ".tmp").toFile(); try { String content = "haha\nhehe"; FileUtil.write(content, file); String result = FileUtil.toString(file); assertThat(result).isEqualTo(content); List lines = FileUtil.toLines(file); assertThat(lines).containsExactly("haha", "hehe"); FileUtil.append("kaka", file); assertThat(new String(FileUtil.toByteArray(file), Charsets.UTF_8)).isEqualTo("haha\nhehekaka"); } finally { FileUtil.deleteFile(file); } } @Test public void opFiles() throws IOException { File file = new File(FilePathUtil.concat(Platforms.TMP_DIR, "testFile" + RandomUtil.nextInt())); FileUtil.touch(file); assertThat(FileUtil.isFileExists(file)).isTrue(); FileUtil.touch(file); String content = "haha\nhehe"; FileUtil.write(content, file); assertThat(FileUtil.toString(file)).isEqualTo(content); File newFile = new File(FilePathUtil.concat(Platforms.TMP_DIR, "testFile" + RandomUtil.nextInt())); File newFile2 = new File(FilePathUtil.concat(Platforms.TMP_DIR, "testFile" + RandomUtil.nextInt())); FileUtil.copyFile(file, newFile); assertThat(FileUtil.isFileExists(newFile)).isTrue(); assertThat(FileUtil.toString(newFile)).isEqualTo(content); FileUtil.moveFile(newFile, newFile2); assertThat(FileUtil.toString(newFile2)).isEqualTo("haha\nhehe"); } @Test public void opDir() throws IOException { String fileName = "testFile" + RandomUtil.nextInt(); File dir = new File(FilePathUtil.concat(Platforms.TMP_DIR, "testDir")); File file = new File(FilePathUtil.concat(Platforms.TMP_DIR, "testDir", fileName)); String content = "haha\nhehe"; FileUtil.makesureDirExists(dir); FileUtil.write(content, file); File dir2 = new File(FilePathUtil.concat(Platforms.TMP_DIR, "testDir2")); FileUtil.copyDir(dir, dir2); File file2 = new File(FilePathUtil.concat(Platforms.TMP_DIR, "testDir2", fileName)); assertThat(FileUtil.toString(file2)).isEqualTo("haha\nhehe"); File dir3 = new File(FilePathUtil.concat(Platforms.TMP_DIR, "testDir3")); FileUtil.moveDir(dir, dir3); File file3 = new File(FilePathUtil.concat(Platforms.TMP_DIR, "testDir3", fileName)); assertThat(FileUtil.toString(file3)).isEqualTo("haha\nhehe"); assertThat(FileUtil.isDirExists(dir)).isFalse(); } @Test public void fileExist() throws IOException { assertThat(FileUtil.isDirExists(Platforms.TMP_DIR)).isTrue(); assertThat(FileUtil.isDirExists(Platforms.TMP_DIR + RandomUtil.nextInt())).isFalse(); File tmpFile = null; try { tmpFile = FileUtil.createTempFile().toFile(); assertThat(FileUtil.isFileExists(tmpFile)).isTrue(); assertThat(FileUtil.isFileExists(tmpFile.getAbsolutePath() + RandomUtil.nextInt())).isFalse(); } finally { FileUtil.deleteFile(tmpFile); } } @Test public void getName() { assertThat(FileUtil.getFileName(FilePathUtil.normalizePath("/a/d/b/abc.txt"))).isEqualTo("abc.txt"); assertThat(FileUtil.getFileName("abc.txt")).isEqualTo("abc.txt"); assertThat(FileUtil.getFileExtension(FilePathUtil.normalizePath("a/d/b/abc.txt"))).isEqualTo("txt"); assertThat(FileUtil.getFileExtension(FilePathUtil.normalizePath("/a/d/b/abc"))).isEqualTo(""); assertThat(FileUtil.getFileExtension(FilePathUtil.normalizePath("/a/d/b/abc."))).isEqualTo(""); } @Test public void testAsInputStream() throws Exception { Path tempPath = FileUtil.createTempFile(); try (InputStream is = FileUtil.asInputStream(tempPath.toString());) { assertThat(is).isNotNull(); } try (InputStream is = FileUtil.asInputStream(tempPath);) { assertThat(is).isNotNull(); } try (InputStream is = FileUtil.asInputStream(tempPath.toFile());) { assertThat(is).isNotNull(); } } @Test public void testAsOututStream() throws Exception { Path tempPath = FileUtil.createTempFile(); try (OutputStream os = FileUtil.asOutputStream(tempPath.toString())) { assertThat(os).isNotNull(); } try (OutputStream os = FileUtil.asOutputStream(tempPath);) { assertThat(os).isNotNull(); } try (OutputStream os = FileUtil.asOutputStream(tempPath.toFile())) { assertThat(os).isNotNull(); } } @Test public void testAsBufferedReader() throws Exception { Path tempPath = FileUtil.createTempFile(); try (BufferedReader br = FileUtil.asBufferedReader(tempPath.toString())) { assertThat(br).isNotNull(); } try (BufferedReader br = FileUtil.asBufferedReader(tempPath)) { assertThat(br).isNotNull(); } } @Test public void testAsBufferedWriter() throws Exception { Path tempPath = FileUtil.createTempFile(); try (BufferedWriter bw = FileUtil.asBufferedWriter(tempPath.toString())) { assertThat(bw).isNotNull(); } try (BufferedWriter bw = FileUtil.asBufferedWriter(tempPath)) { assertThat(bw).isNotNull(); } } @Test public void testCopy() throws Exception { Path dir = FileUtil.createTempDir(); assertThat(dir).exists(); String srcFileName = "src"; File srcFile = dir.resolve(srcFileName).toFile(); FileUtil.touch(srcFile); assertThat(srcFile).exists(); FileUtil.write("test", srcFile); String destFileName = "dest"; File destFile = new File(dir.toFile(), "parent1/parent2/" + destFileName); FileUtil.makesureParentDirExists(destFile); FileUtil.copy(srcFile, destFile); assertThat(Files.readFirstLine(destFile, Charsets.UTF_8)).isEqualTo("test"); } @Test public void testMakesureDirExists() throws Exception { Path dir = FileUtil.createTempDir(); String child1 = "child1"; Path child1Dir = dir.resolve(child1); FileUtil.makesureDirExists(child1Dir.toString()); assertThat(child1Dir).exists(); String child2 = "child2"; Path child2Dir = dir.resolve(child2); FileUtil.makesureDirExists(child2Dir); assertThat(child2Dir).exists(); String child3 = "child3"; Path child3Dir = dir.resolve(child3); FileUtil.makesureDirExists(child3Dir.toFile()); assertThat(child3Dir).exists(); } @Test public void testIsFileExists() throws Exception { assertThat(FileUtil.isFileExists((String) null)).isFalse(); assertThat(FileUtil.isFileExists((File) null)).isFalse(); Path dir = FileUtil.createTempDir(); FileUtil.touch(dir + "/" + "test"); assertThat(FileUtil.isFileExists(dir + "/" + "test")).isTrue(); assertThat(FileUtil.isFileExists(dir.resolve("test").toFile())).isTrue(); } @Test public void testGetFileExtension() throws Exception { Path path = FileUtil.createTempFile("aaa", ".txt"); assertThat(FileUtil.getFileExtension(path.toFile())).isEqualTo("txt"); assertThat(FileUtil.getFileExtension(path.toString())).isEqualTo("txt"); } @Test public void testIsDirExists() throws Exception { assertThat(FileUtil.isDirExists((String) null)).isFalse(); assertThat(FileUtil.isDirExists((File) null)).isFalse(); assertThat(FileUtil.isDirExists((Path) null)).isFalse(); Path dir = FileUtil.createTempDir(); assertThat(FileUtil.isDirExists(dir)).isTrue(); assertThat(FileUtil.isDirExists(dir.toString())).isTrue(); assertThat(FileUtil.isDirExists(dir.toFile())).isTrue(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/io/IOUtilTest.java ================================================ package com.vip.vjtools.vjkit.io; import static org.assertj.core.api.Assertions.*; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import org.junit.Test; import com.vip.vjtools.vjkit.io.type.StringBuilderWriter; import com.vip.vjtools.vjkit.text.Charsets; public class IOUtilTest { @Test public void read() throws IOException { assertThat(IOUtil.toString(ResourceUtil.asStream("test.txt"))).isEqualTo("ABCDEFG\nABC"); assertThat(IOUtil.toLines(ResourceUtil.asStream("test.txt"))).hasSize(2).containsExactly("ABCDEFG", "ABC"); } @Test public void write() throws IOException { StringBuilderWriter sw = new StringBuilderWriter(); IOUtil.write("hahahaha", sw); assertThat(sw.toString()).isEqualTo("hahahaha"); ByteArrayOutputStream out = new ByteArrayOutputStream(); IOUtil.write("hahahaha", out); assertThat(new String(out.toByteArray(), Charsets.UTF_8)).isEqualTo("hahahaha"); IOUtil.closeQuietly(out); IOUtil.closeQuietly((Closeable) null); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/io/ResourceUtilTest.java ================================================ package com.vip.vjtools.vjkit.io; import static org.assertj.core.api.Assertions.*; import java.io.IOException; import java.util.jar.JarFile; import org.junit.Test; import com.google.common.io.Files; public class ResourceUtilTest { @Test public void test() throws IOException { // getResoruce assertThat(ResourceUtil.toString("test.txt")).contains("ABCDEFG"); assertThat(ResourceUtil.toString(ResourceUtilTest.class, "/test.txt")).contains("ABCDEFG"); assertThat(ResourceUtil.toLines("test.txt")).containsExactly("ABCDEFG", "ABC"); assertThat(ResourceUtil.toLines(ResourceUtilTest.class, "/test.txt")).containsExactly("ABCDEFG", "ABC"); // getResoruce 处理重复的资源 System.out.println(ResourceUtil.asUrl("META-INF/MANIFEST.MF")); assertThat(ResourceUtil.toString("META-INF/MANIFEST.MF")).contains("Manifest"); // getResources assertThat(ResourceUtil.getResourcesQuietly("META-INF/MANIFEST.MF").size()).isGreaterThan(1); System.out.println(ResourceUtil.getResourcesQuietly("META-INF/MANIFEST.MF")); assertThat(ResourceUtil.getResourcesQuietly("META-INF/MANIFEST.MF", ResourceUtilTest.class.getClassLoader()) .size()).isGreaterThan(1); } @Test public void resourceNameTest() throws IOException { JarFile guavaFile = new JarFile(FilePathUtil.getJarPath(Files.class)); assertThat(guavaFile.getEntry("META-INF/MANIFEST.MF")).isNotNull(); assertThat(guavaFile.getEntry("/META-INF/MANIFEST.MF")).isNull(); guavaFile.close(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/io/URLResourceTest.java ================================================ package com.vip.vjtools.vjkit.io; import static org.assertj.core.api.Assertions.*; import java.io.File; import java.io.IOException; import java.io.InputStream; import org.junit.Test; public class URLResourceTest { @Test public void resource() throws IOException { File file = URLResourceUtil.asFile("classpath:application.properties"); assertThat(FileUtil.toString(file)).isEqualTo("springside.min=1\nspringside.max=10"); InputStream is = URLResourceUtil.asStream("classpath:application.properties"); assertThat(IOUtil.toString(is)).isEqualTo("springside.min=1\nspringside.max=10"); IOUtil.closeQuietly(is); try { URLResourceUtil.asFile("classpath:notexist.properties"); fail("should fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { URLResourceUtil.asStream("classpath:notexist.properties"); fail("should fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } } @Test public void file() throws IOException { File file = FileUtil.createTempFile().toFile(); FileUtil.write("haha", file); try { File file2 = URLResourceUtil.asFile("file://" + file.getAbsolutePath()); assertThat(FileUtil.toString(file2)).isEqualTo("haha"); File file2NotExist = URLResourceUtil.asFile("file://" + file.getAbsolutePath() + ".noexist"); assertThat(file2NotExist.exists()).isFalse(); File file3 = URLResourceUtil.asFile(file.getAbsolutePath()); assertThat(FileUtil.toString(file3)).isEqualTo("haha"); File file3NotExist = URLResourceUtil.asFile(file.getAbsolutePath() + ".noexist"); assertThat(file3NotExist.exists()).isFalse(); } finally { FileUtil.deleteFile(file); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/logging/PerformanceUtilsTest.java ================================================ package com.vip.vjtools.vjkit.logging; import org.junit.Test; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Logger; public class PerformanceUtilsTest { Logger logger = (Logger) LoggerFactory.getLogger(PerformanceUtilsTest.class); @Test public void test() throws InterruptedException { PerformanceUtil.start(); PerformanceUtil.start("test"); Thread.sleep(1000L);// NOSONAR PerformanceUtil.endWithSlowLog(logger, 100L); PerformanceUtil.endWithSlowLog(logger, "test", 100L); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/mapper/BeanMapperTest.java ================================================ package com.vip.vjtools.vjkit.mapper; import static org.assertj.core.api.Assertions.*; import java.util.List; import java.util.Map; import org.junit.Test; import com.vip.vjtools.vjkit.collection.ListUtil; public class BeanMapperTest { @Test public void copySingleObject() { Student student = new Student("zhang3", 20, new Teacher("li4"), ListUtil.newArrayList("chinese", "english")); StudentVO studentVo = BeanMapper.map(student, StudentVO.class); assertThat(studentVo.name).isEqualTo("zhang3"); assertThat(studentVo.getAge()).isEqualTo(20); assertThat(studentVo.getTeacher().getName()).isEqualTo("li4"); assertThat(studentVo.getCourse()).containsExactly("chinese", "english"); } @Test public void copyListObject() { Student student1 = new Student("zhang3", 20, new Teacher("li4"), ListUtil.newArrayList("chinese", "english")); Student student2 = new Student("zhang4", 30, new Teacher("li5"), ListUtil.newArrayList("chinese2", "english4")); Student student3 = new Student("zhang5", 40, new Teacher("li6"), ListUtil.newArrayList("chinese3", "english5")); List studentList = ListUtil.newArrayList(student1, student2, student3); List studentVoList = BeanMapper.mapList(studentList, StudentVO.class); assertThat(studentVoList).hasSize(3); StudentVO studentVo = studentVoList.get(0); assertThat(studentVo.name).isEqualTo("zhang3"); assertThat(studentVo.getAge()).isEqualTo(20); assertThat(studentVo.getTeacher().getName()).isEqualTo("li4"); assertThat(studentVo.getCourse()).containsExactly("chinese", "english"); } @Test public void copyArrayObject() { Student student1 = new Student("zhang3", 20, new Teacher("li4"), ListUtil.newArrayList("chinese", "english")); Student student2 = new Student("zhang4", 30, new Teacher("li5"), ListUtil.newArrayList("chinese2", "english4")); Student student3 = new Student("zhang5", 40, new Teacher("li6"), ListUtil.newArrayList("chinese3", "english5")); Student[] studentList = new Student[] { student1, student2, student3 }; StudentVO[] studentVoList = BeanMapper.mapArray(studentList, StudentVO.class); assertThat(studentVoList).hasSize(3); StudentVO studentVo = studentVoList[0]; assertThat(studentVo.name).isEqualTo("zhang3"); assertThat(studentVo.getAge()).isEqualTo(20); assertThat(studentVo.getTeacher().getName()).isEqualTo("li4"); assertThat(studentVo.getCourse()).containsExactly("chinese", "english"); } @Test public void copy2Map() { Teacher teacher = new Teacher("zhang"); Map map = BeanMapper.map(teacher, Map.class); assertThat(map).containsKeys("name").containsValues("zhang"); Student student = new Student("zhang3", 20, new Teacher("li4"), ListUtil.newArrayList("chinese", "english")); Map mapStu = BeanMapper.map(student, Map.class); assertThat(mapStu.containsKey("teacher")); assertThat(mapStu.get("teacher")).hasFieldOrProperty("name"); } public static class Student { public String name; private int age; private Teacher teacher; private List course = ListUtil.newArrayList(); public Student() { } public Student(String name, int age, Teacher teacher, List course) { this.name = name; this.age = age; this.teacher = teacher; this.course = course; } public List getCourse() { return course; } public void setCourse(List course) { this.course = course; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public static class Teacher { private String name; public Teacher() { } public Teacher(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public static class StudentVO { public String name; private int age; private TeacherVO teacher; private List course = ListUtil.newArrayList(); public StudentVO() { } public StudentVO(String name, int age, TeacherVO teacher, List course) { this.name = name; this.age = age; this.teacher = teacher; this.course = course; } public List getCourse() { return course; } public void setCourse(List course) { this.course = course; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public TeacherVO getTeacher() { return teacher; } public void setTeacher(TeacherVO teacher) { this.teacher = teacher; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public static class TeacherVO { private String name; public TeacherVO() { } public TeacherVO(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/mapper/JsonMapperTest.java ================================================ package com.vip.vjtools.vjkit.mapper; import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.junit.Test; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.json.JSONException; import org.skyscreamer.jsonassert.JSONAssert; /** * 测试Jackson对Object,Map,List,数组,枚举,日期类等的持久化. 更多测试见showcase中的JsonDemo. */ public class JsonMapperTest { public void assertJSONEqual(String s1, String s2) throws JSONException { JSONAssert.assertEquals(s1, s2, false); } /** * 序列化对象/集合到Json字符串. */ @Test public void toJson() throws Exception { // Bean TestBean bean = new TestBean("A"); String beanString = JsonMapper.INSTANCE.toJson(bean); System.out.println("Bean:" + beanString); assertJSONEqual(beanString, "{\"name\":\"A\",\"defaultValue\":\"hello\",\"nullValue\":null,\"emptyValue\":[]}"); // Map Map map = Maps.newLinkedHashMap(); map.put("name", "A"); map.put("age", 2); String mapString = JsonMapper.INSTANCE.toJson(map); System.out.println("Map:" + mapString); assertThat(mapString).isEqualTo("{\"name\":\"A\",\"age\":2}"); // List List stringList = Lists.newArrayList("A", "B", "C"); String listString = JsonMapper.INSTANCE.toJson(stringList); System.out.println("String List:" + listString); assertThat(listString).isEqualTo("[\"A\",\"B\",\"C\"]"); // List List beanList = Lists.newArrayList(new TestBean("A"), new TestBean("B")); String beanListString = JsonMapper.INSTANCE.toJson(beanList); System.out.println("Bean List:" + beanListString); assertJSONEqual(beanListString, "[{\"name\":\"A\",\"defaultValue\":\"hello\",\"nullValue\":null,\"emptyValue\":[]},{\"name\":\"B\",\"defaultValue\":\"hello\",\"nullValue\":null,\"emptyValue\":[]}]"); // Bean[] TestBean[] beanArray = new TestBean[] { new TestBean("A"), new TestBean("B") }; String beanArrayString = JsonMapper.INSTANCE.toJson(beanArray); System.out.println("Array List:" + beanArrayString); assertJSONEqual(beanArrayString, "[{\"name\":\"A\",\"defaultValue\":\"hello\",\"nullValue\":null,\"emptyValue\":[]},{\"name\":\"B\",\"defaultValue\":\"hello\",\"nullValue\":null,\"emptyValue\":[]}]"); } /** * 从Json字符串反序列化对象/集合. */ @Test public void fromJson() throws Exception { // Bean String beanString = "{\"name\":\"A\"}"; TestBean bean = JsonMapper.INSTANCE.fromJson(beanString, TestBean.class); System.out.println("Bean:" + bean); // Map String mapString = "{\"name\":\"A\",\"age\":2}"; Map map = JsonMapper.INSTANCE.fromJson(mapString, HashMap.class); System.out.println("Map:"); for (Entry entry : map.entrySet()) { System.out.println(entry.getKey() + " " + entry.getValue()); } // List String listString = "[\"A\",\"B\",\"C\"]"; List stringList = JsonMapper.INSTANCE.fromJson(listString, List.class); System.out.println("String List:"); for (String element : stringList) { System.out.println(element); } // List String beanListString = "[{\"name\":\"A\"},{\"name\":\"B\"}]"; List beanList = JsonMapper.INSTANCE.fromJson(beanListString, JsonMapper.INSTANCE.buildCollectionType(List.class, TestBean.class)); System.out.println("Bean List:"); for (TestBean element : beanList) { System.out.println(element); } } /** * 测试传入空对象,空字符串,Empty的集合,"null"字符串的结果. */ @Test public void nullAndEmpty() { // toJson测试 // // Null Bean TestBean nullBean = null; String nullBeanString = JsonMapper.INSTANCE.toJson(nullBean); assertThat(nullBeanString).isEqualTo("null"); // Empty List List emptyList = Lists.newArrayList(); String emptyListString = JsonMapper.INSTANCE.toJson(emptyList); assertThat(emptyListString).isEqualTo("[]"); // fromJson测试 // // Null String for Bean TestBean nullBeanResult = JsonMapper.INSTANCE.fromJson(null, TestBean.class); assertThat(nullBeanResult).isNull(); nullBeanResult = JsonMapper.INSTANCE.fromJson("null", TestBean.class); assertThat(nullBeanResult).isNull(); nullBeanResult = JsonMapper.INSTANCE.fromJson("", TestBean.class); assertThat(nullBeanResult).isNull(); nullBeanResult = JsonMapper.INSTANCE.fromJson("{}", TestBean.class); assertThat(nullBeanResult).isNotNull(); assertThat(nullBeanResult.getDefaultValue()).isEqualTo("hello"); // Null/Empty String for List List nullListResult = JsonMapper.INSTANCE.fromJson(null, List.class); assertThat(nullListResult).isNull(); nullListResult = JsonMapper.INSTANCE.fromJson("null", List.class); assertThat(nullListResult).isNull(); nullListResult = JsonMapper.INSTANCE.fromJson("[]", List.class); assertThat(nullListResult).isEmpty(); } /** * 测试三种不同的Mapper. */ @Test public void threeTypeMappers() throws JSONException { // 打印全部属性 JsonMapper normalBinder = JsonMapper.defaultMapper(); TestBean bean = new TestBean("A"); assertJSONEqual(normalBinder.toJson(bean), "{\"name\":\"A\",\"defaultValue\":\"hello\",\"nullValue\":null,\"emptyValue\":[]}"); // 不打印nullValue属性 JsonMapper nonNullMapper = JsonMapper.nonNullMapper(); assertJSONEqual(nonNullMapper.toJson(bean),"{\"name\":\"A\",\"defaultValue\":\"hello\",\"emptyValue\":[]}"); // 不打印nullValue与empty的属性 JsonMapper nonEmptyMapper = JsonMapper.nonEmptyMapper(); assertJSONEqual(nonEmptyMapper.toJson(bean),"{\"name\":\"A\",\"defaultValue\":\"hello\"}"); TestBean nonEmptyBean = nonEmptyMapper.fromJson("{\"name\":\"A\",\"defaultValue\":\"hello\"}", TestBean.class); assertThat(nonEmptyBean.getEmptyValue()).isEmpty(); } @Test public void jsonp() throws JSONException{ TestBean bean = new TestBean("A"); String jsonp = JsonMapper.nonEmptyMapper().toJsonP("haha", bean); String testJSON="haha({\"name\":\"A\",\"defaultValue\":\"hello\"})"; String expected = testJSON.substring(testJSON.indexOf("(")+1,testJSON.indexOf(")")); String test = jsonp.substring(jsonp.indexOf("(")+1, jsonp.indexOf(")")); assertThat(jsonp.replace(test,"")).isEqualTo(testJSON.replace(expected,"")); assertJSONEqual(expected, test); } @Test public void update() { TestBean bean = new TestBean("A"); bean.setDefaultValue("lalala"); JsonMapper.INSTANCE.update("{\"name\":\"B\"}", bean); assertThat(bean.getName()).isEqualTo("B"); assertThat(bean.getDefaultValue()).isEqualTo("lalala"); } public static class TestBean { private String name; private String defaultValue = "hello"; private String nullValue = null; private List emptyValue = new ArrayList(); public TestBean() { } public TestBean(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDefaultValue() { return defaultValue; } public void setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; } public String getNullValue() { return nullValue; } public void setNullValue(String nullValue) { this.nullValue = nullValue; } public List getEmptyValue() { return emptyValue; } public void setEmptyValue(List emptyValue) { this.emptyValue = emptyValue; } @Override public String toString() { return "TestBean [name=" + name + ", defaultValue=" + defaultValue + ", nullValue=" + nullValue + ", emptyValue=" + emptyValue + "]"; } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/mapper/XmlMapperTest.java ================================================ package com.vip.vjtools.vjkit.mapper; import static org.assertj.core.api.Assertions.*; import java.util.List; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlType; import org.apache.commons.lang3.builder.ToStringBuilder; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.junit.Test; import com.google.common.collect.Lists; /** * 演示基于JAXB2.0的Java对象-XML转换及Dom4j的使用. * * 演示用xml如下: * *

 * 
 * 
 * 	calvin
 * 	
 * 		movie
 * 		sports
 * 	
 * 
 * 
*/ public class XmlMapperTest { @Test public void objectToXml() { User user = new User(); user.setId(1L); user.setName("calvin"); user.getInterests().add("movie"); user.getInterests().add("sports"); String xml = XmlMapper.toXml(user, "UTF-8"); System.out.println("Jaxb Object to Xml result:\n" + xml); assertXmlByDom4j(xml); } @Test public void xmlToObject() { String xml = generateXmlByDom4j(); User user = XmlMapper.fromXml(xml, User.class); System.out.println("Jaxb Xml to Object result:\n" + user); assertThat(user.getId()).isEqualTo(1L); assertThat(user.getInterests()).containsOnly("movie", "sports"); } /** * 测试以List对象作为根节点时的XML输出 */ @Test public void toXmlWithListAsRoot() { User user1 = new User(); user1.setId(1L); user1.setName("calvin"); User user2 = new User(); user2.setId(2L); user2.setName("kate"); List userList = Lists.newArrayList(user1, user2); String xml = XmlMapper.toXml(userList, "userList", User.class, "UTF-8"); System.out.println("Jaxb Object List to Xml result:\n" + xml); } /** * 使用Dom4j生成测试用的XML文档字符串. */ private static String generateXmlByDom4j() { Document document = DocumentHelper.createDocument(); Element root = document.addElement("user").addAttribute("id", "1"); root.addElement("name").setText("calvin"); // List Element interests = root.addElement("interests"); interests.addElement("interest").addText("movie"); interests.addElement("interest").addText("sports"); return document.asXML(); } /** * 使用Dom4j验证Jaxb所生成XML的正确性. */ private static void assertXmlByDom4j(String xml) { Document doc = null; try { doc = DocumentHelper.parseText(xml); } catch (DocumentException e) { fail(e.getMessage()); } Element user = doc.getRootElement(); assertThat(user.attribute("id").getValue()).isEqualTo("1"); Element interests = (Element) doc.selectSingleNode("//interests"); assertThat(interests.elements()).hasSize(2); assertThat(((Element) interests.elements().get(0)).getText()).isEqualTo("movie"); } @XmlRootElement // 指定子节点的顺序 @XmlType(propOrder = { "name", "interests" }) private static class User { private Long id; private String name; private String password; private List interests = Lists.newArrayList(); // 设置转换为xml节点中的属性 @XmlAttribute public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } // 设置不转换为xml @XmlTransient public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } // 设置对List的映射, xml为movie @XmlElementWrapper(name = "interests") @XmlElement(name = "interest") public List getInterests() { return interests; } public void setInterests(List interests) { this.interests = interests; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/net/IPUtilTest.java ================================================ package com.vip.vjtools.vjkit.net; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class IPUtilTest { @Test public void stringAndInt() { assertThat(IPUtil.ipv4StringToInt("192.168.0.1")).isEqualTo(-1062731775); assertThat(IPUtil.ipv4StringToInt("192.168.0.2")).isEqualTo(-1062731774); assertThat(IPUtil.intToIpv4String(-1062731775)).isEqualTo("192.168.0.1"); assertThat(IPUtil.intToIpv4String(-1062731774)).isEqualTo("192.168.0.2"); } @Test public void inetAddress() { assertThat(IPUtil.fromInt(-1062731775).getHostAddress()).isEqualTo("192.168.0.1"); assertThat(IPUtil.fromInt(-1062731774).getHostAddress()).isEqualTo("192.168.0.2"); assertThat(IPUtil.fromIpString("192.168.0.1").getHostAddress()).isEqualTo("192.168.0.1"); assertThat(IPUtil.fromIpString("192.168.0.2").getHostAddress()).isEqualTo("192.168.0.2"); assertThat(IPUtil.fromIpv4String("192.168.0.1").getHostAddress()).isEqualTo("192.168.0.1"); assertThat(IPUtil.fromIpv4String("192.168.0.2").getHostAddress()).isEqualTo("192.168.0.2"); assertThat(IPUtil.toInt(IPUtil.fromIpString("192.168.0.1"))).isEqualTo(-1062731775); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/net/NetUtilTest.java ================================================ package com.vip.vjtools.vjkit.net; import static org.assertj.core.api.Assertions.*; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.UnknownHostException; import javax.net.ServerSocketFactory; import org.junit.Test; import org.mockito.internal.util.io.IOUtil; public class NetUtilTest { @Test public void localhost() { assertThat(NetUtil.getLocalHost()).isNotEqualTo("127.0.0.1"); assertThat(NetUtil.getLocalAddress().getHostAddress()).isNotEqualTo("127.0.0.1"); } @Test public void portDetect() throws UnknownHostException, IOException { int port = NetUtil.findRandomAvailablePort(20000, 20100); assertThat(port).isBetween(20000, 20100); System.out.println("random port:" + port); assertThat(NetUtil.isPortAvailable(port)).isTrue(); int port2 = NetUtil.findAvailablePortFrom(port); assertThat(port2).isEqualTo(port); int port3 = NetUtil.findRandomAvailablePort(); assertThat(port3).isBetween(NetUtil.PORT_RANGE_MIN, NetUtil.PORT_RANGE_MAX); System.out.println("random port:" + port3); // 尝试占住一个端口 ServerSocket serverSocket = null; try { serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost")); assertThat(NetUtil.isPortAvailable(port)).isFalse(); int port4 = NetUtil.findAvailablePortFrom(port); assertThat(port4).isEqualTo(port + 1); try { int port5 = NetUtil.findRandomAvailablePort(port, port); fail("should fail before"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalStateException.class); } } finally { IOUtil.close(serverSocket); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/number/MathUtilTest.java ================================================ package com.vip.vjtools.vjkit.number; import static org.assertj.core.api.Assertions.*; import java.math.RoundingMode; import org.junit.Test; public class MathUtilTest { @Test public void power2() { try { assertThat(MathUtil.nextPowerOfTwo(-5)).isEqualTo(8); fail("should fail here"); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); } assertThat(MathUtil.nextPowerOfTwo(5)).isEqualTo(8); assertThat(MathUtil.nextPowerOfTwo(99)).isEqualTo(128); assertThat(MathUtil.previousPowerOfTwo(5)).isEqualTo(4); assertThat(MathUtil.previousPowerOfTwo(99)).isEqualTo(64); assertThat(MathUtil.isPowerOfTwo(32)).isTrue(); assertThat(MathUtil.isPowerOfTwo(31)).isFalse(); assertThat(MathUtil.nextPowerOfTwo(5L)).isEqualTo(8L); assertThat(MathUtil.nextPowerOfTwo(99L)).isEqualTo(128L); assertThat(MathUtil.previousPowerOfTwo(5L)).isEqualTo(4L); assertThat(MathUtil.previousPowerOfTwo(99L)).isEqualTo(64L); assertThat(MathUtil.isPowerOfTwo(32L)).isTrue(); assertThat(MathUtil.isPowerOfTwo(31L)).isFalse(); assertThat(MathUtil.isPowerOfTwo(-2)).isFalse(); assertThat(MathUtil.modByPowerOfTwo(0, 16)).isEqualTo(0); assertThat(MathUtil.modByPowerOfTwo(1, 16)).isEqualTo(1); assertThat(MathUtil.modByPowerOfTwo(31, 16)).isEqualTo(15); assertThat(MathUtil.modByPowerOfTwo(32, 16)).isEqualTo(0); assertThat(MathUtil.modByPowerOfTwo(65, 16)).isEqualTo(1); assertThat(MathUtil.modByPowerOfTwo(-1, 16)).isEqualTo(15); } @Test public void caculate() { assertThat(MathUtil.mod(15, 10)).isEqualTo(5); assertThat(MathUtil.mod(-15, 10)).isEqualTo(5); assertThat(MathUtil.mod(-5, 3)).isEqualTo(1); assertThat(MathUtil.mod(15l, 10l)).isEqualTo(5); assertThat(MathUtil.mod(-15l, 10l)).isEqualTo(5); assertThat(MathUtil.mod(-5l, 3l)).isEqualTo(1); assertThat(MathUtil.mod(15l, 10)).isEqualTo(5); assertThat(MathUtil.mod(-15l, 10)).isEqualTo(5); assertThat(MathUtil.mod(-5l, 3)).isEqualTo(1); assertThat(MathUtil.pow(2, 3)).isEqualTo(8); assertThat(MathUtil.pow(2, 0)).isEqualTo(1); assertThat(MathUtil.pow(2l, 3)).isEqualTo(8); assertThat(MathUtil.pow(2l, 0)).isEqualTo(1); assertThat(MathUtil.sqrt(15, RoundingMode.HALF_UP)).isEqualTo(4); assertThat(MathUtil.sqrt(16, RoundingMode.HALF_UP)).isEqualTo(4); assertThat(MathUtil.sqrt(10l, RoundingMode.HALF_UP)).isEqualTo(3); } @Test public void divide() { assertThat(11 / 4).isEqualTo(2); assertThat(10L / 4).isEqualTo(2); assertThat(MathUtil.divide(10, 4, RoundingMode.HALF_UP)).isEqualTo(3); assertThat(MathUtil.divide(10L, 4L, RoundingMode.HALF_DOWN)).isEqualTo(2); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/number/MoneyUtilTest.java ================================================ package com.vip.vjtools.vjkit.number; import static org.assertj.core.api.Assertions.*; import java.math.BigDecimal; import java.text.ParseException; import org.junit.Test; public class MoneyUtilTest { @Test public void amountConvertTest() { // 金额分转换成元 assertThat(MoneyUtil.fen2yuan(100).doubleValue()).isEqualTo(new BigDecimal(1.00d).doubleValue()); assertThat(MoneyUtil.fen2yuan("100").doubleValue()).isEqualTo(new BigDecimal(1.00d).doubleValue()); assertThat(MoneyUtil.fen2yuan(BigDecimal.valueOf(100d)).doubleValue()) .isEqualTo(new BigDecimal(1.00d).doubleValue()); // 金额元转换成分 assertThat(MoneyUtil.yuan2fen(BigDecimal.valueOf(1d)).doubleValue()) .isEqualTo(new BigDecimal(100d).doubleValue()); assertThat(MoneyUtil.yuan2fen(1L).doubleValue()).isEqualTo(new BigDecimal(100d).doubleValue()); } @Test public void format() { assertThat(MoneyUtil.format(1111.111)).isEqualTo("1111.11"); assertThat(MoneyUtil.prettyFormat(1111.111)).isEqualTo("1,111.11"); assertThat(MoneyUtil.format(1111.111, "0.0")).isEqualTo("1111.1"); } @Test public void parse() throws ParseException { assertThat(MoneyUtil.parseString("1111.11")).isEqualTo(new BigDecimal(1111.11)); assertThat(MoneyUtil.parsePrettyString("1,111.11")).isEqualTo(new BigDecimal(1111.11)); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/number/NumberUtilTest.java ================================================ package com.vip.vjtools.vjkit.number; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class NumberUtilTest { @Test public void equalsWithin() { float f = 0.15f; float f2 = 0.45f / 3; float f3 = 0.46f / 3; assertThat(NumberUtil.equalsWithin(f, f2)).isTrue(); assertThat(NumberUtil.equalsWithin(f, f3)).isFalse(); assertThat(NumberUtil.equalsWithin(f, f2, 0.0000001)).isTrue(); } @Test public void toBytes() { byte[] bytes = NumberUtil.toBytes(1); assertThat(bytes).hasSize(4).containsSequence((byte) 0, (byte) 0, (byte) 0, (byte) 1); bytes = NumberUtil.toBytes(257); assertThat(bytes).containsSequence((byte) 0, (byte) 0, (byte) 1, (byte) 1); assertThat(NumberUtil.toInt(bytes)).isEqualTo(257); // long byte[] bytes2 = NumberUtil.toBytes(1L); assertThat(bytes2).hasSize(8); bytes2 = NumberUtil.toBytes(257L); assertThat(bytes2).containsSequence((byte) 0, (byte) 0, (byte) 1, (byte) 1); assertThat(NumberUtil.toLong(bytes2)).isEqualTo(257L); // dobule byte[] bytes3 = NumberUtil.toBytes(1.123d); assertThat(NumberUtil.toDouble(bytes3)).isEqualTo(1.123d); // toInt32 assertThat(NumberUtil.toInt32(123l)).isEqualTo(123); try { NumberUtil.toInt32(Long.valueOf(Integer.MAX_VALUE + 1l)); fail("should fail here"); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); } } @Test public void isNumber() { assertThat(NumberUtil.isNumber("123")).isTrue(); assertThat(NumberUtil.isNumber("-123.1")).isTrue(); assertThat(NumberUtil.isNumber("-1a3.1")).isFalse(); assertThat(NumberUtil.isHexNumber("0x12F")).isTrue(); assertThat(NumberUtil.isHexNumber("-0x12A3")).isTrue(); assertThat(NumberUtil.isHexNumber("12A3")).isFalse(); } @Test public void toNumber() { assertThat(NumberUtil.toInt("122")).isEqualTo(122); try { NumberUtil.toInt("12A"); fail("shoud fail here"); } catch (NumberFormatException e) { } try { NumberUtil.toInt((String) null); fail("shoud fail here"); } catch (NumberFormatException e) { } assertThat(NumberUtil.toInt("12A", 123)).isEqualTo(123); assertThat(NumberUtil.toLong("122")).isEqualTo(122L); try { NumberUtil.toLong("12A"); fail("shoud fail here"); } catch ( NumberFormatException e) { } try { NumberUtil.toLong((String) null); fail("shoud fail here"); } catch (NumberFormatException e) { } assertThat(NumberUtil.toLong("12A", 123)).isEqualTo(123L); assertThat(NumberUtil.toDouble("122.1")).isEqualTo(122.1); try { NumberUtil.toDouble("12A"); fail("shoud fail here"); } catch (NumberFormatException e) { } try { NumberUtil.toDouble((String) null); fail("shoud fail here"); } catch (NumberFormatException e) { } assertThat(NumberUtil.toDouble("12A", 123.1)).isEqualTo(123.1); assertThat(NumberUtil.toIntObject("122")).isEqualTo(122); try { NumberUtil.toIntObject("12A"); fail("shoud fail here"); } catch (NumberFormatException e) { } assertThat(NumberUtil.toIntObject("12A", 123)).isEqualTo(123); assertThat(NumberUtil.toIntObject(null, 123)).isEqualTo(123); assertThat(NumberUtil.toIntObject("", 123)).isEqualTo(123); assertThat(NumberUtil.toLongObject("122")).isEqualTo(122L); try { NumberUtil.toLongObject("12A"); fail("shoud fail here"); } catch (NumberFormatException e) { } assertThat(NumberUtil.toLongObject("12A", 123L)).isEqualTo(123L); assertThat(NumberUtil.toLongObject(null, 123L)).isEqualTo(123L); assertThat(NumberUtil.toDoubleObject("122.1")).isEqualTo(122.1); try { NumberUtil.toDoubleObject("12A"); fail("shoud fail here"); } catch (NumberFormatException e) { } assertThat(NumberUtil.toDoubleObject("12A", 123.1)).isEqualTo(123.1); assertThat(NumberUtil.hexToIntObject("0x10")).isEqualTo(16); assertThat(NumberUtil.hexToIntObject("0X100")).isEqualTo(256); assertThat(NumberUtil.hexToIntObject("-0x100")).isEqualTo(-256); try { NumberUtil.hexToIntObject("0xHI"); fail("shoud fail here"); } catch (NumberFormatException e) { } try { NumberUtil.hexToIntObject(null); fail("shoud fail here"); } catch (NumberFormatException e) { } assertThat(NumberUtil.hexToIntObject("0xHI", 123)).isEqualTo(123); assertThat(NumberUtil.hexToLongObject("0x10")).isEqualTo(16L); assertThat(NumberUtil.hexToLongObject("0X100")).isEqualTo(256L); assertThat(NumberUtil.hexToLongObject("-0x100")).isEqualTo(-256L); try { NumberUtil.hexToLongObject("0xHI"); fail("shoud fail here"); } catch (NumberFormatException e) { } try { NumberUtil.hexToLongObject(null); fail("shoud fail here"); } catch (NumberFormatException e) { } assertThat(NumberUtil.hexToLongObject("0xHI", 123L)).isEqualTo(123L); } @Test public void toStringTest() { assertThat(NumberUtil.toString(23)).isEqualTo("23"); assertThat(NumberUtil.toString(new Integer(23))).isEqualTo("23"); assertThat(NumberUtil.toString(23l)).isEqualTo("23"); assertThat(NumberUtil.toString(new Long(23))).isEqualTo("23"); assertThat(NumberUtil.toString(23l)).isEqualTo("23"); assertThat(NumberUtil.toString(new Double(23.112d))).isEqualTo("23.112"); assertThat(NumberUtil.toString(23.112d)).isEqualTo("23.112"); assertThat(NumberUtil.to2DigitString(23.112d)).isEqualTo("23.11"); assertThat(NumberUtil.to2DigitString(23.116d)).isEqualTo("23.12"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/number/RandomUtilTest.java ================================================ package com.vip.vjtools.vjkit.number; import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; public class RandomUtilTest { @Test public void getRandom() { System.out.println(RandomUtil.secureRandom().nextInt()); System.out.println(RandomUtil.threadLocalRandom().nextInt()); } @Test public void randomNumber() { int i = RandomUtil.nextInt(); assertThat(i).isBetween(0, Integer.MAX_VALUE); i = RandomUtil.nextInt(RandomUtil.threadLocalRandom()); assertThat(i).isBetween(0, Integer.MAX_VALUE); i = RandomUtil.nextInt(10); assertThat(i).isBetween(0, 10); i = RandomUtil.nextInt(RandomUtil.threadLocalRandom(), 10); assertThat(i).isBetween(0, 10); i = RandomUtil.nextInt(10, 20); assertThat(i).isBetween(10, 20); i = RandomUtil.nextInt(RandomUtil.threadLocalRandom(), 10, 20); assertThat(i).isBetween(10, 20); long l = RandomUtil.nextLong(); assertThat(l).isBetween(0L, Long.MAX_VALUE); l = RandomUtil.nextLong(RandomUtil.threadLocalRandom()); assertThat(l).isBetween(0L, Long.MAX_VALUE); l = RandomUtil.nextLong(10); assertThat(l).isBetween(0L, 10L); l = RandomUtil.nextLong(RandomUtil.threadLocalRandom(), 10L); assertThat(l).isBetween(0L, 10L); l = RandomUtil.nextLong(10L, 20L); assertThat(l).isBetween(10L, 20L); l = RandomUtil.nextLong(RandomUtil.threadLocalRandom(), 10, 20); assertThat(l).isBetween(10L, 20L); double d = RandomUtil.nextDouble(); assertThat(d).isBetween(0d, Double.MAX_VALUE); d = RandomUtil.nextDouble(RandomUtil.threadLocalRandom()); assertThat(d).isBetween(0d, Double.MAX_VALUE); d = RandomUtil.nextDouble(10); assertThat(d).isBetween(0d, 10d); d = RandomUtil.nextDouble(RandomUtil.threadLocalRandom(), 10L); assertThat(d).isBetween(0d, 10d); d = RandomUtil.nextDouble(10L, 20L); assertThat(d).isBetween(10d, 20d); d = RandomUtil.nextDouble(RandomUtil.threadLocalRandom(), 10, 20); assertThat(d).isBetween(10d, 20d); } @Test public void generateString() { System.out.println(RandomUtil.randomStringFixLength(5)); System.out.println(RandomUtil.randomStringRandomLength(5, 10)); System.out.println(RandomUtil.randomStringFixLength(RandomUtil.threadLocalRandom(), 5)); System.out.println(RandomUtil.randomStringRandomLength(RandomUtil.threadLocalRandom(), 5, 10)); assertThat(RandomUtil.randomStringFixLength(5)).hasSize(5); assertThat(RandomUtil.randomStringFixLength(RandomUtil.threadLocalRandom(), 5)).hasSize(5); System.out.println(RandomUtil.randomLetterFixLength(5)); System.out.println(RandomUtil.randomLetterRandomLength(5, 10)); System.out.println(RandomUtil.randomLetterFixLength(RandomUtil.threadLocalRandom(), 5)); System.out.println(RandomUtil.randomLetterRandomLength(RandomUtil.threadLocalRandom(), 5, 10)); assertThat(RandomUtil.randomLetterFixLength(5)).hasSize(5); assertThat(RandomUtil.randomLetterFixLength(RandomUtil.threadLocalRandom(), 5)).hasSize(5); System.out.println(RandomUtil.randomAsciiFixLength(5)); System.out.println(RandomUtil.randomAsciiRandomLength(5, 10)); System.out.println(RandomUtil.randomAsciiFixLength(RandomUtil.threadLocalRandom(), 5)); System.out.println(RandomUtil.randomAsciiRandomLength(RandomUtil.threadLocalRandom(), 5, 10)); assertThat(RandomUtil.randomAsciiFixLength(5)).hasSize(5); assertThat(RandomUtil.randomAsciiFixLength(RandomUtil.threadLocalRandom(), 5)).hasSize(5); } public void test0() { double a = 0.0; double b = 1.0; int x = 0x7fffffff; } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/number/UnitConverterTest.java ================================================ package com.vip.vjtools.vjkit.number; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class UnitConverterTest { @Test public void convertDurationMillis() { assertThat(UnitConverter.toDurationMillis("12345")).isEqualTo(12345); assertThat(UnitConverter.toDurationMillis("12S")).isEqualTo(12000); assertThat(UnitConverter.toDurationMillis("12s")).isEqualTo(12000); assertThat(UnitConverter.toDurationMillis("12ms")).isEqualTo(12); assertThat(UnitConverter.toDurationMillis("12m")).isEqualTo(12 * 60 * 1000); assertThat(UnitConverter.toDurationMillis("12h")).isEqualTo(12l * 60 * 60 * 1000); assertThat(UnitConverter.toDurationMillis("12d")).isEqualTo(12l * 24 * 60 * 60 * 1000); try { assertThat(UnitConverter.toDurationMillis("12a")).isEqualTo(12 * 60 * 1000); fail("should fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { assertThat(UnitConverter.toDurationMillis("a12")).isEqualTo(12 * 60 * 1000); fail("should fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } } @Test public void convertSizeBytes() { assertThat(UnitConverter.toBytes("12345")).isEqualTo(12345); assertThat(UnitConverter.toBytes("12b")).isEqualTo(12); assertThat(UnitConverter.toBytes("12k")).isEqualTo(12 * 1024); assertThat(UnitConverter.toBytes("12M")).isEqualTo(12 * 1024 * 1024); assertThat(UnitConverter.toBytes("12G")).isEqualTo(12l * 1024 * 1024 * 1024); assertThat(UnitConverter.toBytes("12T")).isEqualTo(12l * 1024 * 1024 * 1024 * 1024); try { UnitConverter.toBytes("12x"); fail("should fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } try { UnitConverter.toBytes("a12"); fail("should fail"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } } @Test public void convertToSizeUnit() { assertThat(UnitConverter.toSizeUnit(966L, 0)).isEqualTo(" 966"); assertThat(UnitConverter.toSizeUnit(1522L, 0)).isEqualTo(" 1k"); assertThat(UnitConverter.toSizeUnit(1522L, 1)).isEqualTo(" 1.5k"); assertThat(UnitConverter.toSizeUnit(1024L * 1024 * 2 + 1024 * 200, 0)).isEqualTo(" 2m"); assertThat(UnitConverter.toSizeUnit(1024L * 1024 * 2 + 1024 * 600, 0)).isEqualTo(" 3m"); assertThat(UnitConverter.toSizeUnit(1024L * 1024 * 2 + 1024 * 140, 1)).isEqualTo(" 2.1m"); assertThat(UnitConverter.toSizeUnit(1024L * 1024 * 2 + 1024 * 160, 1)).isEqualTo(" 2.2m"); assertThat(UnitConverter.toSizeUnit(1024L * 1024 * 1024 * 2 + 1024 * 1024 * 200, 0)).isEqualTo(" 2g"); assertThat(UnitConverter.toSizeUnit(1024L * 1024 * 1024 * 2 + 1024 * 1024 * 200, 1)).isEqualTo(" 2.2g"); assertThat(UnitConverter.toSizeUnit(1024L * 1024 * 1024 * 1024 * 2 + 1024L * 1024 * 1024 * 200, 0)) .isEqualTo(" 2t"); assertThat(UnitConverter.toSizeUnit(1024L * 1024 * 1024 * 1024 * 2 + 1024L * 1024 * 1024 * 200, 1)) .isEqualTo(" 2.2t"); } @Test public void convertToTimeUnit() { assertThat(UnitConverter.toTimeUnit(1322L, 0)).isEqualTo(" 1s"); assertThat(UnitConverter.toTimeUnit(1322L, 1)).isEqualTo(" 1.3s"); assertThat(UnitConverter.toTimeUnit(1000L * 62, 0)).isEqualTo(" 1m"); assertThat(UnitConverter.toTimeUnit(1000L * 90, 0)).isEqualTo(" 2m"); assertThat(UnitConverter.toTimeUnit(1000L * 90, 1)).isEqualTo(" 1.5m"); assertThat(UnitConverter.toTimeUnit(1000L * 60 * 70, 1)).isEqualTo(" 1.2h"); assertThat(UnitConverter.toTimeUnit(1000L * 60 * 60 * 28, 1)).isEqualTo(" 1.2d"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/reflect/ClassUtilTest.java ================================================ package com.vip.vjtools.vjkit.reflect; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.junit.Test; public class ClassUtilTest { @Test public void getMessage() { assertThat(ClassUtil.getShortClassName(ClassUtilTest.class)).isEqualTo("ClassUtilTest"); assertThat(ClassUtil.getShortClassName(BClass.class)).isEqualTo("ClassUtilTest.BClass"); assertThat(ClassUtil.getShortClassName(ClassUtilTest.class.getName())).isEqualTo("ClassUtilTest"); assertThat(ClassUtil.getShortClassName(BClass.class.getName())).isEqualTo("ClassUtilTest.BClass"); assertThat(ClassUtil.getPackageName(ClassUtilTest.class)).isEqualTo("com.vip.vjtools.vjkit.reflect"); assertThat(ClassUtil.getPackageName(BClass.class)).isEqualTo("com.vip.vjtools.vjkit.reflect"); assertThat(ClassUtil.getPackageName(ClassUtilTest.class.getName())).isEqualTo("com.vip.vjtools.vjkit.reflect"); assertThat(ClassUtil.getPackageName(BClass.class.getName())).isEqualTo("com.vip.vjtools.vjkit.reflect"); } @Test public void getAllClass() { assertThat(ClassUtil.getAllInterfaces(BClass.class)).hasSize(4).contains(AInterface.class, BInterface.class, CInterface.class, DInterface.class); assertThat(ClassUtil.getAllSuperclasses(BClass.class)).hasSize(2).contains(AClass.class, Object.class); assertThat(AnnotationUtil.getAllAnnotations(BClass.class)).hasSize(4); assertThat(AnnotationUtil.getAnnotatedPublicFields(BClass.class, AAnnotation.class)).hasSize(2).contains( ReflectionUtil.getField(BClass.class, "sfield"), ReflectionUtil.getField(BClass.class, "tfield")); assertThat(AnnotationUtil.getAnnotatedFields(BClass.class, EAnnotation.class)).hasSize(3).contains( ReflectionUtil.getField(BClass.class, "bfield"), ReflectionUtil.getField(BClass.class, "efield"), ReflectionUtil.getField(AClass.class, "afield")); assertThat(AnnotationUtil.getAnnotatedFields(BClass.class, FAnnotation.class)).hasSize(1) .contains(ReflectionUtil.getField(AClass.class, "dfield")); assertThat(AnnotationUtil.getAnnotatedPublicMethods(BClass.class, FAnnotation.class)).hasSize(3).contains( ReflectionUtil.getAccessibleMethodByName(BClass.class, "hello"), ReflectionUtil.getAccessibleMethodByName(BClass.class, "hello3"), ReflectionUtil.getAccessibleMethodByName(AClass.class, "hello4")); } @Test public void getSuperClassGenericType() { // 获取第1,2个泛型类型 assertThat(ClassUtil.getClassGenericType(TestBean.class)).isEqualTo(String.class); assertThat(ClassUtil.getClassGenericType(TestBean.class, 1)).isEqualTo(Long.class); // 定义父类时无泛型定义 assertThat(ClassUtil.getClassGenericType(TestBean2.class)).isEqualTo(Object.class); // 无父类定义 assertThat(ClassUtil.getClassGenericType(TestBean3.class)).isEqualTo(Object.class); } public void classPresent() { assertThat(ClassLoaderUtil.isPresent("a.b.c", ClassLoaderUtil.getDefaultClassLoader())).isFalse(); assertThat(ClassLoaderUtil.isPresent("com.vip.vjtools.vjkit.reflect.ClassUtil", ClassLoaderUtil.getDefaultClassLoader())).isTrue(); } /** * Unit test case of {@link com.vip.vjtools.vjkit.reflect.ClassUtil#isSubClassOrInterfaceOf(Class, Class)} */ @Test public void testIsSubClassOrInterfaceOf() { assertTrue("TestBean should be subclass of ParentBean", ClassUtil.isSubClassOrInterfaceOf(BClass.class, AClass.class)); assertTrue("BInterface should be subinterface of AInterface", ClassUtil.isSubClassOrInterfaceOf(BInterface.class, AInterface.class)); assertTrue("BClass should be an implementation of BInterface", ClassUtil.isSubClassOrInterfaceOf(BClass.class, BInterface.class)); assertTrue("BClass should be an implementation of AInterface", ClassUtil.isSubClassOrInterfaceOf(BClass.class, AInterface.class)); } public static class ParentBean { } public static class TestBean extends ParentBean { } public static class TestBean2 extends ParentBean { } public static class TestBean3 { } public interface AInterface { } @CAnnotation public interface BInterface extends AInterface { @FAnnotation void hello(); } public interface CInterface { } public interface DInterface { } @Retention(RetentionPolicy.RUNTIME) public @interface AAnnotation { } @Retention(RetentionPolicy.RUNTIME) @AAnnotation public @interface BAnnotation { } @Retention(RetentionPolicy.RUNTIME) public @interface CAnnotation { } @Retention(RetentionPolicy.RUNTIME) public @interface DAnnotation { } @Retention(RetentionPolicy.RUNTIME) public @interface EAnnotation { } @Retention(RetentionPolicy.RUNTIME) public @interface FAnnotation { } @DAnnotation public static class AClass implements DInterface { @EAnnotation private int afield; private int cfield; @FAnnotation private int dfield; @AAnnotation public int tfield; @AAnnotation protected int vfield; // not counted as public annotated method public void hello2(int i) { } // counted as public annotated method @FAnnotation public void hello4(int i) { } // not counted as public annotated method @FAnnotation protected void hello5(int i) { } // not counted as public annotated method @FAnnotation private void hello6(int i) { } // not counted as public annotated method, because the child override it @FAnnotation public void hello7(int i) { } } @BAnnotation public static class BClass extends AClass implements CInterface, BInterface { @EAnnotation private int bfield; @EAnnotation private int efield; @AAnnotation public int sfield; @AAnnotation protected int ufield; // counted as public annotated method, BInterface @Override @EAnnotation public void hello() { // TODO Auto-generated method stub } public void hello2(int i) { } // counted as public annotated method @FAnnotation public void hello3(int i) { } // not counted as public annotated method @Override public void hello7(int i) { } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/reflect/ClassloaderUtilTest.java ================================================ package com.vip.vjtools.vjkit.reflect; import org.junit.Test; public class ClassloaderUtilTest { @Test public void test() { ClassLoader loader = ClassLoaderUtil.getDefaultClassLoader(); ClassLoaderUtil.isPresent("com.vip.vjtools.vjkit.reflect.ClassUtil", loader); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/reflect/ReflectionUtilTest.java ================================================ package com.vip.vjtools.vjkit.reflect; import static org.assertj.core.api.Assertions.*; import java.lang.reflect.InvocationTargetException; import java.util.List; import org.junit.Test; import com.vip.vjtools.vjkit.base.type.UncheckedException; import com.vip.vjtools.vjkit.collection.ListUtil; public class ReflectionUtilTest { @Test public void getAndSetFieldValue() { TestBean bean = new TestBean(); // 无需getter函数, 直接读取privateField assertThat((int) ReflectionUtil.getFieldValue(bean, "privateField")).isEqualTo(1); // 先尝试getter函数, 然后直接读取privateField assertThat((int) ReflectionUtil.getProperty(bean, "privateField")).isEqualTo(1); // 绕过将publicField+1的getter函数,直接读取publicField的原始值 assertThat((int) ReflectionUtil.getFieldValue(bean, "publicField")).isEqualTo(1); // 先尝试getter函数, 成功则补不直接读取publicField assertThat((int) ReflectionUtil.getProperty(bean, "publicField")).isEqualTo(2); bean = new TestBean(); // 无需setter函数, 直接设置privateField ReflectionUtil.setFieldValue(bean, "privateField", 2); assertThat(bean.inspectPrivateField()).isEqualTo(2); ReflectionUtil.setProperty(bean, "privateField", 3); assertThat(bean.inspectPrivateField()).isEqualTo(3); // 绕过将publicField+1的setter函数,直接设置publicField的原始值 ReflectionUtil.setFieldValue(bean, "publicField", 2); assertThat(bean.inspectPublicField()).isEqualTo(2); // 没有绕过将publicField+1的setter函数 ReflectionUtil.setProperty(bean, "publicField", 3); assertThat(bean.inspectPublicField()).isEqualTo(4); try { ReflectionUtil.getFieldValue(bean, "notExist"); failBecauseExceptionWasNotThrown(IllegalArgumentException.class); } catch (IllegalArgumentException e) { // NOSONAR } try { ReflectionUtil.setFieldValue(bean, "notExist", 2); failBecauseExceptionWasNotThrown(IllegalArgumentException.class); } catch (IllegalArgumentException e) { // NOSONAR } } @Test public void invokeGetterAndSetter() { TestBean bean = new TestBean(); assertThat((int) ReflectionUtil.invokeGetter(bean, "publicField")).isEqualTo(bean.inspectPublicField() + 1); bean = new TestBean(); // 通过setter的函数将+1 ReflectionUtil.invokeSetter(bean, "publicField", 10); assertThat(bean.inspectPublicField()).isEqualTo(10 + 1); } @Test public void invokeMethod() { TestBean bean = new TestBean(); // 使用函数名+参数类型的匹配, 支持传参数 assertThat((String) ReflectionUtil.invokeMethod(bean, "privateMethod", new Object[] { "calvin" })) .isEqualTo("hello calvin"); // 使用函数名+参数类型的匹配 assertThat((String) ReflectionUtil.invokeMethod(bean, "privateMethod", new Object[] { "calvin" }, new Class[] { String.class })).isEqualTo("hello calvin"); // 仅匹配函数名 assertThat((String) ReflectionUtil.invokeMethodByName(bean, "privateMethod", new Object[] { "calvin" })) .isEqualTo("hello calvin"); // 各种类型 assertThat((int) ReflectionUtil.invokeMethod(bean, "intType", new Object[] { 1 }, new Class[] { int.class })) .isEqualTo(1); assertThat((int) ReflectionUtil.invokeMethod(bean, "integerType", new Object[] { 1 }, new Class[] { Integer.class })).isEqualTo(1); assertThat((int) ReflectionUtil.invokeMethod(bean, "listType", new Object[] { ListUtil.newArrayList("1", "2") }, new Class[] { List.class })).isEqualTo(2); assertThat((int) ReflectionUtil.invokeMethod(bean, "intType", 1)).isEqualTo(1); assertThat((int) ReflectionUtil.invokeMethod(bean, "integerType", 1)).isEqualTo(1); assertThat((int) ReflectionUtil.invokeMethod(bean, "listType", ListUtil.newArrayList("1", "2"))).isEqualTo(2); // 函数名错 try { ReflectionUtil.invokeMethod(bean, "notExistMethod", new Object[] { "calvin" }, new Class[] { String.class }); failBecauseExceptionWasNotThrown(IllegalArgumentException.class); } catch (IllegalArgumentException e) { } // 参数类型错 try { ReflectionUtil.invokeMethod(bean, "privateMethod", new Object[] { "calvin" }, new Class[] { Integer.class }); failBecauseExceptionWasNotThrown(RuntimeException.class); } catch (RuntimeException e) { } // 函数名错 try { ReflectionUtil.invokeMethodByName(bean, "notExistMethod", new Object[] { "calvin" }); failBecauseExceptionWasNotThrown(IllegalArgumentException.class); } catch (IllegalArgumentException e) { } } @Test public void invokeConstructor() { TestBean bean = ReflectionUtil.invokeConstructor(TestBean.class); assertThat(bean.getPublicField()).isEqualTo(2); TestBean3 bean3 = ReflectionUtil.invokeConstructor(TestBean3.class, 4); assertThat(bean3.getId()).isEqualTo(4); } @Test public void convertReflectionExceptionToUnchecked() { IllegalArgumentException iae = new IllegalArgumentException(); // ReflectionException,normal RuntimeException e = ReflectionUtil.convertReflectionExceptionToUnchecked(iae); assertThat(e).isEqualTo(iae); // InvocationTargetException,extract it's target exception. Exception ex = new Exception(); e = ReflectionUtil.convertReflectionExceptionToUnchecked(new InvocationTargetException(ex)); assertThat(e.getCause()).isEqualTo(ex); // UncheckedException, ignore it. RuntimeException re = new RuntimeException("abc"); e = ReflectionUtil.convertReflectionExceptionToUnchecked(re); assertThat(e).hasMessage("abc"); // Unexcepted Checked exception. e = ReflectionUtil.convertReflectionExceptionToUnchecked(ex); assertThat(e).isInstanceOf(UncheckedException.class); } public static class ParentBean { } public static class TestBean extends ParentBean { /** 没有getter/setter的field */ private int privateField = 1; /** 有getter/setter的field */ private int publicField = 1; // 通過getter函數會比屬性值+1 public int getPublicField() { return publicField + 1; } // 通過setter函數會被比輸入值加1 public void setPublicField(int publicField) { this.publicField = publicField + 1; } public int inspectPrivateField() { return privateField; } public int inspectPublicField() { return publicField; } private String privateMethod(String text) { return "hello " + text; } // 测试原子类型转换 public Integer integerType(Integer i) { return i; } // 测试原子类型转换 public int intType(int i) { return i; } // 测试类型为接口 public int listType(List list) { return list.size(); } } public static class TestBean2 extends ParentBean { } public static class TestBean3 { public TestBean3() { } public TestBean3(int id) { super(); this.id = id; } private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/security/CryptoUtilTest.java ================================================ package com.vip.vjtools.vjkit.security; import static org.assertj.core.api.Assertions.*; import org.junit.Test; import com.vip.vjtools.vjkit.text.EncodeUtil; public class CryptoUtilTest { @Test public void mac() { String input = "foo message"; // key可为任意字符串 // byte[] key = "a foo key".getBytes(); byte[] key = CryptoUtil.generateHmacSha1Key(); assertThat(key).hasSize(20); byte[] macResult = CryptoUtil.hmacSha1(input.getBytes(), key); System.out.println("hmac-sha1 key in hex :" + EncodeUtil.encodeHex(key)); System.out.println("hmac-sha1 in hex result :" + EncodeUtil.encodeHex(macResult)); assertThat(CryptoUtil.isMacValid(macResult, input.getBytes(), key)).isTrue(); } @Test public void aes() { byte[] key = CryptoUtil.generateAesKey(); assertThat(key).hasSize(16); String input = "foo message"; byte[] encryptResult = CryptoUtil.aesEncrypt(input.getBytes(), key); String descryptResult = CryptoUtil.aesDecrypt(encryptResult, key); System.out.println("aes key in hex :" + EncodeUtil.encodeHex(key)); System.out.println("aes encrypt in hex result :" + EncodeUtil.encodeHex(encryptResult)); assertThat(descryptResult).isEqualTo(input); } @Test public void aesWithIV() { byte[] key = CryptoUtil.generateAesKey(); byte[] iv = CryptoUtil.generateIV(); assertThat(key).hasSize(16); assertThat(iv).hasSize(16); String input = "foo message"; byte[] encryptResult = CryptoUtil.aesEncrypt(input.getBytes(), key, iv); String descryptResult = CryptoUtil.aesDecrypt(encryptResult, key, iv); System.out.println("aes key in hex :" + EncodeUtil.encodeHex(key)); System.out.println("iv in hex :" + EncodeUtil.encodeHex(iv)); System.out.println("aes encrypt in hex result :" + EncodeUtil.encodeHex(encryptResult)); assertThat(descryptResult).isEqualTo(input); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/text/CsvUtilTest.java ================================================ package com.vip.vjtools.vjkit.text; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class CsvUtilTest { @Test public void toCsvString() { assertThat(CsvUtil.toCsvString(1, 2)).isEqualTo("1,2"); assertThat(CsvUtil.toCsvString(1, 2, 3, 4)).isEqualTo("1,2,3,4"); // "2" still plain as 2 assertThat(CsvUtil.toCsvString(1, "2")).isEqualTo("1,2"); // "A BC" still plain as A BC assertThat(CsvUtil.toCsvString(1, "A BC")).isEqualTo("1,A BC"); // "A,BC" has ',' as "A,BC" assertThat(CsvUtil.toCsvString(1, "A,BC")).isEqualTo("1,\"A,BC\""); // "A"BC" has '"' as "A""BC" assertThat(CsvUtil.toCsvString(1, "A\"BC")).isEqualTo("1,\"A\"\"BC\""); // "A,B"a"C" has 2 '""' as "A,""a""BC" assertThat(CsvUtil.toCsvString(1, "A,\"a\"BC")).isEqualTo("1,\"A,\"\"a\"\"BC\""); } @Test public void fromCsvString() { assertThat(CsvUtil.fromCsvString("1,2")).hasSize(2).contains("1").contains("2"); assertThat(CsvUtil.fromCsvString("1,A BC")).hasSize(2).contains("1").contains("A BC"); assertThat(CsvUtil.fromCsvString("1,\"A,BC\"")).hasSize(2).contains("1").contains("A,BC"); assertThat(CsvUtil.fromCsvString("1,\"A,\"\"a\"\"BC\"")).hasSize(2).contains("1").contains("A,\"a\"BC"); // wrong format still work assertThat(CsvUtil.fromCsvString("1,\"A,\"a\"\"BC\"")).hasSize(2).contains("1").contains("A,\"a\"BC"); assertThat(CsvUtil.fromCsvString("1,ABC\"")).hasSize(2).contains("1").contains("ABC\""); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/text/EncodeUtilTest.java ================================================ package com.vip.vjtools.vjkit.text; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class EncodeUtilTest { @Test public void hexEncode() { String input = "haha,i am a very long message"; String result = EncodeUtil.encodeHex(input.getBytes()); assertThat(new String(EncodeUtil.decodeHex(result), Charsets.UTF_8)).isEqualTo(input); byte[] bytes = new byte[] { 1, 2, 15, 17 }; result = EncodeUtil.encodeHex(bytes); assertThat(result).isEqualTo("01020F11"); input = "01020F11"; assertThat(EncodeUtil.decodeHex(input)).hasSize(4).containsSequence((byte) 1, (byte) 2, (byte) 15, (byte) 17); try { input = "01020G11"; EncodeUtil.decodeHex(input); fail("should throw exception before"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } } @Test public void base64Encode() { String input = "haha,i am a very long message"; String result = EncodeUtil.encodeBase64(input.getBytes()); assertThat(new String(EncodeUtil.decodeBase64(result), Charsets.UTF_8)).isEqualTo(input); byte[] bytes = new byte[] { 5 }; result = EncodeUtil.encodeBase64(bytes); assertThat(result).isEqualTo("BQ=="); bytes = new byte[] { 1, 2, 15, 17, 127 }; result = EncodeUtil.encodeBase64(bytes); assertThat(result).isEqualTo("AQIPEX8="); } @Test public void base64UrlSafeEncode() { String input = "haha,i am a very long message"; String result = EncodeUtil.encodeBase64UrlSafe(input.getBytes()); assertThat(new String(EncodeUtil.decodeBase64UrlSafe(result), Charsets.UTF_8)).isEqualTo(input); try { assertThat(result).isEqualTo(EncodeUtil.decodeBase64UrlSafe("AQIPE+8=")); fail("should throw exception before"); } catch (Throwable t) { assertThat(t).isInstanceOf(IllegalArgumentException.class); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/text/EscapeUtilTest.java ================================================ package com.vip.vjtools.vjkit.text; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class EscapeUtilTest { @Test public void urlEncode() { String input = "http://locahost/"; String result = EscapeUtil.urlEncode(input); assertThat(result).isEqualTo("http%3A%2F%2Flocahost%2F"); assertThat(EscapeUtil.urlDecode(result)).isEqualTo(input); input = "http://locahost/?query=中文&t=1"; result = EscapeUtil.urlEncode(input); System.out.println(result); assertThat(EscapeUtil.urlDecode(result)).isEqualTo(input); } @Test public void xmlEncode() { String input = "1>2"; String result = EscapeUtil.escapeXml(input); assertThat(result).isEqualTo("1>2"); assertThat(EscapeUtil.unescapeXml(result)).isEqualTo(input); } @Test public void html() { String input = "1>2"; String result = EscapeUtil.escapeHtml(input); assertThat(result).isEqualTo("1>2"); assertThat(EscapeUtil.unescapeHtml(result)).isEqualTo(input); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/text/HashUtilTest.java ================================================ package com.vip.vjtools.vjkit.text; import static org.assertj.core.api.Assertions.*; import java.io.IOException; import java.io.InputStream; import org.junit.Test; import com.vip.vjtools.vjkit.io.ResourceUtil; public class HashUtilTest { @Test public void hashSha1() { // 普通 String result = EncodeUtil.encodeBase64(HashUtil.sha1("hhahah")); System.out.println("sha1:" + result); assertThat(result).isEqualTo("sCtJLx2IJNto032AhdkP64t/os4="); String result2 = EncodeUtil.encodeBase64(HashUtil.sha1("hhahah".getBytes())); assertThat(result).isEqualTo("sCtJLx2IJNto032AhdkP64t/os4="); // 带盐, 每次salt值不一样,所以值也不一样。 result = EncodeUtil.encodeBase64(HashUtil.sha1("hhahah", HashUtil.generateSalt(5))); System.out.println("sha1 with salt:" + result); // 带盐,固定的盐 result = EncodeUtil.encodeBase64(HashUtil.sha1("hhahah", new byte[] { 1, 2, 3 })); assertThat(result).isEqualTo("U/7wy5R1sVrjEf3dOTAPz383g2k="); result2 = EncodeUtil.encodeBase64(HashUtil.sha1("hhahah".getBytes(), new byte[] { 1, 2, 3 })); assertThat(result).isEqualTo(result2); // 带盐迭代, 每次salt值不一样,所以值也不一样。 result = EncodeUtil.encodeBase64(HashUtil.sha1("hhahah", HashUtil.generateSalt(5), 2)); System.out.println("sha1 with salt with iteration:" + result); // 带盐迭代, 固定的盐 result = EncodeUtil.encodeBase64(HashUtil.sha1("hhahah", new byte[] { 1, 2, 3 }, 2)); assertThat(result).isEqualTo("n9O7laits+ovoK8X8xde+XrsCtM="); result2 = EncodeUtil.encodeBase64(HashUtil.sha1("hhahah".getBytes(), new byte[] { 1, 2, 3 }, 2)); assertThat(result).isEqualTo(result2); } @Test public void hashFile() throws IOException { InputStream in = ResourceUtil.asStream("test.txt"); String result = EncodeUtil.encodeBase64(HashUtil.sha1File(in)); assertThat(result).isEqualTo("DmSnwK/Fl0Jplrwtm9tfi7cb/js="); result = EncodeUtil.encodeBase64(HashUtil.md5File(in)); assertThat(result).isEqualTo("1B2M2Y8AsgTpgAmY7PhCfg=="); } @Test public void crc32() { assertThat(HashUtil.crc32AsInt("hahhha1")).isEqualTo(-625925593); assertThat(HashUtil.crc32AsInt("hahhha1".getBytes())).isEqualTo(-625925593); assertThat(HashUtil.crc32AsInt("hahhha2")).isEqualTo(1136161693); assertThat(HashUtil.crc32AsLong("hahhha1")).isEqualTo(3669041703L); assertThat(HashUtil.crc32AsLong("hahhha1".getBytes())).isEqualTo(3669041703L); assertThat(HashUtil.crc32AsLong("hahhha2")).isEqualTo(1136161693L); } @Test public void murmurhash() { assertThat(HashUtil.murmur32AsInt("hahhha1")).isEqualTo(-1920794701); assertThat(HashUtil.murmur32AsInt("hahhha1".getBytes())).isEqualTo(-1920794701); assertThat(HashUtil.murmur32AsInt("hahhha2")).isEqualTo(2065789419); assertThat(HashUtil.murmur32AsInt("hahhha3")).isEqualTo(-293065542); assertThat(HashUtil.murmur32AsInt("hahhha4")).isEqualTo(-2003559207); assertThat(HashUtil.murmur32AsInt("hahhha5")).isEqualTo(-3887993); assertThat(HashUtil.murmur32AsInt("hahhha6")).isEqualTo(-446760132); assertThat(HashUtil.murmur128AsLong("hahhha6")).isEqualTo(-5203515929515563680L); assertThat(HashUtil.murmur128AsLong("hahhha6".getBytes(Charsets.UTF_8))).isEqualTo(-5203515929515563680L); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/text/MoreStringUtilTest.java ================================================ package com.vip.vjtools.vjkit.text; import static org.assertj.core.api.Assertions.*; import java.util.List; import org.junit.Test; import com.google.common.base.Splitter; public class MoreStringUtilTest { @Test public void split() { List result = MoreStringUtil.split("192.168.0.1", '.', 4); assertThat(result).hasSize(4).containsSequence("192", "168", "0", "1"); result = MoreStringUtil.split("192.168..1", '.', 4); assertThat(result).hasSize(3).containsSequence("192", "168", "1"); result = MoreStringUtil.split("192.168.0.", '.', 4); assertThat(result).hasSize(3).containsSequence("192", "168", "0"); assertThat(MoreStringUtil.split(null, '.', 4)).isNull(); assertThat(MoreStringUtil.split("", '.', 4)).hasSize(0); Splitter splitter = MoreStringUtil.charsSplitter("/\\").omitEmptyStrings(); result = splitter.splitToList("/a/b/c"); assertThat(result).hasSize(3).containsSequence("a", "b", "c"); result = splitter.splitToList("\\a\\b\\c"); assertThat(result).hasSize(3).containsSequence("a", "b", "c"); } @Test public void charMatch() { String str = "abc"; assertThat(MoreStringUtil.startWith(str, 'a')).isTrue(); assertThat(MoreStringUtil.startWith(str, 'b')).isFalse(); assertThat(MoreStringUtil.startWith(null, 'b')).isFalse(); assertThat(MoreStringUtil.startWith("", 'b')).isFalse(); assertThat(MoreStringUtil.endWith(str, 'c')).isTrue(); assertThat(MoreStringUtil.endWith(str, 'b')).isFalse(); assertThat(MoreStringUtil.endWith(null, 'b')).isFalse(); assertThat(MoreStringUtil.endWith("", 'b')).isFalse(); assertThat(MoreStringUtil.replaceFirst("abbc", 'b', 'c')).isEqualTo("acbc"); assertThat(MoreStringUtil.replaceFirst("abcc", 'c', 'c')).isEqualTo("abcc"); assertThat(MoreStringUtil.replaceFirst("", 'c', 'c')).isEqualTo(""); assertThat(MoreStringUtil.replaceFirst(null, 'c', 'c')).isNull(); assertThat(MoreStringUtil.replaceLast("abbc", 'b', 'c')).isEqualTo("abcc"); assertThat(MoreStringUtil.replaceLast("abcc", 'c', 'c')).isEqualTo("abcc"); assertThat(MoreStringUtil.replaceLast("", 'c', 'c')).isEqualTo(""); assertThat(MoreStringUtil.replaceLast(null, 'c', 'c')).isNull(); } @Test public void utf8EncodedLength() { assertThat(MoreStringUtil.utf8EncodedLength("ab12")).isEqualTo(4); assertThat(MoreStringUtil.utf8EncodedLength("中文")).isEqualTo(6); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/text/StringBuilderHolderTest.java ================================================ package com.vip.vjtools.vjkit.text; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import org.junit.Test; public class StringBuilderHolderTest { @Test public void test() throws InterruptedException { final CountDownLatch countdown = new CountDownLatch(10); final CyclicBarrier barrier = new CyclicBarrier(10); Runnable runnable = new Runnable() { StringBuilderHolder holder = new StringBuilderHolder(512); @Override public void run() { try { barrier.await(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } StringBuilder builder = StringBuilderHolder.getGlobal(); builder.append(Thread.currentThread().getName() + "-1"); System.out.println(builder.toString()); builder = StringBuilderHolder.getGlobal(); builder.append(Thread.currentThread().getName() + "-2"); System.out.println(builder.toString()); StringBuilder builder2 = holder.get(); builder2.append(Thread.currentThread().getName() + "-11"); System.out.println(builder2.toString()); builder2 = holder.get(); builder2.append(Thread.currentThread().getName() + "-22"); System.out.println(builder2.toString()); countdown.countDown(); } }; for (int i = 0; i < 10; i++) { Thread thread = new Thread(runnable); thread.start(); } countdown.await(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/text/TextValidatorTest.java ================================================ package com.vip.vjtools.vjkit.text; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class TextValidatorTest { @Test public void isMobileSimple() { assertThat(TextValidator.isMobileSimple(null)).isFalse(); assertThat(TextValidator.isMobileSimple("")).isFalse(); assertThat(TextValidator.isMobileSimple("1234a")).isFalse(); assertThat(TextValidator.isMobileSimple("1234561")).isFalse(); assertThat(TextValidator.isMobileSimple("11170998762")).isTrue(); } @Test public void isMobileExact() { assertThat(TextValidator.isMobileExact("1234a")).isFalse(); assertThat(TextValidator.isMobileExact("11170998762")).isFalse(); assertThat(TextValidator.isMobileExact("13970998762")).isTrue(); } @Test public void isTel() { // 含字母 assertThat(TextValidator.isTel("8802973a")).isFalse(); // 太长 assertThat(TextValidator.isTel("8908222222")).isFalse(); // 太短 assertThat(TextValidator.isTel("89081")).isFalse(); assertThat(TextValidator.isTel("89019739")).isTrue(); assertThat(TextValidator.isTel("020-89019739")).isTrue(); } @Test public void isIdCard() { // 含字母 assertThat(TextValidator.isIdCard("440101198987754ab")).isFalse(); // 月份不对 assertThat(TextValidator.isIdCard("440101198987754122")).isFalse(); // 日期不对 assertThat(TextValidator.isIdCard("440101891232451")).isFalse(); // 18位正确 assertThat(TextValidator.isIdCard("440101198909204518")).isTrue(); // 15位正确 assertThat(TextValidator.isIdCard("440101891231451")).isTrue(); } @Test public void isEmail() { assertThat(TextValidator.isEmail("abc")).isFalse(); assertThat(TextValidator.isEmail("abc@a")).isFalse(); assertThat(TextValidator.isEmail("中文@a.com")).isFalse(); assertThat(TextValidator.isEmail("abc@abc.com")).isTrue(); } @Test public void isUrl() { assertThat(TextValidator.isUrl("abc.com")).isFalse(); assertThat(TextValidator.isUrl("http://abc.c om")).isFalse(); assertThat(TextValidator.isUrl("http2://abc.com")).isFalse(); assertThat(TextValidator.isUrl("http://abc.com")).isTrue(); } @Test public void isDate() { assertThat(TextValidator.isDate("2011-02-29")).isFalse(); assertThat(TextValidator.isDate("201a-02-30")).isFalse(); assertThat(TextValidator.isDate("2011-0211")).isFalse(); assertThat(TextValidator.isDate("2011-03-11")).isTrue(); assertThat(TextValidator.isDate("2012-02-29")).isTrue(); } @Test public void isIp() { assertThat(TextValidator.isIp("192.168.0.300")).isFalse(); assertThat(TextValidator.isIp("192.168.300.1")).isFalse(); assertThat(TextValidator.isIp("192.168.300")).isFalse(); assertThat(TextValidator.isIp("192.168.A3.1")).isFalse(); assertThat(TextValidator.isIp("192.168.0.1")).isTrue(); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/text/WildcardMatcherTest.java ================================================ package com.vip.vjtools.vjkit.text; import static org.assertj.core.api.Assertions.*; import org.junit.Test; public class WildcardMatcherTest { @Test public void matchString() { assertThat(WildcardMatcher.match("abc", "*")).isTrue(); assertThat(WildcardMatcher.match("abc", "*c")).isTrue(); assertThat(WildcardMatcher.match("abc", "a*")).isTrue(); assertThat(WildcardMatcher.match("abc", "a*c")).isTrue(); assertThat(WildcardMatcher.match("abc", "a?c")).isTrue(); assertThat(WildcardMatcher.match("abcd", "a?c?")).isTrue(); assertThat(WildcardMatcher.match("abcd", "a??d")).isTrue(); assertThat(WildcardMatcher.match("abcde", "a*d?")).isTrue(); assertThat(WildcardMatcher.match("abcde", "a*d")).isFalse(); assertThat(WildcardMatcher.match("abcde", "a*x")).isFalse(); assertThat(WildcardMatcher.match("abcde", "a*df")).isFalse(); assertThat(WildcardMatcher.match("abcde", "?abcd")).isFalse(); assertThat(WildcardMatcher.match("ab\\\\*cde", "ab\\\\*c*")).isTrue(); assertThat(WildcardMatcher.match("ab\\\\*cde", "ab\\\\*?de")).isTrue(); // matchOne assertThat(WildcardMatcher.matchOne("abcde", new String[] { "a*d?", "abde?" })).isEqualTo(0); assertThat(WildcardMatcher.matchOne("abcde", new String[] { "?abcd", "a*d?" })).isEqualTo(1); assertThat(WildcardMatcher.matchOne("abcde", new String[] { "?abcd", "xyz*" })).isEqualTo(-1); } @Test public void matchPath() { assertThat(WildcardMatcher.matchPath("/a/b/dd", "**")).isTrue(); assertThat(WildcardMatcher.matchPath("/a/b/dd", "**/dd")).isTrue(); assertThat(WildcardMatcher.matchPath("/a/b/c/dd", "/a/**/dd")).isTrue(); assertThat(WildcardMatcher.matchPath("/a/b/dd", "/a/*/dd")).isTrue(); assertThat(WildcardMatcher.matchPath("/a/b/dd", "/a/*/d?")).isTrue(); assertThat(WildcardMatcher.matchPath("/a/b/ddxxa", "/a/*/dd*")).isTrue(); assertThat(WildcardMatcher.matchPath("/a/b/ddxxa", "/a/?/dd*")).isTrue(); assertThat(WildcardMatcher.matchPath("a/b/ddxxa", "a/?/dd*")).isTrue(); assertThat(WildcardMatcher.matchPath("a/b/dd", "**/dd")).isTrue(); assertThat(WildcardMatcher.matchPath("/a/b/c/dd", "/a/*/dd")).isFalse(); assertThat(WildcardMatcher.matchPath("\\a\\b\\c\\dd", "/a/*/dd")).isFalse(); assertThat(WildcardMatcher.matchPath("/a/b/c/dd", "/a/*/dd")).isFalse(); // matchOne assertThat(WildcardMatcher.matchPathOne("/a/b/c/dd", new String[] { "/a/*/dd", "**/dd" })).isEqualTo(1); assertThat(WildcardMatcher.matchPathOne("/a/b/c/dd", new String[] { "/a/**/dd", "**/dd" })).isEqualTo(0); assertThat(WildcardMatcher.matchPathOne("/a/b/c/dd", new String[] { "/b/d", "/a/c/*" })).isEqualTo(-1); assertThat(WildcardMatcher.matchPathOne("\\a\\b\\c\\dd", new String[] { "/a/*/dd", "**\\dd" })).isEqualTo(1); assertThat(WildcardMatcher.matchPathOne("\\a\\b\\c\\dd", new String[] { "\\a\\**\\dd", "**\\dd" })) .isEqualTo(0); assertThat(WildcardMatcher.matchPathOne("\\a\\b\\c\\dd", new String[] { "/b/d", "/a/c/*" })).isEqualTo(-1); assertThat(WildcardMatcher.matchPathOne("/a/b/c/dd", new String[] { "/a/*/dd", "**/dd" })).isEqualTo(1); assertThat(WildcardMatcher.matchPathOne("/a/b/c/dd", new String[] { "/a/**/dd", "**/dd" })).isEqualTo(0); assertThat(WildcardMatcher.matchPathOne("/a/b/c/dd", new String[] { "/b/d", "/a/c/*" })).isEqualTo(-1); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/time/CachingDatFormatterTest.java ================================================ package com.vip.vjtools.vjkit.time; import static org.assertj.core.api.Assertions.*; import java.util.Date; import org.junit.Test; public class CachingDatFormatterTest { @Test public void test() { Date date = new Date(116, 10, 1, 12, 23, 44); CachingDateFormatter formatter = new CachingDateFormatter(DateFormatUtil.PATTERN_DEFAULT); assertThat(formatter.format(date.getTime())).isEqualTo("2016-11-01 12:23:44.000"); assertThat(formatter.format(date.getTime())).isEqualTo("2016-11-01 12:23:44.000"); assertThat(formatter.format(date.getTime() + 2)).isEqualTo("2016-11-01 12:23:44.002"); CachingDateFormatter formatterOnSecond = new CachingDateFormatter(DateFormatUtil.PATTERN_DEFAULT_ON_SECOND); assertThat(formatterOnSecond.format(date.getTime())).isEqualTo("2016-11-01 12:23:44"); assertThat(formatterOnSecond.format(date.getTime())).isEqualTo("2016-11-01 12:23:44"); assertThat(formatterOnSecond.format(date.getTime() + 2)).isEqualTo("2016-11-01 12:23:44"); assertThat(formatterOnSecond.format(date.getTime() + 1000)).isEqualTo("2016-11-01 12:23:45"); } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/time/ClockUtilTest.java ================================================ package com.vip.vjtools.vjkit.time; import static org.assertj.core.api.Assertions.*; import java.util.Date; import org.junit.Test; import com.vip.vjtools.vjkit.time.ClockUtil.DummyClock; public class ClockUtilTest { @Test public void testDummyClock() { DummyClock clock = new DummyClock(); clock.updateNow(111); assertThat(clock.currentTimeMillis()).isEqualTo(111); assertThat(clock.currentDate().getTime()).isEqualTo(111); clock.updateNow(new Date(112)); assertThat(clock.currentTimeMillis()).isEqualTo(112); clock.increaseTime(200); assertThat(clock.currentTimeMillis()).isEqualTo(312); clock.decreaseTime(100); assertThat(clock.currentTimeMillis()).isEqualTo(212); clock.setNanoTime(150); assertThat(clock.nanoTime()).isEqualTo(150); } @Test public void elapsedTime() { try { DummyClock clock = ClockUtil.useDummyClock(2000); clock.increaseTime(1000); assertThat(ClockUtil.elapsedTime(2000)).isEqualTo(1000); } finally { ClockUtil.useDefaultClock(); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/time/DateFormatUtilTest.java ================================================ package com.vip.vjtools.vjkit.time; import static org.assertj.core.api.Assertions.assertThat; import java.text.ParseException; import java.util.Date; import org.junit.Test; public class DateFormatUtilTest { @Test public void isoDateFormat() { Date date = new Date(116, 10, 1, 12, 23, 44); assertThat(DateFormatUtil.ISO_FORMAT.format(date)).contains("2016-11-01T12:23:44.000"); assertThat(DateFormatUtil.ISO_ON_SECOND_FORMAT.format(date)).contains("2016-11-01T12:23:44"); assertThat(DateFormatUtil.ISO_ON_DATE_FORMAT.format(date)).isEqualTo("2016-11-01"); } @Test public void defaultDateFormat() { Date date = new Date(116, 10, 1, 12, 23, 44); assertThat(DateFormatUtil.DEFAULT_FORMAT.format(date)).isEqualTo("2016-11-01 12:23:44.000"); assertThat(DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.format(date)).isEqualTo("2016-11-01 12:23:44"); } @Test public void formatWithPattern() { Date date = new Date(116, 10, 1, 12, 23, 44); assertThat(DateFormatUtil.formatDate(DateFormatUtil.PATTERN_DEFAULT, date)) .isEqualTo("2016-11-01 12:23:44.000"); assertThat(DateFormatUtil.formatDate(DateFormatUtil.PATTERN_DEFAULT, date.getTime())) .isEqualTo("2016-11-01 12:23:44.000"); } @Test public void parseWithPattern() throws ParseException { Date date = new Date(116, 10, 1, 12, 23, 44); Date resultDate = DateFormatUtil.parseDate(DateFormatUtil.PATTERN_DEFAULT, "2016-11-01 12:23:44.000"); assertThat(resultDate.getTime() == date.getTime()).isTrue(); } @Test public void formatDuration() { assertThat(DateFormatUtil.formatDuration(100)).isEqualTo("00:00:00.100"); assertThat(DateFormatUtil.formatDuration(new Date(100), new Date(3000))).isEqualTo("00:00:02.900"); assertThat(DateFormatUtil.formatDuration(DateUtil.MILLIS_PER_DAY * 2 + DateUtil.MILLIS_PER_HOUR * 4)) .isEqualTo("52:00:00.000"); assertThat(DateFormatUtil.formatDurationOnSecond(new Date(100), new Date(3000))).isEqualTo("00:00:02"); assertThat(DateFormatUtil.formatDurationOnSecond(2000)).isEqualTo("00:00:02"); assertThat(DateFormatUtil.formatDurationOnSecond(DateUtil.MILLIS_PER_DAY * 2 + DateUtil.MILLIS_PER_HOUR * 4)) .isEqualTo("52:00:00"); } @Test public void formatFriendlyTimeSpanByNow() throws ParseException { try { Date now = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse("2016-12-11 23:30:00"); ClockUtil.useDummyClock(now); Date lessOneSecond = DateFormatUtil.DEFAULT_FORMAT.parse("2016-12-11 23:29:59.500"); assertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(lessOneSecond)).isEqualTo("刚刚"); Date lessOneMinute = DateFormatUtil.DEFAULT_FORMAT.parse("2016-12-11 23:29:55.000"); assertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(lessOneMinute)).isEqualTo("5秒前"); Date lessOneHour = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse("2016-12-11 23:00:00"); assertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(lessOneHour)).isEqualTo("30分钟前"); Date today = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse("2016-12-11 1:00:00"); assertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(today)).isEqualTo("今天01:00"); Date yesterday = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse("2016-12-10 1:00:00"); assertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(yesterday)).isEqualTo("昨天01:00"); Date threeDayBefore = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse("2016-12-09 1:00:00"); assertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(threeDayBefore)).isEqualTo("2016-12-09"); } finally { ClockUtil.useDefaultClock(); } } } ================================================ FILE: vjkit/src/test/java/com/vip/vjtools/vjkit/time/DateUtilTest.java ================================================ package com.vip.vjtools.vjkit.time; import static org.assertj.core.api.Assertions.*; import java.util.Date; import org.junit.Test; public class DateUtilTest { @Test public void isSameDay() { Date date1 = new Date(106, 10, 1); Date date2 = new Date(106, 10, 1, 12, 23, 44); assertThat(DateUtil.isSameDay(date1, date2)).isTrue(); Date date3 = new Date(106, 10, 1); assertThat(DateUtil.isSameTime(date1, date3)).isTrue(); Date date5 = new Date(106, 10, 2); assertThat(DateUtil.isSameTime(date1, date5)).isFalse(); Date date4 = new Date(106, 10, 1, 12, 23, 43); assertThat(DateUtil.isBetween(date3, date1, date2)).isTrue(); assertThat(DateUtil.isBetween(date4, date1, date2)).isTrue(); try { DateUtil.isBetween(null, date1, date2); fail("should fail before"); } catch (Exception e) { } try { DateUtil.isBetween(date3, date2, date1); fail("should fail before"); } catch (Exception e) { } assertThat(DateUtil.isBetween(date5, date1, date2)).isFalse(); } @Test public void truncateAndCelling() { // Sat Jan 21 12:12:12 CST 2017 Date date = new Date(117, 0, 21, 12, 12, 12); Date beginYear = new Date(117, 0, 1, 0, 0, 0); Date endYear = new Date(new Date(117, 11, 31, 23, 59, 59).getTime() + 999); Date nextYear = new Date(118, 0, 1, 0, 0, 0); Date beginMonth = new Date(117, 0, 1); Date endMonth = new Date(new Date(117, 0, 31, 23, 59, 59).getTime() + 999); Date nextMonth = new Date(117, 1, 1); Date beginWeek = new Date(117, 0, 16); Date endWeek = new Date(new Date(117, 0, 22, 23, 59, 59).getTime() + 999); Date nextWeek = new Date(117, 0, 23); Date beginDate = new Date(117, 0, 21); Date endDate = new Date(new Date(117, 0, 21, 23, 59, 59).getTime() + 999); Date nextDate = new Date(117, 0, 22); Date beginHour = new Date(117, 0, 21, 12, 0, 0); Date endHour = new Date(new Date(117, 0, 21, 12, 59, 59).getTime() + 999); Date nextHour = new Date(117, 0, 21, 13, 0, 0); Date beginMinute = new Date(117, 0, 21, 12, 12, 0); Date endMinute = new Date(new Date(117, 0, 21, 12, 12, 59).getTime() + 999); Date nextMinute = new Date(117, 0, 21, 12, 13, 0); assertThat(DateUtil.isSameTime(DateUtil.beginOfYear(date), beginYear)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.endOfYear(date), endYear)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.nextYear(date), nextYear)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.beginOfMonth(date), beginMonth)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.endOfMonth(date), endMonth)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.nextMonth(date), nextMonth)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.beginOfWeek(date), beginWeek)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.endOfWeek(date), endWeek)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.nextWeek(date), nextWeek)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.beginOfDate(date), beginDate)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.endOfDate(date), endDate)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.nextDate(date), nextDate)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.beginOfHour(date), beginHour)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.endOfHour(date), endHour)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.nextHour(date), nextHour)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.beginOfMinute(date), beginMinute)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.endOfMinute(date), endMinute)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.nextMinute(date), nextMinute)).isTrue(); } @Test public void changeDay() { Date date = new Date(106, 10, 1, 12, 23, 44); Date expectDate1 = new Date(106, 10, 3); Date expectDate2 = new Date(106, 9, 31); Date expectDate3 = new Date(106, 11, 1); Date expectDate4 = new Date(106, 7, 1); Date expectDate5 = new Date(106, 10, 1, 13, 23, 44); Date expectDate6 = new Date(106, 10, 1, 10, 23, 44); Date expectDate7 = new Date(106, 10, 1, 12, 24, 44); Date expectDate8 = new Date(106, 10, 1, 12, 21, 44); Date expectDate9 = new Date(106, 10, 1, 12, 23, 45); Date expectDate10 = new Date(106, 10, 1, 12, 23, 42); Date expectDate11 = new Date(106, 10, 8); Date expectDate12 = new Date(106, 9, 25); assertThat(DateUtil.isSameDay(DateUtil.addDays(date, 2), expectDate1)).isTrue(); assertThat(DateUtil.isSameDay(DateUtil.subDays(date, 1), expectDate2)).isTrue(); assertThat(DateUtil.isSameDay(DateUtil.addWeeks(date, 1), expectDate11)).isTrue(); assertThat(DateUtil.isSameDay(DateUtil.subWeeks(date, 1), expectDate12)).isTrue(); assertThat(DateUtil.isSameDay(DateUtil.addMonths(date, 1), expectDate3)).isTrue(); assertThat(DateUtil.isSameDay(DateUtil.subMonths(date, 3), expectDate4)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.addHours(date, 1), expectDate5)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.subHours(date, 2), expectDate6)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.addMinutes(date, 1), expectDate7)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.subMinutes(date, 2), expectDate8)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.addSeconds(date, 1), expectDate9)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.subSeconds(date, 2), expectDate10)).isTrue(); } @Test public void setDay() { Date date = new Date(116, 10, 1, 10, 10, 1); Date expectedDate = new Date(116, 10, 3); Date expectedDate2 = new Date(116, 10, 1); Date expectedDate3 = new Date(117, 10, 1); Date expectedDate4 = new Date(116, 10, 1, 9, 10, 1); Date expectedDate5 = new Date(116, 10, 1, 10, 9, 1); Date expectedDate6 = new Date(116, 10, 1, 10, 10, 10); assertThat(DateUtil.isSameDay(DateUtil.setDays(date, 3), expectedDate)).isTrue(); assertThat(DateUtil.isSameDay(DateUtil.setMonths(date, 11), expectedDate2)).isTrue(); assertThat(DateUtil.isSameDay(DateUtil.setYears(date, 2017), expectedDate3)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.setHours(date, 9), expectedDate4)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.setMinutes(date, 9), expectedDate5)).isTrue(); assertThat(DateUtil.isSameTime(DateUtil.setSeconds(date, 10), expectedDate6)).isTrue(); } @Test public void getDayOfWeek() { // 2017-01-09 Date date = new Date(117, 0, 9); assertThat(DateUtil.getDayOfWeek(date)).isEqualTo(1); Date date2 = new Date(117, 0, 15); assertThat(DateUtil.getDayOfWeek(date2)).isEqualTo(7); } @Test public void isLeapYear() { // 2008-01-09,整除4年, true Date date = new Date(108, 0, 9); assertThat(DateUtil.isLeapYear(date)).isTrue(); // 2000-01-09,整除400年,true date = new Date(100, 0, 9); assertThat(DateUtil.isLeapYear(date)).isTrue(); // 1900-01-09,整除100年,false date = new Date(0, 0, 9); assertThat(DateUtil.isLeapYear(date)).isFalse(); } @Test public void getXXofXX() { // 2008-02-09, 整除4年, 闰年 Date date = new Date(108, 2, 9); assertThat(DateUtil.getMonthLength(date)).isEqualTo(29); // 2009-02-09, 整除4年, 非闰年 Date date2 = new Date(109, 2, 9); assertThat(DateUtil.getMonthLength(date2)).isEqualTo(28); Date date3 = new Date(108, 8, 9); assertThat(DateUtil.getMonthLength(date3)).isEqualTo(31); Date date4 = new Date(109, 11, 30); assertThat(DateUtil.getDayOfYear(date4)).isEqualTo(364); Date date5 = new Date(117, 0, 12); assertThat(DateUtil.getWeekOfMonth(date5)).isEqualTo(3); assertThat(DateUtil.getWeekOfYear(date5)).isEqualTo(3); } } ================================================ FILE: vjkit/src/test/resources/application.properties ================================================ springside.min=1 springside.max=10 ================================================ FILE: vjkit/src/test/resources/data_mask.properties ================================================ Name=nickName ================================================ FILE: vjkit/src/test/resources/logback-test.xml ================================================ %date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: vjkit/src/test/resources/test.txt ================================================ ABCDEFG ABC ================================================ FILE: vjmap/README.md ================================================ # 1. 概述 分代版的jmap(新生代,存活区,老生代),是排查内存缓慢泄露,老生代增长过快原因的利器。因为`jmap -histo PID` 打印的是整个Heap的对象统计信息,而为了定位上面的问题,我们需要专门查看OldGen对象,和Survivor区大龄对象的工具。 vjmap的原始思路来源于R大的[TBJMap](https://github.com/alibaba/TBJMap) ,翻新后支持JDK8,支持Survivor区大龄对象过滤,以及大天秤对输出结果不要看歪脖子的执着。 这里有一篇实战:[【唯实践】JVM老生代增长过快问题排查](https://mp.weixin.qq.com/s/6cJ5JuEgEWmMBzJFBDsSMg),最后定位到是Jedis的锅。 注意:因为vjmap的原理,只支持CMS和ParallelGC,不支持G1。 # 2.使用说明 [Download vjmap-1.0.8.zip](http://repo1.maven.org/maven2/com/vip/vjtools/vjmap/1.0.8/vjmap-1.0.8.zip) (from Maven Central) # 2.1 注意事项 注意:vjmap在执行过程中,会完全停止应用一段时间,必须摘流量执行!!!! 1. JAVA_HOME定义 vjmap使用的java为JAVA_HOME/bin/java, 需要至少JDK7,且与目标应用的JVM使用相同的JDK大版本。 vjmap需要依赖JAVA_HOME/lib/sa-jdi.jar JAVA_HOME的定位,通过读取环境变量JAVA_HOME,如果没有定义,则尝试通过"which java"定位java从而获得相对路径。 2. 权限说明 需要root权限 (sudo -E vjmap.sh ...,),权限与jmap -heap pid相同. 如果无法联通进程时,可尝试执行jstack -F pid, jmap -heap pid 自行比对。 如果在容器中运行,需要打开ptrace权限。 ## 2.2 常用指令 针对活着的进程,PID为进程号 ``` // 打印整个堆中对象的统计信息,按对象的total size排序: ./vjmap.sh -all PID > /tmp/histo.log // 推荐,打印老年代的对象统计信息,按对象的oldgen size排序,比-all快很多,暂时只支持CMS: ./vjmap.sh -old PID > /tmp/histo-old.log // 推荐,打印Survivor区的对象统计信息,默认age>=3 ./vjmap.sh -sur PID > /tmp/histo-sur.log // 推荐,打印Survivor区的对象统计信息,查看age>=4的对象 ./vjmap.sh -sur:minage=4 PID > /tmp/histo-sur.log // 推荐,打印Survivor区的对象统计信息,单独查看age=4的对象: ./vjmap.sh -sur:age=4 PID > /tmp/histo-sur.log ``` 针对CoreDump文件 ``` ./vjmap.sh -old ${path_to_java} ${path_to_coredump} ``` ## 2.3 仅输出存活的对象 原理为正式统计前先执行一次full gc ``` ./vjmap.sh -old:live PID > /tmp/histo-old-live.log ``` ## 2.4 过滤对象大小,不显示过小的对象: ``` // 按对象的oldgen size进行过滤,只打印OldGen占用超过1K的数据 ./vjmap.sh -old:minsize=1024 PID > /tmp/histo-old.log ``` ## 2.5 按class name排序,配合大小过滤, 生成用于两次结果比较的报表: ``` ./vjmap.sh -all:minsize=1024,byname PID > /tmp/histo.log ``` ## 2.6 其他注意事项 1. 意外停止 vjmap的运行需要一段时间,如果中途需要停止执行,请使用ctrl+c,或者kill vjmap的PID,让vjmap从目标进程退出。 如果错用了kill -9 ,目标java进程会保持在阻塞状态不再工作,此时必须执行两次 kill -SIGCONT $目标进程PID,重新唤醒目标java进程。 2. OldGen碎片 如果很久没都有进行过CMS GC or Full GC,OldGen将有非常非常多的Live Regions,执行 -all 和 -old 时将非常缓慢,比如 -all的第一步Get Live Regions就会非常缓慢,如非要故意观察死对象的场景,此时可尝试先触发一次full gc, 如使用vjmap -all:live, 或 jmap -histo:live 或 jcmd GC.run 等。 # 3.输出示例 ## 3.1 Survivor区年龄大于N的对象统计 ``` Survivor Object Histogram: #num #count #bytes #Class description ----------------------------------------------------------------------------------- 1: 37 1k io.netty.buffer.PoolThreadCache$MemoryRegionCache$Entry 2: 2 64 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node Total: 39/ 1k over age 2 Heap traversal took 1.3 seconds. ``` # 4. 使用Eclipse MAT进一步分析 如果只依靠对象统计信息,不足以定位问题,需要使用完整HeapDump,计算对象关联关系来进一步分析时,可以在MAT中使用OQL过滤出老生代的对象。 假设,OldGen地址范围是"0xfbd4c000" ~ "0xfce94050" ``` SELECT * FROM INSTANCEOF java.lang.Object t WHERE toHex(t.@objectAddress) <= "0xfce94050" AND toHex(t.@objectAddress) >= "0xfbd4c000" ``` 注意,MAT要在偏好设置中 勾选 "Keep unreachable object" 用如下方式可获得老生代地址: 第一种方式是在启动参数增加 -XX:+PrintHeapAtGC,每次GC都打印地址 第二种方式是使用vjmap的命令,在-old, -sur, -address 中,都会打印出该区间的地址 第三种方式,使用vjmap的address命令,快速打印各代地址,不会造成过长时间停顿 ``` ./vjmap.sh -address PID ``` 输出如下: ``` eden [0x0000000119000000,0x0000000119c4a258,0x0000000121880000) space capacity = 143130624, 9.003395387977907 used from [0x0000000121880000,0x0000000121880000,0x0000000122990000) space capacity = 17891328, 0.0 used to [0x0000000122990000,0x0000000122990000,0x0000000123aa0000) space capacity = 17891328, 0.0 used concurrent mark-sweep generation free-list-space[ 0x0000000123aa0000 , 0x0000000139000000 ) space capacity = 357957632 used(4%)= 17024696 free= 340932936 ``` 上例中的 0x123aa0000 即为OldGen的下界。 注意OQL中使用时要把数值前的那串0去掉。 # 5. 打印加载的Class列表 ``` ./vjmap.sh -class PID ``` 为了兼容JDK8,不再打印Class所在的Jar包 # 6. 与TBJMap的对比 * 兼容JDK8 * 新功能:Survivor区 age大于N的对象统计 * 新功能:打印各分代的地址区间,用于MAT进一步分析 * 性能提升:直接访问Survivor或OldGen区,而不是以Heap Visitor回调的方式访问整个Heap * 新配置项:按对象的占用内存进行过滤,不显示过小的对象 * 新配置项:按对象的名称进行排序,可用于两次统计结果的比对 * 输出改进:报表数字的单位化(k,m,g)与对齐,OldGen报表默认按对象在OldGen的大小排序 ``` ================================================ FILE: vjmap/README_EN.md ================================================ # VJMap VJMap prints per GC generation (Eden, Survivor, OldGen) object details of a given process , it is an advanced way to find the reasons of memory leak and fast-growing OldGen. # 1. Introduction Jmap can display whole shared object memory maps or whole heap memory details ,but sometimes you may prefer to know the OldGen object counting and survivor object age counting,VJMap will list such information for you. Initially inspired by [tbjmap](https://github.com/alibaba/TBJMap), JDK8 compatibility was added as well as query on aged survivor objects. **[Note]**: G1 is unsupported. Use it with CMS and ParallelGC only. # 2. Getting Started [download vjmap-1.0.4.zip](http://repo1.maven.org/maven2/com/vip/vjtools/vjmap/1.0.4/vjmap-1.0.4.zip)(from Maven Central) **[Important]**: VJMap DOES cause stop-of-the-world of the target app. Make sure the target app is isolated from user access before you start using VJMap in production. Run VJMap under **the same user who started the target process**. If access errors are still met, try again with root user. VJMap may take quite some time to finish. Use `kill ` to allow for a graceful exit. If `kill -9 ` is mistakenly issued to the VJMap process, the target app will end up in blocked state, in which case you will have to execute `kill -18 ` TWICE to awaken the target app. ## 2.1 Commands ``` // Prints object stats of all gens, ordered by their respective size in total. ./vjmap.sh -all PID > /tmp/histo.log // Prints oldgen object stats, ordered by size in OldGen. Only CMS is supported for this option. ./vjmap.sh -old PID > /tmp/histo-old.log // Prints survivor objects over the age of 3. ./vjmap.sh -sur PID > /tmp/histo-sur.log // Prints survivor objects over the age of 10, as desinated by the argument -sur:minage=10 // When the promotion threshold -XX:MaxTenuringThreshold is lifted, objects with a high age value will be bound for the CMS oldgen ./vjmap.sh -sur:minage=10 PID > /tmp/histo-sur.log ``` > PID is the process ID of target java application ## 2.2 Display Larger Objects, Leaving Smaller Ones Out ``` // Shows objects with sizes over 1KB over the whole heap ./vjmap.sh -all:minsize=1024 PID > /tmp/histo.log // shows objects with sizes over 1KB in OldGen specifically ./vjmap.sh -old:minsize=1024 PID > /tmp/histo-old.log // shows objects with sizes over 1KB in survivor space ./vjmap.sh -sur:minsize=1024 PID > /tmp/histo-sur.log ``` ## 2.3 Order by Classname and Filter by Size for Periodic Comparisons ``` ./vjmap.sh -all:minsize=1024,byname PID > /tmp/histo.log ./vjmap.sh -old:minsize=1024,byname PID > /tmp/histo-old.log ./vjmap.sh -sur:minsize=1024,byname PID > /tmp/histo-sur.log ``` ## 2.4 Prints object stats of old gen, live objects only: ``` ./vjmap.sh -old:live PID > /tmp/histo-old.log ``` # 3.Outputs ## 3.1 Count Survivor Objects over the Age of 3. ``` Survivor Object Histogram: #num #count #bytes #Class description ----------------------------------------------------------------------------------- 1: 37 1k io.netty.buffer.PoolThreadCache$MemoryRegionCache$Entry 2: 2 64 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node Total: 39/ 1k over age 2 Heap traversal took 1.3 seconds. ``` # 4. Eclipse MAT 如果只依靠对象统计信息,不足以定位问题,需要使用完整HeapDump,计算对象关联关系来进一步分析时,可以在MAT中使用OQL过滤出老生代的对象。 假设,OldGen地址范围是"0xfbd4c000" ~ "0xfce94050" ``` SELECT * FROM INSTANCEOF java.lang.Object t WHERE (toHex(t.@objectAddress) >= "0xfbd4c000" AND toHex(t.@objectAddress) <= "0xfce94050") ``` 用如下方式可获得老生代地址: 第一种方式是在启动参数增加 -XX:+PrintHeapAtGC 第二种方式是使用vjmap的命令,在-old, -sur, -address 中,都会打印出区间的地址。 ``` ./vjmap.sh -address PID ``` 输出如下: ``` eden [0x0000000119000000,0x0000000119c4a258,0x0000000121880000) space capacity = 143130624, 9.003395387977907 used from [0x0000000121880000,0x0000000121880000,0x0000000122990000) space capacity = 17891328, 0.0 used to [0x0000000122990000,0x0000000122990000,0x0000000123aa0000) space capacity = 17891328, 0.0 used concurrent mark-sweep generation free-list-space[ 0x0000000123aa0000 , 0x0000000139000000 ) space capacity = 357957632 used(4%)= 17024696 free= 340932936 ``` # 5. Enhancements over TBJMap * Added JDK8 Support. * Added Display: survivor objects over the specified age. * Performance Boost: by accessing Survivor and OldGen directly instead of by accessing the whole heap with Heap Visitor callbacks. * New config Arg: order objects by size and leave out small ones. * New Config Arg: order objects by name for periodic comparison. * Reading Friendliness: output by the unit of (k, m, g) and fix alignment, order objects in OldGen by size in OldGen view by default. ================================================ FILE: vjmap/pom.xml ================================================ 4.0.0 com.vip.vjtools vjmap 1.0.9-SNAPSHOT vjmap jmap with per generation object stats ${java.home}/../lib/sa-jdi.jar ${java.home}/../lib/tools.jar UTF-8 1.7 ${java.version} ${java.version} com.sun sa-jdi 1.0 system ${sajdijar} com.sun tools ${java.version} system ${toolsjar} org.apache.maven.plugins maven-assembly-plugin 2.6 src/main/assembly/distribution.xml false assembly package single release org.apache.maven.plugins maven-javadoc-plugin 2.10.4 false attach-javadocs jar org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar org.apache.maven.plugins maven-release-plugin 2.5.3 v.@{project.version} org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign sonatype-nexus-snapshots https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-releases https://oss.sonatype.org/service/local/staging/deploy/maven2 https://github.com/vipshop/vjtools Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:https://github.com/vipshop/vjtools.git scm:git:https://github.com/vipshop/vjtools.git https://github.com/vipshop/vjtools v.1.0.2 calvin Calvin Xiao calvin.xiao at vipshop.com developer +8 ================================================ FILE: vjmap/src/main/assembly/distribution.xml ================================================ zip zip ${project.artifactId} / com.vip.vjtools:vjmap:* ${artifact.artifactId}.${artifact.extension} src/main/assembly/vjmap.sh 0755 unix src/main/assembly/vjmap.bat windows README.md README.md unix ================================================ FILE: vjmap/src/main/assembly/vjmap.bat ================================================ @echo off rem check java if "%JAVA_HOME%" == "" goto noJavaHome echo WARNING!! STW(Stop-The-World) will be performed on your Java process, if this is NOT wanted, type 'Ctrl+C' to exit. set DIR=%~dp0 set JAVA_OPTS=-Xms512m -Xmx512m -Xmn400m -XX:+TieredCompilation -XX:+UseConcMarkSweepGC -Xverify:none -XX:AutoBoxCacheMax=20000 "%JAVA_HOME%\bin\java" %JAVA_OPTS% -classpath "%DIR%\vjmap.jar;%JAVA_HOME%\lib\sa-jdi.jar" com.vip.vjtools.vjmap.VJMap %* goto end :noJavaHome echo Please set JAVA_HOME before running this script goto end :end pause ================================================ FILE: vjmap/src/main/assembly/vjmap.sh ================================================ #!/bin/sh if [ -z "$JAVA_HOME" ] ; then echo "JAVA_HOME env doesn't exist, try to find the location of java" JAVA_HOME=`readlink -f \`which java 2>/dev/null\` 2>/dev/null | \ sed 's/\jre\/bin\/java//' | sed 's/\/bin\/java//'` fi if [ ! -d "$JAVA_HOME" ] ; then echo "Please set JAVA_HOME env before run this script" exit 1 fi SAJDI_PATH=$JAVA_HOME/lib/sa-jdi.jar if [ ! -f "$SAJDI_PATH" ] ; then echo "$SAJDI_PATH doesn't exist !" >&2 exit 1 fi TOOLS_PATH=$JAVA_HOME/lib/tools.jar if [ ! -f "$TOOLS_PATH" ] ; then echo "$TOOLS_PATH doesn't exist !" >&2 exit 1 fi echo -e "\033[31mWARNING!! STW(Stop-The-World) will be performed on your Java process, if this is NOT wanted, type 'Ctrl+C' to exit. \033[0m" DIR=$( cd $(dirname $0) ; pwd -P ) JAVA_OPTS="-Xms512m -Xmx512m -Xmn400m -XX:+UseConcMarkSweepGC -XX:+TieredCompilation -Xverify:none -XX:AutoBoxCacheMax=20000" "$JAVA_HOME"/bin/java $JAVA_OPTS -classpath $DIR/vjmap.jar:$SAJDI_PATH:$TOOLS_PATH com.vip.vjtools.vjmap.VJMap $* ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/ClassStats.java ================================================ package com.vip.vjtools.vjmap; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.Comparator; import sun.jvm.hotspot.oops.ArrayKlass; import sun.jvm.hotspot.oops.InstanceKlass; import sun.jvm.hotspot.oops.Klass; import sun.jvm.hotspot.oops.ObjArrayKlass; import sun.jvm.hotspot.oops.TypeArrayKlass; public class ClassStats { private Klass klass; private String description; public long count; public long size; public long edenCount; public long edenSize; public long survivorCount; public long survivorSize; public long oldCount; public long oldSize; public ClassStats(Klass k) { this.klass = k; description = initDescription(); } public String getDescription() { return description; } /** * 参考 JDK8 sun.jvm.hotspot.oops.ObjectHistogramElement * * StringBuffer->StringBuilder */ public String initDescription() { Klass k = klass; if (k instanceof InstanceKlass) { return k.getName().asString().replace('/', '.'); } else if (k instanceof ArrayKlass) { ArrayKlass ak = (ArrayKlass) k; if (k instanceof TypeArrayKlass) { TypeArrayKlass tak = (TypeArrayKlass) ak; return tak.getElementTypeName() + "[]"; } else if (k instanceof ObjArrayKlass) { ObjArrayKlass oak = (ObjArrayKlass) ak; Klass bottom = oak.getBottomKlass(); int dim = (int) oak.getDimension(); StringBuilder buf = new StringBuilder(64); if (bottom instanceof TypeArrayKlass) { buf.append(((TypeArrayKlass) bottom).getElementTypeName()); } else if (bottom instanceof InstanceKlass) { buf.append(bottom.getName().asString().replace('/', '.')); } else { throw new RuntimeException("should not reach here"); } for (int i = 0; i < dim; i++) { buf.append("[]"); } return buf.toString(); } } return getInternalName(k); } /** * 参考 sun.jvm.hotspot.oops.ObjectHistogramElement */ private String getInternalName(Klass k) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); klass.printValueOn(new PrintStream(bos)); // '*' is used to denote VM internal klasses. return "* " + bos.toString(); } public Klass getKlass() { return this.klass; } public long getCount() { return this.count; } public long getSize() { return this.size; } public long getOldCount() { return this.oldCount; } public long getOldSize() { return this.oldSize; } public long getSurvivorCount() { return survivorCount; } public long getSurvivorSize() { return survivorSize; } public long getEdenCount() { return edenCount; } public long getEdenSize() { return edenSize; } public static Comparator TOTAL_SIZE_COMPARATOR = new Comparator() { @Override public int compare(ClassStats o1, ClassStats o2) { return (int) (o2.getSize() - o1.getSize()); } }; public static Comparator OLD_SIZE_COMPARATOR = new Comparator() { @Override public int compare(ClassStats o1, ClassStats o2) { return (int) (o2.getOldSize() - o1.getOldSize()); } }; public static Comparator SUR_SIZE_COMPARATOR = new Comparator() { @Override public int compare(ClassStats o1, ClassStats o2) { return (int) (o2.getSurvivorSize() - o1.getSurvivorSize()); } }; public static Comparator NAME_COMPARATOR = new Comparator() { @Override public int compare(ClassStats o1, ClassStats o2) { return o1.getDescription().compareTo(o2.getDescription()); } }; } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/ResultPrinter.java ================================================ package com.vip.vjtools.vjmap; import java.io.PrintStream; import java.util.Collections; import java.util.Iterator; import java.util.List; import com.vip.vjtools.vjmap.utils.FormatUtils; public class ResultPrinter { /** * 打印所有新老生代的结果 */ public void printAllGens(PrintStream tty, List list, boolean orderByName, long minSize) { if (orderByName) { Collections.sort(list, ClassStats.NAME_COMPARATOR); } else { Collections.sort(list, ClassStats.TOTAL_SIZE_COMPARATOR); } tty.println("\nObject Histogram:"); tty.println(); tty.printf("%6s %15s %15s %15s %15s %s%n", "#num", "#all", "#eden", "#from", "#old", "#class description"); tty.println( "--------------------------------------------------------------------------------------------------"); Iterator iterator = list.listIterator(); int num = 0; int totalCount = 0; int totalSize = 0; while (iterator.hasNext()) { ClassStats classStats = iterator.next(); if (classStats.getSize() > minSize) { num++; totalCount = (int) (totalCount + classStats.getCount()); totalSize = (int) (totalSize + classStats.getSize()); tty.printf("%5d: %7d/%7s %7d/%7s %7d/%7s %7d/%7s %s%n", num, classStats.getCount(), FormatUtils.toFloatUnit(classStats.getSize()), classStats.getEdenCount(), FormatUtils.toFloatUnit(classStats.getEdenSize()), classStats.getSurvivorCount(), FormatUtils.toFloatUnit(classStats.getSurvivorSize()), classStats.getOldCount(), FormatUtils.toFloatUnit(classStats.getOldSize()), classStats.getDescription()); } } tty.printf(" Total: %7d/%7s , minSize=%d%n", totalCount, FormatUtils.toFloatUnit(totalSize), minSize); } /** * 打印只包含存活区的结果 */ public void printSurvivor(PrintStream tty, List list, boolean orderByName, long minSize, int age, int minAge) { if (orderByName) { Collections.sort(list, ClassStats.NAME_COMPARATOR); } else { Collections.sort(list, ClassStats.SUR_SIZE_COMPARATOR); } tty.println("\nSurvivor Object Histogram:\n"); tty.printf("%6s %7s %7s %s%n", "#num", "#count", "#bytes", "#Class description"); tty.println("-----------------------------------------------------------------------------------"); Iterator iterator = list.listIterator(); int num = 0; long totalSurCount = 0; long totalSurSize = 0; while (iterator.hasNext()) { ClassStats classStats = iterator.next(); if (classStats.getSurvivorSize() > minSize) { totalSurCount = totalSurCount + classStats.getSurvivorCount(); totalSurSize = totalSurSize + classStats.getSurvivorSize(); num++; tty.printf("%5d: %7d %7s %s%n", num, classStats.getSurvivorCount(), FormatUtils.toFloatUnit(classStats.getSurvivorSize()), classStats.getDescription()); } } if (age != -1) { tty.printf(" Total: %7d %7s, age=%d, minSize=%d%n", totalSurCount, FormatUtils.toFloatUnit(totalSurSize), age, minSize); } else { tty.printf(" Total: %7d %7s, minAge=%d, minSize=%d%n", totalSurCount, FormatUtils.toFloatUnit(totalSurSize), minAge, minSize); } } /** * 打印只包含老生代的结果 */ public void printOldGen(PrintStream tty, List list, boolean orderByName, long minSize) { if (orderByName) { Collections.sort(list, ClassStats.NAME_COMPARATOR); } else { Collections.sort(list, ClassStats.OLD_SIZE_COMPARATOR); } tty.println("\nOldGen Object Histogram:\n"); tty.printf("%6s %7s %7s %s%n", "#num", "#count", "#bytes", "#class description"); tty.println("-----------------------------------------------------------------------------------"); Iterator iterator = list.listIterator(); int num = 0; long totalOldCount = 0; long totalOldSize = 0; while (iterator.hasNext()) { ClassStats classStats = iterator.next(); if (classStats.getOldSize() > minSize) { totalOldCount = totalOldCount + classStats.getOldCount(); totalOldSize = totalOldSize + classStats.getOldSize(); num++; tty.printf("%5d: %7s %7s %s%n", num, classStats.getOldCount(), FormatUtils.toFloatUnit(classStats.getOldSize()), classStats.getDescription()); } } tty.printf(" Total: %7d %7s, minSize=%d%n", totalOldCount, FormatUtils.toFloatUnit(totalOldSize), minSize); } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/VJMap.java ================================================ package com.vip.vjtools.vjmap; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.util.List; import com.sun.tools.attach.VirtualMachine; import com.vip.vjtools.vjmap.oops.GenAddressAccessor; import com.vip.vjtools.vjmap.oops.HeapHistogramVisitor; import com.vip.vjtools.vjmap.oops.HeapUtils; import com.vip.vjtools.vjmap.oops.LoadedClassAccessor; import com.vip.vjtools.vjmap.oops.OldgenAccessor; import com.vip.vjtools.vjmap.oops.SurvivorAccessor; import com.vip.vjtools.vjmap.utils.TimeController.TimeoutException; import sun.jvm.hotspot.HotSpotAgent; import sun.jvm.hotspot.oops.ObjectHeap; import sun.jvm.hotspot.runtime.VM; import sun.tools.attach.HotSpotVirtualMachine;; public class VJMap { public static final String VERSION = "1.0.9"; private static PrintStream tty = System.out; // 用于ctrl-C退出时仍然打印结果 private static OldGenProcessor oldGenProcessor; private static HeapProcessor heapProcessor; public static void runHeapVisitor(int pid, boolean orderByName, long minSize) { ObjectHeap heap = VM.getVM().getObjectHeap(); heapProcessor = new HeapProcessor(orderByName, minSize); tty.println("Iterating over heap. This may take a while..."); tty.println("Geting live regions..."); heap.iterate(heapProcessor.visitor); heapProcessor.printResult(); heapProcessor = null; } public static class HeapProcessor { HeapHistogramVisitor visitor = new HeapHistogramVisitor(); boolean orderByName; long minSize; public HeapProcessor(boolean orderByName, long minSize) { this.orderByName = orderByName; this.minSize = minSize; } public void printResult() { List list = HeapUtils.getClassStatsList(visitor.getClassStatsMap()); ResultPrinter resultPrinter = new ResultPrinter(); resultPrinter.printAllGens(tty, list, orderByName, minSize); } } public static void runSurviorAccessor(int age, int minAge, boolean orderByName, long minSize) { SurvivorAccessor accessor = new SurvivorAccessor(); tty.println("Iterating over survivor area. This may take a while..."); List list = accessor.caculateHistogram(age, minAge); ResultPrinter resultPrinter = new ResultPrinter(); resultPrinter.printSurvivor(tty, list, orderByName, minSize, age, minAge); } public static void runOldGenAccessor(boolean orderByName, long minSize) { oldGenProcessor = new OldGenProcessor(orderByName, minSize); tty.println("Iterating over oldgen area. This may take a while..."); oldGenProcessor.accessor.caculateHistogram(); oldGenProcessor.printResult(); oldGenProcessor = null; } public static class OldGenProcessor { OldgenAccessor accessor = new OldgenAccessor(); boolean orderByName; long minSize; public OldGenProcessor(boolean orderByName, long minSize) { this.orderByName = orderByName; this.minSize = minSize; } public void printResult() { List list = HeapUtils.getClassStatsList(accessor.getClassStatsMap()); ResultPrinter resultPrinter = new ResultPrinter(); resultPrinter.printOldGen(tty, list, orderByName, minSize); } } public static void printGenAddress() { GenAddressAccessor accessor = new GenAddressAccessor(); accessor.printHeapAddress(); } public static void printLoadedClass() { LoadedClassAccessor accessor = new LoadedClassAccessor(); accessor.pringLoadedClass(); } public static void main(String[] args) { // 分析参数 boolean orderByName = false; long minSize = -1; int minAge = 2; int age = -1; boolean live = false; // boolean dead = false; if (!(args.length == 2 || args.length == 3)) { printHelp(); return; } String modeFlag = args[0]; String[] modeFlags = modeFlag.split(":"); if (modeFlags.length > 1) { String[] addtionalFlags = modeFlags[1].split(","); for (String addtionalFlag : addtionalFlags) { if ("byname".equalsIgnoreCase(addtionalFlag)) { orderByName = true; } else if (addtionalFlag.toLowerCase().startsWith("minsize")) { String[] values = addtionalFlag.split("="); if (values.length == 1) { tty.println("parameter " + addtionalFlag + " is wrong"); return; } minSize = Long.parseLong(values[1]); } else if (addtionalFlag.toLowerCase().startsWith("minage")) { String[] values = addtionalFlag.split("="); if (values.length == 1) { tty.println("parameter " + addtionalFlag + " is wrong"); return; } minAge = Integer.parseInt(values[1]); } else if (addtionalFlag.toLowerCase().startsWith("age")) { String[] values = addtionalFlag.split("="); if (values.length == 1) { tty.println("parameter " + addtionalFlag + " is wrong"); return; } age = Integer.parseInt(values[1]); } else if (addtionalFlag.toLowerCase().startsWith("live")) { live = true; } } } Integer pid = null; String executablePath = null; String coredumpPath = null; if (args.length == 2) { pid = Integer.valueOf(args[1]); } else { executablePath = args[1]; coredumpPath = args[2]; } // 如有需要,执行GC if (live) { if (pid == null) { tty.println("only a running vm can be attached when live option is on"); return; } triggerGc(pid); } //// 正式执行 HotSpotAgent agent = new HotSpotAgent(); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { // 如果ctrl+C退出,仍尽量打印结果 if (oldGenProcessor != null) { tty.println("VJMap aborted. Below is the incomplete summary: "); oldGenProcessor.printResult(); } if (heapProcessor != null) { tty.println("VJMap aborted. Below is the incomplete summary: "); heapProcessor.printResult(); } tty.flush(); } }); try { if (args.length == 2) { agent.attach(pid); } else { agent.attach(executablePath, coredumpPath); } long startTime = System.currentTimeMillis(); if (modeFlag.startsWith("-all")) { runHeapVisitor(pid, orderByName, minSize); } else if (modeFlag.startsWith("-sur")) { runSurviorAccessor(age, minAge, orderByName, minSize); } else if (modeFlag.startsWith("-old")) { runOldGenAccessor(orderByName, minSize); } else if (modeFlag.startsWith("-address")) { printGenAddress(); } else if (modeFlag.startsWith("-class")) { printLoadedClass(); } else if (modeFlag.startsWith("-version")) { tty.println("vjmap version:" + VERSION); return; } else { printHelp(); return; } long endTime = System.currentTimeMillis(); double secs = (endTime - startTime) / 1000.0d; tty.printf("%n Heap traversal took %.1f seconds.%n", secs); tty.flush(); } catch (TimeoutException e) { tty.println("\n\nVJMap aborted by timeout."); tty.println("Try to use live option to reduce the fragments which make progress very slow."); tty.println("./vjmap.sh -old:live PID\n\n"); } catch (Exception e) { tty.println("Error Happen:" + e.getMessage()); if (e.getMessage() != null && e.getMessage().contains("Can't attach to the process")) { tty.println( "Please use the same user of the target JVM to run vjmap, or use root user to run it (sudo -E vjmap.sh ...)"); } } finally { agent.detach(); } } /** * Trigger a remote gc using HotSpotVirtualMachine, inspired by jcmd's source code. * * @param pid */ private static void triggerGc(Integer pid) { VirtualMachine vm = null; try { vm = VirtualMachine.attach(String.valueOf(pid)); HotSpotVirtualMachine hvm = (HotSpotVirtualMachine) vm; try (InputStream in = hvm.executeJCmd("GC.run");) { byte b[] = new byte[256]; int n; do { n = in.read(b); if (n > 0) { String s = new String(b, 0, n, "UTF-8"); tty.print(s); } } while (n > 0); tty.println(); } } catch (Exception e) { tty.println(e.getMessage()); } finally { if (vm != null) { try { vm.detach(); } catch (IOException e) { tty.println(e.getMessage()); } } } } private static void printHelp() { int leftLength = "-all:minsize=1024,byname".length(); String format = " %-" + leftLength + "s %s%n"; tty.println("vjmap " + VERSION + " - prints per GC generation (Eden, Survivor, OldGen) object details of a given process."); tty.println("Usage: vjmap.sh "); tty.println("Usage: vjmap.sh "); tty.println(""); tty.printf(format, "-all", "print all gens histogram, order by total size"); tty.printf(format, "-all:live", "print all gens histogram, live objects only"); tty.printf(format, "-all:minsize=1024", "print all gens histogram, total size>=1024"); tty.printf(format, "-all:minsize=1024,byname", "print all gens histogram, total size>=1024, order by class name"); tty.printf(format, "-old", "print oldgen histogram, order by oldgen size"); tty.printf(format, "-old:live", "print oldgen histogram, live objects only"); tty.printf(format, "-old:minsize=1024", "print oldgen histogram, oldgen size>=1024"); tty.printf(format, "-old:minsize=1024,byname", "print oldgen histogram, oldgen size>=1024, order by class name"); tty.printf(format, "-sur", "print survivor histogram, age>=2"); tty.printf(format, "-sur:age=4", "print survivor histogram, age==4"); tty.printf(format, "-sur:minage=4", "print survivor histogram, age>=4, default is 2"); tty.printf(format, "-sur:minsize=1024,byname", "print survivor histogram, age>=3, survivor size>=1024, order by class name"); tty.printf(format, "-address", "print address for all gens"); tty.printf(format, "-class", "print all loaded classes"); } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/oops/GenAddressAccessor.java ================================================ package com.vip.vjtools.vjmap.oops; import java.io.PrintStream; import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen; import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen; import sun.jvm.hotspot.gc_interface.CollectedHeap; import sun.jvm.hotspot.memory.ConcurrentMarkSweepGeneration; import sun.jvm.hotspot.memory.DefNewGeneration; /** * 打印各区地址 */ public class GenAddressAccessor { private PrintStream tty = System.out; public void printHeapAddress() { CollectedHeap heap = HeapUtils.getHeap(); if (HeapUtils.isCMSGC(heap)) { DefNewGeneration youngGen = HeapUtils.getYoungGenForCMS(heap); youngGen.printOn(tty); tty.println(""); ConcurrentMarkSweepGeneration cmsGen = HeapUtils.getOldGenForCMS(heap); cmsGen.printOn(tty); } else if (HeapUtils.isParallelGC(heap)) { // Parallel GC PSYoungGen psYoung = HeapUtils.getYongGenForPar(heap); psYoung.printOn(tty); tty.println(""); PSOldGen oldgen = HeapUtils.getOldGenForPar(heap); oldgen.printOn(tty); } else { throw new IllegalArgumentException("Unsupport heap:" + heap.getClass().getName()); } } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/oops/HeapHistogramVisitor.java ================================================ package com.vip.vjtools.vjmap.oops; import java.util.HashMap; import com.vip.vjtools.vjmap.ClassStats; import com.vip.vjtools.vjmap.utils.ProgressNotifier; import sun.jvm.hotspot.debugger.OopHandle; import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen; import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen; import sun.jvm.hotspot.gc_implementation.shared.MutableSpace; import sun.jvm.hotspot.gc_interface.CollectedHeap; import sun.jvm.hotspot.memory.ConcurrentMarkSweepGeneration; import sun.jvm.hotspot.memory.ContiguousSpace; import sun.jvm.hotspot.memory.DefNewGeneration; import sun.jvm.hotspot.memory.EdenSpace; import sun.jvm.hotspot.oops.HeapVisitor; import sun.jvm.hotspot.oops.Klass; import sun.jvm.hotspot.oops.Oop; /** * 实现HeapVisitor接口,实现遍历堆的回调方法 */ public class HeapHistogramVisitor implements HeapVisitor { private CollectedHeap heap; private EdenSpace cmsEden; private ContiguousSpace cmsSur; private ConcurrentMarkSweepGeneration cmsOld; private MutableSpace parEden; private MutableSpace parSur; private PSOldGen parOld; private boolean isCms; private HashMap classStatsMap; private ProgressNotifier progressNodifier; public HeapHistogramVisitor() { classStatsMap = new HashMap<>(2048, 0.2f); heap = HeapUtils.getHeap(); if (HeapUtils.isCMSGC(heap)) { DefNewGeneration youngGen = HeapUtils.getYoungGenForCMS(heap); cmsEden = youngGen.eden(); cmsSur = youngGen.from(); cmsOld = HeapUtils.getOldGenForCMS(heap); isCms = true; } else if (HeapUtils.isParallelGC(heap)) { PSYoungGen youngGen = HeapUtils.getYongGenForPar(heap); parEden = youngGen.edenSpace(); parSur = youngGen.fromSpace(); parOld = HeapUtils.getOldGenForPar(heap); isCms = false; } else { throw new RuntimeException("Only support CMS and Parallel GC. Unsupport Heap:" + heap.getClass().getName()); } } @Override public boolean doObj(Oop obj) { Klass klass = obj.getKlass(); ClassStats classStats = HeapUtils.getClassStats(klass, classStatsMap); Place place = isCms ? getCmsLocation(obj) : getParLocation(obj); long objSize = obj.getObjectSize(); updateWith(classStats, objSize, place); // 每完成1% 打印一个.,每完成10% 打印百分比提示 progressNodifier.processingSize += objSize; if (progressNodifier.processingSize > progressNodifier.nextNotificationSize) { progressNodifier.printProgress(); } return false; } @Override public void prologue(long size) { progressNodifier = new ProgressNotifier(size); progressNodifier.printHead(); } @Override public void epilogue() { } private void updateWith(ClassStats classStats, long objSize, Place place) { classStats.count++; classStats.size += objSize; switch (place) { case InEden: classStats.edenCount++; classStats.edenSize += objSize; break; case InSurvivor: classStats.survivorCount++; classStats.survivorSize += objSize; break; case InOld: classStats.oldCount++; classStats.oldSize += objSize; break; case Unknown: break; } } private Place getCmsLocation(Oop obj) { OopHandle handle = obj.getHandle(); if (cmsEden.contains(handle)) { return Place.InEden; } if (cmsOld.contains(handle)) { return Place.InOld; } if (cmsSur.contains(handle)) { return Place.InSurvivor; } return Place.Unknown; } public Place getParLocation(Oop obj) { OopHandle handle = obj.getHandle(); if (parEden.contains(handle)) { return Place.InEden; } if (parOld.isIn(handle)) { return Place.InOld; } if (parSur.contains(handle)) { return Place.InSurvivor; } return Place.Unknown; } public HashMap getClassStatsMap() { return classStatsMap; } public enum Place { Unknown, InEden, InSurvivor, InOld; } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/oops/HeapUtils.java ================================================ package com.vip.vjtools.vjmap.oops; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import com.vip.vjtools.vjmap.ClassStats; import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen; import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen; import sun.jvm.hotspot.gc_implementation.parallelScavenge.ParallelScavengeHeap; import sun.jvm.hotspot.gc_interface.CollectedHeap; import sun.jvm.hotspot.memory.ConcurrentMarkSweepGeneration; import sun.jvm.hotspot.memory.DefNewGeneration; import sun.jvm.hotspot.memory.GenCollectedHeap; import sun.jvm.hotspot.oops.Klass; import sun.jvm.hotspot.oops.ObjectHeap; import sun.jvm.hotspot.runtime.VM; public class HeapUtils { public static CollectedHeap getHeap() { return VM.getVM().getUniverse().heap(); } public static ObjectHeap getObjectHeap() { return VM.getVM().getObjectHeap(); } public static boolean isCMSGC(CollectedHeap heap) { return heap instanceof GenCollectedHeap; } public static boolean isParallelGC(CollectedHeap heap) { return heap instanceof ParallelScavengeHeap; } public static DefNewGeneration getYoungGenForCMS(CollectedHeap heap) { return (DefNewGeneration) ((GenCollectedHeap) heap).getGen(0); } public static ConcurrentMarkSweepGeneration getOldGenForCMS(CollectedHeap heap) { return (ConcurrentMarkSweepGeneration) ((GenCollectedHeap) heap).getGen(1); } public static PSYoungGen getYongGenForPar(CollectedHeap heap) { return ((ParallelScavengeHeap) heap).youngGen(); } public static PSOldGen getOldGenForPar(CollectedHeap heap) { return ((ParallelScavengeHeap) heap).oldGen(); } public static List getClassStatsList(HashMap classStatsMap) { List list = new ArrayList<>(classStatsMap.size()); list.addAll(classStatsMap.values()); return list; } public static ClassStats getClassStats(Klass klass, HashMap classStatsMap) { ClassStats stats = classStatsMap.get(klass); if (stats == null) { stats = new ClassStats(klass); classStatsMap.put(klass, stats); } return stats; } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/oops/LoadedClassAccessor.java ================================================ package com.vip.vjtools.vjmap.oops; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import sun.jvm.hotspot.debugger.AddressException; import sun.jvm.hotspot.memory.SystemDictionary; import sun.jvm.hotspot.oops.InstanceKlass; import sun.jvm.hotspot.oops.Klass; import sun.jvm.hotspot.oops.Oop; import sun.jvm.hotspot.runtime.VM; public class LoadedClassAccessor { private PrintStream tty = System.out; public void pringLoadedClass() { tty.println("Finding classes in System Dictionary.."); try { final ArrayList klasses = new ArrayList<>(128); SystemDictionary dict = VM.getVM().getSystemDictionary(); dict.classesDo(new SystemDictionary.ClassVisitor() { @Override public void visit(Klass k) { if (k instanceof InstanceKlass) { klasses.add((InstanceKlass) k); } } }); Collections.sort(klasses, new Comparator() { @Override public int compare(InstanceKlass x, InstanceKlass y) { return x.getName().asString().compareTo(y.getName().asString()); } }); tty.println("#class #loader"); tty.println("-----------------------------------------------"); for (InstanceKlass k : klasses) { tty.printf("%s, %s\n", getClassNameFrom(k), getClassLoaderOopFrom(k)); } } catch (AddressException e) { tty.println("Error accessing address 0x" + Long.toHexString(e.getAddress())); e.printStackTrace(); } } private static String getClassLoaderOopFrom(InstanceKlass klass) { Oop loader = klass.getClassLoader(); return loader != null ? getClassNameFrom((InstanceKlass) loader.getKlass()) + " @ " + loader.getHandle() : ""; } private static String getClassNameFrom(InstanceKlass klass) { return klass != null ? klass.getName().asString().replace('/', '.') : null; } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/oops/OldgenAccessor.java ================================================ package com.vip.vjtools.vjmap.oops; import java.io.PrintStream; import java.util.HashMap; import com.vip.vjtools.vjmap.ClassStats; import com.vip.vjtools.vjmap.utils.ProgressNotifier; import sun.jvm.hotspot.debugger.Address; import sun.jvm.hotspot.gc_interface.CollectedHeap; import sun.jvm.hotspot.memory.CMSCollector; import sun.jvm.hotspot.memory.CompactibleFreeListSpace; import sun.jvm.hotspot.memory.ConcurrentMarkSweepGeneration; import sun.jvm.hotspot.memory.FreeChunk; import sun.jvm.hotspot.oops.Klass; import sun.jvm.hotspot.oops.ObjectHeap; import sun.jvm.hotspot.oops.Oop; import sun.jvm.hotspot.oops.UnknownOopException; import sun.jvm.hotspot.runtime.VM; import sun.jvm.hotspot.runtime.VMObjectFactory; /** * 使用主动访问堆的方式统计OldGen的对象信息, only support CMS GC. * * 迭代分区的代码,来自于 sun.jvm.hotspot.memory.CompactibleFreeListSpace.getLiveRegions() * sun.jvm.hotspot.oops.ObjectHeap.iterateLiveRegions() * * 第一版全抄iterateLiveRegions(),后来发现getLiveRegions()本身已经遍历了一次堆,所以改为在其基础上修改。 */ public class OldgenAccessor { private PrintStream tty = System.out; private Address cur; private Address regionStart; private int liveRegions = 0; private HashMap classStatsMap = new HashMap<>(2048, 0.2f); public HashMap getClassStatsMap() { return classStatsMap; } public void caculateHistogram() { ObjectHeap objectHeap = HeapUtils.getObjectHeap(); CollectedHeap heap = checkHeapType(); ConcurrentMarkSweepGeneration cmsGen = HeapUtils.getOldGenForCMS(heap); CompactibleFreeListSpace cmsSpace = cmsGen.cmsSpace(); CMSCollector cmsCollector = cmsSpace.collector(); cur = cmsSpace.bottom(); regionStart = cur; Address limit = cmsSpace.end(); printGenSummary(cmsGen); ProgressNotifier progressNotifier = new ProgressNotifier(cmsGen.used()); progressNotifier.printHead(); final long addressSize = VM.getVM().getAddressSize(); for (; cur.lessThan(limit);) { Address k = cur.getAddressAt(addressSize); if (FreeChunk.indicatesFreeChunk(cur)) { skipFreeChunk(addressSize); } else if (k != null) { Oop obj = null; try { obj = objectHeap.newOop(cur.addOffsetToAsOopHandle(0)); } catch (UnknownOopException ignored) { // ignored } if (obj == null) { continueNextAddress(cmsCollector); continue; } long objectSize = obj.getObjectSize(); ClassStats stats = HeapUtils.getClassStats(obj.getKlass(), classStatsMap); stats.oldCount++; stats.oldSize += objectSize; progressNotifier.processingSize += objectSize; if (progressNotifier.processingSize > progressNotifier.nextNotificationSize) { progressNotifier.printProgress(); } cur = cur.addOffsetTo(CompactibleFreeListSpace.adjustObjectSizeInBytes(objectSize)); } else { continueNextAddress(cmsCollector); } } tty.println("\ntotal live regions:" + liveRegions); } private CollectedHeap checkHeapType() { CollectedHeap heap = HeapUtils.getHeap(); if (!HeapUtils.isCMSGC(heap)) { throw new IllegalArgumentException("Only support CMS GC. Unsupport heap:" + heap.getClass().getName()); } return heap; } private void printGenSummary(ConcurrentMarkSweepGeneration cmsGen) { cmsGen.printOn(tty); tty.println(""); } private void skipFreeChunk(final long addressSize) { if (!cur.equals(regionStart)) { liveRegions++; } FreeChunk fc = (FreeChunk) VMObjectFactory.newObject(FreeChunk.class, cur); long chunkSize = fc.size(); cur = cur.addOffsetTo(chunkSize * addressSize); } private void continueNextAddress(CMSCollector cmsCollector) { long size = cmsCollector.blockSizeUsingPrintezisBits(cur); if (size <= 0L) { throw new UnknownOopException(); } cur = cur.addOffsetTo(CompactibleFreeListSpace.adjustObjectSizeInBytes(size)); } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/oops/SurvivorAccessor.java ================================================ package com.vip.vjtools.vjmap.oops; import java.io.PrintStream; import java.util.HashMap; import java.util.List; import com.vip.vjtools.vjmap.ClassStats; import com.vip.vjtools.vjmap.utils.FormatUtils; import sun.jvm.hotspot.debugger.Address; import sun.jvm.hotspot.debugger.OopHandle; import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen; import sun.jvm.hotspot.gc_implementation.shared.MutableSpace; import sun.jvm.hotspot.gc_interface.CollectedHeap; import sun.jvm.hotspot.memory.ContiguousSpace; import sun.jvm.hotspot.memory.DefNewGeneration; import sun.jvm.hotspot.oops.Klass; import sun.jvm.hotspot.oops.ObjectHeap; import sun.jvm.hotspot.oops.Oop; import sun.jvm.hotspot.oops.UnknownOopException; /** * 使用主动访问堆的方式统计Survivor区的对象信息, only support CMS and Parallel GC. * * 迭代分区的代码,copy from sun.jvm.hotspot.oops.ObjectHeap.iterateLiveRegions() */ public class SurvivorAccessor { private PrintStream tty = System.out; public List caculateHistogram(int excactAge, int minAge) { HashMap classStatsMap = new HashMap<>(2048, 0.2f); CollectedHeap heap = HeapUtils.getHeap(); ObjectHeap objectHeap = HeapUtils.getObjectHeap(); // 获取Survivor区边界 Address fromBottom = null; Address fromTop = null; if (HeapUtils.isCMSGC(heap)) { DefNewGeneration youngGen = HeapUtils.getYoungGenForCMS(heap); ContiguousSpace from = youngGen.from(); fromBottom = from.bottom(); fromTop = from.top(); from.printOn(tty); tty.println(""); } else if (HeapUtils.isParallelGC(heap)) { PSYoungGen psYoung = HeapUtils.getYongGenForPar(heap); MutableSpace from = psYoung.fromSpace(); fromBottom = from.bottom(); fromTop = from.top(); from.printOn(tty); tty.println(""); } else { throw new IllegalArgumentException( "Only support CMS and Parallel GC. Unsupport heap:" + heap.getClass().getName()); } // 记录分年龄统计 long[] ageSize = new long[50]; int[] ageCount = new int[50]; int maxAge = 1; // 遍历Survivor区 OopHandle handle = fromBottom.addOffsetToAsOopHandle(0); while (handle.lessThan(fromTop)) { Oop obj = null; try { obj = objectHeap.newOop(handle); } catch (UnknownOopException ex) { // ok } if (obj == null) { throw new UnknownOopException(); } long objectSize = obj.getObjectSize(); // handle指针指向下一个对象,后面的处理如果失败,直接进入下一个循环 handle = handle.addOffsetToAsOopHandle(objectSize); Klass klass = obj.getKlass(); if (klass == null) { continue; } int age = obj.getMark().age(); ageCount[age]++; ageSize[age] += objectSize; if (age > maxAge) { maxAge = age; } // 如果设定了精确匹配age if (excactAge != -1) { if (age != excactAge) { continue; } } else if (age < minAge) { // 否则判断age>=minAge continue; } ClassStats stats = HeapUtils.getClassStats(klass, classStatsMap); stats.survivorCount++; stats.survivorSize += objectSize; } tty.printf("%n#age #count #bytes%n"); for (int i = 1; i <= maxAge; i++) { tty.printf("%3d: %9d %7s%n", i, ageCount[i], FormatUtils.toFloatUnit(ageSize[i])); } return HeapUtils.getClassStatsList(classStatsMap); } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/utils/FormatUtils.java ================================================ package com.vip.vjtools.vjmap.utils; public class FormatUtils { private static final long BYTE_UNIT_KILO = 1024; private static final long BYTE_UNIT_MEGA = BYTE_UNIT_KILO * 1024; private static final long BYTE_UNIT_GIGA = BYTE_UNIT_MEGA * 1024; private static final long BYTE_UNIT_TERA = BYTE_UNIT_GIGA * 1024; /** * 转换成带单位的字符串,转换时保留一位小数 */ public static String toFloatUnit(long size) { if (size < BYTE_UNIT_KILO) { return String.format("%5d", size); } if (size < BYTE_UNIT_MEGA) { return String.format("%5.1fk", size / (1d * BYTE_UNIT_KILO)); } if (size < BYTE_UNIT_GIGA) { return String.format("%5.1fm", size / (1d * BYTE_UNIT_MEGA)); } if (size < BYTE_UNIT_TERA) { return String.format("%5.1fg", size / (1d * BYTE_UNIT_GIGA)); } return String.format("%5.1ft", size / (1d * BYTE_UNIT_TERA)); } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/utils/ProgressNotifier.java ================================================ package com.vip.vjtools.vjmap.utils; import java.io.PrintStream; public class ProgressNotifier { public long nextNotificationSize; public long processingSize; private int processingPercent; private long onePercentSize; private long totalSize; private PrintStream tty = System.out; private TimeController timeController = new TimeController(); public ProgressNotifier(long totalSize) { this.totalSize = totalSize; onePercentSize = totalSize / 100; nextNotificationSize = onePercentSize; processingPercent = 0; processingSize = 0; } public void printHead() { tty.println("Total live size to process: " + FormatUtils.toFloatUnit(totalSize)); tty.print(" 0%:"); } public void printProgress() { timeController.checkTimedOut(); tty.print("."); processingPercent++; nextNotificationSize += onePercentSize; if (processingPercent % 10 == 0) { tty.print("\n" + processingPercent + "%:"); } } } ================================================ FILE: vjmap/src/main/java/com/vip/vjtools/vjmap/utils/TimeController.java ================================================ package com.vip.vjtools.vjmap.utils; /** * 最多等待15分钟,超时后打印当前结果并退出,建议用户使用live参数 */ public class TimeController { long start = System.currentTimeMillis(); // 15 minutes long maxTime = Long.parseLong(System.getProperty("vjmap.timeout", String.valueOf(60 * 1000 * 15))); public void checkTimedOut() { if (System.currentTimeMillis() - start > maxTime) { throw new TimeoutException(); } } public static class TimeoutException extends RuntimeException { } } ================================================ FILE: vjmxcli/README.md ================================================ # 1. 概述 在cmdline-jmxclient项目上定制,增加功能 * 支持以pid接入JVM,不需要原JVM在启动参数中打开了JMX选项 * 完全模拟`jstat -gcutil`输出的`gcutil`,用于jstat不能使用的情况, 或者jstat计算使用百分比时,用“已申请大小”,而不是“Max大小”作为分母,不能反映内存是否真正不足的情况。 因为每调度一次`java -jar vjmxclient.jar`,其实是创建了一个新的JVM,因此在vjmxcli.sh 加上了一系列JVM参数减少消耗。 [Download vjmxcli-1.0.8.zip](http://repo1.maven.org/maven2/com/vip/vjtools/vjmxcli/1.0.8/vjmxcli-1.0.8.zip) 必须与目标JVM使用相同的JDK版本运行。 # 2. 获取MBean属性值 ``` // 以host:port接入 ./vjmxcli.sh - 127.0.0.1:8060 java.lang:type=Memory HeapMemoryUsage // 以pid接入 ./vjmxcli.sh - 98583 java.lang:type=Memory HeapMemoryUsage ``` 参数解释 * `-` : 无密码 * `127.0.0.1:8060` or `98582` : 应用地址及jmx端口, 或pid * `java.lang:type=Memory`:MBean名 * `HeapMemoryUsage`:Attribute名 # 3. 模拟并改进jstat gcutil输出 jstat有时候会不可使用,比如目标JVM使用-Djava.tmp.dir 重定义了临时目录,或者使用了-XX:+PerfDisableSharedMem禁止了perfdata。此时,可以用vjmxcli代替jstat。 另一种情况,jstat中的分母是已申请的内存,而不是允许的最大内存,因此如果按此百分比进行内存不足的告警,会造成大量误报,比如该区当前使用了95M内存,当前申请内存是100M,而最大内存其实是1G的情况,就不应该促发该区内存使用量超过了90%的告警。 因此vjmxCli的算法是,如果有设置该区内存的最大值,使用最大值做分母,没有设置时才使用该区已申请内存。 为什么jstat不是这样算呢?因为如果Max未设置时,从JMX会返回-1, 而PerfData则会返回一个很没准的大值,因此只读PerfData的jstat完全无法使用Max值做计算。 ``` //一次性输出 ./vjmxcli.sh - 127.0.0.1:7001 gcutil //间隔5秒连续输出 ./vjmxcli.sh - 127.0.0.1:7001 gcutil 5 // 以pid连入,间隔5秒连续输出 ./vjmxcli.sh - 98583 gcutil 5 ``` JDK7 示例输出 ``` S S E O P YGC YGCT FGC FGCT GCT 41.25 41.25 2.25 0.00 0.48 2 0.025 0 0.0 0.025 ``` JDK8 示例输出 ``` S S E O M CCS YGC YGCT FGC FGCT GCT 41.25 41.25 2.25 0.00 0.48 0 2 0.025 0 0.0 0.025 ``` # 4. 附录 ## 4.1 常用JMX条目 | 条目 | Object Name | Attribute Name| | -------- | -------- | -------- | | 堆内存 | java.lang:type=Memory | HeapMemoryUsage | | 非堆内存(不包含堆外内存) | java.lang:type=Memory | NonHeapMemoryUsage | | 堆外内存(不包含新版Netty申请的堆外内存) | java.nio:type=BufferPool,name=direct |MemoryUsed | | 线程数 | java.lang:type=Threading | ThreadCount | | 守护线程数 | java.lang:type=Threading | DaemonThreadCount | | 分代内存及GC | 不同JDK的值不一样 | 不同JDK的值不一样 | ## 4.2 启用JMX的启动参数 ``` -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 ``` 以pid连入时不需要预先定义上述参数 ================================================ FILE: vjmxcli/pom.xml ================================================ 4.0.0 jar com.vip.vjtools vjmxcli vjmxcli 1.0.9-SNAPSHOT jmx command line client ${java.home}/../lib/tools.jar UTF-8 1.7 ${java.version} ${java.version} org.apache.maven.plugins maven-jar-plugin true com.vip.vjtools.jmx.Client org.apache.maven.plugins maven-assembly-plugin 2.6 src/main/assembly/distribution.xml false assembly package single release org.apache.maven.plugins maven-javadoc-plugin 2.10.4 false attach-javadocs jar org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar org.apache.maven.plugins maven-release-plugin 2.5.3 v.@{project.version} org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign jdk9 [1.9,) default-jdk (,1.8] com.sun tools ${java.version} system ${toolsjar} sonatype-nexus-snapshots https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-releases https://oss.sonatype.org/service/local/staging/deploy/maven2 https://github.com/vipshop/vjtools Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:https://github.com/vipshop/vjtools.git scm:git:https://github.com/vipshop/vjtools.git https://github.com/vipshop/vjtools calvin Calvin Xiao calvin.xiao at vipshop.com developer +8 ================================================ FILE: vjmxcli/src/main/assembly/distribution.xml ================================================ zip ${project.artifactId} zip / com.vip.vjtools:vjmxcli:* ${artifact.artifactId}.${artifact.extension} src/main/assembly/vjmxcli.sh vjmxcli.sh 0755 true unix src/main/assembly/vjmxcli.bat windows README.md README.md unix ================================================ FILE: vjmxcli/src/main/assembly/vjmxcli.bat ================================================ @echo off rem check java if "%JAVA_HOME%" == "" goto noJavaHome set DIR=%~dp0 set JAVA_OPTS=-Xms96m -Xmx96m -Xmn64m -Xss256k -XX:+UseSerialGC -Djava.compiler=NONE -Xverify:none -XX:AutoBoxCacheMax=20000 "%JAVA_HOME%\bin\java" %JAVA_OPTS% -cp "%DIR%/vjmxcli.jar;%JAVA_HOME%/lib/tools.jar" com.vip.vjtools.jmx.Client %* goto end :noJavaHome echo Please set JAVA_HOME before running this script goto end :end pause ================================================ FILE: vjmxcli/src/main/assembly/vjmxcli.sh ================================================ #!/bin/sh if [ -z "$JAVA_HOME" ] ; then echo "JAVA_HOME env doesn't exist, try to find the location of java" JAVA_HOME=`readlink -f \`which java 2>/dev/null\` 2>/dev/null | \ sed 's/\jre\/bin\/java//' | sed 's/\/bin\/java//'` fi if [ ! -d "$JAVA_HOME" ] ; then echo "Please set JAVA_HOME env before run this script" exit 1 fi # returns the JDK version. # 8 for 1.8.0_nn, 9 for 9-ea etc, and "no_java" for undetected GET_JDK_VERSION() { local result local java_cmd if [[ -n $(type -p java) ]] then java_cmd=java elif [[ (-n "$JAVA_HOME") && (-x "$JAVA_HOME/bin/java") ]] then java_cmd="$JAVA_HOME/bin/java" fi local IFS=$'\n' # remove \r for Cygwin local lines=$("$java_cmd" -Xms32M -Xmx32M -version 2>&1 | tr '\r' '\n') if [[ -z $java_cmd ]] then result=no_java else for line in $lines; do if [[ (-z $result) && ($line = *"version \""*) ]] then local ver=$(echo $line | sed -e 's/.*version "\(.*\)"\(.*\)/\1/; 1q') # on macOS, sed doesn't support '?' if [[ $ver = "1."* ]] then result=$(echo $ver | sed -e 's/1\.\([0-9]*\)\(.*\)/\1/; 1q') else result=$(echo $ver | sed -e 's/\([0-9]*\)\(.*\)/\1/; 1q') fi fi done fi echo "$result" } JDK_VERSION=$(GET_JDK_VERSION) echo "JDK_VERSION : $JDK_VERSION" # jdk 8 and before if [[ $JDK_VERSION -le 8 ]]; then TOOLSJAR="$JAVA_HOME/lib/tools.jar" if [ ! -f "$TOOLSJAR" ] ; then echo "$TOOLSJAR doesn't exist" >&2 exit 1 fi JAVA_OPTS="-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -Xverify:none -XX:AutoBoxCacheMax=20000" else # jdk 9 or later JAVA_OPTS="-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -XX:AutoBoxCacheMax=20000" fi DIR=$( cd $(dirname $0) ; pwd -P ) "$JAVA_HOME"/bin/java $JAVA_OPTS -cp "$DIR/vjmxcli.jar:$TOOLSJAR" com.vip.vjtools.jmx.Client $* ================================================ FILE: vjmxcli/src/main/java/com/vip/vjtools/jmx/Client.java ================================================ package com.vip.vjtools.jmx; import java.io.File; /* * Client * * $Id$ * * Created on Nov 12, 2004 * * Copyright (C) 2004 Internet Archive. * * This file is part of the Heritrix web crawler (crawler.archive.org). * * Heritrix is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser Public License * as published by the Free Software Foundation; either version 2.1 of the License, or any later version. * * Heritrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser Public License for more details. * * You should have received a copy of the GNU Lesser Public License along with Heritrix; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.text.FieldPosition; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanFeatureInfo; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.openmbean.CompositeData; import javax.management.openmbean.TabularData; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine; /** * A Simple Command-Line JMX Client. Tested against the JDK 1.5.0 JMX Agent. See *
Monitoring and Management Using JMX. *

* Can supply credentials and do primitive string representation of tabular and composite openmbeans. * @author stack */ public class Client { private static final Logger logger = Logger.getLogger(Client.class.getName()); public static final String V_GCUTIL_BEAN_NAME = "gcutil"; private static final String LOCAL_CONNECTOR_ADDRESS_PROP = "com.sun.management.jmxremote.localConnectorAddress"; /** * Usage string. */ private static final String USAGE = "See README.md"; /** * Pattern that matches a command name followed by an optional equals and optional comma-delimited list of * arguments. */ protected static final Pattern CMD_LINE_ARGS_PATTERN = Pattern.compile("^([^=]+)(?:(?:\\=)(.+))?$"); private static final String CREATE_CMD_PREFIX = "create="; public static void main(String[] args) { Client client = new Client(); // Set the logger to use our all-on-one-line formatter. Logger l = Logger.getLogger(""); Handler[] hs = l.getHandlers(); for (int i = 0; i < hs.length; i++) { Handler h = hs[0]; if (h instanceof ConsoleHandler) { h.setFormatter(client.new OneLineSimpleLogger()); } } try { client.execute(args); } catch (Exception e) { e.printStackTrace(); } } protected static void usage() { usage(0, null); } protected static void usage(int exitCode, String message) { if (message != null && message.length() > 0) { System.out.println(message); } System.out.println(USAGE); System.exit(exitCode); } /** * Constructor. */ public Client() { super(); } /** * Parse a 'login:password' string. Assumption is that no colon in the login name. * @param userpass * @return Array of strings with login in first position. */ protected String[] parseUserpass(final String userpass) { if (userpass == null || userpass.equals("-")) { return null; } int index = userpass.indexOf(':'); if (index <= 0) { throw new RuntimeException("Unable to parse: " + userpass); } return new String[]{userpass.substring(0, index), userpass.substring(index + 1)}; } /** * @param login * @param password * @return Credentials as map for RMI. */ protected static Map formatCredentials(final String login, final String password) { Map env = null; String[] creds = new String[]{login, password}; env = new HashMap(1); env.put(JMXConnector.CREDENTIALS, creds); return env; } /** * 扩展支持以pid or host-port两种方式接入 */ public static JMXConnector connect(final String hostportOrPid, final String login, final String password) throws IOException { // ./vjmxcli.sh - 127.0.0.1:8060 gcutil if (hostportOrPid.contains(":")) { JMXServiceURL rmiurl = new JMXServiceURL( "service:jmx:rmi://" + hostportOrPid + "/jndi/rmi://" + hostportOrPid + "/jmxrmi"); return JMXConnectorFactory.connect(rmiurl, formatCredentials(login, password)); } else { // ./vjmxcli.sh - 112222 gcutil String localAddress = getLocalConnectorAddress(hostportOrPid); JMXServiceURL localRmiurl = new JMXServiceURL(localAddress); return JMXConnectorFactory.connect(localRmiurl); } } /** * VirtualMachine保证JMX Agent已启动, 并向JMXClient提供连接地址 * * 地址样例:service:jmx:rmi://127.0.0.1/stub/rO0ABXN9AAAAAQAl... */ public static String getLocalConnectorAddress(String pid) throws IOException {// NOSONAR VirtualMachine vm = null; // 1. attach vm try { vm = VirtualMachine.attach(pid); } catch (AttachNotSupportedException x) { IOException ioe = new IOException(x.getMessage()); ioe.initCause(x); throw ioe; } try { // 2. 检查smartAgent是否已启动 Properties agentProps = vm.getAgentProperties(); String address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); if (address != null) { return address; } // 3. 未启动,尝试启动 int version = getJavaMajorVersion(vm.getSystemProperties().getProperty("java.specification.version")); if (version >= 8) { vm.startLocalManagementAgent(); agentProps = vm.getAgentProperties(); address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); } else { // JDK8后有更直接的vm.startLocalManagementAgent()方法 String home = vm.getSystemProperties().getProperty("java.home"); // Normally in ${java.home}/jre/lib/management-agent.jar but might // be in ${java.home}/lib in build environments. String agentPath = home + File.separator + "jre" + File.separator + "lib" + File.separator + "management-agent.jar"; File f = new File(agentPath); if (!f.exists()) { agentPath = home + File.separator + "lib" + File.separator + "management-agent.jar"; f = new File(agentPath); if (!f.exists()) { throw new IOException("Management agent not found"); } } agentPath = f.getCanonicalPath(); try { vm.loadAgent(agentPath, "com.sun.management.jmxremote"); } catch (AgentLoadException x) { // 高版本 attach 低版本jdk 抛异常:com.sun.tools.attach.AgentLoadException: 0,实际上是成功的; // 根因: HotSpotVirtualMachine.loadAgentLibrary 高版本jdk实现不一样了 if (!"0".equals(x.getMessage())) { IOException ioe = new IOException(x.getMessage()); ioe.initCause(x); throw ioe; } } catch (AgentInitializationException x) { IOException ioe = new IOException(x.getMessage()); ioe.initCause(x); throw ioe; } // 4. 再次获取connector address agentProps = vm.getAgentProperties(); address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); } if (address == null) { throw new IOException("Fails to find connector address"); } return address; } finally { vm.detach(); } } /** * Version of execute called from the cmdline. Prints out result of execution on stdout. Parses cmdline args. Then * calls {@link #execute(String, String, String, String, String[], boolean)}. * @param args Cmdline args. * @throws Exception */ protected void execute(final String[] args) throws Exception { // Process command-line. if (args.length == 0 || args.length == 1) { usage(); } String userpass = args[0]; String hostportOrPid = args[1]; String beanname = null;// vGCutil String[] command = null; if (args.length > 2) { beanname = args[2]; } if (args.length > 3) { command = new String[args.length - 3]; for (int i = 3; i < args.length; i++) { command[i - 3] = args[i]; } } String[] loginPassword = parseUserpass(userpass); // 模拟GC Util命令的扩展 if (V_GCUTIL_BEAN_NAME.equalsIgnoreCase(beanname)) { // 支持配置interval 固定事件间隔连续输出 int interval = 0; if (command != null && command.length > 0) { try { interval = Math.abs(Integer.parseInt(command[0]));// 拿绝对值, 避免负数的情况 } catch (NumberFormatException e) {// NOSONAR } } ExtraCommand extraCommand = new ExtraCommand(); extraCommand.execute(hostportOrPid, ((loginPassword == null) ? null : loginPassword[0]), ((loginPassword == null) ? null : loginPassword[1]), beanname, interval); return; } Object[] result = execute(hostportOrPid, ((loginPassword == null) ? null : loginPassword[0]), ((loginPassword == null) ? null : loginPassword[1]), beanname, command); if (result != null) { for (int i = 0; i < result.length; i++) { if (result[i] != null && result[i].toString().length() > 0) { if (command != null) { logger.info(command[i] + ": " + result[i]); } else { logger.info("\n" + result[i].toString()); } } } } } protected Object[] execute(final String hostport, final String login, final String password, final String beanname, final String[] command) throws Exception { return execute(hostport, login, password, beanname, command, false); } public Object[] executeOneCmd(final String hostport, final String login, final String password, final String beanname, final String command) throws Exception { return execute(hostport, login, password, beanname, new String[]{command}, true); } /** * Execute command against remote JMX agent. * @param hostportOrPid 'host:port' combination. * @param login RMI login to use. * @param password RMI password to use. * @param beanname Name of remote bean to run command against. * @param command Array of commands to run. * @param oneBeanOnly Set true if passed beanname is an exact name and the query for a bean is only * supposed to return one bean instance. If not, we raise an exception (Otherwise, if false, then we deal with * possibility of multiple bean instances coming back from query). Set to true when want to get an attribute or run * an operation. * @return Array of results -- one per command. * @throws Exception */ protected Object[] execute(final String hostportOrPid, final String login, final String password, final String beanname, String[] command, final boolean oneBeanOnly) throws Exception { JMXConnector jmxc = connect(hostportOrPid, login, password); Object[] result = null; try { MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); result = doBeans(mbsc, getObjectName(beanname), command, oneBeanOnly); } finally { jmxc.close(); } return result; } public static ObjectName getObjectName(final String beanname) throws MalformedObjectNameException, NullPointerException { return notEmpty(beanname) ? new ObjectName(beanname) : null; } public static boolean notEmpty(String s) { return s != null && s.length() > 0; } protected static Object[] doBeans(final MBeanServerConnection mbsc, final ObjectName objName, final String[] command, final boolean oneBeanOnly) throws Exception { Object[] result = null; Set beans = mbsc.queryMBeans(objName, null); if (beans.isEmpty()) { // No bean found. Check if we are to create a bean? if (command.length == 1 && notEmpty(command[0]) && command[0].startsWith(CREATE_CMD_PREFIX)) { String className = command[0].substring(CREATE_CMD_PREFIX.length()); mbsc.createMBean(className, objName); } else { // TODO: Is there a better JMX exception that RE for this // scenario? throw new RuntimeException(objName.getCanonicalName() + " not registered."); } } else if (beans.size() == 1) { result = doBean(mbsc, (ObjectInstance) beans.iterator().next(), command); } else { if (oneBeanOnly) { throw new RuntimeException("Only supposed to be one bean " + "query result"); } // This is case of multiple beans in query results. // Print name of each into a StringBuffer. Return as one // result. StringBuffer buffer = new StringBuffer(); for (Iterator i = beans.iterator(); i.hasNext();) { Object obj = i.next(); if (obj instanceof ObjectName) { buffer.append((((ObjectName) obj).getCanonicalName())); } else if (obj instanceof ObjectInstance) { buffer.append((((ObjectInstance) obj).getObjectName().getCanonicalName())); } else { throw new RuntimeException("Unexpected object type: " + obj); } buffer.append("\n"); } result = new String[]{buffer.toString()}; } return result; } /** * Get attribute or run operation against passed bean instance. * * @param mbsc Server connection. * @param instance Bean instance we're to get attributes from or run operation against. * @param command Command to run (May be null). * @return Result. If multiple commands, multiple results. * @throws Exception */ protected static Object[] doBean(MBeanServerConnection mbsc, ObjectInstance instance, String[] command) throws Exception { // If no command, then print out list of attributes and operations. if (command == null || command.length <= 0) { return new String[]{listOptions(mbsc, instance)}; } // Maybe multiple attributes/operations listed on one command line. Object[] result = new Object[command.length]; for (int i = 0; i < command.length; i++) { result[i] = doSubCommand(mbsc, instance, command[i]); } return result; } public static Object doSubCommand(MBeanServerConnection mbsc, ObjectInstance instance, String subCommand) throws Exception { // First, handle special case of our being asked to destroy a bean. if (subCommand.equals("destroy")) { mbsc.unregisterMBean(instance.getObjectName()); return null; } else if (subCommand.startsWith(CREATE_CMD_PREFIX)) { throw new IllegalArgumentException("You cannot call create " + "on an already existing bean."); } // Get attribute and operation info. MBeanAttributeInfo[] attributeInfo = mbsc.getMBeanInfo(instance.getObjectName()).getAttributes(); MBeanOperationInfo[] operationInfo = mbsc.getMBeanInfo(instance.getObjectName()).getOperations(); // Now, bdbje JMX bean doesn't follow the convention of attributes // having uppercase first letter and operations having lowercase // first letter. But most beans do. Be prepared to handle the bdbje // case. Object result = null; if (Character.isUpperCase(subCommand.charAt(0))) { // Probably an attribute. if (!isFeatureInfo(attributeInfo, subCommand) && isFeatureInfo(operationInfo, subCommand)) { // Its not an attribute name. Looks like its name of an // operation. Try it. result = doBeanOperation(mbsc, instance, subCommand, operationInfo); } else { // Then it is an attribute OR its not an attribute name nor // operation name and the below invocation will throw a // AttributeNotFoundException. result = doAttributeOperation(mbsc, instance, subCommand, attributeInfo); } } else { // Must be an operation. if (!isFeatureInfo(operationInfo, subCommand) && isFeatureInfo(attributeInfo, subCommand)) { // Its not an operation name but looks like it could be an // attribute name. Try it. result = doAttributeOperation(mbsc, instance, subCommand, attributeInfo); } else { // Its an operation name OR its neither operation nor attribute // name and the below will throw a NoSuchMethodException. result = doBeanOperation(mbsc, instance, subCommand, operationInfo); } } // Look at the result. Is it of composite or tabular type? // If so, convert to a String representation. if (result instanceof CompositeData) { result = recurseCompositeData(new StringBuffer("\n"), "", "", (CompositeData) result); } else if (result instanceof TabularData) { result = recurseTabularData(new StringBuffer("\n"), "", "", (TabularData) result); } else if (result instanceof String[]) { String[] strs = (String[]) result; StringBuffer buffer = new StringBuffer("\n"); for (int i = 0; i < strs.length; i++) { buffer.append(strs[i]); buffer.append("\n"); } result = buffer; } else if (result instanceof AttributeList) { AttributeList list = (AttributeList) result; if (list.isEmpty()) { result = null; } else { StringBuffer buffer = new StringBuffer("\n"); for (Iterator ii = list.iterator(); ii.hasNext();) { Attribute a = (Attribute) ii.next(); buffer.append(a.getName()); buffer.append(": "); buffer.append(a.getValue()); buffer.append("\n"); } result = buffer; } } return result; } protected static boolean isFeatureInfo(MBeanFeatureInfo[] infos, String cmd) { return getFeatureInfo(infos, cmd) != null; } protected static MBeanFeatureInfo getFeatureInfo(MBeanFeatureInfo[] infos, String cmd) { // Cmd may be carrying arguments. Don't count them in the compare. int index = cmd.indexOf('='); String name = (index > 0) ? cmd.substring(0, index) : cmd; for (int i = 0; i < infos.length; i++) { if (infos[i].getName().equals(name)) { return infos[i]; } } return null; } protected static StringBuffer recurseTabularData(StringBuffer buffer, String indent, String name, TabularData data) { addNameToBuffer(buffer, indent, name); java.util.Collection c = data.values(); for (Iterator i = c.iterator(); i.hasNext();) { Object obj = i.next(); if (obj instanceof CompositeData) { recurseCompositeData(buffer, indent + " ", "", (CompositeData) obj); } else if (obj instanceof TabularData) { recurseTabularData(buffer, indent, "", (TabularData) obj); } else { buffer.append(obj); } } return buffer; } protected static StringBuffer recurseCompositeData(StringBuffer buffer, String indent, String name, CompositeData data) { indent = addNameToBuffer(buffer, indent, name); for (Iterator i = data.getCompositeType().keySet().iterator(); i.hasNext();) { String key = (String) i.next(); Object o = data.get(key); if (o instanceof CompositeData) { recurseCompositeData(buffer, indent + " ", key, (CompositeData) o); } else if (o instanceof TabularData) { recurseTabularData(buffer, indent, key, (TabularData) o); } else { buffer.append(indent); buffer.append(key); buffer.append(": "); buffer.append(o); buffer.append("\n"); } } return buffer; } protected static String addNameToBuffer(StringBuffer buffer, String indent, String name) { if (name == null || name.length() == 0) { return indent; } buffer.append(indent); buffer.append(name); buffer.append(":\n"); // Move all that comes under this 'name' over by one space. return indent + " "; } /** * Class that parses commandline arguments. Expected format is 'operationName=arg0,arg1,arg2...'. We are assuming no * spaces nor comma's in argument values. */ protected static class CommandParse { private String cmd; private String[] args; protected CommandParse(String command) throws ParseException { parse(command); } private void parse(String command) throws ParseException { Matcher m = CMD_LINE_ARGS_PATTERN.matcher(command); if (m == null || !m.matches()) { throw new ParseException("Failed parse of " + command, 0); } this.cmd = m.group(1); if (m.group(2) != null && m.group(2).length() > 0) { this.args = m.group(2).split(","); } else { this.args = null; } } protected String getCmd() { return this.cmd; } protected String[] getArgs() { return this.args; } } protected static Object doAttributeOperation(MBeanServerConnection mbsc, ObjectInstance instance, String command, MBeanAttributeInfo[] infos) throws Exception { // Usually we get attributes. If an argument, then we're being asked // to set attribute. CommandParse parse = new CommandParse(command); if (parse.getArgs() == null || parse.getArgs().length == 0) { // Special-casing. If the subCommand is 'Attributes', then return // list of all attributes. if (command.equals("Attributes")) { String[] names = new String[infos.length]; for (int i = 0; i < infos.length; i++) { names[i] = infos[i].getName(); } return mbsc.getAttributes(instance.getObjectName(), names); } return mbsc.getAttribute(instance.getObjectName(), parse.getCmd()); } if (parse.getArgs().length != 1) { throw new IllegalArgumentException("One only argument setting " + "attribute values: " + parse.getArgs()); } // Get first attribute of name 'cmd'. Assumption is no method // overrides. Then, look at the attribute and use its type. MBeanAttributeInfo info = (MBeanAttributeInfo) getFeatureInfo(infos, parse.getCmd()); java.lang.reflect.Constructor c = Class.forName(info.getType()).getConstructor(new Class[]{String.class}); Attribute a = new Attribute(parse.getCmd(), c.newInstance(new Object[]{parse.getArgs()[0]})); mbsc.setAttribute(instance.getObjectName(), a); return null; } protected static Object doBeanOperation(MBeanServerConnection mbsc, ObjectInstance instance, String command, MBeanOperationInfo[] infos) throws Exception { // Parse command line. CommandParse parse = new CommandParse(command); // Get first method of name 'cmd'. Assumption is no method // overrides. Then, look at the method and use its signature // to make sure client sends over parameters of the correct type. MBeanOperationInfo op = (MBeanOperationInfo) getFeatureInfo(infos, parse.getCmd()); Object result = null; if (op == null) { result = "Operation " + parse.getCmd() + " not found."; } else { MBeanParameterInfo[] paraminfos = op.getSignature(); int paraminfosLength = (paraminfos == null) ? 0 : paraminfos.length; int objsLength = (parse.getArgs() == null) ? 0 : parse.getArgs().length; if (paraminfosLength != objsLength) { result = "Passed param count does not match signature count"; } else { String[] signature = new String[paraminfosLength]; Object[] params = (paraminfosLength == 0) ? null : new Object[paraminfosLength]; for (int i = 0; i < paraminfosLength; i++) { MBeanParameterInfo paraminfo = paraminfos[i]; java.lang.reflect.Constructor c = Class.forName(paraminfo.getType()) .getConstructor(new Class[]{String.class}); params[i] = c.newInstance(new Object[]{parse.getArgs()[i]}); signature[i] = paraminfo.getType(); } result = mbsc.invoke(instance.getObjectName(), parse.getCmd(), params, signature); } } return result; } protected static String listOptions(MBeanServerConnection mbsc, ObjectInstance instance) throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException { StringBuffer result = new StringBuffer(); MBeanInfo info = mbsc.getMBeanInfo(instance.getObjectName()); MBeanAttributeInfo[] attributes = info.getAttributes(); if (attributes.length > 0) { result.append("Attributes:"); result.append("\n"); for (int i = 0; i < attributes.length; i++) { result.append(' ' + attributes[i].getName() + ": " + attributes[i].getDescription() + " (type=" + attributes[i].getType() + ")"); result.append("\n"); } } MBeanOperationInfo[] operations = info.getOperations(); if (operations.length > 0) { result.append("Operations:"); result.append("\n"); for (int i = 0; i < operations.length; i++) { MBeanParameterInfo[] params = operations[i].getSignature(); StringBuffer paramsStrBuffer = new StringBuffer(); if (params != null) { for (int j = 0; j < params.length; j++) { paramsStrBuffer.append("\n name="); paramsStrBuffer.append(params[j].getName()); paramsStrBuffer.append(" type="); paramsStrBuffer.append(params[j].getType()); paramsStrBuffer.append(" "); paramsStrBuffer.append(params[j].getDescription()); } } result.append(' ' + operations[i].getName() + ": " + operations[i].getDescription() + "\n Parameters " + params != null ? params.length : 0 + ", return type=" + operations[i].getReturnType() + paramsStrBuffer.toString()); result.append("\n"); } } return result.toString(); } /** * Logger that writes entry on one line with less verbose date. Modelled on the OneLineSimpleLogger from Heritrix. * * @author stack * @version $Revision$, $Date$ */ private class OneLineSimpleLogger extends SimpleFormatter { /** * Date instance. * * Keep around instance of date. */ private Date date = new Date(); /** * Field position instance. * * Keep around this instance. */ private FieldPosition position = new FieldPosition(0); /** * MessageFormatter for date. */ private SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss Z"); /** * Persistent buffer in which we conjure the log. */ private StringBuffer buffer = new StringBuffer(); public OneLineSimpleLogger() { super(); } @Override public synchronized String format(LogRecord record) { this.buffer.setLength(0); this.date.setTime(record.getMillis()); this.position.setBeginIndex(0); this.formatter.format(this.date, this.buffer, this.position); this.buffer.append(' '); if (record.getSourceClassName() != null) { this.buffer.append(record.getSourceClassName()); } else { this.buffer.append(record.getLoggerName()); } this.buffer.append(' '); this.buffer.append(formatMessage(record)); this.buffer.append(System.getProperty("line.separator")); if (record.getThrown() != null) { try { StringWriter writer = new StringWriter(); PrintWriter printer = new PrintWriter(writer); record.getThrown().printStackTrace(printer); writer.close(); this.buffer.append(writer.toString()); } catch (Exception e) { this.buffer.append("Failed to get stack trace: " + e.getMessage()); } } return this.buffer.toString(); } } private static int getJavaMajorVersion(String javaSpecificationVersion) { if (javaSpecificationVersion.startsWith("1.8")) { return 8; } else if (javaSpecificationVersion.startsWith("1.7")) { return 7; } else if (javaSpecificationVersion.startsWith("1.6")) { return 6; } else { try { return Integer.parseInt(javaSpecificationVersion); } catch (NumberFormatException e) { return 0; } } } } ================================================ FILE: vjmxcli/src/main/java/com/vip/vjtools/jmx/ExtraCommand.java ================================================ package com.vip.vjtools.jmx; import javax.management.MBeanServerConnection; import javax.management.remote.JMXConnector; public class ExtraCommand { public void execute(final String hostportOrPid, final String login, final String password, final String beanname, int interval) throws Exception { JMXConnector jmxc = Client.connect(hostportOrPid, login, password); try { MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); gcUtilCommand(mbsc, interval); } finally { jmxc.close(); } } private void gcUtilCommand(MBeanServerConnection mbsc, int interval) throws Exception { GCutilExpression gcE = new GCutilExpression(mbsc); String[] commands; if (getJavaVersion(mbsc) > 7) { commands = new String[]{"S", "S", "E", "O", "M", "CCS", "YGC", "YGCT", "FGC", "FGCT", "GCT"}; } else { commands = new String[]{"S", "S", "E", "O", "P", "YGC", "YGCT", "FGC", "FGCT", "GCT"}; } for (String commmand : commands) { System.out.print(commmand + "\t"); } System.out.print("\n"); while (true) { Object[] results = executGCutil(commands, gcE); for (Object result : results) { System.out.print(result.toString() + "\t"); } System.out.print("\n"); if (interval == 0) { break; } Thread.sleep(interval * 1000); } } private Object[] executGCutil(final String[] commands, GCutilExpression gcE) throws Exception { Object[] result = new Object[commands.length]; for (int i = 0; i < commands.length; i++) { String command = commands[i]; if ("S".equals(command)) { result[i] = gcE.getS(); } else if ("E".equals(command)) { result[i] = gcE.getE(); } else if ("O".equals(command)) { result[i] = gcE.getO(); } else if ("M".equals(command)) { result[i] = gcE.getP(); } else if ("P".equals(command)) { result[i] = gcE.getP(); } else if ("CCS".equals(command)) { result[i] = gcE.getCCS(); } else if ("YGC".equals(command)) { result[i] = gcE.getYGC(); } else if ("YGCT".equals(command)) { result[i] = gcE.getYGCT(); } else if ("FGC".equals(command)) { result[i] = gcE.getFGC(); } else if ("FGCT".equals(command)) { result[i] = gcE.getFGCT(); } else if ("GCT".equals(command)) { result[i] = gcE.getGCT(); } else { throw new RuntimeException("Unknown Command:" + command); } } return result; } public static int getJavaVersion(final MBeanServerConnection mbsc) throws Exception { Object version = mbsc.getAttribute(Client.getObjectName("java.lang:type=Runtime"), "SpecVersion"); String javaVersion = version.toString(); if (javaVersion.startsWith("1.8")) { return 8; } else if (javaVersion.startsWith("1.7")) { return 7; } else if (javaVersion.startsWith("1.6")) { return 6; } else { try { return Integer.parseInt(javaVersion); } catch (NumberFormatException e) { return 0; } } } } ================================================ FILE: vjmxcli/src/main/java/com/vip/vjtools/jmx/GCutilExpression.java ================================================ package com.vip.vjtools.jmx; import java.text.DecimalFormat; import java.util.Set; import javax.management.MBeanServerConnection; import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.openmbean.CompositeDataSupport; public class GCutilExpression { // MBean Name private static final String GARBAGE_COLLECTORS = "java.lang:type=GarbageCollector,name=*"; private static final String MEM_POOL_PREFIX = "java.lang:type=MemoryPool,name="; // Collector的Attribute Name private static final String COLLECTION_TIME_ATTRIBUTE = "CollectionTime"; private static final String COLLECTION_COUNT_ATTRIBUTE = "CollectionCount"; private static final DecimalFormat DF = new DecimalFormat("0.00"); private MBeanServerConnection mbsc; private ObjectName ygcCollector; private ObjectName fgcCollector; private ObjectName eden; private ObjectName old; private ObjectName sur; private ObjectName perm; private ObjectName ccs; public GCutilExpression(MBeanServerConnection mbsc) throws Exception { this.mbsc = mbsc; mappingCollctors(); mappingPools(); } private void mappingCollctors() throws Exception { Set beans = mbsc.queryMBeans(Client.getObjectName(GARBAGE_COLLECTORS), null); for (ObjectInstance collector : beans) { ObjectName collectorObjName = collector.getObjectName(); String collectorName = getAttribute(collectorObjName, "Name"); if ("Copy".equals(collectorName) || "PS Scavenge".equals(collectorName) || "ParNew".equals(collectorName) || "G1 Young Generation".equals(collectorName)) { ygcCollector = collectorObjName; } else if ("MarkSweepCompact".equals(collectorName) || "PS MarkSweep".equals(collectorName) || "ConcurrentMarkSweep".equals(collectorName) || "G1 Old Generation".equals(collectorName)) { fgcCollector = collectorObjName; } else { ygcCollector = collectorObjName; } } } private void mappingPools() throws Exception { Set beans = mbsc.queryMBeans(Client.getObjectName(MEM_POOL_PREFIX + "*"), null); for (ObjectInstance pool : beans) { ObjectName poolObjName = pool.getObjectName(); String poolName = getAttribute(poolObjName, "Name"); poolName = poolName.trim().toLowerCase(); if (poolName.contains("eden")) { eden = poolObjName; } else if (poolName.contains("survivor")) { sur = poolObjName; } else if (poolName.contains("old") || poolName.contains("tenured")) { old = poolObjName; } else if (poolName.contains("perm") || poolName.contains("metaspace")) { perm = poolObjName; } else if (poolName.contains("compressed class space")) { ccs = poolObjName; } } } public String getE() throws Exception { return usedPercentage(eden); } public String getS() throws Exception { return usedPercentage(sur); } public String getO() throws Exception { return usedPercentage(old); } public String getP() throws Exception { return usedPercentage(perm); } public String getCCS() throws Exception { return usedPercentage(ccs); } public Object getYGC() throws Exception { return getAttribute(ygcCollector, COLLECTION_COUNT_ATTRIBUTE); } public Double getYGCT() throws Exception { return Double.parseDouble(getAttribute(ygcCollector, COLLECTION_TIME_ATTRIBUTE).toString()) / 1000; } public Object getFGC() throws Exception { if (fgcCollector == null) { return 0; } return getAttribute(fgcCollector, COLLECTION_COUNT_ATTRIBUTE); } public Double getFGCT() throws Exception { if (fgcCollector == null) { return 0.0; } return Double.parseDouble(getAttribute(fgcCollector, COLLECTION_TIME_ATTRIBUTE).toString()) / 1000; } public Object getGCT() throws Exception { return getFGCT() + getYGCT(); } private T getAttribute(ObjectName beanName, String attributeName) throws Exception { if (beanName != null) { return (T) mbsc.getAttribute(beanName, attributeName); } else { return null; } } private String usedPercentage(ObjectName poolObjectName) throws Exception { if (poolObjectName == null) { return DF.format(0.0d); } CompositeDataSupport usage = getAttribute(poolObjectName, "Usage"); double max = Double.parseDouble(usage.get("max").toString()); // 如果Max没有设置或GC算法原因没有max,则以committed为准 if (max < 0) { max = Double.parseDouble(usage.get("committed").toString()); } if (max > 0.0d) { double used = Double.parseDouble(usage.get("used").toString()) / max * 100; return DF.format(used); } else { return DF.format(0.0d); } } } ================================================ FILE: vjstar/README.md ================================================ # 1. 概述 服务化应用的性能、可用性的最佳实践封装,主要体现思路。 # 2. 实践列表 ## 2.1 JVM启动参数 参数兼顾性能及排查问题的便捷性的JVM启动参数推荐, 其中一些参数需要根据JDK版本适配。 源码: [jvm-options](https://github.com/vipshop/vjtools/blob/master/vjstar/src/main/script/jvm-options) 解读:[《关键业务系统的JVM参数推荐》](http://calvin1978.blogcn.com/?p=1602) ## 2.2 容器中JVM获取CPU核数的通用补丁 容器中的JVM,获取的仍然是宿主机的CPU核数,从而引起GC线程数,Netty线程数等一系列混乱。据说JDK8的最新版解决了这个问题,但其他版本的JDK则建议使用此补丁。 基于[libsysconfcpus](https://github.com/obmarg/libsysconfcpus),详见[docker-cpus](https://github.com/vipshop/vjtools/blob/master/vjstar/src/main/script/docker-cpus)。 ## 2.3 闲时主动GC CMS GC 始终对流量有一定的影响。 因此我们希望在夜半闲时,如果检测到老生代已经达到50%, 则主动进行一次GC。 简单的定时器让应用固定在可设定的闲时(如半夜)进行清理动作。 为了避免服务的所有实例同时清理造成服务不可用,加入了随机值。 详见[Proactive GC](https://github.com/vipshop/vjtools/tree/master/vjstar/src/main/java/com/vip/vjstar/gc) ## 2.4 滑动窗口计数器(试验) 滑动窗口的计数器(比如任意时刻的最近一分钟请求数)在熔断计算等方面的使用很广泛,但没有比较标准且抽象成通用类库的实现,我们在考察了几家实现的实现。 详见[Sliding Window](https://github.com/vipshop/vjtools/tree/master/vjstar/src/main/java/com/vip/vjstar/window)(试验性的新方案,还没替换生产上的旧方案) ## 2.5 动态隔离线程池(TODO) 我们希望一个业务方法缓慢时,不会把整个线程池塞爆导致所有方法都不能响应。 但是为每个方法配置独立线程池又存在配置困难和浪费问题,因此我们希望简单实现一个线程池,在平时使用公共池,当某个方法出现问题时对其进行隔离,当问题消失时又自动恢复到公共池。 ================================================ FILE: vjstar/pom.xml ================================================ 4.0.0 com.vip.vjtools vjstar 1.0.9-SNAPSHOT jar vjstar VIP's best practice libraries 4.12 2.6.0 2.18.3 1.1.8 UTF-8 1.7 ${java.version} ${java.version} 1.6 com.vip.vjtools vjkit ${project.version} junit junit ${junit.version} test org.assertj assertj-core ${assertj.version} test org.mockito mockito-core ${mockito.version} test ch.qos.logback logback-classic ${logback.version} test release org.apache.maven.plugins maven-javadoc-plugin 2.10.4 attach-javadocs jar org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar org.apache.maven.plugins maven-gpg-plugin ${maven-gpg-plugin.version} sign-artifacts verify sign sonar 1.7 org.jacoco jacoco-maven-plugin 0.7.2.201409121644 true agent-for-ut prepare-agent org.sonarsource.scanner.maven sonar-maven-plugin 3.4.0.905 sonatype-nexus-snapshots https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-releases https://oss.sonatype.org/service/local/staging/deploy/maven2 https://github.com/vipshop/vjtools Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:https://github.com/vipshop/vjtools.git scm:git:https://github.com/vipshop/vjtools.git https://github.com/vipshop/vjtools calvin Calvin Xiao calvin.xiao at vipshop.com developer +8 dehuizheng Dehui Zheng dehui.zheng at vipshop.com developer +8 lixuanbin Xuanbin.Li ben04.li at vipshop.com developer +8 ================================================ FILE: vjstar/src/main/java/com/vip/vjstar/.gitkeep ================================================ ================================================ FILE: vjstar/src/main/java/com/vip/vjstar/gc/CleanUpScheduler.java ================================================ package com.vip.vjstar.gc; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.FastDateFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vip.vjtools.vjkit.base.ExceptionUtil; import com.vip.vjtools.vjkit.concurrent.threadpool.ThreadPoolUtil; import com.vip.vjtools.vjkit.number.RandomUtil; public class CleanUpScheduler { private static Logger logger = LoggerFactory.getLogger(CleanUpScheduler.class); private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThreadPoolUtil.buildThreadFactory("cleanup", true)); public void schedule(String schedulePlans, Runnable task) { List delayTimes = getDelayMillsList(schedulePlans); for (long delayTime : delayTimes) { scheduler.schedule(task, delayTime, TimeUnit.MILLISECONDS); } } public void reschedule(Runnable task) { if (!scheduler.isShutdown()) { try { scheduler.schedule(task, 24, TimeUnit.HOURS); } catch (Exception e) { logger.error(e.getMessage(), e); } } } public void shutdown(){ scheduler.shutdown(); } /** * Generate delay millis list by given plans string, separated by comma.
* eg, 03:00-05:00,13:00-14:00 */ public static List getDelayMillsList(String schedulePlans) { List result = new ArrayList<>(); String[] plans = StringUtils.split(schedulePlans, ','); for (String plan : plans) { result.add(getDelayMillis(plan)); } return result; } /** * Get scheduled delay for proactive gc task,cross-day setting is supported.
* 01:30-02:40,some time between 01:30-02:40;
* 180000,180 seconds later. */ public static long getDelayMillis(String time) { String pattern = "HH:mm"; Date now = new Date(); if (StringUtils.contains(time, "-")) { String start = time.split("-")[0]; String end = time.split("-")[1]; if (StringUtils.contains(start, ":") && StringUtils.contains(end, ":")) { Date d1 = getCurrentDateByPlan(start, pattern); Date d2 = getCurrentDateByPlan(end, pattern); while (d1.before(now)) { d1 = DateUtils.addDays(d1, 1); } while (d2.before(d1)) { d2 = DateUtils.addDays(d2, 1); } return RandomUtil.nextLong(d1.getTime() - now.getTime(), d2.getTime() - now.getTime()); } } else if (StringUtils.isNumeric(time)) { return Long.parseLong(time); } // default return getDelayMillis("02:00-05:00"); } /** * return current date time by specified hour:minute * * @param plan format: hh:mm */ public static Date getCurrentDateByPlan(String plan, String pattern) { try { FastDateFormat format = FastDateFormat.getInstance(pattern); Date end = format.parse(plan); Calendar today = Calendar.getInstance(); end = DateUtils.setYears(end, (today.get(Calendar.YEAR))); end = DateUtils.setMonths(end, today.get(Calendar.MONTH)); end = DateUtils.setDays(end, today.get(Calendar.DAY_OF_MONTH)); return end; } catch (Exception e) { throw ExceptionUtil.unchecked(e); } } } ================================================ FILE: vjstar/src/main/java/com/vip/vjstar/gc/ProactiveGcTask.java ================================================ package com.vip.vjstar.gc; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vip.vjtools.vjkit.number.UnitConverter; /** * Detect old gen usage of current jvm periodically and trigger a cms gc if necessary.
* In order to enable this feature, add these options to your target jvm:
* -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+ExplicitGCInvokesConcurrent
* You can alter this class to work on a remote jvm using jmx. */ public class ProactiveGcTask implements Runnable { private static Logger logger = LoggerFactory.getLogger(ProactiveGcTask.class); protected CleanUpScheduler scheduler; protected int oldGenOccupancyFraction; protected MemoryPoolMXBean oldGenMemoryPool; protected long maxOldGenBytes; protected boolean valid; public ProactiveGcTask(CleanUpScheduler scheduler, int oldGenOccupancyFraction) { this.scheduler = scheduler; this.oldGenOccupancyFraction = oldGenOccupancyFraction; this.oldGenMemoryPool = getOldGenMemoryPool(); if (oldGenMemoryPool != null && oldGenMemoryPool.isValid()) { this.maxOldGenBytes = getMemoryPoolMaxOrCommitted(oldGenMemoryPool); this.valid = true; } else { this.valid = false; } } public void run() { if (!valid) { logger.warn("OldMemoryPool is not valid, task stop."); return; } try { long usedOldGenBytes = logOldGenStatus("checking oldgen status"); if (needTriggerGc(maxOldGenBytes, usedOldGenBytes, oldGenOccupancyFraction)) { preGc(); doGc(); postGc(); } } catch (Exception e) { logger.error(e.getMessage(), e); } finally { scheduler.reschedule(this); } } /** * Determine whether or not to trigger gc. */ private boolean needTriggerGc(long capacityBytes, long usedBytes, int occupancyFraction) { return (occupancyFraction * capacityBytes / 100) < usedBytes; } /** * Suggests gc. */ protected void doGc() { System.gc(); // NOSONAR } /** * Stuff before gc. You can override this method to do your own stuff, for example, cache clean up, deregister from register center. */ protected void preGc() { logger.warn("old gen is occupied larger than occupancy fraction[{}], trying to trigger gc...", oldGenOccupancyFraction); } /** * Stuff after gc. You can override this method to do your own stuff, for example, cache warmup, reregister to register center. */ protected void postGc() { logOldGenStatus("post gc"); } protected long logOldGenStatus(String hints) { long usedOldBytes = oldGenMemoryPool.getUsage().getUsed(); logger.info(String.format("%s, max old gen:%s, used old gen:%s, current fraction: %.2f%%, gc fraction: %d%%", hints, UnitConverter.toSizeUnit(maxOldGenBytes, 2), UnitConverter.toSizeUnit(usedOldBytes, 2), usedOldBytes * 100d / maxOldGenBytes, oldGenOccupancyFraction)); return usedOldBytes; } private MemoryPoolMXBean getOldGenMemoryPool() { String OLD = "old"; String TENURED = "tenured"; MemoryPoolMXBean oldGenMemoryPool = null; List memoryPoolMXBeans = ManagementFactory.getPlatformMXBeans(MemoryPoolMXBean.class); for (MemoryPoolMXBean memoryPool : memoryPoolMXBeans) { String name = memoryPool.getName().trim().toLowerCase(); if (name.contains(OLD) || name.contains(TENURED)) { oldGenMemoryPool = memoryPool; break; } } return oldGenMemoryPool; } private long getMemoryPoolMaxOrCommitted(MemoryPoolMXBean memoryPool) { MemoryUsage usage = memoryPool.getUsage(); long max = usage.getMax(); return max < 0 ? usage.getCommitted() : max; } } ================================================ FILE: vjstar/src/main/java/com/vip/vjstar/window/AtomicBitSet.java ================================================ package com.vip.vjstar.window; import java.util.concurrent.atomic.AtomicIntegerArray; /** * @author huangyunbin * thread safe bitset */ public class AtomicBitSet { private final AtomicIntegerArray atomicIntegerArray; private final int size; public AtomicBitSet(int size) { this.size = size; int intLength = (size + 31) / 32; atomicIntegerArray = new AtomicIntegerArray(intLength); } public void set(long n, boolean flag) { int bit = 1 << n; int idx = (int) (n >>> 5); while (true) { int num = atomicIntegerArray.get(idx); int num2; if (flag) { num2 = num | bit; } else { num2 = num & ~bit; } if (num == num2 || atomicIntegerArray.compareAndSet(idx, num, num2)) { return; } } } public boolean get(long n) { int bit = 1 << n; int idx = (int) (n >>> 5); int num = atomicIntegerArray.get(idx); return (num & bit) != 0; } public int cardinality() { int result = 0; for (int i = 0; i < size; i++) { if (get(i)) { result++; } } return result; } } ================================================ FILE: vjstar/src/main/java/com/vip/vjstar/window/RequestSlidingWindow.java ================================================ package com.vip.vjstar.window; import java.util.concurrent.atomic.AtomicInteger; /** * @author huangyunbin * SlidingWindow for request */ public class RequestSlidingWindow { /** * thread safe bitset,a request is a bit * success use 1 * fail use 0 */ private final AtomicBitSet bitSet; /** * window size */ private final int size; /** * current position */ private volatile AtomicInteger index = new AtomicInteger(); /** * current request num */ private volatile AtomicInteger capacity = new AtomicInteger(); public RequestSlidingWindow(int size) { this.size = size; bitSet = new AtomicBitSet(size); } public void success() { processCapacity(); setNext(true); } public void fail() { processCapacity(); setNext(false); } private void setNext(boolean flag) { int target = index.getAndIncrement() % size; bitSet.set(target, flag); } private void processCapacity() { if (capacity.get() < size) { capacity.getAndIncrement(); } } public long getSucessNum() { return bitSet.cardinality(); } public long getFailNum() { if (capacity.get() >= size) { return size - bitSet.cardinality(); } else { return capacity.get() - bitSet.cardinality(); } } } ================================================ FILE: vjstar/src/main/java/com/vip/vjstar/window/TimeSlidingWindow.java ================================================ package com.vip.vjstar.window; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicLong; /** * @author huangyunbin * SlidingWindow for time */ public class TimeSlidingWindow { private final int size; private volatile AtomicIntegerArray counts; private volatile AtomicLong lastTime = new AtomicLong(); public TimeSlidingWindow(int size) { this.size = size; counts = new AtomicIntegerArray(size); } /** * add request */ public void add() { long currentSecond = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime()); add(currentSecond); } void add(long time) { clear(time); int index = (int) (time % size); long current = lastTime.longValue(); if (time > current) { int oldValue = counts.get(index); if (lastTime.compareAndSet(current, time)) { counts.getAndAdd(index, 1 - oldValue); } else { counts.getAndIncrement(index); } } else { counts.getAndIncrement(index); } } /** * clear prev data */ private void clear(long time) { if (time < lastTime.get() + size) { return; } int index = (int) (time % size); for (int i = 0; i < size; i++) { if (i != index) { counts.set(i, 0); } } } /** * count request num of time window */ public long count() { long currentSecond = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime()); return count(currentSecond); } long count(long time) { if (time >= lastTime.get() + size) { return 0; } long result = 0; for (int i = 0; i < size; i++) { result += counts.get(i); } return result; } } ================================================ FILE: vjstar/src/main/script/docker-cpus/README.md ================================================ # 容器中JVM获取真实的CPU核数 基于 [libsysconfcpus](https://github.com/obmarg/libsysconfcpus)的方案,可以为各个版本的JDK提供一个通用的解决方案。 libsysconfcpus.so的原理是截获JVM获取CPU核数所用的系统调用sysconf(_SC_NPROCESSORS_CONF),改为读取环境变量LIBSYSCONFCPUS返回。 首先,从[libsysconfcpus](https://github.com/obmarg/libsysconfcpus)获取并编译so文件,放入镜像中。 然后,编写类似的脚本,完成两件事情: 1. 定义环境变量LD_PRELOAD,将libsysconfcpus.so放在最前面达到截获的目的。 2. 我们的系统在部署容器的时候,会额外传入一个环境变量"CONTAINER_CORE_LIMIT"代表分配的CPU核数(需按自己的情况修改),脚本将其转换为libsysconfcpus所需的环境变量。 注意:当JVM是以-server启动时,至少需要2核,否则在启动时会被死锁。 ``` #!/bin/sh if [ "x$CONTAINER_CORE_REQUEST" != "x" ]; then LIBSYSCONFCPUS="$CONTAINER_CORE_REQUEST" if [ ${LIBSYSCONFCPUS} -lt 2 ]; then LIBSYSCONFCPUS=2 fi export LIBSYSCONFCPUS fi export LD_PRELOAD="/usr/local/lib/libsysconfcpus.so:$LD_PRELOAD" ``` ================================================ FILE: vjstar/src/main/script/jvm-options/jvm-options.sh ================================================ #!/bin/bash # 使用指南: # 1. 修改本文件中的LOGDIR 和 APPID变量 # 2. 根据实际情况需求,反注释掉一些参数。 # 3. 修改应用启动脚本,增加 "source ./jvm-options.sh",或者将本文件内容复制进应用启动脚本里. # 4. 修改应用启动脚本,使用输出的JAVA_OTPS变量,如java -jar xxx的应用启动语句,修改为 java $JAVA_OPTS -jar xxx。 # change the jvm error log and backup gc log dir here LOGDIR="./logs" # change the appid for gc log name here APPID="myapp" JAVA_VERSION=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}') # Enable coredump ulimit -c unlimited ## Memory Options## MEM_OPTS="-Xms4g -Xmx4g -XX:NewRatio=1" if [[ "$JAVA_VERSION" < "1.8" ]]; then MEM_OPTS="$MEM_OPTS -XX:PermSize=128m -XX:MaxPermSize=512m" else MEM_OPTS="$MEM_OPTS -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m" fi # 启动时预申请内存 MEM_OPTS="$MEM_OPTS -XX:+AlwaysPreTouch" # 如果线程数较多,函数的递归较少,线程栈内存可以调小节约内存,默认1M。 #MEM_OPTS="$MEM_OPTS -Xss256k" # 堆外内存的最大值默认约等于堆大小,可以显式将其设小,获得一个比较清晰的内存总量预估 #MEM_OPTS="$MEM_OPTS -XX:MaxDirectMemorySize=2g" # 根据JMX/VJTop的观察,调整二进制代码区大小避免满了之后不能再JIT,JDK7/8,是否打开多层编译的默认值都不一样 #MEM_OPTS="$MEM_OPTS -XX:ReservedCodeCacheSize=240M" ## GC Options## GC_OPTS="-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" # System.gc() 使用CMS算法 GC_OPTS="$GC_OPTS -XX:+ExplicitGCInvokesConcurrent" # CMS中的下列阶段并发执行 GC_OPTS="$GC_OPTS -XX:+ParallelRefProcEnabled -XX:+CMSParallelInitialMarkEnabled" # 根据应用的对象生命周期设定,减少事实上的老生代对象在新生代停留时间,加快YGC速度 GC_OPTS="$GC_OPTS -XX:MaxTenuringThreshold=3" # 如果OldGen较大,加大YGC时扫描OldGen关联的卡片,加快YGC速度,默认值256较低 GC_OPTS="$GC_OPTS -XX:+UnlockDiagnosticVMOptions -XX:ParGCCardsPerStrideChunk=1024" # 如果JVM并不独占机器,机器上有其他较繁忙的进程在运行,将GC线程数设置得比默认值(CPU核数*5/8 )更低以减少竞争,反而会大大加快YGC速度。 # 另建议CMS GC线程数简单改为YGC线程数一半. #GC_OPTS="$GC_OPTS -XX:ParallelGCThreads=12 -XX:ConcGCThreads=6" # 如果CMS GC时间很长,并且明显受新生代存活对象数量影响时打开,但会导致每次CMS GC与一次YGC连在一起执行,加大了事实上JVM停顿的时间。 #GC_OPTS="$GC_OPTS -XX:+CMSScavengeBeforeRemark" # 如果永久代使用不会增长,关闭CMS时ClassUnloading,降低CMS GC时出现缓慢的几率 #if [[ "$JAVA_VERSION" > "1.8" ]]; then # GC_OPTS="$GC_OPTS -XX:-CMSClassUnloadingEnabled" #fi ## GC log Options, only for JDK7/JDK8 ## # 默认使用/dev/shm 内存文件系统避免在高IO场景下写GC日志时被阻塞导致STW时间延长 if [ -d /dev/shm/ ]; then GC_LOG_FILE=/dev/shm/gc-${APPID}.log else GC_LOG_FILE=${LOGDIR}/gc-${APPID}.log fi if [ -f ${GC_LOG_FILE} ]; then GC_LOG_BACKUP=${LOGDIR}/gc-${APPID}-$(date +'%Y%m%d_%H%M%S').log echo "saving gc log ${GC_LOG_FILE} to ${GC_LOG_BACKUP}" mv ${GC_LOG_FILE} ${GC_LOG_BACKUP} fi #打印GC日志,包括时间戳,晋升老生代失败原因,应用实际停顿时间(含GC及其他原因) GCLOG_OPTS="-Xloggc:${GC_LOG_FILE} -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintPromotionFailure -XX:+PrintGCApplicationStoppedTime" #打印GC原因,JDK8默认打开 if [[ "$JAVA_VERSION" < "1.8" ]]; then GCLOG_OPTS="$GCLOG_OPTS -XX:+PrintGCCause" fi # 打印GC前后的各代大小 #GCLOG_OPTS="$GCLOG_OPTS -XX:+PrintHeapAtGC" # 打印存活区每段年龄的大小 #GCLOG_OPTS="$GCLOG_OPTS -XX:+PrintTenuringDistribution" # 如果发生晋升失败,观察老生代的碎片 #GCLOG_OPTS="$GCLOG_OPTS -XX:+UnlockDiagnosticVMOptions -XX:PrintFLSStatistics=2" # 打印安全点日志,找出GC日志里非GC的停顿的原因 #GCLOG_OPTS="$GCLOG_OPTS -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+UnlockDiagnosticVMOptions -XX:-DisplayVMOutput -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm-${APPID}.log" ## Optimization Options## OPTIMIZE_OPTS="-XX:-UseBiasedLocking -XX:AutoBoxCacheMax=20000 -Djava.security.egd=file:/dev/./urandom" # 关闭PerfData写入,避免高IO场景GC时因为写PerfData文件被阻塞,但会使得jstats,jps不能使用。 #OPTIMIZE_OPTS="$OPTIMIZE_OPTS -XX:+PerfDisableSharedMem" # 关闭多层编译,减少应用刚启动时的JIT导致的可能超时,以及避免部分函数C1编译后最终没被C2编译。 但导致函数没有被初始C1编译。 #if [[ "$JAVA_VERSION" > "1.8" ]]; then # OPTIMIZE_OPTS="$OPTIMIZE_OPTS -XX:-TieredCompilation" #fi # 如果希望无论函数的热度如何,最终JIT所有函数,关闭GC时将函数调用次数减半。 #OPTIMIZE_OPTS="$OPTIMIZE_OPTS -XX:-UseCounterDecay" ## Trouble shooting Options## SHOOTING_OPTS="-XX:+PrintCommandLineFlags -XX:-OmitStackTraceInFastThrow -XX:ErrorFile=${LOGDIR}/hs_err_%p.log" # OOM 时进行HeapDump,但此时会产生较高的连续IO,如果是容器环境,有可能会影响他的容器 #SHOOTING_OPTS="$SHOOTING_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/" # async-profiler 火焰图效果更好的参数 #SHOOTING_OPTS="$SHOOTING_OPTS -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints" # 在非生产环境,打开JFR进行性能记录(生产环境要收License的哈) #SHOOTING_OPTS="$SHOOTING_OPTS -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints" ## JMX Options## #开放JMX本地访问,设定端口号 JMX_OPTS="-Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" ## Other Options## OTHER_OPTS="-Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" ## All together ## export JAVA_OPTS="$MEM_OPTS $GC_OPTS $GCLOG_OPTS $OPTIMIZE_OPTS $SHOOTING_OPTS $JMX_OPTS $OTHER_OPTS" echo JAVA_OPTS=$JAVA_OPTS ================================================ FILE: vjstar/src/test/java/com/vip/vjstar/.gitkeep ================================================ ================================================ FILE: vjstar/src/test/java/com/vip/vjstar/gc/Enchanter.java ================================================ package com.vip.vjstar.gc; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vip.vjtools.vjkit.number.RandomUtil; public class Enchanter { private static final Logger log = LoggerFactory.getLogger(Enchanter.class); private static final String[] ha = { "苟利国家生死以%d岂因祸福趋避之", "煮豆燃豆萁豆在釜中泣%d本是同根生相煎何太急", "利欲驱人万火牛%d江湖浪迹一沙鸥", "且持梦笔书奇景%d日破云涛万里红", "春来我不先开口%d哪个虫儿敢作声" }; private List garbage = new ArrayList<>(); public void makeGarbage(String val) { log.info("trying to occupy oldGen"); int size = Integer.parseInt(val); StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) { // randomize a little bit to avoid duplicate strings sb.append(String.format(ha[RandomUtil.nextInt(i % ha.length + 1)], i)); } // 大对象直接进old gen,用list hold住不释放 garbage.add(sb.toString()); log.info("Enchanter is littering around, garbage size: {}", sb.length()); } public void clearGarbage() { // 清空list以便cms可以回收 garbage.clear(); log.info("garbage cleared from list..."); } } ================================================ FILE: vjstar/src/test/java/com/vip/vjstar/gc/ProactiveGcTaskDemo.java ================================================ package com.vip.vjstar.gc; import java.io.IOException; public class ProactiveGcTaskDemo { public static void main(String[] args) throws IOException { //// 真实代码示例 //// CleanUpScheduler scheduler = new CleanUpScheduler(); ProactiveGcTask task = new ProactiveGcTask(scheduler, 50); scheduler.schedule("03:30-04:30", task); // .... scheduler.shutdown(); ///// 演示用代码 //// // 模拟内存占用, 根据运行环境调整到可以占用一半以上老生代 final Enchanter enchanter = new Enchanter(); enchanter.makeGarbage("10000000"); // 直接运行看效果 task.run(); System.out.println("hit ENTER to stop"); System.in.read(); enchanter.clearGarbage(); //////////////// } } ================================================ FILE: vjstar/src/test/java/com/vip/vjstar/window/RequestSlidingWindowTest.java ================================================ package com.vip.vjstar.window; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; /** * @author huangyunbin * test for RequestSlidingWindow */ public class RequestSlidingWindowTest { @Test public void notOverWindowTest() { RequestSlidingWindow slidingWindow = new RequestSlidingWindow(10); slidingWindow.success(); slidingWindow.success(); slidingWindow.fail(); slidingWindow.success(); slidingWindow.fail(); assertThat(slidingWindow.getSucessNum()).isEqualTo(3); assertThat(slidingWindow.getFailNum()).isEqualTo(2); } @Test public void overWindowTest() { RequestSlidingWindow slidingWindow = new RequestSlidingWindow(3); slidingWindow.success(); slidingWindow.fail(); slidingWindow.success(); //The following is valid slidingWindow.fail(); slidingWindow.success(); slidingWindow.fail(); assertThat(slidingWindow.getSucessNum()).isEqualTo(1); assertThat(slidingWindow.getFailNum()).isEqualTo(2); } @Test public void mutiThreadNotOverWindowTest() throws Exception { int threadNum = 10; final int num = 100; final RequestSlidingWindow slidingWindow = new RequestSlidingWindow(threadNum * num * 2); final CyclicBarrier barrier = new CyclicBarrier(threadNum); final CountDownLatch countDownLatch = new CountDownLatch(threadNum); for (int i = 0; i < threadNum; i++) { new Thread() { @Override public void run() { try { barrier.await(); for (int j = 0; j < num; j++) { slidingWindow.fail(); slidingWindow.success(); } countDownLatch.countDown(); } catch (Exception e) { e.printStackTrace(); } } }.start(); } countDownLatch.await(); assertThat(slidingWindow.getSucessNum()).isEqualTo(threadNum * num); assertThat(slidingWindow.getFailNum()).isEqualTo(threadNum * num); } @Test public void mutiThreadOverWindowTest() throws Exception { int threadNum = 2; final int num = 100; final int size = 20; final AtomicInteger index = new AtomicInteger(); final RequestSlidingWindow slidingWindow = new RequestSlidingWindow(size); final CyclicBarrier barrier = new CyclicBarrier(threadNum); final CountDownLatch countDownLatch = new CountDownLatch(threadNum); for (int i = 0; i < threadNum; i++) { new Thread() { @Override public void run() { try { barrier.await(); for (int j = 0; j < num; j++) { if (index.getAndIncrement() % 2 == 0) { slidingWindow.success(); } else { slidingWindow.fail(); } } countDownLatch.countDown(); } catch (Exception e) { e.printStackTrace(); } } }.start(); } countDownLatch.await(); assertThat(slidingWindow.getSucessNum()).isEqualTo(size / 2); assertThat(slidingWindow.getFailNum()).isEqualTo(size / 2); } } ================================================ FILE: vjstar/src/test/java/com/vip/vjstar/window/TimeSlidingWindowTest.java ================================================ package com.vip.vjstar.window; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; /** * @author huangyunbin * test for TimeSlidingWindow */ public class TimeSlidingWindowTest { @Test public void notOverWindowTest() { TimeSlidingWindow slidingWindow = new TimeSlidingWindow(1); slidingWindow.add(); slidingWindow.add(); long count = slidingWindow.count(); assertThat(count).isEqualTo(2); } @Test public void overWindowTest() throws Exception { TimeSlidingWindow slidingWindow = new TimeSlidingWindow(1); slidingWindow.add(); slidingWindow.add(); Thread.sleep(1000); slidingWindow.add(); long count = slidingWindow.count(); assertThat(count).isEqualTo(1); } @Test public void mutiThreadNotOverWindowTest() { int threadNum = 10; final int num = 100; final int circle = 30; final TimeSlidingWindow slidingWindow = new TimeSlidingWindow(10); final CyclicBarrier barrier = new CyclicBarrier(threadNum); final CountDownLatch countDownLatch = new CountDownLatch(threadNum); for (int i = 0; i < threadNum; i++) { new Thread() { @Override public void run() { try { barrier.await(); for (int i = 0; i < circle; i++) { for (int j = 0; j < num; j++) { slidingWindow.add(); } TimeUnit.MILLISECONDS.sleep(10); } countDownLatch.countDown(); } catch (Exception e) { e.printStackTrace(); } } }.start(); } try { countDownLatch.await(); } catch (Exception e) { e.printStackTrace(); } long count = slidingWindow.count(); assertThat(count).isEqualTo(circle * threadNum * num); } @Test public void mutiThreadOverWindowTest() { int threadNum = 2; final int num = 100; final int circle = 3; final int size = 2; final TimeSlidingWindow slidingWindow = new TimeSlidingWindow(size); final CyclicBarrier barrier = new CyclicBarrier(threadNum); final CountDownLatch countDownLatch = new CountDownLatch(threadNum); for (int i = 0; i < threadNum; i++) { new Thread() { @Override public void run() { try { barrier.await(); for (int i = 0; i < circle; i++) { for (int j = 0; j < num; j++) { slidingWindow.add(); } if (i != circle - 1) { TimeUnit.MILLISECONDS.sleep(1000); } } countDownLatch.countDown(); } catch (Exception e) { e.printStackTrace(); } } }.start(); } try { countDownLatch.await(); } catch (Exception e) { e.printStackTrace(); } long count = slidingWindow.count(); assertThat(count).isEqualTo(size * threadNum * num); } } ================================================ FILE: vjtop/README.md ================================================ # 1. 概述 对应于观看“OS指标及繁忙进程”的top,vjtop就是观察“JVM进程指标及其繁忙线程”的首选工具。 **JVM进程信息**:收集了进程在OS层面和JVM层面的所有重要指标。大家为什么喜欢用[dstat](http://dag.wiee.rs/home-made/dstat/)看OS状态,因为它将你想看的数据全都收集呈现眼前了,vjtop也是这样的风格。 **繁忙线程信息**: 对比于“先top -H 列出线程,再执行jstack拿到全部线程,再手工换算十与十六进制的线程号”的繁琐过程,vjtop既方便,又可以连续跟踪,更不会因为jstack造成JVM停顿。 对于超出正常范围的值,vjtop还很贴心的进行了变色显示。 运行时不造成应用停顿,可在线上安全使用。 **常用场景:** 1. 性能问题快速定位,用vjtop显示出CPU繁忙或内存消耗大的线程,再实时交互翻查该线程的stack trace。 2. 压测场景,用vjtop实时反馈JVM进程状态,类似于用dstast对操作系统指标的监控。 3. 生产环境,当应用出现问题时,用vjtop快速了解进程的状态。还可与监控系统结合,发现指标(如CPU、超时请求数)超出阈值时,用钩子脚本调用vjtop来纪录事发地的状况。 在[jvmtop](https://github.com/patric-r/jvmtop) 的基础上二次开发,结合 [SJK](https://github.com/aragozin/jvm-tools)的优点,从/proc , PerfData,JMX等处,以更高的性能,获取更多的信息。 # 2. 使用说明 ## 2.1 概述 [Download 1.0.8.zip](http://repo1.maven.org/maven2/com/vip/vjtools/vjtop/1.0.8/vjtop-1.0.8.zip) (from Maven Central) vjtop运行所需权限与jstack相同,必须与目标JVM使用相同的JDK版本运行,必须与目标JVM使用相同用户运行。如果仍有问题,请看后面的执行问题排查章节。 ``` // 占用CPU最多的线程 ./vjtop.sh // 打印选项,每个版本的参数会有变动,特别是模式参数,以help信息为准 ./vjtop.sh -h ``` ## 2.2 找出CPU最繁忙的线程 ### 2.2.1 命令参数 ``` // 按时间区间内,线程占用的CPU排序,默认显示前10的线程,默认每10秒打印一次 ./vjtop.sh // 按时间区间内,线程占用的SYS CPU排序 ./vjtop.sh -m syscpu // 按线程从启动以来的总占用CPU来排序 ./vjtop.sh -m totalcpu // 按线程从启动以来的总SYS CPU排序 ./vjtop.sh -m totalsyscpu ``` ### 2.2.2 输出示例: ``` PID: 191082 - 17:43:12 JVM: 1.7.0_79 USER: calvin UPTIME: 2d02h PROCESS: 685.00% cpu(28.54% of 24 core), 787 thread MEMORY: 6626m rss, 6711m peak, 0m swap | DISK: 0B read, 13mB write THREAD: 756 live, 749 daemon, 1212 peak, 0 new | CLASS: 15176 loaded, 161 unloaded, 0 new HEAP: 630m/1638m eden, 5m/204m sur, 339m/2048m old NON-HEAP: 80m/256m/512m perm, 13m/13m/240m codeCache OFF-HEAP: 0m/0m direct(max=2048m), 0m/0m map(count=0), 756m threadStack GC: 6/66ms/11ms ygc, 0/0ms fgc | SAFE-POINT: 6 count, 66ms time, 5ms syncTime TID NAME STATE CPU SYSCPU TOTAL TOLSYS 23 AsyncAppender-Worker-ACCESSFILE-ASYNC WAITING 23.56% 6.68% 2.73% 0.72% 560 OSP-Server-Worker-4-5 RUNNABLE 22.58% 10.67% 1.08% 0.48% 9218 OSP-Server-Worker-4-14 RUNNABLE 22.37% 11.45% 0.84% 0.40% 8290 OSP-Server-Worker-4-10 RUNNABLE 22.36% 11.24% 0.88% 0.41% 8425 OSP-Server-Worker-4-12 RUNNABLE 22.24% 10.72% 0.98% 0.47% 8132 OSP-Server-Worker-4-9 RUNNABLE 22.00% 10.68% 0.90% 0.42% 8291 OSP-Server-Worker-4-11 RUNNABLE 21.80% 10.09% 0.89% 0.41% 8131 OSP-Server-Worker-4-8 RUNNABLE 21.68% 9.77% 0.93% 0.44% 9219 OSP-Server-Worker-4-15 RUNNABLE 21.56% 10.43% 0.90% 0.41% 8426 OSP-Server-Worker-4-13 RUNNABLE 21.35% 10.42% 0.66% 0.31% Total : 668.56% cpu(user=473.25%, sys=195.31%) by 526 atcive threads(which cpu>0.05%) Setting: top 10 threads order by CPU, flush every 10s Input command (h for help): ``` 进程区数据解释: * `PROCESS`: `thread`: 进程的操作系统线程数, `cxtsw`为主动与被动的线程上下文切换数 * `MEMORY`: `rss` 为 Resident Set Size, 进程实际占用的物理内存; `peak`为最峰值的rss; `swap`为进程被交换到磁盘的虚拟内存。 * `DISK`: 真正达到物理存储层的读/写的速度。 * `THREAD`: Java线程数, `active`为当前线程数, `daemon`为active线程中的daemon线程数, `new`为刷新周期内新创建的线程数。 * `CLASS`: `loaded`为当前加载的类数量,`unloaded`为总卸载掉的类数量,`new`为刷新周期内新加载的类数量。 * `HEAP`: 1.0.3版开始每一项有三个数字, 分别为1.当前使用内存, 2.当前已申请内存, 3.最大内存; 如果后两个数字相同时则合并。 * `sur`: 当前存活区的大小,注意实际有from, to 两个存活区。 * `NON-HEAP`: 数字含义同`HEAP` * `codeCache`: JIT编译的二进制代码的存放区,满后将不能编译新的代码。 * `direct`: 堆外内存,三个数字含义同`HEAP`, 未显式设置最大内存时,约等于堆内存大小。注意新版Netty不经过JDK API所分配的堆外内存未在此统计。 * `map`: 映射文件内存,三个数字分别为1. map数量,2.当前使用内存,3.当前已申请内存,没有最大值数据。 * `threadStack`: Java线程所占的栈内存总和,但不包含VM线程。(since 1.0.3) * `ygc`: YoungGC, 三个数字分别为次数/总停顿时间/平均停顿时间 * `fgc`: OldGC + FullGC, 两个数字分别为次数/总执行时间,注意此时间仅为执行时间,非JVM停顿时间。 * `SAFE-POINT`: PerfData开启时可用,JVM真正的停顿次数及停顿时间,以及等待所有线程进入安全点所消耗的时间。 线程区数据解释: * `CPU`: 线程在打印间隔内使用的CPU百分比(按单个核计算) * `SYSCPU`: 线程在打印间隔内使用的SYS CPU百分比(按单个核计算) * `TOTAL`: 从进程启动到现在,线程的总CPU时间/进程的总CPU时间的百分比 * `TOLSYS`: 从进程启动到现在,线程的总SYS CPU时间/进程的总CPU时间的百分比 底部数据解释: * 如果该线程的平均使用CPU少于单核的0.1%,这条线程将不参与排序显示,减少消耗。 ## 2.3 找出内存分配最频繁的线程 ### 2.3.1 命令参数 ``` // 线程分配内存的速度排序,默认显示前10的线程,默认每10秒打印一次 ./vjtop.sh -m 5 // 按线程的总内存分配而不是打印间隔内的内存分配来排序 ./vjtop.sh -m 6 ``` ### 2.3.2 输出示例 ``` (忽略头信息) THREADS-MEMORY: 30k/s allocation rate TID NAME STATE MEMORY TOTAL-ALLOCATED 47636 RMI TCP Connection(583)-127.0.0.1 RUNNABLE 27k/s(88.76%) 17m( 0.00%) 1 main RUNNABLE 2k/s( 8.44%) 370g(83.16%) 47845 JMX server connection timeout 47845 TIMED_WAIT 251/s( 0.80%) 21k( 0.00%) 46607 Worker-501 TIMED_WAIT 60/s( 0.19%) 934m( 0.20%) 46609 Worker-502 TIMED_WAIT 60/s( 0.19%) 822m( 0.18%) 46610 Worker-503 TIMED_WAIT 60/s( 0.19%) 737m( 0.16%) 46763 Worker-504 TIMED_WAIT 60/s( 0.19%) 696m( 0.15%) 46764 Worker-505 TIMED_WAIT 60/s( 0.19%) 743m( 0.16%) 47149 Worker-506 TIMED_WAIT 60/s( 0.19%) 288m( 0.06%) 46551 Worker-500 TIMED_WAIT 60/s( 0.19%) 757m( 0.17%) ``` 进程区数据解释: * `allocation rate`: 所有线程在打印间隔内每秒分配的内存 线程区数据解释: * `STATE`: 该线程当前的状态 * `MEMORY`: 该线程分配内存的瞬时值,即该线程在打印间隔内每秒分配的内存空间(该线程每秒分配的内存占所有线程在该秒分配的总内存的百分比) * `TOTAL-ALLOCATED`: 该线程分配内存的历史累计值,即从进程启动到现在,该线程分配的总内存大小,该总内存大小包括已回收的对象的内存(该线程分配的总内存大小占所有线程分配的总内存大小的百分比)。 如果该线程的平均内存分配速度少于1K/s,这条线程将不参与排序显示,减少消耗。 ## 2.4 命令行参数 ``` // 打印其他选项 ./vjtop.sh -h // 结果输出到文件 ./vjtop.sh > /tmp/vjtop.log // 每5秒打印一次(默认10秒) ./vjtop.sh -i 5 // 打印20次后退出 ./vjtop.sh -n 20 // 显示前100的线程(默认10) ./vjtop.sh -l 100 // 不带变色与换页控制码的console模式,适合不支持控制码的终端。在Windows及输出到文件时将默认使用次此模式 ./vjtop.sh -o clean // key:value式的文本模式,适用于第三方工具采集vjtop的输出结果 ./vjtop.sh -o text // 只采集JVM信息,不采集繁忙线程信息 ./vjtop.sh -c jvm // 只采集繁忙线程信息,不采集JVM信息 ./vjtop.sh -c thread // 只显示线程名包含worker字样的线程,在热点线程与实时交互打印线程时都会过滤(1.0.6版开始忽略大小写) ./vjtop.sh -f worker // 更宽的120字节的屏幕 (默认100) ./vjtop.sh -w 120 > /tmp/vjtop.log ``` ## 2.5 实时交互 ### 2.5.1 打印线程Stack Trace 1.在页面中输入t,再输入线程号,可打印线程的Stack Trace,看繁忙的线程在忙什么。 会引入暂停,但只取一条线程信息时停顿非常短 ``` Input command (h for help):s Input TID for stack:4161 at java.lang.Object.wait(Native Method) at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188) at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220) at org.eclipse.core.internal.jobs.Worker.run(Worker.java:52) ``` 上例子也可以直接输入 "s 4161" 2. 打印Top繁忙线程的stack trace 会引入暂停,但只取若干条线程信息时停顿非常短 ``` Input command (h for help):t Stack trace of top 10 threads: 15: "RMI TCP Connection(15)-10.100.150.221" java.lang.Thread.State: RUNNABLE at sun.management.ThreadImpl.getThreadInfo1(Native Method) at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:178) ... ``` 3. 打印所有状态为BLOCKED的线程的stack trace 会引入暂停,取所有线程信息,时间比前面两个命令略长。但因为不获取锁信息,也不拿JNI global references等数据,所以仍比jstack快。 ``` Input command (h for help):b Stack trace of blocked threads: ... ``` 4. 打印所有线程的TID和线程名 不引入暂停 ``` Thread Id and name of all live threads: 16 : "JMX server connection timeout 16" (TIMED_WAITING) 15 : "RMI TCP Connection(15)-10.100.150.221" (RUNNABLE) 13 : "RMI Scheduler(0)" (TIMED_WAITING) 11 : "RMI TCP Accept-0" (RUNNABLE) 9 : "Attach Listener" (RUNNABLE) 4 : "Signal Dispatcher" (RUNNABLE) ``` ### 2.5.2 实时切换显示模式 1.改变显示和排序模式,在页面中输入m ``` Input command (h for help):m Input number of Display Mode(1.cpu, 2.syscpu 3.total cpu 4.total syscpu 5.memory 6.total memory, current cpu): 5 ``` 2.改变显示间隔 ``` Input command (h for help):i Input flush interval seconds(current 10):20 Flush interval change to 20 seconds ``` 也可以直接输入"i 20" 切换 3. 设定显示的线程数 ``` Input command (h for help):l Input number of threads to display :20 Number of threads to display changed to 20 for next flush ``` 也可以直接输入"l 20" 切换 4.设定按线程名过滤线程,在打印繁忙线程和全部线程时,线程名都必须包含filter字符串,大小写不敏感,不支持正则匹配和匹配符匹配。 ``` Input command (h for help):f Input filter of thread name (current null):Worker thread name filter change to "Worker" for next flush (3s later) ``` # 3. 原理 ## 3.1 进程区数据来源 * 从/proc/PID/* 文件中获取进程数据, 详见[proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html) * 从JDK的PerfData文件中获取JVM数据(JDK每秒写入/tmp/hsperfdata_$userid/$pid文件的统计数据) * 使用目标JVM的JMX中获取JVM数据(如果目标JVM还没启动JMX,通过attach方式动态加载) 如果数据同时在PerfData和JMX存在,优先使用PerfData。 网络流量数据在/proc/PID/*中未能按进程区分,因此不再监控。 ## 3.2 线程区数据来源 使用ThreadMxBean操作: 1. getAllThreadIds()获得所有Thread Id 2. getThreadCpuTime(tids)获得所有线程的CPU时间 (以及SYS CPU,内存分配) 3. 排序后,用getThreadInfo(tids)获得前10名线程的信息,因为不取线程的StackTrace,不会堵塞应用。 # 4. 监控值变色告警规则 * 进程CPU:服务器总CPU50%黄, 70%红 * 进程线程:如果服务器少于8核,线程数为核数*150 黄,核数*225 红。 如果大于8核,核数*100 黄, 核数*150红。 * 进程内存: swap一旦使用即为红 * 进程磁盘IO: 读写磁盘30MB/s 黄,100MB/s 红 * CLASS: 新加载类时为黄色, 当前加载类8万为黄,15万为红 * JVM内存: 老生代,永久代,CodeCache,如果设置了Max,则Max的85%黄,95%红 * JVM线程: active规则同进程线程,new规则:新建1条线程为黄,每秒创建2条为红。 * YGC: YGC次数每秒1次以上黄,2次以上红,平均耗时100ms黄,200ms红, 总耗时达到了应用运行时间的5%黄,10%红 * FGC: 周期内的次数:1次黄,2次红 * SAFEPOINT: 安全点次数每秒2次黄,4次红,总耗时达到了应用运行时间的5%黄,10%红 # 5. 执行问题排查 1. JDK版本错误或tools.jar不存在 vjtop使用的java为JAVA_HOME/bin/java, 需要JDK7及以上,但"不要求"与目标应用的JVM使用相同的JDK版本。 vjtop需要依赖JAVA_HOME/lib/tools.jar JAVA_HOME的定位,通过读取环境变量JAVA_HOME,如果没有定义,则尝试通过"which java"定位java从而获得相对路径。 2. 不能连入目标jvm 再次,vjtop 使用JVM attach机制 连入PID 并获得JMX的本地连接地址,所需权限与jstack相同, attach失败时出现如下报错 ``` ERROR: Could not attach to process. ``` 可以先执行jstack PID对比一下效果。 可能的原因有: 1. PID写错,进程不存在 2. VM Attach时,会强制检查执行vjtop的用户,与目标JMV的用户一致,否则会抛出"well-known file is not secure"之类的异常。 如果用户有sudo权限,可以尝试切换到目标用户,并把JAVA_HOME等环境变量带到新用户。 ``` sudo -E su - ``` 3. /tmp/.java_pid$PID 文件在首次连接时会生成,但如果生成之后被大家的文件清理脚本错误删除,JVM将不再能连入,只能重启应用。 4. 目标JVM使用启动参数-Djava.io.tmpdir,重定向了tmp目录路径,导致读不到/tmp/.java_pid$PID 文件。 5. 目标JVM使用启动参数-XX:+DisableAttachMechanism禁止了attach。 如果实在没有办法attach,可以考虑在原目标进程中配置JMX启动参数,设定JMX的地址与端口,然后在vjtop中指定该地址 目标进程的JVM参数: ``` -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.X=false -Dcom.sun.management.jmxremote.ssl=false ``` vjtop的命令(since 1.0.3): ``` ./vjtop.sh -j 127.0.0.1:7001 ``` # 6. 与jvmtop相比的改进点 ### 6.1 进程概览 * 新功能:进程的物理内存,SWAP,IO,物理线程信息。 * 新功能:将内存信息与GC信息拆开不同分代独立显示 * 新功能:CodeCache与堆外内存,Thread Stack内存信息,SafePoint等信息 * 新功能:对偏离正常范围的值,进行变色提示 ### 6.2 热点线程 * 新功能:线程内存分配速度的展示与排序 (from SJK) * 新功能:线程SYS CPU的展示与排序,应用启动以来线程的总CPU间的排序 (from SJK) * 新配置项:打印间隔,展示线程数 ### 6.3 实时交互 * 新功能: 选择打印某条线程的线程栈,所有TopN繁忙线程的栈,所有Blocked状态线程的栈 * 新功能: 打印全部的线程名 * 新功能: 实时切换显示模式和排序,刷新频率和显示线程数 ### 6.4 为在生产环境运行优化 * 删除jvmtop会造成应用停顿的Profile页面 * 删除jvmtop获取所有Java进程信息,有着不确定性的Overview页面 * 默认打印间隔调整到10s * 进程信息尝试从PerfData而不是JMX读取数据,减少消耗 * 线程信息减少了几倍的耗时,通过批量获取线程CPU时间(from SJK)等方法 * 支持输出文本格式给第三方监控工具使用 * 支持只输出JVM信息或繁忙线程信息 * 支持vm attach总是失败时,直接配置JMX的方式连入 ================================================ FILE: vjtop/README_EN.md ================================================ # VJtop Out of date... VJtop is a JVM monitoring tool to provide a dynamic real-time view of the busiest ten threads, which plays the similar role of the "top" command for viewing the host operation system. # 1. Introduction VJtop allows to display process summary information of current CPU/Memory intensive threads within JVM. Using the information collected from /proc 、PerfData and JMX, VJtop is originally forked from the jvmtop project but added many new features by exploiting nice properties of the SJK project. The usage of VJtop offers a highly smooth user experience. [jvmtop](https://github.com/patric-r/jvmtop) [SJK](https://github.com/aragozin/jvm-tools) VJtop is built as NON stop-the-world and is considered ready for production diagnostics. # 2. Getting Started ## 2.1 How to run [Download vjtop-1.0.4.zip](http://repo1.maven.org/maven2/com/vip/vjtools/vjtop/1.0.4/vjtop-1.0.4.zip)(from Maven Central) Run the following command under **the same user who started the target process**. ``` // showing threads consuming the most cpu ./vjtop.sh ``` ## 2.2 How it works ### 2.21 Sources of Process Stats Process data are retrieved * from /proc/PID/* * from /tmp/hsperfxxxx, where stats are written by JDK every other second * from JMX of the targeted VM. (If JMX isn't started at the time VJtop will try to attach to the process to start JMX). [Note] If the same items appear in both PerfData and JMX, the one from PerfData is perferred. Item in JMX is used instead when PerfData is unavailable. ### 2.2.2 Sources of Thread Stats With ThreadMxBean: 1. getAllThreadIds() is called to collect Thread Ids 2. getThreadCpuTime(tids) is called to get all thread cpu time as well as sys cpu time and memory allocation. 3. getThreadInfo(tids) is called, top 10 threads are shown. StackTrace is not fetched thus the application will not halt. ## 2.3 Spot the Busiest Threads ### 2.3.1 Commands ``` // ranks threads by their cpu time, by default, the top 10 are shown and refreshed in every 10 secs ./vjtop.sh // ranks threads by total cpu time since startup, differentiated from cpu time within the interval ./vjtop.sh --totalcpu // ranks threads by sys cpu ./vjtop.sh --syscpu // ranks threads by total sys cpu ./vjtop.sh --totalsyscpu ``` ### 2.3.2 Outputs ``` VJTop 1.0.0 - 11:38:02, UPTIME: 3d01h PID: 127197, JVM: 1.7.0_79, USER: even.liang PROCESS: 0.99% cpu ( 0.04% of 24 core), 2491m rss, 0m swap IO: 24k rchar, 1k wchar, 0 read_bytes, 0 write_bytes THREAD: 97 active, 89 daemon, 99 peak, 461 created, CLASS: 12243 loaded, 0 unloaded HEAP: 160m/819m eden, 0m/102m sur, 43m/1024m old NON-HEAP: 55m/256m cms perm gen, 8m/96m codeCache OFF-HEAP: 0m/0m direct, 0m/0m map GC: 0/0ms ygc, 0/0ms fgc, SAFE-POINT: 6 count, 1ms time, 1ms syncTime THREADS-CPU: 1.01% (user= 0.31%, sys= 0.70%) TID NAME STATE CPU SYSCPU TOTAL TOLSYS 43 metrics-mercury-metric-logger-1-thread-1 TIMED_WAIT 0.38% 0.28% 25.48% 9.13% 110 metrics-mercury-metric-logger-2-thread-1 TIMED_WAIT 0.38% 0.18% 25.43% 9.10% 496 RMI TCP Connection(365)-192.168.200.87 RUNNABLE 0.05% 0.05% 0.00% 0.00% 82 Proxy-Worker-5-10 RUNNABLE 0.01% 0.01% 0.93% 0.30% 120 threadDeathWatcher-6-1 TIMED_WAIT 0.00% 0.00% 0.26% 0.09% 98 Proxy-Worker-5-16 RUNNABLE 0.00% 0.00% 0.80% 0.26% 99 Proxy-Worker-5-17 RUNNABLE 0.00% 0.00% 0.92% 0.31% 63 Proxy-Worker-5-2 RUNNABLE 0.00% 0.00% 1.07% 0.37% 70 Proxy-Worker-5-5 RUNNABLE 0.00% 0.00% 0.78% 0.26% 102 Proxy-Worker-5-20 RUNNABLE 0.00% 0.00% 0.80% 0.27% Note: Only top 10 threads (according cpu load) are shown! Cost time: 46ms, CPU time: 60ms ``` Process Region Explained: * `rss`: `Resident Set Size`, size of all the pages, fetched from /proc/\/status, for definition see [proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html) * `swap`: Size of pages that are swapped out, fetched from /proc/\/status, for definition see [proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html) * `rchar/wchar`: Number of bytes read/written with system calls, fetched from /proc/\/io, for definition see [proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html) * `read_bytes/write_bytes`: Bytes read from/written to the actual storage layer, fetched from/proc/\/io, for definition see [proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html) * `codeCache`: Cache size holding binaries as result of JIT compilation. JIT Compilation will cease when code cache is fully occupied. * `direct`: Off-heap memory usage. Note that off-heap usage will not be recorded for recent Netty versions, which bypass the JDK API for memory allocation. * `SAFE-POINT`: JVM real stop counts and stop time, collected only when PerfData is available. Thread Region Explained: * `CPU`: cpu time by percentage within the output interval (100% per core). * `SYSCPU`: sys cpu time by percentage within the output interval (100% per core). * `TOTAL`: thread/process total cpu time by percentage since startup. * `TOLSYS`: total sys cpu time by percentage since startup. Bottom Region Explained : * `Cost time`: time cost for data gathering & outputting for this watch. * `CPU time`: cpu time cost for data gathering & outputting for this watch. ## 2.4 Spot Threads Allocating the Most Memory ### 2.4.1 Commands ``` // ranks threads by memory allocation rates, by default, the top 10 are shown and refreshed in every 10 secs ./vjtop.sh --memory // ranks Threads by total memory allcoation rates since startup (instead of by output interval) ./vjtop.sh --totalmemory ``` ### 2.4.2 Outputs ``` (headers omitted) THREADS-MEMORY: 30k/s allocation rate TID NAME STATE MEMORY TOTAL-ALLOCATED 47636 RMI TCP Connection(583)-127.0.0.1 RUNNABLE 27k/s(88.76%) 17m( 0.00%) 1 main RUNNABLE 2k/s( 8.44%) 370g(83.16%) 47845 JMX server connection timeout 47845 TIMED_WAIT 251/s( 0.80%) 21k( 0.00%) 46607 Worker-501 TIMED_WAIT 60/s( 0.19%) 934m( 0.20%) 46609 Worker-502 TIMED_WAIT 60/s( 0.19%) 822m( 0.18%) 46610 Worker-503 TIMED_WAIT 60/s( 0.19%) 737m( 0.16%) 46763 Worker-504 TIMED_WAIT 60/s( 0.19%) 696m( 0.15%) 46764 Worker-505 TIMED_WAIT 60/s( 0.19%) 743m( 0.16%) 47149 Worker-506 TIMED_WAIT 60/s( 0.19%) 288m( 0.06%) 46551 Worker-500 TIMED_WAIT 60/s( 0.19%) 757m( 0.17%) ``` Process Region Explained: * `allocation rate`: Summed memory allocation speed of all threads. Bottom Region Explained: * `STATE`: Current thread state * `MEMORY`: Instant memory allocation rate by the second (Instant memory allocation by this thread per second, divided by total allocation) * `TOTAL-ALLOCATED`: Accumulated memory allocations since startup, including those recycled (Accumulated memory allocation by this thread, divided by total memory allocation) ## 2.5 Common Args ``` // prints other options ./vjtop.sh -h // outputs to file ./vjtop.sh > /tmp/vjtop.log // refreshes in every 5 secs (default is 10 secs) ./vjtop.sh -d 5 // shows the top 20 threads (default is top 10) ./vjtop.sh -l 20 // prints by width of 120 characters(default is 100) ./vjtop.sh -w 120 > /tmp/vjtop.log // quits after 20 output interations ./vjtop.sh -n 20 ``` # 3. Enhancements over jvmtop ### 3.1 Hot Thread Pages * Added Display: thread memory allocation rankings (from SJK). * Added Display: thread sys cpu time rankings, total cpu time since startup rankings (from SJK). * Added Display: thread physical memory, swapness, IO stats. * Added Display: **per generation memory and GC information display**, CodeCache and off-heap memory display. * Added Config Arg: printing interval, the number of threads to display. * Performance boost: **massively reduced time cost** by means of fetching thread cpu time in batches (inspired by SJK). ### 3.2 Optimizations for Production * Remove profile page that causes STW from jvmtop. * Eliminate the steps to fetch all java threads in jvmtop. Remove Overview page, in which results are sometimes underterministic. * Default output interval set to 10 secs. * Print **cost incurred by the monitoring tool itself**. ================================================ FILE: vjtop/pom.xml ================================================ 4.0.0 com.vip.vjtools vjtop 1.0.9-SNAPSHOT vjtop Linux top-like JVM info and busy threads monitoring tools ${java.home}/../lib/tools.jar UTF-8 1.7 ${java.version} ${java.version} 1.6 4.12 net.sf.jopt-simple jopt-simple 4.9 junit junit ${junit.version} test org.apache.maven.plugins maven-assembly-plugin 2.6 jar-with-dependencies assemble-with-dependencies prepare-package single vjtop false assemble-distribution package single false src/main/assembly/distribution.xml release org.apache.maven.plugins maven-javadoc-plugin 2.10.4 false attach-javadocs jar org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar org.apache.maven.plugins maven-release-plugin 2.5.3 v.@{project.version} org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign jdk9 [1.9,) default-jdk (,1.8] com.sun tools ${java.version} system ${toolsjar} sonatype-nexus-snapshots https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-releases https://oss.sonatype.org/service/local/staging/deploy/maven2 https://github.com/vipshop/vjtools Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:https://github.com/vipshop/vjtools.git scm:git:https://github.com/vipshop/vjtools.git https://github.com/vipshop/vjtools v.1.0.2 calvin Calvin Xiao calvin.xiao at vipshop.com developer +8 ================================================ FILE: vjtop/src/main/assembly/distribution.xml ================================================ distribution zip ${project.artifactId} src/main/assembly/vjtop.bat windows src/main/assembly/vjtop.sh 0755 unix README.md README.md unix ${project.build.directory}/vjtop.jar / ================================================ FILE: vjtop/src/main/assembly/vjtop.bat ================================================ @echo off rem check java if "%JAVA_HOME%" == "" goto noJavaHome set DIR=%~dp0 set JAVA_OPTS=-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -Xverify:none -XX:AutoBoxCacheMax=20000 "%JAVA_HOME%\bin\java" %JAVA_OPTS% -cp "%DIR%/vjtop.jar;%JAVA_HOME%/lib/tools.jar" com.vip.vjtools.vjtop.VJTop %* goto end :noJavaHome echo Please set JAVA_HOME before running this script goto end :end pause ================================================ FILE: vjtop/src/main/assembly/vjtop.sh ================================================ if [ -z "$JAVA_HOME" ] ; then echo "JAVA_HOME env doesn't exist, try to find the location of java" JAVA_HOME=`readlink -f \`which java 2>/dev/null\` 2>/dev/null | \ sed 's/\jre\/bin\/java//' | sed 's/\/bin\/java//'` fi if [ ! -d "$JAVA_HOME" ] ; then echo "Please set JAVA_HOME env before run this script" exit 1 fi # returns the JDK version. # 8 for 1.8.0_nn, 9 for 9-ea etc, and "no_java" for undetected GET_JDK_VERSION() { local result local java_cmd if [[ -n $(type -p java) ]] then java_cmd=java elif [[ (-n "$JAVA_HOME") && (-x "$JAVA_HOME/bin/java") ]] then java_cmd="$JAVA_HOME/bin/java" fi local IFS=$'\n' # remove \r for Cygwin local lines=$("$java_cmd" -Xms32M -Xmx32M -version 2>&1 | tr '\r' '\n') if [[ -z $java_cmd ]] then result=no_java else for line in $lines; do if [[ (-z $result) && ($line = *"version \""*) ]] then local ver=$(echo $line | sed -e 's/.*version "\(.*\)"\(.*\)/\1/; 1q') # on macOS, sed doesn't support '?' if [[ $ver = "1."* ]] then result=$(echo $ver | sed -e 's/1\.\([0-9]*\)\(.*\)/\1/; 1q') else result=$(echo $ver | sed -e 's/\([0-9]*\)\(.*\)/\1/; 1q') fi fi done fi echo "$result" } JDK_VERSION=$(GET_JDK_VERSION) echo "JDK_VERSION : $JDK_VERSION" # jdk 8 and before if [[ $JDK_VERSION -le 8 ]]; then TOOLSJAR="$JAVA_HOME/lib/tools.jar" if [ ! -f "$TOOLSJAR" ] ; then echo "$TOOLSJAR doesn't exist" >&2 exit 1 fi JAVA_OPTS="-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -Xverify:none -XX:AutoBoxCacheMax=20000" else # jdk 9 or later JAVA_OPTS="-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -XX:AutoBoxCacheMax=20000" fi DIR=$( cd $(dirname $0) ; pwd -P ) "$JAVA_HOME"/bin/java $JAVA_OPTS -cp "$DIR/vjtop.jar:$TOOLSJAR" com.vip.vjtools.vjtop.VJTop "$@" exit $? ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/InteractiveTask.java ================================================ package com.vip.vjtools.vjtop; import java.io.Console; import java.io.IOException; import java.io.PrintStream; import com.vip.vjtools.vjtop.VMDetailView.ThreadInfoMode; /** * 与用户交互动态的控制器 */ public class InteractiveTask implements Runnable { private VJTop app; private Console console; private PrintStream tty; private String inputWhenWaitForEnter; public InteractiveTask(VJTop app) { this.app = app; tty = System.err; console = System.console(); } public boolean inputEnabled() { return console != null; } @Override public void run() { // background执行时,console为Null if (console == null) { return; } while (true) { try { String command; if (inputWhenWaitForEnter != null && inputWhenWaitForEnter.length() > 0) { command = inputWhenWaitForEnter; inputWhenWaitForEnter = null; } else { command = readLine(""); if (command == null) { break; } } handleCommand(command.toLowerCase()); if (!app.view.shouldExit()) { tty.print(" Input command (h for help):"); } } catch (Exception e) { e.printStackTrace(tty); } } } public void handleCommand(String command) throws Exception { if (command.equals("s") || command.startsWith("s ")) { printStacktrace(command); } else if (command.equals("t")) { printTopThreadsStack(); } else if (command.equals("b")) { printBlockedThreadsStack(); } else if (command.equals("a")) { printAllThreadsName(); } else if (command.equals("m")) { changeDisplayMode(); } else if (command.equals("i") || command.startsWith("i ")) { changeInterval(command); } else if (command.equals("l") || command.startsWith("l ")) { changeThreadLimit(command); } else if (command.equals("f")) { changeThreadFilter(); } else if (command.equals("q") || command.equals("quit") || command.equals("exit")) { app.exit(); return; } else if (command.equals("h") || command.equals("help")) { printHelp(); } else if (command.equals("")) { // do nothing } else { tty.println(" Unkown command: " + command + ", available options:"); printHelp(); } } private void printStacktrace(String command) throws IOException { app.preventFlush(); String pidStr; if (command.length() == 1) { pidStr = readLine(" Input TID:"); } else { pidStr = command.substring(2); } try { long pid = Long.parseLong(pidStr); app.view.threadPrinter.printStack(pid); waitForEnter(); } catch (NumberFormatException e) { tty.println(" Wrong number format for pid"); } finally { app.continueFlush(); } } private void printTopThreadsStack() throws IOException { try { app.preventFlush(); app.view.threadPrinter.printTopStack(); waitForEnter(); } finally { app.continueFlush(); } } private void printAllThreadsName() throws IOException { try { app.preventFlush(); app.view.threadPrinter.printAllThreads(); waitForEnter(); } finally { app.continueFlush(); } } private void printBlockedThreadsStack() throws IOException { try { app.preventFlush(); app.view.threadPrinter.printBlockedThreads(); waitForEnter(); } finally { app.continueFlush(); } } private void changeDisplayMode() { app.preventFlush(); String mode = readLine( " Input number of Display Mode(1.cpu, 2.syscpu 3.total cpu 4.total syscpu 5.memory 6.total memory, current " + app.view.threadInfoMode + "): "); ThreadInfoMode detailMode = ThreadInfoMode.parseInt(mode); if (detailMode == null) { tty.println(" Wrong option for display mode(1-6)"); } else if (detailMode == app.view.threadInfoMode) { tty.println(" Nothing be changed"); } else { if (app.view.threadInfoMode.isCpuMode != detailMode.isCpuMode) { app.view.switchCpuAndMemory(); app.view.threadInfoMode = detailMode; tty.println(" Display mode changed to " + app.view.threadInfoMode + " for next flush"); app.interruptSleep(); } else { app.view.threadInfoMode = detailMode; tty.println(" Display mode changed to " + app.view.threadInfoMode + " for next flush(" + app.nextFlushTime() + "s later)"); } } app.continueFlush(); } private void changeInterval(String command) { app.preventFlush(); String intervalStr; if (command.length() == 1) { intervalStr = readLine(" Input flush interval seconds(current " + app.getInterval() + "):"); } else { intervalStr = command.substring(2); } try { int interval = Integer.parseInt(intervalStr); if (interval != app.getInterval()) { app.updateInterval(interval); tty.println(" Flush interval change to " + interval + " seconds"); app.interruptSleep(); } else { tty.println(" Nothing be changed"); } } catch (NumberFormatException e) { tty.println(" Wrong number format for interval"); } finally { app.continueFlush(); } } private void changeThreadLimit(String command) { app.preventFlush(); String threadLimitStr; if (command.length() == 1) { threadLimitStr = readLine(" Input number of threads to display(current " + app.view.threadLimit + "):"); } else { threadLimitStr = command.substring(2); } try { int threadLimit = Integer.parseInt(threadLimitStr); if (threadLimit != app.view.threadLimit) { app.view.threadLimit = threadLimit; tty.println(" Number of threads to display change to " + threadLimit + " for next flush(" + app.nextFlushTime() + "s later)"); } else { tty.println(" Nothing be changed"); } } catch (NumberFormatException e) { tty.println(" Wrong number format for number of threads"); } finally { app.continueFlush(); } } private void changeThreadFilter() { app.preventFlush(); String threadNameFilter = readLine(" Input thread name filter (current " + app.view.threadNameFilter + "):"); if (threadNameFilter != null && threadNameFilter.trim().length() == 0) { threadNameFilter = null; } app.view.threadNameFilter = threadNameFilter != null ? threadNameFilter.toLowerCase() : null; tty.println(" Thread name filter change to " + threadNameFilter + " for next flush (" + app.nextFlushTime() + "s later)"); app.continueFlush(); } private void printHelp() throws Exception { app.preventFlush(); tty.println(" s [tid]: print stack trace of the thread you choose"); tty.println(" t : print stack trace of top " + app.view.threadLimit + " threads"); tty.println(" b : print stack trace of blocked threads"); tty.println(" a : list id and name of all threads"); tty.println(" ---------------"); tty.println(" m : change threads display mode and ordering"); tty.println(" i [num]: change flush interval seconds"); tty.println(" l [num]: change number of display threads"); tty.println(" f [name]: set thread name filter"); tty.println(" ---------------"); tty.println(" q : quit"); tty.println(" h : print help"); waitForEnter(); app.continueFlush(); } private void waitForEnter() { inputWhenWaitForEnter = readLine(" Please hit to continue..."); } private String readLine(String hints) { String result = console.readLine(hints); if (result != null) { return result.trim(); } return null; } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/ThreadPrinter.java ================================================ package com.vip.vjtools.vjtop; import java.io.IOException; import java.lang.Thread.State; import java.lang.management.LockInfo; import java.lang.management.ThreadInfo; /** * 打印线程名,线程栈信息 */ public class ThreadPrinter { private VMDetailView view; public ThreadPrinter(VMDetailView view) { this.view = view; } /** * 打印单条线程的stack strace,会造成停顿,但比获取全部线程的stack trace停顿少 */ public void printStack(long tid) throws IOException { System.out.printf("%n Stack trace of thread %d:%n", tid); ThreadInfo info = view.vmInfo.getThreadInfo(tid, 20); if (info == null) { System.err.println(" TID not exist:" + tid); return; } printSingleThread(info); System.out.flush(); } /** * 打印所有活跃线程的stack strace,会造成停顿,但比获取全部线程的stack trace停顿少 */ public void printTopStack() throws IOException { System.out.printf("%n Stack trace of top %d threads:%n", view.threadLimit); ThreadInfo[] infos = view.topThreadInfo.getTopThreadInfo(); for (ThreadInfo info : infos) { if (info == null) { continue; } printSingleThread(info); } System.out.flush(); } public StackTraceElement[] printSingleThread(ThreadInfo info) { StackTraceElement[] trace = info.getStackTrace(); StringBuilder sb = new StringBuilder(512); sb.append(" ").append(info.getThreadId()).append(": \"").append(info.getThreadName()).append("\""); if (view.vmInfo.threadContentionMonitoringSupported) { sb.append(" (blocked:").append(info.getBlockedCount()).append("/").append(info.getBlockedTime()) .append("ms, wait:").append(info.getWaitedCount()).append("/").append(info.getWaitedTime()) .append("ms"); } else { sb.append(" (blocked:").append(info.getBlockedCount()).append(" times, wait:").append(info.getWaitedCount()) .append(" times"); } if (info.isSuspended()) { sb.append(" ,suspended"); } if (info.isInNative()) { sb.append(" ,in native"); } sb.append(")\n"); sb.append(" java.lang.Thread.State: " + info.getThreadState().toString()); LockInfo lockInfo = info.getLockInfo(); if (lockInfo != null) { sb.append("(on " + lockInfo + ")"); } if (info.getLockOwnerName() != null) { sb.append(" owned by " + info.getLockOwnerId() + ":\"" + info.getLockOwnerName() + "\""); } sb.append("\n"); for (StackTraceElement traceElement : trace) { sb.append("\tat ").append(traceElement).append("\n"); } System.out.print(sb.toString()); return trace; } /** * 打印所有线程,只获取名称不获取stack,不造成停顿 */ public void printAllThreads() throws IOException { int[] stateCounter = new int[6]; System.out.println("\n Thread Id and name of all live threads:"); long tids[] = view.vmInfo.getAllThreadIds(); ThreadInfo[] threadInfos = view.vmInfo.getThreadInfo(tids); for (ThreadInfo info : threadInfos) { if (info == null) { continue; } String threadName = info.getThreadName(); if (view.threadNameFilter != null && !threadName.toLowerCase().contains(view.threadNameFilter)) { continue; } System.out.println( " " + info.getThreadId() + "\t: \"" + threadName + "\" (" + info.getThreadState().toString() + ")"); stateCounter[info.getThreadState().ordinal()]++; } StringBuilder statesSummary = new StringBuilder(" Summary: "); for (State state : State.values()) { statesSummary.append(state.toString()).append(':').append(stateCounter[state.ordinal()]).append(" "); } System.out.println(statesSummary.append("\n").toString()); if (view.threadNameFilter != null) { System.out.println(" Thread name filter is:" + view.threadNameFilter); } System.out.flush(); } public void printBlockedThreads() throws IOException { System.out.println("\n Stack trace of blocked threads:"); int counter = 0; ThreadInfo[] threadInfos = view.vmInfo.getAllThreadInfo(); for (ThreadInfo info : threadInfos) { if (info == null) { continue; } String threadName = info.getThreadName(); if (Thread.State.BLOCKED.equals(info.getThreadState()) && (view.threadNameFilter == null || threadName.toLowerCase().contains(view.threadNameFilter))) { printSingleThread(info); counter++; } } System.out.println(" Total " + counter + " blocked threads"); if (view.threadNameFilter != null) { System.out.println(" Thread name filter is:" + view.threadNameFilter); } System.out.flush(); } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/TopThreadInfo.java ================================================ package com.vip.vjtools.vjtop; import java.io.IOException; import java.lang.management.ThreadInfo; import com.vip.vjtools.vjtop.VMDetailView.ThreadInfoMode; import com.vip.vjtools.vjtop.util.LongObjectHashMap; import com.vip.vjtools.vjtop.util.LongObjectMap; import com.vip.vjtools.vjtop.util.Utils; public class TopThreadInfo { private VMInfo vmInfo; private long[] topTidArray; private LongObjectMap lastThreadCpuTotalTimes = new LongObjectHashMap<>(); private LongObjectMap lastThreadSysCpuTotalTimes = new LongObjectHashMap<>(); private LongObjectMap lastThreadMemoryTotalBytes = new LongObjectHashMap<>(); public TopThreadInfo(VMInfo vmInfo) throws Exception { this.vmInfo = vmInfo; } public TopCpuResult topCpuThreads(ThreadInfoMode mode, int threadLimit) throws IOException { TopCpuResult result = new TopCpuResult(); try { long tids[] = vmInfo.getAllThreadIds(); int mapSize = tids.length * 2; result.threadCpuTotalTimes = new LongObjectHashMap<>(mapSize); result.threadCpuDeltaTimes = new LongObjectHashMap<>(mapSize); result.threadSysCpuTotalTimes = new LongObjectHashMap<>(mapSize); result.threadSysCpuDeltaTimes = new LongObjectHashMap<>(mapSize); // 批量获取CPU times,性能大幅提高。 // 两次获取之间有间隔,在低流量下可能造成负数 long[] threadCpuTotalTimeArray = vmInfo.getThreadCpuTime(tids); long[] threadUserCpuTotalTimeArray = vmInfo.getThreadUserTime(tids); // 过滤CPU占用太少的线程,每秒0.01%CPU (0.1ms cpu time) long minDeltaCpuTime = (vmInfo.upTimeMills.delta * Utils.NANOS_TO_MILLS / 10000); // 计算本次CPU Time // 此算法第一次不会显示任何数据,保证每次显示都只显示区间内数据 for (int i = 0; i < tids.length; i++) { long tid = tids[i]; Long threadCpuTotalTime = threadCpuTotalTimeArray[i]; result.threadCpuTotalTimes.put(tid, threadCpuTotalTime); Long lastTime = lastThreadCpuTotalTimes.get(tid); if (lastTime != null) { Long deltaThreadCpuTime = threadCpuTotalTime - lastTime; if (deltaThreadCpuTime >= minDeltaCpuTime) { result.threadCpuDeltaTimes.put(tid, deltaThreadCpuTime); result.deltaAllActiveThreadCpu += deltaThreadCpuTime; } else { result.deltaAllFreeThreadCpu += deltaThreadCpuTime; } } } // 计算本次SYSCPU Time for (int i = 0; i < tids.length; i++) { long tid = tids[i]; // 因为totalTime 与 userTime 的获取时间有先后,实际sys接近0时,后取的userTime可能比前一时刻的totalTime高,计算出来的sysTime可为负数 Long threadSysCpuTotalTime = Math.max(0, threadCpuTotalTimeArray[i] - threadUserCpuTotalTimeArray[i]); result.threadSysCpuTotalTimes.put(tid, threadSysCpuTotalTime); Long lastTime = lastThreadSysCpuTotalTimes.get(tid); if (lastTime != null) { Long deltaThreadSysCpuTime = Math.max(0, threadSysCpuTotalTime - lastTime); if (deltaThreadSysCpuTime >= minDeltaCpuTime) { result.threadSysCpuDeltaTimes.put(tid, deltaThreadSysCpuTime); result.deltaAllActiveThreadSysCpu += deltaThreadSysCpuTime; } } } if (lastThreadCpuTotalTimes.isEmpty()) { lastThreadCpuTotalTimes = result.threadCpuTotalTimes; lastThreadSysCpuTotalTimes = result.threadSysCpuTotalTimes; result.ready = false; return result; } // 按不同类型排序,过滤 if (mode == ThreadInfoMode.cpu) { topTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadCpuDeltaTimes, threadLimit); } else if (mode == ThreadInfoMode.syscpu) { topTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadSysCpuDeltaTimes, threadLimit); } else if (mode == ThreadInfoMode.totalcpu) { topTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadCpuTotalTimes, threadLimit); } else if (mode == ThreadInfoMode.totalsyscpu) { topTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadSysCpuTotalTimes, threadLimit); } else { throw new RuntimeException("unkown mode:" + mode); } result.activeThreads = result.threadCpuDeltaTimes.size(); // 获得线程名等信息threadInfo result.threadInfos = vmInfo.getThreadInfo(topTidArray); lastThreadCpuTotalTimes = result.threadCpuTotalTimes; lastThreadSysCpuTotalTimes = result.threadSysCpuTotalTimes; } catch (Exception e) { vmInfo.handleJmxFetchDataError(e); } return result; } public TopMemoryResult topMemoryThreads(ThreadInfoMode mode, int threadLimit) throws IOException { TopMemoryResult result = new TopMemoryResult(); try { long tids[] = vmInfo.getAllThreadIds(); int mapSize = tids.length * 2; result.threadMemoryTotalBytesMap = new LongObjectHashMap<>(mapSize); result.threadMemoryDeltaBytesMap = new LongObjectHashMap<>(mapSize); // 批量获取内存分配 long[] threadMemoryTotalBytesArray = vmInfo.getThreadAllocatedBytes(tids); // 此算法第一次不会显示任何数据,保证每次显示都只显示区间内数据 for (int i = 0; i < tids.length; i++) { long tid = tids[i]; Long threadMemoryTotalBytes = threadMemoryTotalBytesArray[i]; result.threadMemoryTotalBytesMap.put(tid, threadMemoryTotalBytes); result.totalAllThreadBytes += threadMemoryTotalBytes; Long threadMemoryDeltaBytes = 0L; Long lastBytes = lastThreadMemoryTotalBytes.get(tid); if (lastBytes != null) { threadMemoryDeltaBytes = threadMemoryTotalBytes - lastBytes; if (threadMemoryDeltaBytes > 0) { result.threadMemoryDeltaBytesMap.put(tid, threadMemoryDeltaBytes); result.deltaAllThreadBytes += threadMemoryDeltaBytes; } } } if (lastThreadMemoryTotalBytes.isEmpty()) { lastThreadMemoryTotalBytes = result.threadMemoryTotalBytesMap; result.ready = false; return result; } // 线程排序 long[] topTidArray; if (mode == ThreadInfoMode.memory) { topTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadMemoryDeltaBytesMap, threadLimit); } else { topTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadMemoryTotalBytesMap, threadLimit); } result.activeThreads = result.threadMemoryDeltaBytesMap.size(); result.threadInfos = vmInfo.getThreadInfo(topTidArray); lastThreadMemoryTotalBytes = result.threadMemoryTotalBytesMap; } catch (Exception e) { vmInfo.handleJmxFetchDataError(e); } return result; } public ThreadInfo[] getTopThreadInfo() throws IOException { return vmInfo.getThreadInfo(topTidArray, 20); } public void cleanupThreadsHistory() { this.lastThreadCpuTotalTimes.clear(); this.lastThreadSysCpuTotalTimes.clear(); this.lastThreadMemoryTotalBytes.clear(); } public static class TopCpuResult { public ThreadInfo[] threadInfos; public long activeThreads = 0; public long deltaAllActiveThreadCpu = 0; public long deltaAllActiveThreadSysCpu = 0; public long deltaAllFreeThreadCpu = 0; public LongObjectMap threadCpuTotalTimes; public LongObjectMap threadCpuDeltaTimes; public LongObjectMap threadSysCpuTotalTimes; public LongObjectMap threadSysCpuDeltaTimes; public boolean ready = true; } public static class TopMemoryResult { public ThreadInfo[] threadInfos; public long activeThreads = 0; public long deltaAllThreadBytes = 0; public long totalAllThreadBytes = 0; public LongObjectMap threadMemoryTotalBytesMap; public LongObjectMap threadMemoryDeltaBytesMap; public boolean ready = true; } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/VJTop.java ================================================ package com.vip.vjtools.vjtop; import java.io.BufferedOutputStream; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import com.vip.vjtools.vjtop.VMDetailView.ContentMode; import com.vip.vjtools.vjtop.VMDetailView.OutputFormat; import com.vip.vjtools.vjtop.VMDetailView.ThreadInfoMode; import com.vip.vjtools.vjtop.VMInfo.VMInfoState; import com.vip.vjtools.vjtop.util.Formats; import com.vip.vjtools.vjtop.util.OptionAdvanceParser; import com.vip.vjtools.vjtop.util.Utils; import joptsimple.OptionParser; import joptsimple.OptionSet; public class VJTop { public static final String VERSION = "1.0.9"; public VMDetailView view; private Thread mainThread; private Integer interval; private int maxIterations = -1; private volatile boolean needMoreInput = false; private long sleepStartTime; public static void main(String[] args) { try { // 1. create option parser OptionParser parser = OptionAdvanceParser.createOptionParser(); OptionSet optionSet = parser.parse(args); if (optionSet.has("help")) { printHelper(parser); System.exit(0); } // 2. create vminfo String pid = OptionAdvanceParser.parsePid(parser, optionSet); String jmxHostAndPort = null; if (optionSet.hasArgument("jmxurl")) { jmxHostAndPort = (String) optionSet.valueOf("jmxurl"); } VMInfo vminfo = VMInfo.processNewVM(pid, jmxHostAndPort); if (vminfo.state != VMInfoState.ATTACHED) { System.out .println("\n" + Formats.red("ERROR: Could not attach to process, see the solution in README")); return; } // 3. create view ThreadInfoMode threadInfoMode = OptionAdvanceParser.parseThreadInfoMode(optionSet); OutputFormat format = OptionAdvanceParser.parseOutputFormat(optionSet); ContentMode contentMode = OptionAdvanceParser.parseContentMode(optionSet); Integer width = null; if (optionSet.hasArgument("width")) { width = (Integer) optionSet.valueOf("width"); } Integer interval = OptionAdvanceParser.parseInterval(optionSet); VMDetailView view = new VMDetailView(vminfo, format, contentMode, threadInfoMode, width, interval); if (optionSet.hasArgument("limit")) { Integer limit = (Integer) optionSet.valueOf("limit"); view.threadLimit = limit; } if (optionSet.hasArgument("filter")) { String filter = (String) optionSet.valueOf("filter"); view.threadNameFilter = filter; } // 4. create main application VJTop app = new VJTop(); app.mainThread = Thread.currentThread(); app.view = view; app.updateInterval(interval); if (optionSet.hasArgument("n")) { Integer iterations = (Integer) optionSet.valueOf("n"); app.maxIterations = iterations; } // 5. console/cleanConsole mode start thread to get user input if (format != OutputFormat.text) { InteractiveTask task = new InteractiveTask(app); // 前台运行,接受用户输入时才启动交互进程 if (task.inputEnabled()) { view.displayCommandHints = true; if (app.maxIterations == -1) { Thread interactiveThread = new Thread(task, "InteractiveThread"); interactiveThread.setDaemon(true); interactiveThread.start(); } } else { // 后台运行,输出重定向到文件时,转为没有ansi码的干净模式 format = OutputFormat.cleanConsole; } } // 6. cleanConsole/text mode, 屏蔽ansi码 if (!format.ansi) { Formats.disableAnsi(); if (format == OutputFormat.cleanConsole) { Formats.setCleanClearTerminal(); } else { Formats.setTextClearTerminal(); } } // 7. run app app.run(view); } catch (Exception e) { e.printStackTrace(System.out); System.out.flush(); } } private void run(VMDetailView view) throws Exception { try { // System.out 设为Buffered,需要使用System.out.flush刷新 System.setOut(new PrintStream(new BufferedOutputStream(new FileOutputStream(FileDescriptor.out)), false)); int iterations = 0; while (!view.shouldExit()) { waitForInput(); view.printView(); if (view.shouldExit()) { break; } System.out.flush(); if (maxIterations > 0 && iterations >= maxIterations) { break; } // 第一次最多只等待3秒 int sleepSeconds = (iterations == 0) ? Math.min(3, interval) : interval; iterations++; sleepStartTime = System.currentTimeMillis(); Utils.sleep(sleepSeconds * 1000L); } System.out.println(""); System.out.flush(); } catch (NoClassDefFoundError e) { e.printStackTrace(System.out); System.out.println(Formats.red("ERROR: Some JDK classes cannot be found.")); System.out.println(" Please check if the JAVA_HOME environment variable has been set to a JDK path."); System.out.println(""); System.out.flush(); } } public static void printHelper(OptionParser parser) { try { System.out.println("vjtop " + VERSION + " - java monitoring for the command-line"); System.out.println("Usage: vjtop.sh [options...] "); System.out.println(""); parser.printHelpOn(System.out); } catch (IOException ignored) { } } public void exit() { view.shoulExit(); mainThread.interrupt(); } public void interruptSleep() { mainThread.interrupt(); } public void preventFlush() { needMoreInput = true; } public void continueFlush() { needMoreInput = false; } private void waitForInput() { while (needMoreInput) { Utils.sleep(1000); } } public int nextFlushTime() { return Math.max(0, interval - (int) ((System.currentTimeMillis() - sleepStartTime) / 1000)); } public void updateInterval(int interval) { this.interval = interval; view.interval = interval; } public int getInterval() { return interval; } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/VMDetailView.java ================================================ package com.vip.vjtools.vjtop; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.util.Date; import com.sun.management.OperatingSystemMXBean; import com.vip.vjtools.vjtop.TopThreadInfo.TopCpuResult; import com.vip.vjtools.vjtop.TopThreadInfo.TopMemoryResult; import com.vip.vjtools.vjtop.util.Formats; import com.vip.vjtools.vjtop.util.Utils; @SuppressWarnings("restriction") public class VMDetailView { private static final int DEFAULT_WIDTH = 100; private static final int MIN_WIDTH = 80; public ThreadInfoMode threadInfoMode; private ContentMode contentMode; private OutputFormat format; public int threadLimit = 10; public int interval; public String threadNameFilter = null; private int width; public VMInfo vmInfo; public TopThreadInfo topThreadInfo; public ThreadPrinter threadPrinter; private WarningRule warning; // 纪录vjtop进程本身的消耗 private boolean isDebugCost = false; private long lastCpu = 0; private boolean shouldExit = false; private boolean firstTime = true; public boolean displayCommandHints = false; public VMDetailView(VMInfo vmInfo, OutputFormat format, ContentMode contentMode, ThreadInfoMode threadInfoMode, Integer width, Integer interval) throws Exception { this.vmInfo = vmInfo; this.topThreadInfo = new TopThreadInfo(vmInfo); this.threadPrinter = new ThreadPrinter(this); this.warning = vmInfo.warningRule; this.contentMode = contentMode; this.threadInfoMode = threadInfoMode; this.format = format; this.interval = interval; setWidth(width); if (contentMode == ContentMode.all || contentMode == ContentMode.thread) { vmInfo.initThreadInfoAbility(); } } public void printView() throws Exception { Formats.clearTerminal(); // 计算vjtop自身消耗 long iterationStartTime = 0; long iterationStartCpu = 0; if (isDebugCost) { iterationStartTime = System.currentTimeMillis(); iterationStartCpu = ((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()) .getProcessCpuTime(); } vmInfo.update(contentMode == ContentMode.all || contentMode == ContentMode.jvm); if (!checkState()) { return; } // 打印进程级别内容 if (contentMode == ContentMode.all || contentMode == ContentMode.jvm) { if (format == OutputFormat.text) { printJvmInfoAsText(); } else { printJvmInfoAsConsole(); } } // JMX更新失败,不打印后续一定需要JMX获取的数据 if (!vmInfo.isJmxStateOk()) { printJmxError(); return; } // 打印繁忙线程级别内容 if (contentMode == ContentMode.all || contentMode == ContentMode.thread) { try { if (threadInfoMode.isCpuMode) { printTopCpuThreads(threadInfoMode, format != OutputFormat.text); } else { printTopMemoryThreads(threadInfoMode, format != OutputFormat.text); } } catch (Exception e) { System.out.println(""); e.printStackTrace(); System.out.println(Formats.red("ERROR: Exception happen when fetch thread information via JMX")); } } // 打印vjtop自身消耗 if (isDebugCost) { printIterationCost(iterationStartTime, iterationStartCpu); } if (displayCommandHints) { System.out.print(" Input command (h for help):"); } } private boolean checkState() { if (vmInfo.state != VMInfo.VMInfoState.ATTACHED && vmInfo.state != VMInfo.VMInfoState.ATTACHED_UPDATE_ERROR) { System.out.println("\n" + Formats.red("ERROR: Could not attach to process, exit now.")); shoulExit(); return false; } return true; } private void printJvmInfoAsConsole() { System.out.printf(" %8tT - PID: %s JVM: %s USER: %s UPTIME: %s%n", new Date(), vmInfo.pid, vmInfo.jvmVersion, vmInfo.osUser, Formats.toTimeUnit(vmInfo.upTimeMills.current)); String[] cpuLoadAnsi = Formats.colorAnsi(vmInfo.cpuLoad, warning.cpu); System.out.printf(" PROCESS: %.2f%% cpu(%s%.2f%%%s of %d core)", vmInfo.singleCoreCpuLoad, cpuLoadAnsi[0], vmInfo.cpuLoad, cpuLoadAnsi[1], vmInfo.processors); if (vmInfo.isLinux) { System.out.printf(", %s thread%n", Formats.toColor(vmInfo.osThreads, warning.thread)); System.out.printf(" MEMORY: %s rss, %s peak, %s swap |", Formats.toMB(vmInfo.rss), Formats.toMB(vmInfo.peakRss), Formats.toMBWithColor(vmInfo.swap, warning.swap)); if (vmInfo.ioDataSupport) { System.out.printf(" DISK: %sB read, %sB write", Formats.toSizeUnitWithColor(vmInfo.readBytes.ratePerSecond, warning.io), Formats.toSizeUnitWithColor(vmInfo.writeBytes.ratePerSecond, warning.io)); } } System.out.println(); System.out.printf(" THREAD: %s live, %d daemon, %s peak, %s new", Formats.toColor(vmInfo.threadActive, warning.thread), vmInfo.threadDaemon, vmInfo.threadPeak, Formats.toColor(vmInfo.threadNew.delta, warning.newThread)); System.out.printf(" | CLASS: %s loaded, %d unloaded, %s new%n", Formats.toColor(vmInfo.classLoaded.current, warning.loadClass), vmInfo.classUnLoaded, Formats.toColor(vmInfo.classLoaded.delta, warning.newClass)); if (vmInfo.ygcStrategy.equals("ZGC")) { System.out.printf(" HEAP: %s%n", Formats.formatUsage(vmInfo.eden)); } else { System.out.printf(" HEAP: %s eden, %s sur, %s old%n", Formats.formatUsage(vmInfo.eden), Formats.formatUsage(vmInfo.sur), Formats.formatUsageWithColor(vmInfo.old, warning.old)); } System.out.printf(" NON-HEAP: %s %s, %s codeCache", Formats.formatUsageWithColor(vmInfo.perm, warning.perm), vmInfo.permGenName, Formats.formatUsageWithColor(vmInfo.codeCache, warning.codeCache)); if (vmInfo.jvmMajorVersion >= 8 && !vmInfo.ygcStrategy.equals("ZGC")) { System.out.printf(", %s ccs", Formats.formatUsage(vmInfo.ccs)); } System.out.println(""); System.out.printf(" OFF-HEAP: %s/%s direct(max=%s), %s/%s map(count=%d), %s threadStack%n", Formats.toMB(vmInfo.direct.used), Formats.toMB(vmInfo.direct.committed), Formats.toMB(vmInfo.direct.max), Formats.toMB(vmInfo.map.used), Formats.toMB(vmInfo.map.committed), vmInfo.map.max, Formats.toMB(vmInfo.threadStackSize * vmInfo.threadActive)); // gc strategy System.out.printf(" GC-STRATEGY: %s / %s%n", vmInfo.ygcStrategy, vmInfo.fullgcStrategy); // gc count long ygcCount = vmInfo.ygcCount.delta; long ygcTime = vmInfo.ygcTimeMills.delta; long avgYgcTime = ygcCount == 0 ? 0 : ygcTime / ygcCount; long fgcCount = vmInfo.fullgcCount.delta; if (vmInfo.ygcStrategy.equals("ZGC")) { System.out.printf(" GC: %s/%sms/%sms zgc", Formats.toColor(ygcCount, warning.ygcCount), Formats.toColor(ygcTime, warning.ygcTime), Formats.toColor(avgYgcTime, warning.ygcAvgTime)); } else { System.out.printf(" GC: %s/%sms/%sms ygc, %s/%dms fgc", Formats.toColor(ygcCount, warning.ygcCount), Formats.toColor(ygcTime, warning.ygcTime), Formats.toColor(avgYgcTime, warning.ygcAvgTime), Formats.toColor(fgcCount, warning.fullgcCount), vmInfo.fullgcTimeMills.delta); } if (vmInfo.perfDataSupport) { System.out.printf(" | SAFE-POINT: %s count, %sms time, %dms syncTime", Formats.toColor(vmInfo.safepointCount.delta, warning.safepointCount), Formats.toColor(vmInfo.safepointTimeMills.delta, warning.safepointTime), vmInfo.safepointSyncTimeMills.delta); } System.out.println(""); } private void printJvmInfoAsText() { System.out.printf("time:%8tT%npid:%s%njvm:%s%nuser:%s%nuptime:%s%n", new Date(), vmInfo.pid, vmInfo.jvmVersion, vmInfo.osUser, vmInfo.upTimeMills.current); System.out.printf("process.cpu.core:%.2f%nprocess.cpu.server:%.2f%nserver.core:%d%n", vmInfo.singleCoreCpuLoad, vmInfo.cpuLoad, vmInfo.processors); if (vmInfo.isLinux) { System.out.printf("process.thread:%d%nrss:%d%nrss.peak:%d%nswap:%d%n", vmInfo.osThreads, vmInfo.rss, vmInfo.peakRss, vmInfo.swap); if (vmInfo.ioDataSupport) { System.out.printf("disk.read:%d%ndisk.write:%d%n", vmInfo.readBytes.ratePerSecond, vmInfo.writeBytes.ratePerSecond, warning.io); } } System.out.printf("thread.live:%d%nthread.daemon:%d%nthread.peak:%d%nthread.new:%d%n", vmInfo.threadActive, vmInfo.threadDaemon, vmInfo.threadPeak, vmInfo.threadNew.delta); System.out.printf("class.loaded:%d%nclass.unloaded:%d%nclass.new:%d%n", vmInfo.classLoaded.current, vmInfo.classUnLoaded, vmInfo.classLoaded.delta); System.out.printf( "eden.use:%d%neden.commit:%d%neden.max:%d%nsur.use:%d%nsur.commit:%d%nsur.max:%d%nold.use:%d%nold.commit:%d%nold.max:%d%n", vmInfo.eden.used, vmInfo.eden.committed, vmInfo.eden.max, vmInfo.sur.used, vmInfo.sur.committed, vmInfo.sur.max, vmInfo.old.used, vmInfo.old.committed, vmInfo.old.max); System.out.printf( "%s.use:%d%n%s.commit:%d%n%s.max:%d%ncodeCache.use:%d%ncodeCache.commit:%d%ncodeCache.max:%d%n", vmInfo.permGenName, vmInfo.perm.used, vmInfo.permGenName, vmInfo.perm.committed, vmInfo.permGenName, vmInfo.perm.max, vmInfo.codeCache.used, vmInfo.codeCache.committed, vmInfo.codeCache.max); if (vmInfo.jvmMajorVersion >= 8) { System.out.printf("ccs.use:%d%nccs.commit:%d%nccs.max:%d%n", vmInfo.ccs.used, vmInfo.ccs.committed, vmInfo.ccs.max); } System.out.printf( "direct.use:%d%ndirect.commit:%d%ndirect.max:%d%nmap.use:%d%nmap.commit:%d%nmap.count:%d%nthreadStack:%d%n", vmInfo.direct.used, vmInfo.direct.committed, vmInfo.direct.max, vmInfo.map.used, vmInfo.map.committed, vmInfo.map.max, vmInfo.threadStackSize * vmInfo.threadActive); long ygcCount = vmInfo.ygcCount.delta; long ygcTime = vmInfo.ygcTimeMills.delta; long avgYgcTime = ygcCount == 0 ? 0 : ygcTime / ygcCount; long fgcCount = vmInfo.fullgcCount.delta; System.out.printf("ygc.count:%d%nygc.time:%d%nygc.avgtime::%d%nfgc.count:%d%nfgc.time:%d%n", ygcCount, ygcTime, avgYgcTime, fgcCount, vmInfo.fullgcTimeMills.delta); if (vmInfo.perfDataSupport) { System.out.printf("safePoint.count:%d%nsafePoint.time:%d%nsafePoint.syncTime:%d%n", vmInfo.safepointCount.delta, vmInfo.safepointTimeMills.delta, vmInfo.safepointSyncTimeMills.delta); } } private void printTopCpuThreads(ThreadInfoMode mode, boolean console) throws IOException { if (!vmInfo.threadCpuTimeSupported) { if (console) { System.out.printf("%n -Thread CPU telemetries are not available on the monitored jvm/platform-%n"); } return; } TopCpuResult result = topThreadInfo.topCpuThreads(mode, threadLimit); // 第一次无数据时跳过 if (!result.ready) { if (console) { printWelcome(); } return; } // 打印线程view的页头 String titleFormat = "%n %6s %-" + getThreadNameWidth() + "s %10s %6s %6s %6s %6s%n"; String dataFormat = " %6d %-" + getThreadNameWidth() + "s %10s %5.2f%% %5.2f%% %5.2f%% %5.2f%%%n"; String dataFormatAsText = "thread-%d:%s %s %.2f %.2f %.2f %.2f%n"; if (console) { System.out.printf(titleFormat, "TID", "NAME ", "STATE", "CPU", "SYSCPU", " TOTAL", "TOLSYS"); if (result.activeThreads == 0) { System.out.printf("%n -Every thread use cpu lower than 0.05%%-%n"); } } // 打印线程Detail for (ThreadInfo info : result.threadInfos) { if (info == null) { continue; } Long tid = info.getThreadId(); String threadName = Formats.shortName(info.getThreadName(), getThreadNameWidth(), 20); // 过滤threadName if (threadNameFilter != null && !threadName.toLowerCase().contains(threadNameFilter)) { continue; } // 刷新间隔里,所使用的单核CPU比例 double cpu = Utils.calcLoad(result.threadCpuDeltaTimes.get(tid), vmInfo.upTimeMills.delta, Utils.NANOS_TO_MILLS); double syscpu = Utils.calcLoad(result.threadSysCpuDeltaTimes.get(tid), vmInfo.upTimeMills.delta, Utils.NANOS_TO_MILLS); // 在进程所有消耗的CPU里,本线程的比例 double totalcpuPercent = Utils.calcLoad(result.threadCpuTotalTimes.get(tid), vmInfo.cpuTimeNanos.current, 1); double totalsysPercent = Utils.calcLoad(result.threadSysCpuTotalTimes.get(tid), vmInfo.cpuTimeNanos.current, 1); if (console) { System.out.printf(dataFormat, tid, threadName, Formats.leftStr(info.getThreadState().toString(), 10), cpu, syscpu, totalcpuPercent, totalsysPercent); } else { System.out.printf(dataFormatAsText, tid, threadName, info.getThreadState().toString(), cpu, syscpu, totalcpuPercent, totalsysPercent); } } // 打印线程汇总 double deltaAllActiveThreadCpuLoad = Utils.calcLoad(result.deltaAllActiveThreadCpu / Utils.NANOS_TO_MILLS, vmInfo.upTimeMills.delta); double deltaAllActiveThreadSysCpuLoad = Utils.calcLoad(result.deltaAllActiveThreadSysCpu / Utils.NANOS_TO_MILLS, vmInfo.upTimeMills.delta); double deltaAllFreeThreadCpuLoad = Utils.calcLoad(result.deltaAllFreeThreadCpu / Utils.NANOS_TO_MILLS, vmInfo.upTimeMills.delta); // double deltaAllFreeThreadSysCpuLoad = Utils.calcLoad(result.deltaAllFreeThreadSysCpu / Utils.NANOS_TO_MILLS, // vmInfo.upTimeMills.delta); if (console) { System.out.printf( "%n Total : %.2f%% cpu(user=%.2f%%, sys=%.2f%%) by %d active java threads, %.2f%% by others%n", deltaAllActiveThreadCpuLoad, deltaAllActiveThreadCpuLoad - deltaAllActiveThreadSysCpuLoad, deltaAllActiveThreadSysCpuLoad, result.activeThreads, deltaAllFreeThreadCpuLoad); System.out.printf(" Setting: top %d threads order by %s%s, flush every %ds%n", threadLimit, mode.toString().toUpperCase(), threadNameFilter == null ? "" : " filter by " + threadNameFilter, interval); } else { System.out.printf( "sum.active.threadCount:%d%nsum.active.cpu.total:%.2f%nsum.active.cpu.user:%.2f%nsum.active.cpu.sys:%.2f%nsum.free.cpu.total:%.2f%n", result.activeThreads, deltaAllActiveThreadCpuLoad, deltaAllActiveThreadCpuLoad - deltaAllActiveThreadSysCpuLoad, deltaAllActiveThreadSysCpuLoad, deltaAllFreeThreadCpuLoad); } } private void printTopMemoryThreads(ThreadInfoMode mode, boolean console) throws IOException { if (!vmInfo.threadMemoryAllocatedSupported) { if (console) { System.out.printf( "%n -Thread Memory Allocated telemetries are not available on the monitored jvm/platform-%n"); } return; } TopMemoryResult result = topThreadInfo.topMemoryThreads(mode, threadLimit); // 第一次无数据跳过 if (!result.ready) { if (console) { printWelcome(); } return; } // 打印线程View的页头 String titleFormat = "%n %6s %-" + getThreadNameWidth() + "s %10s %14s %18s%n"; String dataFormat = " %6d %-" + getThreadNameWidth() + "s %10s %5s/s(%5.2f%%) %10s(%5.2f%%)%n"; String dataFormatAsText = "thread-%d:%s %s %s %.2f %s %.2f%n"; if (console) { System.out.printf(titleFormat, "TID", "NAME ", "STATE", "MEMORY", "TOTAL-ALLOCATED"); if (result.activeThreads == 0) { System.out.printf("%n -Every thread allocate memory slower than 1k/s-%n"); } } // 打印线程Detail for (ThreadInfo info : result.threadInfos) { if (info == null) { continue; } Long tid = info.getThreadId(); String threadName = Formats.shortName(info.getThreadName(), getThreadNameWidth(), 12); // 过滤threadName if (threadNameFilter != null && !threadName.toLowerCase().contains(threadNameFilter)) { continue; } Long threadDelta = result.threadMemoryDeltaBytesMap.get(tid); long allocationRate = threadDelta == null ? 0 : (threadDelta * 1000) / vmInfo.upTimeMills.delta; if (console) { System.out.printf(dataFormat, tid, threadName, Formats.leftStr(info.getThreadState().toString(), 10), Formats.toFixLengthSizeUnit(allocationRate), Utils.calcMemoryUtilization(result.threadMemoryDeltaBytesMap.get(tid), result.deltaAllThreadBytes), Formats.toFixLengthSizeUnit(result.threadMemoryTotalBytesMap.get(tid)), Utils.calcMemoryUtilization(result.threadMemoryTotalBytesMap.get(tid), result.totalAllThreadBytes)); } else { System.out.printf(dataFormatAsText, tid, threadName, info.getThreadState().toString(), allocationRate, Utils.calcMemoryUtilization(result.threadMemoryDeltaBytesMap.get(tid), result.deltaAllThreadBytes), result.threadMemoryTotalBytesMap.get(tid), Utils.calcMemoryUtilization( result.threadMemoryTotalBytesMap.get(tid), result.totalAllThreadBytes)); } } if (console) { // 打印线程汇总信息,这里因为最后单位是精确到秒,所以bytes除以毫秒以后要乘以1000才是按秒统计 System.out.printf("%n Total : %5s/s memory allocated by %d active threads%n", Formats.toFixLengthSizeUnit((result.deltaAllThreadBytes * 1000) / vmInfo.upTimeMills.delta), result.activeThreads); System.out.printf(" Setting: top %d threads order by %s%s, flush every %ds%n", threadLimit, mode.toString().toUpperCase(), threadNameFilter == null ? "" : " filter by " + threadNameFilter, interval); } else { System.out.printf("sum.active.threadCount:%d%nsum.active.allocateRate:%d%n", result.activeThreads, (result.deltaAllThreadBytes * 1000) / vmInfo.upTimeMills.delta); } } private void printWelcome() { if (firstTime && contentMode != ContentMode.thread) { if (!vmInfo.isLinux) { System.out.printf( "%n" + Formats.yellow(" OS isn't linux, Process's MEMORY, THREAD, DISK data will be skipped.") + "%n"); } if (!vmInfo.ioDataSupport) { System.out.printf("%n" + Formats.yellow(" /proc/%s/io is not readable, Process's DISK data will be skipped.") + "%n", vmInfo.pid); } if (!vmInfo.perfDataSupport) { System.out.printf( "%n" + Formats.yellow(" Perfdata doesn't support, SAFE-POINT data will be skipped.") + "%n"); } System.out.printf("%n VMARGS: %s%n%n", vmInfo.vmArgs); firstTime = false; } System.out.printf("%n Collecting data, please wait ......%n%n"); } private void printJmxError() { if (!vmInfo.currentGcCause.equals("No GC")) { System.out.println("\n" + Formats.red( "ERROR: Could not fetch data via JMX - Process is doing GC, cause is " + vmInfo.currentGcCause)); } else { System.out.println( System.lineSeparator() + Formats.red("ERROR: Could not fetch data via JMX - Process terminated?")); } } private void printIterationCost(long iterationStartTime, long iterationStartCpu) { long currentCpu = ((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getProcessCpuTime(); long deltaIterationTime = System.currentTimeMillis() - iterationStartTime; long deltaIterationCpuTime = (currentCpu - iterationStartCpu) / Utils.NANOS_TO_MILLS; long deltaOtherCpuTime = (iterationStartCpu - lastCpu) / Utils.NANOS_TO_MILLS; long deltaTotalCpuTime = deltaIterationCpuTime + deltaOtherCpuTime; lastCpu = currentCpu; System.out.printf(" Cost %.2f%% cpu in %dms, other is %dms, total is %dms%n", deltaIterationCpuTime * 100d / deltaIterationTime, deltaIterationTime, deltaOtherCpuTime, deltaTotalCpuTime); } public void switchCpuAndMemory() { topThreadInfo.cleanupThreadsHistory(); } public boolean shouldExit() { return shouldExit; } /** * Requests the disposal of this view - it should be called again. */ public void shoulExit() { shouldExit = true; } private void setWidth(Integer width) { if (width == null) { this.width = DEFAULT_WIDTH; } else if (width < MIN_WIDTH) { this.width = MIN_WIDTH; } else { this.width = width; } } private int getThreadNameWidth() { return this.width - 48; } public enum ThreadInfoMode { cpu(true), totalcpu(true), syscpu(true), totalsyscpu(true), memory(false), totalmemory(false); public boolean isCpuMode; private ThreadInfoMode(boolean isCpuMode) { this.isCpuMode = isCpuMode; } public static ThreadInfoMode parse(String value) { try { return ThreadInfoMode.valueOf(value); } catch (IllegalArgumentException e) { throw new IllegalArgumentException( "wrong option of thread info mode(cpu,syscpu,totalcpu,totalsyscpu,memory,totalmemory)"); } } public static ThreadInfoMode parseInt(String mode) { switch (mode) { case "1": return cpu; case "2": return syscpu; case "3": return totalcpu; case "4": return totalsyscpu; case "5": return memory; case "6": return totalmemory; default: return null; } } } public enum OutputFormat { console(true), cleanConsole(false), text(false); OutputFormat(boolean ansi) { this.ansi = ansi; } public boolean ansi; } public enum ContentMode { all, jvm, thread } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/VMInfo.java ================================================ package com.vip.vjtools.vjtop; import java.io.IOException; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; import java.lang.management.ThreadInfo; import java.util.Locale; import java.util.Map; import com.sun.management.GarbageCollectorMXBean; import com.vip.vjtools.vjtop.data.PerfData; import com.vip.vjtools.vjtop.data.ProcFileData; import com.vip.vjtools.vjtop.data.jmx.JmxClient; import com.vip.vjtools.vjtop.data.jmx.JmxGarbageCollectorManager; import com.vip.vjtools.vjtop.data.jmx.JmxMemoryPoolManager; import com.vip.vjtools.vjtop.util.Formats; import com.vip.vjtools.vjtop.util.Utils; import sun.management.counter.Counter; import sun.management.counter.LongCounter; import sun.management.counter.StringCounter; @SuppressWarnings("restriction") public class VMInfo { private JmxClient jmxClient = null; private PerfData perfData = null; public boolean perfDataSupport = false; public VMInfoState state = VMInfoState.INIT; public String pid; private int jmxUpdateErrorCount; // 静态数据// private long startTime = 0; public String osUser; public String vmArgs = ""; public String jvmVersion = ""; public int jvmMajorVersion; public String permGenName; public long threadStackSize; public long maxDirectMemorySize; public int processors; public boolean isLinux; public boolean ioDataSupport = true;// 不是同一个用户,不能读/proc/PID/io public boolean processDataSupport = true; public boolean threadCpuTimeSupported; public boolean threadMemoryAllocatedSupported; public boolean threadContentionMonitoringSupported; public WarningRule warningRule = new WarningRule(); // 动态数据// public Rate upTimeMills = new Rate(); public Rate cpuTimeNanos = new Rate(); public long rss; public long peakRss; public long swap; public long osThreads; public Rate readBytes = new Rate(); public Rate writeBytes = new Rate(); public double cpuLoad = 0.0; public double singleCoreCpuLoad = 0.0; public String ygcStrategy = ""; public Rate ygcCount = new Rate(); public Rate ygcTimeMills = new Rate(); public String fullgcStrategy = ""; public Rate fullgcCount = new Rate(); public Rate fullgcTimeMills = new Rate(); public String currentGcCause = ""; public long threadActive; public long threadDaemon; public long threadPeak; public Rate threadNew = new Rate(); public Rate classLoaded = new Rate(); public long classUnLoaded; public Rate safepointCount = new Rate(); public Rate safepointTimeMills = new Rate(); public Rate safepointSyncTimeMills = new Rate(); public Usage eden; public Usage sur; public Usage old; public Usage perm; public Usage codeCache; public Usage ccs; public Usage direct; public Usage map; private LongCounter threadLiveCounter; private LongCounter threadDaemonCounter; private LongCounter threadPeakCounter; private LongCounter threadStartedCounter; private LongCounter classUnloadCounter; private LongCounter classLoadedCounter; private LongCounter ygcCountCounter; private LongCounter ygcTimeCounter; private LongCounter fullGcCountCounter; private LongCounter fullgcTimeCounter; private LongCounter safepointCountCounter; private LongCounter safepointTimeCounter; private LongCounter safepointSyncTimeCounter; private StringCounter currentGcCauseCounter; public VMInfo(JmxClient jmxClient, String vmId) throws Exception { this.jmxClient = jmxClient; this.state = VMInfoState.ATTACHED; this.pid = vmId; init(); } private VMInfo() { } /** * 创建JMX连接并构造VMInfo实例 */ public static VMInfo processNewVM(String pid, String jmxHostAndPort) { try { final JmxClient jmxClient = new JmxClient(); jmxClient.connect(pid, jmxHostAndPort); // 注册JMXClient注销的钩子 Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { jmxClient.disconnect(); } })); return new VMInfo(jmxClient, pid); } catch (Exception e) { e.printStackTrace(System.out); } return createDeadVM(pid, VMInfoState.ERROR_DURING_ATTACH); } /** * Creates a dead VMInfo, representing a jvm in a given state which cannot * be attached or other monitoring issues occurred. */ public static VMInfo createDeadVM(String pid, VMInfoState state) { VMInfo vmInfo = new VMInfo(); vmInfo.state = state; vmInfo.pid = pid; return vmInfo; } /** * 初始化静态数据 */ private void init() throws IOException { Map perfCounters = null; try { perfData = PerfData.connect(Integer.parseInt(pid)); perfCounters = perfData.getAllCounters(); initPerfCounters(perfCounters); perfDataSupport = true; } catch (Throwable ignored) { } if (perfDataSupport) { vmArgs = (String) perfCounters.get("java.rt.vmArgs").getValue(); } else { vmArgs = Formats.join(jmxClient.getRuntimeMXBean().getInputArguments(), " "); } startTime = jmxClient.getRuntimeMXBean().getStartTime(); Map taregetVMSystemProperties = jmxClient.getRuntimeMXBean().getSystemProperties(); osUser = taregetVMSystemProperties.get("user.name"); jvmVersion = taregetVMSystemProperties.get("java.version"); jvmMajorVersion = Utils.getJavaMajorVersion(taregetVMSystemProperties.get("java.specification.version")); permGenName = jvmMajorVersion >= 8 ? "metaspace" : "perm"; threadStackSize = 1024 * Long.parseLong(jmxClient.getHotSpotDiagnosticMXBean().getVMOption("ThreadStackSize").getValue()); maxDirectMemorySize = Long .parseLong(jmxClient.getHotSpotDiagnosticMXBean().getVMOption("MaxDirectMemorySize").getValue()); maxDirectMemorySize = maxDirectMemorySize == 0 ? -1 : maxDirectMemorySize; processors = jmxClient.getOperatingSystemMXBean().getAvailableProcessors(); warningRule.updateProcessor(processors); isLinux = System.getProperty("os.name").toLowerCase(Locale.US).contains("linux"); } public void initThreadInfoAbility() throws IOException { threadCpuTimeSupported = jmxClient.getThreadMXBean().isThreadCpuTimeSupported(); threadMemoryAllocatedSupported = jmxClient.getThreadMXBean().isThreadAllocatedMemorySupported(); threadContentionMonitoringSupported = jmxClient.getThreadMXBean().isThreadContentionMonitoringEnabled(); } /** * Updates all jvm metrics to the most recent remote values */ public void update(boolean needJvmInfo) { if (state == VMInfoState.ERROR_DURING_ATTACH || state == VMInfoState.DETACHED) { return; } try { int lastJmxErrorCount = jmxUpdateErrorCount; // 将UPDTATE_ERROR重置开始新一轮循环 state = VMInfoState.ATTACHED; // 清空JMX内部缓存 jmxClient.flush(); updateUpTime(); if (needJvmInfo) { if (isLinux) { updateProcessStatus(); updateIO(); } updateCpu(); updateThreads(); updateClassLoader(); updateMemoryPool(); updateGC(); updateSafepoint(); } // 无新异常,状态重新判定为正常 if (jmxUpdateErrorCount == lastJmxErrorCount) { jmxUpdateErrorCount = 0; } } catch (Throwable e) { // 其他非JMX异常,直接退出 e.printStackTrace(); System.out.flush(); state = VMInfoState.DETACHED; } } private void updateUpTime() { upTimeMills.update(System.currentTimeMillis() - startTime); warningRule.updateInterval(Math.max(1, upTimeMills.delta / 1000)); } private void updateProcessStatus() { if (!processDataSupport) { return; } Map procStatus = ProcFileData.getProcStatus(pid); if (procStatus.isEmpty()) { processDataSupport = false; return; } rss = Formats.parseFromSize(procStatus.get("VmRSS")); peakRss = Formats.parseFromSize(procStatus.get("VmHWM")); swap = Formats.parseFromSize(procStatus.get("VmSwap")); osThreads = Long.parseLong(procStatus.get("Threads")); } private void updateIO() { if (!ioDataSupport) { return; } Map procIo = ProcFileData.getProcIO(pid); if (procIo.isEmpty()) { ioDataSupport = false; return; } readBytes.update(Formats.parseFromSize(procIo.get("read_bytes"))); writeBytes.update(Formats.parseFromSize(procIo.get("write_bytes"))); readBytes.caculateRatePerSecond(upTimeMills.delta); writeBytes.caculateRatePerSecond(upTimeMills.delta); } private void updateCpu() { if (!isJmxStateOk()) { return; } try { cpuTimeNanos.update(jmxClient.getOperatingSystemMXBean().getProcessCpuTime()); singleCoreCpuLoad = Utils.calcLoad(cpuTimeNanos.delta / Utils.NANOS_TO_MILLS, upTimeMills.delta); cpuLoad = singleCoreCpuLoad / processors; } catch (Exception e) { handleJmxFetchDataError(e); } } private void updateThreads() { if (perfDataSupport) { threadActive = threadLiveCounter.longValue(); threadDaemon = threadDaemonCounter.longValue(); threadPeak = threadPeakCounter.longValue(); threadNew.update(threadStartedCounter.longValue()); } else if (isJmxStateOk()) { try { threadActive = jmxClient.getThreadMXBean().getThreadCount(); threadDaemon = jmxClient.getThreadMXBean().getDaemonThreadCount(); threadPeak = jmxClient.getThreadMXBean().getPeakThreadCount(); threadNew.update(jmxClient.getThreadMXBean().getTotalStartedThreadCount()); } catch (Exception e) { handleJmxFetchDataError(e); } } } private void updateClassLoader() { // 优先从perfData取值,注意此处loadedClasses 等于JMX的TotalLoadedClassCount if (perfDataSupport) { classUnLoaded = classUnloadCounter.longValue(); classLoaded.update(classLoadedCounter.longValue() - classUnLoaded); } else if (isJmxStateOk()) { try { classUnLoaded = jmxClient.getClassLoadingMXBean().getUnloadedClassCount(); classLoaded.update(jmxClient.getClassLoadingMXBean().getLoadedClassCount()); } catch (Exception e) { handleJmxFetchDataError(e); } } } private void updateMemoryPool() { if (!isJmxStateOk()) { return; } try { JmxMemoryPoolManager memoryPoolManager = jmxClient.getMemoryPoolManager(); MemoryPoolMXBean edenMXBean = memoryPoolManager.getEdenMemoryPool(); if (edenMXBean != null) { eden = new Usage(edenMXBean.getUsage()); } else { eden = new Usage(); } MemoryPoolMXBean oldMXBean = memoryPoolManager.getOldMemoryPool(); if (oldMXBean != null) { old = new Usage(oldMXBean.getUsage()); warningRule.updateOld(old.max); } else { old = new Usage(); } MemoryPoolMXBean survivorMemoryPool = memoryPoolManager.getSurvivorMemoryPool(); if (survivorMemoryPool != null) { sur = new Usage(survivorMemoryPool.getUsage()); } else { sur = new Usage(); } MemoryPoolMXBean permMXBean = memoryPoolManager.getPermMemoryPool(); if (permMXBean != null) { perm = new Usage(permMXBean.getUsage()); warningRule.updatePerm(perm.max); } else { perm = new Usage(); } if (jvmMajorVersion >= 8) { MemoryPoolMXBean compressedClassSpaceMemoryPool = memoryPoolManager.getCompressedClassSpaceMemoryPool(); if (compressedClassSpaceMemoryPool != null) { ccs = new Usage(compressedClassSpaceMemoryPool.getUsage()); } else { ccs = new Usage(); } } MemoryPoolMXBean memoryPoolMXBean = memoryPoolManager.getCodeCacheMemoryPool(); if (memoryPoolMXBean != null) { codeCache = new Usage(memoryPoolMXBean.getUsage()); } else { codeCache = new Usage(); } direct = new Usage(jmxClient.getBufferPoolManager().getDirectBufferPoolUsed(), jmxClient.getBufferPoolManager().getDirectBufferPoolCapacity(), maxDirectMemorySize); // 取巧用法,将count 放入无用的max中。 long mapUsed = jmxClient.getBufferPoolManager().getMappedBufferPoolUsed(); map = new Usage(mapUsed, jmxClient.getBufferPoolManager().getMappedBufferPoolCapacity(), mapUsed == 0 ? 0 : jmxClient.getBufferPoolManager().getMappedBufferPoolCount()); } catch (Exception e) { handleJmxFetchDataError(e); } } private void updateGC() { if (perfDataSupport) { ygcCount.update(ygcCountCounter.longValue()); ygcTimeMills.update(perfData.tickToMills(ygcTimeCounter)); if (fullGcCountCounter != null) { fullgcCount.update(fullGcCountCounter.longValue()); fullgcTimeMills.update(perfData.tickToMills(fullgcTimeCounter)); } } else if (isJmxStateOk()) { try { JmxGarbageCollectorManager gcManager = jmxClient.getGarbageCollectorManager(); GarbageCollectorMXBean ygcMXBean = gcManager.getYoungCollector(); ygcStrategy = gcManager.getYgcStrategy(); ygcCount.update(ygcMXBean.getCollectionCount()); ygcTimeMills.update(ygcMXBean.getCollectionTime()); GarbageCollectorMXBean fullgcMXBean = gcManager.getFullCollector(); if (fullgcMXBean != null) { fullgcStrategy = gcManager.getFgcStrategy(); fullgcCount.update(fullgcMXBean.getCollectionCount()); fullgcTimeMills.update(fullgcMXBean.getCollectionTime()); } } catch (Exception e) { handleJmxFetchDataError(e); } } } private void updateSafepoint() { if (!perfDataSupport) { return; } safepointCount.update(safepointCountCounter.longValue()); safepointTimeMills.update(perfData.tickToMills(safepointTimeCounter)); safepointSyncTimeMills.update(perfData.tickToMills(safepointSyncTimeCounter)); currentGcCause = (String) currentGcCauseCounter.getValue(); } public long[] getAllThreadIds() throws IOException { return jmxClient.getThreadMXBean().getAllThreadIds(); } public long[] getThreadCpuTime(long[] tids) throws IOException { return jmxClient.getThreadMXBean().getThreadCpuTime(tids); } public long[] getThreadUserTime(long[] tids) throws IOException { return jmxClient.getThreadMXBean().getThreadUserTime(tids); } public ThreadInfo[] getThreadInfo(long[] tids) throws IOException { return jmxClient.getThreadMXBean().getThreadInfo(tids); } public ThreadInfo getThreadInfo(long tid, int maxDepth) throws IOException { return jmxClient.getThreadMXBean().getThreadInfo(tid, maxDepth); } public ThreadInfo[] getThreadInfo(long[] tids, int maxDepth) throws IOException { return jmxClient.getThreadMXBean().getThreadInfo(tids, maxDepth); } public ThreadInfo[] getAllThreadInfo() throws IOException { return jmxClient.getThreadMXBean().dumpAllThreads(false, false); } public long[] getThreadAllocatedBytes(long[] tids) throws IOException { return jmxClient.getThreadMXBean().getThreadAllocatedBytes(tids); } private void initPerfCounters(Map perfCounters) { threadLiveCounter = (LongCounter) perfCounters.get("java.threads.live"); threadDaemonCounter = (LongCounter) perfCounters.get("java.threads.daemon"); threadPeakCounter = (LongCounter) perfCounters.get("java.threads.livePeak"); threadStartedCounter = (LongCounter) perfCounters.get("java.threads.started"); classUnloadCounter = (LongCounter) perfCounters.get("java.cls.unloadedClasses"); classLoadedCounter = (LongCounter) perfCounters.get("java.cls.loadedClasses"); ygcCountCounter = (LongCounter) perfCounters.get("sun.gc.collector.0.invocations"); ygcTimeCounter = (LongCounter) perfCounters.get("sun.gc.collector.0.time"); fullGcCountCounter = (LongCounter) perfCounters.get("sun.gc.collector.1.invocations"); fullgcTimeCounter = (LongCounter) perfCounters.get("sun.gc.collector.1.time"); safepointCountCounter = (LongCounter) perfCounters.get("sun.rt.safepoints"); safepointTimeCounter = (LongCounter) perfCounters.get("sun.rt.safepointTime"); safepointSyncTimeCounter = (LongCounter) perfCounters.get("sun.rt.safepointSyncTime"); currentGcCauseCounter = (StringCounter) perfCounters.get("sun.gc.cause"); } public void handleJmxFetchDataError(Throwable e) { System.out.println(""); e.printStackTrace(); System.out.flush(); jmxUpdateErrorCount++; // 连续三次刷新周期JMX 获取数据失败则退出 if (jmxUpdateErrorCount > 3) { state = VMInfoState.DETACHED; } else { state = VMInfoState.ATTACHED_UPDATE_ERROR; } } public boolean isJmxStateOk() { return state != VMInfoState.ATTACHED_UPDATE_ERROR && state != VMInfoState.DETACHED; } public enum VMInfoState { INIT, ERROR_DURING_ATTACH, ATTACHED, ATTACHED_UPDATE_ERROR, DETACHED } public static class Rate { private long last = -1; public long current = -1; public long delta = 0; public long ratePerSecond = 0; public void update(long current) { this.current = current; if (last != -1) { delta = current - last; } last = current; } public void caculateRatePerSecond(long deltaTimeMills) { if (delta != 0) { ratePerSecond = delta * 1000 / deltaTimeMills; } } } public static class Usage { public long used = -1; public long committed = -1; public long max = -1; public Usage() { } public Usage(long used, long committed, long max) { this.used = used; this.committed = committed; this.max = max; } public Usage(MemoryUsage jmxUsage) { this(jmxUsage.getUsed(), jmxUsage.getCommitted(), jmxUsage.getMax()); } } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/WarningRule.java ================================================ package com.vip.vjtools.vjtop; import com.vip.vjtools.vjtop.util.Formats; public class WarningRule { public DoubleWarning cpu = new DoubleWarning(50d, 70d); public LongWarning swap = new LongWarning(1, 1); public LongWarning thread = new LongWarning(); public LongWarning newThread = new LongWarning(1, Long.MAX_VALUE); public LongWarning io = new LongWarning(100 * Formats.MB_SIZE, Long.MAX_VALUE); public LongWarning loadClass = new LongWarning(80000, Long.MAX_VALUE); public LongWarning newClass = new LongWarning(1, Long.MAX_VALUE); public LongWarning old = new LongWarning(); public LongWarning codeCache = new LongWarning(); public LongWarning perm = new LongWarning(); public LongWarning ygcCount = new LongWarning(); public LongWarning ygcTime = new LongWarning(); public LongWarning ygcAvgTime = new LongWarning(100, 200); public LongWarning fullgcCount = new LongWarning(1, 2); public LongWarning safepointCount = new LongWarning(); public LongWarning safepointTime = new LongWarning(); public void updateProcessor(int processors) { thread.yellow = processors <= 8 ? processors * 150 : Math.max(8 * 150, processors * 100); thread.red = processors <= 8 ? processors * 225 : Math.max(8 * 225, processors * 150); } public void updateInterval(long intervalSeconds) { ygcTime.yellow = intervalSeconds * 1000 * 5 / 100; // 5% interval ygcTime.red = intervalSeconds * 1000 * 10 / 100; // 10% interval ygcCount.yellow = intervalSeconds + 1; ygcCount.red = intervalSeconds * 2 + 1; safepointCount.yellow = intervalSeconds * 2; safepointCount.red = intervalSeconds * 4; safepointTime.yellow = intervalSeconds * 1000 * 5 / 100; // 5% interval safepointTime.red = intervalSeconds * 1000 * 10 / 100; // 10% interval } public void updateOld(long max) { if (max != -1) { old.yellow = max * 85 / 100; old.red = max * 95 / 100; } } public void updatePerm(long max) { if (max != -1) { perm.yellow = max * 85 / 100; perm.red = max * 95 / 100; } } public void updateCodeCache(long max) { if (max != -1) { codeCache.yellow = max * 85 / 100; codeCache.red = max * 95 / 100; } } public static class DoubleWarning { public double yellow; public double red; public DoubleWarning() { yellow = Double.MAX_VALUE; red = Double.MAX_VALUE; } public DoubleWarning(double yellow, double red) { this.yellow = yellow; this.red = red; } } public static class LongWarning { public long yellow; public long red; public LongWarning() { yellow = Long.MAX_VALUE; red = Long.MAX_VALUE; } public LongWarning(long yellow, long red) { this.yellow = yellow; this.red = red; } } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/data/PerfData.java ================================================ package com.vip.vjtools.vjtop.data; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import com.vip.vjtools.vjtop.util.Utils; import sun.management.counter.Counter; import sun.management.counter.LongCounter; import sun.management.counter.perf.PerfInstrumentation; import sun.misc.Perf; @SuppressWarnings("restriction") public class PerfData { private final PerfInstrumentation instr; // PerfData中的时间相关数据以tick表示,每个tick的时长与计算机频率相关 private final double nanosPerTick; private final Map counters; public static PerfData connect(int pid) { try { return new PerfData(pid); } catch (ThreadDeath e) { throw e; } catch (OutOfMemoryError e) { throw e; } catch (Throwable e) { throw new RuntimeException("Cannot perf data for process " + pid + " - " + e.toString()); } } private PerfData(int pid) throws IOException { ByteBuffer bb = Perf.getPerf().attach(pid, "r"); instr = new PerfInstrumentation(bb); counters = buildAllCounters(); long hz = (Long) counters.get("sun.os.hrt.frequency").getValue(); nanosPerTick = ((double) TimeUnit.SECONDS.toNanos(1)) / hz; } private Map buildAllCounters() { Map result = new HashMap<>(512); for (Counter c : instr.getAllCounters()) { result.put(c.getName(), c); } return result; } public Map getAllCounters() { return counters; } public Counter findCounter(String counterName) { return counters.get(counterName); } public long tickToMills(LongCounter tickCounter) { if (tickCounter.getUnits() == sun.management.counter.Units.TICKS) { return (long) ((nanosPerTick * tickCounter.longValue()) / Utils.NANOS_TO_MILLS); } else { throw new IllegalArgumentException(tickCounter.getName() + " is not a ticket counter"); } } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/data/ProcFileData.java ================================================ package com.vip.vjtools.vjtop.data; import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; // See http://man7.org/linux/man-pages/man5/proc.5.html for /proc file details public class ProcFileData { private static final String PROC_SELF_STATUS_FILE_TPL = "/proc/%s/status"; private static final String PROC_SELF_IO_FILE_TPL = "/proc/%s/io"; private static final String VALUE_SEPARATOR = ":"; public static Map getProcStatus(String pid) { return getProcFileAsMap(String.format(PROC_SELF_STATUS_FILE_TPL, pid)); } public static Map getProcIO(String pid) { return getProcFileAsMap(String.format(PROC_SELF_IO_FILE_TPL, pid)); } public static Map getProcFileAsMap(String filePath) { try { File file = new File(filePath); if (!file.exists() || file.isDirectory() || !file.canRead()) { return Collections.emptyMap(); } List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); Map result = new HashMap<>(lines.size() * 2); for (String line : lines) { int index = line.indexOf(VALUE_SEPARATOR); if (index <= 0 || index >= line.length() - 1) { continue; } String key = line.substring(0, index).trim(); String value = line.substring(index + 1).trim(); result.put(key, value); } return result; } catch (Throwable ex) { ex.printStackTrace(); return Collections.emptyMap(); } } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/data/jmx/JmxBufferPoolManager.java ================================================ package com.vip.vjtools.vjtop.data.jmx; import java.io.IOException; import java.lang.management.BufferPoolMXBean; import java.lang.management.ManagementFactory; import java.util.List; import javax.management.MBeanServerConnection; public class JmxBufferPoolManager { private static final String DIRECT = "direct"; private static final String MAPPED = "mapped"; private BufferPoolMXBean directBufferPool = null; private BufferPoolMXBean mappedBufferPool = null; public JmxBufferPoolManager(MBeanServerConnection connection) throws IOException { List bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(connection, BufferPoolMXBean.class); if (bufferPoolMXBeans == null) { return; } for (BufferPoolMXBean bufferPool : bufferPoolMXBeans) { String name = bufferPool.getName().toLowerCase().trim(); if (name.contains(DIRECT)) { directBufferPool = bufferPool; } else if (name.contains(MAPPED)) { mappedBufferPool = bufferPool; } } } public long getDirectBufferPoolUsed() { return directBufferPool != null ? directBufferPool.getMemoryUsed() : 0; } public long getDirectBufferPoolCapacity() { return directBufferPool != null ? directBufferPool.getTotalCapacity() : 0; } public long getMappedBufferPoolUsed() { return mappedBufferPool != null ? mappedBufferPool.getMemoryUsed() : 0; } public long getMappedBufferPoolCapacity() { return mappedBufferPool != null ? mappedBufferPool.getTotalCapacity() : 0; } public long getMappedBufferPoolCount() { return mappedBufferPool != null ? mappedBufferPool.getCount() : 0; } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/data/jmx/JmxClient.java ================================================ /* * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES * OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public * License version 2 only, as published by the Free Software Foundation. Oracle designates this particular file as * subject to the "Classpath" exception as provided by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License version 2 for * more details (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU General Public License version 2 along with this work; if not, write to * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA or visit www.oracle.com if you need * additional information or have any questions. * */ package com.vip.vjtools.vjtop.data.jmx; import java.io.File; import java.io.IOException; import java.lang.management.ClassLoadingMXBean; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.InstanceNotFoundException; import javax.management.JMX; import javax.management.MBeanException; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import com.sun.management.HotSpotDiagnosticMXBean; import com.sun.management.OperatingSystemMXBean; import com.sun.management.ThreadMXBean; import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.VirtualMachine; import com.vip.vjtools.vjtop.util.Utils; @SuppressWarnings("restriction") public class JmxClient { private static final String LOCAL_CONNECTOR_ADDRESS_PROP = "com.sun.management.jmxremote.localConnectorAddress"; private boolean hasPlatformMXBeans = false; private String pid; private MBeanServerConnection mbsc = null; private SnapshotMBeanServerConnection server = null; private JMXConnector jmxc = null; private ClassLoadingMXBean classLoadingMBean = null; private OperatingSystemMXBean operatingSystemMBean = null; private RuntimeMXBean runtimeMBean = null; private HotSpotDiagnosticMXBean hotSpotDiagnosticMXBean = null; private ThreadMXBean threadMBean = null; private JmxGarbageCollectorManager garbageCollectorManager = null; private JmxMemoryPoolManager memoryPoolManager = null; private JmxBufferPoolManager bufferPoolManager = null; public JmxClient() throws IOException { } public void flush() { if (server != null) { server.flush(); } } public void connect(String pid, String jmxHostAndPort) throws Exception { this.pid = pid; if (jmxHostAndPort != null) { JMXServiceURL jmxUrl = new JMXServiceURL( "service:jmx:rmi://" + jmxHostAndPort + "/jndi/rmi://" + jmxHostAndPort + "/jmxrmi"); Map credentials = new HashMap(1); String[] creds = new String[]{null, null}; credentials.put(JMXConnector.CREDENTIALS, creds); this.jmxc = JMXConnectorFactory.connect(jmxUrl, credentials); } else { // 如果jmx agent未启动,主动attach进JVM后加载 String address = attachToGetConnectorAddress(); JMXServiceURL jmxUrl = new JMXServiceURL(address); this.jmxc = JMXConnectorFactory.connect(jmxUrl);// NOSONAR } this.mbsc = jmxc.getMBeanServerConnection(); this.server = Snapshot.newSnapshot(mbsc); try { ObjectName on = createBeanName(ManagementFactory.THREAD_MXBEAN_NAME); this.hasPlatformMXBeans = server.isRegistered(on); } catch (Exception e) { // should not reach here throw new InternalError(e.getMessage()); } if (hasPlatformMXBeans) { // WORKAROUND for bug 5056632 // Check if the access role is correct by getting a RuntimeMXBean getRuntimeMXBean(); } } /** * 因为已在退出JVM,因此仅关闭jmx连接即可 */ public void disconnect() { // Close MBeanServer connection if (jmxc != null) { try { jmxc.close(); } catch (IOException e) { // Ignore ??? } } } public synchronized ClassLoadingMXBean getClassLoadingMXBean() throws IOException { if (hasPlatformMXBeans && classLoadingMBean == null) { classLoadingMBean = ManagementFactory.newPlatformMXBeanProxy(server, ManagementFactory.CLASS_LOADING_MXBEAN_NAME, ClassLoadingMXBean.class); } return classLoadingMBean; } public synchronized RuntimeMXBean getRuntimeMXBean() throws IOException { if (hasPlatformMXBeans && runtimeMBean == null) { runtimeMBean = ManagementFactory.newPlatformMXBeanProxy(server, ManagementFactory.RUNTIME_MXBEAN_NAME, RuntimeMXBean.class); } return runtimeMBean; } public synchronized OperatingSystemMXBean getOperatingSystemMXBean() throws IOException { if (hasPlatformMXBeans && operatingSystemMBean == null) { operatingSystemMBean = ManagementFactory.newPlatformMXBeanProxy(server, ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME, OperatingSystemMXBean.class); } return operatingSystemMBean; } public synchronized HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException { if (hasPlatformMXBeans && hotSpotDiagnosticMXBean == null) { hotSpotDiagnosticMXBean = ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class); } return hotSpotDiagnosticMXBean; } public synchronized ThreadMXBean getThreadMXBean() throws IOException { if (hasPlatformMXBeans && threadMBean == null) { threadMBean = JMX.newMXBeanProxy(server, createBeanName(ManagementFactory.THREAD_MXBEAN_NAME), ThreadMXBean.class); } return threadMBean; } public synchronized JmxMemoryPoolManager getMemoryPoolManager() throws IOException { if (hasPlatformMXBeans && memoryPoolManager == null) { memoryPoolManager = new JmxMemoryPoolManager(server); } return memoryPoolManager; } public synchronized JmxBufferPoolManager getBufferPoolManager() throws IOException { if (hasPlatformMXBeans && bufferPoolManager == null) { bufferPoolManager = new JmxBufferPoolManager(server); } return bufferPoolManager; } public synchronized JmxGarbageCollectorManager getGarbageCollectorManager() throws IOException { if (hasPlatformMXBeans && garbageCollectorManager == null) { garbageCollectorManager = new JmxGarbageCollectorManager(server); } return garbageCollectorManager; } private ObjectName createBeanName(String beanName) { try { return new ObjectName(beanName); } catch (MalformedObjectNameException e) { throw new RuntimeException(e); } } @Override public String toString() { return "JMX Client for PID:" + pid; } /** * VirtualMachine保证JMX Agent已启动, * 并向JMXClient提供连接地址地址样例:service:jmx:rmi://127.0 * .0.1/stub/rO0ABXN9AAAAAQAl... */ public String attachToGetConnectorAddress() throws Exception { VirtualMachine vm = null; // 1. attach vm vm = VirtualMachine.attach(pid); try { // 2. 检查smartAgent是否已启动 Properties agentProps = vm.getAgentProperties(); String address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); if (address != null) { return address; } // 3. 未启动,尝试启动 String home = vm.getSystemProperties().getProperty("java.home"); int version = Utils.getJavaMajorVersion(vm.getSystemProperties().getProperty("java.specification.version")); if (version >= 8) { vm.startLocalManagementAgent(); agentProps = vm.getAgentProperties(); address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); if (address != null) { return address; } } else { // Normally in ${java.home}/jre/lib/management-agent.jar but might // be in ${java.home}/lib in build environments. String agentPath = home + File.separator + "jre" + File.separator + "lib" + File.separator + "management-agent.jar"; File f = new File(agentPath); if (!f.exists()) { agentPath = home + File.separator + "lib" + File.separator + "management-agent.jar"; f = new File(agentPath); if (!f.exists()) { throw new IOException("Management agent not found"); } } agentPath = f.getCanonicalPath(); try { vm.loadAgent(agentPath, "com.sun.management.jmxremote"); } catch (AgentLoadException x) { // 高版本 attach 低版本jdk 抛异常:com.sun.tools.attach.AgentLoadException: 0,实际上是成功的; // 根因: HotSpotVirtualMachine.loadAgentLibrary 高版本jdk实现不一样了 if (!"0".equals(x.getMessage())) { IOException ioe = new IOException(x.getMessage()); ioe.initCause(x); throw ioe; } } catch (AgentInitializationException x) { IOException ioe = new IOException(x.getMessage()); ioe.initCause(x); throw ioe; } // 4. 再次获取connector address agentProps = vm.getAgentProperties(); address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); if (address == null) { throw new IOException("Fails to find connector address"); } } agentProps = vm.getAgentProperties(); address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); return address; } finally { vm.detach(); } } // JDK自带的Snapshot实现,在flush前缓存值 // Snapshot MBeanServerConnection: // // This is an object that wraps an existing MBeanServerConnection and adds // caching to it, as follows: // // - The first time an attribute is called in a given MBean, the result is // cached. Every subsequent time getAttribute is called for that attribute // the cached result is returned. // // - Before every call to VMPanel.update() or when the Refresh button in the // Attributes table is pressed down the attributes cache is flushed. Then // any subsequent call to getAttribute will retrieve all the values for // the attributes that are known to the cache. // // - The attributes cache uses a learning approach and only the attributes // that are in the cache will be retrieved between two subsequent updates. // public interface SnapshotMBeanServerConnection extends MBeanServerConnection { /** * Flush all cached values of attributes. */ void flush(); } public static class Snapshot { private Snapshot() { } public static SnapshotMBeanServerConnection newSnapshot(MBeanServerConnection mbsc) { final InvocationHandler ih = new SnapshotInvocationHandler(mbsc); return (SnapshotMBeanServerConnection) Proxy.newProxyInstance(Snapshot.class.getClassLoader(), new Class[]{SnapshotMBeanServerConnection.class}, ih); } } static class SnapshotInvocationHandler implements InvocationHandler { private final MBeanServerConnection conn; private Map cachedValues = newMap(); private Map> cachedNames = newMap(); @SuppressWarnings("serial") private static final class NameValueMap extends HashMap { } SnapshotInvocationHandler(MBeanServerConnection conn) { this.conn = conn; } synchronized void flush() { cachedValues = newMap(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final String methodName = method.getName(); if (methodName.equals("getAttribute")) { return getAttribute((ObjectName) args[0], (String) args[1]); } else if (methodName.equals("getAttributes")) { return getAttributes((ObjectName) args[0], (String[]) args[1]); } else if (methodName.equals("flush")) { flush(); return null; } else { try { return method.invoke(conn, args); } catch (InvocationTargetException e) { throw e.getCause(); } } } private Object getAttribute(ObjectName objName, String attrName) throws MBeanException, InstanceNotFoundException, AttributeNotFoundException, ReflectionException, IOException { final NameValueMap values = getCachedAttributes(objName, Collections.singleton(attrName)); Object value = values.get(attrName); if (value != null || values.containsKey(attrName)) { return value; } // Not in cache, presumably because it was omitted from the // getAttributes result because of an exception. Following // call will probably provoke the same exception. return conn.getAttribute(objName, attrName); } private AttributeList getAttributes(ObjectName objName, String[] attrNames) throws InstanceNotFoundException, ReflectionException, IOException { final NameValueMap values = getCachedAttributes(objName, new TreeSet(Arrays.asList(attrNames))); final AttributeList list = new AttributeList(); for (String attrName : attrNames) { final Object value = values.get(attrName); if (value != null || values.containsKey(attrName)) { list.add(new Attribute(attrName, value)); } } return list; } private synchronized NameValueMap getCachedAttributes(ObjectName objName, Set attrNames) throws InstanceNotFoundException, ReflectionException, IOException { NameValueMap values = cachedValues.get(objName); if (values != null && values.keySet().containsAll(attrNames)) { return values; } attrNames = new TreeSet(attrNames); Set oldNames = cachedNames.get(objName); if (oldNames != null) { attrNames.addAll(oldNames); } values = new NameValueMap(); final AttributeList attrs = conn.getAttributes(objName, attrNames.toArray(new String[attrNames.size()])); for (Attribute attr : attrs.asList()) { values.put(attr.getName(), attr.getValue()); } cachedValues.put(objName, values); cachedNames.put(objName, attrNames); return values; } // See http://www.artima.com/weblogs/viewpost.jsp?thread=79394 private static Map newMap() { return new HashMap(); } } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/data/jmx/JmxGarbageCollectorManager.java ================================================ package com.vip.vjtools.vjtop.data.jmx; import java.io.IOException; import java.lang.management.ManagementFactory; import java.util.List; import javax.management.MBeanServerConnection; import com.sun.management.GarbageCollectorMXBean; public class JmxGarbageCollectorManager { private GarbageCollectorMXBean ygcMXBean = null; private GarbageCollectorMXBean fgcMXBean = null; private String ygcStrategy = null; private String fgcStrategy = null; public static String getByGcName(String gcName, String defaultName) { return defaultName; } public JmxGarbageCollectorManager(MBeanServerConnection connection) throws IOException { if (ygcMXBean != null || fgcMXBean != null) { return; } List gcMXBeans = ManagementFactory.getPlatformMXBeans(connection, GarbageCollectorMXBean.class); for (GarbageCollectorMXBean gcMXBean : gcMXBeans) { String gcName = gcMXBean.getName(); if ("Copy".equals(gcName) || "PS Scavenge".equals(gcName) || "ParNew".equals(gcName) || "G1 Young Generation".equals(gcName) || "ZGC".equals(gcName)) { ygcMXBean = gcMXBean; ygcStrategy = gcName; } else if ("MarkSweepCompact".equals(gcName) || "PS MarkSweep".equals(gcName) || "ConcurrentMarkSweep".equals(gcName) || "G1 Old Generation".equals(gcName)) { fgcMXBean = gcMXBean; fgcStrategy = gcName; } else { // default ygcMXBean = gcMXBean; ygcStrategy = gcName; } } } public synchronized GarbageCollectorMXBean getYoungCollector() { return ygcMXBean; } public synchronized GarbageCollectorMXBean getFullCollector() { return fgcMXBean; } public String getYgcStrategy() { return ygcStrategy; } public String getFgcStrategy() { return fgcStrategy; } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/data/jmx/JmxMemoryPoolManager.java ================================================ package com.vip.vjtools.vjtop.data.jmx; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; import java.util.List; import javax.management.MBeanServerConnection; public class JmxMemoryPoolManager { public static final String EDEN = "eden"; public static final String SURVIVOR = "survivor"; public static final String OLD = "old"; public static final String TENURED = "tenured"; // 并行GC算法老生代的名称 public static final String PERM = "perm"; public static final String METASPACE = "metaspace";// JDK8永久代名称 public static final String CODE_CACHE = "code cache"; public static final String CODEHEAP = "codeheap"; public static final String CODECACHE = "codecache"; public static final String ZHEAP = "zheap"; // for zgc public static final String COMPRESSED_CLASS_SPACE = "compressed class space"; // zgc not support until jdk15 private MemoryPoolMXBean survivorMemoryPool = null; private MemoryPoolMXBean edenMemoryPool = null; private MemoryPoolMXBean oldMemoryPool = null; private MemoryPoolMXBean permMemoryPool = null; private MemoryPoolMXBean codeCacheMemoryPool = null; private MemoryPoolMXBean compressedClassSpaceMemoryPool = null; public JmxMemoryPoolManager(MBeanServerConnection connection) throws IOException { List memoryPoolMXBeans = ManagementFactory.getPlatformMXBeans(connection, MemoryPoolMXBean.class); for (MemoryPoolMXBean memoryPool : memoryPoolMXBeans) { String name = memoryPool.getName().trim(); String lowerCaseName = name.toLowerCase(); if (lowerCaseName.contains(SURVIVOR)) { survivorMemoryPool = memoryPool; } else if (lowerCaseName.contains(EDEN) || lowerCaseName.contains(ZHEAP)) { edenMemoryPool = memoryPool; } else if (lowerCaseName.contains(OLD) || lowerCaseName.contains(TENURED)) { oldMemoryPool = memoryPool; } else if (lowerCaseName.contains(PERM) || lowerCaseName.contains(METASPACE)) { permMemoryPool = memoryPool; } else if (lowerCaseName.contains(CODE_CACHE) || lowerCaseName.contains(CODEHEAP) || lowerCaseName.contains(CODECACHE)) { codeCacheMemoryPool = memoryPool; } else if (lowerCaseName.contains(COMPRESSED_CLASS_SPACE)) { compressedClassSpaceMemoryPool = memoryPool; } } } public MemoryPoolMXBean getSurvivorMemoryPool() { return survivorMemoryPool; } public MemoryPoolMXBean getEdenMemoryPool() { return edenMemoryPool; } public MemoryPoolMXBean getOldMemoryPool() { return oldMemoryPool; } public MemoryPoolMXBean getPermMemoryPool() { return permMemoryPool; } public MemoryPoolMXBean getCodeCacheMemoryPool() { return codeCacheMemoryPool; } public MemoryPoolMXBean getCompressedClassSpaceMemoryPool() { return compressedClassSpaceMemoryPool; } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/util/Formats.java ================================================ package com.vip.vjtools.vjtop.util; import java.util.List; import java.util.Locale; import com.vip.vjtools.vjtop.VMInfo.Usage; import com.vip.vjtools.vjtop.WarningRule.DoubleWarning; import com.vip.vjtools.vjtop.WarningRule.LongWarning; public class Formats { private static final long BYTE_SIZE = 1; private static final long KB_SIZE = BYTE_SIZE * 1024; public static final long MB_SIZE = KB_SIZE * 1024; private static final long GB_SIZE = MB_SIZE * 1024; private static final long TB_SIZE = GB_SIZE * 1024; private static String[] RED_ANSI = new String[] { "\033[31m\033[01m", "\033[0m" }; private static String[] YELLOW_ANSI = new String[] { "\033[33m\033[01m", "\033[0m" }; private static final String[] NORMAL_ANSI = new String[] { "", "" }; private static String CLEAR_TERMINAL_ANSI_CMD = new String( new byte[] { (byte) 0x1b, (byte) 0x5b, (byte) 0x32, (byte) 0x4a, (byte) 0x1b, (byte) 0x5b, (byte) 0x48 }); public static boolean isWindows = System.getProperty("os.name").toLowerCase(Locale.US).contains("windows"); { if (isWindows) { disableAnsi(); CLEAR_TERMINAL_ANSI_CMD = "%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n"; } } public static void disableAnsi() { RED_ANSI = NORMAL_ANSI; YELLOW_ANSI = NORMAL_ANSI; } public static void setCleanClearTerminal() { CLEAR_TERMINAL_ANSI_CMD = "%n%n"; } public static void setTextClearTerminal() { CLEAR_TERMINAL_ANSI_CMD = "%n"; } public static String toMBWithColor(long bytes, LongWarning warning) { String[] ansi = colorAnsi(bytes, warning); return ansi[0] + toMB(bytes) + ansi[1]; } public static String toColor(long value, LongWarning warning) { String[] ansi = colorAnsi(value, warning); return ansi[0] + value + ansi[1]; } public static String red(String value) { return RED_ANSI[0] + value + RED_ANSI[1]; } public static String yellow(String value) { return YELLOW_ANSI[0] + value + YELLOW_ANSI[1]; } /** * Formats a long value containing "number of bytes" to its megabyte representation. If the value is negative, "n/a" * will be returned. */ public static String toMB(long bytes) { if (bytes < 0) { return "NaN"; } long mb = bytes / MB_SIZE; if (mb < 9999) { return mb + "m"; } else { return toSizeUnit(bytes).trim(); } } public static String toSizeUnitWithColor(Long size, LongWarning warning) { String[] ansi = colorAnsi(size, warning); return ansi[0] + toSizeUnit(size) + ansi[1]; } public static String toSizeUnit(Long size) { if (size == null) { return "NaN"; } if (size < KB_SIZE) { return size.toString(); } if (size < MB_SIZE) { return (size / KB_SIZE) + "k"; } if (size < GB_SIZE) { return (size / MB_SIZE) + "m"; } if (size < TB_SIZE) { return (size / GB_SIZE) + "g"; } return (size / TB_SIZE) + "t"; } public static String toFixLengthSizeUnit(Long size) { if (size == null) { return "NaN"; } if (size < KB_SIZE) { return String.format("%4d", size); } if (size < MB_SIZE) { return String.format("%4dk", size / KB_SIZE); } if (size < GB_SIZE) { return String.format("%4dm", size / MB_SIZE); } if (size < TB_SIZE) { return String.format("%4dg", size / GB_SIZE); } return String.format("%4dt", size / TB_SIZE); } public static String toTimeUnit(long millis) { long seconds = millis / 1000; if (seconds < 60) { return String.format("%02ds", seconds); } if (seconds < 3600) { return String.format("%02dm%02ds", seconds / 60, seconds % 60); } if (seconds < (24 * 3600)) { return String.format("%02dh%02dm", seconds / 3600, (seconds / 60) % 60); } return String.format("%dd%02dh", seconds / (3600 * 24), (seconds / 3600) % 24); } public static String formatUsage(Usage usage) { if (usage.committed == usage.max) { return String.format("%s/%s", toMB(usage.used), toMB(usage.max)); } else { return String.format("%s/%s/%s", toMB(usage.used), toMB(usage.committed), toMB(usage.max)); } } public static String formatUsageWithColor(Usage usage, LongWarning warning) { String[] ansi = colorAnsi(usage.used, warning); return ansi[0] + formatUsage(usage) + ansi[1]; } public static String[] colorAnsi(long value, LongWarning warning) { if (value < warning.yellow) { return NORMAL_ANSI; } else if (value >= warning.red) { return RED_ANSI; } else { return YELLOW_ANSI; } } public static String[] colorAnsi(double value, DoubleWarning warning) { if (value < warning.yellow) { return NORMAL_ANSI; } else if (value >= warning.red) { return RED_ANSI; } else { return YELLOW_ANSI; } } /** * Returns a substring of the given string, representing the 'length' most-right characters */ public static String rightStr(String str, int length) { return str.substring(Math.max(0, str.length() - length)); } /** * Returns a substring of the given string, representing the 'length' most-left characters */ public static String leftStr(String str, int length) { return str.substring(0, Math.min(str.length(), length)); } /** * shortName("123456789", 8, 3) = "12...789" * * shortName("123456789", 8, 2) = "123...89" */ public static String shortName(String str, int length, int rightLength) { if (str.length() > length) { int leftIndex = length - 3 - rightLength; str = str.substring(0, Math.max(0, leftIndex)) + "..." + str.substring(Math.max(0, str.length() - rightLength), str.length()); } return str; } /** * Joins the given list of strings using the given delimiter delim */ public static String join(List list, String delim) { StringBuilder sb = new StringBuilder(); String loopDelim = ""; for (String s : list) { sb.append(loopDelim); sb.append(s); loopDelim = delim; } return sb.toString(); } public static long parseFromSize(String str) { if (str == null || str.isEmpty()) { return -1; } str = str.toLowerCase(); long fromScale = BYTE_SIZE; try { if (str.endsWith("kb")) { str = str.substring(0, str.length() - 2).trim(); fromScale = KB_SIZE; } if (str.endsWith("k")) { str = str.substring(0, str.length() - 1).trim(); fromScale = KB_SIZE; } else if (str.endsWith("mb")) { str = str.substring(0, str.length() - 2).trim(); fromScale = MB_SIZE; } else if (str.endsWith("m")) { str = str.substring(0, str.length() - 1).trim(); fromScale = MB_SIZE; } else if (str.endsWith("gb")) { str = str.substring(0, str.length() - 2).trim(); fromScale = GB_SIZE; } else if (str.endsWith("g")) { str = str.substring(0, str.length() - 1).trim(); fromScale = GB_SIZE; } else if (str.endsWith("tb")) { str = str.substring(0, str.length() - 2).trim(); fromScale = TB_SIZE; } else if (str.endsWith("t")) { str = str.substring(0, str.length() - 1).trim(); fromScale = TB_SIZE; } else if (str.endsWith("bytes")) { str = str.substring(0, str.length() - "bytes".length()).trim(); fromScale = BYTE_SIZE; } str = str.replace(",", ""); long value = (long) Double.parseDouble(str); return value * fromScale; } catch (Throwable ex) { return -1; } } public static void clearTerminal() { System.out.printf(CLEAR_TERMINAL_ANSI_CMD); } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/util/LongObjectHashMap.java ================================================ /* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.vip.vjtools.vjtop.util; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * 移植Netty 4.1.6的Key为原子类型的集合类, 在数据结构上与HashMap不一样,空间占用与读写性能俱比原来更优. * * 原子类型集合类有多个实现,选择Netty是因为有在实战中使用. * * A hash map implementation of {@link LongObjectMap} that uses open addressing for keys. To minimize the memory * footprint, this class uses open addressing rather than chaining. Collisions are resolved using linear probing. * Deletions implement compaction, so cost of remove can approach O(N) for full maps, which makes a small loadFactor * recommended. * * @param The value type stored in the map. */ public class LongObjectHashMap implements LongObjectMap { /** Default initial capacity. Used if not specified in the constructor */ public static final int DEFAULT_CAPACITY = 8; /** Default load factor. Used if not specified in the constructor */ public static final float DEFAULT_LOAD_FACTOR = 0.5f; /** * Placeholder for null values, so we can use the actual null to mean available. (Better than using a placeholder * for available: less references for GC processing.) */ private static final Object NULL_VALUE = new Object(); /** The maximum number of elements allowed without allocating more space. */ private int maxSize; /** The load factor for the map. Used to calculate {@link #maxSize}. */ private final float loadFactor; private long[] keys; private V[] values; private int size; private int mask; private final Set keySet = new KeySet(); private final Set> entrySet = new EntrySet(); private final Iterable> entries = new Iterable>() { @Override public Iterator> iterator() { return new PrimitiveIterator(); } }; public LongObjectHashMap() { this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); } public LongObjectHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public LongObjectHashMap(int initialCapacity, float loadFactor) { if (loadFactor <= 0.0f || loadFactor > 1.0f) { // Cannot exceed 1 because we can never store more than capacity elements; // using a bigger loadFactor would trigger rehashing before the desired load is reached. throw new IllegalArgumentException("loadFactor must be > 0 and <= 1"); } this.loadFactor = loadFactor; // Adjust the initial capacity if necessary. int capacity = safeFindNextPositivePowerOfTwo(initialCapacity); mask = capacity - 1; // Allocate the arrays. keys = new long[capacity]; @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" }) V[] temp = (V[]) new Object[capacity]; values = temp; // Initialize the maximum size value. maxSize = calcMaxSize(capacity); } private static T toExternal(T value) { assert value != null : "null is not a legitimate internal value. Concurrent Modification?"; return value == NULL_VALUE ? null : value; } @SuppressWarnings("unchecked") private static T toInternal(T value) { return value == null ? (T) NULL_VALUE : value; } @Override public V get(long key) { int index = indexOf(key); return index == -1 ? null : toExternal(values[index]); } @Override public V put(long key, V value) { int startIndex = hashIndex(key); int index = startIndex; for (;;) { if (values[index] == null) { // Found empty slot, use it. keys[index] = key; values[index] = toInternal(value); growSize(); return null; } if (keys[index] == key) { // Found existing entry with this key, just replace the value. V previousValue = values[index]; values[index] = toInternal(value); return toExternal(previousValue); } // Conflict, keep probing ... if ((index = probeNext(index)) == startIndex) { // Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow. throw new IllegalStateException("Unable to insert"); } } } @Override public void putAll(Map sourceMap) { if (sourceMap instanceof LongObjectHashMap) { // Optimization - iterate through the arrays. @SuppressWarnings("unchecked") LongObjectHashMap source = (LongObjectHashMap) sourceMap; for (int i = 0; i < source.values.length; ++i) { V sourceValue = source.values[i]; if (sourceValue != null) { put(source.keys[i], sourceValue); } } return; } // Otherwise, just add each entry. for (Entry entry : sourceMap.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public V remove(long key) { int index = indexOf(key); if (index == -1) { return null; } V prev = values[index]; removeAt(index); return toExternal(prev); } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public void clear() { Arrays.fill(keys, 0); Arrays.fill(values, null); size = 0; } @Override public boolean containsKey(long key) { return indexOf(key) >= 0; } @Override public boolean containsValue(Object value) { @SuppressWarnings("unchecked") V v1 = toInternal((V) value); for (V v2 : values) { // The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE). if (v2 != null && v2.equals(v1)) { return true; } } return false; } @Override public Iterable> entries() { return entries; } @Override public Collection values() { return new AbstractCollection() { @Override public Iterator iterator() { return new Iterator() { final PrimitiveIterator iter = new PrimitiveIterator(); @Override public boolean hasNext() { return iter.hasNext(); } @Override public V next() { return iter.next().value(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { return size; } }; } @Override public int hashCode() { // Hashcode is based on all non-zero, valid keys. We have to scan the whole keys // array, which may have different lengths for two maps of same size(), so the // capacity cannot be used as input for hashing but the size can. int hash = size; for (long key : keys) { // 0 can be a valid key or unused slot, but won't impact the hashcode in either case. // This way we can use a cheap loop without conditionals, or hard-to-unroll operations, // or the devastatingly bad memory locality of visiting value objects. // Also, it's important to use a hash function that does not depend on the ordering // of terms, only their values; since the map is an unordered collection and // entries can end up in different positions in different maps that have the same // elements, but with different history of puts/removes, due to conflicts. hash ^= hashCode(key); } return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof LongObjectMap)) { return false; } @SuppressWarnings("rawtypes") LongObjectMap other = (LongObjectMap) obj; if (size != other.size()) { return false; } for (int i = 0; i < values.length; ++i) { V value = values[i]; if (value != null) { long key = keys[i]; Object otherValue = other.get(key); if (value == NULL_VALUE) { if (otherValue != null) { return false; } } else if (!value.equals(otherValue)) { return false; } } } return true; } @Override public boolean containsKey(Object key) { return containsKey(objectToKey(key)); } @Override public V get(Object key) { return get(objectToKey(key)); } @Override public V put(Long key, V value) { return put(objectToKey(key), value); } @Override public V remove(Object key) { return remove(objectToKey(key)); } @Override public Set keySet() { return keySet; } @Override public Set> entrySet() { return entrySet; } private long objectToKey(Object key) { return ((Long) key).longValue(); } /** * Locates the index for the given key. This method probes using double hashing. * * @param key the key for an entry in the map. * @return the index where the key was found, or {@code -1} if no entry is found for that key. */ private int indexOf(long key) { int startIndex = hashIndex(key); int index = startIndex; for (;;) { if (values[index] == null) { // It's available, so no chance that this value exists anywhere in the map. return -1; } if (key == keys[index]) { return index; } // Conflict, keep probing ... if ((index = probeNext(index)) == startIndex) { return -1; } } } /** * Returns the hashed index for the given key. */ private int hashIndex(long key) { // The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds. return hashCode(key) & mask; } /** * Returns the hash code for the key. */ private static int hashCode(long key) { return (int) (key ^ (key >>> 32)); } /** * Get the next sequential index after {@code index} and wraps if necessary. */ private int probeNext(int index) { // The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds. return (index + 1) & mask; } /** * Grows the map size after an insertion. If necessary, performs a rehash of the map. */ private void growSize() { size++; if (size > maxSize) { if (keys.length == Integer.MAX_VALUE) { throw new IllegalStateException("Max capacity reached at size=" + size); } // Double the capacity. rehash(keys.length << 1); } } /** * Removes entry at the given index position. Also performs opportunistic, incremental rehashing if necessary to not * break conflict chains. * * @param index the index position of the element to remove. * @return {@code true} if the next item was moved back. {@code false} otherwise. */ private boolean removeAt(final int index) { --size; // Clearing the key is not strictly necessary (for GC like in a regular collection), // but recommended for security. The memory location is still fresh in the cache anyway. keys[index] = 0; values[index] = null; // In the interval from index to the next available entry, the arrays may have entries // that are displaced from their base position due to prior conflicts. Iterate these // entries and move them back if possible, optimizing future lookups. // Knuth Section 6.4 Algorithm R, also used by the JDK's IdentityHashMap. int nextFree = index; int i = probeNext(index); for (V value = values[i]; value != null; value = values[i = probeNext(i)]) { long key = keys[i]; int bucket = hashIndex(key); if (i < bucket && (bucket <= nextFree || nextFree <= i) || bucket <= nextFree && nextFree <= i) { // Move the displaced entry "back" to the first available position. keys[nextFree] = key; values[nextFree] = value; // Put the first entry after the displaced entry keys[i] = 0; values[i] = null; nextFree = i; } } return nextFree != index; } /** * Calculates the maximum size allowed before rehashing. */ private int calcMaxSize(int capacity) { // Clip the upper bound so that there will always be at least one available slot. int upperBound = capacity - 1; return Math.min(upperBound, (int) (capacity * loadFactor)); } /** * Rehashes the map for the given capacity. * * @param newCapacity the new capacity for the map. */ private void rehash(int newCapacity) { long[] oldKeys = keys; V[] oldVals = values; keys = new long[newCapacity]; @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" }) V[] temp = (V[]) new Object[newCapacity]; values = temp; maxSize = calcMaxSize(newCapacity); mask = newCapacity - 1; // Insert to the new arrays. for (int i = 0; i < oldVals.length; ++i) { V oldVal = oldVals[i]; if (oldVal != null) { // Inlined put(), but much simpler: we don't need to worry about // duplicated keys, growing/rehashing, or failing to insert. long oldKey = oldKeys[i]; int index = hashIndex(oldKey); for (;;) { if (values[index] == null) { keys[index] = oldKey; values[index] = oldVal; break; } // Conflict, keep probing. Can wrap around, but never reaches startIndex again. index = probeNext(index); } } } } @Override public String toString() { if (isEmpty()) { return "{}"; } StringBuilder sb = new StringBuilder(4 * size); sb.append('{'); boolean first = true; for (int i = 0; i < values.length; ++i) { V value = values[i]; if (value != null) { if (!first) { sb.append(", "); } sb.append(keyToString(keys[i])).append('=').append(value == this ? "(this Map)" : toExternal(value)); first = false; } } return sb.append('}').toString(); } /** * Helper method called by {@link #toString()} in order to convert a single map key into a string. This is protected * to allow subclasses to override the appearance of a given key. */ protected String keyToString(long key) { return Long.toString(key); } /** * Set implementation for iterating over the entries of the map. */ private final class EntrySet extends AbstractSet> { @Override public Iterator> iterator() { return new MapIterator(); } @Override public int size() { return LongObjectHashMap.this.size(); } } /** * Set implementation for iterating over the keys. */ private final class KeySet extends AbstractSet { @Override public int size() { return LongObjectHashMap.this.size(); } @Override public boolean contains(Object o) { return LongObjectHashMap.this.containsKey(o); } @Override public boolean remove(Object o) { return LongObjectHashMap.this.remove(o) != null; } @Override public boolean retainAll(Collection retainedKeys) { boolean changed = false; for (Iterator> iter = entries().iterator(); iter.hasNext();) { PrimitiveEntry entry = iter.next(); if (!retainedKeys.contains(entry.key())) { changed = true; iter.remove(); } } return changed; } @Override public void clear() { LongObjectHashMap.this.clear(); } @Override public Iterator iterator() { return new Iterator() { private final Iterator> iter = entrySet.iterator(); @Override public boolean hasNext() { return iter.hasNext(); } @Override public Long next() { return iter.next().getKey(); } @Override public void remove() { iter.remove(); } }; } } /** * Iterator over primitive entries. Entry key/values are overwritten by each call to {@link #next()}. */ private final class PrimitiveIterator implements Iterator>, PrimitiveEntry { private int prevIndex = -1; private int nextIndex = -1; private int entryIndex = -1; private void scanNext() { while (++nextIndex != values.length && values[nextIndex] == null) { } } @Override public boolean hasNext() { if (nextIndex == -1) { scanNext(); } return nextIndex != values.length; } @Override public PrimitiveEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } prevIndex = nextIndex; scanNext(); // Always return the same Entry object, just change its index each time. entryIndex = prevIndex; return this; } @Override public void remove() { if (prevIndex == -1) { throw new IllegalStateException("next must be called before each remove."); } if (removeAt(prevIndex)) { // removeAt may move elements "back" in the array if they have been displaced because their spot in the // array was occupied when they were inserted. If this occurs then the nextIndex is now invalid and // should instead point to the prevIndex which now holds an element which was "moved back". nextIndex = prevIndex; } prevIndex = -1; } // Entry implementation. Since this implementation uses a single Entry, we coalesce that // into the Iterator object (potentially making loop optimization much easier). @Override public long key() { return keys[entryIndex]; } @Override public V value() { return toExternal(values[entryIndex]); } @Override public void setValue(V value) { values[entryIndex] = toInternal(value); } } /** * Iterator used by the {@link Map} interface. */ private final class MapIterator implements Iterator> { private final PrimitiveIterator iter = new PrimitiveIterator(); @Override public boolean hasNext() { return iter.hasNext(); } @Override public Entry next() { if (!hasNext()) { throw new NoSuchElementException(); } iter.next(); return new MapEntry(iter.entryIndex); } @Override public void remove() { iter.remove(); } } /** * A single entry in the map. */ final class MapEntry implements Entry { private final int entryIndex; MapEntry(int entryIndex) { this.entryIndex = entryIndex; } @Override public Long getKey() { verifyExists(); return keys[entryIndex]; } @Override public V getValue() { verifyExists(); return toExternal(values[entryIndex]); } @Override public V setValue(V value) { verifyExists(); V prevValue = toExternal(values[entryIndex]); values[entryIndex] = toInternal(value); return prevValue; } private void verifyExists() { if (values[entryIndex] == null) { throw new IllegalStateException("The map entry has been removed"); } } } public static int safeFindNextPositivePowerOfTwo(final int value) { return value <= 0 ? 1 : value >= 0x40000000 ? 0x40000000 : findNextPositivePowerOfTwo(value); } public static int findNextPositivePowerOfTwo(final int value) { assert value > Integer.MIN_VALUE && value < 0x40000000; return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/util/LongObjectMap.java ================================================ /* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.vip.vjtools.vjtop.util; import java.util.Map; /** * Interface for a primitive map that uses {@code long}s as keys. * * @param the value type stored in the map. */ public interface LongObjectMap extends Map { /** * A primitive entry in the map, provided by the iterator from {@link #entries()} * * @param the value type stored in the map. */ interface PrimitiveEntry { /** * Gets the key for this entry. */ long key(); /** * Gets the value for this entry. */ V value(); /** * Sets the value for this entry. */ void setValue(V value); } /** * Gets the value in the map with the specified key. * * @param key the key whose associated value is to be returned. * @return the value or {@code null} if the key was not found in the map. */ V get(long key); /** * Puts the given entry into the map. * * @param key the key of the entry. * @param value the value of the entry. * @return the previous value for this key or {@code null} if there was no previous mapping. */ V put(long key, V value); /** * Removes the entry with the specified key. * * @param key the key for the entry to be removed from this map. * @return the previous value for the key, or {@code null} if there was no mapping. */ V remove(long key); /** * Gets an iterable to traverse over the primitive entries contained in this map. As an optimization, the * {@link PrimitiveEntry}s returned by the {@link Iterator} may change as the {@link Iterator} progresses. The * caller should not rely on {@link PrimitiveEntry} key/value stability. */ Iterable> entries(); /** * Indicates whether or not this map contains a value for the specified key. */ boolean containsKey(long key); } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/util/OptionAdvanceParser.java ================================================ package com.vip.vjtools.vjtop.util; import java.util.Arrays; import com.vip.vjtools.vjtop.VJTop; import com.vip.vjtools.vjtop.VMDetailView.ContentMode; import com.vip.vjtools.vjtop.VMDetailView.OutputFormat; import com.vip.vjtools.vjtop.VMDetailView.ThreadInfoMode; import joptsimple.OptionParser; import joptsimple.OptionSet; public class OptionAdvanceParser { private static final int DEFAULT_INTERVAL = 10; public static String parsePid(OptionParser parser, OptionSet optionSet) { Integer pid = null; // to support PID as non option argument if (optionSet.nonOptionArguments().size() > 0) { pid = Integer.valueOf((String) optionSet.nonOptionArguments().get(0)); } if (pid == null){ pid = SelectPid.getPidFromJpsList(); } if (pid == null) { System.out.println("PID can't be empty !!!"); VJTop.printHelper(parser); System.exit(0); } return String.valueOf(pid); } public static OutputFormat parseOutputFormat(OptionSet optionSet) { OutputFormat outputFormat = OutputFormat.console; if (optionSet.hasArgument("output")) { String format = (String) optionSet.valueOf("output"); if (format.equals("clean")) { outputFormat = OutputFormat.cleanConsole; } else if (format.equals("text")) { outputFormat = OutputFormat.text; } } return outputFormat; } public static ContentMode parseContentMode(OptionSet optionSet) { ContentMode contentMode = ContentMode.all; if (optionSet.hasArgument("content")) { String format = (String) optionSet.valueOf("content"); if (format.equals("jvm")) { contentMode = ContentMode.jvm; } else if (format.equals("thread")) { contentMode = ContentMode.thread; } } return contentMode; } public static ThreadInfoMode parseThreadInfoMode(OptionSet optionSet) { ThreadInfoMode threadInfoMode = ThreadInfoMode.cpu; if (optionSet.hasArgument("mode")) { String mode = (String) optionSet.valueOf("mode"); threadInfoMode = ThreadInfoMode.parse(mode); } return threadInfoMode; } public static OptionParser createOptionParser() { OptionParser parser = new OptionParser(); // commmon parser.acceptsAll(Arrays.asList("help", "?", "h"), "shows this help").forHelp(); parser.acceptsAll(Arrays.asList("n", "iteration"), "vjtop will exit after n output iterations (defaults to unlimit)").withRequiredArg() .ofType(Integer.class); parser.acceptsAll(Arrays.asList("i", "interval", "d"), "interval between each output iteration (defaults to 10s)").withRequiredArg().ofType(Integer.class); parser.acceptsAll(Arrays.asList("w", "width"), "Number of columns for the console display (defaults to 100)") .withRequiredArg().ofType(Integer.class); parser.acceptsAll(Arrays.asList("l", "limit"), "Number of threads to display ( default to 10 threads)") .withRequiredArg().ofType(Integer.class); parser.acceptsAll(Arrays.asList("f", "filter"), "Thread name filter ( no default)").withRequiredArg() .ofType(String.class); parser.acceptsAll(Arrays.asList("j", "jmxurl"), "give JMX url like 127.0.0.1:7001 when VM attach doesn't work") .withRequiredArg().ofType(String.class); // detail mode parser.acceptsAll(Arrays.asList("m", "mode"), "number of thread display mode: \n" + " cpu(default): display thread cpu usage and sort by its delta cpu time\n" + " syscpu: display thread cpu usage and sort by delta syscpu time\n" + " totalcpu: display thread cpu usage and sort by total cpu time\n" + " totalsyscpu: display thread cpu usage and sort by total syscpu time\n" + " memory: display thread memory allocated and sort by delta\n" + " totalmemory: display thread memory allocated and sort by total") .withRequiredArg().ofType(String.class); parser.acceptsAll(Arrays.asList("o", "output"), "output format: \n" + " console(default): console with warning and flush ansi code\n" + " clean: console without warning and flush ansi code\n" + " text: plain text like /proc/status for 3rd tools\n") .withRequiredArg().ofType(String.class); parser.acceptsAll(Arrays.asList("c", "content"), "output content: \n" + " all(default): jvm info and theads info\n jvm: only jvm info\n thread: only thread info\n") .withRequiredArg().ofType(String.class); return parser; } public static Integer parseInterval(OptionSet optionSet) { Integer interval = OptionAdvanceParser.DEFAULT_INTERVAL; if (optionSet.hasArgument("interval")) { interval = (Integer) (optionSet.valueOf("interval")); if (interval < 1) { throw new IllegalArgumentException("Interval cannot be set below 1.0"); } } return interval; } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/util/SelectPid.java ================================================ package com.vip.vjtools.vjtop.util; import java.io.BufferedReader; import java.io.Console; import java.io.InputStreamReader; /** * Created by traburiss on 2019/12/11. * describe: */ public class SelectPid { public static Integer getPidFromJpsList(){ Integer pid = null; Process process = null; BufferedReader reader = null; try { Console console = System.console(); process = Runtime.getRuntime().exec("jps -l"); reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String defPidStr = ""; String line; System.out.println("please input a pid from list:"); System.out.println("PID\tNAME"); while ((line = reader.readLine()) != null) { //不显示jps和自己 if (!line.contains("sun.tools.jps.Jps") && !line.contains("com.vip.vjtools.vjtop.VJTop")){ String[] pm = line.split(" ",2); if (defPidStr.isEmpty()){ defPidStr = pm[0]; } System.out.println(pm[0] + "\t" + pm[1]); } } String pidString = console.readLine("\nplease input pid(default is " + defPidStr + "):"); if (pidString == null || pidString.isEmpty()){ pidString = defPidStr; } pid = Integer.valueOf(pidString); }catch (Exception ignored){ }finally { try { if (reader != null) { reader.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (process != null) { process.destroy(); } } catch (Exception e) { e.printStackTrace(); } } return pid; } } ================================================ FILE: vjtop/src/main/java/com/vip/vjtools/vjtop/util/Utils.java ================================================ package com.vip.vjtools.vjtop.util; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Map; public class Utils { public static long NANOS_TO_MILLS = 1000 * 1000; /** * Sorts a Map by its values, using natural ordering. */ public static long[] sortAndFilterThreadIdsByValue(LongObjectMap map, int threadLimit) { int max = Math.min(threadLimit, map.size()); List list = new LinkedList(map.entrySet()); Collections.sort(list, new Comparator() { @Override public int compare(Object o1, Object o2) { return ((Comparable) ((Map.Entry) (o2)).getValue()).compareTo(((Map.Entry) (o1)).getValue()); } }); long[] topTidArray = new long[max]; int i = 0; for (Map.Entry entry : list) { topTidArray[i] = (Long) entry.getKey(); if (++i >= max) { break; } } return topTidArray; } /** * calculates a "load", given on two deltas */ public static double calcLoad(long deltaCpuTime, long deltaUptime) { if (deltaCpuTime <= 0 || deltaUptime == 0) { return 0.0; } return deltaCpuTime * 100d / deltaUptime; } /** * calculates a "load", given on two deltas */ public static double calcLoad(Long deltaCpuTime, long deltaUptime, long factor) { if (deltaCpuTime == null || deltaCpuTime <= 0 || deltaUptime == 0) { return 0.0; } return deltaCpuTime * 100d / factor / deltaUptime; } public static double calcMemoryUtilization(Long threadBytes, long totalBytes) { if (threadBytes == null || totalBytes == 0) { return 0; } return (threadBytes * 100d) / totalBytes;// 这里因为最后单位是百分比%,所以bytes除以totalBytes以后要乘以100,才可以再加上单位% } public static void sleep(long mills) { try { Thread.sleep(mills); } catch (InterruptedException e) { } } public static int getJavaMajorVersion(String javaSpecificationVersion) { if (javaSpecificationVersion.startsWith("1.8")) { return 8; } else if (javaSpecificationVersion.startsWith("1.7")) { return 7; } else if (javaSpecificationVersion.startsWith("1.6")) { return 6; } else { try { return Integer.parseInt(javaSpecificationVersion); } catch (NumberFormatException e) { return 0; } } } } ================================================ FILE: vjtop/src/test/java/com/vip/vjtools/vjtop/util/FormatsTest.java ================================================ package com.vip.vjtools.vjtop.util; import com.vip.vjtools.vjtop.WarningRule.LongWarning; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.util.ArrayList; public class FormatsTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void joinInput0NotNullOutputNotNull() { // Arrange final ArrayList list = new ArrayList(); final String delim = "AAAAAAAA"; // Act final String retval = Formats.join(list, delim); // Assert result Assert.assertEquals("", retval); } @Test public void joinInput1NullOutputNotNull() { // Arrange final ArrayList list = new ArrayList(); list.add(""); final String delim = null; // Act final String retval = Formats.join(list, delim); // Assert result Assert.assertEquals("", retval); } @Test public void leftStrInputNotNullNegativeOutputStringIndexOutOfBoundsException() { // Arrange final String str = "!"; final int length = -536_870_911; // Act thrown.expect(StringIndexOutOfBoundsException.class); Formats.leftStr(str, length); // Method is not expected to return due to exception thrown } @Test public void leftStrInputNotNullZeroOutputNotNull() { // Arrange final String str = "!!!!!!!!"; final int length = 0; // Act final String retval = Formats.leftStr(str, length); // Assert result Assert.assertEquals("", retval); } @Test public void rightStrInputNotNullNegativeOutputStringIndexOutOfBoundsException() { // Arrange final String str = "!!!!!!!!"; final int length = -1_048_568; // Act thrown.expect(StringIndexOutOfBoundsException.class); Formats.rightStr(str, length); // Method is not expected to return due to exception thrown } @Test public void rightStrInputNotNullPositiveOutputNotNull() { // Arrange final String str = "!!!!!!!!"; final int length = 2_147_221_512; // Act final String retval = Formats.rightStr(str, length); // Assert result Assert.assertEquals("!!!!!!!!", retval); } @Test public void shortNameInputNotNullPositivePositiveOutputNotNull() { // Arrange final String str = "!"; final int length = 1; final int rightLength = 6; // Act final String retval = Formats.shortName(str, length, rightLength); // Assert result Assert.assertEquals("!", retval); } @Test public void shortNameInputNotNullPositiveZeroOutputNotNull() { // Arrange final String str = "!!!!!!!!!!"; final int length = 4; final int rightLength = 0; // Act final String retval = Formats.shortName(str, length, rightLength); // Assert result Assert.assertEquals("!...", retval); } @Test public void shortNameInputNotNullZeroNegativeOutputStringIndexOutOfBoundsException() { // Arrange final String str = "!!!!!!!!!!"; final int length = 0; final int rightLength = -19; // Act thrown.expect(StringIndexOutOfBoundsException.class); Formats.shortName(str, length, rightLength); // Method is not expected to return due to exception thrown } @Test public void toFixLengthSizeUnitInputNullOutputNotNull() { // Arrange final Long size = null; // Act final String retval = Formats.toFixLengthSizeUnit(size); // Assert result Assert.assertEquals("NaN", retval); } @Test public void toFixLengthSizeUnitInputPositiveOutputNotNull() { // Arrange final Long size = 4_611_686_018_427_387_906L; // Act final String retval = Formats.toFixLengthSizeUnit(size); // Assert result Assert.assertEquals("4194304t", retval); } @Test public void toMBInputNegativeOutputNotNull() { // Arrange final long bytes = -8L; // Act final String retval = Formats.toMB(bytes); // Assert result Assert.assertEquals("NaN", retval); } @Test public void toMBInputPositiveOutputNotNull() { // Arrange final long bytes = 8L; // Act final String retval = Formats.toMB(bytes); // Assert result Assert.assertEquals("0m", retval); } @Test public void toSizeUnitInputNegativeOutputNotNull() { // Arrange final Long size = -1023L; // Act final String retval = Formats.toSizeUnit(size); // Assert result Assert.assertEquals("-1023", retval); } @Test public void toSizeUnitInputNullOutputNotNull() { // Arrange final Long size = null; // Act final String retval = Formats.toSizeUnit(size); // Assert result Assert.assertEquals("NaN", retval); } @Test public void toSizeUnitInputPositiveOutputNotNull() { // Arrange final Long size = 45_312L; // Act final String retval = Formats.toSizeUnit(size); // Assert result Assert.assertEquals("44k", retval); } @Test public void toSizeUnitWithColorInputNullNullOutputNullPointerException() { // Arrange final Long size = null; final LongWarning warning = null; // Act thrown.expect(NullPointerException.class); Formats.toSizeUnitWithColor(size, warning); // Method is not expected to return due to exception thrown } } ================================================ FILE: vjtop/src/test/java/com/vip/vjtools/vjtop/util/UtilsTest.java ================================================ package com.vip.vjtools.vjtop.util; import com.vip.vjtools.vjtop.util.Utils; import org.junit.Assert; import org.junit.Test; public class UtilsTest { @Test public void calcLoadInputNegativeZeroZeroOutputZero() { // Arrange final Long deltaCpuTime = -4_194_304L; final long deltaUptime = 0L; final long factor = 0L; // Act final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime, factor); // Assert result Assert.assertEquals(0.0, retval, 0.0); } @Test public void calcLoadInputPositivePositiveOutputPositive() { // Arrange final long deltaCpuTime = 558_348_370L; final long deltaUptime = 1L; // Act final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime); // Assert result Assert.assertEquals(0x1.a0008001p+35 /* 5.58348e+10 */, retval, 0.0); } @Test public void calcLoadInputPositivePositiveZeroOutputPositiveInfinity() { // Arrange final Long deltaCpuTime = 4_194_304L; final long deltaUptime = 4_611_686_018_427_387_904L; final long factor = 0L; // Act final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime, factor); // Assert result Assert.assertEquals(Double.POSITIVE_INFINITY, retval, 0.0); } @Test public void calcLoadInputPositiveZeroOutputZero() { // Arrange final long deltaCpuTime = 558_348_370L; final long deltaUptime = 0L; // Act final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime); // Assert result Assert.assertEquals(0.0, retval, 0.0); } @Test public void calcLoadInputPositiveZeroZeroOutputZero() { // Arrange final Long deltaCpuTime = 4_194_304L; final long deltaUptime = 0L; final long factor = 0L; // Act final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime, factor); // Assert result Assert.assertEquals(0.0, retval, 0.0); } @Test public void calcLoadInputZeroZeroOutputZero() { // Arrange final long deltaCpuTime = 0L; final long deltaUptime = 0L; // Act final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime); // Assert result Assert.assertEquals(0.0, retval, 0.0); } @Test public void calcMemoryUtilizationInputNegativePositiveOutputNegative() { // Arrange final Long threadBytes = -507_680_768_073_012_547L; final long totalBytes = 1_147_301_170_758_571_992L; // Act final double retval = Utils.calcMemoryUtilization(threadBytes, totalBytes); // Assert result Assert.assertEquals(-0x1.6200000024f82p+5 /* -44.25 */, retval, 0.0); } @Test public void calcMemoryUtilizationInputPositiveZeroOutputZero() { // Arrange final Long threadBytes = 1L; final long totalBytes = 0L; // Act final double retval = Utils.calcMemoryUtilization(threadBytes, totalBytes); // Assert result Assert.assertEquals(0.0, retval, 0.0); } }