[
  {
    "path": ".gitignore",
    "content": "# Eclipse project files\n.classpath\n.project\n.settings/\n\n# Intellij project files\n*.iml\n.idea/\n\n# Otherse\n*.log\ntarget/\n\nvip-java-standard.md\nrelease.sh\nvjmap/vjmap/\nvjtop/vjtop/\nvjmxcli/vjmxcli\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\n\njdk:\n  - openjdk9\n\nbranches:\n  only:\n    - master\n\nscript: mvn clean package\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [2006-2012] [www.springside.org.cn]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "![VJTools](/docs/images/logo.jpg) [![Build Status](https://travis-ci.org/vipshop/vjtools.svg?branch=master)](https://travis-ci.org/vipshop/vjtools) \n\n\n主力于Java的唯品会，关于Java的一些小家底。 \n\n各位看官看着是好的，烦请“Star”。\n\n[1.0.8版](https://github.com/vipshop/vjtools/releases/tag/v.1.0.8) - 2018.9.24\n\n## Java Standard\n\n| Project | Description |\n| -------- | -------- |\n| [standard](https://vipshop.github.io/vjtools/#/standard/) | 唯品会Java开发手册 |\n| [code formatter](/standard/formatter) | IDE格式化模板 |\n| [sonar rule](/standard/sonar-vj) | Sonar规则定制示例 |\n\n\n## Java Core Library\n\n| Project | Description |\n| -------- | -------- |\n| [vjkit](/vjkit) | 关于文本，集合，并发等基础功能的核心类库 |\n| [vjstar](/vjstar) | 关于后端应用的性能、可用性的最佳实践 |\n\n\n## Java Tools\n\n| Project | Description | Manual |\n| -------- | -------- | -------- |\n| [vjtop](/vjtop)  | 观察JVM进程指标及其繁忙线程 | [Chinese](/vjtop/README.md)|\n| [vjmap](/vjmap)  | JMAP的分代打印版 |[Chinese](/vjmap/README.md)|\n| [vjdump](/vjdump)  | 线上紧急收集JVM数据脚本 | [Chinese](/vjdump/README.md), [English](/vjdump/README_EN.md)|\n| [vjmxcli](/vjmxcli)  | JMX 查看工具 | [Chinese](/vjmxcli/README.md)|\n\n视频：[《VJTools如何利用佛性技术玩转JVM》](http://kai.vkaijiang.com/product/course?courseID=120897)\n\n文档：[《入门科普，围绕JVM的各种外挂技术》](https://mp.weixin.qq.com/s/cwU2rLOuwock048rKBz3ew)\n\n其他直接使用的工具，见[常用工具](docs/other/othertools.md)\n\n\n## Contributing\n\nVJTools官方微信讨论群，请搜索微信号viptech128(唯技术)，添加好友后加入。\n\n所有报Bug、建议与咨询，请在[Issues](https://github.com/vipshop/vjtools/issues)发起；所有代码提交，请走[Pull Request](https://github.com/vipshop/vjtools/pulls)流程。\n\n对于优秀的代码提交和建议，唯品会将不吝发挥电商本色，给予[vip.com](https://www.vip.com)购物卡的奖励 ！！！\n\n\n## Developers\n\n唯品会团队: [江南白衣](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)\n\n曾经一起战斗: [杨镌颖@阿里](https://github.com/yangjuanying), 陈维治@阿里\n"
  },
  {
    "path": "docs/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/README.md",
    "content": "All about java in vip.com\n\n- Standard\n- Core Libraries\n- Tools"
  },
  {
    "path": "docs/_coverpage.md",
    "content": "\n![VJTools](images/logo2.png)\n\n- Standard\n- Core Libraries\n- Tools\n\n[GitHub Project](https://github.com/vipshop/vjtools)\n[Java Coding Guidelines](standard/)\n"
  },
  {
    "path": "docs/_sidebar.md",
    "content": "- [GitHub Project](https://github.com/vipshop/vjtools)\n- [Java Coding Guidelines](standard/)\n"
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>VJTools</title>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\"/>\n    <meta name=\"keywords\" content=\"java,coding standard,java开发规范\">\n    <meta name=\"description\" content=\"vip.com‘ java coding standard and guidelines\">\n    <meta name=\"viewport\"\n          content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\">\n    <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/lib/themes/vue.css\">\n</head>\n<body>\n<div id=\"app\"></div>\n<script>\n    window.$docsify = {\n        name: 'VJTools',\n        auto2top: true,\n        coverpage: true,\n        executeScript: true,\n        loadSidebar: true,\n        loadNavbar: false,\n        mergeNavbar: true,\n        maxLevel: 4,\n        subMaxLevel: 3,\n        search: {\n            noData: {\n                '/standard/': '没有结果!',\n                '/': 'No results!'\n            },\n            paths: 'auto',\n            placeholder: {\n                '/standard/': '搜索',\n                '/': 'Search'\n            }\n        },\n    }\n</script>\n<script src=\"//unpkg.com/docsify/lib/docsify.min.js\"></script>\n<script src=\"//unpkg.com/prismjs/components/prism-java.min.js\"></script>\n<script src=\"//unpkg.com/docsify/lib/plugins/search.js\"></script>\n<script type=\"text/javascript\" src=\"//js.users.51.la/19520351.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/other/othertools.md",
    "content": "# 1.问题排查\n\n## BTrace 系\n\n- [btrace](https://github.com/btraceio/btrace)\n- [greys](https://github.com/oldmanpushcart/greys-anatomy) 交互式免脚本，比btrace更易用\n\n[Java神器Btrace，从入门到熟练小工手册](https://mp.weixin.qq.com/s/4bZ6iSvpqPsjdvkSoFVhrg)\n\n## 在线日志分析\n\n- [easygc.io](http://www.gceasy.io/) gc日志分析\n- [fastthread.io](http://fastthread.io/) thread dump分析\n\n## HeapDump分析\n\n- [Eclipse MAT](https://www.eclipse.org/mat/)\n\n# 2.性能Profile\n\n- [Java Mission Control](http://www.oracle.com/technetwork/java/javaseproducts/mission-control/index.html) JDK自带Profiler\n- [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) 火焰图生成工具"
  },
  {
    "path": "docs/standard/README.md",
    "content": "# 《唯品会Java开发手册》1.0.3版\n\n## 1. 概述\n\n[《阿里巴巴Java开发手册》](https://github.com/alibaba/p3c)，是首个对外公布的企业级Java开发手册，对整个业界都有重要的意义。\n\n我们结合唯品会的内部经验，参考《Clean Code》、《Effective Java》等重磅资料，增补了一些条目，也做了些精简。\n\n感谢阿里授权我们定制和再发布。\n\n\n## 2. 规范正文\n\n1. [命名规约](standard/chapter01.md)\n2. [格式规约](standard/chapter02.md)\n3. [注释规约](standard/chapter03.md)\n4. [方法设计](standard/chapter04.md)\n5. [类设计](standard/chapter05.md)\n6. [控制语句](standard/chapter06.md)\n7. [基本类型](standard/chapter07.md)\n8. [集合处理](standard/chapter08.md)\n9. [并发处理](standard/chapter09.md)\n10. [异常处理](standard/chapter10.md)\n11. [日志规约](standard/chapter11.md)\n12. [其他设计](standard/chapter12.md)\n\n\n注意： 如需全文pdf版，请下载源码，在docs/standard/目录运行merge.sh生成。\n\n## 3. 规范落地\n\n规则落地主要依靠代码格式模版与[Sonar代码规则检查](https://www.sonarqube.org/)。\n\n其中Sonar规则不如人意的地方，我们进行了定制。\n\n* [Eclipse/Intellij 格式模板](https://github.com/vipshop/vjtools/tree/master/standard/formatter)\n* [Sonar 规则修改示例](https://github.com/vipshop/vjtools/tree/master/standard/sonar-vj)\n\n## 4. 参考资料\n\n* [《Clean Code》](https://book.douban.com/subject/4199741/)\n* [《Effective Java 2nd》](https://book.douban.com/subject/3360807/)\n* [《SEI CERT Oracle Coding Standard for Java》(在线版)](https://www.securecoding.cert.org/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java)\n* [Sonar Java Rules](https://rules.sonarsource.com/java/)\n\n## 5. 定制记录\n\n* [《唯品会Java开发手册》－与阿里手册的比较文学I](http://calvin1978.blogcn.com/?p=1771)\n* [阿里手册的增补与删减记录](standard/ali.md)\n"
  },
  {
    "path": "docs/standard/_sidebar.md",
    "content": "- [命名规约](standard/chapter01.md)\n- [格式规约](standard/chapter02.md)\n- [注释规约](standard/chapter03.md)\n- [方法设计](standard/chapter04.md)\n- [类设计](standard/chapter05.md)\n- [控制语句](standard/chapter06.md)\n- [基本类型](standard/chapter07.md)\n- [集合处理](standard/chapter08.md)\n- [并发处理](standard/chapter09.md)\n- [异常处理](standard/chapter10.md)\n- [日志规约](standard/chapter11.md)\n- [其他设计](standard/chapter12.md)\n- [定制记录](standard/ali.md)\n"
  },
  {
    "path": "docs/standard/ali.md",
    "content": "# 《阿里Java开发手册》定制纪录\n\n只记录较大的改动，对更多条目内容的重新组织与扩写，则未一一冗述。\n\n* [《唯品会Java开发手册》－与阿里手册的比较文学I](http://calvin1978.blogcn.com/?p=1771)\n\n\n## (一) 命名规约\n\n对应 [阿里规范《命名风格》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/命名风格.md)\n\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n| 13. 变量、参数重名覆盖|  |新增规则|\n| 1. 禁止拼音缩写 | 2. 严禁使用拼音与英文混合的方式 |改写规则|\n| 3. 禁用其他编程语言风格的前缀和后缀 | 1. 代码中的命名均不能以下划线或美元符号开始| 扩写规则，把其他语言的啰嗦都禁止掉|\n| 4. 命名的好坏，在于其“模糊度”| 11. 为了达到代码自解释的目标 | 扩写规则，参考《Clean Code》的更多例子|\n| 6. 常量命名全部大写| 5.常量名大写| 扩写规则|\n| | 7. 类型与中括号紧挨相连来定义数组 | 删除规则，非命名风格，也不重要|\n| | 13. 接口类中的方法和成员变量不要加任何修饰符号| 移动规则，非命名风格，移到类设计|\n| | 16. 各层命名规约| 删除规则，各公司有自己的习惯|\n\n----\n\n## (二) 格式规约\n\n\n对应 [阿里规范《代码格式》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/代码格式.md)\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n| 1. 项目组统一的代码格式模板| 规则1-8 | 用IDE模版代替逐条描述<br/>同时对Tab/空格不做硬性规定|\n| 3. 用小括号来限定运算优先级| | 新增规则|\n| 4. 类内方法定义的顺序| | 新增规则|\n| 5. 通过空行进行逻辑分段| 11. 不同逻辑、不同语义| 改写规则|\n| 6. 避免IDE格式化| | 新增规则|\n| | 10.单个方法行数不超过80行| 删除规则，非格式规约，移动方法设计|\n| | 11.没有必要增加若干空格来对齐| 删除规则，现在很少人这么做|\n\n----\n\n## (三) 注释规约\n\n对应 [阿里规范《注释规约》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/注释规约.md)\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n| 2. 删除空注释，无意义注释| |增加规则|\n| 7. JavaDoc中不要大量使用HTML标签和转义字符 | | 增加规则|\n| 1. 注释的基本要求| 9. 对于注释的要求|扩写规则|\n| 4.避免创建人的注释 | 3.所有的类都必须添加创建者| 冲突规则|\n| | 2.所有的抽象方法必须用Javadoc注释| 删除规则，因为规则2不强制，并入规则1 |\n| | 4.方法内部单行注释，使用//注释| 删除规则，区别不大不强求 |\n| | 5. 所有的枚举类型字段必须要有注释|删除规则，因为规则2不强制|\n\n\n\n----\n\n## (四) 方法设计\n\n\n* 规则 6，7，12，13 从[阿里规范《控制语句》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/控制语句.md) 移入\n* 规则 9 从[阿里规范《异常处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/异常处理.md) 移入\n* 规则1，2，3，4，5，8，10，11，14为新建规则\n\n----\n\n## (五) 类设计\n\n对应 [阿里规范《OOP规范》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/OOP规范.md)\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n|2.减少类之间的依赖 | |增加规则|\n|3.定义变量与方法参数时，尽量使用接口 | |增加规则|\n|4.类的长度度量 | |增加规则|\n|5.Builder模式 | |增加规则|\n|8.静态方法不能被覆写 | |增加规则|\n|9.静态方法的访问原则 | |扩写规则|\n|10.内部类原则 | |增加规则|\n|12-14.hashCode，equals，toString的规则 | |增加规则|\n|16.总是移除无用属性、方法与参数 | |增加规则|\n|18.【推荐】得墨忒耳法则 | |增加规则|\n| | 3. 提倡同学们尽量不用可变参数编程|删除规则|\n| | 9. 定义DO/DTO/VO等POJO类时，不要设定任何属性默认值|删除规则|\n| | 10. 序列化类新增属性时，请不要修改serialVersionUID字段|删除规则|\n| | 13. 使用索引访问用String的split方法时|删除规则|\n| | 19. 慎用Object的clone方法来拷贝对象|删除规则|\n| | 规则4，5 |移到《方法规约》|\n| | 规则6 |移到《通用设计》|\n| | 规则7，8，17 |移到《基础类型》|\n| | 规则14，15|移到《格式规约》|\n\n----\n\n## (六) 控制语句\n\n对应 [阿里规范《控制语句》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/控制语句.md)\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n|4.布尔表达式中的运算符个数不超过4个 | |扩写规则|\n|5.善用三目运算符 | |增加规则|\n|6.能造成短路概率较大的逻辑放前面 | |增加规则|\n|10.能用while循环实现的代码，就不用do-while循环 | |增加规则|\n| | 3. 在高并发场景中，避免使用 ”等于”作为条件|删除规则|\n| | 8. 接口入参保护|删除规则|\n| | 9. 下列情形，需要进行参数校验|移到《方法规约》|\n| | 10. 下列情形，不需要进行参数校|移到《方法规约》|\n\n----\n\n## (七) 基本类型与字符串\n\n* 规则1，3 从 阿里规范[《OOP规范》](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/OOP规范.md)移入，并对2进行扩写\n* 规则5 从 阿里规范[《常量定义》](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/常量定义.md)移入并扩写\n* 规则8 从 阿里规范[《其他》](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/其他.md)移入\n* 规则4，6，7为新建规则\n\n----\n\n## (八) 集合处理\n\n对应 [阿里规范《集合处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/集合处理.md)\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n| 2. foreach语法遍历| |增加规则|\n| 7. 长生命周期的集合| |增加规则|\n| 8. 并发集合| |增加规则|\n| 9. 泛型的通配符| |增加规则|\n| 10. `List, List<?>` 与 `List<Object>`的选择| |增加规则|\n| 11. EnumMap| |增加规则|\n| | 2. ArrayList的subList结果|删除规则|\n| | 6. 泛型通配符|删除规则|\n| | 12.合理利用好集合的有序性|删除规则|\n| | 13.利用Set元素唯一的特性|删除规则|\n|12.Array 与 List互转的|使用集合转数组的方法，必须使用集合的toArray|某位老大的测试，new String[0]也不错|\n\n----\n\n## (九) 并发处理： 并发与多线程\n\n对应 [阿里规范《并发处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/并发处理.md)\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n|1. 指定线程名 | |扩写规则|\n|4. 正确停止线程 | |扩写规则|\n|5. 编写可中断的Runnable| |增加规则|\n|6. Runnable中必须捕获一切异常 |9.多线程并行处理定时任务 |扩写规则|\n|7. 全局变量的线程安全 | |扩写规则|\n|10. 选择分离锁, 分散锁甚至无锁的数据结构| |增加规则|\n|13. volatile修饰符，AtomicXX系列的正确使用 | |扩写规则|\n| |8.并发修改同一记录时，需要加锁 |删除规则|\n| |10.使用CountDownLatch进行异步转同步操作 |删除规则|\n| |14.HashMap在容量不够进行resize|移到《集合规约》一章|\n|14. 延时初始化的正确写法 |12.双重检查锁 |冲突规则|\n\n----\n\n## (十) 异常处理\n\n对应 [阿里规范《异常处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/异常处理.md)\n\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n|2.在特定场合，避免每次构造异常| |增加规则|\n|5.异常抛出的原则| |增加规则|\n|6.异常捕获的原则| |增加规则|\n|7.异常处理的原则| |增加规则|\n| |8.捕获异常与抛异常，必须是完全匹配 |删除规则|\n| |12.对于公司外的开放接口必须使用“错误码” |删除规则|\n| |13.DRY原则 |删除规则，为什么会出现在这章，太著名了|\n| |9.返回值可以为null |移到《方法设计》一章|\n| |10. 【推荐】防止NPE，是程序员的基本修养 |拆开到各章|\n| |11.避免直接抛出RuntimeException |规则冲突|\n\n----\n\n## (十一) 日志规约\n\n对应 [阿里规范《日志规约》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/日志规约.md)\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n|4.尽量使用异步日志| |增加规则|\n|5.禁止使用System.out()| |增加规则|\n|6.禁止配置日志框架打印日志打印时的类名，行号等信息| |增加规则|\n| |2.日志文件推荐至少保存15天 |删除规则|\n| |3.应用中的扩展日志命名方式 |删除规则|\n| |6.异常信息应该包括两类信息 |移到《异常处理》|\n|2.合理使用使用占位符 |4.对trace/debug/info级别的日志使用占位符 |还是要判断日志是否必然输出，<br>并强调条件判断与占位符之间的差别|\n\n----\n\n## (十二) 其他规约\n\n保留 [阿里规范《常量定义》一章](dhttps://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/常量定义.md)的规则1\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n| | 规则2－4  | 删除规则|\n| | 规则5. 如果变量值仅在一个固定范围内变化用enum类型来定义 | 移到《基本类型》|\n\n保留 [阿里规范《其他》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/其他.md)的规则7\n\n| VIP 规范 | 阿里规范 | 修改|\n| -------- | -------- |-------- |\n| | 规则2－4，6－8 | 删除规则 |\n| | 规则1. 在使用正则表达式时，利用好其预编译功 | 移到《基本类型》|\n\n* 规则3，4，5，6，7均为新增规则\n"
  },
  {
    "path": "docs/standard/chapter01.md",
    "content": "# (一) 命名规约\n\n**Rule 1. 【强制】禁止拼音缩写，避免阅读者费劲猜测；尽量不用拼音，除非中国式业务词汇没有通用易懂的英文对应。**\n\n```text\n禁止： DZ[打折] / getPFByName() [评分]\n\n尽量避免：Dazhe / DaZhePrice\n\n```\n\n----\n\n**Rule 2. 【强制】禁止使用非标准的英文缩写**\n\n```text\n反例： AbstractClass 缩写成 AbsClass；condition 缩写成 condi。\n```\n\n----\n\n**Rule 3. 【强制】禁用其他编程语言风格的前缀和后缀**\n\n在其它编程语言中使用的特殊前缀或后缀，如 `_name`、`name_`、`mName`、`i_name`，在 Java 中都禁止使用。\n\n----\n\n**Rule 4. 【推荐】命名的好坏，在于其“模糊度”**\n\n1）如果上下文很清晰，局部变量可以使用 `list` 这种简略命名， 否则应该使用 `userList` 这种更清晰的命名。\n\n\n2）禁止 `a1, a2, a3` 这种带编号的没诚意的命名方式。\n\n\n3）方法的参数名叫 `bookList` ，方法里的局部变量名叫 `theBookList` 也是很没诚意。\n\n\n4）如果一个应用里同时存在 `Account、AccountInfo、AccountData` 类，或者一个类里同时有 `getAccountInfo()、getAccountData()`, `save()、 store()` 的函数，阅读者将非常困惑。\n\n\n5） `callerId` 与 `calleeId`， `mydearfriendswitha` 与 `mydearfriendswithb` 这种拼写极度接近，考验阅读者眼力的。\n\n----\n\n**Rule 5. 【推荐】包名全部小写。点分隔符之间尽量只有一个英语单词，即使有多个单词也不使用下划线或大小写分隔**\n\n```text\n正例： com.vip.javatool\n\n反例： com.vip.java_tool, com.vip.javaTool\n```\n\n* [Sonar-120:Package names should comply with a naming convention](https://rules.sonarsource.com/java/RSPEC-120)\n\n----\n\n**Rule 6. 【强制】类名与接口名使用UpperCamelCase风格，遵从驼峰形式**\n\nTcp, Xml等缩写也遵循驼峰形式，可约定例外如：DTO/ VO等。\n\n``` text\n正例：UserId / XmlService / TcpUdpDeal / UserVO\n\n反例：UserID / XMLService / TCPUDPDeal / UserVo\n```\n\n* [Sonar-101:Class names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-101)\n* [Sonar-114:Interface names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-114)\n\n----\n\n**Rule 7. 【强制】方法名、参数名、成员变量、局部变量使用lowerCamelCase风格，遵从驼峰形式**\n\n```text\n正例： localValue / getHttpMessage();\n```\n\n* [Sonar-100:Method names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-100)\n* [Sonar-116:Field names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-116)\n* [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)\n\n----\n\n**Rule 8. 【强制】常量命名全大写，单词间用下划线隔开。力求语义表达完整清楚，不要嫌名字长**\n\n```text\n正例： MAX_STOCK_COUNT\n\n反例： MAX_COUNT\n```\n\n例外：当一个static final字段不是一个真正常量，比如不是基本类型时，不需要使用大写命名。\n\n```java\nprivate static final Logger logger = Logger.getLogger(MyClass.class);\n```\n\n例外：枚举常量推荐全大写，但如果历史原因未遵循也是允许的，所以我们修改了Sonar的规则。\n\n* [Sonar-115:Constant names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-115)\n* [Sonar-308:Static non-final field names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-308)\n\n----\n\n**Rule 9. 【推荐】如果使用到了通用的设计模式，在类名中体现，有利于阅读者快速理解设计思想**\n\n``` text\n正例：OrderFactory， LoginProxy ，ResourceObserver\n```\n\n----\n\n**Rule 10. 【推荐】枚举类名以Enum结尾; 抽象类使用Abstract或Base开头；异常类使用Exception结尾；测试类以它要测试的类名开始，以Test结尾**\n\n```text\n正例：DealStatusEnum， AbstractView，BaseView， TimeoutException，UserServiceTest\n```\n\n* [Sonar-2166:Classes named like \"Exception\" should extend \"Exception\" or a subclass](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-2166)\n* [Sonar-3577:Test classes should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-3577)\n\n\n----\n\n**Rule 11. 【推荐】实现类尽量用Impl的后缀与接口关联，除了形容能力的接口**\n\n```text\n正例：CacheServiceImpl 实现 CacheService接口。\n\n正例: Foo 实现 Translatable接口。\n```\n\n----\n\n**Rule 12. 【强制】POJO类中布尔类型的变量名，不要加is前缀，否则部分框架解析会引起序列化错误**\n\n```text\n反例：Boolean isSuccess的成员变量，它的GET方法也是isSuccess()，部分框架在反射解析的时候，“以为”对应的成员变量名称是success，导致出错。\n```\n\n----\n\n**Rule 13. 【强制】避免成员变量，方法参数，局部变量的重名复写，引起混淆**\n\n* 类的私有成员变量名，不与父类的成员变量重名\n\n* 方法的参数名/局部变量名，不与类的成员变量重名 (getter/setter例外)\n\n下面错误的地方，Java在编译时很坑人的都是合法的，但给阅读者带来极大的障碍。\n\n```java\npublic class A {\n  int foo;\n}\n\npublic class B extends A {\n  int foo; //WRONG\n  int bar;\n\n  public void hello(int bar) { //WRONG\n    int foo = 0; //WRONG\n  }\n\n  public void setBar(int bar) { //OK\n    this.bar = bar;\n  }\n}\n```\n\n* [Sonar-2387: Child class fields should not shadow parent class fields](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-2387)\n* [Sonar: Local variables should not shadow class fields](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-1117)\n\n----\n"
  },
  {
    "path": "docs/standard/chapter02.md",
    "content": "# (二) 格式规约\n\n**Rule 1. 【强制】使用项目组统一的代码格式模板，基于IDE自动的格式化**\n\n1）IDE的默认代码格式模板，能简化绝大部分关于格式规范(如空格，括号)的描述。\n\n2）统一的模板，并在接手旧项目先进行一次全面格式化，可以避免， 不同开发者之间，因为格式不统一产生代码合并冲突，或者代码变更日志中因为格式不同引起的变更，掩盖了真正的逻辑变更。\n\n3）设定项目组统一的行宽，建议120。\n\n4）设定项目组统一的缩进方式(Tab或二空格，四空格均可)，基于IDE自动转换。\n\n\n* [VIP代码格式化模板](https://github.com/vipshop/vjtools/tree/master/standard/formatter)\n\n----\n\n**Rule 2. 【强制】IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式，不要使用Windows格式**\n\n----\n\n**Rule 3. 【推荐】 用小括号来限定运算优先级**\n\n我们没有理由假设读者能记住整个Java运算符优先级表。除非作者和Reviewer都认为去掉小括号也不会使代码被误解，甚至更易于阅读。\n\n```java\nif ((a == b) && (c == d))\n```\n\n* [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:\"\"` 不需要加括号。\n\n----\n\n**Rule 4. 【推荐】类内方法定义的顺序，不要“总是在类的最后添加新方法”**\n\n一个类就是一篇文章，想象一个阅读者的存在，合理安排方法的布局。\n\n1）顺序依次是：构造函数 > (公有方法>保护方法>私有方法)  > getter/setter方法。\n\n如果公有方法可以分成几组，私有方法也紧跟公有方法的分组。\n\n\n2）当一个类有多个构造方法，或者多个同名的重载方法，这些方法应该放置在一起。其中参数较多的方法在后面。\n\n```java\npublic Foo(int a) {...}\npublic Foo(int a, String b) {...}\n\npublic void foo(int a) {...}\npublic void foo(int a, String b) {...}\n```\n\n\n3）作为调用者的方法，尽量放在被调用的方法前面。\n\n```java\npublic void foo() {\n\tbar();\n}\n\npublic void bar() {...}\n```\n\n----\n\n**Rule 5. 【推荐】通过空行进行逻辑分段**\n\n一段代码也是一段文章，需要合理的分段而不是一口气读到尾。\n\n不同组的变量之间，不同业务逻辑的代码行之间，插入一个空行，起逻辑分段的作用。\n\n而联系紧密的变量之间、语句之间，则尽量不要插入空行。\n\n```java\nint width;\nint height;\n\nString name;\n```\n\n----\n\n**Rule 6.【推荐】避免IDE格式化**\n\n对于一些特殊场景（如使用大量的字符串拼接成一段文字，或者想把大量的枚举值排成一列），为了避免IDE自动格式化，土办法是把注释符号//加在每一行的末尾，但这有视觉的干扰，可以使用@formatter:off和@formatter:on来包装这段代码，让IDE跳过它。\n\n``` java\n// @formatter:off\n...\n// @formatter:on\n```\n----\n"
  },
  {
    "path": "docs/standard/chapter03.md",
    "content": "# (三) 注释规约\n\n**Rule 1.【推荐】基本的注释要求**\n\n完全没有注释的大段代码对于阅读者形同天书，注释是给自己看的，即使隔很长时间，也能清晰理解当时的思路；注释也是给继任者看的，使其能够快速接替自己的工作。\n\n代码将被大量后续维护，注释如果对阅读者有帮助，不要吝啬在注释上花费的时间。(但也综合参见规则2，3)\n\n第一、能够准确反应设计思想和代码逻辑；第二、能够描述业务含义，使别的程序员能够迅速了解到代码背后的信息。\n\n除了特别清晰的类，都尽量编写类级别注释，说明类的目的和使用方法。\n\n除了特别清晰的方法，对外提供的公有方法，抽象类的方法，同样尽量清晰的描述：期待的输入，对应的输出，错误的处理和返回码，以及可能抛出的异常。\n\n----\n\n**Rule 2. 【推荐】通过更清晰的代码来避免注释**\n\n在编写注释前，考虑是否可以通过更好的命名，更清晰的代码结构，更好的函数和变量的抽取，让代码不言自明，此时不需要额外的注释。\n\n----\n\n**Rule 3. 【推荐】删除空注释，无意义注释**\n\n《Clean Code》建议，如果没有想说的，不要留着IDE自动生成的，空的@param，@return，@throws 标记，让代码更简洁。\n\n反例：方法名为put，加上两个有意义的变量名elephant和fridge，已经说明了这是在干什么，不需要任何额外的注释。\n\n```java\n/**\n * put elephant into fridge.\n *\n * @param elephant\n * @param fridge\n * @return\n */\npublic void put(Elephant elephant, Fridge fridge);\n```\n----\n\n**Rule 4.【推荐】避免创建人，创建日期，及更新日志的注释**\n\n代码后续还会有多人多次维护，而创建人可能会离职，让我们相信源码版本控制系统对更新记录能做得更好。\n\n----\n\n**Rule 5. 【强制】代码修改的同时，注释也要进行相应的修改。尤其是参数、返回值、异常、核心逻辑等的修改**\n\n----\n\n**Rule 6. 【强制】类、类的公有成员、方法的注释必须使用Javadoc规范，使用/\\*\\* xxx \\*/格式，不得使用`//xxx`方式**\n\n正确的JavaDoc格式可以在IDE中，查看调用方法时，不进入方法即可悬浮提示方法、参数、返回值的意义，提高阅读效率。\n\n----\n\n**Rule 7. 【推荐】JavaDoc中不要为了HTML格式化而大量使用HTML标签和转义字符**\n\n如果为了Html版JavaDoc的显示，大量使用`<p\\>` `<pre\\>`这样的html标签，以及`&lt` `&quot` 这样的html转义字符，严重影响了直接阅读代码时的直观性，而直接阅读代码的几率其实比看Html版的JavaDoc大得多。\n\n另外IDE对JavaDoc的格式化也要求`<p>`之类的标签来换行，可以配置让IDE不对JavaDoc的格式化。\n\n----\n\n**Rule 8. 【推荐】注释不要为了英文而英文**\n\n如果没有国际化要求，中文能表达得更清晰时还是用中文。\n\n----\n\n**Rule 9. 【推荐】TODO 标记，清晰说明代办事项和处理人**\n\n“对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释. ”\n\n清晰描述待修改的事项，保证过几个月后仍然能够清楚要做什么修改。\n\n如果近期会处理的事项，写明处理人。如果远期的，写明提出人。\n\n通过 IDE 和 Sonar 的标记扫描，经常清理此类标记，线上故障经常来源于这些标记但未处理的代码。\n\n正例：\n\n```java\n// TODO(who): to do what.\n// TODO(who): when to do what.\n// TODO(calvin): use xxx to replace yyy.\n```\n\n反例：\n\n```java\n// TODO: refactor it\n```\n\n不推荐使用 FIXME 标记。这样，在项目中搜索 TODO 就可以得到完整的 TODO list，而不需要搜索 TODO 和 FIXME。\n\n* [Sonar: Track uses of \"TODO\" tags](https://rules.sonarsource.com/java/RSPEC-1135)\n\n----\n\n**Rule 10. 【推荐】合理处理注释掉的代码**\n\n如果后续会恢复此段代码，在目标代码上方用`///`说明注释动机，而不是简单的注释掉代码。\n\n如果很大概率不再使用，则直接删除（版本管理工具保存了历史代码）。\n\n* [Sonar: Sections of code should not be \"commented out\"](https://rules.sonarsource.com/java/RSPEC-125)\n\n----\n"
  },
  {
    "path": "docs/standard/chapter04.md",
    "content": "# (四) 方法设计\n\n**Rule 1. 【推荐】方法的长度度量**\n\n方法尽量不要超过100行，或其他团队共同商定的行数。\n\n另外，方法长度超过8000个字节码时，将不会被JIT编译成二进制码。\n\n\n* [Sonar-107: Methods should not have too many lines](https://rules.sonarsource.com/java/RSPEC-107)，默认值改为100\n* Facebook-Contrib:Performance - This method is too long to be compiled by the JIT\n\n----\n\n**Rule 2. 【推荐】方法的语句在同一个抽象层级上**\n\n反例：一个方法里，前20行代码在进行很复杂的基本价格计算，然后调用一个折扣计算函数，再调用一个赠品计算函数。\n\n此时可将前20行也封装成一个价格计算函数，使整个方法在同一抽象层级上。\n\n----\n\n**Rule 3. 【推荐】为了帮助阅读及方法内联，将小概率发生的异常处理及其他极小概率进入的代码路径，封装成独立的方法**\n\n```java\nif(seldomHappenCase) {\n  hanldMethod();\n}\n\ntry {\n  ...\n} catch(SeldomHappenException e) {\n  handleException();\n}\n```\n\n----\n\n**Rule 4. 【推荐】尽量减少重复的代码，抽取方法**\n\n超过5行以上重复的代码，都可以考虑抽取公用的方法。\n\n----\n\n**Rule 5. 【推荐】方法参数最好不超过3个，最多不超过7个**\n\n1）如果多个参数同属于一个对象，直接传递对象。\n\n例外: 你不希望依赖整个对象，传播了类之间的依赖性。\n\n\n2）将多个参数合并为一个新创建的逻辑对象。\n\n例外: 多个参数之间毫无逻辑关联。\n\n\n3）将函数拆分成多个函数，让每个函数所需的参数减少。\n\n* [Sonar-107: Methods should not have too many parameters](https://rules.sonarsource.com/java/RSPEC-107)\n\n----\n\n**Rule 6.【推荐】下列情形，需要进行参数校验**\n\n1） 调用频次低的方法。\n\n2） 执行时间开销很大的方法。此情形中，参数校验时间几乎可以忽略不计，但如果因为参数错误导致中间执行回退，或者错误，代价更大。\n\n3） 需要极高稳定性和可用性的方法。\n\n4） 对外提供的开放接口，不管是RPC/HTTP/公共类库的API接口。\n\n如果使用Apache Validate 或 Guava Precondition进行校验，并附加错误提示信息时，注意不要每次校验都做一次字符串拼接。\n\n```java\n//WRONG\nValidate.isTrue(length > 2, \"length is \"+keys.length+\", less than 2\", length);\n//RIGHT\nValidate.isTrue(length > 2, \"length is %d, less than 2\", length);\n```\n\n----\n\n**Rule 7.【推荐】下列情形，不需要进行参数校验**\n\n1） 极有可能被循环调用的方法。\n\n\n2） 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道，参数错误不太可能到底层才会暴露问题。\n\n比如，一般DAO层与Service层都在同一个应用中，所以DAO层的参数校验，可以省略。\n\n\n3） 被声明成private，或其他只会被自己代码所调用的方法，如果能够确定在调用方已经做过检查，或者肯定不会有问题则可省略。\n\n即使忽略检查，也尽量在方法说明里注明参数的要求，比如vjkit中的@NotNull，@Nullable标识。\n\n----\n\n**Rule 8.【推荐】禁用assert做参数校验**\n\nassert断言仅用于测试环境调试，无需在生产环境时进行的校验。因为它需要增加-ea启动参数才会被执行。而且校验失败会抛出一个AssertionError(属于Error，需要捕获Throwable）\n\n因此在生产环境进行的校验，需要使用Apache Commons Lang的Validate或Guava的Precondition。\n\n----\n\n**Rule 9.【推荐】返回值可以为Null，可以考虑使用JDK8的Optional类**\n\n不强制返回空集合，或者空对象。但需要添加注释充分说明什么情况下会返回null值。\n\n本手册明确`防止NPE是调用者的责任`。即使被调用方法返回空集合或者空对象，对调用者来说，也并非高枕无忧，必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。\n\nJDK8的Optional类的使用这里不展开。\n\n----\n\n**Rule 10.【推荐】返回值可以为内部数组和集合**\n\n如果觉得被外部修改的可能性不大，或没有影响时，不强制在返回前包裹成Immutable集合，或进行数组克隆。\n\n----\n\n**Rule 11.【推荐】不能使用有继承关系的参数类型来重载方法**\n\n因为方法重载的参数类型是根据编译时表面类型匹配的，不根据运行时的实际类型匹配。\n\n```java\nclass A {\n  void hello(List list);\n  void hello(ArrayList arrayList);\n}\n\nList arrayList = new ArrayList();\n\n// 下句调用的是hello(List list)，因为arrayList的定义类型是List\na.hello(arrayList);\n```\n\n----\n\n**Rule 12.【强制】正被外部调用的接口，不允许修改方法签名，避免对接口的调用方产生影响**\n\n只能新增新接口，并对已过时接口加@Deprecated注解，并清晰地说明新接口是什么。\n\n----\n\n**Rule 13.【推荐】不使用`@Deprecated`的类或方法**\n\n接口提供方既然明确是过时接口并提供新接口，那么作为调用方来说，有义务去考证过时方法的新实现是什么。\n\n比如java.net.URLDecoder 中的方法decode(String encodeStr) 这个方法已经过时，应该使用双参数decode(String source, String encode)。\n\n----\n\n**Rule 14.【推荐】不使用不稳定方法，如com.sun.\\*包下的类，底层类库中internal包下的类**\n\n`com.sun.*`，`sun.*`包下的类，或者底层类库中名称为internal的包下的类，都是不对外暴露的，可随时被改变的不稳定类。\n\n* [Sonar-1191: Classes from \"sun.*\" packages should not be used](https://rules.sonarsource.com/java/RSPEC-1191)\n\n----\n"
  },
  {
    "path": "docs/standard/chapter05.md",
    "content": "# (五) 类设计\n\n**Rule 1. 【推荐】类成员与方法的可见性最小化**\n\n任何类、方法、参数、变量，严控访问范围。过于宽泛的访问范围，不利于模块解耦。思考：如果是一个private的方法，想删除就删除，可是一个public的service方法，或者一个public的成员变量，删除一下，不得手心冒点汗吗？\n\n例外：为了单元测试，有时也可能将访问范围扩大，此时需要加上JavaDoc说明或vjkit中的`@VisibleForTesting`注解。\n\n----\n\n**Rule 2.【推荐】 减少类之间的依赖**\n\n比如如果A类只依赖B类的某个属性，在构造函数和方法参数中，只传入该属性。让阅读者知道，A类只依赖了B类的这个属性，而不依赖其他属性，也不会调用B类的任何方法。\n\n```java\na.foo(b);     //WRONG\n\na.foo(b.bar); //RIGHT\n```\n----\n\n**Rule 3.【推荐】 定义变量与方法参数时，尽量使用接口而不是具体类**\n\n使用接口可以保持一定的灵活性，也能向读者更清晰的表达你的需求：变量和参数只是要求有一个Map，而不是特定要求一个HashMap。\n\n例外：如果变量和参数要求某种特殊类型的特性，则需要清晰定义该参数类型，同样是为了向读者表达你的需求。\n\n----\n\n**Rule 4. 【推荐】类的长度度量**\n\n类尽量不要超过300行，或其他团队共同商定的行数。\n\n对过大的类进行分拆时，可考虑其内聚性，即类的属性与类的方法的关联程度，如果有些属性没有被大部分的方法使用，其内聚性是低的。\n\n----\n\n**Rule 5.【推荐】 构造函数如果有很多参数，且有多种参数组合时，建议使用Builder模式**\n\n```java\nExecutor executor = new ThreadPoolBuilder().coreThread(10).queueLenth(100).build();\n```\n\n即使仍然使用构造函数，也建议使用chain constructor模式，逐层加入默认值传递调用，仅在参数最多的构造函数里实现构造逻辑。\n\n```java\npublic A(){\n  A(DEFAULT_TIMEOUT);\n}\n\npublic A(int timeout) {\n  ...\n}\n```\n\n----\n\n**Rule 6.【推荐】构造函数要简单，尤其是存在继承关系的时候**\n\n可以将复杂逻辑，尤其是业务逻辑，抽取到独立函数，如init()，start()，让使用者显式调用。\n\n```java\nFoo foo = new Foo();\nfoo.init();\n```\n\n----\n\n**Rule 7.【强制】所有的子类覆写方法，必须加`@Override`注解**\n\n比如有时候子类的覆写方法的拼写有误，或方法签名有误，导致没能真正覆写，加`@Override`可以准确判断是否覆写成功。\n\n而且，如果在父类中对方法签名进行了修改，子类会马上编译报错。\n\n另外，也能提醒阅读者这是个覆写方法。\n\n最后，建议在IDE的Save Action中配置自动添加`@Override`注解，如果无意间错误同名覆写了父类方法也能被发现。\n\n* [Sonar-1161: \"@Override\" should be used on overriding and implementing methods](https://rules.sonarsource.com/java/RSPEC-1161)\n\n----\n\n**Rule 8.【强制】静态方法不能被子类覆写。**\n\n因为它只会根据表面类型来决定调用的方法。\n\n```java\nBase base = new Children();\n\n// 下句实际调用的是父类的静态方法，虽然对象实例是子类的。\nbase.staticMethod();\n```\n\n----\n\n**Rule 9.静态方法访问的原则**\n\n**9.1【推荐】避免通过一个类的对象引用访问此类的静态变量或静态方法，直接用类名来访问即可**\n\n目的是向读者更清晰传达调用的是静态方法。可在IDE的Save Action中配置自动转换。\n\n```java\nint i = objectA.staticMethod(); // WRONG\n\nint i = ClassA.staticMethod(); // RIGHT\n```\n\n* [Sonar-2209: \"static\" members should be accessed statically](https://rules.sonarsource.com/java/RSPEC-2209)\n* [Sonar-2440: Classes with only \"static\" methods should not be instantiated](https://rules.sonarsource.com/java/RSPEC-2440)\n\n\n**9.2 【推荐】除测试用例，不要static import 静态方法**\n\n静态导入后忽略掉的类名，给阅读者造成障碍。\n\n例外：测试环境中的assert语句，大家都太熟悉了。\n\n* [Sonar-3030: Classes should not have too many \"static\" imports](https://rules.sonarsource.com/java/RSPEC-3030) 但IDEA经常自动转换static import，所以暂不作为规则。\n\n\n**9.3【推荐】尽量避免在非静态方法中修改静态成员变量的值**\n\n```java\n// WRONG\npublic void foo() {\n  ClassA.staticFiled = 1;\n}\n```\n\n* [Sonar-2696: Instance methods should not write to \"static\" fields](https://rules.sonarsource.com/java/RSPEC-2696)\n* [Sonar-3010: Static fields should not be updated in constructors](https://rules.sonarsource.com/java/RSPEC-3010)\n\n\n----\n\n**Rule 10.【推荐】 内部类的定义原则**\n\n当一个类与另一个类关联非常紧密，处于从属的关系，特别是只有该类会访问它时，可定义成私有内部类以提高封装性。\n\n另外，内部类也常用作回调函数类，在JDK8下建议写成Lambda。\n\n内部类分匿名内部类，内部类，静态内部类三种。\n\n1) 匿名内部类 与 内部类，按需使用：\n\n在性能上没有区别；当内部类会被多个地方调用，或匿名内部类的长度太长，已影响对调用它的方法的阅读时，定义有名字的内部类。\n\n\n2) 静态内部类 与 内部类，优先使用静态内部类：\n\n1. 非静态内部类持有外部类的引用，能访问外类的实例方法与属性。构造时多传入一个引用对性能没有太大影响，更关键的是向阅读者传递自己的意图，内部类会否访问外部类。\n2. 非静态内部类里不能定义static的属性与方法。\n\n* [Sonar-2694: Inner classes which do not reference their owning classes should be \"static\"](https://rules.sonarsource.com/java/RSPEC-2694)\n* [Sonar-1604: Anonymous inner classes containing only one method should become lambdas](https://rules.sonarsource.com/java/RSPEC-1604)\n\n----\n\n**Rule 11.【推荐】使用getter/setter方法，还是直接public成员变量的原则。**\n\n除非因为特殊原因方法内联失败，否则使用getter方法与直接访问成员变量的性能是一样的。\n\n使用getter/setter，好处是可以进一步的处理：\n\n1. 通过隐藏setter方法使得成员变量只读\n\n2. 增加简单的校验逻辑\n\n3. 增加简单的值处理，值类型转换等\n\n建议通过IDE生成getter/setter。\n\n但getter/seter中不应有复杂的业务处理，建议另外封装函数，并且不要以getXX/setXX命名。\n\n\n如果是内部类，以及无逻辑的POJO/VO类，使用getter/setter除了让一些纯OO论者感觉舒服，没有任何的好处，建议直接使用public成员变量。\n\n例外：有些序列化框架只能从getter/setter反射，不能直接反射public成员变量。\n\n----\n\n**Rule 12.【强制】POJO类必须覆写toString方法。**\n\n便于记录日志，排查问题时调用POJO的toString方法打印其属性值。否则默认的Object.toString()只打印`类名@数字`的无效信息。\n\n----\n\n**Rule 13. hashCode和equals方法的处理，遵循如下规则:**\n\n\n**13.1【强制】只要重写equals，就必须重写hashCode。 而且选取相同的属性进行运算。**\n\n\n**13.2【推荐】只选取真正能决定对象是否一致的属性，而不是所有属性，可以改善性能。**\n\n\n**13.3【推荐】对不可变对象，可以缓存hashCode值改善性能（比如String就是例子）。**\n\n\n**13.4【强制】类的属性增加时，及时重新生成toString，hashCode和equals方法。**\n\n\n* [Sonar-1206: \"equals(Object obj)\" and \"hashCode()\" should be overridden in pairs](https://rules.sonarsource.com/java/RSPEC-1206)\n\n----\n\n**Rule 14.【强制】使用 IDE 生成 toString、hashCode 和 equals 方法。**\n\n使用 IDE 生成而不是手写，能保证 toString 有统一的格式，equals 和 hashCode 则避免不正确的 null 值处理。\n\n子类生成 toString 时，还需要勾选父类的属性。\n\n----\n\n**Rule 15. 【强制】Object的equals方法容易抛空指针异常，应使用常量或确定非空的对象来调用equals**\n\n推荐使用java.util.Objects#equals（JDK7引入的工具类）\n\n```java\n\"test\".equals(object);  //RIGHT\n\nObjects.equals(object, \"test\"); //RIGHT\n```\n\n* [Sonar-1132: Strings literals should be placed on the left side when checking for equality](https://rules.sonarsource.com/java/RSPEC-1132)\n\n----\n\n**Rule 16.【强制】除了保持兼容性的情况，总是移除无用属性、方法与参数**\n\n特别是private的属性、方法、内部类，private方法上的参数，一旦无用立刻移除。信任代码版本管理系统。\n\n* [Sonar-3985: Unused \"private\" classes should be removed](https://rules.sonarsource.com/java/RSPEC-3985)\n* [Sonar-1068: Unused \"private\" fields should be removed](https://rules.sonarsource.com/java/RSPEC-1068)\n* [Sonar: Unused \"private\" methods should be removed](https://rules.sonarsource.com/java/RSPEC-1144)\n* [Sonar-1481: Unused local variables should be removed](https://rules.sonarsource.com/java/RSPEC-1481)\n* [Sonar-1172: Unused method parameters should be removed](https://rules.sonarsource.com/java/RSPEC-1172) Sonar-VJ版只对private方法的无用参数告警。\n\n\n----\n\n**Rule 17.【推荐】final关键字与性能无关，仅用于下列不可修改的场景**\n\n1） 定义类及方法时，类不可继承，方法不可覆写；\n\n2） 定义基本类型的函数参数和变量，不可重新赋值；\n\n3） 定义对象型的函数参数和变量，仅表示变量所指向的对象不可修改，而对象自身的属性是可以修改的。\n\n----\n\n**Rule 18.【推荐】得墨忒耳法则，不要和陌生人说话**\n\n以下调用，一是导致了对A对象的内部结构(B,C)的紧耦合，二是连串的调用很容易产生NPE，因此链式调用尽量不要过长。\n\n```java\nobj.getA().getB().getC().hello();\n```\n\n----\n"
  },
  {
    "path": "docs/standard/chapter06.md",
    "content": "# (六) 控制语句\n\n**Rule 1. 【强制】if, else, for, do, while语句必须使用大括号，即使只有单条语句**\n\n曾经试过合并代码时，因为没加括号，单条语句合并成两条语句后，仍然认为只有单条语句，另一条语句在循环外执行。\n\n其他增加调试语句等情况也经常引起同样错误。\n\n可在IDE的Save Action中配置自动添加。\n\n```java\nif (a == b) {\n  ...\n}\n```\n\n例外：一般由IDE生成的equals()函数\n\n* [Sonar-121: Control structures should use curly braces](https://rules.sonarsource.com/java/RSPEC-121) Sonar-VJ版豁免了equals()函数\n\n----\n\n**Rule 2.【推荐】少用if-else方式，多用哨兵语句式以减少嵌套层次**\n\n```java\nif (condition) {\n  ...\n  return obj;\n}\n\n// 接着写else的业务逻辑代码;\n```\n\n* Facebook-Contrib: Style - Method buries logic to the right (indented) more than it needs to be\n\n----\n\n**Rule 3.【推荐】限定方法的嵌套层次**\n\n所有if/else/for/while/try的嵌套，当层次过多时，将引起巨大的阅读障碍，因此一般推荐嵌套层次不超过4。\n\n通过抽取方法，或哨兵语句（见Rule 2）来减少嵌套。\n\n```java\npublic void applyDriverLicense() {\n  if (isTooYoung()) {\n    System.out.println(\"You are too young to apply driver license.\");\n    return;\n  }\n\n  if (isTooOld()) {\n    System.out.println(\"You are too old to apply driver license.\");\n    return;\n  }\n\n  System.out.println(\"You've applied the driver license successfully.\");\n  return;\n}\n```\n\n* [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\n\n----\n\n**Rule 4.【推荐】布尔表达式中的布尔运算符(&&,||)的个数不超过4个，将复杂逻辑判断的结果赋值给一个有意义的布尔变量名，以提高可读性**\n\n\n```java\n//WRONG\nif ((file.open(fileName, \"w\") != null) && (...) || (...)|| (...)) {\n  ...\n}\n\n//RIGHT\nboolean existed = (file.open(fileName, \"w\") != null) && (...) || (...);\nif (existed || (...)) {\n  ...\n}\n```\n\n* [Sonar-1067: Expressions should not be too complex](https://rules.sonarsource.com/java/RSPEC-1067)，增大为4\n\n----\n\n**Rule 5.【推荐】简单逻辑，善用三目运算符，减少if-else语句的编写**\n\n```java\ns != null ? s : \"\";\n```\n\n----\n\n**Rule 6.【推荐】减少使用取反的逻辑**\n\n不使用取反的逻辑，有利于快速理解。且大部分情况，取反逻辑存在对应的正向逻辑写法。\n\n```java\n//WRONG\nif (!(x >= 268) { ... }\n\n//RIGHT\nif (x < 268) { ... }\n```\n\n* [Sonar-1940: Boolean checks should not be inverted](https://rules.sonarsource.com/java/RSPEC-1940)\n\n----\n\n**Rule 7.【推荐】表达式中，能造成短路概率较大的逻辑尽量放前面，使得后面的判断可以免于执行**\n\n\n```java\nif (maybeTrue() || maybeFalse()) { ... }\n\nif (maybeFalse() && maybeTrue()) { ... }\n```\n\n----\n\n\n**Rule 8.【强制】switch的规则**\n\n1）在一个switch块内，每个case要么通过break/return等来终止，要么注释说明程序将继续执行到哪一个case为止；\n\n2）在一个switch块内，都必须包含一个default语句并且放在最后，即使它什么代码也没有。\n\n```java\nString animal = \"tomcat\";\n\nswitch (animal) {\ncase \"cat\":\n  System.out.println(\"It's a cat.\");\n  break;\ncase \"lion\": // 执行到tiger\ncase \"tiger\":\n  System.out.println(\"It's a beast.\");\n  break;\ndefault:\n  // 什么都不做，也要有default\n  break;\n}\n```\n\n* [Sonar: \"switch\" statements should end with \"default\" clauses](https://rules.sonarsource.com/java/RSPEC-131)\n\n----\n\n**Rule 9.【推荐】循环体中的语句要考量性能，操作尽量移至循环体外处理**\n\n1）不必要的耗时较大的对象构造；\n\n2）不必要的try-catch（除非出错时需要循环下去）。\n\n----\n\n**Rule 10.【推荐】能用while循环实现的代码，就不用do-while循环**\n\nwhile语句能在循环开始的时候就看到循环条件，便于帮助理解循环内的代码；\n\ndo-while语句要在循环最后才看到循环条件，不利于代码维护，代码逻辑容易出错。\n\n----\n"
  },
  {
    "path": "docs/standard/chapter07.md",
    "content": "# (七) 基本类型与字符串\n\n**Rule 1. 原子数据类型(int等)与包装类型(Integer等)的使用原则**\n\n**1.1 【推荐】需要序列化的POJO类属性使用包装数据类型**\n\n\n**1.2 【推荐】RPC方法的返回值和参数使用包装数据类型**\n\n\n**1.3 【推荐】局部变量尽量使用基本数据类型**\n\n\n包装类型的坏处:\n\n1）Integer 24字节，而原子类型 int 4字节。\n\n2）包装类型每次赋值还需要额外创建对象，如Integer var = 200， 除非数值在缓存区间内(见Integer.IntegerCache与Long.LongCache)才会复用已缓存对象。默认缓存区间为-128到127，其中Integer的缓存区间还受启动参数的影响，如-XX:AutoBoxCacheMax=20000。\n\n3）包装类型还有==比较的陷阱（见规则3）\n\n\n包装类型的好处:\n\n1）包装类型能表达 null 的语义。\n\n比如数据库的查询结果可能是null，如果用基本数据类型有NPE风险。又比如显示成交总额涨跌情况，如果调用的RPC服务不成功时，应该返回null，显示成-%，而不是0%。\n\n2）集合需要包装类型，除非使用数组，或者特殊的原子类型集合。\n\n3）泛型需要包装类型，如`Result<Integer>`。\n\n----\n\n**Rule 2.原子数据类型与包装类型的转换原则**\n\n**2.1【推荐】自动转换(AutoBoxing)有一定成本，调用者与被调用函数间尽量使用同一类型，减少默认转换**\n\n```java\n//WRONG, sum 类型为Long， i类型为long，每次相加都需要AutoBoxing。\nLong sum=0L;\n\nfor( long i = 0; i < 10000; i++) {\n  sum+=i;\n}\n\n//RIGHT, 准确使用API返回正确的类型\nInteger i = Integer.valueOf(str);\nint i = Integer.parseInt(str);\n```\n\n* [Sonar-2153: Boxing and unboxing should not be immediately reversed](https://rules.sonarsource.com/java/RSPEC-2153)\n\n\n**2.2 【推荐】自动拆箱有可能产生NPE，要注意处理**\n\n```java\n//如果intObject为null，产生NPE\nint i = intObject;\n```\n\n----\n\n**Rule 3. 数值equals比较的原则**\n\n**3.1【强制】 所有包装类对象之间值的比较，全部使用equals方法比较**\n\n\\==判断对象是否同一个。Integer var = ?在缓存区间的赋值（见规则1），会复用已有对象，因此这个区间内的Integer使用==进行判断可通过，但是区间之外的所有数据，则会在堆上新产生，不会通过。因此如果用\\== 来比较数值，很可能在小的测试数据中通过，而到了生产环境才出问题。\n\n\n**3.2【强制】 BigDecimal需要使用compareTo()**\n\n因为BigDecimal的equals()还会比对精度，2.0与2.00不一致。\n\n* Facebook-Contrib: Correctness - Method calls BigDecimal.equals()\n\n**3.3【强制】 Atomic\\* 系列，不能使用 equals 方法**\n\n因为 Atomic\\* 系列没有覆写 equals 方法。\n\n```java\n// RIGHT\nif (counter1.get() == counter2.get()) {\n    // ...\n}\n```\n\n* [Sonar-2204: \".equals()\" should not be used to test the values of \"Atomic\" classes](https://rules.sonarsource.com/java/RSPEC-2204)\n\n**3.4【强制】 double 及 float 的比较，要特殊处理**\n\n因为精度问题，浮点数间的 equals 非常不可靠，在 vjkit 的 NumberUtil 中有对应的封装函数。\n\n```java\nfloat f1 = 0.15f;\nfloat f2 = 0.45f/3; // 实际等于0.14999999\n\n// 反例\nif (f1 == f2) {\n    // ...\n}\n\n// 反例\nif (Double.compare(f1, f2) == 0) {\n    // ...\n}\n\n// 正例\n// 绝对误差\nstatic final float EPSILON = 0.00001f;\nif (Math.abs(f1 - f2) < EPSILON) {\n    // ...\n}\n\n// 正例\n// 相对误差\nstatic final float EPSILON = 0.001f;\nif (Math.abs(f1 - f2) / (Math.abs(f1) + Math.abs(f2)) < EPSILON) {\n    // ...\n}\n```\n\n推荐使用“相对误差”的方式比较浮点数。\n\n* [Sonar-1244: Floating point numbers should not be tested for equality](https://rules.sonarsource.com/java/RSPEC-1244)\n\n----\n\n**Rule 4. 数字类型的计算原则**\n\n**4.1【强制】数字运算表达式，因为先进行等式右边的运算，再赋值给等式左边的变量，所以等式两边的类型要一致**\n\n例子1: int与int相除后，哪怕被赋值给float或double，结果仍然是四舍五入取整的int。\n\n需要强制将除数或被除数转换为float或double。\n\n```java\ndouble d = 24/7;  //结果是3.0\ndouble d =  (double)24/7; //结果是正确的3.42857\n```\n\n例子2： int与int相乘，哪怕被赋值给long，仍然会溢出。\n\n需要强制将乘数的一方转换为long。\n\n```java\nlong l = Integer.MAX_VALUE * 2; // 结果是溢出的－2\nlong l = Integer.MAX_VALUE * 2L; //结果是正确的4294967294\n```\n\n另外，int的最大值约21亿，留意可能溢出的情况。\n\n* [Sonar-2184: Math operands should be cast before assignment](https://rules.sonarsource.com/java/RSPEC-2184)\n\n\n**4.2【强制】数字取模的结果不一定是正数，负数取模的结果仍然负数**\n\n取模做数组下标时，如果不处理负数的情况，很容易ArrayIndexOutOfBoundException。\n\n另外，Integer.MIN_VALUE取绝对值也仍然是负数。因此，vjkit的MathUtil对上述情况做了安全的封装。\n\n```java\n-4 % 3  = -1;\nMath.abs(Integer.MIN_VALUE) = -2147483648;\n```\n\n* Findbugs: Style - Remainder of hashCode could be negative\n\n\n**4.3【推荐】 double 或 float 计算时有不可避免的精度问题**\n\n```java\n\nfloat f = 0.45f/3;    //结果是0.14999999\n\ndouble d1 = 0.45d/3;  //结果是正确的0.15\n\ndouble d2 = 1.03d - 0.42d; //结果是0.6100000000000001\n\n```\n\n尽量用double而不用float，但如果是金融货币的计算，则必须使用如下选择：\n\n选项1， 使用性能较差的BigDecimal。BigDecimal还能精确控制四舍五入或是其他取舍的方式。\n\n选项2， 在预知小数精度的情况下，将浮点运算放大为整数计数，比如货币以\"分\"而不是以\"元\"计算。\n\n\n* [Sonar-2164: Math should not be performed on floats](https://rules.sonarsource.com/java/RSPEC-2164)\n\n----\n\n**Rule 5. 【推荐】如果变量值仅有有限的可选值，用枚举类来定义常量**\n\n尤其是变量还希望带有名称之外的延伸属性时，如下例：\n\n```java\n//WRONG\npublic String MONDAY = \"MONDAY\";\npublic int MONDAY_SEQ = 1;\n\n//RIGHT\npublic enum SeasonEnum {\n\tSPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);\n\tint seq;\n\tSeasonEnum(int seq) { this.seq = seq; }\n}\n```\n\n业务代码中不要依赖ordinary()函数进行业务运算，而是自定义数字属性，以免枚举值的增减调序造成影响。 例外：永远不会有变化的枚举，比如上例的一年四季。\n\n----\n\n**Rule 6. 字符串拼接的原则**\n\n**6.1 【推荐】 当字符串拼接不在一个命令行内写完，而是存在多次拼接时(比如循环)，使用StringBuilder的append()**\n\n```java\nString s  = \"hello\" + str1 +  str2;  //Almost OK，除非初始长度有问题，见第3点.\n\nString s  = \"hello\";  //WRONG\nif (condition) {\n  s += str1;\n}\n\nString str = \"start\";       //WRONG\nfor (int i = 0; i < 100; i++) {\n  str = str + \"hello\";\n}\n```\n\n反编译出的字节码文件显示，其实每条用`+`进行字符拼接的语句，都会new出一个StringBuilder对象，然后进行append操作，最后通过toString方法返回String对象。所以上面两个错误例子，会重复构造StringBuilder，重复toString()造成资源浪费。\n\n\n* [Sonar-1643: Strings should not be concatenated using '+' in a loop](https://rules.sonarsource.com/java/RSPEC-1643)\n\n\n**6.2 【强制】 字符串拼接对象时，不要显式调用对象的toString()**\n\n如上，`+`实际是StringBuilder，本身会调用对象的toString()，且能很好的处理null的情况。\n\n```java\n//WRONG\nstr = \"result:\" + myObject.toString();  // myObject 为 null时，抛 NPE\n\n//RIGHT\nstr = \"result:\" + myObject;  // myObject 为 null 时，输出 result:null\n```\n\n\n**6.3【强制】使用StringBuilder，而不是有所有方法都有同步修饰符的StringBuffer**\n\n因为内联不成功，逃逸分析并不能抹除StringBuffer上的同步修饰符\n\n* [Sonar-1149: Synchronized classes Vector, Hashtable, Stack and StringBuffer should not be used](https://rules.sonarsource.com/java/RSPEC-1149)\n\n\n**6.4 【推荐】当拼接后字符串的长度远大于16时，指定StringBuilder的大概长度，避免容量不足时的成倍扩展**\n\n\n**6.5 【推荐】如果字符串长度很大且频繁拼接，可考虑ThreadLocal重用StringBuilder对象**\n\n参考BigDecimal的toString()实现，及vjkit中的StringBuilderHolder。\n\n----\n\n**Rule 7. 【推荐】字符操作时，优先使用字符参数，而不是字符串，能提升性能**\n\n```java\n//WRONG\nstr.indexOf(\"e\");\n\n//RIGHT\nstringBuilder.append('a');\nstr.indexOf('e');\nstr.replace('m','z');\n```\n\n其他包括split等方法，在JDK String中未提供针对字符参数的方法，可考虑使用Apache Commons StringUtils 或Guava的Splitter。\n\n* [Sonar-3027: String function use should be optimized for single characters](https://rules.sonarsource.com/java/RSPEC-3027)\n\n----\n\n**Rule 8. 【推荐】利用好正则表达式的预编译功能，可以有效加快正则匹配速度**\n\n反例：\n```java\n//直接使用String的matches()方法\nresult = \"abc\".matches(\"[a-zA-z]\");\n\n//每次重新构造Pattern\nPattern pattern = Pattern.compile(\"[a-zA-z]\");\nresult = pattern.matcher(\"abc\").matches();\n```\n\n正例：\n```java\n//在某个地方预先编译Pattern，比如类的静态变量\nprivate static Pattern pattern = Pattern.compile(\"[a-zA-z]\");\n...\n//真正使用Pattern的地方\nresult = pattern.matcher(\"abc\").matches();\n```\n\n----\n"
  },
  {
    "path": "docs/standard/chapter08.md",
    "content": "# (八) 集合处理\n\n**Rule 1. 【推荐】底层数据结构是数组的集合，指定集合初始大小**\n\n底层数据结构为数组的集合包括 ArrayList，HashMap，HashSet，ArrayDequeue等。\n\n数组有大小限制，当超过容量时，需要进行复制式扩容，新申请一个是原来容量150% or 200%的数组，将原来的内容复制过去，同时浪费了内存与性能。HashMap/HashSet的扩容，还需要所有键值对重新落位，消耗更大。\n\n\n默认构造函数使用默认的数组大小，比如ArrayList默认大小为10，HashMap为16。因此建议使用ArrayList(int initialCapacity)等构造函数，明确初始化大小。\n\n\nHashMap/HashSet的初始值还要考虑加载因子:\n\n为了降低哈希冲突的概率(Key的哈希值按数组大小取模后，如果落在同一个数组下标上，将组成一条需要遍历的Entry链)，默认当HashMap中的键值对达到数组大小的75%时，即会触发扩容。因此，如果预估容量是100，即需要设定`100/0.75 +1＝135`的数组大小。vjkit的MapUtil的Map创建函数封装了该计算。\n\n如果希望加快Key查找的时间，还可以进一步降低加载因子，加大初始大小，以降低哈希冲突的概率。\n\n----\n\n**Rule 2. 【推荐】尽量使用新式的foreach语法遍历Collection与数组**\n\nforeach是语法糖，遍历集合的实际字节码等价于基于Iterator的循环。\n\nforeach代码一来代码简洁，二来有效避免了有多个循环或嵌套循环时，因为不小心的复制粘贴，用错了iterator或循环计数器(i,j)的情况。\n\n----\n\n**Rule 3. 【强制】不要在foreach循环里进行元素的remove/add操作，remove元素可使用Iterator方式**\n\n```java\n//WRONG\nfor (String str : list) {\n  if (condition) {\n    list.remove(str);\n  }\n}\n\n//RIGHT\nIterator<String> it = list.iterator();\nwhile (it.hasNext()) {\n  String str = it.next();\n  if (condition) {\n    it.remove();\n  }\n}\n```\n\n* Facebook-Contrib: Correctness - Method modifies collection element while iterating\n* Facebook-Contrib: Correctness - Method deletes collection element while iterating\n\n----\n\n**Rule 4. 【强制】使用entrySet遍历Map类集合Key/Value，而不是keySet\t方式进行遍历**\n\nkeySet遍历的方式，增加了N次用key获取value的查询。\n\n* [Sonar-2864:\"entrySet()\" should be iterated when both the key and value are needed](https://rules.sonarsource.com/java/RSPEC-2864)\n\n----\n\n**Rule 5. 【强制】当对象用于集合时，下列情况需要重新实现hashCode()和 equals()**\n\n1） 以对象做为Map的KEY时；\n\n2） 将对象存入Set时。\n\n上述两种情况，都需要使用hashCode和equals比较对象，默认的实现会比较是否同一个对象（对象的引用相等）。\n\n另外，对象放入集合后，会影响hashCode()，equals()结果的属性，将不允许修改。\n\n* [Sonar-2141:Classes that don't define \"hashCode()\" should not be used in hashes](https://rules.sonarsource.com/java/RSPEC-2141)\n\n----\n\n**Rule 6. 【强制】高度注意各种Map类集合Key/Value能不能存储null值的情况**\n\n| Map | Key | Value |\n| -------- | -------- |-------- |\n|HashMap|Nullable | Nullable|\n|ConcurrentHashMap| NotNull| NotNull|\n|TreeMap| NotNull| Nullable |\n\n由于HashMap的干扰，很多人认为ConcurrentHashMap是可以置入null值。同理，Set中的value实际是Map中的key。\n\n----\n\n**Rule 7. 【强制】长生命周期的集合，里面内容需要及时清理，避免内存泄漏**\n\n长生命周期集合包括下面情况，都要小心处理。\n\n1） 静态属性定义；\n\n2） 长生命周期对象的属性；\n\n3） 保存在ThreadLocal中的集合。\n\n如无法保证集合的大小是有限的，使用合适的缓存方案代替直接使用HashMap。\n\n另外，如果使用WeakHashMap保存对象，当对象本身失效时，就不会因为它在集合中存在引用而阻止回收。但JDK的WeakHashMap并不支持并发版本，如果需要并发可使用Guava Cache的实现。\n\n----\n\n**Rule 8. 【强制】集合如果存在并发修改的场景，需要使用线程安全的版本**\n\n1) 著名的反例，HashMap扩容时，遇到并发修改可能造成100%CPU占用。\n\n推荐使用`java.util.concurrent(JUC)`工具包中的并发版集合，如ConcurrentHashMap等，优于使用Collections.synchronizedXXX()系列函数进行同步化封装(等价于在每个方法都加上synchronized关键字)。\n\n\n例外：ArrayList所对应的CopyOnWriteArrayList，每次更新时都会复制整个数组，只适合于读多写很少的场景。如果频繁写入，可能退化为使用Collections.synchronizedList(list)。\n\n\n2) 即使线程安全类仍然要注意函数的正确使用。\n\n例如：即使用了 ConcurrentHashMap，但直接使用 get/put 方法，仍然可能会多线程间互相覆盖。\n\n```java\n//WRONG\nE e = map.get(key);\nif (e == null) {\n  e = new E();\n  map.put(key, e); //仍然能两条线程并发执行put，互相覆盖\n}\nreturn e;\n\n//RIGHT\nE e = map.get(key);\nif (e == null) {\n  e = new E();\n  E previous = map.putIfAbsent(key, e);\n  if(previous != null) {\n    return previous;\n  }\n}\nreturn e;\n```\n----\n\n**Rule 9. 【推荐】正确使用集合泛型的通配符**\n\n`List<String>`并不是`List<Object>`的子类，如果希望泛型的集合能向上向下兼容转型，而不仅仅适配唯一类，则需定义通配符，可以按需要extends 和 super的字面意义，也可以遵循`PECS(Producer Extends Consumer Super)`原则:\n\n1) 如果集合要被读取，定义成`<? extends T>`\n\n```java\nClass Stack<E>{\n  public void pushAll(Iterable<? extends E> src){\n    for (E e: src)\n      push(e);\n  }\n}\n\nStack<Number> stack = new Stack<Number>();\nIterable<Integer> integers = ...;\nstack.pushAll(integers);\n```\n\n\n2) 如果集合要被写入，定义成`<? super T>`\n\n```java\nClass Stack<E>{\n  public void popAll(Collection<? super E> dist){\n     while(!isEmpty())\n   \t   dist.add(pop);\n  }\n}\n\nStack<Number> stack = new Stack<Number>();\nCollection<Object> objects = ...;\nstack.popAll(objects);\n```\n----\n\n**Rule 10. 【推荐】`List`, `List<?>` 与 `List<Object>`的选择**\n\n定义成`List`，会被IDE提示需要定义泛型。 如果实在无法确定泛型，就仓促定义成`List<?>`来蒙混过关的话，该list只能读，不能增改。定义成`List<Object>`呢，如规则9所述，`List<String>` 并不是`List<Object>`的子类，除非函数定义使用了通配符。\n\n因此实在无法明确其泛型时，使用`List`也是可以的。\n\n----\n\n**Rule 11. 【推荐】如果Key只有有限的可选值，先将Key封装成Enum，并使用EnumMap**\n\nEnumMap，以Enum为Key的Map，内部存储结构为`Object[enum.size]`，访问时以`value = Object[enum.ordinal()]`获取值，同时具备HashMap的清晰结构与数组的性能。\n\n```java\npublic enum COLOR {\n  RED, GREEN, BLUE, ORANGE;\n}\n\nEnumMap<COLOR, String> moodMap = new EnumMap<COLOR, String> (COLOR.class);\n```\n\n* [Sonar-1640: Maps with keys that are enum values should be replaced with EnumMap](https://rules.sonarsource.com/java/RSPEC-1640)\n\n----\n\n**Rule 12. 【推荐】Array 与 List互转的正确写法**\n\n```java\n// list -> array，构造数组时不需要设定大小\nString[] array = (String[])list.toArray(); //WRONG;\nString[] array = list.toArray(new String[0]); //RIGHT\nString[] array = list.toArray(new String[list.size()]); //RIGHT，但list.size()可用0代替。\n\n\n// array -> list\n//非原始类型数组，且List不能再扩展\nList list = Arrays.asList(array);\n\n//非原始类型数组， 但希望List能再扩展\nList list = new ArrayList(array.length);\nCollections.addAll(list, array);\n\n//原始类型数组，JDK8\nList myList = Arrays.stream(intArray).boxed().collect(Collectors.toList());\n\n//原始类型数组，JDK7则要自己写个循环来加入了\n```\nArrays.asList(array)，如果array是原始类型数组如int[]，会把整个array当作List的一个元素，String[] 或 Foo[]则无此问题。\nCollections.addAll()实际是循环加入元素，性能相对较低，同样会把int[]认作一个元素。\n\n* Facebook-Contrib: Correctness - Impossible downcast of toArray() result\n* Facebook-Contrib: Correctness - Method calls Array.asList on an array of primitive values\n\n----\n"
  },
  {
    "path": "docs/standard/chapter09.md",
    "content": "# (九) 并发处理\n\n**Rule 1. 【强制】创建线程或线程池时请指定有意义的线程名称，方便出错时回溯**\n\n1）创建单条线程时直接指定线程名称\n\n```java\nThread t = new Thread();\nt.setName(\"cleanup-thread\");\n```\n\n2） 线程池则使用guava或自行封装的ThreadFactory，指定命名规则。\n\n```java\n//guava 或自行封装的ThreadFactory\nThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(threadNamePrefix + \"-%d\").build();\n\nThreadPoolExecutor executor = new ThreadPoolExecutor(..., threadFactory, ...);\n```\n\n----\n\n**Rule 2. 【推荐】尽量使用线程池来创建线程**\n\n除特殊情况，尽量不要自行创建线程，更好的保护线程资源。\n\n```java\n//WRONG\nThread thread = new Thread(...);\nthread.start();\n```\n\n同理，定时器也不要使用Timer，而应该使用ScheduledExecutorService。\n\n因为Timer只有单线程，不能并发的执行多个在其中定义的任务，而且如果其中一个任务抛出异常，整个Timer也会挂掉，而ScheduledExecutorService只有那个没捕获到异常的任务不再定时执行，其他任务不受影响。\n\n----\n\n**Rule 3. 【强制】线程池不允许使用 Executors去创建，避资源耗尽风险**\n\nExecutors返回的线程池对象的弊端 ：\n\n1）FixedThreadPool 和 SingleThreadPool:\n\n允许的请求队列长度为 Integer.MAX_VALUE，可能会堆积大量的请求，从而导致 OOM。\n\n\n2）CachedThreadPool 和 ScheduledThreadPool:\n\n允许的创建线程数量为 Integer.MAX_VALUE，可能会创建大量的线程，从而导致 OOM。\n\n\n应通过 new ThreadPoolExecutor(xxx,xxx,xxx,xxx)这样的方式，更加明确线程池的运行规则，合理设置Queue及线程池的core size和max size，建议使用vjkit封装的ThreadPoolBuilder。\n\n----\n\n**Rule 4. 【强制】正确停止线程**\n\nThread.stop()不推荐使用，强行的退出太不安全，会导致逻辑不完整，操作不原子，已被定义成Deprecate方法。\n\n停止单条线程，执行Thread.interrupt()。\n\n停止线程池：\n\n* ExecutorService.shutdown(): 不允许提交新任务，等待当前任务及队列中的任务全部执行完毕后退出；\n\n* ExecutorService.shutdownNow(): 通过Thread.interrupt()试图停止所有正在执行的线程，并不再处理还在队列中等待的任务。\n\n最优雅的退出方式是先执行shutdown()，再执行shutdownNow()，vjkit的`ThreadPoolUtil`进行了封装。\n\n注意，Thread.interrupt()并不保证能中断正在运行的线程，需编写可中断退出的Runnable，见规则5。\n\n----\n\n**Rule 5. 【强制】编写可停止的Runnable**\n\n执行Thread.interrupt()时，如果线程处于sleep(), wait(), join(), lock.lockInterruptibly()等blocking状态，会抛出InterruptedException，如果线程未处于上述状态，则将线程状态设为interrupted。\n\n因此，如下的代码无法中断线程:\n\n```java\npublic void run() {\n\n  while (true) { //WRONG，无判断线程状态。\n    sleep();\n  }\n\n  public void sleep() {\n    try {\n\t  Thread.sleep(1000);\n\t} catch (InterruptedException e) {\n\t  logger.warn(\"Interrupted!\", e); //WRONG，吃掉了异常，interrupt状态未再传递\n\t}\n  }\n}\n```\n\n\n**5.1 正确处理InterruptException**\n\n因为InterruptException异常是个必须处理的Checked Exception，所以run()所调用的子函数很容易吃掉异常并简单的处理成打印日志，但这等于停止了中断的传递，外层函数将收不到中断请求，继续原有循环或进入下一个堵塞。\n\n正确处理是调用`Thread.currentThread().interrupt();` 将中断往外传递。\n\n```java\n//RIGHT\npublic void myMethod() {\n  try {\n    ...\n  } catch (InterruptedException e) {\n    Thread.currentThread().interrupt();\n  }\n}\n```\n\n* [Sonar-2142: \"InterruptedException\" should not be ignored](https://rules.sonarsource.com/java/RSPEC-2142)\n\n\n\n**5.2 主循环及进入阻塞状态前要判断线程状态**\n\n```java\n//RIGHT\npublic void run() {\n  try {\n    while (!Thread.isInterrupted()) {\n      // do stuff\n    }\n  } catch (InterruptedException e) {\n    logger.warn(\"Interrupted!\", e);\n  }\n}\n```\n\n其他如Thread.sleep()的代码，在正式sleep前也会判断线程状态。\n\n----\n\n**Rule 6. 【强制】Runnable中必须捕获一切异常**\n\n如果Runnable中没有捕获RuntimeException而向外抛出，会发生下列情况：\n\n1) ScheduledExecutorService执行定时任务，任务会被中断，该任务将不再定时调度，但线程池里的线程还能用于其他任务。\n\n2) ExecutorService执行任务，当前线程会中断，线程池需要创建新的线程来响应后续任务。\n\n3) 如果没有在ThreadFactory设置自定义的UncaughtExceptionHanlder，则异常最终只打印在System.err，而不会打印在项目的日志中。\n\n\n因此建议自写的Runnable都要保证捕获异常; 如果是第三方的Runnable，可以将其再包裹一层vjkit中的SafeRunnable。\n\n```java\nexecutor.execute(ThreadPoolUtil.safeRunner(runner));\n```\n\n----\n\n**Rule 7. 【强制】全局的非线程安全的对象可考虑使用ThreadLocal存放**\n\n全局变量包括单例对象，static成员变量。\n\n著名的非线程安全类包括SimpleDateFormat，MD5/SHA1的Digest。\n\n对这些类，需要每次使用时创建。\n\n但如果创建有一定成本，可以使用ThreadLocal存放并重用。\n\nThreadLocal变量需要定义成static，并在每次使用前重置。\n\n```java\nprivate static final ThreadLocal<MessageDigest> SHA1_DIGEST = new ThreadLocal<MessageDigest>() {\n  @Override\n  protected MessageDigest initialValue() {\n    try {\n\t  return MessageDigest.getInstance(\"SHA\");\n\t} catch (NoSuchAlgorithmException e) {\n\t  throw new RuntimeException(\"...\", e);\n\t}\n  }\n};\n\npublic void digest(byte[] input) {\n  MessageDigest digest = SHA1_DIGEST.get();\n  digest.reset();\n  return digest.digest(input);\n}\n```\n\n* [Sonar-2885: Non-thread-safe fields should not be static](https://rules.sonarsource.com/java/RSPEC-2885)\n* Facebook-Contrib: Correctness - Field is an instance based ThreadLocal variable\n\n----\n\n**Rule 8. 【推荐】缩短锁**\n\n1） 能锁区块，就不要锁整个方法体；\n\n```java\n//锁整个方法，等价于整个方法体内synchronized(this)\npublic synchronized boolean foo(){};\n\n//锁区块方法，仅对需要保护的原子操作的连续代码块进行加锁。\npublic boolean foo() {\n\tsynchronized(this) {\n\t\t...\n\t\t...\n\t}\n\t//other stuff\n}\n```\n\n2）能用对象锁，就不要用类锁。\n\n```java\n//对象锁，只影响使用同一个对象加锁的线程\nsynchronized(this) {\n\t...\n}\n\n//类锁，使用类对象作为锁对象，影响所有线程。\nsynchronized(A.class) {\n\t...\n}\n```\n\n----\n\n**Rule 10. 【推荐】选择分离锁，分散锁甚至无锁的数据结构**\n\n* 分离锁：\n\n1） 读写分离锁ReentrantReadWriteLock，读读之间不加锁，仅在写读和写写之间加锁；\n\n2） Array Base的queue一般是全局一把锁，而Linked Base的queue一般是队头队尾两把锁。\n\n\n\n* 分散锁（又称分段锁）：\n\n1）如JDK7的ConcurrentHashMap，分散成16把锁；\n\n2）对于经常写，少量读的计数器，推荐使用JDK8或vjkit封装的LongAdder对象性能更好（内部分散成多个counter，减少乐观锁的使用，取值时再相加所有counter）\n\n\n\n* 无锁的数据结构：\n\n1）完全无锁无等待的结构，如JDK8的ConcurrentHashMap；\n\n2）基于CAS的无锁有等待的数据结构，如AtomicXXX系列。\n\n----\n\n**Rule 11. 【推荐】基于ThreadLocal来避免锁**\n\n比如Random实例虽然是线程安全的，但其实它的seed的访问是有锁保护的。因此建议使用JDK7的ThreadLocalRandom，通过在每个线程里放一个seed来避免了加锁。\n\n----\n\n**Rule 12. 【推荐】规避死锁风险**\n\n对多个资源多个对象的加锁顺序要一致。\n\n如果无法确定完全避免死锁，可以使用带超时控制的tryLock语句加锁。\n\n----\n\n**Rule 13. 【推荐】volatile修饰符，AtomicXX系列的正确使用**\n\n多线程共享的对象，在单一线程内的修改并不保证对所有线程可见。使用volatile定义变量可以解决（解决了可见性）。\n\n但是如果多条线程并发进行基于当前值的修改，如并发的counter++，volatile则无能为力（解决不了原子性）。\n\n此时可使用Atomic*系列:\n\n```java\nAtomicInteger count = new AtomicInteger();\ncount.addAndGet(2);\n```\n\n但如果需要原子地同时对多个AtomicXXX的Counter进行操作，则仍然需要使用synchronized将改动代码块加锁。\n\n----\n\n**Rule 14. 【推荐】延时初始化的正确写法**\n\n通过双重检查锁（double-checked locking）实现延迟初始化存在隐患，需要将目标属性声明为volatile型，为了更高的性能，还要把volatile属性赋予给临时变量，写法复杂。\n\n所以如果只是想简单的延迟初始化，可用下面的静态类的做法，利用JDK本身的class加载机制保证唯一初始化。\n\n```java\nprivate static class LazyObjectHolder {\n  static final LazyObject instance = new LazyObject();\n}\n\npublic void myMethod() {\n  LazyObjectHolder.instance.doSomething();\n}\n```\n\n* [Sonar-2168: Double-checked locking should not be used](https://rules.sonarsource.com/java/RSPEC-2168)\n\n----\n"
  },
  {
    "path": "docs/standard/chapter10.md",
    "content": "# (十) 异常处理\n\n**Rule 1. 【强制】创建异常的消耗大，只用在真正异常的场景**\n\n构造异常时，需要获得整个调用栈，有一定消耗。\n\n不要用来做流程控制，条件控制，因为异常的处理效率比条件判断低。\n\n发生概率较高的条件，应该先进行检查规避，比如：IndexOutOfBoundsException，NullPointerException等，所以如果代码里捕获这些异常通常是个坏味道。\n\n```java\n//WRONG\ntry {\n  return obj.method();\n} catch (NullPointerException e) {\n  return false;\n}\n\n//RIGHT\nif (obj == null) {\n  return false;\n}\n```\n\n* [Sonar-1696: \"NullPointerException\" should not be caught](https://rules.sonarsource.com/java/RSPEC-1696)\n\n----\n\n**Rule 2. 【推荐】在特定场景，避免每次构造异常**\n\n如上，异常的构造函数需要获得整个调用栈。\n\n如果异常频繁发生，且不需要打印完整的调用栈时，可以考虑绕过异常的构造函数。\n\n1） 如果异常的message不变，将异常定义为静态成员变量;\n\n下例定义静态异常，并简单定义一层的StackTrace。`ExceptionUtil`见vjkit。\n\n```java\nprivate static RuntimeException TIMEOUT_EXCEPTION = ExceptionUtil.setStackTrace(new RuntimeException(\"Timeout\"),\nMyClass.class, \"mymethod\");\n\n...\n\nthrow TIMEOUT_EXCEPTION;\n```\n\n\n2） 如果异常的message会变化，则对静态的异常实例进行clone()再修改message。\n\nException默认不是Cloneable的，`CloneableException`见vjkit。\n\n```java\nprivate static CloneableException TIMEOUT_EXCEPTION = new CloneableException(\"Timeout\") .setStackTrace(My.class,\n \"hello\");\n\n...\n\nthrow TIMEOUT_EXCEPTION.clone(\"Timeout for 40ms\");\n```\n\n\n3）自定义异常，也可以考虑重载fillStackTrace()为空函数，但相对没那么灵活，比如无法按场景指定一层的StackTrace。\n\n----\n\n**Rule 3. 【推荐】自定义异常，建议继承`RuntimeException`**\n\n详见《Clean Code》，争论已经结束，不再推荐原本初衷很好的CheckedException。\n\n因为CheckedException需要在抛出异常的地方，与捕获处理异常的地方之间，层层定义throws XXX来传递Exception，如果底层代码改动，将影响所有上层函数的签名，导致编译出错，对封装的破坏严重。对CheckedException的处理也给上层程序员带来了额外的负担。因此其他语言都没有CheckedException的设计。\n\n----\n\n**Rule 4. 【推荐】异常日志应包含排查问题的足够信息**\n\n异常信息应包含排查问题时足够的上下文信息。\n\n捕获异常并记录异常日志的地方，同样需要记录没有包含在异常信息中，而排查问题需要的信息，比如捕获处的上下文信息。\n\n```java\n//WRONG\nnew TimeoutException(\"timeout\");\nlogger.error(e.getMessage(), e);\n\n\n//RIGHT\nnew TimeoutException(\"timeout:\" + eclapsedTime + \", configuration:\" + configTime);\nlogger.error(\"user[\" + userId + \"] expired:\" + e.getMessage(), e);\n```\n\n* Facebook-Contrib: Style - Method throws exception with static message string\n\n----\n\n**Rule 5. 异常抛出的原则**\n\n\n**5.1 【推荐】尽量使用JDK标准异常，项目标准异常**\n\n尽量使用JDK标准的Runtime异常如`IllegalArgumentException`，`IllegalStateException`，`UnsupportedOperationException`，项目定义的Exception如`ServiceException`。\n\n\n**5.2 【推荐】根据调用者的需要来定义异常类，直接使用`RuntimeException`是允许的**\n\n是否定义独立的异常类，关键是调用者会如何处理这个异常，如果没有需要特别的处理，直接抛出RuntimeException也是允许的。\n\n----\n\n**Rule 6. 异常捕获的原则**\n\n**6.1 【推荐】按需要捕获异常，捕获`Exception`或`Throwable`是允许的**\n\n如果无特殊处理逻辑，统一捕获Exception统一处理是允许的。\n\n捕获Throwable是为了捕获Error类异常，包括其实无法处理的`OOM` `StackOverflow` `ThreadDeath`，以及类加载，反射时可能抛出的`NoSuchMethodError` `NoClassDefFoundError`等。\n\n\n**6.2【推荐】多个异常的处理逻辑一致时，使用JDK7的语法避免重复代码**\n\n```java\ntry {\n  ...\n} catch (AException | BException | CException ex) {\n  handleException(ex);\n}\n```\n\n* [Sonar-2147: Catches should be combined](https://rules.sonarsource.com/java/RSPEC-2147)\n\n----\n\n**Rule 7.异常处理的原则**\n\n**7.1 【强制】捕获异常一定要处理；如果故意捕获并忽略异常，须要注释写明原因**\n\n方便后面的阅读者知道，此处不是漏了处理。\n\n```java\n//WRONG\ntry {\n} catch(Exception e) {\n}\n\n//RIGHT\ntry {\n} catch(Exception ignoredExcetpion) {\n\t//continue the loop\n}\n```\n\n\n**7.2 【强制】异常处理不能吞掉原异常，要么在日志打印，要么在重新抛出的异常里包含原异常**\n\n```java\n //WRONG\nthrow new MyException(\"message\");\n\n//RIGHT 记录日志后抛出新异常，向上次调用者屏蔽底层异常\nlogger.error(\"message\", ex);\nthrow new MyException(\"message\");\n\n//RIGHT 传递底层异常\nthrow new MyException(\"message\", ex);\n```\n\n* [Sonar-1166: Exception handlers should preserve the original exceptions](https://rules.sonarsource.com/java/RSPEC-1166)，其中默认包含了InterruptedException, NumberFormatException，NoSuchMethodException等若干例外\n\n\n**7.3 【强制】如果不想处理异常，可以不进行捕获。但最外层的业务使用者，必须处理异常，将其转化为用户可以理解的内容**\n\n----\n\n**Rule 8. finally块的处理原则**\n\n**8.1 【强制】必须对资源对象、流对象进行关闭，或使用语法try-with-resource**\n\n关闭动作必需放在finally块，不能放在try块 或 catch块，这是经典的错误。\n\n更加推荐直接使用JDK7的try-with-resource语法自动关闭Closeable的资源，无需在finally块处理，避免潜在问题。\n\n```java\ntry (Writer writer = ...) {\n  writer.append(content);\n}\n```\n\n\n**8.2 【强制】如果处理过程中有抛出异常的可能，也要做try-catch，否则finally块中抛出的异常，将代替try块中抛出的异常**\n\n```java\n//WRONG\ntry {\n  ...\n  throw new TimeoutException();\n} finally {\n  file.close();//如果file.close()抛出IOException, 将代替TimeoutException\n}\n\n//RIGHT, 在finally块中try－catch\ntry {\n  ...\n  throw new TimeoutException();\n} finally {\n  IOUtil.closeQuietly(file); //该方法中对所有异常进行了捕获\n}\n```\n\n* [Sonar-1163: Exceptions should not be thrown in finally blocks](https://rules.sonarsource.com/java/RSPEC-1163)\n\n\n**8.3 【强制】不能在finally块中使用return，finally块中的return将代替try块中的return及throw Exception**\n\n```java\n//WRONG\ntry {\n  ...\n  return 1;\n} finally {\n  return 2; //实际return 2 而不是1\n}\n\ntry {\n  ...\n  throw TimeoutException();\n} finally {\n  return 2; //实际return 2 而不是TimeoutException\n}\n```\n\n* [Sonar-1143: Jump statements should not occur in \"finally\" blocks](https://rules.sonarsource.com/java/RSPEC-1143)\n\n----\n"
  },
  {
    "path": "docs/standard/chapter11.md",
    "content": "# (十一) 日志规约\n\n**Rule 1. 【强制】应用中不可直接使用日志库（Log4j、Logback）中的API，而应使用日志框架SLF4J中的API**\n\n使用门面模式的日志框架，有利于维护各个类的日志处理方式统一。\n\n```java\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nprivate static Logger logger = LoggerFactory.getLogger(Foo.class);\n```\n\n----\n\n**Rule 2. 【推荐】对不确定会否输出的日志，采用占位符或条件判断**\n\n```java\n//WRONG\nlogger.debug(\"Processing trade with id: \" + id + \" symbol: \" + symbol);\n```\n\n如果日志级别是info，上述日志不会打印，但是会执行1)字符串拼接操作，2)如果symbol是对象，还会执行toString()方法，浪费了系统资源，最终日志却没有打印。\n\n```java\n//RIGHT\nlogger.debug(\"Processing trade with id: {} symbol : {} \", id, symbol);\n```\n\n\n但如果symbol.getMessage()本身是个消耗较大的动作，占位符在此时并没有帮助，须要改为条件判断方式来完全避免它的执行。\n\n```java\n//WRONG\nlogger.debug(\"Processing trade with id: {} symbol : {} \", id, symbol.getMessage());\n\n//RIGHT\nif (logger.isDebugEnabled()) {\n  logger.debug(\"Processing trade with id: \" + id + \" symbol: \" + symbol.getMessage());\n}\n```\n\n----\n\n**Rule 3. 【推荐】对确定输出，而且频繁输出的日志，采用直接拼装字符串的方式**\n\n如果这是一条WARN，ERROR级别的日志，或者确定输出的INFO级别的业务日志，直接字符串拼接，比使用占位符替换，更加高效。\n\nSlf4j的占位符并没有魔术，每次输出日志都要进行占位符的查找，字符串的切割与重新拼接。\n\n```java\n//RIGHT\nlogger.info(\"I am a business log with id: \" + id + \" symbol: \" + symbol);\n\n//RIGHT\nlogger.warn(\"Processing trade with id: \" + id + \" symbol: \" + symbol);\n```\n\n----\n\n**Rule 4. 【推荐】尽量使用异步日志**\n\n低延时的应用，使用异步输出的形式(以AsyncAppender串接真正的Appender)，可减少IO造成的停顿。\n\n需要正确配置异步队列长度及队列满的行为，是丢弃还是等待可用，业务上允许丢弃的尽量选丢弃。\n\n----\n\n**Rule 5. 【强制】禁止使用性能很低的System.out()打印日志信息**\n\n同理也禁止e.printStackTrace();\n\n例外: 应用启动和关闭时，担心日志框架还未初始化或已关闭。\n\n* [Sonar-106: Standard outputs should not be used directly to log anything](https://rules.sonarsource.com/java/RSPEC-106)\n* [Sonar-1148: Throwable.printStackTrace(...) should not be called](https://rules.sonarsource.com/java/RSPEC-1148)\n\n----\n\n**Rule 6. 【强制】禁止配置日志框架输出日志打印处的类名，方法名及行号的信息**\n\n日志框架在每次打印时，通过主动获得当前线程的StackTrace来获取上述信息的消耗非常大，尽量通过Logger名本身给出足够信息。\n\n----\n\n**Rule 7. 【推荐】谨慎地记录日志，避免大量输出无效日志，信息不全的日志**\n\n大量地输出无效日志，不利于系统性能，也不利于快速定位错误点。\n\n记录日志时请思考：这些日志真的有人看吗？看到这条日志你能做什么？能不能给问题排查带来好处？\n\n----\n\n**Rule 8. 【推荐】使用warn级别而不是error级别，记录外部输入参数错误的情况**\n\n如非必要，请不在此场景打印error级别日志，避免频繁报警。\n\nerror级别只记录系统逻辑出错、异常或重要的错误信息。\n\n----\n"
  },
  {
    "path": "docs/standard/chapter12.md",
    "content": "# (十二) 其他规约\n\n**Rule 1. 【参考】尽量不要让魔法值（即未经定义的数字或字符串常量）直接出现在代码中**\n\n```java\n //WRONG\n String key = \"Id#taobao_\"+tradeId;\n cache.put(key, value);\n```\n例外：-1,0,1,2,3 不认为是魔法数\n\n* [Sonar-109: Magic numbers should not be used](https://rules.sonarsource.com/java/RSPEC-109) 但现实中所谓魔法数还是太多，该规则不能被真正执行。\n\n----\n\n**Rule 2. 【推荐】时间获取的原则**\n\n1）获取当前毫秒数System.currentTimeMillis() 而不是new Date().getTime()，后者的消耗要大得多。\n\n\n2）如果要获得更精确的，且不受NTP时间调整影响的流逝时间，使用System.nanoTime()获得机器从启动到现在流逝的纳秒数。\n\n\n3）如果希望在测试用例中控制当前时间的值，则使用vjkit的Clock类封装，在测试和生产环境中使用不同的实现。\n\n----\n\n**Rule 3. 【推荐】变量声明尽量靠近使用的分支**\n\n不要在一个代码块的开头把局部变量一次性都声明了(这是c语言的做法)，而是在第一次需要使用它时才声明。\n\n否则如果方法已经退出或进入其他分支，就白白初始化了变量。\n\n```java\n//WRONG\nFoo foo = new Foo();\n\nif(ok){\n\treturn;\n}\n\nfoo.bar();\n```\n\n----\n\n**Rule 4. 【推荐】不要像C那样一行里做多件事情**\n\n```java\n //WRONG\nfooBar.fChar = barFoo.lchar = 'c';\nargv++; argc--;\nint level, size;\n```\n\n* [Sonar-1659: Multiple variables should not be declared on the same line](https://rules.sonarsource.com/java/RSPEC-1659)\n\n----\n\n**Rule 5. 【推荐】不要为了性能而使用JNI本地方法**\n\nJava在JIT后并不比C代码慢，JNI方法因为要反复跨越JNI与Java的边界反而有额外的性能损耗。\n\n因此JNI方法仅建议用于调用\"JDK所没有包括的, 对特定操作系统的系统调用\"\n\n----\n\n**Rule 6. 【推荐】正确使用反射，减少性能损耗**\n\n获取Method/Field对象的性能消耗较大, 而如果对Method与Field对象进行缓存再反复调用，则并不会比直接调用类的方法与成员变量慢（前15次使用NativeAccessor，第15次后会生成GeneratedAccessorXXX，bytecode为直接调用实际方法）\n\n```java\n//用于对同一个方法多次调用\nprivate Method method = ....\n\npublic void foo(){\n  method.invoke(obj, args);\n}\n\n//用于仅会对同一个方法单次调用\nReflectionUtils.invoke(obj, methodName, args);\n```\n\n----\n\n**Rule 7.【推荐】可降低优先级的常见代码检查规则**\n\n1. 接口内容的定义中，去除所有modifier，如public等。 (多个public也没啥，反正大家都看惯了)\n\n2. 工具类，定义private构造函数使其不能被实例化。（命名清晰的工具类，也没人会去实例化它，对静态方法通过类来访问也能避免实例化）\n\n----\n"
  },
  {
    "path": "docs/standard/merge.bat",
    "content": "@echo off\ntype README.md > vip-java-standard.md\ntype chapter01.md >> vip-java-standard.md\ntype chapter02.md >> vip-java-standard.md\ntype chapter03.md >> vip-java-standard.md\ntype chapter04.md >> vip-java-standard.md\ntype chapter05.md >> vip-java-standard.md\ntype chapter06.md >> vip-java-standard.md\ntype chapter07.md >> vip-java-standard.md\ntype chapter08.md >> vip-java-standard.md\ntype chapter09.md >> vip-java-standard.md\ntype chapter10.md >> vip-java-standard.md\ntype chapter11.md >> vip-java-standard.md\ntype chapter12.md >> vip-java-standard.md\ntype ali.md >> vip-java-standard.md\n\necho vip-java-standard.md is ready to be converted to pdf by any tools you like.\necho press any key to exit.\n\npause > nul\nexit\n"
  },
  {
    "path": "docs/standard/merge.sh",
    "content": "#!/bin/sh\n\ncat README.md > vip-java-standard.md\ncat chapter01.md >> vip-java-standard.md\ncat chapter02.md >> vip-java-standard.md\ncat chapter03.md >> vip-java-standard.md\ncat chapter04.md >> vip-java-standard.md\ncat chapter05.md >> vip-java-standard.md\ncat chapter06.md >> vip-java-standard.md\ncat chapter07.md >> vip-java-standard.md\ncat chapter08.md >> vip-java-standard.md\ncat chapter09.md >> vip-java-standard.md\ncat chapter10.md >> vip-java-standard.md\ncat chapter11.md >> vip-java-standard.md\ncat chapter12.md >> vip-java-standard.md\ncat ali.md >> vip-java-standard.md\n\necho vip-java-standard.md is ready to be converted to pdf by any tools you like.\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>org.sonatype.oss</groupId>\n\t\t<artifactId>oss-parent</artifactId>\n\t\t<version>7</version>\n\t</parent>\n\n\t<groupId>com.vip.vjtools</groupId>\n\t<artifactId>vjtools</artifactId>\n\t<version>1.0.9-SNAPSHOT</version>\n\t<name>vjtools</name>\n\t<packaging>pom</packaging>\n\n\t<description>VJTools - VIP's core libraries and tools</description>\n\n\t<properties>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t</properties>\n\n\n\t<distributionManagement>\n\t\t<snapshotRepository>\n\t\t\t<id>sonatype-nexus-snapshots</id>\n\t\t\t<url>https://oss.sonatype.org/content/repositories/snapshots</url>\n\t\t</snapshotRepository>\n\t\t<repository>\n\t\t\t<id>sonatype-nexus-releases</id>\n\t\t\t<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>\n\t\t</repository>\n\t</distributionManagement>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>jdk7</id>\n\t\t\t<activation>\n\t\t\t\t<jdk>1.7</jdk>\n\t\t\t</activation>\n\t\t\t<modules>\n\t\t\t\t<module>vjkit</module>\n\t\t\t\t<module>vjstar</module>\n\t\t\t\t<module>vjmap</module>\n\t\t\t\t<module>vjtop</module>\n\t\t\t\t<module>vjmxcli</module>\n\t\t\t</modules>\n\t\t</profile>\n\n\t\t<profile>\n\t\t\t<id>jdk8</id>\n\t\t\t<activation>\n\t\t\t\t<jdk>1.8</jdk>\n\t\t\t</activation>\n\t\t\t<modules>\n\t\t\t\t<module>vjkit</module>\n\t\t\t\t<module>vjstar</module>\n\t\t\t\t<module>vjmap</module>\n\t\t\t\t<module>vjtop</module>\n\t\t\t\t<module>vjmxcli</module>\n\t\t\t\t<module>standard/sonar-vj</module>\n\t\t\t</modules>\n\t\t</profile>\n\n\t\t<profile>\n\t\t\t<id>jdk9</id>\n\t\t\t<activation>\n\t\t\t\t<jdk>9</jdk>\n\t\t\t</activation>\n\t\t\t<modules>\n\t\t\t\t<module>vjkit</module>\n\t\t\t\t<module>vjstar</module>\n\t\t\t</modules>\n\t\t</profile>\n\n\t\t<profile>\n\t\t\t<id>release</id>\n\t\t\t<modules>\n\t\t\t\t<module>vjkit</module>\n\t\t\t\t<module>vjmap</module>\n\t\t\t\t<module>vjtop</module>\n\t\t\t\t<module>vjmxcli</module>\n\t\t\t</modules>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-release-plugin</artifactId>\n\t\t\t\t\t\t<version>2.5.3</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<tagNameFormat>v.@{project.version}</tagNameFormat>\n\t\t\t\t\t\t </configuration>\n\t\t\t\t\t</plugin>\n\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-gpg-plugin</artifactId>\n\t\t\t\t\t\t<version>1.6</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>sign-artifacts</id>\n\t\t\t\t\t\t\t\t<phase>verify</phase>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>sign</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\n\t</profiles>\n\n\t<scm>\n\t\t<connection>scm:git:https://github.com/vipshop/vjtools.git</connection>\n\t\t<developerConnection>scm:git:https://github.com/vipshop/vjtools.git</developerConnection>\n\t\t<url>https://github.com/vipshop/vjtools</url>\n\t  <tag>v.1.0.2</tag>\n  </scm>\n\n\t<url>https://github.com/vipshop/vjtools</url>\n\n\t<licenses>\n\t\t<license>\n\t\t\t<name>Apache License 2.0</name>\n\t\t\t<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n\t\t\t<distribution>repo</distribution>\n\t\t</license>\n\t</licenses>\n\n\t<developers>\n\t\t<!--not noly me, write a name here just for sonatype requirement -->\n\t\t<developer>\n\t\t\t<id>calvin</id>\n\t\t\t<name>Calvin Xiao</name>\n\t\t\t<email>calvin.xiao at vipshop.com</email>\n\t\t\t<roles>\n\t\t\t\t<role>developer</role>\n\t\t\t</roles>\n\t\t\t<timezone>+8</timezone>\n\t\t</developer>\n\t</developers>\n</project>\n"
  },
  {
    "path": "standard/README.md",
    "content": "# Java Standard\n\n| Project | Description |\n| -------- | -------- |\n| [standard](https://vipshop.github.io/vjtools/#/standard/) | 唯品会Java开发手册 |\n| [code formatter](formatter) | IDE格式化模板 |\n| [sonar rule](sonar-vj) | Sonar规则定制示例 |"
  },
  {
    "path": "standard/formatter/README.md",
    "content": "# 公司通用代码格式化模板\n\n定制原因详见[《唯品会Java开发手册》 第二章:格式规约](https://vipshop.github.io/vjtools/#/standard/chapter02)，同时参考了一些Intellij IDEA默认模板的部分设置。\n\n将下列profile下载并导入IDE即可，导入后Profile名称为`vipshop2.0`：\n\n* [Eclipse Code Formatter Profile](https://raw.githubusercontent.com/vipshop/vjtools/master/standard/formatter/vjtools-code-conventions-eclipse.xml)\n* [Intellij Code Formatter Profile](https://raw.githubusercontent.com/vipshop/vjtools/master/standard/formatter/vjtools-code-conventions-idea.xml)\n\n因为Intellij导入Eclipse Profile存在问题，因此同时提供了两者的Profile。\n\n## 1. 与 Eclipse 4.6 的`Eclipse [build-in]`模板的区别\n\n* 不格式化JavaDoc\n* 注释行宽从80改为120\n* 打开format on/off标志\n* 参考Intellij IDEA默认模板的修改(见后)\n\n\n注意：Eclipse后来的build-in模板，代码行宽已经默认120。\n\n## 2. 与IDEA默认模板的区别\n\n\n本模板参考了Intellij IDEA默认模板中如下部分： \n\n* 简单的if语句，如果没有括号，则格式化成同一行。(勾选Control Statement->if else->Keep  simple 'if'  on one line)\n\n```java\nif (2 < 3) return;\n```\n\n当然，我们还是建议用括号，此处格式化成一行只是兜底的保护。\n\n* 主动输入的空行，最多可保留两行 (Blank Lines->Existing blank lines -> Number of empty lines to preserve 从1 改为 2)\n\n* switch 和 case 之间缩进(勾选Indentation-> Indent->Statements within switch body)\n\n```java\nswitch (a) {\n  case 0:\n    doCase0();\n    break;\n  default:\n    doDefault();    \n}\n```\n\n* 数组构造时不要那么多空格(取消White Space->Arrays->Array Initializers->before opening brace,after opening brace,before closing brace)\n\n```java\nint[] a  = new int[]{1, 2, 3}; \n```"
  },
  {
    "path": "standard/formatter/vjtools-code-conventions-eclipse.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<profiles version=\"12\">\n<profile kind=\"CodeFormatterProfile\" name=\"vjtools\" version=\"12\">\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_ellipsis\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_after_imports\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_javadoc_comments\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indentation.size\" value=\"4\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.disabling_tag\" value=\"@formatter:off\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.continuation_indentation\" value=\"2\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_enum_constants\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_imports\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_after_package\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_binary_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.indent_root_tags\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.enabling_tag\" value=\"@formatter:on\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.problem.enumIdentifier\" value=\"error\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_statements_compare_to_block\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.line_length\" value=\"120\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.use_on_off_tags\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_method_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_binary_expression\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_block\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_lambda_body\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.compact_else_if\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_type_parameters\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.problem.assertIdentifier\" value=\"error\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_binary_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_unary_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve\" value=\"2\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_ellipsis\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_line_comments\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.align_type_members_on_columns\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_assignment\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_conditional_expression\" value=\"80\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_block_in_case\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_header\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode\" value=\"enabled\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_method_declaration\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.join_wrapped_lines\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_conditional_operator\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines\" value=\"2147483647\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_resources_in_try\" value=\"80\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.source\" value=\"1.8\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.tabulation.size\" value=\"4\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_source_code\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_field\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer\" value=\"2\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_method\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_assignment_operator\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.codegen.targetPlatform\" value=\"1.8\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_switch\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_html\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_compact_if\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_empty_lines\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_type_arguments\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_unary_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line\" value=\"false\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_label\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_member_type\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_semicolon\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.format_block_comments\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_statements_compare_to_body\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_multiple_fields\" value=\"16\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_array_initializer\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_binary_operator\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.compiler.compliance\" value=\"1.8\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration\" value=\"common_lines\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_enum_constant\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_type_declaration\" value=\"end_of_line\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_package\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header\" value=\"0\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.join_lines_in_comments\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.comment.indent_parameter_description\" value=\"true\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.tabulation.char\" value=\"tab\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_between_import_groups\" value=\"1\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.lineSplit\" value=\"120\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch\" value=\"insert\"/>\n</profile>\n</profiles>\n"
  },
  {
    "path": "standard/formatter/vjtools-code-conventions-idea.xml",
    "content": "<code_scheme name=\"vjtools\">\n  <option name=\"AUTODETECT_INDENTS\" value=\"false\" />\n  <option name=\"LINE_SEPARATOR\" value=\"&#xA;\" />\n  <option name=\"ENABLE_JAVADOC_FORMATTING\" value=\"false\" />\n  <option name=\"FORMATTER_TAGS_ENABLED\" value=\"true\" />\n  <codeStyleSettings language=\"JAVA\">\n    <option name=\"KEEP_LINE_BREAKS\" value=\"false\" />\n    <option name=\"KEEP_FIRST_COLUMN_COMMENT\" value=\"false\" />\n    <option name=\"KEEP_CONTROL_STATEMENT_IN_ONE_LINE\" value=\"false\" />\n    <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\" />\n    <option name=\"ALIGN_MULTILINE_RESOURCES\" value=\"false\" />\n    <option name=\"CALL_PARAMETERS_WRAP\" value=\"1\" />\n    <option name=\"METHOD_PARAMETERS_WRAP\" value=\"1\" />\n    <option name=\"RESOURCE_LIST_WRAP\" value=\"5\" />\n    <option name=\"EXTENDS_LIST_WRAP\" value=\"1\" />\n    <option name=\"THROWS_LIST_WRAP\" value=\"1\" />\n    <option name=\"EXTENDS_KEYWORD_WRAP\" value=\"1\" />\n    <option name=\"THROWS_KEYWORD_WRAP\" value=\"1\" />\n    <option name=\"METHOD_CALL_CHAIN_WRAP\" value=\"1\" />\n    <option name=\"BINARY_OPERATION_WRAP\" value=\"1\" />\n    <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\" />\n    <option name=\"TERNARY_OPERATION_WRAP\" value=\"5\" />\n    <option name=\"KEEP_SIMPLE_BLOCKS_IN_ONE_LINE\" value=\"true\" />\n    <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"1\" />\n    <indentOptions>\n      <option name=\"USE_TAB_CHARACTER\" value=\"true\" />\n    </indentOptions>\n  </codeStyleSettings>\n</code_scheme>"
  },
  {
    "path": "standard/sonar-vj/README.md",
    "content": "# Sonar VJ 规则\n\n## 概述\n\n我们使用[Sonar](https://www.sonarqube.org/)代码检查工具来辅助[《唯品会Java开发手册》](https://vipshop.github.io/vjtools/#/standard/)的落地。选择的原因是它同时提供了服务端的公共报表，及IDE端的Sonar Lint插件，而且对比PMD与FindBugs，规则也相对容易定制。\n\n因为Sonar有些规则存在误报的情况，我们在力所能及的范围内对规则的实现进行了修改，以符合我们的规范。\n\n## 实现方式\n\n根据[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)规则进行修改。\n\n\n## 使用方式\n\n官方的Sonar Java Plugin在不断更新，以下修改未必对应其最新版，仅作为修改示例供大家参考(修改部分在代码中以//VJ 标注)。 \n\n如果需要直接使用，编译后扔进sonar的lib目录，重启sonar后取消对原规则的检查，改为使用这些编号一样，带标题带VJ字样的规则即可。\n\n## 修改规则列表\n\n| 编号 | 等级 | 规则描述 | 修改 |\n| -------- | -------- |-------- | -------- |\n| 1068| Major | Unused \"private\" fields should be removed | 忽略由Lombok自动生成的getter/setter的类，私有变量不算无用变量 |\n| 1172| Major | Unused method parameters should be removed | 只检查private方法是否有无用参数, 忽略其他公共方法 |\n| 1166| Major | Exception handlers should preserve the original exceptions | 忽略异常变量名含ignore字样的检查，可以不进行处理，如catch(Exception ignore) |\n| 121| Major | Control structures should use curly braces | if语句忽略一般由IDE生成的equals()方法，以及if(condition) return;的单行模式|\n| 1068| Major |Limited dependence should be placed on operator precedence rules in expressions| 忽略三目运算符，不需要加括号来清晰优先级 |\n| 115| Minor| Constant names should comply with a naming convention| 忽略对枚举成员的全大写检查 |\n| 1312| Minor| IP addresses should not be hardcoded | 忽略对\"127.0.0.1\"的检查 |\n| 1291| Info | Track uses of \"NOSONAR\" comments| 忽略行内含 Exception/Throwable, System.in/System.err的//NOSOANR 检查  |\n"
  },
  {
    "path": "standard/sonar-vj/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<groupId>com.vip</groupId>\n\t<artifactId>sonar-vj</artifactId>\n\t<version>1.0.9-SNAPSHOT</version>\n\t<packaging>sonar-plugin</packaging>\n\n\t<properties>\n\t\t<sonar.version>6.7.1</sonar.version>\n\t\t<java.plugin.version>5.0.1.12818</java.plugin.version>\n\t\t<sslr.version>1.21</sslr.version>\n\t\t<gson.version>2.6.2</gson.version>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t</properties>\n\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.sonarsource.sonarqube</groupId>\n\t\t\t<artifactId>sonar-plugin-api</artifactId>\n\t\t\t<version>${sonar.version}</version>\n\t\t\t<scope>provided</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.sonarsource.java</groupId>\n\t\t\t<artifactId>sonar-java-plugin</artifactId>\n\t\t\t<type>sonar-plugin</type>\n\t\t\t<version>${java.plugin.version}</version>\n\t\t\t<scope>provided</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.sonarsource.java</groupId>\n\t\t\t<artifactId>java-frontend</artifactId>\n\t\t\t<version>${java.plugin.version}</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.sonarsource.sslr-squid-bridge</groupId>\n\t\t\t<artifactId>sslr-squid-bridge</artifactId>\n\t\t\t<version>2.6.1</version>\n\t\t\t<exclusions>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>org.codehaus.sonar.sslr</groupId>\n\t\t\t\t\t<artifactId>sslr-core</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>org.codehaus.sonar</groupId>\n\t\t\t\t\t<artifactId>sonar-plugin-api</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>org.codehaus.sonar.sslr</groupId>\n\t\t\t\t\t<artifactId>sslr-xpath</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>org.slf4j</groupId>\n\t\t\t\t\t<artifactId>jcl-over-slf4j</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>org.slf4j</groupId>\n\t\t\t\t\t<artifactId>slf4j-api</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t</exclusions>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>com.google.code.gson</groupId>\n\t\t\t<artifactId>gson</artifactId>\n\t\t\t<version>${gson.version}</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.sonarsource.sslr</groupId>\n\t\t\t<artifactId>sslr-testing-harness</artifactId>\n\t\t\t<version>${sslr.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.slf4j</groupId>\n\t\t\t<artifactId>slf4j-api</artifactId>\n\t\t\t<version>1.6.2</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>junit</groupId>\n\t\t\t<artifactId>junit</artifactId>\n\t\t\t<version>4.11</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.assertj</groupId>\n\t\t\t<artifactId>assertj-core</artifactId>\n\t\t\t<version>3.6.1</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>ch.qos.logback</groupId>\n\t\t\t<artifactId>logback-classic</artifactId>\n\t\t\t<version>0.9.30</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>\n\t\t\t\t<artifactId>sonar-packaging-maven-plugin</artifactId>\n\t\t\t\t<version>1.17</version>\n\t\t\t\t<extensions>true</extensions>\n\t\t\t\t<configuration>\n\t\t\t\t\t<pluginKey>vj-rules</pluginKey>\n\t\t\t\t\t<pluginName>VJ Rules</pluginName>\n\t\t\t\t\t<pluginClass>com.vip.vjkit.sonarvj.SonarPlugin</pluginClass>\n\t\t\t\t\t<pluginDescription>vip sonar java plugin</pluginDescription>\n\t\t\t\t\t<sonarLintSupported>true</sonarLintSupported>\n\t\t\t\t\t<sonarQubeMinVersion>6.7</sonarQubeMinVersion>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\n\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-compiler-plugin</artifactId>\n\t\t\t\t<version>3.7.0</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<source>1.8</source>\n\t\t\t\t\t<target>1.8</target>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarCheckRegistrar.java",
    "content": "package com.vip.vjkit.sonarvj;\n\nimport com.vip.vjkit.sonarvj.checks.*;\nimport org.sonar.plugins.java.api.CheckRegistrar;\nimport org.sonar.plugins.java.api.JavaCheck;\n\nimport java.util.Arrays;\n\npublic class SonarCheckRegistrar implements CheckRegistrar {\n\n\t@Override\n\tpublic void register(RegistrarContext registrarContext) {\n\t\tregistrarContext.registerClassesForRepository(SonarDefinition.REPOSITORY_KEY, Arrays.asList(checkClasses()),\n\t\t\t\tArrays.asList(testCheckClasses()));\n\t}\n\n\tpublic static Class<? extends JavaCheck>[] checkClasses() {\n\t\treturn new Class[] { BadConstantNameCheck.class, OperatorPrecedenceCheck.class,\n\t\t\t\tUnusedMethodParameterCheck.class, UnusedPrivateFieldCheck.class, MissingCurlyBracesCheck.class,\n\t\t\t\tHardcodedIpCheck.class, NoSonarCheck.class, CatchUsesExceptionWithContextCheck.class };\n\t}\n\n\tpublic static Class<? extends JavaCheck>[] testCheckClasses() {\n\t\treturn new Class[] {};\n\t}\n}"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarDefinition.java",
    "content": "\npackage com.vip.vjkit.sonarvj;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.Iterables;\nimport com.google.common.io.Resources;\nimport com.google.gson.Gson;\nimport org.apache.commons.lang.StringUtils;\nimport org.sonar.api.rule.RuleStatus;\nimport org.sonar.api.rules.RuleType;\nimport org.sonar.api.server.debt.DebtRemediationFunction;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonar.api.server.rule.RulesDefinitionAnnotationLoader;\nimport org.sonar.api.utils.AnnotationUtils;\nimport org.sonar.check.Cardinality;\nimport org.sonar.squidbridge.annotations.RuleTemplate;\n\nimport javax.annotation.Nullable;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\nimport java.util.Locale;\n\n/**\n * Declare rule metadata in server repository of rules.\n * That allows to list the rules in the page \"Rules\".\n */\npublic class SonarDefinition implements RulesDefinition {\n\n    // don't change that because the path is hard coded in CheckVerifier\n    private static final String RESOURCE_BASE_PATH = \"/com/vip/java/rules\";\n\n    public static final String REPOSITORY_KEY = \"v-squid\";\n\n    private final Gson gson = new Gson();\n\n    @Override\n    public void define(Context context) {\n        NewRepository repository = context\n                .createRepository(REPOSITORY_KEY, \"java\")\n                .setName(\"VIPAnalyzer \");\n\n        List<Class> checks = SonarRulesList.getChecks();\n        new RulesDefinitionAnnotationLoader().load(repository, Iterables.toArray(checks, Class.class));\n\n        for (Class ruleClass : checks) {\n            newRule(ruleClass, repository);\n        }\n        repository.done();\n    }\n\n    @VisibleForTesting\n    protected void newRule(Class<?> ruleClass, NewRepository repository) {\n\n        org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(ruleClass, org.sonar.check.Rule.class);\n        if (ruleAnnotation == null) {\n            throw new IllegalArgumentException(\"No Rule annotation was found on \" + ruleClass);\n        }\n        String ruleKey = ruleAnnotation.key();\n        if (StringUtils.isEmpty(ruleKey)) {\n            throw new IllegalArgumentException(\"No key is defined in Rule annotation of \" + ruleClass);\n        }\n        NewRule rule = repository.rule(ruleKey);\n        if (rule == null) {\n            throw new IllegalStateException(\"No rule was created for \" + ruleClass + \" in \" + repository.key());\n        }\n        ruleMetadata(ruleClass, rule);\n\n        rule.setTemplate(AnnotationUtils.getAnnotation(ruleClass, RuleTemplate.class) != null);\n        if (ruleAnnotation.cardinality() == Cardinality.MULTIPLE) {\n            throw new IllegalArgumentException(\"Cardinality is not supported, use the RuleTemplate annotation instead for \" + ruleClass);\n        }\n    }\n\n    private String ruleMetadata(Class<?> ruleClass, NewRule rule) {\n        String metadataKey = rule.key();\n        org.sonar.java.RspecKey rspecKeyAnnotation = AnnotationUtils.getAnnotation(ruleClass, org.sonar.java.RspecKey.class);\n        if (rspecKeyAnnotation != null) {\n            metadataKey = rspecKeyAnnotation.value();\n            rule.setInternalKey(metadataKey);\n        }\n        addHtmlDescription(rule, metadataKey);\n        addMetadata(rule, metadataKey);\n        return metadataKey;\n    }\n\n    private void addMetadata(NewRule rule, String metadataKey) {\n        URL resource = SonarDefinition.class.getResource(RESOURCE_BASE_PATH + \"/\" + metadataKey + \"_java.json\");\n        if (resource != null) {\n            RuleMetatada metatada = gson.fromJson(readResource(resource), RuleMetatada.class);\n            rule.setSeverity(metatada.defaultSeverity.toUpperCase(Locale.US));\n            rule.setName(metatada.title);\n            rule.addTags(metatada.tags);\n            rule.setType(RuleType.valueOf(metatada.type));\n            rule.setStatus(RuleStatus.valueOf(metatada.status.toUpperCase(Locale.US)));\n            if (metatada.remediation != null) {\n                rule.setDebtRemediationFunction(metatada.remediation.remediationFunction(rule.debtRemediationFunctions()));\n                rule.setGapDescription(metatada.remediation.linearDesc);\n            }\n        }\n    }\n\n    private static void addHtmlDescription(NewRule rule, String metadataKey) {\n        URL resource = SonarDefinition.class.getResource(RESOURCE_BASE_PATH + \"/\" + metadataKey + \"_java.html\");\n        if (resource != null) {\n            rule.setHtmlDescription(readResource(resource));\n        }\n    }\n\n    private static String readResource(URL resource) {\n        try {\n            return Resources.toString(resource, StandardCharsets.UTF_8);\n        } catch (IOException e) {\n            throw new IllegalStateException(\"Failed to read: \" + resource, e);\n        }\n    }\n\n    private static class RuleMetatada {\n        String title;\n        String status;\n        @Nullable\n        Remediation remediation;\n\n        String type;\n        String[] tags;\n        String defaultSeverity;\n    }\n\n    private static class Remediation {\n        String func;\n        String constantCost;\n        String linearDesc;\n        String linearOffset;\n        String linearFactor;\n\n        public DebtRemediationFunction remediationFunction(DebtRemediationFunctions drf) {\n            if (func.startsWith(\"Constant\")) {\n                return drf.constantPerIssue(constantCost.replace(\"mn\", \"min\"));\n            }\n            if (\"Linear\".equals(func)) {\n                return drf.linear(linearFactor.replace(\"mn\", \"min\"));\n            }\n            return drf.linearWithOffset(linearFactor.replace(\"mn\", \"min\"), linearOffset.replace(\"mn\", \"min\"));\n        }\n    }\n\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarPlugin.java",
    "content": "package com.vip.vjkit.sonarvj;\n\nimport org.sonar.api.Plugin;\n\n/**\n * Created by cloud.huang on 18/1/5.\n */\npublic class SonarPlugin implements Plugin {\n    @Override\n    public void define(Context context) {\n        context.addExtension(SonarDefinition.class);\n        context.addExtension(SonarCheckRegistrar.class);\n    }\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarRulesList.java",
    "content": "package com.vip.vjkit.sonarvj;\n\nimport com.google.common.collect.ImmutableList;\nimport com.vip.vjkit.sonarvj.checks.*;\nimport org.sonar.plugins.java.api.JavaCheck;\n\nimport java.util.List;\n\n/**\n * Created by cloud.huang on 18/1/5.\n */\npublic class SonarRulesList {\n\n    public static List<Class> getChecks() {\n        return ImmutableList.<Class>builder().addAll(getJavaChecks()).addAll(getJavaTestChecks()).build();\n    }\n\n    public static List<Class<? extends JavaCheck>> getJavaChecks() {\n        return ImmutableList.<Class<? extends JavaCheck>>builder()\n                .add(BadConstantNameCheck.class)\n                .add(OperatorPrecedenceCheck.class)\n                .add(UnusedPrivateFieldCheck.class)\n                .add(UnusedMethodParameterCheck.class)\n                .add(MissingCurlyBracesCheck.class)\n                .add(HardcodedIpCheck.class)\n                .add(NoSonarCheck.class)\n                .add(CatchUsesExceptionWithContextCheck.class)\n                .build();\n    }\n\n    public static List<Class<? extends JavaCheck>> getJavaTestChecks() {\n        return ImmutableList.<Class<? extends JavaCheck>>builder()\n                .build();\n    }\n\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/BadConstantNameCheck.java",
    "content": "/*\n * SonarQube Java Copyright (C) 2012-2018 SonarSource SA mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General\n * Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any\n * later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more\n * details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to\n * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n */\npackage com.vip.vjkit.sonarvj.checks;\n\nimport java.util.List;\nimport java.util.regex.Pattern;\n\nimport org.sonar.check.Rule;\nimport org.sonar.check.RuleProperty;\nimport org.sonar.java.checks.serialization.SerializableContract;\nimport org.sonar.java.resolve.JavaType;\nimport org.sonar.plugins.java.api.IssuableSubscriptionVisitor;\nimport org.sonar.plugins.java.api.JavaFileScannerContext;\nimport org.sonar.plugins.java.api.semantic.Type;\nimport org.sonar.plugins.java.api.tree.ClassTree;\nimport org.sonar.plugins.java.api.tree.Modifier;\nimport org.sonar.plugins.java.api.tree.ModifierKeywordTree;\nimport org.sonar.plugins.java.api.tree.Tree;\nimport org.sonar.plugins.java.api.tree.VariableTree;\n\nimport com.google.common.collect.ImmutableList;\n\n/**\n * 不对枚举的命名进行检查\n * \n * 另顺便修正了isConstantTyp一个BUG,加强了报错信息\n * \n * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/naming/BadConstantNameCheck.java\n * \n * 0d54578 Jan 8, 2018\n */\n@Rule(key = \"S115\")\npublic class BadConstantNameCheck extends IssuableSubscriptionVisitor {\n\n\tprivate static final String DEFAULT_FORMAT = \"^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$\";\n\t@RuleProperty(key = \"format\", description = \"Regular expression used to check the constant names against.\", defaultValue = \"\"\n\t\t\t+ DEFAULT_FORMAT)\n\tpublic String format = DEFAULT_FORMAT;\n\n\tprivate Pattern pattern = null;\n\n\t@Override\n\tpublic List<Tree.Kind> nodesToVisit() {\n\t\t// VJ Replace\n\t\treturn ImmutableList.of(Tree.Kind.CLASS, Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE);\n\t\t// return ImmutableList.of(Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE);\n\t}\n\n\t@Override\n\tpublic void scanFile(JavaFileScannerContext context) {\n\t\tif (pattern == null) {\n\t\t\tpattern = Pattern.compile(format, Pattern.DOTALL);\n\t\t}\n\t\tsuper.scanFile(context);\n\t}\n\n\t@Override\n\tpublic void visitNode(Tree tree) {\n\t\tClassTree classTree = (ClassTree) tree;\n\t\tfor (Tree member : classTree.members()) {\n\t\t\tif (member.is(Tree.Kind.VARIABLE) && hasSemantic()) {\n\t\t\t\tVariableTree variableTree = (VariableTree) member;\n\t\t\t\tType symbolType = variableTree.type().symbolType();\n\t\t\t\t if (isConstantType(symbolType) && (classTree.is(Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE) || isStaticFinal(variableTree))) {\n\t\t\t          checkName(variableTree);\n\t\t\t     }\n\t\t\t}\n\t\t\t// VJ Remove //\n\t\t\t// else if (member.is(Tree.Kind.ENUM_CONSTANT)) {\n\t\t\t// checkName((VariableTree) member);\n\t\t\t// }\n\t\t}\n\t}\n\n\t//VJ: 加上symbolType instanceof JavaType的判断，防止数组转换出错\n\tprivate static boolean isConstantType(Type symbolType) {\n\t\treturn symbolType.isPrimitive() || symbolType.is(\"java.lang.String\") ||  symbolType.is(\"java.lang.Byte\") ||\n\t\t\t\tsymbolType.is(\"java.lang.Character\") ||\n\t\t\t\tsymbolType.is(\"java.lang.Short\") ||\n\t\t\t\tsymbolType.is(\"java.lang.Integer\") ||\n\t\t\t\tsymbolType.is(\"java.lang.Long\") ||\n\t\t\t\tsymbolType.is(\"java.lang.Float\") ||\n\t\t\t\tsymbolType.is(\"java.lang.Double\") ||\n\t\t\t\tsymbolType.is(\"java.lang.Boolean\");\n\t}\n\n\tprivate void checkName(VariableTree variableTree) {\n\t\tString name = variableTree.simpleName().name();\n\t\tif (!SerializableContract.SERIAL_VERSION_UID_FIELD.equals(name)\n\t\t\t\t&& !pattern.matcher(name).matches()) {\n\t\t\t//VJ 报错信息加上变量名\n\t\t\treportIssue(variableTree.simpleName(),\n\t\t\t\t\t\"Rename this constant name '\" + name + \"' to match the regular expression '\" + format + \"'.\");\n\t\t}\n\t}\n\n\tprivate static boolean isStaticFinal(VariableTree variableTree) {\n\t\tboolean isStatic = false;\n\t\tboolean isFinal = false;\n\t\tfor (ModifierKeywordTree modifierKeywordTree : variableTree.modifiers().modifiers()) {\n\t\t\tModifier modifier = modifierKeywordTree.modifier();\n\t\t\tif (modifier == Modifier.STATIC) {\n\t\t\t\tisStatic = true;\n\t\t\t}\n\t\t\tif (modifier == Modifier.FINAL) {\n\t\t\t\tisFinal = true;\n\t\t\t}\n\t\t}\n\t\treturn isStatic && isFinal;\n\t}\n\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/CatchUsesExceptionWithContextCheck.java",
    "content": "package com.vip.vjkit.sonarvj.checks;\n\nimport com.google.common.base.Splitter;\nimport com.google.common.collect.Iterables;\nimport com.google.common.collect.Lists;\nimport org.apache.commons.lang.StringUtils;\nimport org.sonar.check.Rule;\nimport org.sonar.check.RuleProperty;\nimport org.sonar.java.checks.helpers.ExpressionsHelper;\nimport org.sonar.java.matcher.MethodMatcher;\nimport org.sonar.java.matcher.TypeCriteria;\nimport org.sonar.plugins.java.api.JavaFileScanner;\nimport org.sonar.plugins.java.api.JavaFileScannerContext;\nimport org.sonar.plugins.java.api.semantic.Symbol;\nimport org.sonar.plugins.java.api.tree.*;\nimport org.sonar.plugins.java.api.tree.Tree.Kind;\n\nimport java.util.*;\n\n/**\n * catch异常的变量名包含ignore，忽略检查\n */\n@Rule(key = \"S1166\")\npublic class CatchUsesExceptionWithContextCheck extends BaseTreeVisitor implements JavaFileScanner {\n\n    private static final String EXCLUDED_EXCEPTION_TYPE = \"java.lang.InterruptedException, \" +\n            \"java.lang.NumberFormatException, \" +\n            \"java.lang.NoSuchMethodException, \" +\n            \"java.text.ParseException, \" +\n            \"java.net.MalformedURLException, \" +\n            \"java.time.format.DateTimeParseException\";\n\n    @RuleProperty(\n            key = \"exceptions\",\n            description = \"List of exceptions which should not be checked\",\n            defaultValue = \"\" + EXCLUDED_EXCEPTION_TYPE)\n    public String exceptionsCommaSeparated = EXCLUDED_EXCEPTION_TYPE;\n\n    private JavaFileScannerContext context;\n    private Deque<Collection<IdentifierTree>> validUsagesStack;\n    private Iterable<String> exceptions;\n    private List<String> exceptionIdentifiers;\n    private Set<CatchTree> excludedCatchTrees = new HashSet<>();\n\n    @Override\n    public void scanFile(JavaFileScannerContext context) {\n        this.context = context;\n        validUsagesStack = new ArrayDeque<>();\n        exceptions = Splitter.on(\",\").trimResults().split(exceptionsCommaSeparated);\n        exceptionIdentifiers = Lists.newArrayList();\n        for (String exception : exceptions) {\n            exceptionIdentifiers.add(exception.substring(exception.lastIndexOf('.') + 1));\n        }\n        if (context.getSemanticModel() != null) {\n            scan(context.getTree());\n        }\n        excludedCatchTrees.clear();\n    }\n\n    @Override\n    public void visitTryStatement(TryStatementTree tree) {\n        if (containsEnumValueOf(tree.block())) {\n            tree.catches().stream()\n                    .filter(c -> c.parameter().symbol().type().is(\"java.lang.IllegalArgumentException\"))\n                    .findAny()\n                    .ifPresent(excludedCatchTrees::add);\n        }\n        super.visitTryStatement(tree);\n    }\n\n    private static boolean containsEnumValueOf(Tree tree) {\n        EnumValueOfVisitor visitor = new EnumValueOfVisitor();\n        tree.accept(visitor);\n        return visitor.hasEnumValueOf;\n    }\n\n    private static class EnumValueOfVisitor extends BaseTreeVisitor {\n        private static final MethodMatcher ENUM_VALUE_OF = MethodMatcher.create()\n                .typeDefinition(TypeCriteria.subtypeOf(\"java.lang.Enum\"))\n                .name(\"valueOf\")\n                .withAnyParameters();\n        private boolean hasEnumValueOf = false;\n\n        @Override\n        public void visitMethodInvocation(MethodInvocationTree tree) {\n            if (ENUM_VALUE_OF.matches(tree)) {\n                hasEnumValueOf = true;\n            }\n            super.visitMethodInvocation(tree);\n        }\n\n        @Override\n        public void visitClass(ClassTree tree) {\n            // skip anonymous classes\n        }\n\n        @Override\n        public void visitLambdaExpression(LambdaExpressionTree lambdaExpressionTree) {\n            // skip lambdas\n        }\n    }\n\n    @Override\n    public void visitCatch(CatchTree tree) {\n        if (!isExcludedType(tree.parameter().type()) && !excludedCatchTrees.contains(tree)) {\n            Symbol exception = tree.parameter().symbol();\n            //catch异常的变量名包含ignore，忽略检查\n            if (StringUtils.containsIgnoreCase(exception.name(), \"ignore\")) {\n                return;\n            }\n            validUsagesStack.addFirst(Lists.newArrayList(exception.usages()));\n            super.visitCatch(tree);\n            Collection<IdentifierTree> usages = validUsagesStack.pop();\n            if (usages.isEmpty()) {\n                context.reportIssue(this, tree.parameter(), \"Either log or rethrow this exception.\");\n            }\n        }\n    }\n\n    @Override\n    public void visitMemberSelectExpression(MemberSelectExpressionTree tree) {\n        IdentifierTree identifier = null;\n        ExpressionTree expression = tree.expression();\n        if (expression.is(Kind.IDENTIFIER)) {\n            identifier = (IdentifierTree) expression;\n        } else if (expression.is(Kind.PARENTHESIZED_EXPRESSION) && ((ParenthesizedTree) expression).expression().is(Kind.IDENTIFIER)) {\n            identifier = (IdentifierTree) ((ParenthesizedTree) expression).expression();\n        }\n        if (!validUsagesStack.isEmpty() && identifier != null) {\n            Iterator<Collection<IdentifierTree>> iterator = validUsagesStack.iterator();\n            while (iterator.hasNext()) {\n                iterator.next().remove(identifier);\n            }\n        }\n        super.visitMemberSelectExpression(tree);\n\n    }\n\n    private boolean isExcludedType(Tree tree) {\n        return isUnqualifiedExcludedType(tree) ||\n                isQualifiedExcludedType(tree);\n    }\n\n    private boolean isUnqualifiedExcludedType(Tree tree) {\n        return tree.is(Kind.IDENTIFIER) &&\n                exceptionIdentifiers.contains(((IdentifierTree) tree).name());\n    }\n\n    private boolean isQualifiedExcludedType(Tree tree) {\n        if (!tree.is(Kind.MEMBER_SELECT)) {\n            return false;\n        }\n        return Iterables.contains(exceptions, concatenate((MemberSelectExpressionTree) tree));\n    }\n    \n    public static String concatenate(ExpressionTree tree) {\n        if(tree == null) {\n          return \"\";\n        }\n        Deque<String> pieces = new LinkedList<>();\n        ExpressionTree expr = tree;\n        while (expr.is(Tree.Kind.MEMBER_SELECT)) {\n          MemberSelectExpressionTree mse = (MemberSelectExpressionTree) expr;\n          pieces.push(mse.identifier().name());\n          pieces.push(\".\");\n          expr = mse.expression();\n        }\n        if (expr.is(Tree.Kind.IDENTIFIER)) {\n          IdentifierTree idt = (IdentifierTree) expr;\n          pieces.push(idt.name());\n        }\n\n        StringBuilder sb = new StringBuilder();\n        for (String piece: pieces) {\n          sb.append(piece);\n        }\n        return sb.toString();\n      }\n\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/HardcodedIpCheck.java",
    "content": "package com.vip.vjkit.sonarvj.checks;\n\nimport com.google.common.base.Splitter;\n\nimport org.sonar.check.Rule;\nimport org.sonar.java.model.LiteralUtils;\nimport org.sonar.plugins.java.api.JavaFileScanner;\nimport org.sonar.plugins.java.api.JavaFileScannerContext;\nimport org.sonar.plugins.java.api.tree.BaseTreeVisitor;\nimport org.sonar.plugins.java.api.tree.LiteralTree;\nimport org.sonar.plugins.java.api.tree.Tree;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n/**\n * 忽略127.0.0.1\n * \n * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/HardcodedIpCheck.java\n * \n * 0d54578  Jan 8, 2018\n */\n@Rule(key = \"S1313\")\npublic class HardcodedIpCheck extends BaseTreeVisitor implements JavaFileScanner {\n\n\tprivate static final Matcher IP = Pattern\n\t\t\t.compile(\"([^\\\\d.]*\\\\/)?(?<ip>(?:\\\\d{1,3}\\\\.){3}\\\\d{1,3}(?!\\\\d|\\\\.))(\\\\/.*)?\").matcher(\"\");\n\n\tprivate JavaFileScannerContext context;\n\n\t@Override\n\tpublic void scanFile(final JavaFileScannerContext context) {\n\t\tthis.context = context;\n\t\tscan(context.getTree());\n\t}\n\n\t@Override\n\tpublic void visitLiteral(LiteralTree tree) {\n\t\tif (tree.is(Tree.Kind.STRING_LITERAL)) {\n\t\t\tString value = LiteralUtils.trimQuotes(tree.value());\n\t\t\tIP.reset(value);\n\t\t\tif (IP.matches()) {\n\t\t\t\tString ip = IP.group(\"ip\");\n\n\t\t\t\t// VJ:ADD 忽略127.0.0.1\n\t\t\t\tif (\"127.0.0.1\".equals(ip)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// VJ:END\n\t\t\t\tif (areAllBelow256(Splitter.on('.').split(ip))) {\n\t\t\t\t\tcontext.reportIssue(this, tree, \"Make this IP \\\"\" + ip + \"\\\" address configurable.\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static boolean areAllBelow256(Iterable<String> numbersAsStrings) {\n\t\tfor (String numberAsString : numbersAsStrings) {\n\t\t\tif (Integer.valueOf(numberAsString) > 255) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/MissingCurlyBracesCheck.java",
    "content": "package com.vip.vjkit.sonarvj.checks;\n\nimport com.google.common.collect.ImmutableList;\nimport org.sonar.check.Rule;\nimport org.sonar.plugins.java.api.IssuableSubscriptionVisitor;\nimport org.sonar.plugins.java.api.tree.*;\n\nimport java.util.List;\n\n/**\n * 1. equals 方法忽略if的检查 2. if(conditon) return true; 忽略在同一行的模式\n * \n * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/MissingCurlyBracesCheck.java\n * \n * 0d54578 Jan 8, 2018\n */\n@Rule(key = \"S121\")\npublic class MissingCurlyBracesCheck extends IssuableSubscriptionVisitor {\n\n\t@Override\n\tpublic List<Tree.Kind> nodesToVisit() {\n\t\treturn ImmutableList.of(Tree.Kind.IF_STATEMENT, Tree.Kind.FOR_EACH_STATEMENT, Tree.Kind.FOR_STATEMENT,\n\t\t\t\tTree.Kind.WHILE_STATEMENT, Tree.Kind.DO_STATEMENT);\n\t}\n\n\t@Override\n\tpublic void visitNode(Tree tree) {\n\t\tswitch (tree.kind()) {\n\t\tcase WHILE_STATEMENT:\n\t\t\tWhileStatementTree whileStatementTree = (WhileStatementTree) tree;\n\t\t\tcheckStatement(whileStatementTree.whileKeyword(), whileStatementTree.statement());\n\t\t\tbreak;\n\t\tcase DO_STATEMENT:\n\t\t\tDoWhileStatementTree doWhileStatementTree = (DoWhileStatementTree) tree;\n\t\t\tcheckStatement(doWhileStatementTree.doKeyword(), doWhileStatementTree.statement());\n\t\t\tbreak;\n\t\tcase FOR_STATEMENT:\n\t\t\tForStatementTree forStatementTree = (ForStatementTree) tree;\n\t\t\tcheckStatement(forStatementTree.forKeyword(), forStatementTree.statement());\n\t\t\tbreak;\n\t\tcase FOR_EACH_STATEMENT:\n\t\t\tForEachStatement forEachStatement = (ForEachStatement) tree;\n\t\t\tcheckStatement(forEachStatement.forKeyword(), forEachStatement.statement());\n\t\t\tbreak;\n\t\tcase IF_STATEMENT:\n\t\t\tcheckIfStatement((IfStatementTree) tree);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate void checkIfStatement(IfStatementTree ifStmt) {\n\t\t// equals 方法忽略if的检查, 如果if 与处理函数在同一行忽略。\n\t\tif (isInEqualsMethod(ifStmt)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isSameLine(ifStmt)) {\n\t\t\tcheckStatement(ifStmt.ifKeyword(), ifStmt.thenStatement());\n\t\t}\n\n\t\tStatementTree elseStmt = ifStmt.elseStatement();\n\t\tif (elseStmt != null && !elseStmt.is(Tree.Kind.IF_STATEMENT)) {\n\t\t\tcheckStatement(ifStmt.elseKeyword(), elseStmt);\n\t\t}\n\t}\n\n\tprivate boolean isSameLine(IfStatementTree ifStmt) {\n\t\tStatementTree thenStmt = ifStmt.thenStatement();\n\t\tif (thenStmt.is(Tree.Kind.BLOCK)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn ifStmt.firstToken().line() == thenStmt.firstToken().line();\n\t}\n\n\tprivate boolean isInEqualsMethod(IfStatementTree ifStmt) {\n\t\tTree tree = ifStmt.parent();\n\t\twhile (tree != null && !(tree instanceof MethodTree)) {\n\t\t\ttree = tree.parent();\n\t\t}\n\n\t\tif (tree == null) {\n\t\t\treturn false;\n\t\t}\n\n\t\tMethodTree methodTree = (MethodTree) tree;\n\t\tif (methodTree.simpleName().toString().equals(\"equals\")) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate void checkStatement(SyntaxToken reportToken, StatementTree statement) {\n\t\tif (!statement.is(Tree.Kind.BLOCK)) {\n\t\t\treportIssue(reportToken, \"Missing curly brace.\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/NoSonarCheck.java",
    "content": "/*\n * SonarQube Java Copyright (C) 2012-2018 SonarSource SA mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General\n * Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any\n * later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more\n * details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to\n * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n */\npackage com.vip.vjkit.sonarvj.checks;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.sonar.check.Rule;\nimport org.sonar.java.RspecKey;\nimport org.sonar.plugins.java.api.IssuableSubscriptionVisitor;\nimport org.sonar.plugins.java.api.JavaFileScannerContext;\nimport org.sonar.plugins.java.api.tree.Tree;\n\n/**\n * 忽略在system.out, e.printStacktrace, catch exception 后设置的NONSAR\n * \n * 基于TooLongLineCheck完全重写\n */\n@Rule(key = \"NoSonar\")\n@RspecKey(\"S1291\")\npublic class NoSonarCheck extends IssuableSubscriptionVisitor {\n\tprivate static final String PATTERN = \"NOSONAR\";\n\tprivate static final String[] IGNORE_PATTERNS = new String[] { \"Exception\", \"Throwable\", \"System.out\", \"System.err\",\n\t\t\t\"printStackTrace\" };\n\tprivate static final String MESSAGE = \"//NOSONAR found: \";\n\n\t@Override\n\tpublic List<Tree.Kind> nodesToVisit() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void scanFile(JavaFileScannerContext context) {\n\t\tsuper.context = context;\n\t\tsuper.scanFile(context);\n\t\tvisitFile();\n\t}\n\n\tprivate void visitFile() {\n\t\tList<String> lines = context.getFileLines();\n\t\tfor (int i = 0; i < lines.size(); i++) {\n\t\t\tString line = lines.get(i);\n\t\t\tif (line.contains(PATTERN) && !ignoredLine(line)) {\n\t\t\t\taddIssue(i + 1, MESSAGE + line);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate boolean ignoredLine(String line) {\n\t\tfor (String ignorePattern : IGNORE_PATTERNS) {\n\t\t\tif (line.contains(ignorePattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/OperatorPrecedenceCheck.java",
    "content": "/*\n * SonarQube Java\n * Copyright (C) 2012-2017 SonarSource SA\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage com.vip.vjkit.sonarvj.checks;\n\nimport com.google.common.collect.HashBasedTable;\nimport com.google.common.collect.Iterables;\nimport com.google.common.collect.Table;\nimport org.apache.commons.lang.BooleanUtils;\nimport org.sonar.check.Rule;\nimport org.sonar.plugins.java.api.JavaFileScanner;\nimport org.sonar.plugins.java.api.JavaFileScannerContext;\nimport org.sonar.plugins.java.api.tree.*;\n\nimport java.util.*;\n\n/**\n * 三目运算符中的表达式可以不要括号\n * \n * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/OperatorPrecedenceCheck.java\n * \n * 0d54578  Jan 8, 2018\n */\n@Rule(key = \"S864\")\npublic class OperatorPrecedenceCheck extends BaseTreeVisitor implements JavaFileScanner {\n\n    private static final Table<Tree.Kind, Tree.Kind, Boolean> OPERATORS_RELATION_TABLE;\n\n    private static final Set<Tree.Kind> ARITHMETIC_OPERATORS = EnumSet.of(\n            Tree.Kind.MINUS,\n            Tree.Kind.REMAINDER,\n            Tree.Kind.MULTIPLY,\n            Tree.Kind.PLUS\n    );\n\n    private static final Set<Tree.Kind> EQUALITY_RELATIONAL_OPERATORS = EnumSet.of(\n            Tree.Kind.EQUAL_TO,\n            Tree.Kind.GREATER_THAN,\n            Tree.Kind.GREATER_THAN_OR_EQUAL_TO,\n            Tree.Kind.LESS_THAN,\n            Tree.Kind.LESS_THAN_OR_EQUAL_TO,\n            Tree.Kind.NOT_EQUAL_TO\n    );\n\n    private static final Set<Tree.Kind> SHIFT_OPERATORS = EnumSet.of(\n            Tree.Kind.LEFT_SHIFT,\n            Tree.Kind.RIGHT_SHIFT,\n            Tree.Kind.UNSIGNED_RIGHT_SHIFT\n    );\n\n    private static final Tree.Kind[] CONDITIONAL_EXCLUSIONS = new Tree.Kind[]{\n            Tree.Kind.METHOD_INVOCATION, Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT,\n            Tree.Kind.PARENTHESIZED_EXPRESSION, Tree.Kind.TYPE_CAST, Tree.Kind.NEW_CLASS,\n            Tree.Kind.ARRAY_ACCESS_EXPRESSION, Tree.Kind.NEW_ARRAY\n    };\n\n    static {\n        OPERATORS_RELATION_TABLE = HashBasedTable.create();\n        put(ARITHMETIC_OPERATORS, Iterables.concat(SHIFT_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.XOR, Tree.Kind.OR)));\n        put(SHIFT_OPERATORS, Iterables.concat(ARITHMETIC_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.XOR, Tree.Kind.OR)));\n        put(EnumSet.of(Tree.Kind.AND), Iterables.concat(ARITHMETIC_OPERATORS, SHIFT_OPERATORS, EnumSet.of(Tree.Kind.XOR, Tree.Kind.OR)));\n        put(EnumSet.of(Tree.Kind.XOR), Iterables.concat(ARITHMETIC_OPERATORS, SHIFT_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.OR)));\n        put(EnumSet.of(Tree.Kind.OR), Iterables.concat(ARITHMETIC_OPERATORS, SHIFT_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.XOR)));\n        put(EnumSet.of(Tree.Kind.CONDITIONAL_AND), EnumSet.of(Tree.Kind.CONDITIONAL_OR));\n        put(EnumSet.of(Tree.Kind.CONDITIONAL_OR), EnumSet.of(Tree.Kind.CONDITIONAL_AND));\n    }\n\n    private JavaFileScannerContext context;\n    private Deque<Tree.Kind> stack = new LinkedList<>();\n    private Set<Integer> reportedLines = new HashSet<>();\n\n    private static void put(Iterable<Tree.Kind> firstSet, Iterable<Tree.Kind> secondSet) {\n        for (Tree.Kind first : firstSet) {\n            for (Tree.Kind second : secondSet) {\n                OPERATORS_RELATION_TABLE.put(first, second, true);\n            }\n        }\n    }\n\n    @Override\n    public void scanFile(JavaFileScannerContext context) {\n        this.context = context;\n        reportedLines.clear();\n        scan(context.getTree());\n        reportedLines.clear();\n    }\n\n    @Override\n    public void visitAnnotation(AnnotationTree tree) {\n        stack.push(null);\n        for (ExpressionTree argument : tree.arguments()) {\n            if (argument.is(Tree.Kind.ASSIGNMENT)) {\n                scan(((AssignmentExpressionTree) argument).expression());\n            } else {\n                scan(argument);\n            }\n        }\n        stack.pop();\n    }\n\n    @Override\n    public void visitArrayAccessExpression(ArrayAccessExpressionTree tree) {\n        scan(tree.expression());\n        stack.push(null);\n        scan(tree.dimension());\n        stack.pop();\n    }\n\n    @Override\n    public void visitBinaryExpression(BinaryExpressionTree tree) {\n        Tree.Kind peek = stack.peek();\n        Tree.Kind kind = tree.kind();\n        if (requiresParenthesis(peek, kind)) {\n            raiseIssue(tree.operatorToken().line(), tree);\n        }\n        stack.push(kind);\n        super.visitBinaryExpression(tree);\n        stack.pop();\n    }\n\n    private static boolean requiresParenthesis(Tree.Kind kind1, Tree.Kind kind2) {\n        return BooleanUtils.isTrue(OPERATORS_RELATION_TABLE.get(kind1, kind2));\n    }\n\n    @Override\n    public void visitIfStatement(IfStatementTree tree) {\n        super.visitIfStatement(tree);\n        ExpressionTree condition = tree.condition();\n        if (condition.is(Tree.Kind.ASSIGNMENT) && EQUALITY_RELATIONAL_OPERATORS.contains(((AssignmentExpressionTree) condition).expression().kind())) {\n            raiseIssue(((AssignmentExpressionTree) condition).operatorToken().line(), tree);\n        }\n    }\n\n    @Override\n    public void visitMethodInvocation(MethodInvocationTree tree) {\n        scan(tree.methodSelect());\n        scan(tree.typeArguments());\n        for (ExpressionTree argument : tree.arguments()) {\n            stack.push(null);\n            scan(argument);\n            stack.pop();\n        }\n    }\n\n    @Override\n    public void visitNewArray(NewArrayTree tree) {\n        stack.push(null);\n        super.visitNewArray(tree);\n        stack.pop();\n    }\n\n    @Override\n    public void visitNewClass(NewClassTree tree) {\n        stack.push(null);\n        super.visitNewClass(tree);\n        stack.pop();\n    }\n\n    @Override\n    public void visitParenthesized(ParenthesizedTree tree) {\n        stack.push(null);\n        super.visitParenthesized(tree);\n        stack.pop();\n    }\n\n    @Override\n    public void visitConditionalExpression(ConditionalExpressionTree tree) {\n        checkConditionalOperand(tree.trueExpression());\n        checkConditionalOperand(tree.falseExpression());\n        super.visitConditionalExpression(tree);\n    }\n\n    private void checkConditionalOperand(ExpressionTree tree) {\n        if (!(tree.is(CONDITIONAL_EXCLUSIONS) || tree instanceof LiteralTree || tree instanceof UnaryExpressionTree)) {\n            //VJ ADD 三目运算符中的表达式可以不要括号\n            if (tree.parent() != null && tree.parent().kind().equals(Tree.Kind.CONDITIONAL_EXPRESSION)) {\n                return;\n            }\n            //VJ END\n            raiseIssue(tree.firstToken().line(), tree);\n        }\n    }\n\n    private void raiseIssue(int line, Tree tree) {\n        if (reportedLines.add(line)) {\n            context.reportIssue(this, tree, \"Add parentheses to make the operator precedence explicit.\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/UnusedMethodParameterCheck.java",
    "content": "/*\n * SonarQube Java\n * Copyright (C) 2012-2018 SonarSource SA\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n\npackage com.vip.vjkit.sonarvj.checks;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport org.sonar.check.Rule;\nimport org.sonar.java.model.ModifiersUtils;\nimport org.sonar.plugins.java.api.IssuableSubscriptionVisitor;\nimport org.sonar.plugins.java.api.JavaFileScannerContext;\nimport org.sonar.plugins.java.api.semantic.Symbol;\nimport org.sonar.plugins.java.api.semantic.Type;\nimport org.sonar.plugins.java.api.tree.BaseTreeVisitor;\nimport org.sonar.plugins.java.api.tree.ExpressionTree;\nimport org.sonar.plugins.java.api.tree.IdentifierTree;\nimport org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;\nimport org.sonar.plugins.java.api.tree.MethodInvocationTree;\nimport org.sonar.plugins.java.api.tree.MethodTree;\nimport org.sonar.plugins.java.api.tree.Modifier;\nimport org.sonar.plugins.java.api.tree.Tree;\nimport org.sonar.plugins.java.api.tree.VariableTree;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Lists;\n\n/**\n * 只判读private方法里的无用参数\n * \n * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedMethodParameterCheck.java\n * \n * 2c86dbb Jan 12, 2018\n */\n@Rule(key = \"S1172\")\npublic class UnusedMethodParameterCheck extends IssuableSubscriptionVisitor {\n\tprivate static final String AUTHORIZED_ANNOTATION = \"javax.enterprise.event.Observes\";\n\tprivate static final String STRUTS_ACTION_SUPERCLASS = \"org.apache.struts.action.Action\";\n\tprivate static final Collection<String> EXCLUDED_STRUTS_ACTION_PARAMETER_TYPES = ImmutableList.of(\n\t\t\t\"org.apache.struts.action.ActionMapping\", \"org.apache.struts.action.ActionForm\",\n\t\t\t\"javax.servlet.http.HttpServletRequest\", \"javax.servlet.http.HttpServletResponse\");\n\n\t@Override\n\tpublic List<Tree.Kind> nodesToVisit() {\n\t\treturn ImmutableList.of(Tree.Kind.METHOD, Tree.Kind.CONSTRUCTOR);\n\t}\n\n\t@Override\n\tpublic void visitNode(Tree tree) {\n\t\tif (!hasSemantic()) {\n\t\t\treturn;\n\t\t}\n\t\tMethodTree methodTree = (MethodTree) tree;\n\t\tif (methodTree.block() == null || !isIncluded(methodTree)) {\n\t\t\treturn;\n\t\t}\n\t\tList<IdentifierTree> unused = Lists.newArrayList();\n\t\tfor (VariableTree var : methodTree.parameters()) {\n\t\t\tSymbol symbol = var.symbol();\n\t\t\tif (symbol.usages().isEmpty() && !symbol.metadata().isAnnotatedWith(AUTHORIZED_ANNOTATION)\n\t\t\t\t\t&& !isStrutsActionParameter(var)) {\n\t\t\t\tunused.add(var.simpleName());\n\t\t\t}\n\t\t}\n\t\tSet<String> unresolvedIdentifierNames = unresolvedIdentifierNames(methodTree.block());\n\t\t// kill the noise regarding unresolved identifiers, and remove the one with matching names from the list of\n\t\t// unused\n\t\tunused = unused.stream().filter(id -> !unresolvedIdentifierNames.contains(id.name()))\n\t\t\t\t.collect(Collectors.toList());\n\t\tif (!unused.isEmpty()) {\n\t\t\treportUnusedParameters(unused);\n\t\t}\n\t}\n\n\tprivate void reportUnusedParameters(List<IdentifierTree> unused) {\n\t\tList<JavaFileScannerContext.Location> locations = new ArrayList<>();\n\t\tfor (IdentifierTree identifier : unused) {\n\t\t\tlocations.add(new JavaFileScannerContext.Location(\n\t\t\t\t\t\"Remove this unused method parameter \" + identifier.name() + \"\\\".\", identifier));\n\t\t}\n\t\tIdentifierTree firstUnused = unused.get(0);\n\t\tString msg;\n\t\tif (unused.size() > 1) {\n\t\t\tmsg = \"Remove these unused method parameters.\";\n\t\t} else {\n\t\t\tmsg = \"Remove this unused method parameter \\\"\" + firstUnused.name() + \"\\\".\";\n\t\t}\n\t\treportIssue(firstUnused, msg, locations, null);\n\t}\n\n\t// VJ: 改为只判读private方法里的无用参数，替代原isExclude\n\t// 大量删除原来exclude里的复杂判断\n\tprivate static boolean isIncluded(MethodTree tree) {\n\t\treturn isPrivateMethod(tree);\n\t}\n\n\tprivate static boolean isStrutsActionParameter(VariableTree variableTree) {\n\t\tType superClass = variableTree.symbol().enclosingClass().superClass();\n\t\treturn superClass != null && superClass.isSubtypeOf(STRUTS_ACTION_SUPERCLASS)\n\t\t\t\t&& EXCLUDED_STRUTS_ACTION_PARAMETER_TYPES.contains(variableTree.symbol().type().fullyQualifiedName());\n\t}\n\n\tprivate static boolean isPrivateMethod(MethodTree methodTree) {\n\t\treturn ModifiersUtils.hasModifier(methodTree.modifiers(), Modifier.PRIVATE);\n\t}\n\n\tprivate static Set<String> unresolvedIdentifierNames(Tree tree) {\n\t\tUnresolvedIdentifierVisitor visitor = new UnresolvedIdentifierVisitor();\n\t\ttree.accept(visitor);\n\t\treturn visitor.unresolvedIdentifierNames;\n\t}\n\n\tprivate static class UnresolvedIdentifierVisitor extends BaseTreeVisitor {\n\n\t\tprivate Set<String> unresolvedIdentifierNames = new HashSet<>();\n\n\t\t@Override\n\t\tpublic void visitMemberSelectExpression(MemberSelectExpressionTree tree) {\n\t\t\t// skip annotations and identifier, a method parameter will only be used in expression side (before the dot)\n\t\t\tscan(tree.expression());\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMethodInvocation(MethodInvocationTree tree) {\n\t\t\tExpressionTree methodSelect = tree.methodSelect();\n\t\t\tif (!methodSelect.is(Tree.Kind.IDENTIFIER)) {\n\t\t\t\t// not interested in simple method invocations, we are targeting usage of method parameters\n\t\t\t\tscan(methodSelect);\n\t\t\t}\n\t\t\tscan(tree.typeArguments());\n\t\t\tscan(tree.arguments());\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitIdentifier(IdentifierTree tree) {\n\t\t\tif (tree.symbol().isUnknown()) {\n\t\t\t\tunresolvedIdentifierNames.add(tree.name());\n\t\t\t}\n\t\t\tsuper.visitIdentifier(tree);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/UnusedPrivateFieldCheck.java",
    "content": "/*\n * SonarQube Java Copyright (C) 2012-2018 SonarSource SA mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General\n * Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any\n * later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more\n * details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to\n * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n */\n\npackage com.vip.vjkit.sonarvj.checks;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.sonar.check.Rule;\nimport org.sonar.java.model.ExpressionUtils;\nimport org.sonar.java.model.ModifiersUtils;\nimport org.sonar.plugins.java.api.IssuableSubscriptionVisitor;\nimport org.sonar.plugins.java.api.JavaFileScannerContext;\nimport org.sonar.plugins.java.api.semantic.Symbol;\nimport org.sonar.plugins.java.api.tree.AssignmentExpressionTree;\nimport org.sonar.plugins.java.api.tree.ClassTree;\nimport org.sonar.plugins.java.api.tree.ExpressionStatementTree;\nimport org.sonar.plugins.java.api.tree.ExpressionTree;\nimport org.sonar.plugins.java.api.tree.IdentifierTree;\nimport org.sonar.plugins.java.api.tree.ImportTree;\nimport org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;\nimport org.sonar.plugins.java.api.tree.MethodInvocationTree;\nimport org.sonar.plugins.java.api.tree.MethodReferenceTree;\nimport org.sonar.plugins.java.api.tree.MethodTree;\nimport org.sonar.plugins.java.api.tree.Modifier;\nimport org.sonar.plugins.java.api.tree.Tree;\nimport org.sonar.plugins.java.api.tree.Tree.Kind;\nimport org.sonar.plugins.java.api.tree.VariableTree;\n\nimport com.google.common.collect.ArrayListMultimap;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ListMultimap;\nimport com.google.common.collect.Lists;\n\n/**\n * 不检查lombok自动生成getter/setter的类\n * \n * https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java\n * 0d54578 Jan 8, 2018\n */\n@Rule(key = \"S1068\")\npublic class UnusedPrivateFieldCheck extends IssuableSubscriptionVisitor {\n\n\tprivate static final Tree.Kind[] ASSIGNMENT_KINDS = { Tree.Kind.ASSIGNMENT, Tree.Kind.MULTIPLY_ASSIGNMENT,\n\t\t\tTree.Kind.DIVIDE_ASSIGNMENT, Tree.Kind.REMAINDER_ASSIGNMENT, Tree.Kind.PLUS_ASSIGNMENT,\n\t\t\tTree.Kind.MINUS_ASSIGNMENT, Tree.Kind.LEFT_SHIFT_ASSIGNMENT, Tree.Kind.RIGHT_SHIFT_ASSIGNMENT,\n\t\t\tTree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, Tree.Kind.AND_ASSIGNMENT, Tree.Kind.XOR_ASSIGNMENT,\n\t\t\tTree.Kind.OR_ASSIGNMENT };\n\n\tprivate List<ClassTree> classes = Lists.newArrayList();\n\tprivate ListMultimap<Symbol, IdentifierTree> assignments = ArrayListMultimap.create();\n\tprivate Set<String> unknownIdentifiers = new HashSet<>();\n\tprivate boolean hasNativeMethod = false;\n\tprivate boolean lombokClass = false;\n\n\t@Override\n\tpublic List<Kind> nodesToVisit() {\n\t\treturn ImmutableList.of(Tree.Kind.IMPORT, Tree.Kind.CLASS, Tree.Kind.METHOD, Tree.Kind.EXPRESSION_STATEMENT,\n\t\t\t\tTree.Kind.IDENTIFIER);\n\t}\n\n\t@Override\n\tpublic void scanFile(JavaFileScannerContext context) {\n\t\tsuper.scanFile(context);\n\t\tif (!hasNativeMethod && !lombokClass) {\n\t\t\tclasses.forEach(this::checkClassFields);\n\t\t}\n\t\tclasses.clear();\n\t\tassignments.clear();\n\t\tunknownIdentifiers.clear();\n\t\thasNativeMethod = false;\n\t\tlombokClass = false;\n\t}\n\n\t@Override\n\tpublic void visitNode(Tree tree) {\n\t\tif (!hasSemantic()) {\n\t\t\treturn;\n\t\t}\n\t\tswitch (tree.kind()) {\n\t\tcase METHOD:\n\t\t\tcheckIfNativeMethod((MethodTree) tree);\n\t\t\tbreak;\n\t\tcase CLASS:\n\t\t\tclasses.add((ClassTree) tree);\n\t\t\tbreak;\n\t\tcase IMPORT:// VJ\n\t\t\tcheckIfLombokClass((ImportTree) tree);\n\t\t\tbreak;\n\t\tcase EXPRESSION_STATEMENT:\n\t\t\tcollectAssignment(((ExpressionStatementTree) tree).expression());\n\t\t\tbreak;\n\t\tcase IDENTIFIER:\n\t\t\tcollectUnknownIdentifier((IdentifierTree) tree);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new IllegalStateException(\"Unexpected subscribed tree.\");\n\t\t}\n\t}\n\n\t// VJ\n\tprivate void checkIfLombokClass(ImportTree tree) {\n\t\tString importStr = fullQualifiedName(tree.qualifiedIdentifier());\n\t\tif (importStr.contains(\"lombok\")) {\n\t\t\tlombokClass = true;\n\t\t}\n\t}\n\n\t// from WildcardImportsShouldNotBeUsedCheck\n\tprivate static String fullQualifiedName(Tree tree) {\n\t\tif (tree.is(Tree.Kind.IDENTIFIER)) {\n\t\t\treturn ((IdentifierTree) tree).name();\n\t\t} else if (tree.is(Tree.Kind.MEMBER_SELECT)) {\n\t\t\tMemberSelectExpressionTree m = (MemberSelectExpressionTree) tree;\n\t\t\treturn fullQualifiedName(m.expression()) + \".\" + m.identifier().name();\n\t\t}\n\t\tthrow new UnsupportedOperationException(String.format(\"Kind/Class '%s' not supported\", tree.getClass()));\n\t}\n\n\tprivate void collectUnknownIdentifier(IdentifierTree identifier) {\n\t\tif (identifier.symbol().isUnknown() && !isMethodIdentifier(identifier)) {\n\t\t\tunknownIdentifiers.add(identifier.name());\n\t\t}\n\t}\n\n\tprivate static boolean isMethodIdentifier(IdentifierTree identifier) {\n\t\tTree parent = identifier.parent();\n\t\twhile (parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION, Tree.Kind.METHOD_REFERENCE)) {\n\t\t\tparent = parent.parent();\n\t\t}\n\t\tif (parent == null) {\n\t\t\treturn false;\n\t\t}\n\t\tif (parent.is(Tree.Kind.METHOD_INVOCATION)) {\n\t\t\treturn identifier.equals(methodName((MethodInvocationTree) parent));\n\t\t} else {\n\t\t\treturn identifier.equals(((MethodReferenceTree) parent).method());\n\t\t}\n\t}\n\n\t//VJ: copy from MethodsHelper\n\tpublic static IdentifierTree methodName(MethodInvocationTree mit) {\n\t    IdentifierTree id;\n\t    if (mit.methodSelect().is(Tree.Kind.IDENTIFIER)) {\n\t      id = (IdentifierTree) mit.methodSelect();\n\t    } else {\n\t      id = ((MemberSelectExpressionTree) mit.methodSelect()).identifier();\n\t    }\n\t    return id;\n\t  }\n\t\n\tprivate void checkIfNativeMethod(MethodTree method) {\n\t\tif (ModifiersUtils.hasModifier(method.modifiers(), Modifier.NATIVE)) {\n\t\t\thasNativeMethod = true;\n\t\t}\n\t}\n\n\tprivate void checkClassFields(ClassTree classTree) {\n\t\tclassTree.members().stream().filter(member -> member.is(Tree.Kind.VARIABLE)).map(VariableTree.class::cast)\n\t\t\t\t.forEach(this::checkIfUnused);\n\t}\n\n\tpublic void checkIfUnused(VariableTree tree) {\n\t\tif (hasNoAnnotation(tree)) {\n\t\t\tSymbol symbol = tree.symbol();\n\t\t\tString name = symbol.name();\n\t\t\tif (symbol.isPrivate() && onlyUsedInVariableAssignment(symbol) && !\"serialVersionUID\".equals(name)\n\t\t\t\t\t&& !unknownIdentifiers.contains(name)) {\n\t\t\t\treportIssue(tree.simpleName(), \"Remove this unused \\\"\" + name + \"\\\" private field.\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate boolean onlyUsedInVariableAssignment(Symbol symbol) {\n\t\treturn symbol.usages().size() == assignments.get(symbol).size();\n\t}\n\n\tprivate static boolean hasNoAnnotation(VariableTree tree) {\n\t\treturn tree.modifiers().annotations().isEmpty();\n\t}\n\n\tprivate void collectAssignment(ExpressionTree expressionTree) {\n\t\tif (expressionTree.is(ASSIGNMENT_KINDS)) {\n\t\t\taddAssignment(((AssignmentExpressionTree) expressionTree).variable());\n\t\t}\n\t}\n\n\tprivate void addAssignment(ExpressionTree tree) {\n\t\tExpressionTree variable = ExpressionUtils.skipParentheses(tree);\n\t\tif (variable.is(Tree.Kind.IDENTIFIER)) {\n\t\t\taddAssignment((IdentifierTree) variable);\n\t\t} else if (variable.is(Tree.Kind.MEMBER_SELECT)) {\n\t\t\taddAssignment(((MemberSelectExpressionTree) variable).identifier());\n\t\t}\n\t}\n\n\tprivate void addAssignment(IdentifierTree identifier) {\n\t\tSymbol reference = identifier.symbol();\n\t\tif (!reference.isUnknown()) {\n\t\t\tassignments.put(reference, identifier);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1068_java.html",
    "content": "<h2>VJ update: 使用lobmok的类不告警</h2>\n<p>If a <code>private</code> field is declared but not used in the program, it can be considered dead code and should therefore be removed. This will\nimprove maintainability because developers will not wonder what the variable is used for.</p>\n<p>Note that this rule does not take reflection into account, which means that issues will be raised on <code>private</code> fields that are only\naccessed using the reflection API.</p>\n<h2>Noncompliant Code Example</h2>\n<pre>\npublic class MyClass {\n  private int foo = 42;\n\n  public int compute(int a) {\n    return a * 42;\n  }\n\n}\n</pre>\n<h2>Compliant Solution</h2>\n<pre>\npublic class MyClass {\n  public int compute(int a) {\n    return a * 42;\n  }\n}\n</pre>\n<h2>Exceptions</h2>\n<p>The Java serialization runtime associates with each serializable class a version number, called <code>serialVersionUID</code>, which is used during\ndeserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to\nserialization.</p>\n<p>A serializable class can declare its own <code>serialVersionUID</code> explicitly by declaring a field named <code>serialVersionUID</code> that\nmust be static, final, and of type long. By definition those <code>serialVersionUID</code> fields should not be reported by this rule:</p>\n<pre>\npublic class MyClass implements java.io.Serializable {\n  private static final long serialVersionUID = 42L;\n}\n</pre>\n<p>Moreover, this rule doesn't raise any issue on annotated fields.</p>"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1068_java.json",
    "content": "{\n  \"title\": \"VJ: Unused \\\"private\\\" fields should be removed\",\n  \"type\": \"CODE_SMELL\",\n  \"status\": \"ready\",\n  \"remediation\": {\n    \"func\": \"Constant\\/Issue\",\n    \"constantCost\": \"5min\"\n  },\n  \"tags\": [\n    \"cert\",\n    \"unused\"\n  ],\n  \"defaultSeverity\": \"Major\",\n  \"ruleSpecification\": \"RSPEC-1068\",\n  \"sqKey\": \"S1068\"\n}"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S115_java.html",
    "content": "<h2>VJ update: 枚举可以是小写</h2>\n<p>Shared coding conventions allow teams to collaborate efficiently. This rule checks that all constant names match a provided regular expression.</p>\n<h2>Noncompliant Code Example</h2>\n<p>With the default regular expression <code>^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$</code>:</p>\n<pre>\npublic class MyClass {\n  public static final int first = 1;\n}\n\n</pre>\n<h2>Compliant Solution</h2>\n<pre>\npublic class MyClass {\n  public static final int FIRST = 1;\n}\n\n</pre>\n\n"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S115_java.json",
    "content": "{\n  \"title\": \"VJ: Constant names should comply with a naming convention\",\n  \"type\": \"CODE_SMELL\",\n  \"status\": \"ready\",\n  \"remediation\": {\n    \"func\": \"Constant\\/Issue\",\n    \"constantCost\": \"2min\"\n  },\n  \"tags\": [\n    \"convention\"\n  ],\n  \"defaultSeverity\": \"Minor\"\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1166_java.html",
    "content": "<h2>VJ update: catch异常的变量名包含ignore，忽略检查</h2>\n<p>When handling a caught exception, the original exception's message and stack trace should be logged or passed forward.</p>\n<h2>Noncompliant Code Example</h2>\n<pre>\ntry {\n  /* ... */\n} catch (Exception e) {   // Noncompliant - exception is lost\n  LOGGER.info(\"context\");\n}\n\ntry {\n  /* ... */\n} catch (Exception e) {  // Noncompliant - exception is lost (only message is preserved)\n  LOGGER.info(e.getMessage());\n}\n\ntry {\n  /* ... */\n} catch (Exception e) {  // Noncompliant - original exception is lost\n  throw new RuntimeException(\"context\");\n}\n</pre>\n<h2>Compliant Solution</h2>\n<pre>\ntry {\n  /* ... */\n} catch (Exception e) {\n  LOGGER.info(e);  // exception is logged\n}\n\ntry {\n  /* ... */\n} catch (Exception e) {\n  throw new RuntimeException(e);   // exception stack trace is propagated\n}\n\ntry {\n  /* ... */\n} catch (RuntimeException e) {\n  doSomething();\n  throw e;  // original exception passed forward\n} catch (Exception e) {\n  throw new RuntimeException(e);  // Conversion into unchecked exception is also allowed\n}\n</pre>\n<h2>Exceptions</h2>\n<p><code>InterruptedException</code>, <code>NumberFormatException</code>, <code>DateTimeParseException</code>, <code>ParseException</code> and\n<code>MalformedURLException</code> exceptions are arguably used to indicate nonexceptional outcomes. Similarly, handling\n<code>NoSuchMethodException</code> is often required when dealing with the Java reflection API.</p>\n<p>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\ncorrectly handled.</p>\n<pre>\nint myInteger;\ntry {\n  myInteger = Integer.parseInt(myString);\n} catch (NumberFormatException e) {\n  // It is perfectly acceptable to not handle \"e\" here\n  myInteger = 0;\n}\n</pre>\n<h2>See</h2>\n<ul>\n  <li> <a href=\"https://www.securecoding.cert.org/confluence/x/6gEqAQ\">CERT, ERR00-J.</a> - Do not suppress or ignore checked exceptions </li>\n  <li> OWASP Top 10 2017 Category A10 - Insufficient Logging &amp; Monitoring </li>\n</ul>\n\n"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1166_java.json",
    "content": "{\n  \"title\": \"VJ：Exception handlers should preserve the original exceptions\",\n  \"type\": \"CODE_SMELL\",\n  \"status\": \"ready\",\n  \"remediation\": {\n    \"func\": \"Constant\\/Issue\",\n    \"constantCost\": \"10min\"\n  },\n  \"tags\": [\n    \"error-handling\",\n    \"owasp-a10\",\n    \"cert\",\n    \"suspicious\"\n  ],\n  \"standards\": [\n    \"CERT\"\n  ],\n  \"defaultSeverity\": \"Major\",\n  \"ruleSpecification\": \"RSPEC-1166\",\n  \"sqKey\": \"S1166\"\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1172_java.html",
    "content": "<h2>VJ update：只判断private函数</h2>\n\n<p>Unused parameters for private method misleading. Whatever the values passed to such parameters, the behavior will be the same.</p>\n\n<h2>Noncompliant Code Example</h2>\n<pre>\nprivate void doSomething(int a, int b) {     // \"b\" is unused\n  compute(a);\n}\n</pre>\n<h2>Compliant Solution</h2>\n<pre>\nprivate void doSomething(int a) {\n  compute(a);\n}\n</pre>\n\n<h2>Exceptions</h2>\n<p>The rule will not raise issues for protected or public methods </p>\n\n<h2>See</h2>\n<ul>\n  <li> MISRA C++:2008, 0-1-11 - There shall be no unused parameters (named or unnamed) in nonvirtual functions. </li>\n  <li> MISRA C:2012, 2.7 - There should be no unused parameters in functions </li>\n  <li> <a href=\"https://www.securecoding.cert.org/confluence/x/NYA5\">CERT, MSC12-C.</a> - Detect and remove code that has no effect or is never\n  executed </li>\n  <li> <a href=\"https://www.securecoding.cert.org/confluence/x/SIIyAQ\">CERT, MSC12-CPP.</a> - Detect and remove code that has no effect </li>\n</ul>"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1172_java.json",
    "content": "{\n  \"title\": \"VJ: Unused private method parameters should be removed\",\n  \"type\": \"CODE_SMELL\",\n  \"status\": \"ready\",\n  \"remediation\": {\n    \"func\": \"Constant\\/Issue\",\n    \"constantCost\": \"5min\"\n  },\n  \"tags\": [\n    \"misra\",\n    \"cert\",\n    \"unused\"\n  ],\n  \"defaultSeverity\": \"Major\",\n  \"ruleSpecification\": \"RSPEC-1172\",\n  \"sqKey\": \"S1172\"\n}"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S121_java.html",
    "content": "<h2>VJ update: IDE自动生成的方法如equals忽略</h2>\n<p>While not technically incorrect, the omission of curly braces can be misleading, and may lead to the introduction of errors during maintenance.</p>\n<h2>Noncompliant Code Example</h2>\n<pre>\nif (condition)  // Noncompliant\n  executeSomething();\n</pre>\n<h2>Compliant Solution</h2>\n<pre>\nif (condition) {\n  executeSomething();\n}\n</pre>\n<h2>See</h2>\n<ul>\n  <li> MISRA C:2004, 14.8 - The statement forming the body of a switch, while, do ... while or for statement shall be a compound statement </li>\n  <li> 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\n  compound statement, or another if statement </li>\n  <li> 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 </li>\n  <li> 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\n  compound statement, or another if statement </li>\n  <li> MISRA C:2012, 15.6 - The body of an iteration-statement or a selection-statement shall be a compound-statement </li>\n  <li> <a href=\"https://www.securecoding.cert.org/confluence/x/1QGMAg\">CERT, EXP19-C.</a> - Use braces for the body of an if, for, or while statement\n  </li>\n  <li> <a href=\"https://www.securecoding.cert.org/confluence/x/3wHEAw\">CERT, EXP52-J.</a> - Use braces for the body of an if, for, or while statement\n  </li>\n</ul>\n\n"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S121_java.json",
    "content": "{\n  \"title\": \"VJ: Control structures should use curly braces\",\n  \"type\": \"CODE_SMELL\",\n  \"status\": \"ready\",\n  \"remediation\": {\n    \"func\": \"Constant\\/Issue\",\n    \"constantCost\": \"2min\"\n  },\n  \"tags\": [\n    \"misra\",\n    \"cert\",\n    \"pitfall\"\n  ],\n  \"standards\": [\n    \"CERT\"\n  ],\n  \"defaultSeverity\": \"Major\",\n  \"ruleSpecification\": \"RSPEC-121\",\n  \"sqKey\": \"S121\"\n}\n"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1291_java.html",
    "content": "<h2>VJ update: 忽略在异常捕获语句和System.out后的NOSONAR</h2>\n<p>Any issue to quality rule can be deactivated with the <code>NOSONAR</code> marker. This marker is pretty useful to exclude false-positive results\nbut it can also be used abusively to hide real quality flaws.</p>\n<p>This rule raises an issue when <code>NOSONAR</code> is used.</p>"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1291_java.json",
    "content": "{\n  \"title\": \"VJ: Track uses of \\\"NOSONAR\\\" comments\",\n  \"type\": \"CODE_SMELL\",\n  \"status\": \"ready\",\n  \"remediation\": {\n    \"func\": \"Constant\\/Issue\",\n    \"constantCost\": \"1min\"\n  },\n  \"tags\": [\n    \"bad-practice\"\n  ],\n  \"defaultSeverity\": \"Info\",\n  \"ruleSpecification\": \"RSPEC-1291\",\n  \"sqKey\": \"S1291\"\n}"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1313_java.html",
    "content": "<h2>VJ update: 忽略\"127.0.0.1\"</h2>\n<p>Hardcoding an IP address into source code is a bad idea for several reasons:</p>\n<ul>\n  <li> a recompile is required if the address changes </li>\n  <li> it forces the same address to be used in every environment (dev, sys, qa, prod) </li>\n  <li> it places the responsibility of setting the value to use in production on the shoulders of the developer </li>\n  <li> it allows attackers to decompile the code and thereby discover a potentially sensitive address </li>\n</ul>\n<h2>Noncompliant Code Example</h2>\n<pre>\nString ip = \"200.112.44.55\";\nSocket socket = new Socket(ip, 6667);\n</pre>\n<h2>Compliant Solution</h2>\n<pre>\nString ip = System.getProperty(\"myapplication.ip\");\nSocket socket = new Socket(ip, 6667);\n</pre>\n<h2>See</h2>\n<ul>\n  <li> <a href=\"https://www.securecoding.cert.org/confluence/x/qQCHAQ\">CERT, MSC03-J.</a> - Never hard code sensitive information </li>\n</ul>"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S1313_java.json",
    "content": "{\n  \"title\": \"VJ: IP addresses should not be hardcoded\",\n  \"type\": \"VULNERABILITY\",\n  \"status\": \"ready\",\n  \"remediation\": {\n    \"func\": \"Constant\\/Issue\",\n    \"constantCost\": \"30min\"\n  },\n  \"tags\": [\n    \"cert\"\n  ],\n  \"standards\": [\n    \"CERT\"\n  ],\n  \"defaultSeverity\": \"Minor\",\n  \"ruleSpecification\": \"RSPEC-1313\",\n  \"sqKey\": \"S1313\"\n}"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S864_java.html",
    "content": "<h2>VJ update: 三目运算符中的简单表达式可以不要括号</h2>\n<p>The rules of operator precedence are complicated and can lead to errors. For this reason, parentheses should be used for clarification in complex\nstatements. However, this does not mean that parentheses should be gratuitously added around every operation. </p>\n<p>This rule raises issues when <code>&amp;&amp;</code> and <code>||</code> are used in combination, when assignment and equality or relational\noperators are used in together in a condition, and for other operator combinations according to the following table:</p>\n<table>\n  <tbody>\n    <tr>\n      <td> </td>\n      <td><code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>%</code></td>\n      <td><code>&lt;&lt;</code>, <code>&gt;&gt;</code>, <code>&gt;&gt;&gt;</code></td>\n      <td><code>&amp;</code></td>\n      <td><code>^</code></td>\n      <td> <code>|</code> </td>\n    </tr>\n    <tr>\n      <td><code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>%</code></td>\n      <td> </td>\n      <td>x</td>\n      <td>x</td>\n      <td>x</td>\n      <td>x</td>\n    </tr>\n    <tr>\n      <td><code>&lt;&lt;</code>, <code>&gt;&gt;</code>, <code>&gt;&gt;&gt;</code></td>\n      <td>x</td>\n      <td> </td>\n      <td>x</td>\n      <td>x</td>\n      <td>x</td>\n    </tr>\n    <tr>\n      <td><code>&amp;</code></td>\n      <td>x</td>\n      <td>x</td>\n      <td> </td>\n      <td>x</td>\n      <td>x</td>\n    </tr>\n    <tr>\n      <td><code>^</code></td>\n      <td>x</td>\n      <td>x</td>\n      <td>x</td>\n      <td> </td>\n      <td>x</td>\n    </tr>\n    <tr>\n      <td> <code>|</code> </td>\n      <td>x</td>\n      <td>x</td>\n      <td>x</td>\n      <td>x</td>\n      <td> </td>\n    </tr>\n  </tbody>\n</table>\n<h2>Noncompliant Code Example</h2>\n<pre>\nx = a + b - c;\nx = a + 1 &lt;&lt; b;  // Noncompliant\n\nif ( a &gt; b || c &lt; d || a == d) {...}\nif ( a &gt; b &amp;&amp; c &lt; d || a == b) {...}  // Noncompliant\nif (a = f(b,c) == 1) { ... } // Noncompliant; == evaluated first\n</pre>\n<h2>Compliant Solution</h2>\n<pre>\nx = a + b - c;\nx = (a + 1) &lt;&lt; b;\n\nif ( a &gt; b || c &lt; d || a == d) {...}\nif ( (a &gt; b &amp;&amp; c &lt; d) || a == b) {...}\nif ( (a = f(b,c)) == 1) { ... }\n</pre>\n<h2>See</h2>\n<ul>\n  <li> MISRA C:2004, 12.1 - Limited dependence should be placed on C's operator precedence rules in expressions </li>\n  <li> MISRA C:2004, 12.2 - The value of an expression shall be the same under any order of evaluation that the standard permits. </li>\n  <li> MISRA C:2004, 12.5 - The operands of a logical &amp;&amp; or || shall be primary-expressions. </li>\n  <li> MISRA C++:2008, 5-0-1 - The value of an expression shall be the same under any order of evaluation that the standard permits. </li>\n  <li> MISRA C++:2008, 5-0-2 - Limited dependence should be placed on C++ operator precedence rules in expressions </li>\n  <li> MISRA C++:2008, 5-2-1 - Each operand of a logical &amp;&amp; or || shall be a postfix-expression. </li>\n  <li> MISRA C:2012, 12.1 - The precedence of operators within expressions should be made explicit </li>\n  <li> <a href=\"https://www.securecoding.cert.org/confluence/x/_wI\">CERT, EXP00-C.</a> - Use parentheses for precedence of operation </li>\n  <li> <a href=\"https://www.securecoding.cert.org/confluence/x/VoAyAQ\">CERT, EXP00-CPP.</a> - Use parentheses for precedence of operation </li>\n  <li> <a href=\"https://www.securecoding.cert.org/confluence/x/9wHEAw\">CERT, EXP53-J.</a> - Use parentheses for precedence of operation </li>\n  <li> <a href=\"http://cwe.mitre.org/data/definitions/783.html\">MITRE, CWE-783</a> - Operator Precedence Logic Error </li>\n</ul>\n\n"
  },
  {
    "path": "standard/sonar-vj/src/main/resources/com/vip/java/rules/S864_java.json",
    "content": "{\n  \"title\": \"VJ: Limited dependence should be placed on operator precedence rules in expressions\",\n  \"type\": \"CODE_SMELL\",\n  \"status\": \"ready\",\n  \"remediation\": {\n    \"func\": \"Constant\\/Issue\",\n    \"constantCost\": \"2min\"\n  },\n  \"tags\": [\n    \"cwe\",\n    \"misra\",\n    \"cert\"\n  ],\n  \"standards\": [\n    \"CWE\",\n    \"CERT\"\n  ],\n  \"defaultSeverity\": \"Major\",\n  \"ruleSpecification\": \"RSPEC-864\",\n  \"sqKey\": \"S864\"\n}\n"
  },
  {
    "path": "vjdump/README.md",
    "content": "# 1. 概述\n\nVJDump是线上JVM数据紧急收集脚本。 \n\n它可以在紧急场景下（比如马上要对进程进行重启），一键收集jstack、jmap以及GC日志等相关信息，并以zip包保存(默认在目录`/tmp/vjtools/vjdump`下)，保证在紧急情况下仍能收集足够的问题排查信息，减轻运维团队的工作量，以及与开发团队的沟通成本。\n\n收集数据包括：\n* thread dump数据：`jstack -l $PID`\n* vjtop JVM概况及繁忙线程：`vjtop.sh -n 1 $PID` (需要将vjtop.sh 加入用户的PATH变量中)\n* jmap histo 堆对象统计数据：`jmap -histo $PID` & `jmap -histo:live $PID`\n* GC日志(如果JVM有设定GC日志输出)\n* heap dump数据（需指定--liveheap开启）：`jmap -dump:live,format=b,file=${DUMP_FILE} $PID`\n\n# 2. 下载\n\n[vjdump.sh](https://raw.githubusercontent.com/vipshop/vjtools/master/vjdump/vjdump.sh)\n\n# 3. 快速入门\n\n以目标JVM相同用户或root用户运行脚本：\n\n```shell\n\n# 对指定的进程PID进行急诊\nvjdump.sh $pid\n\n\n# 额外收集heap dump信息（jmap -dump:live的信息）\nvjdump.sh --liveheap $pid\n\n```\n\n\n在收集过程中，某些命令如`jmap -histo:live $PID` 会造成JVM停顿，因此仅用于紧急情况或已摘流量的情况。为了避免连续停顿，在每条会造成停顿的收集指令之间，默认插入了1秒的执行间隔。\n"
  },
  {
    "path": "vjdump/README_EN.md",
    "content": "# VJDump\nVJDump comes as a handy script for collecting diagnostic data for JVM during urgent failures to\n**allow for complete analysis later**.\n\n# 1.Introduction\nWhen major system failures occur for reasons not yet known, rebooting may be the only option to ease user complaints.\nThere is so little time that even an experienced system admin might forget to pick up all he needs for offline analysis. \n\nVJDump script is written to make us eaiser, which just packs outputs from T\njstack, jmap, gc logs into the form of a single zip file under `/tmp/vjtools/vjdump`.\n\n**[Important]**: Commands like jstack and jmap DO cause stop-of-the-world of the target app. Make sure the target app \nis isolated from user access before you run this full check in production.\n\nItems to be collected by VJDump are:\n* thread dump via `jstack -l $PID`\n* vjtop JVM overview and busy threads snapshot via  `vjtop.sh -n 1 -d 3 $PID` (enabled when you have our vjtop installed and \nhave the folder of vjtop.sh appended to the PATH environment variable)\n* jmap histo object statistics via`jmap -histo $PID` & `jmap -histo:live $PID`\n* GC logs if available\n* heap dump (which can be optionally switched on via --liveheap)：`jmap -dump:live,format=b,file=${DUMP_FILE} $PID`\n\n# 2. Download\n[vjdump.sh](https://raw.githubusercontent.com/vipshop/vjtools/master/vjdump/vjdump.sh)\n\n# 3. Getting Started\nUse the following commands under **the same user who started the target process** and collect results in `/tmp/vjtools/vjdump`.\n\n```shell\n\n# collect dignostics for target $pid\nvjdump.sh $pid\n\n\n# include heap dump also via jmap -dump:live, might take longer\nvjdump.sh --liveheap $pid\n\n```\n"
  },
  {
    "path": "vjdump/vjdump.sh",
    "content": "#!/bin/bash\n\nUSAGE()\n{\n  echo \"usage: $0 [--liveheap][-nz|--nozip][-i|--interval] <pid>\"\n}\n\nif [ $# -lt 1 ]; then\n  USAGE\n  exit -1\nfi\n\nBASEDIR=/tmp/vjtools\nLOGDIR=${BASEDIR}/vjdump\nSLEEP_TIME=1\nCLOSE_COMPRESS=0\nNEED_HEAP_DUMP=0\nPID=\"$1\"\n\nwhile true; do\n  case \"$1\" in\n    -i|--interval) SLEEP_TIME=\"$2\"; PID=\"$3\"; shift 1;;\n    -nz|--nozip) CLOSE_COMPRESS=1; PID=\"$2\"; shift;;\n    --liveheap) NEED_HEAP_DUMP=1; PID=\"$2\"; shift;;\n    *) break;;\n  esac\ndone\n\nCMD=\"$1\"\nshift\n\nSTART()\n{\n  if [[ x\"$PID\" == x ]]; then\n     echo -e \"The pid is empty, please enter pid\".\n     exit -1\n  else \n     echo -e \"The pid is ${PID}\"\n  fi\n\n  # try to find $JAVA_HOME if not set\n  if [ -z \"$JAVA_HOME\" ] ; then\n      JAVA_HOME=`readlink -f \\`which java 2>/dev/null\\` 2>/dev/null | \\\n      sed 's/\\jre\\/bin\\/java//' | sed 's/\\/bin\\/java//'`\n  fi\n\n  if [ ! -f \"$JAVA_HOME/bin/jstack\" ] ; then\n      echo -e \"\\033[31m\\$JAVA_HOME not found. please export JAVA_HOME manually.\\033[0m\"\n      exit -1\n  fi\n\n\n  # clean all history logs\n  rm -rf ${LOGDIR}/*.log ${LOGDIR}/*jmap_dump_live-*.bin\n  mkdir -p ${LOGDIR}\n    \n  DATE=$(date \"+%Y%m%d%H%M%S\")\n  \n  echo -e \"\\033[34m$(date '+%Y-%m-%d %H:%M:%S') vjdump begin. command interval is ${SLEEP_TIME}s.\\033[0m\"\n  \n  # jstack\n  echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to process jstack.\"\n  JSTACK_LOG=${LOGDIR}/jstack-${PID}-${DATE}.log\n  ${JAVA_HOME}/bin/jstack -l $PID > ${JSTACK_LOG}\n  if [[ $? != 0 ]]; then\n    echo -e \"\\033[31mprocess jstack error.\\033[0m\"\n  fi\n  echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to process jstack.\"\n  sleep ${SLEEP_TIME}\n  \n  # vjtop\n  VJTOP_SCRIPT=vjtop.sh\n  which $VJTOP_SCRIPT 2>/dev/null\n  if [[ $? == 0 ]]; then\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to process vjtop.\"\n    echo -e \"It will take 3 seconds, please wait.\"\n    VJTOP_LOG=${LOGDIR}/vjtop-${PID}-${DATE}.log\n    $VJTOP_SCRIPT -n 3 -d 1 $PID > ${VJTOP_LOG}\n    if [[ $? != 0 ]]; then\n      echo -e \"\\033[31mprocess vjtop error.\\033[0m\"\n    fi\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to process vjtop.\"\n  else\n    # no vjtop, use other replacement\n    # jinfo -flags $PID\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to process jinfo -flags.\"\n    JINFO_FLAGS_LOG=${LOGDIR}/jinfo-flags-${PID}-${DATE}.log\n    ${JAVA_HOME}/bin/jinfo -flags $PID 1>${JINFO_FLAGS_LOG} 2>&1\n    if [[ $? != 0 ]]; then\n      echo -e \"\\033[31mprocess jinfo -flags error.\\033[0m\"\n    fi\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to process jinfo -flags.\"\n  \n    #jmap -heap\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -heap.\"\n    JMAP_HEAP_LOG=${LOGDIR}/jmap_heap-${PID}-${DATE}.log\n    ${JAVA_HOME}/bin/jmap -heap $PID > ${JMAP_HEAP_LOG}\n    if [[ $? != 0 ]]; then\n      echo -e \"\\033[31mprocess jmap -heap error.\\033[0m\"\n    fi\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -heap.\"\n  fi\n  \n  # jmap -histo\n  echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -histo.\"\n  JMAP_HISTO_LOG=${LOGDIR}/jmap_histo-${PID}-${DATE}.log\n  ${JAVA_HOME}/bin/jmap -histo $PID > ${JMAP_HISTO_LOG}\n  if [[ $? != 0 ]]; then\n    echo -e \"\\033[31mprocess jmap -histo error.\\033[0m\"\n  fi\n  echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -histo.\"\n  sleep ${SLEEP_TIME}\n  \n  # jmap -histo:live\n  echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -histo:live.\"\n  JMAP_HISTO_LIVE_LOG=${LOGDIR}/jmap_histo_live-${PID}-${DATE}.log\n  ${JAVA_HOME}/bin/jmap -histo:live $PID > ${JMAP_HISTO_LIVE_LOG}\n  if [[ $? != 0 ]]; then\n    echo -e \"\\033[31mprocess jmap -histo:live error.\\033[0m\"\n  fi\n  echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -histo:live.\"\n  sleep ${SLEEP_TIME}\n  \n  # jmap -dump:live\n  if [[ $NEED_HEAP_DUMP == 1 ]]; then\n    JMAP_DUMP_FILE=${LOGDIR}/jmap_dump_live-${PID}-${DATE}.bin\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -dump:live.\"\n    ${JAVA_HOME}/bin/jmap -dump:live,format=b,file=${JMAP_DUMP_FILE} $PID\n    if [[ $? != 0 ]]; then\n      echo -e \"\\033[31mprocess jmap -dump:live error.\\033[0m\"\n    fi\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -dump:live.\"\n     \n    sleep ${SLEEP_TIME}\n  fi\n  \n\n  # gc log\n  echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to process gc log.\"\n  GCLOG=$(strings /proc/${PID}/cmdline |grep '\\-Xloggc' |cut -d : -f 2)\n  if [[ x\"$GCLOG\" == x ]]; then\n    echo -e \"No GC log existing.\"\n  else\n    # \"\\cp\" means unalias cp, it can cover files without prompting\n    \\cp -rf $GCLOG ${LOGDIR}/\n    if [[ $? != 0 ]]; then\n      echo -e \"copy gc log error.\"\n    fi\n  fi\n  echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to process gc log.\"\n\n  # packaging\n  if [[ $CLOSE_COMPRESS == 1 ]]; then\n    echo -e \"The zip option is closed, no zip package will be generated.\"\n  else\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to zip all files.\"\n    # zip files without heap dump \n    if [ -x \"$(command -v zip)\" ]; then\n        COMPRESS_FILE=${BASEDIR}/vjdump-${PID}-${DATE}.zip\n        zip -j ${COMPRESS_FILE} ${LOGDIR}/*.log\n    else\n        COMPRESS_FILE=${BASEDIR}/vjdump-${PID}-${DATE}.tar.gz\n        (cd ${LOGDIR} && tar -zcvf ${COMPRESS_FILE} *.log)\n    fi\n\n    if [[ $? != 0 ]]; then\n      echo -e \"\\033[31mzip files error.\\033[0m\"\n    else\n      echo -e \"zip files success, the zip file is \\033[34m${ZIP_FILE}\\033[0m\"\n    fi\n    echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to zip all files.\"\n    \n    if [[ $NEED_HEAP_DUMP == 1 ]]; then\n      # compress all files\n      echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Begin to zip files which include dump file.\"\n\n      if [ -x \"$(command -v zip)\" ]; then\n        COMPRESS_FILE_WITH_HEAP_DUMP=${BASEDIR}/vjdump-with-heap-${PID}-${DATE}.zip\n        zip -j ${COMPRESS_FILE_WITH_HEAP_DUMP} ${LOGDIR}/*.log ${JMAP_DUMP_FILE}\n      else\n        COMPRESS_FILE_WITH_HEAP_DUMP=${BASEDIR}/vjdump-with-heap-${PID}-${DATE}.tar.gz\n        (cd ${LOGDIR} && tar -zcvf ${COMPRESS_FILE_WITH_HEAP_DUMP} *.log *.bin)\n      fi\n\n      if [[ $? != 0 ]]; then\n        echo -e \"\\033[31mzip files which include dump file error.\\033[0m\"\n      else\n        echo -e \"zip files which include dump file success, the zip path is \\033[34m${ZIP_FILE_WITH_HEAP_DUMP}\\033[0m\"\n      fi\n      echo -e \"$(date '+%Y-%m-%d %H:%M:%S') Finish to zip files which include dump file.\"\n    fi\n  fi\n  echo -e \"\\033[34m$(date '+%Y-%m-%d %H:%M:%S') vjdump finish. \\033[0m\"\n}\n\ncase \"$CMD\" in\n  help) USAGE;;\n  *) START;;\nesac\n"
  },
  {
    "path": "vjkit/README.md",
    "content": "# 1. Overview\n\n唯品会Java开发基础类库，综合各门各派众多开源类库的精华而成， 让开发人员避免底层代码的重复开发，默认就拥有最佳实践，尤其在性能的方面。\n\n\n综合众多开源类库的精华而成， 让开发人员避免底层代码的重复开发，默认就拥有最佳实践，尤其在性能的方面。\n\n针对“基础，文本，数字，日期，文件，集合，并发，反射，日志脱敏”这些开发人员的日常，VJKit做了两件事情：\n\n一是对[Guava](https://github.com/google/guava) 与[Common Lang](https://github.com/apache/commons-lang)中最常用的API的提炼归类，避免了大家直面茫茫多的API(但有些工具类如Guava Cache还是建议直接使用，详见[直用三方工具类](docs/direct_3rd.md) )\n\n二是对各门各派的精华的借鉴移植：比如一些大项目的附送基础库： [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)\n\n\n具体使用文档请在IDE中阅读JavaDoc，以及对应的单元测试写法。\n\n* [日志脱敏](docs/data_masking.md)\n\n\n# 2. Usage\n\nMaven : \n```\n<dependency>\n\t<groupId>com.vip.vjtools</groupId>\n\t<artifactId>vjkit</artifactId>\n\t<version>1.0.8</version>\n</dependency>\n```\n\nDownload: [![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\")\n\n\n# 3. Dependency\n\n要求JDK 7.0及以上版本。\n\n| Project | Version | Optional|\n|--- | --- | --- |\n|[Guava](https://github.com/google/guava) | 20.0 ||\n|[Apache Common Lang](https://github.com/apache/commons-lang) | 3.7 ||\n|[Slf4j](https://www.slf4j.org) | 1.7.25 ||\n|[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) |\n\n如果使用Optional的依赖，请参考pom文件在业务项目自行引入\n\n# 4. Sonar Status\n\n[https://sonarcloud.io/dashboard?id=com.vip.vjtools:vjkit](https://sonarcloud.io/dashboard?id=com.vip.vjtools:vjkit)\n\n(sonarcloud use \"Sonar Way\")\n\n\n\n"
  },
  {
    "path": "vjkit/docs/data_masking.md",
    "content": "# 1.简介\nvjmask是唯品会的日志脱敏组件，在业务中广泛使用。基于性能和通用性考虑，采用了现在的方案。让使用方用最少的配置和代码，就可以轻松实现敏感信息过滤。\n\n# 2.使用\n## 2.1 依赖\n\n```\n<dependency>\n    <groupId>com.vip.vjtools</groupId>\n    <artifactId>vjkit</artifactId>\n    <version>${version}</version>\n</dependency>\n```\n\n## 2.2 最佳实践\n脱敏组件支持对单个字符串进行脱敏，也支持toJSON和toString的序列化脱敏\n\n```\nimport com.vip.vjtools.vjkit.datamasking.DataMask;\n\n// 单个字符串，按中文姓名规则脱敏\nlogger.info(\"some sensitive info:{}\",DataMask.mask(name,SensitiveType.Name));\n//单个字符串，按默认方式脱敏，结果为 \"t***\";\nlogger.info(\"some sensitive info:{}\",DataMask.mask(\"test\")); \n \n//对象json序列化脱敏\nlogger.info(\"some sensitive object:{}\",DataMask.toJSONString(obj));\n//对象toString序列化脱敏\nlogger.info(\"some sensitive object:{}\",DataMask.toString(obj));\n```\n\n对类的序列化脱敏，先要对相关敏感字段标记 @Sensitive，支持字符串类型的字段，包括String、String[]和Collection<String>. 嵌套类的敏感字段也是可以识别的。\n\n```\npublic class User{\n  @Sensitive(type = SensitiveType.Phone)  type参考2.3\n  private String phone;\n}\n```\n\n## 2.3 脱敏规则\n我们已经定义实现了常用的脱敏类型，可以直接使用\n\n敏感信息 | 脱敏要求 | 样例 | SensitiveType\n---|---|---|---\n中文姓名 | 三个字及以下，只显示最后一个字;三个字以上，显示最后两个字 | *明，****小明 | Name\n手机号/固定电话 | 只显示前三后三 | 138*****111 | Phone\n身份证号 | 显示前五个和后二个字符 | 44010************58 | IDCard\n银行卡号 | 显示前四个和后二个字符 | 6228************89 | BankCard\n地址 | 保留前9个字符 | 广东省广州市荔湾区****** | Address\n电子邮箱 | 只显示前一后一及@和后面的内容 | a***b@abc.com | Email\n验证码 | 只显示前一后一 | a**b | Captcha\n护照/军官号 | 只显示前二后二 | EI****64 | Passport\n账号 | 只显示前一后一 | a****b | Account\n密码 | 不显示任意字符 | ********* | Password\n散列 | sha1(source+salt) ，可以通过DataMask.setSalt设置salt，用于希望在日志系统中精确找回这条日志的场景，可以再自己hash获得hash值 | 6b76e070c5b5d1b889295506faa8b98e97da7e87 | Hash\n\n\n# 3.详细介绍\n## 3.1 Annotation标注\n要使用序列化脱敏，先要对敏感字段标注@Sensitive，注意，只对字符串相关的类型字段会生效。\n\n```\n//根据类型来标注\n@Sensitive(type = SensitiveType.Name)\nprivate String name;\n \n//也可以自定义掩码规则\n@Sensitive(keepChars = 2) //首尾保留2个字符串，如果keepChars = {1,3} 表示头部保留1个字符，尾部保留3个字符\nprivate String[] phone;\n \n// 散列的方式\n@Sensitive(type = SensitiveType.Hash)\nprivate String hash;\n \n//默认的方式,只保留第一个字符串\n@Sensitive\nprivate List<String> account;\n```\n\n## 3.2 映射配置\n如果你不想对一个个类字段标注@Sensitive,也可以在resource 目录下新建一个data_mask。properties,添加敏感字段映射\n\n```\n#SensitiveType=字段名称\nName=nickName\n```\n那么nickName字段即使没有标注@Sensitive ，在序列化的时候，也会自动脱敏，按照SentiveType.Name的方式脱敏。 \n\n==注意，标注@Sensitive优先级高于文件配置==\n\n组件中已经默认配置映射了以下配置，sys_data_mask.properties\n\n```\nName=chineseName,userName\nPhone=phone,phoneNum,mobile,tel,telephone\nIDCard=IDCard,IdNo\nBankCard=bankCard\nAddress=address,addr\nEmail=mail,email\nCaptcha=captcha\nPassport=passport\nAccount=account\nPassword=password,passwd\n```\n\n# 4.Benchmark\n我们使用JMH基准测试，测试了2个使用场景。\n## 4.1 场景1\n通用场景，场景1包括了10个左右的Sensitive字段。toJSONString() 平均耗时 0.003ms，toString() 平均耗时0.005ms。\n\n\n```\nBenchmark                      Mode    Score    Error  Units\nDataMaskTest.testJson(不脱敏)   avgt    0.001 ±  0.001  ms/op\nDataMaskTest.testMaskJson      avgt    0.003 ±  0.002  ms/op\nDataMaskTest.testMaskToString  avgt    0.005 ±  0.003  ms/op\nDataMaskTest.testToString      avgt    ≈ 10⁻⁴          ms/op\n```\n\n## 4.2 场景2\n极限场景，场景2测试超大的类序列化，有160多个字段需要脱敏，3层嵌套。toJSONString() 平均耗时 0.064ms，toString() 平均耗时0.098ms。\n```\nBenchmark                      Mode  Score    Error  Units\nDataMaskTest.testJson          avgt  0.043 ±  0.025  ms/op\nDataMaskTest.testMaskJson      avgt  0.064 ±  0.002  ms/op\nDataMaskTest.testMaskToString  avgt  0.098 ±  0.095  ms/op\nDataMaskTest.testToString      avgt  ≈ 10⁻⁴          ms/op\n```\n\n## 4.3 结论\n脱敏处理的性能和脱敏字段的数量相关。\n\na.如果需要脱敏的字段不多，时间在0.001ms~0.009ms范围内，对于业务来说，不会造成太多额外开销，在可控范围内。\n\nb.如果需要脱敏的字段特别多，建议要性能测试评估\n\nc.toString() 性能比toJson() 要慢，建议是优先使用toJson()\n"
  },
  {
    "path": "vjkit/docs/direct_3rd.md",
    "content": "# 建议直接使用的第三方类\n\n\n| Project | Class | \n|--- | --- |\n|Common Lang | StringUtils |\n| | Validate|\n|Guava | Cache |\n| | Ordering |\n|JDK|Arrays|\n| |Collections|\n"
  },
  {
    "path": "vjkit/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<groupId>com.vip.vjtools</groupId>\n\t<artifactId>vjkit</artifactId>\n\t<version>1.0.9-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\t<name>vjkit</name>\n\t<description>VIP's core java libraries</description>\n\n\t<properties>\n\t\t<guava.version>20.0</guava.version>\n\t\t<commons-lang3.version>3.8.1</commons-lang3.version>\n\t\t<slf4j.version>1.7.25</slf4j.version>\n\t\t<logback.version>1.1.11</logback.version>\n\t\t<dozer.version>5.5.1</dozer.version>\n\t\t<jackson.version>2.9.10.4</jackson.version>\n\t\t<junit.version>4.12</junit.version>\n\t\t<assertj.version>2.6.0</assertj.version>\n\t\t<mockito.version>2.18.3</mockito.version>\n\t\t<fastjson.version>1.2.70</fastjson.version>\n\n\t\t<!-- Plugin的属性 -->\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<java.version>1.7</java.version>\n\t\t<maven.compiler.source>${java.version}</maven.compiler.source>\n\t\t<maven.compiler.target>${java.version}</maven.compiler.target>\n\t</properties>\n\n\t<!-- 尽量少的引入依赖包，方便单独重用本模块 -->\n\t<dependencies>\n\n\t\t<!-- Util -->\n\t\t<dependency>\n\t\t\t<groupId>com.google.guava</groupId>\n\t\t\t<artifactId>guava</artifactId>\n\t\t\t<version>${guava.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.commons</groupId>\n\t\t\t<artifactId>commons-lang3</artifactId>\n\t\t\t<version>${commons-lang3.version}</version>\n\t\t</dependency>\n\n\t\t<!-- Log -->\n\t\t<dependency>\n\t\t\t<groupId>org.slf4j</groupId>\n\t\t\t<artifactId>slf4j-api</artifactId>\n\t\t\t<version>${slf4j.version}</version>\n\t\t</dependency>\n\n\t\t<!-- optional for BeanMapper -->\n\t\t<dependency>\n\t\t\t<groupId>net.sf.dozer</groupId>\n\t\t\t<artifactId>dozer</artifactId>\n\t\t\t<version>${dozer.version}</version>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\n\t\t<!-- optional for JsonMapper -->\n\t\t<dependency>\n\t\t\t<groupId>com.fasterxml.jackson.core</groupId>\n\t\t\t<artifactId>jackson-databind</artifactId>\n\t\t\t<version>${jackson.version}</version>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\n        <!-- optional for DataMask -->\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>fastjson</artifactId>\n            <version>${fastjson.version}</version>\n            <optional>true</optional>\n        </dependency>\n\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-classic</artifactId>\n            <version>${logback.version}</version>\n            <optional>true</optional>\n        </dependency>\n\n\t\t<!-- Test -->\n\t\t<dependency>\n\t\t\t<groupId>junit</groupId>\n\t\t\t<artifactId>junit</artifactId>\n\t\t\t<version>${junit.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.assertj</groupId>\n\t\t\t<artifactId>assertj-core</artifactId>\n\t\t\t<version>${assertj.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.mockito</groupId>\n\t\t\t<artifactId>mockito-core</artifactId>\n\t\t\t<version>${mockito.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<!-- only for XMLMapper test -->\n\t\t<dependency>\n\t\t\t<groupId>dom4j</groupId>\n\t\t\t<artifactId>dom4j</artifactId>\n\t\t\t<version>1.6.1</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<!-- only for XMLMapper test -->\n\t\t<dependency>\n\t\t\t<groupId>jaxen</groupId>\n\t\t\t<artifactId>jaxen</artifactId>\n\t\t\t<version>1.1.6</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.skyscreamer</groupId>\n\t\t\t<artifactId>jsonassert</artifactId>\n\t\t\t<version>1.5.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>release</id>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<!-- javadoc attach plugin -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-javadoc-plugin</artifactId>\n\t\t\t\t\t\t<version>2.10.4</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<failOnError>false</failOnError>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-javadocs</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<!-- source attach plugin -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-source-plugin</artifactId>\n\t\t\t\t\t\t<version>3.0.1</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-sources</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-release-plugin</artifactId>\n\t\t\t\t\t\t<version>2.5.3</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<tagNameFormat>v.@{project.version}</tagNameFormat>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</plugin>\n\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-gpg-plugin</artifactId>\n\t\t\t\t\t\t<version>1.6</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>sign-artifacts</id>\n\t\t\t\t\t\t\t\t<phase>verify</phase>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>sign</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\n\t\t<profile>\n\t\t\t<id>sonar</id>\n\t\t\t<properties>\n\t\t\t\t<sonar.java.source>1.7</sonar.java.source>\n\t\t\t\t<sonar.exclusions>**/primitive/**/*.java,**/jsr166e/**/*.java,**/SortedArrayList.java,**/WildcardMatcher.java,**/CsvUtil.java</sonar.exclusions>\n\t\t\t</properties>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.jacoco</groupId>\n\t\t\t\t\t\t<artifactId>jacoco-maven-plugin</artifactId>\n\t\t\t\t\t\t<version>0.7.2.201409121644</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<append>true</append>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>agent-for-ut</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.sonarsource.scanner.maven</groupId>\n\t\t\t\t\t\t<artifactId>sonar-maven-plugin</artifactId>\n\t\t\t\t\t\t<version>3.4.0.905</version>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\n\t\t<profile>\n\t\t\t<id>jdk9</id>\n\t\t\t<activation>\n\t\t\t\t<jdk>9</jdk>\n\t\t\t</activation>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<!-- only for XMLMapper test -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-surefire-plugin</artifactId>\n\t\t\t\t\t\t<version>2.22.0</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<argLine>--add-modules java.xml.bind</argLine>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t</profiles>\n\n\t<distributionManagement>\n\t\t<snapshotRepository>\n\t\t\t<id>sonatype-nexus-snapshots</id>\n\t\t\t<url>https://oss.sonatype.org/content/repositories/snapshots</url>\n\t\t</snapshotRepository>\n\t\t<repository>\n\t\t\t<id>sonatype-nexus-releases</id>\n\t\t\t<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>\n\t\t</repository>\n\t</distributionManagement>\n\n\t<url>https://github.com/vipshop/vjtools</url>\n\n\t<licenses>\n\t\t<license>\n\t\t\t<name>Apache License 2.0</name>\n\t\t\t<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n\t\t\t<distribution>repo</distribution>\n\t\t</license>\n\t</licenses>\n\n\t<scm>\n\t\t<connection>scm:git:https://github.com/vipshop/vjtools.git</connection>\n\t\t<developerConnection>scm:git:https://github.com/vipshop/vjtools.git</developerConnection>\n\t\t<url>https://github.com/vipshop/vjtools</url>\n\t  <tag>v.1.0.2</tag>\n  </scm>\n\n\t<developers>\n\t\t<!--not noly me, write a name here just for sonatype requirement -->\n\t\t<developer>\n\t\t\t<id>calvin</id>\n\t\t\t<name>Calvin Xiao</name>\n\t\t\t<email>calvin.xiao at vipshop.com</email>\n\t\t\t<roles>\n\t\t\t\t<role>developer</role>\n\t\t\t</roles>\n\t\t\t<timezone>+8</timezone>\n\t\t</developer>\n\t</developers>\n</project>\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/BooleanUtil.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport org.apache.commons.lang3.BooleanUtils;\n\n/**\n * 1. 从String(true/false, yes/no)，转换为Boolean或boolean\n * \n * 2. 逻辑运算：取反，多个boolean的and,or 计算\n * \n * 封装 {@code org.apache.commons.lang3.BooleanUtils}\n */\npublic class BooleanUtil {\n\n\t/**\n\t * 使用标准JDK，只分析是否忽略大小写的\"true\", str为空时返回false\n\t */\n\tpublic static boolean toBoolean(String str) {\n\t\treturn Boolean.parseBoolean(str);\n\t}\n\n\t/**\n\t * 使用标准JDK，只分析是否忽略大小写的\"true\", str为空时返回null\n\t */\n\tpublic static Boolean toBooleanObject(String str) {\n\t\treturn str != null ? Boolean.valueOf(str) : null;\n\t}\n\n\t/**\n\t * 使用标准JDK，只分析是否忽略大小写的\"true\", str为空时返回defaultValue\n\t */\n\tpublic static Boolean toBooleanObject(String str, Boolean defaultValue) {\n\t\treturn str != null ? Boolean.valueOf(str) : defaultValue;\n\t}\n\n\t/**\n\t * 支持true/false, on/off, y/n, yes/no的转换, str为空或无法分析时返回null\n\t */\n\tpublic static Boolean parseGeneralString(String str) {\n\t\treturn BooleanUtils.toBooleanObject(str);\n\t}\n\n\t/**\n\t * 支持true/false,on/off, y/n, yes/no的转换, str为空或无法分析时返回defaultValue\n\t */\n\tpublic static Boolean parseGeneralString(String str, Boolean defaultValue) {\n\t\treturn BooleanUtils.toBooleanDefaultIfNull(BooleanUtils.toBooleanObject(str), defaultValue);\n\t}\n\n\t/**\n\t * 取反\n\t */\n\tpublic static boolean negate(final boolean bool) {\n\t\treturn !bool;\n\t}\n\n\t/**\n\t * 取反\n\t */\n\tpublic static Boolean negate(final Boolean bool) {\n\t\treturn BooleanUtils.negate(bool);\n\t}\n\n\t/**\n\t * 多个值的and\n\t */\n\tpublic static boolean and(final boolean... array) {\n\t\treturn BooleanUtils.and(array);\n\t}\n\n\t/**\n\t * 多个值的or\n\t */\n\tpublic static boolean or(final boolean... array) {\n\t\treturn BooleanUtils.or(array);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/EnumUtil.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport java.util.EnumSet;\n\nimport org.apache.commons.lang3.EnumUtils;\n\n/**\n * 枚举工具集\n * \n * 1. 将多个枚举值按bit与long的转换\n * \n * 2. 与String的转换\n * \n * 封装 {@code org.apache.commons.lang3.EnumUtils}\n */\npublic class EnumUtil {\n\n\t/**\n\t * 将若干个枚举值转换为long(按bits 1,2,4,8...的方式叠加)，用于使用long保存多个选项的情况.\n\t */\n\tpublic static <E extends Enum<E>> long generateBits(final Class<E> enumClass, final Iterable<? extends E> values) {\n\t\treturn EnumUtils.generateBitVector(enumClass, values);\n\t}\n\n\t/**\n\t * 将若干个枚举值转换为long(按bits 1,2,4,8...的方式叠加)，用于使用long保存多个选项的情况.\n\t */\n\tpublic static <E extends Enum<E>> long generateBits(final Class<E> enumClass, final E... values) {\n\t\treturn EnumUtils.generateBitVector(enumClass, values);\n\t}\n\n\t/**\n\t * long重新解析为若干个枚举值，用于使用long保存多个选项的情况.\n\t */\n\tpublic static <E extends Enum<E>> EnumSet<E> processBits(final Class<E> enumClass, final long value) {\n\t\treturn EnumUtils.processBitVector(enumClass, value);\n\t}\n\n\t/**\n\t * Enum转换为String\n\t */\n\tpublic static String toString(Enum e) {\n\t\treturn e.name();\n\t}\n\n\t/**\n\t * String转换为Enum\n\t */\n\tpublic static <T extends Enum<T>> T fromString(Class<T> enumClass, String value) {\n\t\treturn Enum.valueOf(enumClass, value);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/ExceptionUtil.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport java.io.PrintWriter;\nimport java.lang.reflect.UndeclaredThrowableException;\n\nimport org.apache.commons.lang3.ClassUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\n\nimport com.google.common.base.Throwables;\nimport com.vip.vjtools.vjkit.base.annotation.NotNull;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\nimport com.vip.vjtools.vjkit.base.type.UncheckedException;\nimport com.vip.vjtools.vjkit.io.type.StringBuilderWriter;\n\n/**\n * 关于异常的工具类.\n * \n * 1. Checked/Unchecked及Wrap(如ExecutionException)的转换.\n * \n * 2. 打印Exception的辅助函数. (其中一些来自Common Lang ExceptionUtils)\n * \n * 3. 查找Cause(其中一些来自Guava Throwables)\n * \n * 4. StackTrace性能优化相关，尽量使用静态异常避免异常生成时获取StackTrace(Netty)\n * \n * @see com.vip.vjtools.vjkit.base.type.CloneableException\n */\npublic class ExceptionUtil {\n\n\tprivate static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];\n\n\t///// Checked/Unchecked及Wrap(如ExecutionException)的转换/////\n\n\t/**\n\t * 将CheckedException转换为RuntimeException重新抛出, 可以减少函数签名中的CheckException定义.\n\t * \n\t * CheckedException会用UndeclaredThrowableException包裹，RunTimeException和Error则不会被转变.\n\t * \n\t * copy from Commons Lang 3.5 ExceptionUtils.\n\t * \n\t * 虽然unchecked()里已直接抛出异常，但仍然定义返回值，方便欺骗Sonar。因此本函数也改变了一下返回值\n\t * \n\t * 示例代码:\n\t * \n\t * <pre>\n\t * try{ ... }catch(Exception e){ throw unchecked(t); }\n\t * </pre>\n\t * \n\t * @see ExceptionUtils#wrapAndThrow(Throwable)\n\t */\n\tpublic static RuntimeException unchecked(@Nullable Throwable t) {\n\t\tif (t instanceof RuntimeException) {\n\t\t\tthrow (RuntimeException) t;\n\t\t}\n\t\tif (t instanceof Error) {\n\t\t\tthrow (Error) t;\n\t\t}\n\n\t\tthrow new UncheckedException(t);\n\t}\n\n\t/**\n\t * 如果是著名的包裹类，从cause中获得真正异常. 其他异常则不变.\n\t * \n\t * Future中使用的ExecutionException 与 反射时定义的InvocationTargetException， 真正的异常都封装在Cause中\n\t * \n\t * 前面 unchecked() 使用的UncheckedException同理.\n\t */\n\tpublic static Throwable unwrap(@Nullable Throwable t) {\n\t\tif (t instanceof UncheckedException || t instanceof java.util.concurrent.ExecutionException\n\t\t\t\t|| t instanceof java.lang.reflect.InvocationTargetException\n\t\t\t\t|| t instanceof UndeclaredThrowableException) {\n\t\t\treturn t.getCause();\n\t\t}\n\n\t\treturn t;\n\t}\n\n\t/**\n\t * 组合unwrap与unchecked，用于处理反射/Callable的异常\n\t */\n\tpublic static RuntimeException unwrapAndUnchecked(@Nullable Throwable t) {\n\t\tthrow unchecked(unwrap(t));\n\t}\n\n\t////// 输出内容相关 //////\n\n\t/**\n\t * 将StackTrace[]转换为String, 供Logger或e.printStackTrace()外的其他地方使用.\n\t * \n\t * 为了使用StringBuilderWriter，没有用Throwables#getStackTraceAsString(Throwable)\n\t */\n\tpublic static String stackTraceText(@NotNull Throwable t) {\n\t\tStringBuilderWriter stringWriter = new StringBuilderWriter();\n\t\tt.printStackTrace(new PrintWriter(stringWriter)); // NOSONAR\n\t\treturn stringWriter.toString();\n\t}\n\n\t/**\n\t * 拼装 短异常类名: 异常信息.\n\t * \n\t * 与Throwable.toString()相比使用了短类名\n\t * \n\t * @see ExceptionUtils#getMessage(Throwable)\n\t */\n\tpublic static String toStringWithShortName(@Nullable Throwable t) {\n\t\treturn ExceptionUtils.getMessage(t);\n\t}\n\n\t/**\n\t * 拼装 短异常类名: 异常信息 <-- RootCause的短异常类名: 异常信息\n\t */\n\tpublic static String toStringWithRootCause(@Nullable Throwable t) {\n\t\tif (t == null) {\n\t\t\treturn StringUtils.EMPTY;\n\t\t}\n\n\t\tfinal String clsName = ClassUtils.getShortClassName(t, null);\n\t\tfinal String message = StringUtils.defaultString(t.getMessage());\n\t\tThrowable cause = getRootCause(t);\n\n\t\tStringBuilder sb = new StringBuilder(128).append(clsName).append(\": \").append(message);\n\t\tif (cause != t) {\n\t\t\tsb.append(\"; <---\").append(toStringWithShortName(cause));\n\t\t}\n\n\t\treturn sb.toString();\n\t}\n\n\t////////// Cause 相关 /////////\n\n\t/**\n\t * 获取异常的Root Cause.\n\t * \n\t * 如无底层Cause, 则返回自身\n\t * \n\t * @see Throwables#getRootCause(Throwable)\n\t */\n\tpublic static Throwable getRootCause(@NotNull Throwable t) {\n\t\treturn Throwables.getRootCause(t);\n\t}\n\n\t/**\n\t * 获取某种类型的cause，如果没有则返回空\n\t * \n\t * copy from Jodd ExceptionUtil\n\t */\n\tpublic static <T extends Throwable> T findCause(@NotNull Throwable throwable, Class<T> cause) {\n\t\twhile (throwable != null) {\n\t\t\tif (throwable.getClass().equals(cause)) {\n\t\t\t\treturn (T) throwable;\n\t\t\t}\n\t\t\tthrowable = throwable.getCause();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * 判断异常是否由某些底层的异常引起.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static boolean isCausedBy(@Nullable Throwable throwable,\n\t\t\tClass<? extends Exception>... causeExceptionClasses) {\n\t\tThrowable cause = throwable;\n\n\t\twhile (cause != null) {\n\t\t\tfor (Class<? extends Exception> causeClass : causeExceptionClasses) {\n\t\t\t\tif (causeClass.isInstance(cause)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcause = cause.getCause();\n\t\t}\n\t\treturn false;\n\t}\n\t/////////// StackTrace 性能优化相关////////\n\n\t/**\n\t * copy from Netty, 为静态异常设置StackTrace.\n\t * \n\t * 对某些已知且经常抛出的异常, 不需要每次创建异常类并很消耗性能的并生成完整的StackTrace. 此时可使用静态声明的异常.\n\t * \n\t * 如果异常可能在多个地方抛出，使用本函数设置抛出的类名和方法名.\n\t * \n\t * <pre>\n\t * private static RuntimeException TIMEOUT_EXCEPTION = ExceptionUtil.setStackTrace(new RuntimeException(\"Timeout\"),\n\t * \t\tMyClass.class, \"mymethod\");\n\t * </pre>\n\t */\n\tpublic static <T extends Throwable> T setStackTrace(@NotNull T throwable, Class<?> throwClass, String throwClazz) {\n\t\tthrowable.setStackTrace(\n\t\t\t\tnew StackTraceElement[] { new StackTraceElement(throwClass.getName(), throwClazz, null, -1) });\n\t\treturn throwable;\n\t}\n\n\t/**\n\t * 清除StackTrace. 假设StackTrace已生成, 但把它打印出来也有不小的消耗.\n\t * \n\t * 如果不能控制StackTrace的生成，也不能控制它的打印端(如logger)，可用此方法暴力清除Trace.\n\t * \n\t * 但Cause链依然不能清除, 只能清除每一个Cause的StackTrace.\n\t */\n\tpublic static <T extends Throwable> T clearStackTrace(@NotNull T throwable) {\n\t\tThrowable cause = throwable;\n\t\twhile (cause != null) {\n\t\t\tcause.setStackTrace(EMPTY_STACK_TRACE);\n\t\t\tcause = cause.getCause();\n\t\t}\n\t\treturn throwable;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/MoreValidate.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 参数校验统一使用Apache Common Lang Validate, 补充一些缺少的.\n * \n * 为什么不用Guava的{@code com.google.common.base.Preconditions} , 一是少打几个字而已, 二是Validate的方法多，比如noNullElements()判断多个元素都不为空\n * \n * 目前主要参考 {@code com.google.common.math.MathPreconditions} , 补充数字为正数或非负数的校验\n * \n */\npublic class MoreValidate extends org.apache.commons.lang3.Validate {\n\n\t/**\n\t * 校验为正数则返回该数字，否则抛出异常.\n\t */\n\tpublic static int positive(@Nullable String role, int x) {\n\t\tif (x <= 0) {\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be > 0\");\n\t\t}\n\t\treturn x;\n\t}\n\n\t/**\n\t * 校验为正数则返回该数字，否则抛出异常.\n\t */\n\tpublic static Integer positive(@Nullable String role, Integer x) {\n\t\tif (x.intValue() <= 0) {\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be > 0\");\n\t\t}\n\t\treturn x;\n\t}\n\n\t/**\n\t * 校验为正数则返回该数字，否则抛出异常.\n\t */\n\tpublic static long positive(@Nullable String role, long x) {\n\t\tif (x <= 0) {\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be > 0\");\n\t\t}\n\t\treturn x;\n\t}\n\n\t/**\n\t * 校验为正数则返回该数字，否则抛出异常.\n\t */\n\tpublic static Long positive(@Nullable String role, Long x) {\n\t\tif (x.longValue() <= 0) {\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be > 0\");\n\t\t}\n\t\treturn x;\n\t}\n\n\t/**\n\t * 校验为正数则返回该数字，否则抛出异常.\n\t */\n\tpublic static double positive(@Nullable String role, double x) {\n\t\tif (!(x > 0)) { // not x < 0, to work with NaN.\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be >= 0\");\n\t\t}\n\t\treturn x;\n\t}\n\n\t/**\n\t * 校验为非负数则返回该数字，否则抛出异常.\n\t */\n\tpublic static int nonNegative(@Nullable String role, int x) {\n\t\tif (x < 0) {\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be >= 0\");\n\t\t}\n\t\treturn x;\n\t}\n\n\t/**\n\t * 校验为非负数则返回该数字，否则抛出异常.\n\t */\n\tpublic static Integer nonNegative(@Nullable String role, Integer x) {\n\t\tif (x.intValue() < 0) {\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be >= 0\");\n\t\t}\n\t\treturn x;\n\t}\n\n\t/**\n\t * 校验为非负数则返回该数字，否则抛出异常.\n\t */\n\tpublic static long nonNegative(@Nullable String role, long x) {\n\t\tif (x < 0) {\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be >= 0\");\n\t\t}\n\t\treturn x;\n\t}\n\n\t/**\n\t * 校验为非负数则返回该数字，否则抛出异常.\n\t */\n\tpublic static Long nonNegative(@Nullable String role, Long x) {\n\t\tif (x.longValue() < 0) {\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be >= 0\");\n\t\t}\n\t\treturn x;\n\t}\n\n\t/**\n\t * 校验为非负数则返回该数字，否则抛出异常.\n\t */\n\tpublic static double nonNegative(@Nullable String role, double x) {\n\t\tif (!(x >= 0)) { // not x < 0, to work with NaN.\n\t\t\tthrow new IllegalArgumentException(role + \" (\" + x + \") must be >= 0\");\n\t\t}\n\t\treturn x;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/ObjectUtil.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport java.util.Arrays;\n\nimport com.google.common.base.Objects;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 1. Object打印优化，主要解决数组的打印\n * \n * 2. 多个对象的HashCode串联\n */\npublic class ObjectUtil {\n\n\tprivate static final String NULL = \"null\";\n\n\t/**\n\t * JDK7 引入的Null安全的equals\n\t */\n\tpublic static boolean equals(@Nullable Object a, @Nullable Object b) {\n\t\treturn Objects.equal(a, b);\n\t}\n\n\t/**\n\t * 多个对象的HashCode串联, 组成新的HashCode\n\t */\n\tpublic static int hashCode(Object... objects) {\n\t\treturn Arrays.hashCode(objects);\n\t}\n\n\t/**\n\t * 对象的toString(), 处理了对象为数组的情况，JDK的默认toString()只打数组的地址如 \"[Ljava.lang.Integer;@490d6c15.\n\t */\n\tpublic static String toPrettyString(Object value) {\n\t\tif (value == null) {\n\t\t\treturn NULL;\n\t\t}\n\n\t\tClass<?> type = value.getClass();\n\n\t\tif (type.isArray()) {\n\t\t\tClass componentType = type.getComponentType();\n\n\t\t\tif (componentType.isPrimitive()) {\n\t\t\t\treturn primitiveArrayToString(value, componentType);\n\t\t\t} else {\n\t\t\t\treturn objectArrayToString(value);\n\t\t\t}\n\t\t} else if (value instanceof Iterable) {\n\t\t\t// 因为Collection的处理也是默认调用元素的toString(),\n\t\t\t// 为了处理元素是数组的情况，同样需要重载\n\t\t\treturn collectionToString(value);\n\t\t}\n\n\t\treturn value.toString();\n\t}\n\n\tprivate static String primitiveArrayToString(Object value, Class componentType) {\n\t\tStringBuilder sb = new StringBuilder();\n\n\t\tif (componentType == int.class) {\n\t\t\tsb.append(Arrays.toString((int[]) value));\n\t\t} else if (componentType == long.class) {\n\t\t\tsb.append(Arrays.toString((long[]) value));\n\t\t} else if (componentType == double.class) {\n\t\t\tsb.append(Arrays.toString((double[]) value));\n\t\t} else if (componentType == float.class) {\n\t\t\tsb.append(Arrays.toString((float[]) value));\n\t\t} else if (componentType == boolean.class) {\n\t\t\tsb.append(Arrays.toString((boolean[]) value));\n\t\t} else if (componentType == short.class) {\n\t\t\tsb.append(Arrays.toString((short[]) value));\n\t\t} else if (componentType == byte.class) {\n\t\t\tsb.append(Arrays.toString((byte[]) value));\n\t\t} else if (componentType == char.class) {\n\t\t\tsb.append(Arrays.toString((char[]) value));\n\t\t} else {\n\t\t\tthrow new IllegalArgumentException(\"unsupport array type\");\n\t\t}\n\n\t\treturn sb.toString();\n\t}\n\n\tprivate static String objectArrayToString(Object value) {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tsb.append('[');\n\n\t\tObject[] array = (Object[]) value;\n\t\tfor (int i = 0; i < array.length; i++) {\n\t\t\tif (i > 0) {\n\t\t\t\tsb.append(\", \");\n\t\t\t}\n\t\t\tsb.append(toPrettyString(array[i]));\n\t\t}\n\t\tsb.append(']');\n\t\treturn sb.toString();\n\t}\n\n\tprivate static String collectionToString(Object value) {\n\t\tIterable iterable = (Iterable) value;\n\t\tStringBuilder sb = new StringBuilder();\n\t\tsb.append('{');\n\t\tint i = 0;\n\t\tfor (Object o : iterable) {\n\t\t\tif (i > 0) {\n\t\t\t\tsb.append(',');\n\t\t\t}\n\t\t\tsb.append(toPrettyString(o));\n\t\t\ti++;\n\t\t}\n\t\tsb.append('}');\n\t\treturn sb.toString();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/Platforms.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport java.io.File;\n\nimport org.apache.commons.lang3.SystemUtils;\n\n/**\n * 关于系统设定，平台信息的变量(via Common Lang SystemUtils)\n */\npublic class Platforms {\n\n\t// 文件路径分隔符\n\tpublic static final String FILE_PATH_SEPARATOR = File.separator;\n\tpublic static final char FILE_PATH_SEPARATOR_CHAR = File.separatorChar;\n\tpublic static final char WINDOWS_FILE_PATH_SEPARATOR_CHAR = '\\\\';\n\tpublic static final char LINUX_FILE_PATH_SEPARATOR_CHAR = '/';\n\n\t// ClassPath分隔符\n\tpublic static final String CLASS_PATH_SEPARATOR = File.pathSeparator;\n\tpublic static final char CLASS_PATH_SEPARATOR_CHAR = File.pathSeparatorChar;\n\n\t// 换行符\n\tpublic static final String LINE_SEPARATOR = System.lineSeparator();\n\n\t// 临时目录\n\tpublic static final String TMP_DIR = SystemUtils.JAVA_IO_TMPDIR;\n\t// 应用的工作目录\n\tpublic static final String WORKING_DIR = SystemUtils.USER_DIR;\n\t// 用户 HOME目录\n\tpublic static final String USER_HOME = SystemUtils.USER_HOME;\n\t// Java HOME目录\n\tpublic static final String JAVA_HOME = SystemUtils.JAVA_HOME;\n\n\t// Java版本\n\tpublic static final String JAVA_SPECIFICATION_VERSION = SystemUtils.JAVA_SPECIFICATION_VERSION; // e.g. 1.8\n\tpublic static final String JAVA_VERSION = SystemUtils.JAVA_VERSION; // e.g. 1.8.0_102\n\tpublic static final boolean IS_JAVA7 = SystemUtils.IS_JAVA_1_7;\n\tpublic static final boolean IS_JAVA8 = SystemUtils.IS_JAVA_1_8;\n\tpublic static final boolean IS_ATLEASET_JAVA7 = IS_JAVA7 || IS_JAVA8;\n\tpublic static final boolean IS_ATLEASET_JAVA8 = IS_JAVA8;\n\n\t// 操作系统类型及版本\n\tpublic static final String OS_NAME = SystemUtils.OS_NAME;\n\tpublic static final String OS_VERSION = SystemUtils.OS_VERSION;\n\tpublic static final String OS_ARCH = SystemUtils.OS_ARCH; // e.g. x86_64\n\tpublic static final boolean IS_LINUX = SystemUtils.IS_OS_LINUX;\n\tpublic static final boolean IS_UNIX = SystemUtils.IS_OS_UNIX;\n\tpublic static final boolean IS_WINDOWS = SystemUtils.IS_OS_WINDOWS;\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/PropertiesUtil.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.util.Properties;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.vip.vjtools.vjkit.io.URLResourceUtil;\nimport com.vip.vjtools.vjkit.number.NumberUtil;\nimport com.vip.vjtools.vjkit.text.Charsets;\n\n/**\n * 关于Properties的工具类\n * \n * 1. 统一风格读取Properties到各种数据类型\n * \n * 2. 从文件或字符串装载Properties\n */\npublic class PropertiesUtil {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);\n\n\t/////////////////// 读取Properties ////////////////////\n\n\tpublic static Boolean getBoolean(Properties p, String name, Boolean defaultValue) {\n\t\treturn BooleanUtil.toBooleanObject(p.getProperty(name), defaultValue);\n\t}\n\n\tpublic static Integer getInt(Properties p, String name, Integer defaultValue) {\n\t\treturn NumberUtil.toIntObject(p.getProperty(name), defaultValue);\n\t}\n\n\tpublic static Long getLong(Properties p, String name, Long defaultValue) {\n\t\treturn NumberUtil.toLongObject(p.getProperty(name), defaultValue);\n\t}\n\n\tpublic static Double getDouble(Properties p, String name, Double defaultValue) {\n\t\treturn NumberUtil.toDoubleObject(p.getProperty(name), defaultValue);\n\t}\n\n\tpublic static String getString(Properties p, String name, String defaultValue) {\n\t\treturn p.getProperty(name, defaultValue);\n\t}\n\n\t/////////// 加载Properties////////\n\t/**\n\t * 从文件路径加载properties. 默认使用utf-8编码解析文件\n\t * \n\t * 路径支持从外部文件或resources文件加载, \"file://\"或无前缀代表外部文件, \"classpath:\"代表resources\n\t */\n\tpublic static Properties loadFromFile(String generalPath) {\n\t\tProperties p = new Properties();\n\t\ttry (Reader reader = new InputStreamReader(URLResourceUtil.asStream(generalPath), Charsets.UTF_8)) {\n\t\t\tp.load(reader);\n\t\t} catch (IOException e) {\n\t\t\tlogger.warn(\"Load property from \" + generalPath + \" failed\", e);\n\t\t}\n\t\treturn p;\n\t}\n\n\t/**\n\t * 从字符串内容加载Properties\n\t */\n\tpublic static Properties loadFromString(String content) {\n\t\tProperties p = new Properties();\n\t\ttry (Reader reader = new StringReader(content)) {\n\t\t\tp.load(reader);\n\t\t} catch (IOException ignored) { // NOSONAR\n\t\t}\n\t\treturn p;\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/RuntimeUtil.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport java.lang.management.ManagementFactory;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.apache.commons.lang3.StringUtils;\n\n/**\n * 运行时工具类\n * \n * 1.取得当前进程PID, JVM参数\n * \n * 2.注册JVM关闭钩子, 获得CPU核数\n * \n * 3.通过StackTrace 获得当前方法的类名方法名，调用者的类名方法名(获取StackTrace有消耗，不要滥用)\n */\npublic class RuntimeUtil {\n\n\tprivate static AtomicInteger shutdownHookThreadIndex = new AtomicInteger(0);\n\n\t/////// RuntimeMXBean相关 //////\n\n\t/**\n\t * 获得当前进程的PID\n\t * \n\t * 当失败时返回-1\n\t */\n\tpublic static int getPid() {\n\n\t\t// format: \"pid@hostname\"\n\t\tString jvmName = ManagementFactory.getRuntimeMXBean().getName();\n\t\tString[] split = jvmName.split(\"@\");\n\t\tif (split.length != 2) {\n\t\t\treturn -1;\n\t\t}\n\n\t\ttry {\n\t\t\treturn Integer.parseInt(split[0]);\n\t\t} catch (Exception e) { // NOSONAR\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/**\n\t * 返回应用启动到现在的毫秒数\n\t */\n\tpublic static long getUpTime() {\n\t\treturn ManagementFactory.getRuntimeMXBean().getUptime();\n\t}\n\n\t/**\n\t * 返回输入的JVM参数列表\n\t */\n\tpublic static String getVmArguments() {\n\t\tList<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();\n\t\treturn StringUtils.join(vmArguments, \" \");\n\t}\n\n\t//////////// Runtime 相关////////////////\n\t/**\n\t * 获取CPU核数\n\t */\n\tpublic static int getCores() {\n\t\treturn Runtime.getRuntime().availableProcessors();\n\t}\n\n\t/**\n\t * 注册JVM关闭时的钩子程序\n\t */\n\tpublic static void addShutdownHook(Runnable runnable) {\n\t\tRuntime.getRuntime().addShutdownHook(\n\t\t\t\tnew Thread(runnable, \"Thread-ShutDownHook-\" + shutdownHookThreadIndex.incrementAndGet()));\n\t}\n\n\t//////// 通过StackTrace 获得当前方法的调用者 ////\n\t/**\n\t * 通过StackTrace，获得调用者的类名.\n\t * \n\t * 获取StackTrace有消耗，不要滥用\n\t */\n\tpublic static String getCallerClass() {\n\t\tStackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();\n\t\tif (stacktrace.length >= 4) {\n\t\t\tStackTraceElement element = stacktrace[3];\n\t\t\treturn element.getClassName();\n\t\t} else {\n\t\t\treturn StringUtils.EMPTY;\n\t\t}\n\t}\n\n\t/**\n\t * 通过StackTrace，获得调用者的\"类名.方法名()\"\n\t * \n\t * 获取StackTrace有消耗，不要滥用\n\t */\n\tpublic static String getCallerMethod() {\n\t\tStackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();\n\t\tif (stacktrace.length >= 4) {\n\t\t\tStackTraceElement element = stacktrace[3];\n\t\t\treturn element.getClassName() + '.' + element.getMethodName() + \"()\";\n\t\t} else {\n\t\t\treturn StringUtils.EMPTY;\n\t\t}\n\t}\n\n\t/**\n\t * 通过StackTrace，获得当前方法的类名.\n\t * \n\t * 获取StackTrace有消耗，不要滥用\n\t */\n\tpublic static String getCurrentClass() {\n\t\tStackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();\n\t\tif (stacktrace.length >= 3) {\n\t\t\tStackTraceElement element = stacktrace[2];\n\t\t\treturn element.getClassName();\n\t\t} else {\n\t\t\treturn StringUtils.EMPTY;\n\t\t}\n\t}\n\n\t/**\n\t * 通过StackTrace，获得当前方法的\"类名.方法名()\"\n\t * \n\t * 获取StackTrace有消耗，不要滥用\n\t */\n\tpublic static String getCurrentMethod() {\n\t\tStackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();\n\t\tif (stacktrace.length >= 3) {\n\t\t\tStackTraceElement element = stacktrace[2];\n\t\t\treturn element.getClassName() + '.' + element.getMethodName() + \"()\";\n\t\t} else {\n\t\t\treturn StringUtils.EMPTY;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/SystemPropertiesUtil.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport com.vip.vjtools.vjkit.number.NumberUtil;\n\n/**\n * 关于SystemProperties的工具类\n * \n * 1. 统一风格的读取系统变量到各种数据类型，其中Boolean.readBoolean的风格不统一，Double则不支持，都进行了扩展.\n * \n * 2. 简单的合并系统变量(-D)，环境变量 和默认值，以系统变量优先，在未引入Commons Config时使用.\n * \n * 3. Properties 本质上是一个HashTable，每次读写都会加锁，所以不支持频繁的System.getProperty(name)来检查系统内容变化 因此扩展了一个ListenableProperties,\n * 在其所关心的属性变化时进行通知.\n */\npublic class SystemPropertiesUtil {\n\n\t/**\n\t * 读取Boolean类型的系统变量，为空时返回null，代表未设置，而不是Boolean.getBoolean()的false.\n\t */\n\tpublic static Boolean getBoolean(String name) {\n\t\tString stringResult = System.getProperty(name);\n\t\treturn BooleanUtil.toBooleanObject(stringResult);\n\t}\n\n\t/**\n\t * 读取Boolean类型的系统变量，为空时返回默认值, 而不是Boolean.getBoolean()的false.\n\t */\n\tpublic static Boolean getBoolean(String name, Boolean defaultValue) {\n\t\tString stringResult = System.getProperty(name);\n\t\treturn BooleanUtil.toBooleanObject(stringResult, defaultValue);\n\t}\n\n\t/**\n\t * 读取String类型的系统变量，为空时返回null.\n\t */\n\tpublic static String getString(String name) {\n\t\treturn System.getProperty(name);\n\t}\n\n\t/**\n\t * 读取String类型的系统变量，为空时返回默认值\n\t */\n\tpublic static String getString(String name, String defaultValue) {\n\t\treturn System.getProperty(name, defaultValue);\n\t}\n\n\t/**\n\t * 读取Integer类型的系统变量，为空时返回null.\n\t */\n\tpublic static Integer getInteger(String name) {\n\t\treturn Integer.getInteger(name);\n\t}\n\n\t/**\n\t * 读取Integer类型的系统变量，为空时返回默认值\n\t */\n\tpublic static Integer getInteger(String name, Integer defaultValue) {\n\t\treturn Integer.getInteger(name, defaultValue);\n\t}\n\n\t/**\n\t * 读取Long类型的系统变量，为空时返回null.\n\t */\n\tpublic static Long getLong(String name) {\n\t\treturn Long.getLong(name);\n\t}\n\n\t/**\n\t * 读取Integer类型的系统变量，为空时返回默认值\n\t */\n\tpublic static Long getLong(String name, Long defaultValue) {\n\t\treturn Long.getLong(name, defaultValue);\n\t}\n\n\t/**\n\t * 读取Double类型的系统变量，为空时返回null.\n\t */\n\tpublic static Double getDouble(String propertyName) {\n\t\treturn NumberUtil.toDoubleObject(System.getProperty(propertyName), null);\n\t}\n\n\t/**\n\t * 读取Double类型的系统变量，为空时返回默认值.\n\t */\n\tpublic static Double getDouble(String propertyName, Double defaultValue) {\n\t\tDouble propertyValue = NumberUtil.toDoubleObject(System.getProperty(propertyName), null);\n\t\treturn propertyValue != null ? propertyValue : defaultValue;\n\t}\n\n\t/////////// 简单的合并系统变量(-D)，环境变量 和默认值，以系统变量优先.///////////////\n\n\t/**\n\t * 合并系统变量(-D)，环境变量 和默认值，以系统变量优先\n\t */\n\tpublic static String getString(String propertyName, String envName, String defaultValue) {\n\t\tcheckEnvName(envName);\n\t\tString propertyValue = System.getProperty(propertyName);\n\t\tif (propertyValue != null) {\n\t\t\treturn propertyValue;\n\t\t} else {\n\t\t\tpropertyValue = System.getenv(envName);\n\t\t\treturn propertyValue != null ? propertyValue : defaultValue;\n\t\t}\n\t}\n\n\t/**\n\t * 合并系统变量(-D)，环境变量 和默认值，以系统变量优先\n\t */\n\tpublic static Integer getInteger(String propertyName, String envName, Integer defaultValue) {\n\t\tcheckEnvName(envName);\n\t\tInteger propertyValue = NumberUtil.toIntObject(System.getProperty(propertyName), null);\n\t\tif (propertyValue != null) {\n\t\t\treturn propertyValue;\n\t\t} else {\n\t\t\tpropertyValue = NumberUtil.toIntObject(System.getenv(envName), null);\n\t\t\treturn propertyValue != null ? propertyValue : defaultValue;\n\t\t}\n\t}\n\n\t/**\n\t * 合并系统变量(-D)，环境变量 和默认值，以系统变量优先\n\t */\n\tpublic static Long getLong(String propertyName, String envName, Long defaultValue) {\n\t\tcheckEnvName(envName);\n\t\tLong propertyValue = NumberUtil.toLongObject(System.getProperty(propertyName), null);\n\t\tif (propertyValue != null) {\n\t\t\treturn propertyValue;\n\t\t} else {\n\t\t\tpropertyValue = NumberUtil.toLongObject(System.getenv(envName), null);\n\t\t\treturn propertyValue != null ? propertyValue : defaultValue;\n\t\t}\n\t}\n\n\t/**\n\t * 合并系统变量(-D)，环境变量 和默认值，以系统变量优先\n\t */\n\tpublic static Double getDouble(String propertyName, String envName, Double defaultValue) {\n\t\tcheckEnvName(envName);\n\t\tDouble propertyValue = NumberUtil.toDoubleObject(System.getProperty(propertyName), null);\n\t\tif (propertyValue != null) {\n\t\t\treturn propertyValue;\n\t\t} else {\n\t\t\tpropertyValue = NumberUtil.toDoubleObject(System.getenv(envName), null);\n\t\t\treturn propertyValue != null ? propertyValue : defaultValue;\n\t\t}\n\t}\n\n\t/**\n\t * 合并系统变量(-D)，环境变量 和默认值，以系统变量优先\n\t */\n\tpublic static Boolean getBoolean(String propertyName, String envName, Boolean defaultValue) {\n\t\tcheckEnvName(envName);\n\t\tBoolean propertyValue = BooleanUtil.toBooleanObject(System.getProperty(propertyName), null);\n\t\tif (propertyValue != null) {\n\t\t\treturn propertyValue;\n\t\t} else {\n\t\t\tpropertyValue = BooleanUtil.toBooleanObject(System.getenv(envName), null);\n\t\t\treturn propertyValue != null ? propertyValue : defaultValue;\n\t\t}\n\t}\n\n\t/**\n\t * 检查环境变量名不能有'.'，在linux下不支持\n\t */\n\tprivate static void checkEnvName(String envName) {\n\t\tif (envName == null || envName.indexOf('.') != -1) {\n\t\t\tthrow new IllegalArgumentException(\"envName \" + envName + \"is null or has dot which is not valid\");\n\t\t}\n\t}\n\n\t/////////// ListenableProperties /////////////\n\t/**\n\t * Properties 本质上是一个HashTable，每次读写都会加锁，所以不支持频繁的System.getProperty(name)来检查系统内容变化 因此扩展了一个ListenableProperties,\n\t * 在其所关心的属性变化时进行通知.\n\t * \n\t * @see ListenableProperties\n\t */\n\tpublic static synchronized void registerSystemPropertiesListener(PropertiesListener listener) {\n\t\tProperties currentProperties = System.getProperties();\n\n\t\t// 将System的properties实现替换为ListenableProperties\n\t\tif (!(currentProperties instanceof ListenableProperties)) {\n\t\t\tListenableProperties newProperties = new ListenableProperties(currentProperties);\n\t\t\tSystem.setProperties(newProperties);\n\t\t\tcurrentProperties = newProperties;\n\t\t}\n\n\t\t((ListenableProperties) currentProperties).register(listener);\n\t}\n\n\t/**\n\t * Properties 本质上是一个HashTable，每次读写都会加锁，所以不支持频繁的System.getProperty(name)来检查系统内容变化 因此扩展了Properties子类,\n\t * 在其所关心的属性变化时进行通知.\n\t * \n\t * @see PropertiesListener\n\t */\n\tpublic static class ListenableProperties extends Properties {\n\n\t\tprivate static final long serialVersionUID = -8282465702074684324L;\n\n\t\tprotected transient List<PropertiesListener> listeners = new CopyOnWriteArrayList<PropertiesListener>();\n\n\t\tpublic ListenableProperties(Properties properties) {\n\t\t\tsuper(properties);\n\t\t}\n\n\t\tpublic void register(PropertiesListener listener) {\n\t\t\tlisteners.add(listener);\n\t\t}\n\n\t\t@Override\n\t\tpublic synchronized Object setProperty(String key, String value) {\n\t\t\tObject result = put(key, value);\n\t\t\tfor (PropertiesListener listener : listeners) {\n\t\t\t\tif (listener.propertyName.equals(key)) {\n\t\t\t\t\tlistener.onChange(key, value);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\n\t/**\n\t * 获取所关心的Property变更的Listener基类.\n\t */\n\tpublic abstract static class PropertiesListener {\n\n\t\t// 关心的Property\n\t\tprotected String propertyName;\n\n\t\tpublic PropertiesListener(String propertyName) {\n\t\t\tthis.propertyName = propertyName;\n\t\t}\n\n\t\tpublic abstract void onChange(String propertyName, String value);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/ValueValidator.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport org.apache.commons.lang3.StringUtils;\n\n/**\n * 数值校验取值器 \n * \n * 提供对配置值进行校验，并根据结果决定是否使用默认值。\n * \n * Guava, Commons Lang里的Validate类用于判断并抛异常。\n * \n * 而ValueValidator的行为是取默认值，多用于配置值的处理。\n * \n * 除默认提供的Validator，可自行扩写。\n */\npublic class ValueValidator {\n\n\t/**\n\t * 对目标值进行校验，并根据校验结果取值\n\t * \n\t * 使用示例(校验目标值是否大于0, 如果小于 0 则取值为 1)\n\t * \n\t * ValueValidator.checkAndGet(idleTime, 1, Validator.INTEGER_GT_ZERO_VALIDATOR)\n\t * \n\t * @param value 校验值\n\t * @param defaultValue 校验失败默认值\n\t * @param v 校验器\n\t * @return 经Validator校验后的返回值，校验成功返回 value, 校验失败返回 defaultValue\n\t */\n\tpublic static <T> T checkAndGet(T value, T defaultValue, Validator<T> v) {\n\t\tif (v.validate(value)) {\n\t\t\treturn value;\n\t\t}\n\n\t\treturn defaultValue;\n\t}\n\n\t/**\n\t * 对值进行规则匹配的验证器\n\t */\n\tpublic interface Validator<T> {\n\t\t/**\n\t\t * 校验值是否匹配\n\t\t */\n\t\tboolean validate(T value);\n\n\t\t/**\n\t\t * 校验器: 数值配置不为null, 且大于0较验\n\t\t */\n\t\tValidator<Integer> INTEGER_GT_ZERO_VALIDATOR = new Validator<Integer>() {\n\t\t\t@Override\n\t\t\tpublic boolean validate(Integer value) {\n\t\t\t\treturn (value != null && value > 0);\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * 校验器: 字符串不为空串较验\n\t\t */\n\t\tValidator<String> STRING_EMPTY_VALUE_VALIDATOR = new Validator<String>() {\n\t\t\t@Override\n\t\t\tpublic boolean validate(String value) {\n\t\t\t\treturn StringUtils.isNotEmpty(value);\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * 校验器: BOOL字符串较验\n\t\t */\n\t\tValidator<String> STRICT_BOOL_VALUE_VALIDATOR = new Validator<String>() {\n\t\t\t@Override\n\t\t\tpublic boolean validate(String value) {\n\t\t\t\treturn \"true\".equalsIgnoreCase(value) || \"false\".equalsIgnoreCase(value);\n\t\t\t}\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/annotation/NotNull.java",
    "content": "package com.vip.vjtools.vjkit.base.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Target;\n\n/**\n * 标注参数、属性、方法不可为 Null\n */\n@Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD })\npublic @interface NotNull {\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/annotation/Nullable.java",
    "content": "package com.vip.vjtools.vjkit.base.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Target;\n\n/**\n * 标注参数、属性、方法可为 Null\n */\n@Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD })\npublic @interface Nullable {\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/annotation/VisibleForTesting.java",
    "content": "package com.vip.vjtools.vjkit.base.annotation;\n\n/**\n * 标注因为方便UT，将方法／属性的可访问范围扩大了，参考Guava\n */\npublic @interface VisibleForTesting {\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/CloneableException.java",
    "content": "package com.vip.vjtools.vjkit.base.type;\n\nimport com.vip.vjtools.vjkit.base.ExceptionUtil;\n\n/**\n * 适用于异常信息需要变更的情况, 可通过clone()，不经过构造函数（也就避免了获得StackTrace）地从之前定义的静态异常中克隆，再设定新的异常信息\n * \n * private static CloneableException TIMEOUT_EXCEPTION = new CloneableException(\"Timeout\") .setStackTrace(My.class,\n * \"hello\"); ...\n * \n * throw TIMEOUT_EXCEPTION.clone(\"Timeout for 40ms\");\n * \n */\npublic class CloneableException extends Exception implements Cloneable {\n\n\tprivate static final long serialVersionUID = -6270471689928560417L;\n\n\tprotected String message; // NOSONAR\n\n\tpublic CloneableException() {\n\t\tsuper((Throwable) null);\n\t}\n\n\tpublic CloneableException(String message) {\n\t\tsuper((Throwable) null);\n\t\tthis.message = message;\n\t}\n\n\tpublic CloneableException(String message, Throwable cause) {\n\t\tsuper(cause);\n\t\tthis.message = message;\n\t}\n\n\t@Override\n\tpublic CloneableException clone() { // NOSONAR\n\t\ttry {\n\t\t\treturn (CloneableException) super.clone();\n\t\t} catch (CloneNotSupportedException e) {// NOSONAR\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t@Override\n\tpublic String getMessage() {\n\t\treturn message;\n\t}\n\n\t/**\n\t * 简便函数，定义静态异常时使用\n\t */\n\tpublic CloneableException setStackTrace(Class<?> throwClazz, String throwMethod) {\n\t\tExceptionUtil.setStackTrace(this, throwClazz, throwMethod);\n\t\treturn this;\n\t}\n\n\t/**\n\t * 简便函数, clone并重新设定Message\n\t */\n\tpublic CloneableException clone(String message) {\n\t\tCloneableException newException = this.clone();\n\t\tnewException.setMessage(message);\n\t\treturn newException;\n\t}\n\n\t/**\n\t * 简便函数, 重新设定Message\n\t */\n\tpublic CloneableException setMessage(String message) {\n\t\tthis.message = message;\n\t\treturn this;\n\t}\n}"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/CloneableRuntimeException.java",
    "content": "package com.vip.vjtools.vjkit.base.type;\n\nimport com.vip.vjtools.vjkit.base.ExceptionUtil;\n\n/**\n * 适用于异常信息需要变更的情况, 可通过clone()，不经过构造函数（也就避免了获得StackTrace）地从之前定义的静态异常中克隆，再设定新的异常信息\n * \n * @see CloneableException\n */\npublic class CloneableRuntimeException extends RuntimeException implements Cloneable {\n\n\tprivate static final long serialVersionUID = 3984796576627959400L;\n\n\tprotected String message; // NOSONAR\n\n\tpublic CloneableRuntimeException() {\n\t\tsuper((Throwable) null);\n\t}\n\n\tpublic CloneableRuntimeException(String message) {\n\t\tsuper((Throwable) null);\n\t\tthis.message = message;\n\t}\n\n\tpublic CloneableRuntimeException(String message, Throwable cause) {\n\t\tsuper(cause);\n\t\tthis.message = message;\n\t}\n\n\t@Override\n\tpublic CloneableRuntimeException clone() { // NOSONAR\n\t\ttry {\n\t\t\treturn (CloneableRuntimeException) super.clone();\n\t\t} catch (CloneNotSupportedException e) { // NOSONAR\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t@Override\n\tpublic String getMessage() {\n\t\treturn message;\n\t}\n\n\t/**\n\t * 简便函数，定义静态异常时使用\n\t */\n\tpublic CloneableRuntimeException setStackTrace(Class<?> throwClazz, String throwMethod) {\n\t\tExceptionUtil.setStackTrace(this, throwClazz, throwMethod);\n\t\treturn this;\n\t}\n\n\t/**\n\t * 简便函数, clone并重新设定Message\n\t */\n\tpublic CloneableRuntimeException clone(String message) {\n\t\tCloneableRuntimeException newException = this.clone();\n\t\tnewException.setMessage(message);\n\t\treturn newException;\n\t}\n\n\t/**\n\t * 简便函数, 重新设定Message\n\t */\n\tpublic CloneableRuntimeException setMessage(String message) {\n\t\tthis.message = message;\n\t\treturn this;\n\t}\n}"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/Pair.java",
    "content": "package com.vip.vjtools.vjkit.base.type;\n\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 引入一个简简单单的Pair, 用于返回值返回两个元素.\n *\n * copy from Twitter Common\n */\npublic class Pair<L, R> {\n\n\t@Nullable\n\tprivate final L left;\n\t@Nullable\n\tprivate final R right;\n\n\t/**\n\t * Creates a new pair.\n\t */\n\tpublic Pair(@Nullable L left, @Nullable R right) {\n\t\tthis.left = left;\n\t\tthis.right = right;\n\t}\n\n\t@Nullable\n\tpublic L getLeft() {\n\t\treturn left;\n\t}\n\n\t@Nullable\n\tpublic R getRight() {\n\t\treturn right;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tfinal int prime = 31;\n\t\tint result = 1;\n\t\tresult = prime * result + ((left == null) ? 0 : left.hashCode());\n\t\treturn prime * result + ((right == null) ? 0 : right.hashCode());\n\t}\n\n\t@Override\n\tpublic boolean equals(Object obj) {\n\t\tif (this == obj) {\n\t\t\treturn true;\n\t\t}\n\t\tif (obj == null || getClass() != obj.getClass()) {\n\t\t\treturn false;\n\t\t}\n\t\tPair other = (Pair) obj;\n\t\tif (left == null) {\n\t\t\tif (other.left != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else if (!left.equals(other.left)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (right == null) {\n\t\t\tif (other.right != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else if (!right.equals(other.right)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Pair [left=\" + left + \", right=\" + right + ']';\n\t}\n\n\t/**\n\t * 根据等号左边的泛型，自动构造合适的Pair\n\t */\n\tpublic static <L, R> Pair<L, R> of(@Nullable L left, @Nullable R right) {\n\t\treturn new Pair<L, R>(left, right);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/Triple.java",
    "content": "package com.vip.vjtools.vjkit.base.type;\n\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 引入一个简简单单的Triple, 用于返回值返回三个元素.\n *\n * copy from Twitter Common\n */\npublic class Triple<L, M, R> {\n\n\t@Nullable\n\tprivate final L left;\n\t@Nullable\n\tprivate final M middle;\n\t@Nullable\n\tprivate final R right;\n\n\t/**\n\t * Creates a new Triple.\n\t */\n\tpublic Triple(@Nullable L left, @Nullable M middle, @Nullable R right) {\n\t\tthis.left = left;\n\t\tthis.middle = middle;\n\t\tthis.right = right;\n\t}\n\n\t@Nullable\n\tpublic L getLeft() {\n\t\treturn left;\n\t}\n\n\t@Nullable\n\tpublic M getMiddle() {\n\t\treturn middle;\n\t}\n\n\t@Nullable\n\tpublic R getRight() {\n\t\treturn right;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tfinal int prime = 31;\n\t\tint result = 1;\n\t\tresult = prime * result + ((left == null) ? 0 : left.hashCode());\n\t\tresult = prime * result + ((middle == null) ? 0 : middle.hashCode());\n\t\treturn prime * result + ((right == null) ? 0 : right.hashCode());\n\t}\n\n\t@Override\n\tpublic boolean equals(Object obj) {\n\t\tif (this == obj) {\n\t\t\treturn true;\n\t\t}\n\t\tif (obj == null || getClass() != obj.getClass()) {\n\t\t\treturn false;\n\t\t}\n\t\tTriple other = (Triple) obj;\n\t\tif (left == null) {\n\t\t\tif (other.left != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else if (!left.equals(other.left)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (middle == null) {\n\t\t\tif (other.middle != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else if (!middle.equals(other.middle)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (right == null) {\n\t\t\tif (other.right != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else if (!right.equals(other.right)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Triple [left=\" + left + \", middle=\" + middle + \", right=\" + right + ']';\n\t}\n\n\t/**\n\t * 根据等号左边的泛型，自动构造合适的Triple\n\t */\n\tpublic static <L, M, R> Triple<L, M, R> of(@Nullable L left, @Nullable M middle, @Nullable R right) {\n\t\treturn new Triple<L, M, R>(left, middle, right);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/UncheckedException.java",
    "content": "package com.vip.vjtools.vjkit.base.type;\n\n/**\n * CheckedException的wrapper.\n * \n * 返回Message时, 将返回内层Exception的Message.\n */\npublic class UncheckedException extends RuntimeException {\n\n\tprivate static final long serialVersionUID = 4140223302171577501L;\n\n\tpublic UncheckedException(Throwable wrapped) {\n\t\tsuper(wrapped);\n\t}\n\n\t@Override\n\tpublic String getMessage() {\n\t\treturn super.getCause().getMessage();\n\t}\n}"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/ArrayUtil.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport java.lang.reflect.Array;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Random;\n\nimport com.google.common.collect.ObjectArrays;\nimport com.google.common.primitives.Doubles;\nimport com.google.common.primitives.Ints;\nimport com.google.common.primitives.Longs;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 数组工具类.\n * \n * 1. 创建Array的函数\n * \n * 2. 数组的乱序与contact相加\n * \n * 3. 从Array转换到Guava的底层为原子类型的List\n * \n * JDK Arrays的其他函数，如sort(), toString() 请直接调用\n * \n * Common Lang ArrayUtils的其他函数，如subarray(),reverse(),indexOf(), 请直接调用\n */\npublic class ArrayUtil {\n\n\t/**\n\t * 传入类型与大小创建数组.\n\t * \n\t * Array.newInstance()的性能并不差\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <T> T[] newArray(Class<T> type, int length) {\n\t\treturn (T[]) Array.newInstance(type, length);\n\t}\n\n\t/**\n\t * 从collection转为Array, 以 list.toArray(new String[0]); 最快 不需要创建list.size()的数组.\n\t * \n\t * 本函数等价于list.toArray(new String[0]); 用户也可以直接用后者.\n\t * \n\t * https://shipilev.net/blog/2016/arrays-wisdom-ancients/\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <T> T[] toArray(Collection<T> col, Class<T> type) {\n\t\treturn col.toArray((T[]) Array.newInstance(type, 0));\n\t}\n\n\t/**\n\t * Swaps the two specified elements in the specified array.\n\t */\n\tprivate static void swap(Object[] arr, int i, int j) {\n\t\tObject tmp = arr[i];\n\t\tarr[i] = arr[j];\n\t\tarr[j] = tmp;\n\t}\n\n\t/**\n\t * 将传入的数组乱序\n\t */\n\tpublic static <T> T[] shuffle(T[] array) {\n\t\tif (array != null && array.length > 1) {\n\t\t\tRandom rand = new Random();\n\t\t\treturn shuffle(array, rand);\n\t\t} else {\n\t\t\treturn array;\n\t\t}\n\t}\n\n\t/**\n\t * 将传入的数组乱序\n\t */\n\tpublic static <T> T[] shuffle(T[] array, Random random) {\n\t\tif (array != null && array.length > 1 && random != null) {\n\t\t\tfor (int i = array.length; i > 1; i--) {\n\t\t\t\tswap(array, i - 1, random.nextInt(i));\n\t\t\t}\n\t\t}\n\t\treturn array;\n\t}\n\n\t/**\n\t * 添加元素到数组头.\n\t */\n\tpublic static <T> T[] concat(@Nullable T element, T[] array) {\n\t\treturn ObjectArrays.concat(element, array);\n\t}\n\n\t/**\n\t * 添加元素到数组末尾.\n\t */\n\tpublic static <T> T[] concat(T[] array, @Nullable T element) {\n\t\treturn ObjectArrays.concat(array, element);\n\t}\n\n\t////////////////// guava Array 转换为底层为原子类型的List ///////////\n\t/**\n\t * 原版将数组转换为List.\n\t * \n\t * 注意转换后的List不能写入, 否则抛出UnsupportedOperationException\n\t * \n\t * @see java.util.Arrays#asList(Object...)\n\t */\n\tpublic static <T> List<T> asList(T... a) {\n\t\treturn Arrays.asList(a);\n\t}\n\n\t/**\n\t * Arrays.asList()的加强版, 返回一个底层为原始类型int的List\n\t * \n\t * 与保存Integer相比节约空间，同时只在读取数据时AutoBoxing.\n\t * \n\t * @see java.util.Arrays#asList(Object...)\n\t * @see com.google.common.primitives.Ints#asList(int...)\n\t * \n\t */\n\tpublic static List<Integer> intAsList(int... backingArray) {\n\t\treturn Ints.asList(backingArray);\n\t}\n\n\t/**\n\t * Arrays.asList()的加强版, 返回一个底层为原始类型long的List\n\t * \n\t * 与保存Long相比节约空间，同时只在读取数据时AutoBoxing.\n\t * \n\t * @see java.util.Arrays#asList(Object...)\n\t * @see com.google.common.primitives.Longs#asList(long...)\n\t */\n\tpublic static List<Long> longAsList(long... backingArray) {\n\t\treturn Longs.asList(backingArray);\n\t}\n\n\t/**\n\t * Arrays.asList()的加强版, 返回一个底层为原始类型double的List\n\t * \n\t * 与保存Double相比节约空间，同时也避免了AutoBoxing.\n\t * \n\t * @see java.util.Arrays#asList(Object...)\n\t * @see com.google.common.primitives.Doubles#asList(double...)\n\t */\n\tpublic static List<Double> doubleAsList(double... backingArray) {\n\t\treturn Doubles.asList(backingArray);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/CollectionUtil.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport com.google.common.collect.Iterables;\nimport com.google.common.collect.Iterators;\nimport com.google.common.collect.Ordering;\nimport com.vip.vjtools.vjkit.base.type.Pair;\n\n/**\n * 通用Collection的工具集\n * \n * 1. 集合是否为空，取得集合中首个及最后一个元素，判断集合是否完全相等\n * \n * 2. 集合的最大最小值，及Top N, Bottom N\n * \n * 关于List, Map, Queue, Set的特殊工具集，另见特定的Util.\n * \n * 另JDK中缺少ComparableComparator和NullComparator，直到JDK8才补上。\n * 因此平时请使用guava的Ordering.natural(),fluentable的API更好用，可以链式设置nullFirst，nullLast,reverse\n * \n * @see com.google.common.collect.Ordering\n */\npublic class CollectionUtil {\n\n\t/**\n\t * 判断是否为空.\n\t */\n\tpublic static boolean isEmpty(Collection<?> collection) {\n\t\treturn (collection == null) || collection.isEmpty();\n\t}\n\n\t/**\n\t * 判断是否不为空.\n\t */\n\tpublic static boolean isNotEmpty(Collection<?> collection) {\n\t\treturn (collection != null) && !(collection.isEmpty());\n\t}\n\n\t/**\n\t * 取得Collection的第一个元素，如果collection为空返回null.\n\t */\n\tpublic static <T> T getFirst(Collection<T> collection) {\n\t\tif (isEmpty(collection)) {\n\t\t\treturn null;\n\t\t}\n\t\tif (collection instanceof List) {\n\t\t\treturn ((List<T>) collection).get(0);\n\t\t}\n\t\treturn collection.iterator().next();\n\t}\n\n\t/**\n\t * 获取Collection的最后一个元素，如果collection为空返回null.\n\t */\n\tpublic static <T> T getLast(Collection<T> collection) {\n\t\tif (isEmpty(collection)) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// 当类型List时，直接取得最后一个元素.\n\t\tif (collection instanceof List) {\n\t\t\tList<T> list = (List<T>) collection;\n\t\t\treturn list.get(list.size() - 1);\n\t\t}\n\n\t\treturn Iterators.getLast(collection.iterator());\n\t}\n\n\t/**\n\t * 两个集合中的所有元素按顺序相等.\n\t */\n\tpublic static boolean elementsEqual(Iterable<?> iterable1, Iterable<?> iterable2) {\n\t\treturn Iterables.elementsEqual(iterable1, iterable2);\n\t}\n\n\t///////////// 求最大最小值，及Top N, Bottom N//////////\n\t/**\n\t * 返回无序集合中的最小值，使用元素默认排序\n\t */\n\tpublic static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) {\n\t\treturn Collections.min(coll);\n\t}\n\n\t/**\n\t * 返回无序集合中的最小值\n\t */\n\tpublic static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp) {\n\t\treturn Collections.min(coll, comp);\n\t}\n\n\t/**\n\t * 返回无序集合中的最大值，使用元素默认排序\n\t */\n\tpublic static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {\n\t\treturn Collections.max(coll);\n\t}\n\n\t/**\n\t * 返回无序集合中的最大值\n\t */\n\tpublic static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {\n\t\treturn Collections.max(coll, comp);\n\t}\n\n\t/**\n\t * 同时返回无序集合中的最小值和最大值，使用元素默认排序\n\t * \n\t * 在返回的Pair中，第一个为最小值，第二个为最大值\n\t */\n\tpublic static <T extends Object & Comparable<? super T>> Pair<T, T> minAndMax(Collection<? extends T> coll) {\n\t\tIterator<? extends T> i = coll.iterator();\n\t\tT minCandidate = i.next();\n\t\tT maxCandidate = minCandidate;\n\n\t\twhile (i.hasNext()) {\n\t\t\tT next = i.next();\n\t\t\tif (next.compareTo(minCandidate) < 0) {\n\t\t\t\tminCandidate = next;\n\t\t\t} else if (next.compareTo(maxCandidate) > 0) {\n\t\t\t\tmaxCandidate = next;\n\t\t\t}\n\t\t}\n\t\treturn Pair.of(minCandidate, maxCandidate);\n\t}\n\n\t/**\n\t * 返回无序集合中的最小值和最大值\n\t * \n\t * 在返回的Pair中，第一个为最小值，第二个为最大值\n\t */\n\tpublic static <T> Pair<T, T> minAndMax(Collection<? extends T> coll, Comparator<? super T> comp) {\n\n\t\tIterator<? extends T> i = coll.iterator();\n\t\tT minCandidate = i.next();\n\t\tT maxCandidate = minCandidate;\n\n\t\twhile (i.hasNext()) {\n\t\t\tT next = i.next();\n\t\t\tif (comp.compare(next, minCandidate) < 0) {\n\t\t\t\tminCandidate = next;\n\t\t\t} else if (comp.compare(next, maxCandidate) > 0) {\n\t\t\t\tmaxCandidate = next;\n\t\t\t}\n\t\t}\n\n\t\treturn Pair.of(minCandidate, maxCandidate);\n\t}\n\n\t/**\n\t * 返回Iterable中最大的N个对象, back by guava.\n\t */\n\tpublic static <T extends Comparable<?>> List<T> topN(Iterable<T> coll, int n) {\n\t\treturn Ordering.natural().greatestOf(coll, n);\n\t}\n\n\t/**\n\t * 返回Iterable中最大的N个对象, back by guava.\n\t */\n\tpublic static <T> List<T> topN(Iterable<T> coll, int n, Comparator<? super T> comp) {\n\t\treturn Ordering.from(comp).greatestOf(coll, n);\n\t}\n\n\t/**\n\t * 返回Iterable中最小的N个对象, back by guava.\n\t */\n\tpublic static <T extends Comparable<?>> List<T> bottomN(Iterable<T> coll, int n) {\n\t\treturn Ordering.natural().leastOf(coll, n);\n\t}\n\n\t/**\n\t * 返回Iterable中最小的N个对象, back by guava.\n\t */\n\tpublic static <T> List<T> bottomN(Iterable<T> coll, int n, Comparator<? super T> comp) {\n\t\treturn Ordering.from(comp).leastOf(coll, n);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/ListUtil.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport com.google.common.collect.Lists;\n\n/**\n * 关于List的工具集合.\n * \n * 1. 常用函数(如是否为空，sort/binarySearch/shuffle/reverse(via JDK Collection)\n * \n * 2. 各种构造函数(from guava and JDK Collection)\n * \n * 3. 各种扩展List类型的创建函数\n * \n * 5. 集合运算：交集，并集, 差集, 补集，from Commons Collection，但对其不合理的地方做了修正\n */\n@SuppressWarnings(\"unchecked\")\npublic class ListUtil {\n\n\t/**\n\t * 判断是否为空.\n\t */\n\tpublic static boolean isEmpty(List<?> list) {\n\t\treturn (list == null) || list.isEmpty();\n\t}\n\n\t/**\n\t * 判断是否不为空.\n\t */\n\tpublic static boolean isNotEmpty(List<?> list) {\n\t\treturn (list != null) && !(list.isEmpty());\n\t}\n\n\t/**\n\t * 获取第一个元素, 如果List为空返回 null.\n\t */\n\tpublic static <T> T getFirst(List<T> list) {\n\t\tif (isEmpty(list)) {\n\t\t\treturn null;\n\t\t}\n\t\treturn list.get(0);\n\t}\n\n\t/**\n\t * 获取最后一个元素，如果List为空返回null.\n\t */\n\tpublic static <T> T getLast(List<T> list) {\n\t\tif (isEmpty(list)) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn list.get(list.size() - 1);\n\t}\n\n\t///////////////// from Guava的构造函数///////////////////\n\t/**\n\t * 根据等号左边的类型，构造类型正确的ArrayList.\n\t *\n\t * @deprecated JDK7开始已经简化 \n\t */\n\t@Deprecated\n\tpublic static <T> ArrayList<T> newArrayList() {\n\t\treturn new ArrayList<T>();\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的ArrayList, 并初始化元素.\n\t * \n\t * @see com.google.common.collect.Lists#newArrayList(Object...)\n\t */\n\tpublic static <T> ArrayList<T> newArrayList(T... elements) {\n\t\treturn Lists.newArrayList(elements);\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的ArrayList, 并初始化元素.\n\t * \n\t * @see com.google.common.collect.Lists#newArrayList(Iterable)\n\t */\n\tpublic static <T> ArrayList<T> newArrayList(Iterable<T> elements) {\n\t\treturn Lists.newArrayList(elements);\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的ArrayList, 并初始化数组大小.\n\t * \n\t * @see com.google.common.collect.Lists#newArrayListWithCapacity(int)\n\t */\n\tpublic static <T> ArrayList<T> newArrayListWithCapacity(int initSize) {\n\t\treturn new ArrayList<T>(initSize);\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的LinkedList.\n\t * \n\t * @deprecated JDK7开始已经简化 \n\t */\n\t@Deprecated\n\tpublic static <T> LinkedList<T> newLinkedList() {\n\t\treturn new LinkedList<T>();\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的LinkedList.\n\t * \n\t * @see com.google.common.collect.Lists#newLinkedList()\n\t */\n\tpublic static <T> LinkedList<T> newLinkedList(Iterable<? extends T> elements) {\n\t\treturn Lists.newLinkedList(elements);\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的CopyOnWriteArrayList.\n\t * \n\t * @deprecated JDK7开始已经简化 \n\t */\n\t@Deprecated\n\tpublic static <T> CopyOnWriteArrayList<T> newCopyOnWriteArrayList() {\n\t\treturn new CopyOnWriteArrayList<T>();\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型转换的CopyOnWriteArrayList, 并初始化元素.\n\t */\n\tpublic static <T> CopyOnWriteArrayList<T> newCopyOnWriteArrayList(T... elements) {\n\t\treturn new CopyOnWriteArrayList<T>(elements);\n\t}\n\n\t///////////////// from JDK Collections的常用构造函数 ///////////////////\n\n\t/**\n\t * 返回一个空的结构特殊的List，节约空间.\n\t * \n\t * 注意返回的List不可写, 写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#emptyList()\n\t */\n\tpublic static final <T> List<T> emptyList() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t/**\n\t * 如果list为null，转化为一个安全的空List.\n\t * \n\t * 注意返回的List不可写, 写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#emptyList()\n\t */\n\tpublic static <T> List<T> emptyListIfNull(final List<T> list) {\n\t\treturn list == null ? (List<T>) Collections.EMPTY_LIST : list;\n\t}\n\n\t/**\n\t * 返回只含一个元素但结构特殊的List，节约空间.\n\t * \n\t * 注意返回的List不可写, 写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#singletonList(Object)\n\t */\n\tpublic static <T> List<T> singletonList(T o) {\n\t\treturn Collections.singletonList(o);\n\t}\n\n\t/**\n\t * 返回包装后不可修改的List.\n\t * \n\t * 如果尝试写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#unmodifiableList(List)\n\t */\n\tpublic static <T> List<T> unmodifiableList(List<? extends T> list) {\n\t\treturn Collections.unmodifiableList(list);\n\t}\n\n\t/**\n\t * 返回包装后同步的List，所有方法都会被synchronized原语同步.\n\t * \n\t * 用于CopyOnWriteArrayList与 ArrayDequeue均不符合的场景\n\t */\n\tpublic static <T> List<T> synchronizedList(List<T> list) {\n\t\treturn Collections.synchronizedList(list);\n\t}\n\n\t///////////////// from JDK Collections的常用函数 ///////////////////\n\n\t/**\n\t * 升序排序, 采用JDK认为最优的排序算法, 使用元素自身的compareTo()方法\n\t * \n\t * @see java.util.Collections#sort(List)\n\t */\n\tpublic static <T extends Comparable<? super T>> void sort(List<T> list) {\n\t\tCollections.sort(list);\n\t}\n\n\t/**\n\t * 倒序排序, 采用JDK认为最优的排序算法,使用元素自身的compareTo()方法\n\t * \n\t * @see java.util.Collections#sort(List)\n\t */\n\tpublic static <T extends Comparable<? super T>> void sortReverse(List<T> list) {\n\t\tCollections.sort(list, Collections.reverseOrder());\n\t}\n\n\t/**\n\t * 升序排序, 采用JDK认为最优的排序算法, 使用Comparator.\n\t * \n\t * @see java.util.Collections#sort(List, Comparator)\n\t */\n\tpublic static <T> void sort(List<T> list, Comparator<? super T> c) {\n\t\tCollections.sort(list, c);\n\t}\n\n\t/**\n\t * 倒序排序, 采用JDK认为最优的排序算法, 使用Comparator\n\t * \n\t * @see java.util.Collections#sort(List, Comparator)\n\t */\n\tpublic static <T> void sortReverse(List<T> list, Comparator<? super T> c) {\n\t\tCollections.sort(list, Collections.reverseOrder(c));\n\t}\n\n\t/**\n\t * 二分法快速查找对象, 使用Comparable对象自身的比较.\n\t * \n\t * list必须已按升序排序.\n\t * \n\t * 如果不存在，返回一个负数，代表如果要插入这个对象，应该插入的位置\n\t * \n\t * @see java.util.Collections#binarySearch(List, Object)\n\t */\n\tpublic static <T> int binarySearch(List<? extends Comparable<? super T>> sortedList, T key) {\n\t\treturn Collections.binarySearch(sortedList, key);\n\t}\n\n\t/**\n\t * 二分法快速查找对象，使用Comparator.\n\t * \n\t * list必须已按升序排序.\n\t * \n\t * 如果不存在，返回一个负数，代表如果要插入这个对象，应该插入的位置\n\t * \n\t * @see java.util.Collections#binarySearch(List, Object, Comparator)\n\t */\n\tpublic static <T> int binarySearch(List<? extends T> sortedList, T key, Comparator<? super T> c) {\n\t\treturn Collections.binarySearch(sortedList, key, c);\n\t}\n\n\t/**\n\t * 随机乱序，使用默认的Random.\n\t * \n\t * @see java.util.Collections#shuffle(List)\n\t */\n\tpublic static void shuffle(List<?> list) {\n\t\tCollections.shuffle(list);\n\t}\n\n\t/**\n\t * 随机乱序，使用传入的Random.\n\t * \n\t * @see java.util.Collections#shuffle(List, Random)\n\t */\n\tpublic static void shuffle(List<?> list, Random rnd) {\n\t\tCollections.shuffle(list, rnd);\n\t}\n\n\t/**\n\t * 返回一个倒转顺序访问的List，仅仅是一个倒序的View，不会实际多生成一个List\n\t * \n\t * @see com.google.common.collect.Lists#reverse(List)\n\t */\n\tpublic static <T> List<T> reverse(final List<T> list) {\n\t\treturn Lists.reverse(list);\n\t}\n\t///////////////// from guava的函数 ///////////////////\n\n\t/**\n\t * List分页函数\n\t */\n\tpublic static <T> List<List<T>> partition(List<T> list, int size) {\n\t\treturn Lists.partition(list, size);\n\t}\n\n\t///////////////// 其他处理函数 ///////////////\n\n\t/**\n\t * 清理掉List中的Null对象\n\t */\n\tpublic static <T> void notNullList(List<T> list) {\n\t\tif (isEmpty(list)) {\n\t\t\treturn;\n\t\t}\n\n\t\tIterator<T> ite = list.iterator();\n\t\twhile (ite.hasNext()) {\n\t\t\tT obj = ite.next();\n\t\t\t// 清理掉null的集合\n\t\t\tif (null == obj) {\n\t\t\t\tite.remove();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static <T> void uniqueNotNullList(List<T> list) {\n\t\tif (isEmpty(list)) {\n\t\t\treturn;\n\t\t}\n\t\tIterator<T> ite = list.iterator();\n\t\tSet<T> set = new HashSet<>((int) (list.size() / 0.75F + 1.0F));\n\n\t\twhile (ite.hasNext()) {\n\t\t\tT obj = ite.next();\n\t\t\t// 清理掉null的集合\n\t\t\tif (null == obj) {\n\t\t\t\tite.remove();\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// 清理掉重复的集合\n\t\t\tif (set.contains(obj)) {\n\t\t\t\tite.remove();\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tset.add(obj);\n\t\t}\n\t}\n\n\n\t///////////////// 集合运算 ///////////////////\n\n\t/**\n\t * list1,list2的并集（在list1或list2中的对象），产生新List\n\t * \n\t * 对比Apache Common Collection4 ListUtils, 优化了初始大小\n\t */\n\tpublic static <E> List<E> union(final List<? extends E> list1, final List<? extends E> list2) {\n\t\tfinal List<E> result = new ArrayList<E>(list1.size() + list2.size());\n\t\tresult.addAll(list1);\n\t\tresult.addAll(list2);\n\t\treturn result;\n\t}\n\n\t/**\n\t * list1, list2的交集（同时在list1和list2的对象），产生新List\n\t * \n\t * copy from Apache Common Collection4 ListUtils，但其做了不合理的去重，因此重新改为性能较低但不去重的版本\n\t * \n\t * 与List.retainAll()相比，考虑了的List中相同元素出现的次数, 如\"a\"在list1出现两次，而在list2中只出现一次，则交集里会保留一个\"a\".\n\t */\n\tpublic static <T> List<T> intersection(final List<? extends T> list1, final List<? extends T> list2) {\n\t\tList<? extends T> smaller = list1;\n\t\tList<? extends T> larger = list2;\n\t\tif (list1.size() > list2.size()) {\n\t\t\tsmaller = list2;\n\t\t\tlarger = list1;\n\t\t}\n\n\t\t// 克隆一个可修改的副本\n\t\tList<T> newSmaller = new ArrayList<T>(smaller);\n\t\tList<T> result = new ArrayList<T>(smaller.size());\n\t\tfor (final T e : larger) {\n\t\t\tif (newSmaller.contains(e)) {\n\t\t\t\tresult.add(e);\n\t\t\t\tnewSmaller.remove(e);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * list1, list2的差集（在list1，不在list2中的对象），产生新List.\n\t * \n\t * 与List.removeAll()相比，会计算元素出现的次数，如\"a\"在list1出现两次，而在list2中只出现一次，则差集里会保留一个\"a\".\n\t */\n\tpublic static <T> List<T> difference(final List<? extends T> list1, final List<? extends T> list2) {\n\t\tfinal List<T> result = new ArrayList<T>(list1);\n\t\tfinal Iterator<? extends T> iterator = list2.iterator();\n\n\t\twhile (iterator.hasNext()) {\n\t\t\tresult.remove(iterator.next());\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * list1, list2的补集（在list1或list2中，但不在交集中的对象，又叫反交集）产生新List.\n\t * \n\t * copy from Apache Common Collection4 ListUtils，但其并集－交集时，初始大小没有对交集*2，所以做了修改\n\t */\n\tpublic static <T> List<T> disjoint(final List<? extends T> list1, final List<? extends T> list2) {\n\t\tList<T> intersection = intersection(list1, list2);\n\t\tList<T> towIntersection = union(intersection, intersection);\n\t\treturn difference(union(list1, list2), towIntersection);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/MapUtil.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.EnumMap;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\nimport java.util.SortedMap;\nimport java.util.TreeMap;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.concurrent.ConcurrentSkipListMap;\n\nimport org.apache.commons.lang3.Validate;\n\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.MapDifference;\nimport com.google.common.collect.Maps;\nimport com.google.common.collect.Ordering;\nimport com.vip.vjtools.vjkit.base.annotation.NotNull;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 关于Map的工具集合，\n * \n * 1. 常用函数(如是否为空, 两个map的Diff对比，针对value值的排序)\n * \n * 2. 对于并发Map，增加putIfAbsent(返回最终值版), createIfAbsent这两个重要函数(from Common Lang)\n * \n * 3. 便捷的构造函数(via guava,Java Collections，并增加了用数组，List等方式初始化Map的函数)\n * \n * 4. JDK Collections的empty,singleton\n */\n@SuppressWarnings(\"unchecked\")\npublic class MapUtil {\n\n\tpublic static final float DEFAULT_LOAD_FACTOR = 0.75f;\n\n\t/**\n\t * 判断是否为空.\n\t */\n\tpublic static boolean isEmpty(final Map<?, ?> map) {\n\t\treturn (map == null) || map.isEmpty();\n\t}\n\n\t/**\n\t * 判断是否为空.\n\t */\n\tpublic static boolean isNotEmpty(final Map<?, ?> map) {\n\t\treturn (map != null) && !map.isEmpty();\n\t}\n\n\t/**\n\t * ConcurrentMap的putIfAbsent()返回之前的Value，此函数封装返回最终存储在Map中的Value\n\t * \n\t * @see org.apache.commons.lang3.concurrent.ConcurrentUtils#putIfAbsent(ConcurrentMap, Object, Object)\n\t */\n\tpublic static <K, V> V putIfAbsentReturnLast(@NotNull final ConcurrentMap<K, V> map, final K key, final V value) {\n\t\tfinal V result = map.putIfAbsent(key, value);\n\t\treturn result != null ? result : value;\n\t}\n\n\t/**\n\t * 如果Key不存在则创建，返回最后存储在Map中的Value.\n\t * \n\t * 如果创建Value对象有一定成本, 直接使用PutIfAbsent可能重复浪费，则使用此类，传入一个被回调的ValueCreator，Lazy创建对象。\n\t * \n\t * @see org.apache.commons.lang3.concurrent.ConcurrentUtils#createIfAbsent(ConcurrentMap, Object,\n\t * org.apache.commons.lang3.concurrent.ConcurrentInitializer)\n\t */\n\tpublic static <K, V> V createIfAbsentReturnLast(@NotNull final ConcurrentMap<K, V> map, final K key,\n\t\t\t@NotNull final ValueCreator<? extends V> creator) {\n\t\tfinal V value = map.get(key);\n\t\tif (value == null) {\n\t\t\treturn putIfAbsentReturnLast(map, key, creator.get());\n\t\t}\n\t\treturn value;\n\t}\n\n\t/**\n\t * Lazy创建Value值的回调类\n\t * \n\t * @see MapUtil#createIfAbsentReturnLast(ConcurrentMap, Object, ValueCreator)\n\t */\n\tpublic interface ValueCreator<T> {\n\t\t/**\n\t\t * 创建对象\n\t\t */\n\t\tT get();\n\t}\n\n\t///////////////// from Guava的构造函数///////////////////\n\n\t/**\n\t * 根据等号左边的类型, 构造类型正确的HashMap.\n\t * \n\t * 未初始化数组大小, 默认为16个桶.\n\t * \n\t * @deprecated JDK7开始已经简化 \n\t */\n\t@Deprecated\n\tpublic static <K, V> HashMap<K, V> newHashMap() {\n\t\treturn new HashMap<K, V>();\n\t}\n\n\t/**\n\t * 根据等号左边的类型, 构造类型正确的HashMap.\n\t * \n\t * 注意HashMap中有0.75的加载因子的影响, 需要进行运算后才能正确初始化HashMap的大小.\n\t * \n\t * 加载因子也是HashMap中减少Hash冲突的重要一环，如果读写频繁，总记录数不多的Map，可以比默认值0.75进一步降低，建议0.5\n\t * \n\t * @see com.google.common.collect.Maps#newHashMap\n\t */\n\tpublic static <K, V> HashMap<K, V> newHashMapWithCapacity(int expectedSize, float loadFactor) {\n\t\tint finalSize = (int) (expectedSize / loadFactor + 1.0F);\n\t\treturn new HashMap<K, V>(finalSize, loadFactor);\n\t}\n\n\t/**\n\t * 根据等号左边的类型, 构造类型正确的HashMap.\n\t * \n\t * 同时初始化第一个元素\n\t */\n\tpublic static <K, V> HashMap<K, V> newHashMap(final K key, final V value) {\n\t\tHashMap<K, V> map = new HashMap<K, V>();\n\t\tmap.put(key, value);\n\t\treturn map;\n\t}\n\n\t/**\n\t * 根据等号左边的类型, 构造类型正确的HashMap.\n\t * \n\t * 同时初始化元素.\n\t */\n\tpublic static <K, V> HashMap<K, V> newHashMap(@NotNull final K[] keys, @NotNull final V[] values) {\n\t\tValidate.isTrue(keys.length == values.length, \"keys.length is %d but values.length is %d\", keys.length,\n\t\t\t\tvalues.length);\n\n\t\tHashMap<K, V> map = new HashMap<K, V>(keys.length * 2);\n\n\t\tfor (int i = 0; i < keys.length; i++) {\n\t\t\tmap.put(keys[i], values[i]);\n\t\t}\n\n\t\treturn map;\n\t}\n\n\t/**\n\t * 根据等号左边的类型, 构造类型正确的HashMap.\n\t * \n\t * 同时初始化元素.\n\t */\n\tpublic static <K, V> HashMap<K, V> newHashMap(@NotNull final List<K> keys, @NotNull final List<V> values) {\n\t\tValidate.isTrue(keys.size() == values.size(), \"keys.length is %s  but values.length is %s\", keys.size(),\n\t\t\t\tvalues.size());\n\n\t\tHashMap<K, V> map = new HashMap<K, V>(keys.size() * 2);\n\t\tIterator<K> keyIt = keys.iterator();\n\t\tIterator<V> valueIt = values.iterator();\n\n\t\twhile (keyIt.hasNext()) {\n\t\t\tmap.put(keyIt.next(), valueIt.next());\n\t\t}\n\n\t\treturn map;\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的TreeMap.\n\t * \n\t * @see com.google.common.collect.Maps#newTreeMap()\n\t */\n\t@SuppressWarnings(\"rawtypes\")\n\tpublic static <K extends Comparable, V> TreeMap<K, V> newSortedMap() {\n\t\treturn new TreeMap<K, V>();\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的TreeMap.\n\t * \n\t * @see com.google.common.collect.Maps#newTreeMap(Comparator)\n\t */\n\tpublic static <C, K extends C, V> TreeMap<K, V> newSortedMap(@Nullable Comparator<C> comparator) {\n\t\treturn Maps.newTreeMap(comparator);\n\t}\n\n\t/**\n\t * 相比HashMap，当key是枚举类时, 性能与空间占用俱佳.\n\t */\n\tpublic static <K extends Enum<K>, V> EnumMap<K, V> newEnumMap(@NotNull Class<K> type) {\n\t\treturn new EnumMap<K, V>(Preconditions.checkNotNull(type));\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的ConcurrentHashMap.\n\t */\n\tpublic static <K, V> ConcurrentHashMap<K, V> newConcurrentHashMap() {\n\t\treturn new ConcurrentHashMap<K, V>();\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的ConcurrentSkipListMap.\n\t */\n\tpublic static <K, V> ConcurrentSkipListMap<K, V> newConcurrentSortedMap() {\n\t\treturn new ConcurrentSkipListMap<K, V>();\n\t}\n\n\t///////////////// from JDK Collections的常用构造函数 ///////////////////\n\n\t/**\n\t * 返回一个空的结构特殊的Map，节约空间.\n\t *\n\t * 注意返回的Map不可写, 写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#emptyMap()\n\t */\n\tpublic static <K, V> Map<K, V> emptyMap() {\n\t\treturn Collections.emptyMap();\n\t}\n\n\t/**\n\t * 如果map为null，转化为一个安全的空Map.\n\t * \n\t * 注意返回的Map不可写, 写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#emptyMap()\n\t */\n\tpublic static <K, V> Map<K, V> emptyMapIfNull(final Map<K, V> map) {\n\t\treturn map == null ? (Map<K, V>) Collections.EMPTY_MAP : map;\n\t}\n\n\t/**\n\t * 返回一个只含一个元素但结构特殊的Map，节约空间.\n\t * \n\t * 注意返回的Map不可写, 写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#singletonMap(Object, Object)\n\t */\n\tpublic static <K, V> Map<K, V> singletonMap(final K key, final V value) {\n\t\treturn Collections.singletonMap(key, value);\n\t}\n\n\t/**\n\t * 返回包装后不可修改的Map.\n\t * \n\t * 如果尝试修改，会抛出UnsupportedOperationException\n\t * \n\t * @see java.util.Collections#unmodifiableMap(Map)\n\t */\n\tpublic static <K, V> Map<K, V> unmodifiableMap(final Map<? extends K, ? extends V> m) {\n\t\treturn Collections.unmodifiableMap(m);\n\t}\n\n\t/**\n\t * 返回包装后不可修改的有序Map.\n\t * \n\t * @see java.util.Collections#unmodifiableSortedMap(SortedMap)\n\t */\n\tpublic static <K, V> SortedMap<K, V> unmodifiableSortedMap(final SortedMap<K, ? extends V> m) {\n\t\treturn Collections.unmodifiableSortedMap(m);\n\t}\n\n\t//////// 对两个Map进行diff的操作 ///////\n\t/**\n\t * 对两个Map进行比较，返回MapDifference，然后各种妙用.\n\t * \n\t * 包括key的差集，key的交集，以及key相同但value不同的元素。\n\t * \n\t * @see com.google.common.collect.MapDifference\n\t */\n\tpublic static <K, V> MapDifference<K, V> difference(Map<? extends K, ? extends V> left,\n\t\t\tMap<? extends K, ? extends V> right) {\n\t\treturn Maps.difference(left, right);\n\t}\n\n\t//////////// 按值排序及取TOP N的操作 /////////\n\t/**\n\t * 对一个Map按Value进行排序，返回排序LinkedHashMap，多用于Value是Counter的情况.\n\t * \n\t * @param reverse 按Value的倒序 or 正序排列\n\t */\n\tpublic static <K, V extends Comparable> Map<K, V> sortByValue(Map<K, V> map, final boolean reverse) {\n\t\treturn sortByValueInternal(map, reverse ? Ordering.from(new ComparableEntryValueComparator<K, V>()).reverse()\n\t\t\t\t: new ComparableEntryValueComparator<K, V>());\n\t}\n\n\t/**\n\t * 对一个Map按Value进行排序，返回排序LinkedHashMap.\n\t */\n\tpublic static <K, V> Map<K, V> sortByValue(Map<K, V> map, final Comparator<? super V> comparator) {\n\t\treturn sortByValueInternal(map, new EntryValueComparator<K, V>(comparator));\n\t}\n\n\tprivate static <K, V> Map<K, V> sortByValueInternal(Map<K, V> map, Comparator<Entry<K, V>> comparator) {\n\t\tSet<Entry<K, V>> entrySet = map.entrySet();\n\t\tEntry<K, V>[] entryArray = entrySet.toArray(new Entry[0]);\n\n\t\tArrays.sort(entryArray, comparator);\n\n\t\tMap<K, V> result = new LinkedHashMap<K, V>();\n\t\tfor (Entry<K, V> entry : entryArray) {\n\t\t\tresult.put(entry.getKey(), entry.getValue());\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * 对一个Map按Value进行排序，返回排序LinkedHashMap，最多只返回n条，多用于Value是Counter的情况.\n\t * @param reverse 按Value的倒序 or 正序排列\n\t */\n\tpublic static <K, V extends Comparable> Map<K, V> topNByValue(Map<K, V> map, final boolean reverse, int n) {\n\t\treturn topNByValueInternal(map, n, reverse ? Ordering.from(new ComparableEntryValueComparator<K, V>()).reverse()\n\t\t\t\t: new ComparableEntryValueComparator<K, V>());\n\t}\n\n\t/**\n\t * 对一个Map按Value进行排序，返回排序LinkedHashMap, 最多只返回n条，多用于Value是Counter的情况.\n\t */\n\tpublic static <K, V> Map<K, V> topNByValue(Map<K, V> map, final Comparator<? super V> comparator, int n) {\n\t\treturn topNByValueInternal(map, n, new EntryValueComparator<K, V>(comparator));\n\t}\n\n\tprivate static <K, V> Map<K, V> topNByValueInternal(Map<K, V> map, int n, Comparator<Entry<K, V>> comparator) {\n\t\tSet<Entry<K, V>> entrySet = map.entrySet();\n\t\tEntry<K, V>[] entryArray = entrySet.toArray(new Entry[0]);\n\t\tArrays.sort(entryArray, comparator);\n\n\t\tMap<K, V> result = new LinkedHashMap<K, V>();\n\t\tint size = Math.min(n, entryArray.length);\n\t\tfor (int i = 0; i < size; i++) {\n\t\t\tEntry<K, V> entry = entryArray[i];\n\t\t\tresult.put(entry.getKey(), entry.getValue());\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate static final class ComparableEntryValueComparator<K, V extends Comparable>\n\t\t\timplements Comparator<Entry<K, V>> {\n\t\t@Override\n\t\tpublic int compare(Entry<K, V> o1, Entry<K, V> o2) {\n\t\t\treturn (o1.getValue()).compareTo(o2.getValue());\n\t\t}\n\t}\n\n\tprivate static final class EntryValueComparator<K, V> implements Comparator<Entry<K, V>> {\n\t\tprivate final Comparator<? super V> comparator;\n\n\t\tprivate EntryValueComparator(Comparator<? super V> comparator2) {\n\t\t\tthis.comparator = comparator2;\n\t\t}\n\n\t\t@Override\n\t\tpublic int compare(Entry<K, V> o1, Entry<K, V> o2) {\n\t\t\treturn comparator.compare(o1.getValue(), o2.getValue());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/QueueUtil.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport java.util.ArrayDeque;\nimport java.util.Deque;\nimport java.util.LinkedList;\nimport java.util.concurrent.ArrayBlockingQueue;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport java.util.concurrent.LinkedBlockingDeque;\nimport java.util.concurrent.LinkedBlockingQueue;\n\n/**\n * Queue工具集.\n * \n * 各种Queue，Dequeue的创建\n */\npublic class QueueUtil {\n\n\t/**\n\t * 创建ArrayDeque (JDK无ArrayQueue)\n\t * \n\t * 需设置初始长度，默认为16，数组满时成倍扩容\n\t */\n\tpublic static <E> ArrayDeque<E> newArrayDeque(int initSize) {\n\t\treturn new ArrayDeque<E>(initSize);\n\t}\n\n\t/**\n\t * 创建LinkedDeque (LinkedList实现了Deque接口)\n\t */\n\tpublic static <E> LinkedList<E> newLinkedDeque() {\n\t\treturn new LinkedList<E>();\n\t}\n\n\t/**\n\t * 创建无阻塞情况下，性能最优的并发队列\n\t */\n\tpublic static <E> ConcurrentLinkedQueue<E> newConcurrentNonBlockingQueue() {\n\t\treturn new ConcurrentLinkedQueue<E>();\n\t}\n\n\t/**\n\t * 创建无阻塞情况下，性能最优的并发双端队列\n\t */\n\tpublic static <E> Deque<E> newConcurrentNonBlockingDeque() {\n\t\treturn new java.util.concurrent.ConcurrentLinkedDeque<E>();\n\t}\n\n\t/**\n\t * 创建并发阻塞情况下，长度不受限的队列.\n\t * \n\t * 长度不受限，即生产者不会因为满而阻塞，但消费者会因为空而阻塞.\n\t */\n\tpublic static <E> LinkedBlockingQueue<E> newBlockingUnlimitQueue() {\n\t\treturn new LinkedBlockingQueue<E>();\n\t}\n\n\t/**\n\t * 创建并发阻塞情况下，长度不受限的双端队列.\n\t * \n\t * 长度不受限，即生产者不会因为满而阻塞，但消费者会因为空而阻塞.\n\t */\n\tpublic static <E> LinkedBlockingDeque<E> newBlockingUnlimitDeque() {\n\t\treturn new LinkedBlockingDeque<E>();\n\t}\n\n\t/**\n\t * 创建并发阻塞情况下，长度受限，更节约内存，但共用一把锁的队列（无双端队列实现）.\n\t */\n\tpublic static <E> ArrayBlockingQueue<E> newArrayBlockingQueue(int capacity) {\n\t\treturn new ArrayBlockingQueue<E>(capacity);\n\t}\n\n\t/**\n\t * 创建并发阻塞情况下，长度受限，头队尾两把锁, 但使用更多内存的队列.\n\t */\n\tpublic static <E> LinkedBlockingQueue<E> newLinkedBlockingQueue(int capacity) {\n\t\treturn new LinkedBlockingQueue<E>(capacity);\n\t}\n\n\t/**\n\t * 创建并发阻塞情况下，长度受限，头队尾两把锁, 但使用更多内存的双端队列.\n\t */\n\tpublic static <E> LinkedBlockingDeque<E> newBlockingDeque(int capacity) {\n\t\treturn new LinkedBlockingDeque<E>(capacity);\n\t}\n\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/SetUtil.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeSet;\n\nimport com.google.common.collect.Sets;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\nimport com.vip.vjtools.vjkit.collection.type.ConcurrentHashSet;\n\n/**\n * 关于Set的工具集合.\n * \n * 1. 各种Set的创建函数, 包括ConcurrentHashSet\n * \n * 2. 集合运算函数(交集，并集等,from guava)\n */\npublic class SetUtil {\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的HashSet.\n\t * \n\t * @see com.google.common.collect.Sets#newHashSet()\n\t */\n\tpublic static <T> HashSet<T> newHashSet() {\n\t\treturn new HashSet<T>();\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的HashSet, 并初始化元素.\n\t * \n\t * @see com.google.common.collect.Sets#newHashSet(Object...)\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <T> HashSet<T> newHashSet(T... elements) {\n\t\treturn Sets.newHashSet(elements);\n\t}\n\n\t/**\n\t * HashSet涉及HashMap大小，因此建议在构造时传入需要初始的集合，其他如TreeSet不需要.\n\t * \n\t * @see com.google.common.collect.Sets#newHashSet(Iterable)\n\t */\n\tpublic static <T> HashSet<T> newHashSet(Iterable<? extends T> elements) {\n\t\treturn Sets.newHashSet(elements);\n\t}\n\n\t/**\n\t * 创建HashSet并设置初始大小，因为HashSet内部是HashMap，会计算LoadFactor后的真实大小.\n\t * \n\t * @see com.google.common.collect.Sets#newHashSetWithExpectedSize(int)\n\t */\n\tpublic static <T> HashSet<T> newHashSetWithCapacity(int expectedSize) {\n\t\treturn Sets.newHashSetWithExpectedSize(expectedSize);\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的TreeSet, 通过实现了Comparable的元素自身进行排序.\n\t * \n\t * @see com.google.common.collect.Sets#newTreeSet()\n\t */\n\t@SuppressWarnings(\"rawtypes\")\n\tpublic static <T extends Comparable> TreeSet<T> newSortedSet() {\n\t\treturn new TreeSet<T>();\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的TreeSet, 并设置comparator.\n\t * \n\t * @see com.google.common.collect.Sets#newTreeSet(Comparator)\n\t */\n\tpublic static <T> TreeSet<T> newSortedSet(@Nullable Comparator<? super T> comparator) {\n\t\treturn Sets.newTreeSet(comparator);\n\t}\n\n\t/**\n\t * 根据等号左边的类型，构造类型正确的ConcurrentHashSet\n\t */\n\tpublic static <T> ConcurrentHashSet<T> newConcurrentHashSet() {\n\t\treturn new ConcurrentHashSet<T>();\n\t}\n\n\t///////////////// from JDK Collections的常用构造函数 ///////////////////\n\n\t/**\n\t * 返回一个空的结构特殊的Set，节约空间.\n\t * \n\t * 注意返回的Set不可写, 写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#emptySet()\n\t */\n\tpublic static <T> Set<T> emptySet() {\n\t\treturn Collections.emptySet();\n\t}\n\n\t/**\n\t * 如果set为null，转化为一个安全的空Set.\n\t * \n\t * 注意返回的Set不可写, 写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#emptySet()\n\t */\n\tpublic static <T> Set<T> emptySetIfNull(final Set<T> set) {\n\t\treturn set == null ? (Set<T>) Collections.EMPTY_SET : set;\n\t}\n\n\t/**\n\t * 返回只含一个元素但结构特殊的Set，节约空间.\n\t * \n\t * 注意返回的Set不可写, 写入会抛出UnsupportedOperationException.\n\t * \n\t * @see java.util.Collections#singleton(Object)\n\t */\n\tpublic static <T> Set<T> singletonSet(T o) {\n\t\treturn Collections.singleton(o);\n\t}\n\n\t/**\n\t * 返回包装后不可修改的Set.\n\t * \n\t * 如果尝试修改，会抛出UnsupportedOperationException\n\t * \n\t * @see java.util.Collections#unmodifiableSet(Set)\n\t */\n\tpublic static <T> Set<T> unmodifiableSet(Set<? extends T> s) {\n\t\treturn Collections.unmodifiableSet(s);\n\t}\n\n\t/**\n\t * 从Map构造Set的大杀器, 可以用来制造各种Set\n\t * \n\t * @see java.util.Collections#newSetFromMap(Map)\n\t */\n\tpublic static <T> Set<T> newSetFromMap(Map<T, Boolean> map) {\n\t\treturn Collections.newSetFromMap(map);\n\t}\n\n\t//////////////// from guava的集合运算函数/////////////\n\t/**\n\t * set1, set2的并集（在set1或set2的对象）的只读view，不复制产生新的Set对象.\n\t * \n\t * 如果尝试写入该View会抛出UnsupportedOperationException\n\t */\n\tpublic static <E> Set<E> unionView(final Set<? extends E> set1, final Set<? extends E> set2) {\n\t\treturn Sets.union(set1, set2);\n\t}\n\n\t/**\n\t * set1, set2的交集（同时在set1和set2的对象）的只读view，不复制产生新的Set对象.\n\t * \n\t * 如果尝试写入该View会抛出UnsupportedOperationException\n\t */\n\tpublic static <E> Set<E> intersectionView(final Set<E> set1, final Set<?> set2) {\n\t\treturn Sets.intersection(set1, set2);\n\t}\n\n\t/**\n\t * set1, set2的差集（在set1，不在set2中的对象）的只读view，不复制产生新的Set对象.\n\t * \n\t * 如果尝试写入该View会抛出UnsupportedOperationException\n\t */\n\tpublic static <E> Set<E> differenceView(final Set<E> set1, final Set<?> set2) {\n\t\treturn Sets.difference(set1, set2);\n\t}\n\n\t/**\n\t * set1, set2的补集（在set1或set2中，但不在交集中的对象，又叫反交集）的只读view，不复制产生新的Set对象.\n\t * \n\t * 如果尝试写入该View会抛出UnsupportedOperationException\n\t */\n\tpublic static <E> Set<E> disjointView(final Set<? extends E> set1, final Set<? extends E> set2) {\n\t\treturn Sets.symmetricDifference(set1, set2);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/ConcurrentHashSet.java",
    "content": "package com.vip.vjtools.vjkit.collection.type;\n\nimport java.util.AbstractSet;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * JDK并没有提供ConcurrentHashSet，考虑到JDK的HashSet也是基于HashMap实现的，因此ConcurrentHashSet也由ConcurrentHashMap完成。\n * \n * 虽然也可以通过Collections.newSetFromMap(new ConcurrentHashMap())，\n * \n * 但声明一个单独的类型，阅读代码时能更清晰的知道set的并发友好性，代码来自JDK的SetFromMap，去除JDK8接口.\n */\npublic class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>, java.io.Serializable {\n\n\tprivate static final long serialVersionUID = -8672117787651310382L;\n\n\tprivate final Map<E, Boolean> m;\n\n\tprivate transient Set<E> s; // Its keySet\n\n\tpublic ConcurrentHashSet() {\n\t\tm = new ConcurrentHashMap<E, Boolean>();\n\t\ts = m.keySet();\n\t}\n\n\tpublic void clear() {\n\t\tm.clear();\n\t}\n\n\tpublic int size() {\n\t\treturn m.size();\n\t}\n\n\tpublic boolean isEmpty() {\n\t\treturn m.isEmpty();\n\t}\n\n\tpublic boolean contains(Object o) {\n\t\treturn m.containsKey(o);\n\t}\n\n\tpublic boolean remove(Object o) {\n\t\treturn m.remove(o) != null;\n\t}\n\n\tpublic boolean add(E e) {\n\t\treturn m.put(e, Boolean.TRUE) == null;\n\t}\n\n\tpublic Iterator<E> iterator() {\n\t\treturn s.iterator();\n\t}\n\n\tpublic Object[] toArray() {\n\t\treturn s.toArray();\n\t}\n\n\tpublic <T> T[] toArray(T[] a) {\n\t\treturn s.toArray(a);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn s.toString();\n\t}\n\n\tpublic int hashCode() {\n\t\treturn s.hashCode();\n\t}\n\n\tpublic boolean equals(Object o) {\n\t\treturn o == this || s.equals(o);\n\t}\n\n\tpublic boolean containsAll(Collection<?> c) {\n\t\treturn s.containsAll(c);\n\t}\n\n\tpublic boolean removeAll(Collection<?> c) {\n\t\treturn s.removeAll(c);\n\t}\n\n\tpublic boolean retainAll(Collection<?> c) {\n\t\treturn s.retainAll(c);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/MoreLists.java",
    "content": "package com.vip.vjtools.vjkit.collection.type;\n\nimport java.util.Comparator;\n\n/**\n * 特殊的List类型\n */\npublic class MoreLists {\n\n\t/**\n\t * 排序的ArrayList.\n\t * \n\t * from Jodd的新类型，插入时排序，但指定插入index的方法如add(index,element)不支持\n\t */\n\t@SuppressWarnings(\"rawtypes\")\n\tpublic static <T extends Comparable> SortedArrayList<T> createSortedArrayList() {\n\t\treturn new SortedArrayList<T>();\n\t}\n\n\t/**\n\t * 排序的ArrayList.\n\t * \n\t * from Jodd的新类型，插入时排序，但指定插入index的方法如add(index,element)不支持\n\t */\n\tpublic static <T> SortedArrayList<T> createSortedArrayList(Comparator<? super T> c) {\n\t\treturn new SortedArrayList<T>(c);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/MoreMaps.java",
    "content": "package com.vip.vjtools.vjkit.collection.type;\n\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.concurrent.ConcurrentMap;\n\nimport org.apache.commons.lang3.mutable.MutableInt;\nimport org.apache.commons.lang3.mutable.MutableLong;\n\nimport com.google.common.collect.ArrayListMultimap;\nimport com.google.common.collect.MapMaker;\nimport com.google.common.collect.MultimapBuilder;\nimport com.google.common.collect.SortedSetMultimap;\nimport com.google.common.collect.TreeRangeMap;\nimport com.google.common.util.concurrent.AtomicLongMap;\nimport com.vip.vjtools.vjkit.collection.type.primitive.IntObjectHashMap;\nimport com.vip.vjtools.vjkit.collection.type.primitive.LongObjectHashMap;\n\n/**\n * 来自Guava，Netty等的特殊Map类型\n */\npublic class MoreMaps {\n\n\t/**\n\t * 创建Key为弱引用的ConcurrentMap，Key对象可被回收.\n\t * \n\t * JDK没有WeakHashMap的并发实现, 由Guava提供\n\t */\n\tpublic static <K, V> ConcurrentMap<K, V> createWeakKeyConcurrentMap(int initialCapacity, int concurrencyLevel) {\n\t\treturn new MapMaker().weakKeys().initialCapacity(initialCapacity).concurrencyLevel(concurrencyLevel).makeMap();\n\t}\n\n\t/**\n\t * 创建Value为弱引用的ConcurrentMap，Value对象可被回收.\n\t * \n\t * JDK没有WeakHashMap的并发实现, 由Guava提供\n\t */\n\tpublic static <K, V> ConcurrentMap<K, V> createWeakValueConcurrentMap(int initialCapacity, int concurrencyLevel) {\n\t\treturn new MapMaker().weakValues().initialCapacity(initialCapacity).concurrencyLevel(concurrencyLevel)\n\t\t\t\t.makeMap();\n\t}\n\n\t/**\n\t * 创建移植自Netty的key为int的优化HashMap\n\t * \n\t * @param initialCapacity 默认为8\n\t * @param loadFactor 默认为0.5\n\t */\n\tpublic static <V> IntObjectHashMap<V> createPrimitiveIntKeyMap(int initialCapacity, float loadFactor) {\n\t\treturn new IntObjectHashMap<V>(initialCapacity, loadFactor);\n\t}\n\n\t/**\n\t * 创建移植自Netty的key为long的优化HashMap\n\t * \n\t * @param initialCapacity 默认为8\n\t * @param loadFactor 默认为0.5\n\t */\n\tpublic static <V> LongObjectHashMap<V> createPrimitiveLongKeyMap(int initialCapacity, float loadFactor) {\n\t\treturn new LongObjectHashMap<V>(initialCapacity, loadFactor);\n\t}\n\n\t/**\n\t * 创建值为可更改的Integer的HashMap. 可更改的Integer在更改时不需要重新创建Integer对象，节约了内存\n\t * \n\t * @param initialCapacity 建议为16\n\t * @param loadFactor 建议为0.5\n\t */\n\tpublic static <K> HashMap<K, MutableInt> createMutableIntValueMap(int initialCapacity, float loadFactor) {\n\t\treturn new HashMap<K, MutableInt>(initialCapacity, loadFactor);\n\t}\n\n\t/**\n\t * 创建值为可更改的Long的HashMap. 可更改的Long在更改时不需要重新创建Long对象，节约了内存\n\t * \n\t * @param initialCapacity 建议为16\n\t * @param loadFactor 建议为0.5\n\t */\n\tpublic static <K> HashMap<K, MutableLong> createMutableLongValueMap(int initialCapacity, float loadFactor) {\n\t\treturn new HashMap<K, MutableLong>(initialCapacity, loadFactor);\n\t}\n\n\t/**\n\t * 以Guava的AtomicLongMap，实现线程安全的HashMap<E,AtomicLong>结构的Counter\n\t */\n\tpublic static <E> AtomicLongMap<E> createConcurrentCounterMap() {\n\t\treturn AtomicLongMap.create();\n\t}\n\n\t/**\n\t * 以Guava的MultiMap，实现的HashMap<E,List<V>>结构的一个Key对应多个值的map.\n\t * \n\t * 注意非线程安全, MultiMap无线程安全的实现.\n\t * \n\t * 另有其他结构存储values的MultiMap，请自行参考MultimapBuilder使用.\n\t * \n\t * @param expectedKeys 默认为16\n\t * @param expectedValuesPerKey 默认为3\n\t */\n\tpublic static <K, V> ArrayListMultimap<K, V> createListMultiValueMap(int expectedKeys, int expectedValuesPerKey) {\n\t\treturn ArrayListMultimap.create(expectedKeys, expectedValuesPerKey);\n\t}\n\n\t/**\n\t * 以Guava的MultiMap，实现的HashMap<E,TreeSet<V>>结构的一个Key对应多个值的map.\n\t * \n\t * 注意非线程安全, MultiValueMap无线程安全的实现.\n\t * \n\t * 另有其他结构存储values的MultiMap，请自行参考MultimapBuilder使用.\n\t */\n\tpublic static <K, V extends Comparable> SortedSetMultimap<K, V> createSortedSetMultiValueMap() {\n\t\treturn MultimapBuilder.hashKeys().treeSetValues().build();\n\t}\n\n\t/**\n\t * 以Guava的MultiMap，实现的HashMap<E,TreeSet<V>>结构的一个Key对应多个值的map.\n\t * \n\t * 注意非线程安全, MultiValueMap无线程安全的实现.\n\t * \n\t * 另有其他结构存储values的MultiMap，请自行参考MultimapBuilder使用.\n\t */\n\tpublic static <K, V> SortedSetMultimap<K, V> createSortedSetMultiValueMap(Comparator<V> comparator) {\n\t\treturn (SortedSetMultimap<K, V>) MultimapBuilder.hashKeys().treeSetValues(comparator);\n\t}\n\n\t/**\n\t * 以Guava TreeRangeMap实现的, 一段范围的Key指向同一个Value的Map\n\t */\n\t@SuppressWarnings(\"rawtypes\")\n\tpublic static <K extends Comparable, V> TreeRangeMap<K, V> createRangeMap() {\n\t\treturn TreeRangeMap.create();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/MoreQueues.java",
    "content": "package com.vip.vjtools.vjkit.collection.type;\n\nimport java.util.ArrayDeque;\nimport java.util.Collections;\nimport java.util.Queue;\n\nimport com.google.common.collect.EvictingQueue;\nimport com.vip.vjtools.vjkit.collection.QueueUtil;\n\n/**\n * 特殊类型Queue:LIFO的Stack, LRU的Queue\n */\npublic class MoreQueues {\n\n\t//////////////// 特殊类型Queue：Stack ///////////\n\n\t/**\n\t * 支持后进先出的栈，用ArrayDeque实现, 经过Collections#asLifoQueue转换顺序\n\t * \n\t * 需设置初始长度，默认为16，数组满时成倍扩容\n\t * \n\t * @see Collections#asLifoQueue\n\t */\n\tpublic static <E> Queue<E> createStack(int initSize) {\n\t\treturn Collections.asLifoQueue(new ArrayDeque<E>(initSize));\n\t}\n\n\t/**\n\t * 支持后进先出的无阻塞的并发栈，用ConcurrentLinkedDeque实现，经过Collections#asLifoQueue转换顺序\n\t * \n\t * 另对于BlockingQueue接口， JDK暂无Lifo倒转实现，因此只能直接使用未调转顺序的LinkedBlockingDeque\n\t * \n\t * @see Collections#asLifoQueue\n\t */\n\tpublic static <E> Queue<E> createConcurrentStack() {\n\t\treturn (Queue<E>) Collections.asLifoQueue(QueueUtil.newConcurrentNonBlockingDeque());\n\t}\n\n\t//////////////// 特殊类型Queue：LRUQueue ///////////\n\n\t/**\n\t * LRUQueue, 如果Queue已满，则删除最旧的元素.\n\t * \n\t * 内部实现是ArrayDeque\n\t */\n\tpublic static <E> EvictingQueue<E> createLRUQueue(int maxSize) {\n\t\treturn EvictingQueue.create(maxSize);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/SortedArrayList.java",
    "content": "// Copyright (c) 2003-present, Jodd Team (http://jodd.org)\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n//\n// 1. Redistributions of source code must retain the above copyright notice,\n// this list of conditions and the following disclaimer.\n//\n// 2. Redistributions in binary form must reproduce the above copyright\n// notice, this list of conditions and the following disclaimer in the\n// documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n// POSSIBILITY OF SUCH DAMAGE.\npackage com.vip.vjtools.vjkit.collection.type;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Comparator;\nimport java.util.Iterator;\n\n/**\n * 从Jodd整体复制，部分指定了index的操作不支持，如 add(index, element)\n * \n * 修改包括：改进Comparator泛型定义，findInsertionPoint的位移改进\n * \n * https://github.com/oblac/jodd/blob/master/jodd-core/src/main/java/jodd/util/collection/SortedArrayList.java\n * \n * An extension of <code>ArrayList</code> that insures that all of the items\n * added are sorted. <b>This breaks original list contract!</b>.\n * A binary search method is used to provide a quick way to\n * auto sort this list.Note: Not all methods for adding and\n * removing elements are supported.\n */\npublic final class SortedArrayList<E> extends ArrayList<E> {\n\n\tprivate static final long serialVersionUID = -8301136559614447593L;\n\n\tprotected final Comparator<? super E> comparator;\n\n\t/**\n\t * Constructs a new <code>SortedArrayList</code>.\n\t */\n\tpublic SortedArrayList(Comparator<? super E> c) {\n\t\tcomparator = c;\n\t}\n\n\t/**\n\t * Constructs a new <code>SortedArrayList</code> expecting\n\t * elements are comparable.\n\t */\n\tpublic SortedArrayList() {\n\t\tcomparator = null;\n\t}\n\n\t/**\n\t * Constructs a new <code>SortedArrayList</code> expecting\n\t * elements are comparable.\n\t */\n\tpublic SortedArrayList(Collection<? extends E> c) {\n\t\tcomparator = null;\n\t\taddAll(c);\n\t}\n\n\t/**\n\t * Returns comparator assigned to this collection, if such exist.\n\t */\n\tpublic Comparator getComparator() {\n\t\treturn comparator;\n\t}\n\n\t// ---------------------------------------------------------------- override\n\n\t/**\n\t * Adds an Object to sorted list. Object is inserted at correct place, found\n\t * using binary search. If the same item exist, it will be put to the end of\n\t * the range.\n\t * <p>\n\t * This method breaks original list contract since objects are not\n\t * added at the list end, but in sorted manner.\n\t */\n\t@Override\n\tpublic boolean add(E o) {\n\t\tint idx = 0;\n\t\tif (!isEmpty()) {\n\t\t\tidx = findInsertionPoint(o);\n\t\t}\n\t\tsuper.add(idx, o);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Add all of the elements in the given collection to this list.\n\t */\n\t@Override\n\tpublic boolean addAll(Collection<? extends E> c) {\n\t\tIterator<? extends E> i = c.iterator();\n\t\tboolean changed = false;\n\t\twhile (i.hasNext()) {\n\t\t\tboolean ret = add(i.next());\n\t\t\tif (!changed) {\n\t\t\t\tchanged = ret;\n\t\t\t}\n\t\t}\n\t\treturn changed;\n\t}\n\n\t/**\n\t * Finds the index at which object should be inserted.\n\t */\n\tpublic int findInsertionPoint(E o) {\n\t\treturn findInsertionPoint(o, 0, size() - 1);\n\t}\n\n\t// ---------------------------------------------------------------- unsupported methods\n\n\t/**\n\t * @throws UnsupportedOperationException This method not supported.\n\t */\n\t@Override\n\t@Deprecated\n\tpublic void add(int index, E element) {\n\t\tthrow new UnsupportedOperationException();\n\t}\n\n\t/**\n\t * @throws UnsupportedOperationException This method not supported.\n\t */\n\t@Override\n\t@Deprecated\n\tpublic E set(int index, E element) {\n\t\tthrow new UnsupportedOperationException();\n\t}\n\n\t/**\n\t * @throws UnsupportedOperationException This method not supported.\n\t */\n\t@Override\n\t@Deprecated\n\tpublic boolean addAll(int index, Collection<? extends E> c) {\n\t\tthrow new UnsupportedOperationException();\n\t}\n\n\n\t// ---------------------------------------------------------------- sorting\n\n\t/**\n\t * Compares two keys using the correct comparison method for this\n\t * collection.\n\t */\n\t@SuppressWarnings({ \"unchecked\" })\n\tprotected int compare(E k1, E k2) {\n\t\tif (comparator == null) {\n\t\t\treturn ((Comparable) k1).compareTo(k2);\n\t\t}\n\t\treturn comparator.compare(k1, k2);\n\t}\n\n\t/**\n\t * Conducts a binary search to find the index where Object\n\t * should be inserted.\n\t */\n\tprotected int findInsertionPoint(E o, int originalLow, int originalHigh) {\n\t\tint low = originalLow;\n\t\tint high = originalHigh;\n\t\twhile (low <= high) {\n\t\t\tint mid = low + ((high - low) >>> 1);\n\t\t\tint delta = compare(get(mid), o);\n\n\t\t\tif (delta > 0) {\n\t\t\t\thigh = mid - 1;\n\t\t\t} else {\n\t\t\t\tlow = mid + 1;\n\t\t\t}\n\t\t}\n\n\t\treturn low;\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/primitive/IntObjectHashMap.java",
    "content": "/*\n * Copyright 2014 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License, version 2.0 (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of the License at:\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.vip.vjtools.vjkit.collection.type.primitive;\n\nimport java.util.AbstractCollection;\nimport java.util.AbstractSet;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.Set;\n\n\n/**\n * 移植Netty 4.1.9的Key为原子类型的集合类, 在数据结构上与HashMap不一样，空间占用与读写性能俱比原来更优.\n * \n * 原子类型集合类有多个实现，选择Netty是因为有在实战中使用.\n * \n * https://github.com/netty/netty/blob/4.1/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template\n * \n * A hash map implementation of {@link IntObjectMap} that uses open addressing for keys. To minimize the memory\n * footprint, this class uses open addressing rather than chaining. Collisions are resolved using linear probing.\n * Deletions implement compaction, so cost of remove can approach O(N) for full maps, which makes a small loadFactor\n * recommended.\n *\n * @param <V> The value type stored in the map.\n */\npublic class IntObjectHashMap<V> implements IntObjectMap<V> {\n\n\t/** Default initial capacity. Used if not specified in the constructor */\n\tpublic static final int DEFAULT_CAPACITY = 8;\n\n\t/** Default load factor. Used if not specified in the constructor */\n\tpublic static final float DEFAULT_LOAD_FACTOR = 0.5f;\n\n\t/**\n\t * Placeholder for null values, so we can use the actual null to mean available. (Better than using a placeholder\n\t * for available: less references for GC processing.)\n\t */\n\tprivate static final Object NULL_VALUE = new Object();\n\n\t/** The maximum number of elements allowed without allocating more space. */\n\tprivate int maxSize;\n\n\t/** The load factor for the map. Used to calculate {@link #maxSize}. */\n\tprivate final float loadFactor;\n\n\tprivate int[] keys;\n\tprivate V[] values;\n\tprivate int size;\n\tprivate int mask;\n\n\tprivate final Set<Integer> keySet = new KeySet();\n\tprivate final Set<Entry<Integer, V>> entrySet = new EntrySet();\n\tprivate final Iterable<PrimitiveEntry<V>> entries = new Iterable<PrimitiveEntry<V>>() {\n\t\t@Override\n\t\tpublic Iterator<PrimitiveEntry<V>> iterator() {\n\t\t\treturn new PrimitiveIterator();\n\t\t}\n\t};\n\n\tpublic IntObjectHashMap() {\n\t\tthis(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);\n\t}\n\n\tpublic IntObjectHashMap(int initialCapacity) {\n\t\tthis(initialCapacity, DEFAULT_LOAD_FACTOR);\n\t}\n\n\tpublic IntObjectHashMap(int initialCapacity, float loadFactor) {\n\t\tif (loadFactor <= 0.0f || loadFactor > 1.0f) {\n\t\t\t// Cannot exceed 1 because we can never store more than capacity elements;\n\t\t\t// using a bigger loadFactor would trigger rehashing before the desired load is reached.\n\t\t\tthrow new IllegalArgumentException(\"loadFactor must be > 0 and <= 1\");\n\t\t}\n\n\t\tthis.loadFactor = loadFactor;\n\n\t\t// Adjust the initial capacity if necessary.\n\t\tint capacity = safeFindNextPositivePowerOfTwo(initialCapacity);\n\t\tmask = capacity - 1;\n\n\t\t// Allocate the arrays.\n\t\tkeys = new int[capacity];\n\t\t@SuppressWarnings({ \"unchecked\", \"SuspiciousArrayCast\" })\n\t\tV[] temp = (V[]) new Object[capacity];\n\t\tvalues = temp;\n\n\t\t// Initialize the maximum size value.\n\t\tmaxSize = calcMaxSize(capacity);\n\t}\n\n\tprivate static <T> T toExternal(T value) {\n\t\tassert value != null : \"null is not a legitimate internal value. Concurrent Modification?\";\n\t\treturn value == NULL_VALUE ? null : value;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <T> T toInternal(T value) {\n\t\treturn value == null ? (T) NULL_VALUE : value;\n\t}\n\n\t@Override\n\tpublic V get(int key) {\n\t\tint index = indexOf(key);\n\t\treturn index == -1 ? null : toExternal(values[index]);\n\t}\n\n\t@Override\n\tpublic V put(int key, V value) {\n\t\tint startIndex = hashIndex(key);\n\t\tint index = startIndex;\n\n\t\tfor (;;) {\n\t\t\tif (values[index] == null) {\n\t\t\t\t// Found empty slot, use it.\n\t\t\t\tkeys[index] = key;\n\t\t\t\tvalues[index] = toInternal(value);\n\t\t\t\tgrowSize();\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (keys[index] == key) {\n\t\t\t\t// Found existing entry with this key, just replace the value.\n\t\t\t\tV previousValue = values[index];\n\t\t\t\tvalues[index] = toInternal(value);\n\t\t\t\treturn toExternal(previousValue);\n\t\t\t}\n\n\t\t\t// Conflict, keep probing ...\n\t\t\tif ((index = probeNext(index)) == startIndex) {\n\t\t\t\t// Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow.\n\t\t\t\tthrow new IllegalStateException(\"Unable to insert\");\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void putAll(Map<? extends Integer, ? extends V> sourceMap) {\n\t\tif (sourceMap instanceof IntObjectHashMap) {\n\t\t\t// Optimization - iterate through the arrays.\n\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\tIntObjectHashMap<V> source = (IntObjectHashMap<V>) sourceMap;\n\t\t\tfor (int i = 0; i < source.values.length; ++i) {\n\t\t\t\tV sourceValue = source.values[i];\n\t\t\t\tif (sourceValue != null) {\n\t\t\t\t\tput(source.keys[i], sourceValue);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Otherwise, just add each entry.\n\t\tfor (Entry<? extends Integer, ? extends V> entry : sourceMap.entrySet()) {\n\t\t\tput(entry.getKey(), entry.getValue());\n\t\t}\n\t}\n\n\t@Override\n\tpublic V remove(int key) {\n\t\tint index = indexOf(key);\n\t\tif (index == -1) {\n\t\t\treturn null;\n\t\t}\n\n\t\tV prev = values[index];\n\t\tremoveAt(index);\n\t\treturn toExternal(prev);\n\t}\n\n\t@Override\n\tpublic int size() {\n\t\treturn size;\n\t}\n\n\t@Override\n\tpublic boolean isEmpty() {\n\t\treturn size == 0;\n\t}\n\n\t@Override\n\tpublic void clear() {\n\t\tArrays.fill(keys, 0);\n\t\tArrays.fill(values, null);\n\t\tsize = 0;\n\t}\n\n\t@Override\n\tpublic boolean containsKey(int key) {\n\t\treturn indexOf(key) >= 0;\n\t}\n\n\t@Override\n\tpublic boolean containsValue(Object value) {\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tV v1 = toInternal((V) value);\n\t\tfor (V v2 : values) {\n\t\t\t// The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE).\n\t\t\tif (v2 != null && v2.equals(v1)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic Iterable<PrimitiveEntry<V>> entries() {\n\t\treturn entries;\n\t}\n\n\t@Override\n\tpublic Collection<V> values() {\n\t\treturn new AbstractCollection<V>() {\n\t\t\t@Override\n\t\t\tpublic Iterator<V> iterator() {\n\t\t\t\treturn new Iterator<V>() {\n\t\t\t\t\tfinal PrimitiveIterator iter = new PrimitiveIterator();\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic boolean hasNext() {\n\t\t\t\t\t\treturn iter.hasNext();\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic V next() {\n\t\t\t\t\t\treturn iter.next().value();\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void remove() {\n\t\t\t\t\t\tthrow new UnsupportedOperationException();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic int size() {\n\t\t\t\treturn size;\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\t// Hashcode is based on all non-zero, valid keys. We have to scan the whole keys\n\t\t// array, which may have different lengths for two maps of same size(), so the\n\t\t// capacity cannot be used as input for hashing but the size can.\n\t\tint hash = size;\n\t\tfor (int key : keys) {\n\t\t\t// 0 can be a valid key or unused slot, but won't impact the hashcode in either case.\n\t\t\t// This way we can use a cheap loop without conditionals, or hard-to-unroll operations,\n\t\t\t// or the devastatingly bad memory locality of visiting value objects.\n\t\t\t// Also, it's important to use a hash function that does not depend on the ordering\n\t\t\t// of terms, only their values; since the map is an unordered collection and\n\t\t\t// entries can end up in different positions in different maps that have the same\n\t\t\t// elements, but with different history of puts/removes, due to conflicts.\n\t\t\thash ^= hashCode(key);\n\t\t}\n\t\treturn hash;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object obj) {\n\t\tif (this == obj) {\n\t\t\treturn true;\n\t\t}\n\t\tif (!(obj instanceof IntObjectMap)) {\n\t\t\treturn false;\n\t\t}\n\t\t@SuppressWarnings(\"rawtypes\")\n\t\tIntObjectMap other = (IntObjectMap) obj;\n\t\tif (size != other.size()) {\n\t\t\treturn false;\n\t\t}\n\t\tfor (int i = 0; i < values.length; ++i) {\n\t\t\tV value = values[i];\n\t\t\tif (value != null) {\n\t\t\t\tint key = keys[i];\n\t\t\t\tObject otherValue = other.get(key);\n\t\t\t\tif (value == NULL_VALUE) {\n\t\t\t\t\tif (otherValue != null) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} else if (!value.equals(otherValue)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean containsKey(Object key) {\n\t\treturn containsKey(objectToKey(key));\n\t}\n\n\t@Override\n\tpublic V get(Object key) {\n\t\treturn get(objectToKey(key));\n\t}\n\n\t@Override\n\tpublic V put(Integer key, V value) {\n\t\treturn put(objectToKey(key), value);\n\t}\n\n\t@Override\n\tpublic V remove(Object key) {\n\t\treturn remove(objectToKey(key));\n\t}\n\n\t@Override\n\tpublic Set<Integer> keySet() {\n\t\treturn keySet;\n\t}\n\n\t@Override\n\tpublic Set<Entry<Integer, V>> entrySet() {\n\t\treturn entrySet;\n\t}\n\n\tprivate int objectToKey(Object key) {\n\t\treturn ((Integer) key).intValue();\n\t}\n\n\t/**\n\t * Locates the index for the given key. This method probes using double hashing.\n\t *\n\t * @param key the key for an entry in the map.\n\t * @return the index where the key was found, or {@code -1} if no entry is found for that key.\n\t */\n\tprivate int indexOf(int key) {\n\t\tint startIndex = hashIndex(key);\n\t\tint index = startIndex;\n\n\t\tfor (;;) {\n\t\t\tif (values[index] == null) {\n\t\t\t\t// It's available, so no chance that this value exists anywhere in the map.\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif (key == keys[index]) {\n\t\t\t\treturn index;\n\t\t\t}\n\n\t\t\t// Conflict, keep probing ...\n\t\t\tif ((index = probeNext(index)) == startIndex) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns the hashed index for the given key.\n\t */\n\tprivate int hashIndex(int key) {\n\t\t// The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds.\n\t\treturn hashCode(key) & mask;\n\t}\n\n\t/**\n\t * Returns the hash code for the key.\n\t */\n\tprivate static int hashCode(int key) {\n\t\treturn key;\n\t}\n\n\t/**\n\t * Get the next sequential index after {@code index} and wraps if necessary.\n\t */\n\tprivate int probeNext(int index) {\n\t\t// The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds.\n\t\treturn (index + 1) & mask;\n\t}\n\n\t/**\n\t * Grows the map size after an insertion. If necessary, performs a rehash of the map.\n\t */\n\tprivate void growSize() {\n\t\tsize++;\n\n\t\tif (size > maxSize) {\n\t\t\tif (keys.length == Integer.MAX_VALUE) {\n\t\t\t\tthrow new IllegalStateException(\"Max capacity reached at size=\" + size);\n\t\t\t}\n\n\t\t\t// Double the capacity.\n\t\t\trehash(keys.length << 1);\n\t\t}\n\t}\n\n\t/**\n\t * Removes entry at the given index position. Also performs opportunistic, incremental rehashing if necessary to not\n\t * break conflict chains.\n\t *\n\t * @param index the index position of the element to remove.\n\t * @return {@code true} if the next item was moved back. {@code false} otherwise.\n\t */\n\tprivate boolean removeAt(final int index) {\n\t\t--size;\n\t\t// Clearing the key is not strictly necessary (for GC like in a regular collection),\n\t\t// but recommended for security. The memory location is still fresh in the cache anyway.\n\t\tkeys[index] = 0;\n\t\tvalues[index] = null;\n\n\t\t// In the interval from index to the next available entry, the arrays may have entries\n\t\t// that are displaced from their base position due to prior conflicts. Iterate these\n\t\t// entries and move them back if possible, optimizing future lookups.\n\t\t// Knuth Section 6.4 Algorithm R, also used by the JDK's IdentityHashMap.\n\n\t\tint nextFree = index;\n\t\tint i = probeNext(index);\n\t\tfor (V value = values[i]; value != null; value = values[i = probeNext(i)]) {\n\t\t\tint key = keys[i];\n\t\t\tint bucket = hashIndex(key);\n\t\t\tif (i < bucket && (bucket <= nextFree || nextFree <= i) || bucket <= nextFree && nextFree <= i) {\n\t\t\t\t// Move the displaced entry \"back\" to the first available position.\n\t\t\t\tkeys[nextFree] = key;\n\t\t\t\tvalues[nextFree] = value;\n\t\t\t\t// Put the first entry after the displaced entry\n\t\t\t\tkeys[i] = 0;\n\t\t\t\tvalues[i] = null;\n\t\t\t\tnextFree = i;\n\t\t\t}\n\t\t}\n\t\treturn nextFree != index;\n\t}\n\n\t/**\n\t * Calculates the maximum size allowed before rehashing.\n\t */\n\tprivate int calcMaxSize(int capacity) {\n\t\t// Clip the upper bound so that there will always be at least one available slot.\n\t\tint upperBound = capacity - 1;\n\t\treturn Math.min(upperBound, (int) (capacity * loadFactor));\n\t}\n\n\t/**\n\t * Rehashes the map for the given capacity.\n\t *\n\t * @param newCapacity the new capacity for the map.\n\t */\n\tprivate void rehash(int newCapacity) {\n\t\tint[] oldKeys = keys;\n\t\tV[] oldVals = values;\n\n\t\tkeys = new int[newCapacity];\n\t\t@SuppressWarnings({ \"unchecked\", \"SuspiciousArrayCast\" })\n\t\tV[] temp = (V[]) new Object[newCapacity];\n\t\tvalues = temp;\n\n\t\tmaxSize = calcMaxSize(newCapacity);\n\t\tmask = newCapacity - 1;\n\n\t\t// Insert to the new arrays.\n\t\tfor (int i = 0; i < oldVals.length; ++i) {\n\t\t\tV oldVal = oldVals[i];\n\t\t\tif (oldVal != null) {\n\t\t\t\t// Inlined put(), but much simpler: we don't need to worry about\n\t\t\t\t// duplicated keys, growing/rehashing, or failing to insert.\n\t\t\t\tint oldKey = oldKeys[i];\n\t\t\t\tint index = hashIndex(oldKey);\n\n\t\t\t\tfor (;;) {\n\t\t\t\t\tif (values[index] == null) {\n\t\t\t\t\t\tkeys[index] = oldKey;\n\t\t\t\t\t\tvalues[index] = oldVal;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Conflict, keep probing. Can wrap around, but never reaches startIndex again.\n\t\t\t\t\tindex = probeNext(index);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tif (isEmpty()) {\n\t\t\treturn \"{}\";\n\t\t}\n\t\tStringBuilder sb = new StringBuilder(4 * size);\n\t\tsb.append('{');\n\t\tboolean first = true;\n\t\tfor (int i = 0; i < values.length; ++i) {\n\t\t\tV value = values[i];\n\t\t\tif (value != null) {\n\t\t\t\tif (!first) {\n\t\t\t\t\tsb.append(\", \");\n\t\t\t\t}\n\t\t\t\tsb.append(keyToString(keys[i])).append('=').append(value == this ? \"(this Map)\" : toExternal(value));\n\t\t\t\tfirst = false;\n\t\t\t}\n\t\t}\n\t\treturn sb.append('}').toString();\n\t}\n\n\t/**\n\t * Helper method called by {@link #toString()} in order to convert a single map key into a string. This is protected\n\t * to allow subclasses to override the appearance of a given key.\n\t */\n\tprotected String keyToString(int key) {\n\t\treturn Integer.toString(key);\n\t}\n\n\t/**\n\t * Set implementation for iterating over the entries of the map.\n\t */\n\tprivate final class EntrySet extends AbstractSet<Entry<Integer, V>> {\n\t\t@Override\n\t\tpublic Iterator<Entry<Integer, V>> iterator() {\n\t\t\treturn new MapIterator();\n\t\t}\n\n\t\t@Override\n\t\tpublic int size() {\n\t\t\treturn IntObjectHashMap.this.size();\n\t\t}\n\t}\n\n\t/**\n\t * Set implementation for iterating over the keys.\n\t */\n\tprivate final class KeySet extends AbstractSet<Integer> {\n\t\t@Override\n\t\tpublic int size() {\n\t\t\treturn IntObjectHashMap.this.size();\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean contains(Object o) {\n\t\t\treturn IntObjectHashMap.this.containsKey(o);\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean remove(Object o) {\n\t\t\treturn IntObjectHashMap.this.remove(o) != null;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean retainAll(Collection<?> retainedKeys) {\n\t\t\tboolean changed = false;\n\t\t\tfor (Iterator<PrimitiveEntry<V>> iter = entries().iterator(); iter.hasNext();) {\n\t\t\t\tPrimitiveEntry<V> entry = iter.next();\n\t\t\t\tif (!retainedKeys.contains(entry.key())) {\n\t\t\t\t\tchanged = true;\n\t\t\t\t\titer.remove();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn changed;\n\t\t}\n\n\t\t@Override\n\t\tpublic void clear() {\n\t\t\tIntObjectHashMap.this.clear();\n\t\t}\n\n\t\t@Override\n\t\tpublic Iterator<Integer> iterator() {\n\t\t\treturn new Iterator<Integer>() {\n\t\t\t\tprivate final Iterator<Entry<Integer, V>> iter = entrySet.iterator();\n\n\t\t\t\t@Override\n\t\t\t\tpublic boolean hasNext() {\n\t\t\t\t\treturn iter.hasNext();\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic Integer next() {\n\t\t\t\t\treturn iter.next().getKey();\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic void remove() {\n\t\t\t\t\titer.remove();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Iterator over primitive entries. Entry key/values are overwritten by each call to {@link #next()}.\n\t */\n\tprivate final class PrimitiveIterator implements Iterator<PrimitiveEntry<V>>, PrimitiveEntry<V> {\n\t\tprivate int prevIndex = -1;\n\t\tprivate int nextIndex = -1;\n\t\tprivate int entryIndex = -1;\n\n\t\tprivate void scanNext() {\n\t\t\twhile (++nextIndex != values.length && values[nextIndex] == null) {\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean hasNext() {\n\t\t\tif (nextIndex == -1) {\n\t\t\t\tscanNext();\n\t\t\t}\n\t\t\treturn nextIndex != values.length;\n\t\t}\n\n\t\t@Override\n\t\tpublic PrimitiveEntry<V> next() {\n\t\t\tif (!hasNext()) {\n\t\t\t\tthrow new NoSuchElementException();\n\t\t\t}\n\n\t\t\tprevIndex = nextIndex;\n\t\t\tscanNext();\n\n\t\t\t// Always return the same Entry object, just change its index each time.\n\t\t\tentryIndex = prevIndex;\n\t\t\treturn this;\n\t\t}\n\n\t\t@Override\n\t\tpublic void remove() {\n\t\t\tif (prevIndex == -1) {\n\t\t\t\tthrow new IllegalStateException(\"next must be called before each remove.\");\n\t\t\t}\n\t\t\tif (removeAt(prevIndex)) {\n\t\t\t\t// removeAt may move elements \"back\" in the array if they have been displaced because their spot in the\n\t\t\t\t// array was occupied when they were inserted. If this occurs then the nextIndex is now invalid and\n\t\t\t\t// should instead point to the prevIndex which now holds an element which was \"moved back\".\n\t\t\t\tnextIndex = prevIndex;\n\t\t\t}\n\t\t\tprevIndex = -1;\n\t\t}\n\n\t\t// Entry implementation. Since this implementation uses a single Entry, we coalesce that\n\t\t// into the Iterator object (potentially making loop optimization much easier).\n\n\t\t@Override\n\t\tpublic int key() {\n\t\t\treturn keys[entryIndex];\n\t\t}\n\n\t\t@Override\n\t\tpublic V value() {\n\t\t\treturn toExternal(values[entryIndex]);\n\t\t}\n\n\t\t@Override\n\t\tpublic void setValue(V value) {\n\t\t\tvalues[entryIndex] = toInternal(value);\n\t\t}\n\t}\n\n\t/**\n\t * Iterator used by the {@link Map} interface.\n\t */\n\tprivate final class MapIterator implements Iterator<Entry<Integer, V>> {\n\t\tprivate final PrimitiveIterator iter = new PrimitiveIterator();\n\n\t\t@Override\n\t\tpublic boolean hasNext() {\n\t\t\treturn iter.hasNext();\n\t\t}\n\n\t\t@Override\n\t\tpublic Entry<Integer, V> next() {\n\t\t\tif (!hasNext()) {\n\t\t\t\tthrow new NoSuchElementException();\n\t\t\t}\n\n\t\t\titer.next();\n\n\t\t\treturn new MapEntry(iter.entryIndex);\n\t\t}\n\n\t\t@Override\n\t\tpublic void remove() {\n\t\t\titer.remove();\n\t\t}\n\t}\n\n\t/**\n\t * A single entry in the map.\n\t */\n\tfinal class MapEntry implements Entry<Integer, V> {\n\t\tprivate final int entryIndex;\n\n\t\tMapEntry(int entryIndex) {\n\t\t\tthis.entryIndex = entryIndex;\n\t\t}\n\n\t\t@Override\n\t\tpublic Integer getKey() {\n\t\t\tverifyExists();\n\t\t\treturn keys[entryIndex];\n\t\t}\n\n\t\t@Override\n\t\tpublic V getValue() {\n\t\t\tverifyExists();\n\t\t\treturn toExternal(values[entryIndex]);\n\t\t}\n\n\t\t@Override\n\t\tpublic V setValue(V value) {\n\t\t\tverifyExists();\n\t\t\tV prevValue = toExternal(values[entryIndex]);\n\t\t\tvalues[entryIndex] = toInternal(value);\n\t\t\treturn prevValue;\n\t\t}\n\n\t\tprivate void verifyExists() {\n\t\t\tif (values[entryIndex] == null) {\n\t\t\t\tthrow new IllegalStateException(\"The map entry has been removed\");\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static int safeFindNextPositivePowerOfTwo(final int value) {\n\t\treturn value <= 0 ? 1 : value >= 0x40000000 ? 0x40000000 : findNextPositivePowerOfTwo(value);\n\t}\n\n\tpublic static int findNextPositivePowerOfTwo(final int value) {\n\t\tassert value > Integer.MIN_VALUE && value < 0x40000000;\n\t\treturn 1 << (32 - Integer.numberOfLeadingZeros(value - 1));\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/primitive/IntObjectMap.java",
    "content": "/*\n * Copyright 2014 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License, version 2.0 (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of the License at:\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.vip.vjtools.vjkit.collection.type.primitive;\n\nimport java.util.Map;\n\n/**\n * Interface for a primitive map that uses {@code int}s as keys.\n *\n * @param <V> the value type stored in the map.\n */\npublic interface IntObjectMap<V> extends Map<Integer, V> {\n\n\t/**\n\t * A primitive entry in the map, provided by the iterator from {@link #entries()}\n\t *\n\t * @param <V> the value type stored in the map.\n\t */\n\tinterface PrimitiveEntry<V> {\n\t\t/**\n\t\t * Gets the key for this entry.\n\t\t */\n\t\tint key();\n\n\t\t/**\n\t\t * Gets the value for this entry.\n\t\t */\n\t\tV value();\n\n\t\t/**\n\t\t * Sets the value for this entry.\n\t\t */\n\t\tvoid setValue(V value);\n\t}\n\n\t/**\n\t * Gets the value in the map with the specified key.\n\t *\n\t * @param key the key whose associated value is to be returned.\n\t * @return the value or {@code null} if the key was not found in the map.\n\t */\n\tV get(int key);\n\n\t/**\n\t * Puts the given entry into the map.\n\t *\n\t * @param key the key of the entry.\n\t * @param value the value of the entry.\n\t * @return the previous value for this key or {@code null} if there was no previous mapping.\n\t */\n\tV put(int key, V value);\n\n\t/**\n\t * Removes the entry with the specified key.\n\t *\n\t * @param key the key for the entry to be removed from this map.\n\t * @return the previous value for the key, or {@code null} if there was no mapping.\n\t */\n\tV remove(int key);\n\n\t/**\n\t * Gets an iterable to traverse over the primitive entries contained in this map. As an optimization, the\n\t * {@link PrimitiveEntry}s returned by the {@link Iterator} may change as the {@link Iterator} progresses. The\n\t * caller should not rely on {@link PrimitiveEntry} key/value stability.\n\t */\n\tIterable<PrimitiveEntry<V>> entries();\n\n\t/**\n\t * Indicates whether or not this map contains a value for the specified key.\n\t */\n\tboolean containsKey(int key);\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/primitive/LongObjectHashMap.java",
    "content": "/*\n * Copyright 2014 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License, version 2.0 (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of the License at:\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.vip.vjtools.vjkit.collection.type.primitive;\n\nimport java.util.AbstractCollection;\nimport java.util.AbstractSet;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.Set;\n\n/**\n * 移植Netty 4.1.6的Key为原子类型的集合类, 在数据结构上与HashMap不一样，空间占用与读写性能俱比原来更优.\n * \n * 原子类型集合类有多个实现，选择Netty是因为有在实战中使用.\n * \n * A hash map implementation of {@link LongObjectMap} that uses open addressing for keys. To minimize the memory\n * footprint, this class uses open addressing rather than chaining. Collisions are resolved using linear probing.\n * Deletions implement compaction, so cost of remove can approach O(N) for full maps, which makes a small loadFactor\n * recommended.\n *\n * @param <V> The value type stored in the map.\n */\npublic class LongObjectHashMap<V> implements LongObjectMap<V> {\n\n\t/** Default initial capacity. Used if not specified in the constructor */\n\tpublic static final int DEFAULT_CAPACITY = 8;\n\n\t/** Default load factor. Used if not specified in the constructor */\n\tpublic static final float DEFAULT_LOAD_FACTOR = 0.5f;\n\n\t/**\n\t * Placeholder for null values, so we can use the actual null to mean available. (Better than using a placeholder\n\t * for available: less references for GC processing.)\n\t */\n\tprivate static final Object NULL_VALUE = new Object();\n\n\t/** The maximum number of elements allowed without allocating more space. */\n\tprivate int maxSize;\n\n\t/** The load factor for the map. Used to calculate {@link #maxSize}. */\n\tprivate final float loadFactor;\n\n\tprivate long[] keys;\n\tprivate V[] values;\n\tprivate int size;\n\tprivate int mask;\n\n\tprivate final Set<Long> keySet = new KeySet();\n\tprivate final Set<Entry<Long, V>> entrySet = new EntrySet();\n\tprivate final Iterable<PrimitiveEntry<V>> entries = new Iterable<PrimitiveEntry<V>>() {\n\t\t@Override\n\t\tpublic Iterator<PrimitiveEntry<V>> iterator() {\n\t\t\treturn new PrimitiveIterator();\n\t\t}\n\t};\n\n\tpublic LongObjectHashMap() {\n\t\tthis(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);\n\t}\n\n\tpublic LongObjectHashMap(int initialCapacity) {\n\t\tthis(initialCapacity, DEFAULT_LOAD_FACTOR);\n\t}\n\n\tpublic LongObjectHashMap(int initialCapacity, float loadFactor) {\n\t\tif (loadFactor <= 0.0f || loadFactor > 1.0f) {\n\t\t\t// Cannot exceed 1 because we can never store more than capacity elements;\n\t\t\t// using a bigger loadFactor would trigger rehashing before the desired load is reached.\n\t\t\tthrow new IllegalArgumentException(\"loadFactor must be > 0 and <= 1\");\n\t\t}\n\n\t\tthis.loadFactor = loadFactor;\n\n\t\t// Adjust the initial capacity if necessary.\n\t\tint capacity = safeFindNextPositivePowerOfTwo(initialCapacity);\n\t\tmask = capacity - 1;\n\n\t\t// Allocate the arrays.\n\t\tkeys = new long[capacity];\n\t\t@SuppressWarnings({ \"unchecked\", \"SuspiciousArrayCast\" })\n\t\tV[] temp = (V[]) new Object[capacity];\n\t\tvalues = temp;\n\n\t\t// Initialize the maximum size value.\n\t\tmaxSize = calcMaxSize(capacity);\n\t}\n\n\tprivate static <T> T toExternal(T value) {\n\t\tassert value != null : \"null is not a legitimate internal value. Concurrent Modification?\";\n\t\treturn value == NULL_VALUE ? null : value;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <T> T toInternal(T value) {\n\t\treturn value == null ? (T) NULL_VALUE : value;\n\t}\n\n\t@Override\n\tpublic V get(long key) {\n\t\tint index = indexOf(key);\n\t\treturn index == -1 ? null : toExternal(values[index]);\n\t}\n\n\t@Override\n\tpublic V put(long key, V value) {\n\t\tint startIndex = hashIndex(key);\n\t\tint index = startIndex;\n\n\t\tfor (;;) {\n\t\t\tif (values[index] == null) {\n\t\t\t\t// Found empty slot, use it.\n\t\t\t\tkeys[index] = key;\n\t\t\t\tvalues[index] = toInternal(value);\n\t\t\t\tgrowSize();\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (keys[index] == key) {\n\t\t\t\t// Found existing entry with this key, just replace the value.\n\t\t\t\tV previousValue = values[index];\n\t\t\t\tvalues[index] = toInternal(value);\n\t\t\t\treturn toExternal(previousValue);\n\t\t\t}\n\n\t\t\t// Conflict, keep probing ...\n\t\t\tif ((index = probeNext(index)) == startIndex) {\n\t\t\t\t// Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow.\n\t\t\t\tthrow new IllegalStateException(\"Unable to insert\");\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void putAll(Map<? extends Long, ? extends V> sourceMap) {\n\t\tif (sourceMap instanceof LongObjectHashMap) {\n\t\t\t// Optimization - iterate through the arrays.\n\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\tLongObjectHashMap<V> source = (LongObjectHashMap<V>) sourceMap;\n\t\t\tfor (int i = 0; i < source.values.length; ++i) {\n\t\t\t\tV sourceValue = source.values[i];\n\t\t\t\tif (sourceValue != null) {\n\t\t\t\t\tput(source.keys[i], sourceValue);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Otherwise, just add each entry.\n\t\tfor (Entry<? extends Long, ? extends V> entry : sourceMap.entrySet()) {\n\t\t\tput(entry.getKey(), entry.getValue());\n\t\t}\n\t}\n\n\t@Override\n\tpublic V remove(long key) {\n\t\tint index = indexOf(key);\n\t\tif (index == -1) {\n\t\t\treturn null;\n\t\t}\n\n\t\tV prev = values[index];\n\t\tremoveAt(index);\n\t\treturn toExternal(prev);\n\t}\n\n\t@Override\n\tpublic int size() {\n\t\treturn size;\n\t}\n\n\t@Override\n\tpublic boolean isEmpty() {\n\t\treturn size == 0;\n\t}\n\n\t@Override\n\tpublic void clear() {\n\t\tArrays.fill(keys, 0);\n\t\tArrays.fill(values, null);\n\t\tsize = 0;\n\t}\n\n\t@Override\n\tpublic boolean containsKey(long key) {\n\t\treturn indexOf(key) >= 0;\n\t}\n\n\t@Override\n\tpublic boolean containsValue(Object value) {\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tV v1 = toInternal((V) value);\n\t\tfor (V v2 : values) {\n\t\t\t// The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE).\n\t\t\tif (v2 != null && v2.equals(v1)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic Iterable<PrimitiveEntry<V>> entries() {\n\t\treturn entries;\n\t}\n\n\t@Override\n\tpublic Collection<V> values() {\n\t\treturn new AbstractCollection<V>() {\n\t\t\t@Override\n\t\t\tpublic Iterator<V> iterator() {\n\t\t\t\treturn new Iterator<V>() {\n\t\t\t\t\tfinal PrimitiveIterator iter = new PrimitiveIterator();\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic boolean hasNext() {\n\t\t\t\t\t\treturn iter.hasNext();\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic V next() {\n\t\t\t\t\t\treturn iter.next().value();\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void remove() {\n\t\t\t\t\t\tthrow new UnsupportedOperationException();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic int size() {\n\t\t\t\treturn size;\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\t// Hashcode is based on all non-zero, valid keys. We have to scan the whole keys\n\t\t// array, which may have different lengths for two maps of same size(), so the\n\t\t// capacity cannot be used as input for hashing but the size can.\n\t\tint hash = size;\n\t\tfor (long key : keys) {\n\t\t\t// 0 can be a valid key or unused slot, but won't impact the hashcode in either case.\n\t\t\t// This way we can use a cheap loop without conditionals, or hard-to-unroll operations,\n\t\t\t// or the devastatingly bad memory locality of visiting value objects.\n\t\t\t// Also, it's important to use a hash function that does not depend on the ordering\n\t\t\t// of terms, only their values; since the map is an unordered collection and\n\t\t\t// entries can end up in different positions in different maps that have the same\n\t\t\t// elements, but with different history of puts/removes, due to conflicts.\n\t\t\thash ^= hashCode(key);\n\t\t}\n\t\treturn hash;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object obj) {\n\t\tif (this == obj) {\n\t\t\treturn true;\n\t\t}\n\t\tif (!(obj instanceof LongObjectMap)) {\n\t\t\treturn false;\n\t\t}\n\t\t@SuppressWarnings(\"rawtypes\")\n\t\tLongObjectMap other = (LongObjectMap) obj;\n\t\tif (size != other.size()) {\n\t\t\treturn false;\n\t\t}\n\t\tfor (int i = 0; i < values.length; ++i) {\n\t\t\tV value = values[i];\n\t\t\tif (value != null) {\n\t\t\t\tlong key = keys[i];\n\t\t\t\tObject otherValue = other.get(key);\n\t\t\t\tif (value == NULL_VALUE) {\n\t\t\t\t\tif (otherValue != null) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} else if (!value.equals(otherValue)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean containsKey(Object key) {\n\t\treturn containsKey(objectToKey(key));\n\t}\n\n\t@Override\n\tpublic V get(Object key) {\n\t\treturn get(objectToKey(key));\n\t}\n\n\t@Override\n\tpublic V put(Long key, V value) {\n\t\treturn put(objectToKey(key), value);\n\t}\n\n\t@Override\n\tpublic V remove(Object key) {\n\t\treturn remove(objectToKey(key));\n\t}\n\n\t@Override\n\tpublic Set<Long> keySet() {\n\t\treturn keySet;\n\t}\n\n\t@Override\n\tpublic Set<Entry<Long, V>> entrySet() {\n\t\treturn entrySet;\n\t}\n\n\tprivate long objectToKey(Object key) {\n\t\treturn ((Long) key).longValue();\n\t}\n\n\t/**\n\t * Locates the index for the given key. This method probes using double hashing.\n\t *\n\t * @param key the key for an entry in the map.\n\t * @return the index where the key was found, or {@code -1} if no entry is found for that key.\n\t */\n\tprivate int indexOf(long key) {\n\t\tint startIndex = hashIndex(key);\n\t\tint index = startIndex;\n\n\t\tfor (;;) {\n\t\t\tif (values[index] == null) {\n\t\t\t\t// It's available, so no chance that this value exists anywhere in the map.\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif (key == keys[index]) {\n\t\t\t\treturn index;\n\t\t\t}\n\n\t\t\t// Conflict, keep probing ...\n\t\t\tif ((index = probeNext(index)) == startIndex) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns the hashed index for the given key.\n\t */\n\tprivate int hashIndex(long key) {\n\t\t// The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds.\n\t\treturn hashCode(key) & mask;\n\t}\n\n\t/**\n\t * Returns the hash code for the key.\n\t */\n\tprivate static int hashCode(long key) {\n\t\treturn (int) (key ^ (key >>> 32));\n\t}\n\n\t/**\n\t * Get the next sequential index after {@code index} and wraps if necessary.\n\t */\n\tprivate int probeNext(int index) {\n\t\t// The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds.\n\t\treturn (index + 1) & mask;\n\t}\n\n\t/**\n\t * Grows the map size after an insertion. If necessary, performs a rehash of the map.\n\t */\n\tprivate void growSize() {\n\t\tsize++;\n\n\t\tif (size > maxSize) {\n\t\t\tif (keys.length == Integer.MAX_VALUE) {\n\t\t\t\tthrow new IllegalStateException(\"Max capacity reached at size=\" + size);\n\t\t\t}\n\n\t\t\t// Double the capacity.\n\t\t\trehash(keys.length << 1);\n\t\t}\n\t}\n\n\t/**\n\t * Removes entry at the given index position. Also performs opportunistic, incremental rehashing if necessary to not\n\t * break conflict chains.\n\t *\n\t * @param index the index position of the element to remove.\n\t * @return {@code true} if the next item was moved back. {@code false} otherwise.\n\t */\n\tprivate boolean removeAt(final int index) {\n\t\t--size;\n\t\t// Clearing the key is not strictly necessary (for GC like in a regular collection),\n\t\t// but recommended for security. The memory location is still fresh in the cache anyway.\n\t\tkeys[index] = 0;\n\t\tvalues[index] = null;\n\n\t\t// In the interval from index to the next available entry, the arrays may have entries\n\t\t// that are displaced from their base position due to prior conflicts. Iterate these\n\t\t// entries and move them back if possible, optimizing future lookups.\n\t\t// Knuth Section 6.4 Algorithm R, also used by the JDK's IdentityHashMap.\n\n\t\tint nextFree = index;\n\t\tint i = probeNext(index);\n\t\tfor (V value = values[i]; value != null; value = values[i = probeNext(i)]) {\n\t\t\tlong key = keys[i];\n\t\t\tint bucket = hashIndex(key);\n\t\t\tif (i < bucket && (bucket <= nextFree || nextFree <= i) || bucket <= nextFree && nextFree <= i) {\n\t\t\t\t// Move the displaced entry \"back\" to the first available position.\n\t\t\t\tkeys[nextFree] = key;\n\t\t\t\tvalues[nextFree] = value;\n\t\t\t\t// Put the first entry after the displaced entry\n\t\t\t\tkeys[i] = 0;\n\t\t\t\tvalues[i] = null;\n\t\t\t\tnextFree = i;\n\t\t\t}\n\t\t}\n\t\treturn nextFree != index;\n\t}\n\n\t/**\n\t * Calculates the maximum size allowed before rehashing.\n\t */\n\tprivate int calcMaxSize(int capacity) {\n\t\t// Clip the upper bound so that there will always be at least one available slot.\n\t\tint upperBound = capacity - 1;\n\t\treturn Math.min(upperBound, (int) (capacity * loadFactor));\n\t}\n\n\t/**\n\t * Rehashes the map for the given capacity.\n\t *\n\t * @param newCapacity the new capacity for the map.\n\t */\n\tprivate void rehash(int newCapacity) {\n\t\tlong[] oldKeys = keys;\n\t\tV[] oldVals = values;\n\n\t\tkeys = new long[newCapacity];\n\t\t@SuppressWarnings({ \"unchecked\", \"SuspiciousArrayCast\" })\n\t\tV[] temp = (V[]) new Object[newCapacity];\n\t\tvalues = temp;\n\n\t\tmaxSize = calcMaxSize(newCapacity);\n\t\tmask = newCapacity - 1;\n\n\t\t// Insert to the new arrays.\n\t\tfor (int i = 0; i < oldVals.length; ++i) {\n\t\t\tV oldVal = oldVals[i];\n\t\t\tif (oldVal != null) {\n\t\t\t\t// Inlined put(), but much simpler: we don't need to worry about\n\t\t\t\t// duplicated keys, growing/rehashing, or failing to insert.\n\t\t\t\tlong oldKey = oldKeys[i];\n\t\t\t\tint index = hashIndex(oldKey);\n\n\t\t\t\tfor (;;) {\n\t\t\t\t\tif (values[index] == null) {\n\t\t\t\t\t\tkeys[index] = oldKey;\n\t\t\t\t\t\tvalues[index] = oldVal;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Conflict, keep probing. Can wrap around, but never reaches startIndex again.\n\t\t\t\t\tindex = probeNext(index);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tif (isEmpty()) {\n\t\t\treturn \"{}\";\n\t\t}\n\t\tStringBuilder sb = new StringBuilder(4 * size);\n\t\tsb.append('{');\n\t\tboolean first = true;\n\t\tfor (int i = 0; i < values.length; ++i) {\n\t\t\tV value = values[i];\n\t\t\tif (value != null) {\n\t\t\t\tif (!first) {\n\t\t\t\t\tsb.append(\", \");\n\t\t\t\t}\n\t\t\t\tsb.append(keyToString(keys[i])).append('=').append(value == this ? \"(this Map)\" : toExternal(value));\n\t\t\t\tfirst = false;\n\t\t\t}\n\t\t}\n\t\treturn sb.append('}').toString();\n\t}\n\n\t/**\n\t * Helper method called by {@link #toString()} in order to convert a single map key into a string. This is protected\n\t * to allow subclasses to override the appearance of a given key.\n\t */\n\tprotected String keyToString(long key) {\n\t\treturn Long.toString(key);\n\t}\n\n\t/**\n\t * Set implementation for iterating over the entries of the map.\n\t */\n\tprivate final class EntrySet extends AbstractSet<Entry<Long, V>> {\n\t\t@Override\n\t\tpublic Iterator<Entry<Long, V>> iterator() {\n\t\t\treturn new MapIterator();\n\t\t}\n\n\t\t@Override\n\t\tpublic int size() {\n\t\t\treturn LongObjectHashMap.this.size();\n\t\t}\n\t}\n\n\t/**\n\t * Set implementation for iterating over the keys.\n\t */\n\tprivate final class KeySet extends AbstractSet<Long> {\n\t\t@Override\n\t\tpublic int size() {\n\t\t\treturn LongObjectHashMap.this.size();\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean contains(Object o) {\n\t\t\treturn LongObjectHashMap.this.containsKey(o);\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean remove(Object o) {\n\t\t\treturn LongObjectHashMap.this.remove(o) != null;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean retainAll(Collection<?> retainedKeys) {\n\t\t\tboolean changed = false;\n\t\t\tfor (Iterator<PrimitiveEntry<V>> iter = entries().iterator(); iter.hasNext();) {\n\t\t\t\tPrimitiveEntry<V> entry = iter.next();\n\t\t\t\tif (!retainedKeys.contains(entry.key())) {\n\t\t\t\t\tchanged = true;\n\t\t\t\t\titer.remove();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn changed;\n\t\t}\n\n\t\t@Override\n\t\tpublic void clear() {\n\t\t\tLongObjectHashMap.this.clear();\n\t\t}\n\n\t\t@Override\n\t\tpublic Iterator<Long> iterator() {\n\t\t\treturn new Iterator<Long>() {\n\t\t\t\tprivate final Iterator<Entry<Long, V>> iter = entrySet.iterator();\n\n\t\t\t\t@Override\n\t\t\t\tpublic boolean hasNext() {\n\t\t\t\t\treturn iter.hasNext();\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic Long next() {\n\t\t\t\t\treturn iter.next().getKey();\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic void remove() {\n\t\t\t\t\titer.remove();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Iterator over primitive entries. Entry key/values are overwritten by each call to {@link #next()}.\n\t */\n\tprivate final class PrimitiveIterator implements Iterator<PrimitiveEntry<V>>, PrimitiveEntry<V> {\n\t\tprivate int prevIndex = -1;\n\t\tprivate int nextIndex = -1;\n\t\tprivate int entryIndex = -1;\n\n\t\tprivate void scanNext() {\n\t\t\twhile (++nextIndex != values.length && values[nextIndex] == null) {\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean hasNext() {\n\t\t\tif (nextIndex == -1) {\n\t\t\t\tscanNext();\n\t\t\t}\n\t\t\treturn nextIndex != values.length;\n\t\t}\n\n\t\t@Override\n\t\tpublic PrimitiveEntry<V> next() {\n\t\t\tif (!hasNext()) {\n\t\t\t\tthrow new NoSuchElementException();\n\t\t\t}\n\n\t\t\tprevIndex = nextIndex;\n\t\t\tscanNext();\n\n\t\t\t// Always return the same Entry object, just change its index each time.\n\t\t\tentryIndex = prevIndex;\n\t\t\treturn this;\n\t\t}\n\n\t\t@Override\n\t\tpublic void remove() {\n\t\t\tif (prevIndex == -1) {\n\t\t\t\tthrow new IllegalStateException(\"next must be called before each remove.\");\n\t\t\t}\n\t\t\tif (removeAt(prevIndex)) {\n\t\t\t\t// removeAt may move elements \"back\" in the array if they have been displaced because their spot in the\n\t\t\t\t// array was occupied when they were inserted. If this occurs then the nextIndex is now invalid and\n\t\t\t\t// should instead point to the prevIndex which now holds an element which was \"moved back\".\n\t\t\t\tnextIndex = prevIndex;\n\t\t\t}\n\t\t\tprevIndex = -1;\n\t\t}\n\n\t\t// Entry implementation. Since this implementation uses a single Entry, we coalesce that\n\t\t// into the Iterator object (potentially making loop optimization much easier).\n\n\t\t@Override\n\t\tpublic long key() {\n\t\t\treturn keys[entryIndex];\n\t\t}\n\n\t\t@Override\n\t\tpublic V value() {\n\t\t\treturn toExternal(values[entryIndex]);\n\t\t}\n\n\t\t@Override\n\t\tpublic void setValue(V value) {\n\t\t\tvalues[entryIndex] = toInternal(value);\n\t\t}\n\t}\n\n\t/**\n\t * Iterator used by the {@link Map} interface.\n\t */\n\tprivate final class MapIterator implements Iterator<Entry<Long, V>> {\n\t\tprivate final PrimitiveIterator iter = new PrimitiveIterator();\n\n\t\t@Override\n\t\tpublic boolean hasNext() {\n\t\t\treturn iter.hasNext();\n\t\t}\n\n\t\t@Override\n\t\tpublic Entry<Long, V> next() {\n\t\t\tif (!hasNext()) {\n\t\t\t\tthrow new NoSuchElementException();\n\t\t\t}\n\n\t\t\titer.next();\n\n\t\t\treturn new MapEntry(iter.entryIndex);\n\t\t}\n\n\t\t@Override\n\t\tpublic void remove() {\n\t\t\titer.remove();\n\t\t}\n\t}\n\n\t/**\n\t * A single entry in the map.\n\t */\n\tfinal class MapEntry implements Entry<Long, V> {\n\t\tprivate final int entryIndex;\n\n\t\tMapEntry(int entryIndex) {\n\t\t\tthis.entryIndex = entryIndex;\n\t\t}\n\n\t\t@Override\n\t\tpublic Long getKey() {\n\t\t\tverifyExists();\n\t\t\treturn keys[entryIndex];\n\t\t}\n\n\t\t@Override\n\t\tpublic V getValue() {\n\t\t\tverifyExists();\n\t\t\treturn toExternal(values[entryIndex]);\n\t\t}\n\n\t\t@Override\n\t\tpublic V setValue(V value) {\n\t\t\tverifyExists();\n\t\t\tV prevValue = toExternal(values[entryIndex]);\n\t\t\tvalues[entryIndex] = toInternal(value);\n\t\t\treturn prevValue;\n\t\t}\n\n\t\tprivate void verifyExists() {\n\t\t\tif (values[entryIndex] == null) {\n\t\t\t\tthrow new IllegalStateException(\"The map entry has been removed\");\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static int safeFindNextPositivePowerOfTwo(final int value) {\n\t\treturn value <= 0 ? 1 : value >= 0x40000000 ? 0x40000000 : findNextPositivePowerOfTwo(value);\n\t}\n\n\tpublic static int findNextPositivePowerOfTwo(final int value) {\n\t\tassert value > Integer.MIN_VALUE && value < 0x40000000;\n\t\treturn 1 << (32 - Integer.numberOfLeadingZeros(value - 1));\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/collection/type/primitive/LongObjectMap.java",
    "content": "/*\n * Copyright 2014 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License, version 2.0 (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of the License at:\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.vip.vjtools.vjkit.collection.type.primitive;\n\nimport java.util.Map;\n\n/**\n * Interface for a primitive map that uses {@code long}s as keys.\n *\n * @param <V> the value type stored in the map.\n */\npublic interface LongObjectMap<V> extends Map<Long, V> {\n\n\t/**\n\t * A primitive entry in the map, provided by the iterator from {@link #entries()}\n\t *\n\t * @param <V> the value type stored in the map.\n\t */\n\tinterface PrimitiveEntry<V> {\n\t\t/**\n\t\t * Gets the key for this entry.\n\t\t */\n\t\tlong key();\n\n\t\t/**\n\t\t * Gets the value for this entry.\n\t\t */\n\t\tV value();\n\n\t\t/**\n\t\t * Sets the value for this entry.\n\t\t */\n\t\tvoid setValue(V value);\n\t}\n\n\t/**\n\t * Gets the value in the map with the specified key.\n\t *\n\t * @param key the key whose associated value is to be returned.\n\t * @return the value or {@code null} if the key was not found in the map.\n\t */\n\tV get(long key);\n\n\t/**\n\t * Puts the given entry into the map.\n\t *\n\t * @param key the key of the entry.\n\t * @param value the value of the entry.\n\t * @return the previous value for this key or {@code null} if there was no previous mapping.\n\t */\n\tV put(long key, V value);\n\n\t/**\n\t * Removes the entry with the specified key.\n\t *\n\t * @param key the key for the entry to be removed from this map.\n\t * @return the previous value for the key, or {@code null} if there was no mapping.\n\t */\n\tV remove(long key);\n\n\t/**\n\t * Gets an iterable to traverse over the primitive entries contained in this map. As an optimization, the\n\t * {@link PrimitiveEntry}s returned by the {@link Iterator} may change as the {@link Iterator} progresses. The\n\t * caller should not rely on {@link PrimitiveEntry} key/value stability.\n\t */\n\tIterable<PrimitiveEntry<V>> entries();\n\n\t/**\n\t * Indicates whether or not this map contains a value for the specified key.\n\t */\n\tboolean containsKey(long key);\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/Concurrents.java",
    "content": "package com.vip.vjtools.vjkit.concurrent;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.CyclicBarrier;\nimport java.util.concurrent.Semaphore;\nimport java.util.concurrent.TimeUnit;\n\nimport com.google.common.util.concurrent.RateLimiter;\nimport com.vip.vjtools.vjkit.concurrent.jsr166e.LongAdder;\nimport com.vip.vjtools.vjkit.concurrent.limiter.RateLimiterUtil;\nimport com.vip.vjtools.vjkit.concurrent.limiter.Sampler;\nimport com.vip.vjtools.vjkit.concurrent.limiter.TimeIntervalLimiter;\n\n/**\n * 并发常用工具类\n */\npublic class Concurrents {\n\n\t/**\n\t * 返回没有激烈CAS冲突的LongAdder, 并发的＋1将在不同的Counter里进行，只在取值时将多个Counter求和.\n\t * \n\t * 为了保持JDK版本兼容性，统一采用移植版\n\t */\n\tpublic static LongAdder longAdder() {\n\t\treturn new LongAdder();\n\t}\n\n\t/**\n\t * 返回CountDownLatch, 每条线程减1，减到0时正在latch.wait()的进程继续进行\n\t */\n\tpublic static CountDownLatch countDownLatch(int count) {\n\t\treturn new CountDownLatch(count);\n\t}\n\n\t/**\n\t * 返回CyclicBarrier，每条线程减1并等待，减到0时，所有线程继续运行\n\t */\n\tpublic static CyclicBarrier cyclicBarrier(int count) {\n\t\treturn new CyclicBarrier(count);\n\t}\n\n\t/**\n\t * 返回默认的非公平信号量，先请求的线程不一定先拿到信号量\n\t */\n\tpublic static Semaphore nonFairSemaphore(int permits) {\n\t\treturn new Semaphore(permits);\n\t}\n\n\t/**\n\t * 返回公平的信号量，先请求的线程先拿到信号量\n\t */\n\tpublic static Semaphore fairSemaphore(int permits) {\n\t\treturn new Semaphore(permits, true);\n\t}\n\n\t/////////// 限流采样 //////\n\t/**\n\t * 返回令牌桶算法的RateLimiter默认版，默认令牌桶大小等于期望的QPS，且刚启动时桶为空。\n\t * \n\t * @param permitsPerSecond 每秒允许的请求数，可看成QPS，同时将QPS平滑到毫秒级别上，请求到达速度不平滑时依赖缓冲能力.\n\t */\n\tpublic static RateLimiter rateLimiter(int permitsPerSecond) {\n\t\treturn RateLimiter.create(permitsPerSecond);\n\t}\n\n\t/**\n\t * 返回令牌桶算法的RateLimiter定制版，可定制令牌桶的大小，且刚启动时桶已装满。\n\t * \n\t * @param permitsPerSecond 每秒允许的请求数，可看成QPS，同时将QPS平滑到毫秒级别上，请求到达速度不平滑时依赖缓冲能力.\n\t * @param maxBurstSeconds 可看成桶的容量，Guava中最大的突发流量缓冲时间，默认是1s, permitsPerSecond * maxBurstSeconds，就是闲时能累积的缓冲token最大数量。\n\t */\n\tpublic static RateLimiter rateLimiter(int permitsPerSecond, int maxBurstSeconds)\n\t\t\tthrows ReflectiveOperationException {\n\t\treturn RateLimiterUtil.create(permitsPerSecond, maxBurstSeconds);\n\t}\n\n\t/**\n\t * 返回采样器.\n\t * \n\t * @param selectPercent 采样率，在0-100 之间，可以有小数位\n\t */\n\tpublic static Sampler sampler(double selectPercent) {\n\t\treturn Sampler.create(selectPercent);\n\t}\n\n\t/**\n\t * 返回时间间隔限制器.\n\t * @param interval 间隔时间\n\t * @param timeUnit 间隔时间单位\n\t */\n\tpublic static TimeIntervalLimiter timeIntervalLimiter(long interval, TimeUnit timeUnit) {\n\t\treturn new TimeIntervalLimiter(interval, timeUnit);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/ThreadDumpper.java",
    "content": "package com.vip.vjtools.vjkit.concurrent;\n\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.concurrent.TimeUnit;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.vip.vjtools.vjkit.concurrent.limiter.TimeIntervalLimiter;\n\n/**\n * 由程序触发的ThreadDump，打印到日志中.\n * \n * 因为ThreadDump本身会造成JVM停顿，所以加上了开关和最少间隔时间的选项(默认不限制)\n * \n * 因为ThreadInfo的toString()最多只会打印8层的StackTrace，所以加上了最大打印层数的选项.(默认为8)\n */\npublic class ThreadDumpper {\n\n\tprivate static final int DEFAULT_MAX_STACK_LEVEL = 8;\n\n\tprivate static final int DEFAULT_MIN_INTERVAL = 1000 * 60 * 10; // 10分钟\n\n\tprivate static Logger logger = LoggerFactory.getLogger(ThreadDumpper.class);\n\n\tprivate int maxStackLevel; // 打印StackTrace的最大深度\n\n\tprivate TimeIntervalLimiter timeIntervalLimiter;\n\n\tpublic ThreadDumpper() {\n\t\tthis(DEFAULT_MIN_INTERVAL, DEFAULT_MAX_STACK_LEVEL);\n\t}\n\n\tpublic ThreadDumpper(long leastIntervalMills, int maxStackLevel) {\n\t\tthis.maxStackLevel = maxStackLevel;\n\t\ttimeIntervalLimiter = new TimeIntervalLimiter(leastIntervalMills, TimeUnit.MILLISECONDS);\n\t}\n\n\t/**\n\t * 符合条件则打印线程栈.\n\t */\n\tpublic void tryThreadDump() {\n\t\ttryThreadDump(null);\n\t}\n\n\t/**\n\t * 符合条件则打印线程栈.\n\t * \n\t * @param reasonMsg 发生ThreadDump的原因\n\t */\n\tpublic void tryThreadDump(String reasonMsg) {\n\t\tif (timeIntervalLimiter.tryAcquire()) {\n\t\t\tthreadDump(reasonMsg);\n\t\t}\n\t}\n\n\t/**\n\t * 强行打印ThreadDump，使用最轻量的采集方式，不打印锁信息\n\t */\n\tpublic void threadDump(String reasonMsg) {\n\t\tlogger.info(\"Thread dump by ThreadDumpper\" + (reasonMsg != null ? (\" for \" + reasonMsg) : \"\"));\n\n\t\tMap<Thread, StackTraceElement[]> threads = Thread.getAllStackTraces();\n\t\t// 两条日志间的时间间隔，是VM被thread dump堵塞的时间.\n\t\tlogger.info(\"Finish the threads snapshot\");\n\n\t\tStringBuilder sb = new StringBuilder(8192 * 20).append('\\n');\n\n\t\tfor (Entry<Thread, StackTraceElement[]> entry : threads.entrySet()) {\n\t\t\tdumpThreadInfo(entry.getKey(), entry.getValue(), sb);\n\t\t}\n\t\tlogger.info(sb.toString());\n\t}\n\n\t/**\n\t * 打印全部的stack，重新实现threadInfo的toString()函数，因为默认最多只打印8层的stack. 同时，不再打印lockedMonitors和lockedSynchronizers.\n\t */\n\tprivate String dumpThreadInfo(Thread thread, StackTraceElement[] stackTrace, StringBuilder sb) {\n\t\tsb.append('\\\"').append(thread.getName()).append(\"\\\" Id=\").append(thread.getId()).append(' ')\n\t\t\t\t.append(thread.getState());\n\t\tsb.append('\\n');\n\t\tint i = 0;\n\t\tfor (; i < Math.min(maxStackLevel, stackTrace.length); i++) {\n\t\t\tStackTraceElement ste = stackTrace[i];\n\t\t\tsb.append(\"\\tat \").append(ste).append('\\n');\n\t\t}\n\t\tif (i < stackTrace.length) {\n\t\t\tsb.append(\"\\t...\").append('\\n');\n\t\t}\n\n\t\tsb.append('\\n');\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * 打印ThreadDump的最小时间间隔，单位为秒，默认为0不限制.\n\t */\n\tpublic void setLeastInterval(int leastIntervalSeconds) {\n\t\tthis.timeIntervalLimiter = new TimeIntervalLimiter(leastIntervalSeconds, TimeUnit.MILLISECONDS);\n\t}\n\n\t/**\n\t * 打印StackTrace的最大深度, 默认为8.\n\t */\n\tpublic void setMaxStackLevel(int maxStackLevel) {\n\t\tthis.maxStackLevel = maxStackLevel;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/ThreadUtil.java",
    "content": "package com.vip.vjtools.vjkit.concurrent;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 线程相关工具类.\n * \n * 1. 处理了InterruptedException的sleep\n * \n * 2. 正确的InterruptedException处理方法\n */\npublic class ThreadUtil {\n\n\t/**\n\t * sleep等待, 单位为毫秒, 已捕捉并处理InterruptedException.\n\t */\n\tpublic static void sleep(long durationMillis) {\n\t\ttry {\n\t\t\tThread.sleep(durationMillis);\n\t\t} catch (InterruptedException e) {\n\t\t\tThread.currentThread().interrupt();\n\t\t}\n\t}\n\n\t/**\n\t * sleep等待，已捕捉并处理InterruptedException.\n\t */\n\tpublic static void sleep(long duration, TimeUnit unit) {\n\t\ttry {\n\t\t\tThread.sleep(unit.toMillis(duration));\n\t\t} catch (InterruptedException e) {\n\t\t\tThread.currentThread().interrupt();\n\t\t}\n\t}\n\n\t/**\n\t * 纯粹为了提醒下处理InterruptedException的正确方式，除非你是在写不可中断的任务.\n\t */\n\tpublic static void handleInterruptedException() {\n\t\tThread.currentThread().interrupt();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/jsr166e/LongAdder.java",
    "content": "/*\n * Written by Doug Lea with assistance from members of JCP JSR-166\n * Expert Group and released to the public domain, as explained at\n * http://creativecommons.org/publicdomain/zero/1.0/\n */\n\npackage com.vip.vjtools.vjkit.concurrent.jsr166e;\n\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.Serializable;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * 移植\n * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java Revision.1.17\n * \n * One or more variables that together maintain an initially zero\n * {@code long} sum.  When updates (method {@link #add}) are contended\n * across threads, the set of variables may grow dynamically to reduce\n * contention. Method {@link #sum} (or, equivalently, {@link\n * #longValue}) returns the current total combined across the\n * variables maintaining the sum.\n *\n * <p>This class is usually preferable to {@link AtomicLong} when\n * multiple threads update a common sum that is used for purposes such\n * as collecting statistics, not for fine-grained synchronization\n * control.  Under low update contention, the two classes have similar\n * characteristics. But under high contention, expected throughput of\n * this class is significantly higher, at the expense of higher space\n * consumption.\n *\n * <p>This class extends {@link Number}, but does <em>not</em> define\n * methods such as {@code equals}, {@code hashCode} and {@code\n * compareTo} because instances are expected to be mutated, and so are\n * not useful as collection keys.\n *\n * <p><em>jsr166e note: This class is targeted to be placed in\n * java.util.concurrent.atomic.</em>\n *\n * @since 1.8\n * @author Doug Lea\n */\npublic class LongAdder extends Striped64 implements Serializable {\n\tprivate static final long serialVersionUID = 7249069246863182397L;\n\n\t/**\n\t * Version of plus for use in retryUpdate\n\t */\n\tfinal long fn(long v, long x) {\n\t\treturn v + x;\n\t}\n\n\t/**\n\t * Creates a new adder with initial sum of zero.\n\t */\n\tpublic LongAdder() {\n\t\t// empty\n\t}\n\n\t/**\n\t * Adds the given value.\n\t *\n\t * @param x the value to add\n\t */\n\tpublic void add(long x) {\n\t\tCell[] as;\n\t\tlong b, v;\n\t\tint[] hc;\n\t\tCell a;\n\t\tint n;\n\t\tif ((as = cells) != null || !casBase(b = base, b + x)) {\n\t\t\tboolean uncontended = true;\n\t\t\tif ((hc = threadHashCode.get()) == null || as == null || (n = as.length) < 1\n\t\t\t\t\t|| (a = as[(n - 1) & hc[0]]) == null || !(uncontended = a.cas(v = a.value, v + x)))\n\t\t\t\tretryUpdate(x, hc, uncontended);\n\t\t}\n\t}\n\n\t/**\n\t * Equivalent to {@code add(1)}.\n\t */\n\tpublic void increment() {\n\t\tadd(1L);\n\t}\n\n\t/**\n\t * Equivalent to {@code add(-1)}.\n\t */\n\tpublic void decrement() {\n\t\tadd(-1L);\n\t}\n\n\t/**\n\t * Returns the current sum.  The returned value is <em>NOT</em> an\n\t * atomic snapshot; invocation in the absence of concurrent\n\t * updates returns an accurate result, but concurrent updates that\n\t * occur while the sum is being calculated might not be\n\t * incorporated.\n\t *\n\t * @return the sum\n\t */\n\tpublic long sum() {\n\t\tlong sum = base;\n\t\tCell[] as = cells;\n\t\tif (as != null) {\n\t\t\tint n = as.length;\n\t\t\tfor (int i = 0; i < n; ++i) {\n\t\t\t\tCell a = as[i];\n\t\t\t\tif (a != null) sum += a.value;\n\t\t\t}\n\t\t}\n\t\treturn sum;\n\t}\n\n\t/**\n\t * Resets variables maintaining the sum to zero.  This method may\n\t * be a useful alternative to creating a new adder, but is only\n\t * effective if there are no concurrent updates.  Because this\n\t * method is intrinsically racy, it should only be used when it is\n\t * known that no threads are concurrently updating.\n\t */\n\tpublic void reset() {\n\t\tinternalReset(0L);\n\t}\n\n\t/**\n\t * Equivalent in effect to {@link #sum} followed by {@link\n\t * #reset}. This method may apply for example during quiescent\n\t * points between multithreaded computations.  If there are\n\t * updates concurrent with this method, the returned value is\n\t * <em>not</em> guaranteed to be the final value occurring before\n\t * the reset.\n\t *\n\t * @return the sum\n\t */\n\tpublic long sumThenReset() {\n\t\tlong sum = base;\n\t\tCell[] as = cells;\n\t\tbase = 0L;\n\t\tif (as != null) {\n\t\t\tint n = as.length;\n\t\t\tfor (int i = 0; i < n; ++i) {\n\t\t\t\tCell a = as[i];\n\t\t\t\tif (a != null) {\n\t\t\t\t\tsum += a.value;\n\t\t\t\t\ta.value = 0L;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn sum;\n\t}\n\n\t/**\n\t * Returns the String representation of the {@link #sum}.\n\t * @return the String representation of the {@link #sum}\n\t */\n\tpublic String toString() {\n\t\treturn Long.toString(sum());\n\t}\n\n\t/**\n\t * Equivalent to {@link #sum}.\n\t *\n\t * @return the sum\n\t */\n\tpublic long longValue() {\n\t\treturn sum();\n\t}\n\n\t/**\n\t * Returns the {@link #sum} as an {@code int} after a narrowing\n\t * primitive conversion.\n\t */\n\tpublic int intValue() {\n\t\treturn (int) sum();\n\t}\n\n\t/**\n\t * Returns the {@link #sum} as a {@code float}\n\t * after a widening primitive conversion.\n\t */\n\tpublic float floatValue() {\n\t\treturn (float) sum();\n\t}\n\n\t/**\n\t * Returns the {@link #sum} as a {@code double} after a widening\n\t * primitive conversion.\n\t */\n\tpublic double doubleValue() {\n\t\treturn (double) sum();\n\t}\n\n\tprivate void writeObject(ObjectOutputStream s) throws IOException {\n\t\ts.defaultWriteObject();\n\t\ts.writeLong(sum());\n\t}\n\n\tprivate void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {\n\t\ts.defaultReadObject();\n\t\tbusy = 0;\n\t\tcells = null;\n\t\tbase = s.readLong();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/jsr166e/Striped64.java",
    "content": "/*\n * Written by Doug Lea with assistance from members of JCP JSR-166\n * Expert Group and released to the public domain, as explained at\n * http://creativecommons.org/publicdomain/zero/1.0/\n */\n\npackage com.vip.vjtools.vjkit.concurrent.jsr166e;\n\nimport java.util.Random;\n\n/**\n * 移植\n * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java Revision 1.10\n * \n * A package-local class holding common representation and mechanics\n * for classes supporting dynamic striping on 64bit values. The class\n * extends Number so that concrete subclasses must publicly do so.\n */\npublic abstract class Striped64 extends Number {\n\t/*\n\t * This class maintains a lazily-initialized table of atomically updated variables, plus an extra \"base\" field. The\n\t * table size is a power of two. Indexing uses masked per-thread hash codes. Nearly all declarations in this class\n\t * are package-private, accessed directly by subclasses.\n\t *\n\t * Table entries are of class Cell; a variant of AtomicLong padded to reduce cache contention on most processors.\n\t * Padding is overkill for most Atomics because they are usually irregularly scattered in memory and thus don't\n\t * interfere much with each other. But Atomic objects residing in arrays will tend to be placed adjacent to each\n\t * other, and so will most often share cache lines (with a huge negative performance impact) without this\n\t * precaution.\n\t *\n\t * In part because Cells are relatively large, we avoid creating them until they are needed. When there is no\n\t * contention, all updates are made to the base field. Upon first contention (a failed CAS on base update), the\n\t * table is initialized to size 2. The table size is doubled upon further contention until reaching the nearest\n\t * power of two greater than or equal to the number of CPUS. Table slots remain empty (null) until they are needed.\n\t *\n\t * A single spinlock (\"busy\") is used for initializing and resizing the table, as well as populating slots with new\n\t * Cells. There is no need for a blocking lock; when the lock is not available, threads try other slots (or the\n\t * base). During these retries, there is increased contention and reduced locality, which is still better than\n\t * alternatives.\n\t *\n\t * Per-thread hash codes are initialized to random values. Contention and/or table collisions are indicated by\n\t * failed CASes when performing an update operation (see method retryUpdate). Upon a collision, if the table size is\n\t * less than the capacity, it is doubled in size unless some other thread holds the lock. If a hashed slot is empty,\n\t * and lock is available, a new Cell is created. Otherwise, if the slot exists, a CAS is tried. Retries proceed by\n\t * \"double hashing\", using a secondary hash (Marsaglia XorShift) to try to find a free slot.\n\t *\n\t * The table size is capped because, when there are more threads than CPUs, supposing that each thread were bound to\n\t * a CPU, there would exist a perfect hash function mapping threads to slots that eliminates collisions. When we\n\t * reach capacity, we search for this mapping by randomly varying the hash codes of colliding threads. Because\n\t * search is random, and collisions only become known via CAS failures, convergence can be slow, and because threads\n\t * are typically not bound to CPUS forever, may not occur at all. However, despite these limitations, observed\n\t * contention rates are typically low in these cases.\n\t *\n\t * It is possible for a Cell to become unused when threads that once hashed to it terminate, as well as in the case\n\t * where doubling the table causes no thread to hash to it under expanded mask. We do not try to detect or remove\n\t * such cells, under the assumption that for long-running instances, observed contention levels will recur, so the\n\t * cells will eventually be needed again; and for short-lived ones, it does not matter.\n\t */\n\n\t/**\n\t * Padded variant of AtomicLong supporting only raw accesses plus CAS.\n\t * The value field is placed between pads, hoping that the JVM doesn't\n\t * reorder them.\n\t *\n\t * JVM intrinsics note: It would be possible to use a release-only\n\t * form of CAS here, if it were provided.\n\t */\n\tstatic final class Cell {\n\t\tvolatile long p0, p1, p2, p3, p4, p5, p6;\n\t\tvolatile long value;\n\t\tvolatile long q0, q1, q2, q3, q4, q5, q6;\n\n\t\tCell(long x) {\n\t\t\tvalue = x;\n\t\t}\n\n\t\tfinal boolean cas(long cmp, long val) {\n\t\t\treturn UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);\n\t\t}\n\n\t\t// Unsafe mechanics\n\t\tprivate static final sun.misc.Unsafe UNSAFE;\n\t\tprivate static final long valueOffset;\n\t\tstatic {\n\t\t\ttry {\n\t\t\t\tUNSAFE = getUnsafe();\n\t\t\t\tClass<?> ak = Cell.class;\n\t\t\t\tvalueOffset = UNSAFE.objectFieldOffset(ak.getDeclaredField(\"value\"));\n\t\t\t} catch (Exception e) {\n\t\t\t\tthrow new Error(e);\n\t\t\t}\n\t\t}\n\n\t}\n\n\t/**\n\t * ThreadLocal holding a single-slot int array holding hash code.\n\t * Unlike the JDK8 version of this class, we use a suboptimal\n\t * int[] representation to avoid introducing a new type that can\n\t * impede class-unloading when ThreadLocals are not removed.\n\t */\n\tstatic final ThreadLocal<int[]> threadHashCode = new ThreadLocal<int[]>();\n\n\t/**\n\t * Generator of new random hash codes\n\t */\n\tstatic final Random rng = new Random();\n\n\t/** Number of CPUS, to place bound on table size */\n\tstatic final int NCPU = Runtime.getRuntime().availableProcessors();\n\n\t/**\n\t * Table of cells. When non-null, size is a power of 2.\n\t */\n\ttransient volatile Cell[] cells;\n\n\t/**\n\t * Base value, used mainly when there is no contention, but also as\n\t * a fallback during table initialization races. Updated via CAS.\n\t */\n\ttransient volatile long base;\n\n\t/**\n\t * Spinlock (locked via CAS) used when resizing and/or creating Cells.\n\t */\n\ttransient volatile int busy;\n\n\t/**\n\t * Package-private default constructor\n\t */\n\tStriped64() {\n\t}\n\n\t/**\n\t * CASes the base field.\n\t */\n\tfinal boolean casBase(long cmp, long val) {\n\t\treturn UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val);\n\t}\n\n\t/**\n\t * CASes the busy field from 0 to 1 to acquire lock.\n\t */\n\tfinal boolean casBusy() {\n\t\treturn UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1);\n\t}\n\n\t/**\n\t * Computes the function of current and new value. Subclasses\n\t * should open-code this update function for most uses, but the\n\t * virtualized form is needed within retryUpdate.\n\t *\n\t * @param currentValue the current value (of either base or a cell)\n\t * @param newValue the argument from a user update call\n\t * @return result of the update function\n\t */\n\tabstract long fn(long currentValue, long newValue);\n\n\t/**\n\t * Handles cases of updates involving initialization, resizing,\n\t * creating new Cells, and/or contention. See above for\n\t * explanation. This method suffers the usual non-modularity\n\t * problems of optimistic retry code, relying on rechecked sets of\n\t * reads.\n\t *\n\t * @param x the value\n\t * @param hc the hash code holder\n\t * @param wasUncontended false if CAS failed before call\n\t */\n\tfinal void retryUpdate(long x, int[] hc, boolean wasUncontended) {\n\t\tint h;\n\t\tif (hc == null) {\n\t\t\tthreadHashCode.set(hc = new int[1]); // Initialize randomly\n\t\t\tint r = rng.nextInt(); // Avoid zero to allow xorShift rehash\n\t\t\th = hc[0] = (r == 0) ? 1 : r;\n\t\t} else\n\t\t\th = hc[0];\n\t\tboolean collide = false; // True if last slot nonempty\n\t\tfor (;;) {\n\t\t\tCell[] as;\n\t\t\tCell a;\n\t\t\tint n;\n\t\t\tlong v;\n\t\t\tif ((as = cells) != null && (n = as.length) > 0) {\n\t\t\t\tif ((a = as[(n - 1) & h]) == null) {\n\t\t\t\t\tif (busy == 0) { // Try to attach new Cell\n\t\t\t\t\t\tCell r = new Cell(x); // Optimistically create\n\t\t\t\t\t\tif (busy == 0 && casBusy()) {\n\t\t\t\t\t\t\tboolean created = false;\n\t\t\t\t\t\t\ttry { // Recheck under lock\n\t\t\t\t\t\t\t\tCell[] rs;\n\t\t\t\t\t\t\t\tint m, j;\n\t\t\t\t\t\t\t\tif ((rs = cells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) {\n\t\t\t\t\t\t\t\t\trs[j] = r;\n\t\t\t\t\t\t\t\t\tcreated = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tbusy = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (created) break;\n\t\t\t\t\t\t\tcontinue; // Slot is now non-empty\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcollide = false;\n\t\t\t\t} else if (!wasUncontended) // CAS already known to fail\n\t\t\t\t\twasUncontended = true; // Continue after rehash\n\t\t\t\telse if (a.cas(v = a.value, fn(v, x)))\n\t\t\t\t\tbreak;\n\t\t\t\telse if (n >= NCPU || cells != as)\n\t\t\t\t\tcollide = false; // At max size or stale\n\t\t\t\telse if (!collide)\n\t\t\t\t\tcollide = true;\n\t\t\t\telse if (busy == 0 && casBusy()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (cells == as) { // Expand table unless stale\n\t\t\t\t\t\t\tCell[] rs = new Cell[n << 1];\n\t\t\t\t\t\t\tfor (int i = 0; i < n; ++i)\n\t\t\t\t\t\t\t\trs[i] = as[i];\n\t\t\t\t\t\t\tcells = rs;\n\t\t\t\t\t\t}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tbusy = 0;\n\t\t\t\t\t}\n\t\t\t\t\tcollide = false;\n\t\t\t\t\tcontinue; // Retry with expanded table\n\t\t\t\t}\n\t\t\t\th ^= h << 13; // Rehash\n\t\t\t\th ^= h >>> 17;\n\t\t\t\th ^= h << 5;\n\t\t\t\thc[0] = h; // Record index for next time\n\t\t\t} else if (busy == 0 && cells == as && casBusy()) {\n\t\t\t\tboolean init = false;\n\t\t\t\ttry { // Initialize table\n\t\t\t\t\tif (cells == as) {\n\t\t\t\t\t\tCell[] rs = new Cell[2];\n\t\t\t\t\t\trs[h & 1] = new Cell(x);\n\t\t\t\t\t\tcells = rs;\n\t\t\t\t\t\tinit = true;\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\tbusy = 0;\n\t\t\t\t}\n\t\t\t\tif (init) break;\n\t\t\t} else if (casBase(v = base, fn(v, x))) break; // Fall back on using base\n\t\t}\n\t}\n\n\n\t/**\n\t * Sets base and all cells to the given value.\n\t */\n\tfinal void internalReset(long initialValue) {\n\t\tCell[] as = cells;\n\t\tbase = initialValue;\n\t\tif (as != null) {\n\t\t\tint n = as.length;\n\t\t\tfor (int i = 0; i < n; ++i) {\n\t\t\t\tCell a = as[i];\n\t\t\t\tif (a != null) a.value = initialValue;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Unsafe mechanics\n\tprivate static final sun.misc.Unsafe UNSAFE;\n\tprivate static final long baseOffset;\n\tprivate static final long busyOffset;\n\tstatic {\n\t\ttry {\n\t\t\tUNSAFE = getUnsafe();\n\t\t\tClass<?> sk = Striped64.class;\n\t\t\tbaseOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField(\"base\"));\n\t\t\tbusyOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField(\"busy\"));\n\t\t} catch (Exception e) {\n\t\t\tthrow new Error(e);\n\t\t}\n\t}\n\n\t/**\n\t * Returns a sun.misc.Unsafe.  Suitable for use in a 3rd party package.\n\t * Replace with a simple call to Unsafe.getUnsafe when integrating\n\t * into a jdk.\n\t *\n\t * @return a sun.misc.Unsafe\n\t */\n\tprivate static sun.misc.Unsafe getUnsafe() {\n\t\ttry {\n\t\t\treturn sun.misc.Unsafe.getUnsafe();\n\t\t} catch (SecurityException tryReflectionInstead) {\n\t\t}\n\t\ttry {\n\t\t\treturn java.security.AccessController\n\t\t\t\t\t.doPrivileged(new java.security.PrivilegedExceptionAction<sun.misc.Unsafe>() {\n\t\t\t\t\t\tpublic sun.misc.Unsafe run() throws Exception {\n\t\t\t\t\t\t\tClass<sun.misc.Unsafe> k = sun.misc.Unsafe.class;\n\t\t\t\t\t\t\tfor (java.lang.reflect.Field f : k.getDeclaredFields()) {\n\t\t\t\t\t\t\t\tf.setAccessible(true);\n\t\t\t\t\t\t\t\tObject x = f.get(null);\n\t\t\t\t\t\t\t\tif (k.isInstance(x)) return k.cast(x);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tthrow new NoSuchFieldError(\"the Unsafe\");\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t} catch (java.security.PrivilegedActionException e) {\n\t\t\tthrow new RuntimeException(\"Could not initialize intrinsics\", e.getCause());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/limiter/RateLimiterUtil.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.limiter;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\n\nimport com.google.common.util.concurrent.RateLimiter;\n\npublic class RateLimiterUtil {\n\n\t/**\n\t * 一个用来定制RateLimiter的方法，默认一开始就桶里就装满token。\n\t * \n\t * @param permitsPerSecond 每秒允许的请求数，可看成QPS。\n\t * @param maxBurstSeconds 可看成桶的容量，Guava中最大的突发流量缓冲时间，默认是1s, permitsPerSecond * maxBurstSeconds，就是闲置时累积的缓冲token最大值。\n\t */\n\tpublic static RateLimiter create(double permitsPerSecond, double maxBurstSeconds)\n\t\t\tthrows ReflectiveOperationException {\n\t\treturn create(permitsPerSecond, maxBurstSeconds, true);\n\t}\n\n\t/**\n\t * 一个用来定制RateLimiter的方法。\n\t * \n\t * @param permitsPerSecond 每秒允许的请求书，可看成QPS\n\t * @param maxBurstSeconds 最大的突发缓冲时间。用来应对突发流量。Guava的实现默认是1s。permitsPerSecond * maxBurstSeconds的数量，就是闲置时预留的缓冲token数量\n\t * @param filledWithToken 是否需要创建时就保留有permitsPerSecond * maxBurstSeconds的token\n\t */\n\tpublic static RateLimiter create(double permitsPerSecond, double maxBurstSeconds, boolean filledWithToken)\n\t\t\tthrows ReflectiveOperationException {\n\t\tClass<?> sleepingStopwatchClass = Class\n\t\t\t\t.forName(\"com.google.common.util.concurrent.RateLimiter$SleepingStopwatch\");\n\t\tMethod createStopwatchMethod = sleepingStopwatchClass.getDeclaredMethod(\"createFromSystemTimer\");\n\t\tcreateStopwatchMethod.setAccessible(true);\n\t\tObject stopwatch = createStopwatchMethod.invoke(null);\n\n\t\tClass<?> burstyRateLimiterClass = Class\n\t\t\t\t.forName(\"com.google.common.util.concurrent.SmoothRateLimiter$SmoothBursty\");\n\t\tConstructor<?> burstyRateLimiterConstructor = burstyRateLimiterClass.getDeclaredConstructors()[0];\n\t\tburstyRateLimiterConstructor.setAccessible(true);\n\n\t\t// set maxBurstSeconds\n\t\tRateLimiter rateLimiter = (RateLimiter) burstyRateLimiterConstructor.newInstance(stopwatch, maxBurstSeconds);\n\t\trateLimiter.setRate(permitsPerSecond);\n\n\t\tif (filledWithToken) {\n\t\t\t// set storedPermits\n\t\t\tsetField(rateLimiter, \"storedPermits\", permitsPerSecond * maxBurstSeconds);\n\t\t}\n\n\t\treturn rateLimiter;\n\t}\n\n\tprivate static boolean setField(Object targetObject, String fieldName, Object fieldValue) {\n\t\tField field;\n\t\ttry {\n\t\t\tfield = targetObject.getClass().getDeclaredField(fieldName);\n\t\t} catch (NoSuchFieldException e) {\n\t\t\tfield = null;\n\t\t}\n\t\tClass superClass = targetObject.getClass().getSuperclass();\n\t\twhile (field == null && superClass != null) {\n\t\t\ttry {\n\t\t\t\tfield = superClass.getDeclaredField(fieldName);\n\t\t\t} catch (NoSuchFieldException e) {\n\t\t\t\tsuperClass = superClass.getSuperclass();\n\t\t\t}\n\t\t}\n\t\tif (field == null) {\n\t\t\treturn false;\n\t\t}\n\t\tfield.setAccessible(true);\n\t\ttry {\n\t\t\tfield.set(targetObject, fieldValue);\n\t\t\treturn true;\n\t\t} catch (IllegalAccessException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/limiter/Sampler.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.limiter;\n\nimport org.apache.commons.lang3.Validate;\n\nimport com.vip.vjtools.vjkit.number.RandomUtil;\n\n/**\n * 采样器\n * \n * 移植 Twitter Common, 优化使用ThreadLocalRandom\n * \n * https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/util/Sampler.java\n * \n */\npublic class Sampler {\n\n\tprivate static final Double ALWAYS = Double.valueOf(100);\n\tprivate static final Double NEVER = Double.valueOf(0);\n\n\tprivate double threshold;\n\n\tprotected Sampler() {\n\t}\n\n\t/**\n\t * @param selectPercent 采样率，在0-100 之间，可以有小数位\n\t */\n\tprotected Sampler(double selectPercent) {\n\t\tValidate.isTrue((selectPercent >= 0) && (selectPercent <= 100),\n\t\t\t\t\"Invalid selectPercent value: \" + selectPercent);\n\n\t\tthis.threshold = selectPercent / 100;\n\t}\n\n\t/**\n\t * 优化的创建函数，如果为0或100时，返回更直接的采样器\n\t */\n\tpublic static Sampler create(Double selectPercent) {\n\t\tif (selectPercent.equals(ALWAYS)) {\n\t\t\treturn new AlwaysSampler();\n\t\t} else if (selectPercent.equals(NEVER)) {\n\t\t\treturn new NeverSampler();\n\t\t} else {\n\t\t\treturn new Sampler(selectPercent);\n\t\t}\n\t}\n\n\t/**\n\t * 判断当前请求是否命中采样\n\t */\n\tpublic boolean select() {\n\t\treturn RandomUtil.threadLocalRandom().nextDouble() < threshold;\n\t}\n\n\t/**\n\t * 采样率为100时，总是返回true\n\t */\n\tprotected static class AlwaysSampler extends Sampler {\n\t\t@Override\n\t\tpublic boolean select() {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/**\n\t * 采样率为0时，总是返回false\n\t */\n\tprotected static class NeverSampler extends Sampler {\n\t\t@Override\n\t\tpublic boolean select() {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/limiter/TimeIntervalLimiter.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.limiter;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\n\npublic class TimeIntervalLimiter {\n\n\tprivate final AtomicLong lastTimeAtom = new AtomicLong(0);\n\n\tprivate long windowSizeMillis;\n\n\tpublic TimeIntervalLimiter(long interval, TimeUnit timeUnit) {\n\t\tthis.windowSizeMillis = timeUnit.toMillis(interval);\n\t}\n\n\tpublic boolean tryAcquire() {\n\t\tlong currentTime = System.currentTimeMillis();\n\t\tlong lastTime = lastTimeAtom.get();\n\t\treturn currentTime - lastTime >= windowSizeMillis && lastTimeAtom.compareAndSet(lastTime, currentTime);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/threadpool/AbortPolicyWithReport.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.threadpool;\n\nimport java.util.concurrent.RejectedExecutionException;\nimport java.util.concurrent.ThreadPoolExecutor;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.vip.vjtools.vjkit.concurrent.ThreadDumpper;\n\n/**\n * Abort Policy.\n * 如果线程池已满，退出申请并打印Thread Dump(会有一定的最少间隔，默认为10分钟）\n */\npublic class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {\n\n\tprotected static final Logger logger = LoggerFactory.getLogger(AbortPolicyWithReport.class);\n\n\tprivate final String threadName;\n\n\tprivate ThreadDumpper dumpper = new ThreadDumpper();\n\n\tpublic AbortPolicyWithReport(String threadName) {\n\t\tthis.threadName = threadName;\n\t}\n\n\t@Override\n\tpublic void rejectedExecution(Runnable r, ThreadPoolExecutor e) {\n\t\tString msg = String.format(\n\t\t\t\t\"Thread pool is EXHAUSTED!\"\n\t\t\t\t\t\t+ \" Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d),\"\n\t\t\t\t\t\t+ \" Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s)!\",\n\t\t\t\tthreadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(),\n\t\t\t\te.getLargestPoolSize(), e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(),\n\t\t\t\te.isTerminating());\n\t\tlogger.warn(msg);\n\t\tdumpper.tryThreadDump(null);\n\t\tthrow new RejectedExecutionException(msg);\n\t}\n\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/threadpool/QueuableCachedThreadPool.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.threadpool;\n\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.RejectedExecutionException;\nimport java.util.concurrent.RejectedExecutionHandler;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * From Tomcat 8.5.6, 传统的FixedThreadPool有Queue但线程数量不变，而CachedThreadPool线程数可变但没有Queue\n * \n * Tomcat的线程池，通过控制TaskQueue，线程数，但线程数到达最大时会进入Queue中.\n * \n * 代码从Tomcat复制，主要修改包括：\n * \n * 1. 删除定期重启线程避免内存泄漏的功能，\n * \n * 2. TaskQueue中可能3次有锁的读取线程数量，改为只读取1次，这把锁也是这个实现里的唯一遗憾了。\n * \n * https://github.com/apache/tomcat/blob/trunk/java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java\n */\npublic final class QueuableCachedThreadPool extends java.util.concurrent.ThreadPoolExecutor {\n\n\t/**\n\t * The number of tasks submitted but not yet finished. This includes tasks in the queue and tasks that have been\n\t * handed to a worker thread but the latter did not start executing the task yet. This number is always greater or\n\t * equal to {@link #getActiveCount()}.\n\t */\n\tprivate final AtomicInteger submittedCount = new AtomicInteger(0);\n\n\tpublic QueuableCachedThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,\n\t\t\tControllableQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {\n\t\tsuper(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);\n\t\tworkQueue.setParent(this);\n\t\tprestartAllCoreThreads(); // NOSOANR\n\t}\n\n\t@Override\n\tprotected void afterExecute(Runnable r, Throwable t) {\n\t\tsubmittedCount.decrementAndGet();\n\t}\n\n\tpublic int getSubmittedCount() {\n\t\treturn submittedCount.get();\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t */\n\t@Override\n\tpublic void execute(Runnable command) {\n\t\texecute(command, 0, TimeUnit.MILLISECONDS);\n\t}\n\n\t/**\n\t * Executes the given command at some time in the future. The command may execute in a new thread, in a pooled\n\t * thread, or in the calling thread, at the discretion of the <tt>Executor</tt> implementation. If no threads are\n\t * available, it will be added to the work queue. If the work queue is full, the system will wait for the specified\n\t * time and it throw a RejectedExecutionException if the queue is still full after that.\n\t *\n\t * @param command the runnable task\n\t * @param timeout A timeout for the completion of the task\n\t * @param unit The timeout time unit\n\t * @throws RejectedExecutionException if this task cannot be accepted for execution - the queue is full\n\t * @throws NullPointerException if command or unit is null\n\t */\n\tpublic void execute(Runnable command, long timeout, TimeUnit unit) {\n\t\tsubmittedCount.incrementAndGet();\n\t\ttry {\n\t\t\tsuper.execute(command);\n\t\t} catch (RejectedExecutionException rx) { // NOSONAR\n\t\t\t// not to re-throw this exception because this is only used to find out whether the pool is full, not for a\n\t\t\t// exception purpose\n\t\t\tfinal ControllableQueue queue = (ControllableQueue) super.getQueue();\n\t\t\ttry {\n\t\t\t\tif (!queue.force(command, timeout, unit)) {\n\t\t\t\t\tsubmittedCount.decrementAndGet();\n\t\t\t\t\tthrow new RejectedExecutionException(\"Queue capacity is full.\");\n\t\t\t\t}\n\t\t\t} catch (InterruptedException ignore) {\n\t\t\t\tsubmittedCount.decrementAndGet();\n\t\t\t\tthrow new RejectedExecutionException(ignore);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * https://github.com/apache/tomcat/blob/trunk/java/org/apache/tomcat/util/threads/TaskQueue.java\n\t */\n\tprotected static class ControllableQueue extends LinkedBlockingQueue<Runnable> {\n\n\t\tprivate static final long serialVersionUID = 5044057462066661171L;\n\t\tprivate transient volatile QueuableCachedThreadPool parent = null;\n\n\t\tpublic ControllableQueue(int capacity) {\n\t\t\tsuper(capacity);\n\t\t}\n\n\t\tpublic void setParent(QueuableCachedThreadPool tp) {\n\t\t\tparent = tp;\n\t\t}\n\n\t\tpublic boolean force(Runnable o) {\n\t\t\tif (parent.isShutdown()) {\n\t\t\t\tthrow new RejectedExecutionException(\"Executor not running, can't force a command into the queue\");\n\t\t\t}\n\t\t\treturn super.offer(o); // forces the item onto the queue, to be used if the task is rejected\n\t\t}\n\n\t\tpublic boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {\n\t\t\tif (parent.isShutdown()) {\n\t\t\t\tthrow new RejectedExecutionException(\"Executor not running, can't force a command into the queue\");\n\t\t\t}\n\t\t\treturn super.offer(o, timeout, unit); // forces the item onto the queue, to be used if the task is rejected\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean offer(Runnable o) {\n\t\t\t// springside: threadPool.getPoolSize() 是个有锁的操作，所以尽量减少\n\n\t\t\tint currentPoolSize = parent.getPoolSize();\n\n\t\t\t// we are maxed out on threads, simply queue the object\n\t\t\tif (currentPoolSize >= parent.getMaximumPoolSize()) {\n\t\t\t\treturn super.offer(o);\n\t\t\t}\n\t\t\t// we have idle threads, just add it to the queue\n\t\t\tif (parent.getSubmittedCount() < currentPoolSize) {\n\t\t\t\treturn super.offer(o);\n\t\t\t}\n\t\t\t// if we have less threads than maximum force creation of a new thread\n\t\t\tif (currentPoolSize < parent.getMaximumPoolSize()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t// if we reached here, we need to add it to the queue\n\t\t\treturn super.offer(o);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/threadpool/ThreadPoolBuilder.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.threadpool;\n\nimport java.util.concurrent.ArrayBlockingQueue;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.RejectedExecutionHandler;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.ThreadPoolExecutor.AbortPolicy;\nimport java.util.concurrent.TimeUnit;\n\nimport org.apache.commons.lang3.Validate;\n\nimport com.vip.vjtools.vjkit.concurrent.threadpool.QueuableCachedThreadPool.ControllableQueue;\n\n/**\n * ThreadPool创建的工具类.\n * \n * 对比JDK Executors中的newFixedThreadPool(), newCachedThreadPool(),newScheduledThreadPool, 提供更多有用的配置项.\n * \n * 另包含了移植自Tomcat的QueuableCachedPool.\n * \n * 使用示例如下：\n * \n * <pre>\n * ExecutorService ExecutorService = new FixedThreadPoolBuilder().setPoolSize(10).build();\n * </pre>\n * \n * 参考文章 《Java ThreadPool的正确打开方式》http://calvin1978.blogcn.com/articles/java-threadpool.html\n */\npublic class ThreadPoolBuilder {\n\n\tprivate static RejectedExecutionHandler defaultRejectHandler = new AbortPolicy();\n\n\t/**\n\t * @see FixedThreadPoolBuilder\n\t */\n\tpublic static FixedThreadPoolBuilder fixedPool() {\n\t\treturn new FixedThreadPoolBuilder();\n\t}\n\n\t/**\n\t * @see CachedThreadPoolBuilder\n\t */\n\tpublic static CachedThreadPoolBuilder cachedPool() {\n\t\treturn new CachedThreadPoolBuilder();\n\t}\n\n\t/**\n\t * @see ScheduledThreadPoolBuilder\n\t */\n\tpublic static ScheduledThreadPoolBuilder scheduledPool() {\n\t\treturn new ScheduledThreadPoolBuilder();\n\t}\n\n\t/**\n\t * @see QueuableCachedThreadPoolBuilder\n\t */\n\tpublic static QueuableCachedThreadPoolBuilder queuableCachedPool() {\n\t\treturn new QueuableCachedThreadPoolBuilder();\n\t}\n\n\t/**\n\t * 创建FixedThreadPool.建议必须设置queueSize保证有界。\n\t * \n\t * 1. 任务提交时, 如果线程数还没达到poolSize即创建新线程并绑定任务(即poolSize次提交后线程总数必达到poolSize，不会重用之前的线程)\n\t * \n\t * poolSize默认为1，即singleThreadPool.\n\t * \n\t * 2. 第poolSize次任务提交后, 新增任务放入Queue中, Pool中的所有线程从Queue中take任务执行.\n\t * \n\t * Queue默认为无限长的LinkedBlockingQueue, 但建议设置queueSize换成有界的队列.\n\t * \n\t * 如果使用有界队列, 当队列满了之后,会调用RejectHandler进行处理, 默认为AbortPolicy，抛出RejectedExecutionException异常.\n\t * 其他可选的Policy包括静默放弃当前任务(Discard)，放弃Queue里最老的任务(DiscardOldest)，或由主线程来直接执行(CallerRuns).\n\t * \n\t * 3. 因为线程全部为core线程，所以不会在空闲时回收.\n\t */\n\tpublic static class FixedThreadPoolBuilder {\n\n\t\tprivate int poolSize = 1;\n\t\tprivate int queueSize = -1;\n\n\t\tprivate ThreadFactory threadFactory;\n\t\tprivate String threadNamePrefix;\n\t\tprivate Boolean daemon;\n\n\t\tprivate RejectedExecutionHandler rejectHandler;\n\n\t\t/**\n\t\t * Pool大小，默认为1，即singleThreadPool\n\t\t */\n\t\tpublic FixedThreadPoolBuilder setPoolSize(int poolSize) {\n\t\t\tValidate.isTrue(poolSize >= 1);\n\t\t\tthis.poolSize = poolSize;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 不设置时默认为-1, 使用无限长的LinkedBlockingQueue.\n\t\t * \n\t\t * 为正数时使用ArrayBlockingQueue.\n\t\t */\n\t\tpublic FixedThreadPoolBuilder setQueueSize(int queueSize) {\n\t\t\tthis.queueSize = queueSize;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与threadNamePrefix互斥, 优先使用ThreadFactory\n\t\t */\n\t\tpublic FixedThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) {\n\t\t\tthis.threadFactory = threadFactory;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与ThreadFactory互斥, 优先使用ThreadFactory\n\t\t */\n\t\tpublic FixedThreadPoolBuilder setThreadNamePrefix(String threadNamePrefix) {\n\t\t\tthis.threadNamePrefix = threadNamePrefix;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与threadFactory互斥, 优先使用ThreadFactory\n\t\t * \n\t\t * 默认为NULL，不进行设置，使用JDK的默认值.\n\t\t */\n\t\tpublic FixedThreadPoolBuilder setDaemon(Boolean daemon) {\n\t\t\tthis.daemon = daemon;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic FixedThreadPoolBuilder setRejectHandler(RejectedExecutionHandler rejectHandler) {\n\t\t\tthis.rejectHandler = rejectHandler;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic ThreadPoolExecutor build() {\n\t\t\tBlockingQueue<Runnable> queue = null;\n\t\t\tif (queueSize < 1) {\n\t\t\t\tqueue = new LinkedBlockingQueue<Runnable>();\n\t\t\t} else {\n\t\t\t\tqueue = new ArrayBlockingQueue<Runnable>(queueSize);\n\t\t\t}\n\n\t\t\tthreadFactory = createThreadFactory(threadFactory, threadNamePrefix, daemon);\n\n\t\t\tif (rejectHandler == null) {\n\t\t\t\trejectHandler = defaultRejectHandler;\n\t\t\t}\n\n\t\t\treturn new ThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.MILLISECONDS, queue, threadFactory,\n\t\t\t\t\trejectHandler);\n\t\t}\n\t}\n\n\t/**\n\t * 创建CachedThreadPool, maxSize建议设置\n\t * \n\t * 1. 任务提交时, 如果线程数还没达到minSize即创建新线程并绑定任务(即minSize次提交后线程总数必达到minSize, 不会重用之前的线程)\n\t * \n\t * minSize默认为0, 可设置保证有基本的线程处理请求不被回收.\n\t * \n\t * 2. 第minSize次任务提交后, 新增任务提交进SynchronousQueue后，如果没有空闲线程立刻处理，则会创建新的线程, 直到总线程数达到上限.\n\t * \n\t * maxSize默认为Integer.Max, 可以进行设置.\n\t * \n\t * 如果设置了maxSize, 当总线程数达到上限, 会调用RejectHandler进行处理, 默认为AbortPolicy, 抛出RejectedExecutionException异常.\n\t * 其他可选的Policy包括静默放弃当前任务(Discard)，或由主线程来直接执行(CallerRuns).\n\t * \n\t * 3. minSize以上, maxSize以下的线程, 如果在keepAliveTime中都poll不到任务执行将会被结束掉, keeAliveTimeJDK默认为10秒.\n\t * JDK默认值60秒太高，如高达1000线程时，要低于16QPS时才会开始回收线程, 因此改为默认10秒.\n\t */\n\tpublic static class CachedThreadPoolBuilder {\n\n\t\tprivate int minSize = 0;\n\t\tprivate int maxSize = Integer.MAX_VALUE;\n\t\tprivate int keepAliveSecs = 10;\n\n\t\tprivate ThreadFactory threadFactory;\n\t\tprivate String threadNamePrefix;\n\t\tprivate Boolean daemon;\n\n\t\tprivate RejectedExecutionHandler rejectHandler;\n\n\t\tpublic CachedThreadPoolBuilder setMinSize(int minSize) {\n\t\t\tthis.minSize = minSize;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * Max默认Integer.MAX_VALUE的，建议设置\n\t\t */\n\t\tpublic CachedThreadPoolBuilder setMaxSize(int maxSize) {\n\t\t\tthis.maxSize = maxSize;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * JDK默认值60秒太高，如高达1000线程时，要低于16QPS时才会开始回收线程, 因此改为默认10秒.\n\t\t */\n\t\tpublic CachedThreadPoolBuilder setKeepAliveSecs(int keepAliveSecs) {\n\t\t\tthis.keepAliveSecs = keepAliveSecs;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与threadNamePrefix互斥, 优先使用ThreadFactory\n\t\t */\n\t\tpublic CachedThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) {\n\t\t\tthis.threadFactory = threadFactory;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与threadFactory互斥, 优先使用ThreadFactory\n\t\t */\n\t\tpublic CachedThreadPoolBuilder setThreadNamePrefix(String threadNamePrefix) {\n\t\t\tthis.threadNamePrefix = threadNamePrefix;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与threadFactory互斥, 优先使用ThreadFactory\n\t\t * \n\t\t * 默认为NULL，不进行设置，使用JDK的默认值.\n\t\t */\n\t\tpublic CachedThreadPoolBuilder setDaemon(Boolean daemon) {\n\t\t\tthis.daemon = daemon;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic CachedThreadPoolBuilder setRejectHandler(RejectedExecutionHandler rejectHandler) {\n\t\t\tthis.rejectHandler = rejectHandler;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic ThreadPoolExecutor build() {\n\n\t\t\tthreadFactory = createThreadFactory(threadFactory, threadNamePrefix, daemon);\n\n\t\t\tif (rejectHandler == null) {\n\t\t\t\trejectHandler = defaultRejectHandler;\n\t\t\t}\n\n\t\t\treturn new ThreadPoolExecutor(minSize, maxSize, keepAliveSecs, TimeUnit.SECONDS,\n\t\t\t\t\tnew SynchronousQueue<Runnable>(), threadFactory, rejectHandler);\n\t\t}\n\t}\n\n\t/*\n\t * 创建ScheduledPool.\n\t */\n\tpublic static class ScheduledThreadPoolBuilder {\n\n\t\tprivate int poolSize = 1;\n\t\tprivate ThreadFactory threadFactory;\n\t\tprivate String threadNamePrefix;\n\n\t\t/**\n\t\t * 默认为1\n\t\t */\n\t\tpublic ScheduledThreadPoolBuilder setPoolSize(int poolSize) {\n\t\t\tthis.poolSize = poolSize;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与threadNamePrefix互斥, 优先使用ThreadFactory\n\t\t */\n\t\tpublic ScheduledThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) {\n\t\t\tthis.threadFactory = threadFactory;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic ScheduledThreadPoolBuilder setThreadNamePrefix(String threadNamePrefix) {\n\t\t\tthis.threadNamePrefix = threadNamePrefix;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic ScheduledThreadPoolExecutor build() {\n\t\t\tthreadFactory = createThreadFactory(threadFactory, threadNamePrefix, Boolean.TRUE);\n\t\t\treturn new ScheduledThreadPoolExecutor(poolSize, threadFactory);\n\t\t}\n\t}\n\n\t/**\n\t * 从Tomcat移植过来的可扩展可用Queue缓存任务的ThreadPool\n\t * \n\t * @see QueuableCachedThreadPool\n\t */\n\tpublic static class QueuableCachedThreadPoolBuilder {\n\n\t\tprivate int minSize = 0;\n\t\tprivate int maxSize = Integer.MAX_VALUE;\n\t\tprivate int keepAliveSecs = 10;\n\t\tprivate int queueSize = 100;\n\n\t\tprivate ThreadFactory threadFactory;\n\t\tprivate String threadNamePrefix;\n\t\tprivate Boolean daemon;\n\n\t\tprivate RejectedExecutionHandler rejectHandler;\n\n\t\tpublic QueuableCachedThreadPoolBuilder setMinSize(int minSize) {\n\t\t\tthis.minSize = minSize;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic QueuableCachedThreadPoolBuilder setMaxSize(int maxSize) {\n\t\t\tthis.maxSize = maxSize;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * LinkedQueue长度, 默认100\n\t\t */\n\t\tpublic QueuableCachedThreadPoolBuilder setQueueSize(int queueSize) {\n\t\t\tthis.queueSize = queueSize;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic QueuableCachedThreadPoolBuilder setKeepAliveSecs(int keepAliveSecs) {\n\t\t\tthis.keepAliveSecs = keepAliveSecs;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与threadNamePrefix互斥, 优先使用ThreadFactory\n\t\t */\n\t\tpublic QueuableCachedThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) {\n\t\t\tthis.threadFactory = threadFactory;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与threadFactory互斥, 优先使用ThreadFactory\n\t\t */\n\t\tpublic QueuableCachedThreadPoolBuilder setThreadNamePrefix(String threadNamePrefix) {\n\t\t\tthis.threadNamePrefix = threadNamePrefix;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * 与threadFactory互斥, 优先使用ThreadFactory\n\t\t * \n\t\t * 默认为NULL，不进行设置，使用JDK的默认值.\n\t\t */\n\t\tpublic QueuableCachedThreadPoolBuilder setDaemon(Boolean daemon) {\n\t\t\tthis.daemon = daemon;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic QueuableCachedThreadPoolBuilder setRejectHandler(RejectedExecutionHandler rejectHandler) {\n\t\t\tthis.rejectHandler = rejectHandler;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic QueuableCachedThreadPool build() {\n\n\t\t\tthreadFactory = createThreadFactory(threadFactory, threadNamePrefix, daemon);\n\n\t\t\tif (rejectHandler == null) {\n\t\t\t\trejectHandler = defaultRejectHandler;\n\t\t\t}\n\n\t\t\treturn new QueuableCachedThreadPool(minSize, maxSize, keepAliveSecs, TimeUnit.SECONDS,\n\t\t\t\t\tnew ControllableQueue(queueSize), threadFactory, rejectHandler);\n\t\t}\n\t}\n\n\t/**\n\t * 优先使用threadFactory，否则如果threadNamePrefix不为空则使用自建ThreadFactory，否则使用defaultThreadFactory\n\t */\n\tprivate static ThreadFactory createThreadFactory(ThreadFactory threadFactory, String threadNamePrefix,\n\t\t\tBoolean daemon) {\n\t\tif (threadFactory != null) {\n\t\t\treturn threadFactory;\n\t\t}\n\n\t\tif (threadNamePrefix != null) {\n\t\t\tif (daemon != null) {\n\t\t\t\treturn ThreadPoolUtil.buildThreadFactory(threadNamePrefix, daemon);\n\t\t\t} else {\n\t\t\t\treturn ThreadPoolUtil.buildThreadFactory(threadNamePrefix);\n\t\t\t}\n\t\t}\n\n\t\treturn Executors.defaultThreadFactory();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/threadpool/ThreadPoolUtil.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.threadpool;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\n\nimport org.apache.commons.lang3.Validate;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport com.google.common.util.concurrent.ThreadFactoryBuilder;\nimport com.vip.vjtools.vjkit.base.annotation.NotNull;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 线程池工具集\n * \n * 1. 优雅关闭线程池的(via guava)\n * \n * 2. 创建可自定义线程名的ThreadFactory(via guava)\n * \n * 3. 防止第三方Runnable未捕捉异常导致线程跑飞\n * \n * @author calvin\n *\n */\npublic class ThreadPoolUtil {\n\n\t/**\n\t * 按照ExecutorService JavaDoc示例代码编写的Graceful Shutdown方法.\n\t * \n\t * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.\n\t * \n\t * 如果1/2超时时间后, 则调用shutdownNow,取消在workQueue中Pending的任务,并中断所有阻塞函数.\n\t * \n\t * 如果1/2超时仍然超時，則強制退出.\n\t * \n\t * 另对在shutdown时线程本身被调用中断做了处理.\n\t * \n\t * 返回线程最后是否被中断.\n\t * \n\t * 使用了Guava的工具类\n\t * @see MoreExecutors#shutdownAndAwaitTermination(ExecutorService, long, TimeUnit)\n\t */\n\tpublic static boolean gracefulShutdown(@Nullable ExecutorService threadPool, int shutdownTimeoutMills) {\n\t\treturn threadPool == null\n\t\t\t\t|| MoreExecutors.shutdownAndAwaitTermination(threadPool, shutdownTimeoutMills, TimeUnit.MILLISECONDS);\n\t}\n\n\t/**\n\t * @see #gracefulShutdown\n\t */\n\tpublic static boolean gracefulShutdown(@Nullable ExecutorService threadPool, int shutdownTimeout,\n\t\t\tTimeUnit timeUnit) {\n\t\treturn threadPool == null || MoreExecutors.shutdownAndAwaitTermination(threadPool, shutdownTimeout, timeUnit);\n\t}\n\n\t/**\n\t * 创建ThreadFactory，使得创建的线程有自己的名字而不是默认的\"pool-x-thread-y\"\n\t * \n\t * 使用了Guava的工具类\n\t * \n\t * @see ThreadFactoryBuilder#build()\n\t */\n\tpublic static ThreadFactory buildThreadFactory(@NotNull String threadNamePrefix) {\n\t\treturn new ThreadFactoryBuilder().setNameFormat(threadNamePrefix + \"-%d\").build();\n\t}\n\n\t/**\n\t * 可设定是否daemon, daemon线程在主线程已执行完毕时, 不会阻塞应用不退出, 而非daemon线程则会阻塞.\n\t * \n\t * @see #buildThreadFactory\n\t */\n\tpublic static ThreadFactory buildThreadFactory(@NotNull String threadNamePrefix, @NotNull boolean daemon) {\n\t\treturn new ThreadFactoryBuilder().setNameFormat(threadNamePrefix + \"-%d\").setDaemon(daemon).build();\n\t}\n\n\t/**\n\t * 防止用户没有捕捉异常导致中断了线程池中的线程, 使得SchedulerService无法继续执行.\n\t * \n\t * 在无法控制第三方包的Runnable实现时，调用本函数进行包裹.\n\t */\n\tpublic static Runnable safeRunnable(@NotNull Runnable runnable) {\n\t\treturn new SafeRunnable(runnable);\n\t}\n\n\t/**\n\t * 保证不会有Exception抛出到线程池的Runnable包裹类，防止用户没有捕捉异常导致中断了线程池中的线程, 使得SchedulerService无法执行. 在无法控制第三方包的Runnable实现时，使用本类进行包裹.\n\t */\n\tprivate static class SafeRunnable implements Runnable {\n\n\t\tprivate static Logger logger = LoggerFactory.getLogger(SafeRunnable.class);\n\n\t\tprivate Runnable runnable;\n\n\t\tpublic SafeRunnable(Runnable runnable) {\n\t\t\tValidate.notNull(runnable);\n\t\t\tthis.runnable = runnable;\n\t\t}\n\n\t\t@Override\n\t\tpublic void run() {\n\t\t\ttry {\n\t\t\t\trunnable.run();\n\t\t\t} catch (Throwable e) {\n\t\t\t\t// catch any exception, because the scheduled thread will break if the exception thrown to outside.\n\t\t\t\tlogger.error(\"Unexpected error occurred in task\", e);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/type/BasicFuture.java",
    "content": "/*\n * ==================================================================== Licensed to the Apache Software Foundation (ASF)\n * under one or more contributor license agreements. See the NOTICE file distributed with this work for additional\n * information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the\n * License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n * ====================================================================\n *\n * This software consists of voluntary contributions made by many individuals on behalf of the Apache Software\n * Foundation. For more information on the Apache Software Foundation, please see <http://www.apache.org/>.\n *\n */\npackage com.vip.vjtools.vjkit.concurrent.type;\n\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport org.apache.commons.lang3.Validate;\n\n/**\n * 从Apache HttpClient 移植(2017.4)，一个Future实现类的基本框架.\n * \n * https://github.com/apache/httpcomponents-core/blob/master/httpcore5/src/main/java/org/apache/hc/core5/concurrent/BasicFuture.java\n * \n * 不过HC用的是callback，这里用的是继承\n */\npublic abstract class BasicFuture<T> implements Future<T> {\n\n\tprivate volatile boolean completed; // NOSONAR\n\tprivate volatile boolean cancelled;\n\tprivate volatile T result;\n\tprivate volatile Exception ex;\n\n\t@Override\n\tpublic boolean isCancelled() {\n\t\treturn this.cancelled;\n\t}\n\n\t@Override\n\tpublic boolean isDone() {\n\t\treturn this.completed;\n\t}\n\n\t@Override\n\tpublic synchronized T get() throws InterruptedException, ExecutionException {\n\t\twhile (!this.completed) {\n\t\t\twait();\n\t\t}\n\t\treturn getResult();\n\t}\n\n\t@Override\n\tpublic synchronized T get(final long timeout, final TimeUnit unit)\n\t\t\tthrows InterruptedException, ExecutionException, TimeoutException {\n\t\tValidate.notNull(unit, \"Time unit\");\n\t\tfinal long msecs = unit.toMillis(timeout);\n\t\tfinal long startTime = (msecs <= 0) ? 0 : System.currentTimeMillis();\n\t\tlong waitTime = msecs;\n\t\tif (this.completed) {\n\t\t\treturn getResult();\n\t\t} else if (waitTime <= 0) {\n\t\t\tthrow new TimeoutException();\n\t\t} else {\n\t\t\tfor (;;) {\n\t\t\t\twait(waitTime);\n\t\t\t\tif (this.completed) {\n\t\t\t\t\treturn getResult();\n\t\t\t\t} else {\n\t\t\t\t\twaitTime = msecs - (System.currentTimeMillis() - startTime);\n\t\t\t\t\tif (waitTime <= 0) {\n\t\t\t\t\t\tthrow new TimeoutException();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate T getResult() throws ExecutionException {\n\t\tif (this.ex != null) {\n\t\t\tthrow new ExecutionException(this.ex);\n\t\t}\n\n\t\tif (cancelled) {\n\t\t\tthrow new CancellationException();\n\t\t}\n\t\treturn this.result;\n\t}\n\n\tpublic boolean completed(final T result) {\n\t\tsynchronized (this) {\n\t\t\tif (this.completed) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis.completed = true;\n\t\t\tthis.result = result;\n\t\t\tnotifyAll();\n\t\t}\n\t\tonCompleted(result);\n\n\t\treturn true;\n\t}\n\n\tpublic boolean failed(final Exception exception) {\n\t\tsynchronized (this) {\n\t\t\tif (this.completed) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis.completed = true;\n\t\t\tthis.ex = exception;\n\t\t\tnotifyAll();\n\t\t}\n\n\t\tonFailed(exception);\n\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean cancel(final boolean mayInterruptIfRunning) {\n\t\tsynchronized (this) {\n\t\t\tif (this.completed) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis.completed = true;\n\t\t\tthis.cancelled = true;\n\t\t\tnotifyAll();\n\t\t}\n\n\t\tonCancelled();\n\n\t\treturn true;\n\t}\n\n\tprotected abstract void onCompleted(T result);\n\n\tprotected abstract void onFailed(Exception ex);\n\n\tprotected abstract void onCancelled();\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/concurrent/type/ThreadLocalContext.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.type;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 存储于ThreadLocal的Map, 用于存储上下文.<br/>\n * \n * 但HashMap<String,Object>的存储其实较为低效，在高性能场景下可改为EnumMap<br/>\n * \n * 1.先定义枚举类，列举所有可能的Key<br/>\n * 2.替换contextMap的创建语句，见下例<br/>\n * 3.修改put()/get()中key的类型<br/>\n * \n * <pre>\n * private static ThreadLocal<Map<MyEnum, Object>> contextMap = new ThreadLocal<Map<MyEnum, Object>>() {\n * \t&#64;Override\n * \tprotected Map<MyEnum, Object> initialValue() {\n * \t\treturn new EnumMap<MyEnum, Object>(MyEnum.class);\n * \t}\n * };\n * </pre>\n */\npublic class ThreadLocalContext {\n\n\tprivate static ThreadLocal<Map<String, Object>> contextMap = new ThreadLocal<Map<String, Object>>() {\n\t\t@Override\n\t\tprotected Map<String, Object> initialValue() {\n\t\t\t// 降低loadFactory减少冲突\n\t\t\treturn new HashMap<String, Object>(16, 0.5f);\n\t\t}\n\t};\n\n\t/**\n\t * 放入ThreadLocal的上下文信息.\n\t */\n\tpublic static void put(String key, Object value) {\n\t\tcontextMap.get().put(key, value);\n\t}\n\n\t/**\n\t * 取出ThreadLocal的上下文信息.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <T> T get(String key) {\n\t\treturn (T) (contextMap.get().get(key));\n\t}\n\n\t/**\n\t * 清理ThreadLocal的Context内容.\n\t */\n\tpublic static void reset() {\n\t\tcontextMap.get().clear();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/DataMask.java",
    "content": "package com.vip.vjtools.vjkit.datamasking;\n\nimport com.alibaba.fastjson.JSON;\nimport com.vip.vjtools.vjkit.datamasking.strategy.HashMask;\n\n/**\n * 脱敏工具类\n */\npublic class DataMask {\n\n\tprivate static DataMaskJsonFilter jsonFilter = new DataMaskJsonFilter();\n\n\t/**\n\t * 设置Hash脱敏的salt\n\t */\n\tpublic static void setHashSalt(String salt){\n\t\tHashMask.setSalt(salt);\n\t}\n\n\t/**\n\t * 默认的脱敏方式，只显示第一个字符串\n\t * @param source 源字符串\n\t * @return 脱敏后的字符串\n\t */\n\tpublic static String mask(String source) {\n\t\ttry {\n\t\t\treturn mask(source, SensitiveType.Default);\n\t\t} catch (Exception e) {\n\t\t\treturn source;\n\t\t}\n\t}\n\n\t/**\n\t * 根据类型来脱敏\n\t * @param source 源字符串\n\t * @param type 类型\n\t * @return 掩码后的字符串\n\t */\n\tpublic static String mask(String source, SensitiveType type) {\n\t\ttry {\n\t\t\treturn type.getStrategy().mask(source, type.getParams());\n\t\t} catch (Exception e) {\n\t\t\treturn source;\n\t\t}\n\t}\n\n\t/**\n\t * 自定义脱敏策略来脱敏\n\t * @param source 源字符串\n\t * @param strategy 策略\n\t * @param param 策略参数\n\t * @return 脱敏后的字符串\n\t */\n\tpublic static String mask(String source,MaskStrategy strategy,int...param){\n\t\ttry {\n\t\t\treturn strategy.mask(source,param);\n\t\t} catch (Exception e) {\n\t\t\treturn source;\n\t\t}\n\t}\n\n\t/**\n\t * 将需要脱敏的字段先进行脱敏操作，最后转成json格式\n\t * @param object 需要序列化的对象\n\t * @return 脱敏后的json格式\n\t */\n\tpublic static String toJSONString(Object object) {\n\t\tif (object == null) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\treturn JSON.toJSONString(object, jsonFilter);\n\t\t} catch (Exception e) {\n\t\t\treturn JSON.toJSONString(object);\n\t\t}\n\t}\n\n\t/**\n\t * 对脱敏字段进行处理，然后调用对象原来的toString()\n\t * 注意，如果需要脱敏的字段比较多，性能会被toJson差，建议优先使用toJSON\n\t * @param object 需要序列化的对象\n\t * @return 脱敏后的toString()格式\n\t */\n\tpublic static String toString(Object object) {\n\t\tif (object == null) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\tString json = toJSONString(object);\n\t\t\tObject cloneObj = JSON.parseObject(json, object.getClass());\n\t\t\treturn cloneObj.toString();\n\t\t} catch (Exception e) {\n\t\t\treturn object.toString();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/DataMaskJsonFilter.java",
    "content": "package com.vip.vjtools.vjkit.datamasking;\n\nimport com.alibaba.fastjson.serializer.BeanContext;\nimport com.alibaba.fastjson.serializer.ContextValueFilter;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.Collection;\n\n/**\n * 对每个json字段进行脱敏处理\n * 处理String,String[] Collection<String>\n */\npublic class DataMaskJsonFilter implements ContextValueFilter {\n\t@Override\n\tpublic Object process(BeanContext context, Object object, String name, Object value) {\n\t\tif (null == value) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\tField field = context.getField();\n\n\t\t\t//处理字符串,只处理字符串相关类型的字段\n\t\t\tif (field.getType() == String.class) {\n\t\t\t\tif (((String) value).length() == 0) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\n\t\t\t\tSensitive sensitive = field.getAnnotation(Sensitive.class);\n\t\t\t\tSensitiveType sensitiveType = getSensitiveType(field, sensitive);\n\t\t\t\treturn mask((String) value, sensitive, sensitiveType);\n\n\t\t\t} else if (field.getType() == String[].class) {\n\t\t\t\t//处理数组String[]\n\t\t\t\tSensitive sensitive = field.getAnnotation(Sensitive.class);\n\t\t\t\tSensitiveType sensitiveType = getSensitiveType(field, sensitive);\n\n\t\t\t\tif (sensitiveType == null) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t\tString[] strArr = (String[]) value;\n\n\t\t\t\tfor (int i = 0; i < strArr.length; i++) {\n\t\t\t\t\tstrArr[i] = mask(strArr[i], sensitive, sensitiveType);\n\t\t\t\t}\n\t\t\t\treturn strArr;\n\t\t\t} else if (Collection.class.isAssignableFrom(field.getType())) {\n\t\t\t\t//处理Collection<String>\n\t\t\t\tSensitive sensitive = field.getAnnotation(Sensitive.class);\n\t\t\t\tSensitiveType sensitiveType = getSensitiveType(field, sensitive);\n\n\t\t\t\tif (sensitiveType == null) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\n\t\t\t\tif(!isStringCollection(field)){\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\n\t\t\t\t//没有set()的接口，重新构造一个\n\t\t\t\tCollection<String> newValue = (Collection<String>) value.getClass().newInstance();\n\t\t\t\tfor (String item : (Collection<String>) value) {\n\t\t\t\t\tnewValue.add(mask(item, sensitive, sensitiveType));\n\t\t\t\t}\n\n\t\t\t\treturn newValue;\n\n\t\t\t} else {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/**\n\t * 是否Collection<String>\n\t * @param field\n\t * @return\n\t */\n\tprivate boolean isStringCollection(Field field){\n\t\tType type = field.getGenericType();\n\t\tif (!(type instanceof ParameterizedType)) {\n\t\t\treturn false;\n\t\t}\n\t\tClass parameterizedType = (Class) ((ParameterizedType) type).getActualTypeArguments()[0];\n\t\tif (parameterizedType != String.class) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * 对字段进行mask\n\t * @param value 值\n\t * @return 脱敏后的值\n\t */\n\tprivate String mask(String value, Sensitive sensitive, SensitiveType type) {\n\t\tif (value == null || value.isEmpty()) {\n\t\t\treturn value;\n\t\t}\n\t\tif (sensitive == null) {\n\t\t\tif (type != null) {\n\t\t\t\treturn DataMask.mask(value, type);\n\t\t\t} else {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t} else {\n\t\t\treturn sensitive.type().getStrategy().mask(value, sensitive.keepChars());\n\t\t}\n\t}\n\n\tprivate SensitiveType getSensitiveType(Field field, Sensitive sensitive) {\n\t\tSensitiveType type = null;\n\t\tif (sensitive == null) {\n\t\t\tString fieldName = field.getName();\n\t\t\t//没有@Sensitive，但是有mapping命中\n\t\t\ttype = MaskMapping.getMaskTypeMapping(fieldName);\n\t\t} else {\n\t\t\ttype = sensitive.type();\n\t\t}\n\t\treturn type;\n\t}\n\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/MaskMapping.java",
    "content": "package com.vip.vjtools.vjkit.datamasking;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\n/**\n * 加载系统定义的和用户定义的mapping配置\n */\npublic class MaskMapping {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(MaskMapping.class);\n\n\tprivate static final String SYS_MASK_MAPPING = \"sys_data_mask.properties\";//系统定义的\n\tprivate static final String USER_MASK_MAPPING = \"data_mask.properties\";//用户自定义的\n\n\tprivate static final Map<String, SensitiveType> mappings = new HashMap<>();\n\n\tstatic {\n\t\tinit();\n\t}\n\n\t/**\n\t * 初始化\n\t * 加载系统定义的和用户自定义的掩码字段映射\n\t */\n\tprivate static void init() {\n\t\tloadPropertyFromFile(SYS_MASK_MAPPING);\n\t\tloadPropertyFromFile(USER_MASK_MAPPING);\n\t}\n\n\t/**\n\t * 加载掩码映射文件\n\t */\n\tprivate static void loadPropertyFromFile(String resourcePath) {\n\n\n\t\tClassLoader loader = Thread.currentThread().getContextClassLoader();\n\t\tProperties props = new Properties();\n\t\tboolean isUserMapping = resourcePath.equals(USER_MASK_MAPPING);\n\n\t\ttry {\n\t\t\t//加载所有的用户自定义mapping配置\n\t\t\tEnumeration<URL> paths = loader.getResources(resourcePath);\n\n\t\t\t//没有配置文件\n\t\t\tif (paths == null || !paths.hasMoreElements()) {\n\t\t\t\tlogger.info(\"data-masking property file don't exist:{}\", resourcePath);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\twhile (paths.hasMoreElements()) {\n\t\t\t\tURL path = paths.nextElement();\n\t\t\t\t//打印路径\n\t\t\t\tlogger.info(\"load data-masking property file:{},path is {}\", resourcePath, path);\n\n\n\t\t\t\ttry (InputStream resourceStream = path.openStream()) {\n\n\t\t\t\t\tif (resourceStream == null) {\n\t\t\t\t\t\tlogger.info(\"data-masking property file's stream is null:{},{}\", path, resourcePath);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tprops.load(resourceStream);\n\n\n\t\t\t\t\tfor (Map.Entry<Object, Object> prop : props.entrySet()) {\n\t\t\t\t\t\tString type = prop.getKey().toString().trim();\n\t\t\t\t\t\tString values = prop.getValue().toString().trim();\n\t\t\t\t\t\tString[] maps = values.split(\",\");\n\t\t\t\t\t\tSensitiveType sensitiveType = SensitiveType.valueOf(type);\n\n\t\t\t\t\t\tfor (String map : maps) {\n\t\t\t\t\t\t\tmappings.put(map.toLowerCase(), sensitiveType);\n\n\t\t\t\t\t\t\tif (isUserMapping) {\n\t\t\t\t\t\t\t\tlogger.info(\"load user mask mapping {}={},from path:{}\", sensitiveType, map, path);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\tlogger.warn(\"load data-masking property file error!,{}\", path, e);\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t} catch (IOException e) {\n\t\t\tlogger.warn(\"load data-masking property file error!\", e);\n\t\t}\n\n\t}\n\n\n\tpublic static SensitiveType getMaskTypeMapping(String fieldName) {\n\t\treturn mappings.get(fieldName.toLowerCase());\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/MaskStrategy.java",
    "content": "package com.vip.vjtools.vjkit.datamasking;\n\n/**\n * 脱敏策略接口\n */\npublic interface MaskStrategy {\n\n\tString mask(String source, int[] params);\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/Sensitive.java",
    "content": "package com.vip.vjtools.vjkit.datamasking;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Inherited;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 掩码类型标记\n * 在Java类字段中标记，对象在被DataMask序列化的时候，就可以自动被掩码\n */\n@Target(ElementType.FIELD)\n@Inherited\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Sensitive {\n\n\tSensitiveType type() default SensitiveType.Default;\n\n\t/**\n\t * 如果首尾保留数量一样的，可以只用一个数字 {2} 表示首尾各保留2个字符 {1,2} 表示头部保留1个字符串，尾部保留2个字符串\n\t */\n\tint[] keepChars() default {1, 0};\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/SensitiveType.java",
    "content": "package com.vip.vjtools.vjkit.datamasking;\n\nimport com.vip.vjtools.vjkit.datamasking.strategy.EmailMask;\nimport com.vip.vjtools.vjkit.datamasking.strategy.HashMask;\nimport com.vip.vjtools.vjkit.datamasking.strategy.NameMask;\nimport com.vip.vjtools.vjkit.datamasking.strategy.PartMask;\n\n/**\n * 脱敏类型\n */\npublic enum SensitiveType {\n\n\tName(new NameMask()),//中文名\n\tPhone(new PartMask(), 3),//电话\n\tIDCard(new PartMask(), 5, 2),//身份证号\n\tBankCard(new PartMask(), 4, 2),//银行卡号\n\tAddress(new PartMask(), 9, 0),//地址\n\tEmail(new EmailMask()),//电子邮件\n\tCaptcha(new PartMask(), 1),//验证码\n\tPassport(new PartMask(), 2),//护照/军官证\n\tAccount(new PartMask(), 1),//账号\n\tPassword(new PartMask(), 0),//密码\n\t/**\n\t * 散列，这种掩码方式，用户可以手工计算Hash值来精确查询日志。\n\t */\n\tHash(new HashMask()),\n\n\tDefault(new PartMask(), 1, 0); //缺省,只显示第一个字符串\n\n\tprivate MaskStrategy strategy;\n\tprivate int[] params;\n\n\tSensitiveType(MaskStrategy strategy, int... params) {\n\t\tthis.strategy = strategy;\n\t\tthis.params = params;\n\t}\n\n\tpublic MaskStrategy getStrategy() {\n\t\treturn strategy;\n\t}\n\n\n\tpublic int[] getParams() {\n\t\treturn params;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/strategy/EmailMask.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.strategy;\n\nimport com.vip.vjtools.vjkit.datamasking.MaskStrategy;\n\n/**\n * 电子邮件掩码\n * 规则：\n * '@' 前面的字符串，首尾保留1位\n */\npublic class EmailMask extends PartMask implements MaskStrategy {\n\n\t@Override\n\tpublic String mask(String source, int[] params) {\n\t\tif (source == null || source.length() == 0) {\n\t\t\treturn source;\n\t\t}\n\n\t\tint index = source.indexOf(\"@\");\n\t\t//不是电子邮件格式，直接回退到默认的模式\n\t\tif (index <= 0) {\n\t\t\treturn super.mask(source, new int[]{1, 0});\n\t\t}\n\n\t\treturn super.mask(source.substring(0, index), new int[]{1}).concat(source.substring(index));\n\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/strategy/HashMask.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.strategy;\n\nimport com.vip.vjtools.vjkit.datamasking.MaskStrategy;\nimport com.vip.vjtools.vjkit.text.EncodeUtil;\nimport com.vip.vjtools.vjkit.text.HashUtil;\n\n/**\n * Hash掩码，主要用于一些敏感信息掩码后查询。\n * 算法：sha1(source+salt)\n */\npublic class HashMask implements MaskStrategy {\n\n\tprivate static String salt=\"default_salt\";\n\n\n\t@Override\n\tpublic String mask(String source, int[] params) {\n\t\tif (source == null || source.isEmpty()) {\n\t\t\treturn source;\n\t\t}\n\t\treturn EncodeUtil.encodeHex(HashUtil.sha1(source+getSalt()));\n\t}\n\n\t/**\n\t * 设置salt\n\t */\n\tpublic static void setSalt(String salt){\n\t\tHashMask.salt = salt;\n\t}\n\n\t/**\n\t * 默认用 default_salt\n\t * 可以通过DataMask来设置\n\t */\n\tpublic static String getSalt() {\n\t\treturn salt;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/strategy/NameMask.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.strategy;\n\nimport com.vip.vjtools.vjkit.datamasking.MaskStrategy;\n\n/**\n * 姓名掩码\n * 规则：\n * 长度小于3，显示最后一位\n * 长度大于3，显示最后两位\n */\npublic class NameMask extends PartMask implements MaskStrategy {\n\n\t@Override\n\tpublic String mask(String source, int[] params) {\n\t\tif (source == null || source.length() == 0) {\n\t\t\treturn source;\n\t\t}\n\t\tif (source.length() <= 3) {\n\t\t\treturn super.mask(source, new int[]{0, 1});\n\t\t} else {\n\t\t\treturn super.mask(source, new int[]{0, 2});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/datamasking/strategy/PartMask.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.strategy;\n\nimport com.vip.vjtools.vjkit.datamasking.MaskStrategy;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 部分掩码\n * 可以支持前后保留n个字符串\n */\npublic class PartMask implements MaskStrategy {\n\n\tprivate static Map<Integer, String> maskCodes = new HashMap<>();\n\n\tstatic {\n\t\t//预生成指定长度的掩码星号\n\t\tfor (int i = 1; i <= 10; i++) {\n\t\t\tmaskCodes.put(i, getMaskCodeByNum(i));\n\t\t}\n\t}\n\n\t/**\n\t * 生成指定长度的*\n\t */\n\tprivate static String getMaskCodeByNum(int num) {\n\t\tString maskCode = maskCodes.get(num);\n\t\tif (maskCode != null) {\n\t\t\treturn maskCode;\n\t\t}\n\t\tStringBuilder mask = new StringBuilder();\n\t\tfor (int j = 0; j < num; j++) {\n\t\t\tmask.append('*');\n\t\t}\n\t\treturn mask.toString();\n\t}\n\n\t@Override\n\tpublic String mask(String source, int[] params) {\n\t\t//空数据直接返回\n\t\tif (source == null || source.isEmpty()) {\n\t\t\treturn source;\n\t\t}\n\n\t\tif (source.length() == 1) {\n\t\t\treturn \"*\";\n\t\t}\n\n\t\tint leftKeep = Math.max(0, params[0]);\n\t\tint rightKeep = leftKeep;\n\t\t//左右保留不一样，可以配置两个参数\n\t\tif (params.length > 1) {\n\t\t\trightKeep = Math.max(0, params[1]);\n\t\t}\n\n\t\t//各种不符合条件的(leftKeep,rightKeep过界），都回退到只保留第一位的情况\n\t\tboolean useDefault =\n\t\t\t\tleftKeep >= source.length() || rightKeep >= source.length() || (leftKeep + rightKeep) >= source\n\t\t\t\t\t\t.length();\n\n\t\tif (useDefault) {\n\t\t\treturn source.substring(0, 1).concat(getMaskCodeByNum(source.length() - 1));\n\t\t}\n\n\t\treturn source.substring(0, leftKeep).concat(getMaskCodeByNum(source.length() - leftKeep - rightKeep))\n\t\t\t\t.concat(source.substring(source.length() - rightKeep, source.length()));\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/id/IdUtil.java",
    "content": "package com.vip.vjtools.vjkit.id;\n\nimport java.util.UUID;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class IdUtil {\n\t/*\n\t * 返回使用ThreadLocalRandom的UUID，比默认的UUID性能更优\n\t */\n\tpublic static UUID fastUUID() {\n\t\tThreadLocalRandom random = ThreadLocalRandom.current();\n\t\treturn new UUID(random.nextLong(), random.nextLong());\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/io/FilePathUtil.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.google.common.io.Files;\nimport com.vip.vjtools.vjkit.base.Platforms;\nimport com.vip.vjtools.vjkit.text.MoreStringUtil;\n\n/**\n * 关于文件路径的工具集. 这个类只适合处理纯字符串的路径，如果是File对象或者Path对象的路径处理，建议直接使用Path类的方法。\n * \n * @see java.nio.file.Path\n */\npublic class FilePathUtil {\n\n\t/**\n\t * 在Windows环境里，兼容Windows上的路径分割符，将 '/' 转回 '\\'\n\t */\n\tpublic static String normalizePath(String path) {\n\t\tif (Platforms.FILE_PATH_SEPARATOR_CHAR == Platforms.WINDOWS_FILE_PATH_SEPARATOR_CHAR\n\t\t\t\t&& StringUtils.indexOf(path, Platforms.LINUX_FILE_PATH_SEPARATOR_CHAR) != -1) {\n\t\t\treturn StringUtils.replaceChars(path, Platforms.LINUX_FILE_PATH_SEPARATOR_CHAR,\n\t\t\t\t\tPlatforms.WINDOWS_FILE_PATH_SEPARATOR_CHAR);\n\t\t}\n\t\treturn path;\n\n\t}\n\n\t/**\n\t * 将路径整理，如 \"a/../b\"，整理成 \"b\"\n\t */\n\tpublic static String simplifyPath(String path) {\n\t\treturn Files.simplifyPath(path);\n\t}\n\n\t/**\n\t * 以拼接路径名\n\t */\n\tpublic static String concat(String baseName, String... appendName) {\n\t\tif (appendName.length == 0) {\n\t\t\treturn baseName;\n\t\t}\n\n\t\tStringBuilder concatName = new StringBuilder();\n\t\tif (MoreStringUtil.endWith(baseName, Platforms.FILE_PATH_SEPARATOR_CHAR)) {\n\t\t\tconcatName.append(baseName).append(appendName[0]);\n\t\t} else {\n\t\t\tconcatName.append(baseName).append(Platforms.FILE_PATH_SEPARATOR_CHAR).append(appendName[0]);\n\t\t}\n\n\t\tif (appendName.length > 1) {\n\t\t\tfor (int i = 1; i < appendName.length; i++) {\n\t\t\t\tconcatName.append(Platforms.FILE_PATH_SEPARATOR_CHAR).append(appendName[i]);\n\t\t\t}\n\t\t}\n\n\t\treturn concatName.toString();\n\t}\n\n\t/**\n\t * 获得上层目录的路径\n\t */\n\tpublic static String getParentPath(String path) {\n\t\tString parentPath = path;\n\n\t\tif (Platforms.FILE_PATH_SEPARATOR.equals(parentPath)) {\n\t\t\treturn parentPath;\n\t\t}\n\n\t\tparentPath = MoreStringUtil.removeEnd(parentPath, Platforms.FILE_PATH_SEPARATOR_CHAR);\n\n\t\tint idx = parentPath.lastIndexOf(Platforms.FILE_PATH_SEPARATOR_CHAR);\n\t\tif (idx >= 0) {\n\t\t\tparentPath = parentPath.substring(0, idx + 1);\n\t\t} else {\n\t\t\tparentPath = Platforms.FILE_PATH_SEPARATOR;\n\t\t}\n\n\t\treturn parentPath;\n\t}\n\n\t/**\n\t * 获得参数clazz所在的Jar文件的绝对路径\n\t */\n\tpublic static String getJarPath(Class<?> clazz) {\n\t\treturn clazz.getProtectionDomain().getCodeSource().getLocation().getFile();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/io/FileTreeWalker.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport java.io.File;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\nimport com.google.common.base.Predicate;\nimport com.google.common.collect.TreeTraverser;\nimport com.google.common.io.Files;\nimport com.vip.vjtools.vjkit.text.WildcardMatcher;\n\npublic class FileTreeWalker {\n\n\t/**\n\t * 前序递归列出所有文件, 包含文件与目录，及根目录本身.\n\t * \n\t * 前序即先列出父目录，在列出子目录. 如要后序遍历, 直接使用Files.fileTreeTraverser()\n\t */\n\tpublic static List<File> listAll(File rootDir) {\n\t\treturn Files.fileTreeTraverser().preOrderTraversal(rootDir).toList();\n\t}\n\n\t/**\n\t * 前序递归列出所有文件, 只包含文件.\n\t */\n\tpublic static List<File> listFile(File rootDir) {\n\t\treturn Files.fileTreeTraverser().preOrderTraversal(rootDir).filter(Files.isFile()).toList();\n\t}\n\n\t/**\n\t * 前序递归列出所有文件, 列出后缀名匹配的文件. （后缀名不包含.）\n\t */\n\tpublic static List<File> listFileWithExtension(final File rootDir, final String extension) {\n\t\treturn Files.fileTreeTraverser().preOrderTraversal(rootDir).filter(new FileExtensionFilter(extension)).toList();\n\t}\n\n\t/**\n\t * 前序递归列出所有文件, 列出文件名匹配通配符的文件\n\t * \n\t * 如 (\"/a/b/hello.txt\", \"he*\") 将被返回\n\t */\n\tpublic static List<File> listFileWithWildcardFileName(final File rootDir, final String fileNamePattern) {\n\t\treturn Files.fileTreeTraverser().preOrderTraversal(rootDir).filter(new WildcardFileNameFilter(fileNamePattern))\n\t\t\t\t.toList();\n\t}\n\n\t/**\n\t * 前序递归列出所有文件, 列出文件名匹配正则表达式的文件\n\t * \n\t * 如 (\"/a/b/hello.txt\", \"he.*\\.txt\") 将被返回\n\t */\n\tpublic static List<File> listFileWithRegexFileName(final File rootDir, final String regexFileNamePattern) {\n\t\treturn Files.fileTreeTraverser().preOrderTraversal(rootDir)\n\t\t\t\t.filter(new RegexFileNameFilter(regexFileNamePattern)).toList();\n\t}\n\n\t/**\n\t * 前序递归列出所有文件, 列出符合ant path风格表达式的文件\n\t * \n\t * 如 (\"/a/b/hello.txt\", \"he.*\\.txt\") 将被返回\n\t */\n\tpublic static List<File> listFileWithAntPath(final File rootDir, final String antPathPattern) {\n\t\treturn Files.fileTreeTraverser().preOrderTraversal(rootDir)\n\t\t\t\t.filter(new AntPathFilter(FilePathUtil.concat(rootDir.getAbsolutePath(), antPathPattern))).toList();\n\t}\n\n\t/**\n\t * 直接使用Guava的TreeTraverser，获得更大的灵活度, 比如加入各类filter，前序/后序的选择，一边遍历一边操作\n\t * \n\t * <pre>\n\t * FileUtil.fileTreeTraverser().preOrderTraversal(root).iterator();\n\t * </pre>\n\t */\n\tpublic static TreeTraverser<File> fileTreeTraverser() {\n\t\treturn Files.fileTreeTraverser();\n\t}\n\n\t/**\n\t * 以文件名正则表达式为filter，配合fileTreeTraverser使用\n\t */\n\tpublic static final class RegexFileNameFilter implements Predicate<File> {\n\t\tprivate final Pattern pattern;\n\n\t\tprivate RegexFileNameFilter(String pattern) {\n\t\t\tthis.pattern = Pattern.compile(pattern);\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean apply(File input) {\n\t\t\treturn input.isFile() && pattern.matcher(input.getName()).matches();\n\t\t}\n\t}\n\n\t/**\n\t * 以文件名通配符为filter，配合fileTreeTraverser使用.\n\t * \n\t * @param pattern 支持*与?的通配符，如hello*.txt 匹配 helloworld.txt\n\t */\n\tpublic static final class WildcardFileNameFilter implements Predicate<File> {\n\t\tprivate final String pattern;\n\n\t\tprivate WildcardFileNameFilter(String pattern) {\n\t\t\tthis.pattern = pattern;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean apply(File input) {\n\t\t\treturn input.isFile() && WildcardMatcher.match(input.getName(), pattern);\n\t\t}\n\t}\n\n\t/**\n\t * 以文件名后缀做filter，配合fileTreeTraverser使用\n\t */\n\tpublic static final class FileExtensionFilter implements Predicate<File> {\n\t\tprivate final String extension;\n\n\t\tprivate FileExtensionFilter(String extension) {\n\t\t\tthis.extension = extension;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean apply(File input) {\n\t\t\treturn input.isFile() && extension.equals(FileUtil.getFileExtension(input));\n\t\t}\n\t}\n\n\t/**\n\t * 以ant风格的path为filter，配合fileTreeTraverser使用.\n\t * \n\t * @param pattern 支持ant风格的通配符，如/var/?/a?.txt 匹配 /var/b/ab.txt, 其他通配符包括**,*\n\t */\n\tpublic static final class AntPathFilter implements Predicate<File> {\n\t\tprivate final String pattern;\n\n\t\tprivate AntPathFilter(String pattern) {\n\t\t\tthis.pattern = pattern;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean apply(File input) {\n\t\t\treturn input.isFile() && WildcardMatcher.matchPath(input.getAbsolutePath(), pattern);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/io/FileUtil.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.file.DirectoryStream;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.FileVisitor;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.StandardOpenOption;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.List;\n\nimport org.apache.commons.lang3.Validate;\n\nimport com.vip.vjtools.vjkit.base.Platforms;\nimport com.vip.vjtools.vjkit.base.annotation.NotNull;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\nimport com.vip.vjtools.vjkit.text.Charsets;\n\n/**\n * 关于文件的工具集.\n * \n * 主要是调用JDK自带的Files工具类，少量代码调用Guava Files。 固定encoding为UTF8.\n * \n * 1.文件读写\n * \n * 2.文件及目录操作\n */\npublic class FileUtil {\n\n\t// fileVisitor for file deletion on Files.walkFileTree\n\tprivate static FileVisitor<Path> deleteFileVisitor = new SimpleFileVisitor<Path>() {\n\n\t\t@Override\n\t\tpublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n\t\t\tFiles.delete(file);\n\t\t\treturn FileVisitResult.CONTINUE;\n\t\t}\n\n\t\t@Override\n\t\tpublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {\n\t\t\tFiles.delete(dir);\n\t\t\treturn FileVisitResult.CONTINUE;\n\t\t}\n\t};\n\n\t//////// 文件读写//////\n\n\t/**\n\t * 读取文件到byte[].\n\t * \n\t * @see Files#readAllBytes\n\t */\n\tpublic static byte[] toByteArray(final File file) throws IOException {\n\t\treturn Files.readAllBytes(file.toPath());\n\t}\n\n\t/**\n\t * 读取文件到String.\n\t */\n\tpublic static String toString(final File file) throws IOException {\n\t\treturn com.google.common.io.Files.toString(file, Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 读取文件的每行内容到List<String>.\n\t * \n\t * @see Files#readAllLines\n\t */\n\tpublic static List<String> toLines(final File file) throws IOException {\n\t\treturn Files.readAllLines(file.toPath(), Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 简单写入String到File.\n\t */\n\tpublic static void write(final CharSequence data, final File file) throws IOException {\n\t\tValidate.notNull(file);\n\t\tValidate.notNull(data);\n\n\t\ttry (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), Charsets.UTF_8)) {\n\t\t\twriter.append(data);\n\t\t}\n\t}\n\n\t/**\n\t * 追加String到File.\n\t */\n\tpublic static void append(final CharSequence data, final File file) throws IOException {\n\t\tValidate.notNull(file);\n\t\tValidate.notNull(data);\n\n\t\ttry (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), Charsets.UTF_8,\n\t\t\t\tStandardOpenOption.APPEND)) {\n\t\t\twriter.append(data);\n\t\t}\n\t}\n\n\t/**\n\t * 打开文件为InputStream.\n\t * \n\t * @see Files#newInputStream\n\t */\n\tpublic static InputStream asInputStream(String fileName) throws IOException {\n\t\treturn asInputStream(getPath(fileName));\n\t}\n\n\t/**\n\t * 打开文件为InputStream.\n\t * \n\t * @see Files#newInputStream\n\t */\n\tpublic static InputStream asInputStream(File file) throws IOException {\n\t\tValidate.notNull(file, \"file is null\");\n\t\treturn asInputStream(file.toPath());\n\t}\n\n\t/**\n\t * 打开文件为InputStream.\n\t * \n\t * @see Files#newInputStream\n\t */\n\tpublic static InputStream asInputStream(Path path) throws IOException {\n\t\tValidate.notNull(path, \"path is null\");\n\t\treturn Files.newInputStream(path);\n\t}\n\n\t/**\n\t * 打开文件为OutputStream.\n\t * \n\t * @see Files#newOutputStream\n\t */\n\tpublic static OutputStream asOutputStream(String fileName) throws IOException {\n\t\treturn asOutputStream(getPath(fileName));\n\t}\n\n\t/**\n\t * 打开文件为OutputStream.\n\t * \n\t * @see Files#newOutputStream\n\t */\n\tpublic static OutputStream asOutputStream(File file) throws IOException {\n\t\tValidate.notNull(file, \"file is null\");\n\t\treturn asOutputStream(file.toPath());\n\t}\n\n\t/**\n\t * 打开文件为OutputStream.\n\t * \n\t * @see Files#newOutputStream\n\t */\n\tpublic static OutputStream asOutputStream(Path path) throws IOException {\n\t\tValidate.notNull(path, \"path is null\");\n\t\treturn Files.newOutputStream(path);\n\t}\n\n\t/**\n\t * 获取File的BufferedReader.\n\t * \n\t * @see Files#newBufferedReader\n\t */\n\tpublic static BufferedReader asBufferedReader(String fileName) throws IOException {\n\t\tValidate.notBlank(fileName, \"filename is blank\");\n\t\treturn asBufferedReader(getPath(fileName));\n\t}\n\n\tpublic static BufferedReader asBufferedReader(Path path) throws IOException {\n\t\tValidate.notNull(path, \"path is null\");\n\t\treturn Files.newBufferedReader(path, Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 获取File的BufferedWriter.\n\t * \n\t * @see Files#newBufferedWriter\n\t */\n\tpublic static BufferedWriter asBufferedWriter(String fileName) throws IOException {\n\t\tValidate.notBlank(fileName, \"filename is blank\");\n\t\treturn Files.newBufferedWriter(getPath(fileName), Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 获取File的BufferedWriter.\n\t * \n\t * @see Files#newBufferedWriter\n\t */\n\tpublic static BufferedWriter asBufferedWriter(Path path) throws IOException {\n\t\tValidate.notNull(path, \"path is null\");\n\t\treturn Files.newBufferedWriter(path, Charsets.UTF_8);\n\t}\n\n\t///// 文件操作 /////\n\n\t/**\n\t * 复制文件或目录, not following links.\n\t * \n\t * @param from 如果为null，或者是不存在的文件或目录，抛出异常.\n\t * @param to 如果为null，或者from是目录而to是已存在文件，或相反\n\t */\n\tpublic static void copy(@NotNull File from, @NotNull File to) throws IOException {\n\t\tValidate.notNull(from);\n\t\tValidate.notNull(to);\n\n\t\tcopy(from.toPath(), to.toPath());\n\t}\n\n\t/**\n\t * 复制文件或目录, not following links.\n\t * \n\t * @param from 如果为null，或者是不存在的文件或目录，抛出异常.\n\t * @param to 如果为null，或者from是目录而to是已存在文件，或相反\n\t */\n\tpublic static void copy(@NotNull Path from, @NotNull Path to) throws IOException {\n\t\tValidate.notNull(from);\n\t\tValidate.notNull(to);\n\n\t\tif (Files.isDirectory(from)) {\n\t\t\tcopyDir(from, to);\n\t\t} else {\n\t\t\tcopyFile(from, to);\n\t\t}\n\t}\n\n\t/**\n\t * 文件复制. {@link Files#copy}\n\t * \n\t * @param from 如果为null，或文件不存在或者是目录，，抛出异常\n\t * @param to 如果to为null，或文件存在但是一个目录，抛出异常\n\t */\n\tpublic static void copyFile(@NotNull File from, @NotNull File to) throws IOException {\n\t\tValidate.notNull(from);\n\t\tValidate.notNull(to);\n\t\tcopyFile(from.toPath(), to.toPath());\n\t}\n\n\t/**\n\t * 文件复制. {@link Files#copy}\n\t * \n\t * @param from 如果为null，或文件不存在或者是目录，，抛出异常\n\t * @param to 如果to为null，或文件存在但是一个目录，抛出异常\n\t */\n\tpublic static void copyFile(@NotNull Path from, @NotNull Path to) throws IOException {\n\t\tValidate.isTrue(Files.exists(from), \"%s is not exist or not a file\", from);\n\t\tValidate.notNull(to);\n\t\tValidate.isTrue(!FileUtil.isDirExists(to), \"%s is exist but it is a dir\", to);\n\t\tFiles.copy(from, to);\n\t}\n\n\t/**\n\t * 复制目录\n\t */\n\tpublic static void copyDir(@NotNull File from, @NotNull File to) throws IOException {\n\t\tValidate.isTrue(isDirExists(from), \"%s is not exist or not a dir\", from);\n\t\tValidate.notNull(to);\n\n\t\tcopyDir(from.toPath(), to.toPath());\n\t}\n\n\t/**\n\t * 复制目录\n\t */\n\tpublic static void copyDir(@NotNull Path from, @NotNull Path to) throws IOException {\n\t\tValidate.isTrue(isDirExists(from), \"%s is not exist or not a dir\", from);\n\t\tValidate.notNull(to);\n\t\tmakesureDirExists(to);\n\n\t\ttry (DirectoryStream<Path> dirStream = Files.newDirectoryStream(from)) {\n\t\t\tfor (Path path : dirStream) {\n\t\t\t\tcopy(path, to.resolve(path.getFileName()));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * 文件移动/重命名.\n\t * \n\t * @see Files#move\n\t */\n\tpublic static void moveFile(@NotNull File from, @NotNull File to) throws IOException {\n\t\tValidate.notNull(from);\n\t\tValidate.notNull(to);\n\n\t\tmoveFile(from.toPath(), to.toPath());\n\t}\n\n\t/**\n\t * 文件移动/重命名.\n\t * \n\t * @see Files#move\n\t */\n\tpublic static void moveFile(@NotNull Path from, @NotNull Path to) throws IOException {\n\t\tValidate.isTrue(isFileExists(from), \"%s is not exist or not a file\", from);\n\t\tValidate.notNull(to);\n\t\tValidate.isTrue(!isDirExists(to), \"%s is  exist but it is a dir\", to);\n\n\t\tFiles.move(from, to);\n\t}\n\n\t/**\n\t * 目录移动/重命名\n\t */\n\tpublic static void moveDir(@NotNull File from, @NotNull File to) throws IOException {\n\t\tValidate.isTrue(isDirExists(from), \"%s is not exist or not a dir\", from);\n\t\tValidate.notNull(to);\n\t\tValidate.isTrue(!isFileExists(to), \"%s is exist but it is a file\", to);\n\n\t\tfinal boolean rename = from.renameTo(to);\n\t\tif (!rename) {\n\t\t\tif (to.getCanonicalPath().startsWith(from.getCanonicalPath() + File.separator)) {\n\t\t\t\tthrow new IOException(\"Cannot move directory: \" + from + \" to a subdirectory of itself: \" + to);\n\t\t\t}\n\t\t\tcopyDir(from, to);\n\t\t\tdeleteDir(from);\n\t\t\tif (from.exists()) {\n\t\t\t\tthrow new IOException(\"Failed to delete original directory '\" + from + \"' after copy to '\" + to + '\\'');\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * 创建文件或更新时间戳.\n\t * \n\t * @see com.google.common.io.Files#touch\n\t */\n\tpublic static void touch(String filePath) throws IOException {\n\t\ttouch(new File(filePath));\n\t}\n\n\t/**\n\t * 创建文件或更新时间戳.\n\t * \n\t * @see com.google.common.io.Files#touch\n\t */\n\tpublic static void touch(File file) throws IOException {\n\t\tcom.google.common.io.Files.touch(file);\n\t}\n\n\t/**\n\t * 删除文件.\n\t * \n\t * 如果文件不存在或者是目录，则不做修改\n\t */\n\tpublic static void deleteFile(@Nullable File file) throws IOException {\n\t\tValidate.isTrue(isFileExists(file), \"%s is not exist or not a file\", file);\n\t\tdeleteFile(file.toPath());\n\t}\n\n\t/**\n\t * 删除文件.\n\t * \n\t * 如果文件不存在或者是目录，则不做修改\n\t */\n\tpublic static void deleteFile(@Nullable Path path) throws IOException {\n\t\tValidate.isTrue(isFileExists(path), \"%s is not exist or not a file\", path);\n\n\t\tFiles.delete(path);\n\t}\n\n\t/**\n\t * 删除目录及所有子目录/文件\n\t * \n\t * @see Files#walkFileTree\n\t */\n\tpublic static void deleteDir(Path dir) throws IOException {\n\t\tValidate.isTrue(isDirExists(dir), \"%s is not exist or not a dir\", dir);\n\n\t\t// 后序遍历，先删掉子目录中的文件/目录\n\t\tFiles.walkFileTree(dir, deleteFileVisitor);\n\t}\n\n\t/**\n\t * 删除目录及所有子目录/文件\n\t */\n\tpublic static void deleteDir(File dir) throws IOException {\n\t\tValidate.isTrue(isDirExists(dir), \"%s is not exist or not a dir\", dir);\n\t\tdeleteDir(dir.toPath());\n\t}\n\n\t/**\n\t * 判断目录是否存在, from Jodd\n\t */\n\tpublic static boolean isDirExists(String dirPath) {\n\t\tif (dirPath == null) {\n\t\t\treturn false;\n\t\t}\n\t\treturn isDirExists(getPath(dirPath));\n\t}\n\n\tpublic static boolean isDirExists(Path dirPath) {\n\t\treturn dirPath != null && Files.exists(dirPath) && Files.isDirectory(dirPath);\n\t}\n\n\t/**\n\t * 判断目录是否存在, from Jodd\n\t */\n\tpublic static boolean isDirExists(File dir) {\n\t\tif (dir == null) {\n\t\t\treturn false;\n\t\t}\n\t\treturn isDirExists(dir.toPath());\n\t}\n\n\t/**\n\t * 确保目录存在, 如不存在则创建\n\t */\n\tpublic static void makesureDirExists(String dirPath) throws IOException {\n\t\tmakesureDirExists(getPath(dirPath));\n\t}\n\n\t/**\n\t * 确保目录存在, 如不存在则创建\n\t */\n\tpublic static void makesureDirExists(File file) throws IOException {\n\t\tValidate.notNull(file);\n\t\tmakesureDirExists(file.toPath());\n\t}\n\n\t/**\n\t * 确保目录存在, 如不存在则创建.\n\t * \n\t * @see Files#createDirectories\n\t * \n\t */\n\tpublic static void makesureDirExists(Path dirPath) throws IOException {\n\t\tValidate.notNull(dirPath);\n\t\tFiles.createDirectories(dirPath);\n\t}\n\n\t/**\n\t * 确保父目录及其父目录直到根目录都已经创建.\n\t *\n\t */\n\tpublic static void makesureParentDirExists(File file) throws IOException {\n\t\tValidate.notNull(file);\n\t\tmakesureDirExists(file.getParentFile());\n\t}\n\n\t/**\n\t * 判断文件是否存在, from Jodd.\n\t * \n\t * @see Files#exists\n\t * @see Files#isRegularFile\n\t */\n\tpublic static boolean isFileExists(String fileName) {\n\t\tif (fileName == null) {\n\t\t\treturn false;\n\t\t}\n\t\treturn isFileExists(getPath(fileName));\n\t}\n\n\t/**\n\t * 判断文件是否存在, from Jodd.\n\t * \n\t * @see Files#exists\n\t * @see Files#isRegularFile\n\t */\n\tpublic static boolean isFileExists(File file) {\n\t\tif (file == null) {\n\t\t\treturn false;\n\t\t}\n\t\treturn isFileExists(file.toPath());\n\t}\n\n\t/**\n\t * 判断文件是否存在, from Jodd.\n\t * \n\t * @see Files#exists\n\t * @see Files#isRegularFile\n\t */\n\tpublic static boolean isFileExists(Path path) {\n\t\tif (path == null) {\n\t\t\treturn false;\n\t\t}\n\t\treturn Files.exists(path) && Files.isRegularFile(path);\n\t}\n\n\t/**\n\t * 在临时目录创建临时目录，命名为${毫秒级时间戳}-${同一毫秒内的随机数}.\n\t *\n\t * @see Files#createTempDirectory\n\t */\n\tpublic static Path createTempDir() throws IOException {\n\t\treturn Files.createTempDirectory(System.currentTimeMillis() + \"-\");\n\t}\n\n\t/**\n\t * 在临时目录创建临时文件，命名为tmp-${random.nextLong()}.tmp\n\t * \n\t * @see Files#createTempFile\n\t */\n\tpublic static Path createTempFile() throws IOException {\n\t\treturn Files.createTempFile(\"tmp-\", \".tmp\");\n\t}\n\n\t/**\n\t * 在临时目录创建临时文件，命名为${prefix}${random.nextLong()}${suffix}\n\t * \n\t * @see Files#createTempFile\n\t */\n\tpublic static Path createTempFile(String prefix, String suffix) throws IOException {\n\t\treturn Files.createTempFile(prefix, suffix);\n\t}\n\n\tprivate static Path getPath(String filePath) {\n\t\treturn Paths.get(filePath);\n\t}\n\n\t/**\n\t * 获取文件名(不包含路径)\n\t */\n\tpublic static String getFileName(@NotNull String fullName) {\n\t\tValidate.notEmpty(fullName);\n\t\tint last = fullName.lastIndexOf(Platforms.FILE_PATH_SEPARATOR_CHAR);\n\t\treturn fullName.substring(last + 1);\n\t}\n\n\t/**\n\t * 获取文件名的扩展名部分(不包含.)\n\t * \n\t * @see com.google.common.io.Files#getFileExtension\n\t */\n\tpublic static String getFileExtension(File file) {\n\t\treturn com.google.common.io.Files.getFileExtension(file.getName());\n\t}\n\n\t/**\n\t * 获取文件名的扩展名部分(不包含.)\n\t * \n\t * @see com.google.common.io.Files#getFileExtension\n\t */\n\tpublic static String getFileExtension(String fullName) {\n\t\treturn com.google.common.io.Files.getFileExtension(fullName);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/io/IOUtil.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport java.io.BufferedReader;\nimport java.io.ByteArrayInputStream;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.Reader;\nimport java.io.Writer;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.CharStreams;\nimport com.google.common.io.Closeables;\nimport com.vip.vjtools.vjkit.text.Charsets;\n\n/**\n * IO Stream/Reader相关工具集. 固定encoding为UTF8.\n * \n * 建议使用Apache Commons IO和Guava关于IO的工具类(com.google.common.io.*), 在未引入Commons IO时可以用本类做最基本的事情.\n * \n * 1. 安静关闭Closeable对象\n * \n * 2. 读出InputStream/Reader全部内容到String 或 List<String>\n * \n * 3. 读出InputStream/Reader一行内容到String\n * \n * 4. 将String写到OutputStream/Writer\n * \n * 5. 将String 转换为InputStream/Reader\n * \n * 5. InputStream/Reader与OutputStream/Writer之间复制的copy\n * \n */\npublic class IOUtil {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(IOUtil.class);\n\n\t/**\n\t * 在final中安静的关闭, 不再往外抛出异常避免影响原有异常，最常用函数. 同时兼容Closeable为空未实际创建的情况.\n\t * \n\t * @see Closeables#close\n\t */\n\tpublic static void closeQuietly(Closeable closeable) {\n\t\tif (closeable == null) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tcloseable.close();\n\t\t} catch (IOException e) {\n\t\t\tlogger.warn(\"IOException thrown while closing Closeable.\", e);\n\t\t}\n\t}\n\n\t/**\n\t * 简单读取InputStream到String.\n\t */\n\tpublic static String toString(InputStream input) throws IOException {\n\t\tInputStreamReader reader = new InputStreamReader(input, Charsets.UTF_8);\n\t\treturn toString(reader);\n\t}\n\n\t/**\n\t * 简单读取Reader到String\n\t * \n\t * @see CharStreams#toString\n\t */\n\tpublic static String toString(Reader input) throws IOException {\n\t\treturn CharStreams.toString(input);\n\t}\n\n\t/**\n\t * 简单读取Reader的每行内容到List<String>\n\t */\n\tpublic static List<String> toLines(final InputStream input) throws IOException {\n\t\treturn CharStreams.readLines(new BufferedReader(new InputStreamReader(input, Charsets.UTF_8)));\n\t}\n\n\t/**\n\t * 简单读取Reader的每行内容到List<String>\n\t * \n\t * @see CharStreams#readLines\n\t */\n\tpublic static List<String> toLines(final Reader input) throws IOException {\n\t\treturn CharStreams.readLines(toBufferedReader(input));\n\t}\n\n\t/**\n\t * 读取一行数据，比如System.in的用户输入\n\t */\n\tpublic static String readLine(final InputStream input) throws IOException {\n\t\treturn new BufferedReader(new InputStreamReader(input, Charsets.UTF_8)).readLine();\n\t}\n\n\t/**\n\t * 读取一行数据\n\t */\n\tpublic static String readLine(final Reader reader) throws IOException {\n\t\treturn toBufferedReader(reader).readLine();\n\t}\n\n\t/**\n\t * 简单写入String到OutputStream.\n\t */\n\tpublic static void write(final String data, final OutputStream output) throws IOException {\n\t\tif (data != null) {\n\t\t\toutput.write(data.getBytes(Charsets.UTF_8));\n\t\t}\n\t}\n\n\t/**\n\t * 简单写入String到Writer.\n\t */\n\tpublic static void write(final String data, final Writer output) throws IOException {\n\t\tif (data != null) {\n\t\t\toutput.write(data);\n\t\t}\n\t}\n\n\t/**\n\t * 字符串转换成InputStream\n\t */\n\tpublic static InputStream toInputStream(String input) {\n\t\treturn new ByteArrayInputStream(input.getBytes(Charsets.UTF_8));\n\t}\n\n\t/**\n\t * 字符串转换成Reader\n\t */\n\tpublic static Reader toInputStreamReader(String input) {\n\t\treturn new InputStreamReader(toInputStream(input), Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 在Reader与Writer间复制内容\n\t * \n\t * @see CharStreams#copy\n\t */\n\tpublic static long copy(final Reader input, final Writer output) throws IOException {\n\t\treturn CharStreams.copy(input, output);\n\t}\n\n\t/**\n\t * 在InputStream与OutputStream间复制内容\n\t * \n\t * @see ByteStreams#copy\n\t */\n\tpublic static long copy(final InputStream input, final OutputStream output) throws IOException {\n\t\treturn ByteStreams.copy(input, output);\n\t}\n\n\tpublic static BufferedReader toBufferedReader(final Reader reader) {\n\t\treturn reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/io/ResourceUtil.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Enumeration;\nimport java.util.List;\n\nimport com.google.common.io.Resources;\nimport com.vip.vjtools.vjkit.collection.ListUtil;\nimport com.vip.vjtools.vjkit.reflect.ClassLoaderUtil;\nimport com.vip.vjtools.vjkit.text.Charsets;\n\n/**\n * 针对Jar包内的文件的工具类.\n * <p>\n * 1.ClassLoader\n * \n * 不指定contextClass时，优先使用Thread.getContextClassLoader()， 如果ContextClassLoader未设置则使用Guava Resources类的ClassLoader\n * \n * 指定contextClass时，则直接使用该contextClass的ClassLoader.\n * <p>\n * 2.路径\n * \n * 不指定contextClass时，按URLClassLoader的实现, 从jar file中查找resourceName，\n * \n * 所以resourceName无需以\"/\"打头即表示jar file中的根目录，带了\"/\" 反而导致JarFile.getEntry(resourceName)时没有返回.\n * \n * 指定contextClass时，class.getResource()会先对name进行处理再交给classLoader，打头的\"/\"的会被去除，不以\"/\"打头则表示与该contextClass package的相对路径,\n * 会先转为绝对路径.\n * <p>\n * 3.同名资源\n * \n * 如果有多个同名资源，除非调用getResources()获取全部资源，否则在URLClassLoader中按ClassPath顺序打开第一个命中的Jar文件.\n */\npublic class ResourceUtil {\n\n\t// 打开单个文件////\n\t/**\n\t * 读取规则见本类注释.\n\t */\n\tpublic static URL asUrl(String resourceName) {\n\t\treturn Resources.getResource(resourceName);\n\t}\n\n\t/**\n\t * 读取规则见本类注释.\n\t */\n\tpublic static URL asUrl(Class<?> contextClass, String resourceName) {\n\t\treturn Resources.getResource(contextClass, resourceName);\n\t}\n\n\t/**\n\t * 读取规则见本类注释.\n\t */\n\tpublic static InputStream asStream(String resourceName) throws IOException {\n\t\treturn Resources.getResource(resourceName).openStream();\n\t}\n\n\t/**\n\t * 读取文件的每一行，读取规则见本类注释.\n\t */\n\tpublic static InputStream asStream(Class<?> contextClass, String resourceName) throws IOException {\n\t\treturn Resources.getResource(contextClass, resourceName).openStream();\n\t}\n\n\t////// 读取单个文件内容／／／／／\n\n\t/**\n\t * 读取文件的每一行，读取规则见本类注释.\n\t */\n\tpublic static String toString(String resourceName) throws IOException {\n\t\treturn Resources.toString(Resources.getResource(resourceName), Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 读取文件的每一行，读取规则见本类注释.\n\t */\n\tpublic static String toString(Class<?> contextClass, String resourceName) throws IOException {\n\t\treturn Resources.toString(Resources.getResource(contextClass, resourceName), Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 读取文件的每一行，读取规则见本类注释.\n\t */\n\tpublic static List<String> toLines(String resourceName) throws IOException {\n\t\treturn Resources.readLines(Resources.getResource(resourceName), Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 读取文件的每一行，读取规则见本类注释.\n\t */\n\tpublic static List<String> toLines(Class<?> contextClass, String resourceName) throws IOException {\n\t\treturn Resources.readLines(Resources.getResource(contextClass, resourceName), Charsets.UTF_8);\n\t}\n\n\t///////////// 打开所有同名文件///////\n\n\tpublic static List<URL> getResourcesQuietly(String resourceName) {\n\t\treturn getResourcesQuietly(resourceName, ClassLoaderUtil.getDefaultClassLoader());\n\t}\n\n\tpublic static List<URL> getResourcesQuietly(String resourceName, ClassLoader contextClassLoader) {\n\t\ttry {\n\t\t\tEnumeration<URL> urls = contextClassLoader.getResources(resourceName);\n\t\t\tList<URL> list = new ArrayList<URL>(10);\n\t\t\twhile (urls.hasMoreElements()) {\n\t\t\t\tlist.add(urls.nextElement());\n\t\t\t}\n\t\t\treturn list;\n\t\t} catch (IOException e) {// NOSONAR\n\t\t\treturn ListUtil.emptyList();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/io/URLResourceUtil.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Validate;\n\n/**\n * 兼容文件url为无前缀, classpath:, file:// 三种方式的Resource读取工具集\n * \n * e.g: classpath:com/myapp/config.xml, file:///data/config.xml, /data/config.xml\n * \n * 参考Spring ResourceUtils\n */\npublic class URLResourceUtil {\n\n\tprivate static final String CLASSPATH_PREFIX = \"classpath:\";\n\n\tprivate static final String URL_PROTOCOL_FILE = \"file\";\n\n\t/**\n\t * 兼容无前缀, classpath:, file:// 的情况获取文件\n\t * \n\t * 如果以classpath: 定义的文件不存在会抛出IllegalArgumentException异常，以file://定义的则不会\n\t */\n\tpublic static File asFile(String generalPath) throws IOException {\n\t\tif (StringUtils.startsWith(generalPath, CLASSPATH_PREFIX)) {\n\t\t\tString resourceName = StringUtils.substringAfter(generalPath, CLASSPATH_PREFIX);\n\t\t\treturn getFileByURL(ResourceUtil.asUrl(resourceName));\n\t\t}\n\t\ttry {\n\t\t\t// try URL\n\t\t\treturn getFileByURL(new URL(generalPath));\n\t\t} catch (MalformedURLException ex) {\n\t\t\t// no URL -> treat as file path\n\t\t\treturn new File(generalPath);\n\t\t}\n\t}\n\n\t/**\n\t * 兼容无前缀, classpath:, file:// 的情况打开文件成Stream\n\t */\n\tpublic static InputStream asStream(String generalPath) throws IOException {\n\t\tif (StringUtils.startsWith(generalPath, CLASSPATH_PREFIX)) {\n\t\t\tString resourceName = StringUtils.substringAfter(generalPath, CLASSPATH_PREFIX);\n\t\t\treturn ResourceUtil.asStream(resourceName);\n\t\t}\n\n\t\ttry {\n\t\t\t// try URL\n\t\t\treturn FileUtil.asInputStream(getFileByURL(new URL(generalPath)));\n\t\t} catch (MalformedURLException ex) {\n\t\t\t// no URL -> treat as file path\n\t\t\treturn FileUtil.asInputStream(generalPath);\n\t\t}\n\t}\n\n\tprivate static File getFileByURL(URL fileUrl) throws FileNotFoundException {\n\t\tValidate.notNull(fileUrl, \"Resource URL must not be null\");\n\t\tif (!URL_PROTOCOL_FILE.equals(fileUrl.getProtocol())) {\n\t\t\tthrow new FileNotFoundException(\"URL cannot be resolved to absolute file path \"\n\t\t\t\t\t+ \"because it does not reside in the file system: \" + fileUrl);\n\t\t}\n\t\ttry {\n\t\t\treturn new File(toURI(fileUrl.toString()).getSchemeSpecificPart());\n\t\t} catch (URISyntaxException ex) { // NOSONAR\n\t\t\t// Fallback for URLs that are not valid URIs (should hardly ever happen).\n\t\t\treturn new File(fileUrl.getFile());\n\t\t}\n\t}\n\n\tpublic static URI toURI(String location) throws URISyntaxException {\n\t\treturn new URI(StringUtils.replace(location, \" \", \"%20\"));\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/io/type/StringBuilderWriter.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE\n * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file\n * to You under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the\n * License. You may obtain a copy of the License at\n * \n * http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.vip.vjtools.vjkit.io.type;\n\nimport java.io.Serializable;\nimport java.io.Writer;\n\n/**\n * JDK的java.io.StringWriter使用StringBuffer，移植Commons IO使用StringBuilder的版本.\n * \n * https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java\n * \n * {@link Writer} implementation that outputs to a {@link StringBuilder}.\n * <p>\n * <strong>NOTE:</strong> This implementation, as an alternative to <code>java.io.StringWriter</code>, provides an\n * <i>un-synchronized</i> (i.e. for use in a single thread) implementation for better performance. For safe usage with\n * multiple {@link Thread}s then <code>java.io.StringWriter</code> should be used.\n *\n * @since 2.0\n */\npublic class StringBuilderWriter extends Writer implements Serializable {\n\n\tprivate static final long serialVersionUID = -146927496096066153L;\n\tprivate final StringBuilder builder;\n\n\t/**\n\t * Constructs a new {@link StringBuilder} instance with default capacity.\n\t */\n\tpublic StringBuilderWriter() {\n\t\tthis.builder = new StringBuilder();\n\t}\n\n\t/**\n\t * Constructs a new {@link StringBuilder} instance with the specified capacity.\n\t *\n\t * @param capacity The initial capacity of the underlying {@link StringBuilder}\n\t */\n\tpublic StringBuilderWriter(final int capacity) {\n\t\tthis.builder = new StringBuilder(capacity);\n\t}\n\n\t/**\n\t * Constructs a new instance with the specified {@link StringBuilder}.\n\t *\n\t * <p>\n\t * If {@code builder} is null a new instance with default capacity will be created.\n\t * </p>\n\t *\n\t * @param builder The String builder. May be null.\n\t */\n\tpublic StringBuilderWriter(final StringBuilder builder) {\n\t\tthis.builder = builder != null ? builder : new StringBuilder();\n\t}\n\n\t/**\n\t * Appends a single character to this Writer.\n\t *\n\t * @param value The character to append\n\t * @return This writer instance\n\t */\n\t@Override\n\tpublic Writer append(final char value) {\n\t\tbuilder.append(value);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Appends a character sequence to this Writer.\n\t *\n\t * @param value The character to append\n\t * @return This writer instance\n\t */\n\t@Override\n\tpublic Writer append(final CharSequence value) {\n\t\tbuilder.append(value);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Appends a portion of a character sequence to the {@link StringBuilder}.\n\t *\n\t * @param value The character to append\n\t * @param start The index of the first character\n\t * @param end The index of the last character + 1\n\t * @return This writer instance\n\t */\n\t@Override\n\tpublic Writer append(final CharSequence value, final int start, final int end) {\n\t\tbuilder.append(value, start, end);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Closing this writer has no effect.\n\t */\n\t@Override\n\tpublic void close() {\n\t\t// no-op\n\t}\n\n\t/**\n\t * Flushing this writer has no effect.\n\t */\n\t@Override\n\tpublic void flush() {\n\t\t// no-op\n\t}\n\n\t/**\n\t * Writes a String to the {@link StringBuilder}.\n\t *\n\t * @param value The value to write\n\t */\n\t@Override\n\tpublic void write(final String value) {\n\t\tif (value != null) {\n\t\t\tbuilder.append(value);\n\t\t}\n\t}\n\n\t/**\n\t * Writes a portion of a character array to the {@link StringBuilder}.\n\t *\n\t * @param value The value to write\n\t * @param offset The index of the first character\n\t * @param length The number of characters to write\n\t */\n\t@Override\n\tpublic void write(final char[] value, final int offset, final int length) {\n\t\tif (value != null) {\n\t\t\tbuilder.append(value, offset, length);\n\t\t}\n\t}\n\n\t/**\n\t * Returns the underlying builder.\n\t *\n\t * @return The underlying builder\n\t */\n\tpublic StringBuilder getBuilder() {\n\t\treturn builder;\n\t}\n\n\t/**\n\t * Returns {@link StringBuilder#toString()}.\n\t *\n\t * @return The contents of the String builder.\n\t */\n\t@Override\n\tpublic String toString() {\n\t\treturn builder.toString();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/logging/PerformanceUtil.java",
    "content": "package com.vip.vjtools.vjkit.logging;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\n\n/**\n * 性能日志工具\n */\npublic class PerformanceUtil {\n\tprivate PerformanceUtil() {\n\t}\n\n\t// 全局共享ThreadLocal Timer\n\tprivate static ThreadLocal<Timer> localTimer = new ThreadLocal<Timer>() {\n\t\t@Override\n\t\tprotected Timer initialValue() {\n\t\t\treturn new Timer();\n\t\t}\n\t};\n\n\t// 按Key定义多个ThreadLocal Timer\n\tprivate static ThreadLocal<Map<String, Timer>> localTimerMap = new ThreadLocal<Map<String, Timer>>() {\n\t\t@Override\n\t\tprotected Map<String, Timer> initialValue() {\n\t\t\treturn new HashMap<String, Timer>();\n\t\t}\n\t};\n\n\t/**\n\t * 记录开始时间\n\t */\n\tpublic static void start() {\n\t\tlocalTimer.get().start();\n\t}\n\n\t/**\n\t * 返回开始到现在的时间\n\t */\n\tpublic static long duration() {\n\t\treturn localTimer.get().duration();\n\t}\n\n\t/**\n\t * 记录结束时间\n\t */\n\tpublic static long end() {\n\t\tlong duration = localTimer.get().duration();\n\t\tlocalTimer.remove();\n\t\treturn duration;\n\t}\n\n\t/**\n\t * 记录特定Timer的开始时间\n\t */\n\tpublic static void start(String key) {\n\t\tgetTimer(key).start();\n\t}\n\n\t/**\n\t * 记录特定Timer的开始时间\n\t */\n\tpublic static long duration(String key) {\n\t\treturn getTimer(key).duration();\n\t}\n\n\t/**\n\t * 记录特定Timer结束时间，返回耗时\n\t */\n\tpublic static long end(String key) {\n\t\tlong duration = getTimer(key).duration();\n\t\tlocalTimerMap.get().remove(key);\n\t\treturn duration;\n\t}\n\n\t/**\n\t * 清除所有ThreadLocal Timer\n\t */\n\tpublic static void removeAll() {\n\t\tlocalTimer.remove();\n\t\tlocalTimerMap.remove();\n\t}\n\n\n\t/**\n\t * 当处理时间超过预定的阈值时发出警告信息\n\t * @param logger\n\t * @param duration\n\t * @param threshold 阈值（单位：ms）\n\t */\n\tpublic static void slowLog(Logger logger, long duration, long threshold) {\n\t\tif (duration > threshold) {\n\t\t\tlogger.warn(\"[Performance Warning]  use {}ms, slow than {}ms\", duration, threshold);\n\t\t}\n\t}\n\n\t/**\n\t * 当处理时间超过预定的阈值时发出警告信息\n\t * @param logger\n\t * @param key\n\t * @param threshold 阈值（单位：ms）\n\t */\n\tpublic static void slowLog(Logger logger, String key, long duration, long threshold) {\n\t\tif (duration > threshold) {\n\t\t\tlogger.warn(\"[Performance Warning] task {} use {}ms, slow than {}ms\", key, duration, threshold);\n\t\t}\n\t}\n\n\t/**\n\t * 当处理时间超过预定的阈值时发出警告信息\n\t * \n\t * @param logger 写日志的logger\n\t * @param threshold 阈值（单位：ms）\n\t * @param context 需要记录的context信息，如请求的json等\n\t */\n\tpublic static void slowLog(Logger logger, long duration, long threshold, String context) {\n\t\tif (duration > threshold) {\n\t\t\tlogger.warn(\"[Performance Warning] use {}ms, slow than {}ms, context={}\", duration, threshold, context);\n\t\t}\n\t}\n\n\t/**\n\t * 当处理时间超过预定的阈值时发出警告信息\n\t * @param logger\n\t * @param key\n\t * @param threshold 阈值（单位：ms）\n\t * @param context 需要记录的context信息，如请求的json等\n\t */\n\tpublic static void slowLog(Logger logger, String key, long duration, long threshold, String context) {\n\t\tif (duration > threshold) {\n\t\t\tlogger.warn(\"[Performance Warning] task {} use {}ms, slow than {}ms, contxt={}\", key, duration, threshold,\n\t\t\t\t\tcontext);\n\t\t}\n\t}\n\n\t/**\n\t * 记录结束时间并当处理时间超过预定的阈值时发出警告信息，最后清除\n\t * @param logger\n\t * @param threshold 阈值（单位：ms）\n\t */\n\tpublic static void endWithSlowLog(Logger logger, long threshold) {\n\t\tslowLog(logger, end(), threshold);\n\t}\n\n\t/**\n\t * 记录结束时间并当处理时间超过预定的阈值时发出警告信息，最后清除\n\t * @param logger\n\t * @param key\n\t * @param threshold 阈值（单位：ms）\n\t */\n\tpublic static void endWithSlowLog(Logger logger, String key, long threshold) {\n\t\tslowLog(logger, key, end(key), threshold);\n\t}\n\n\t/**\n\t * 记录结束时间并当处理时间超过预定的阈值时发出警告信息，最后清除\n\t * @param logger\n\t * @param threshold 阈值（单位：ms）\n\t * @param context 需要记录的context信息，如请求的json等\n\t */\n\tpublic static void endWithSlowLog(Logger logger, long threshold, String context) {\n\t\tslowLog(logger, end(), threshold, context);\n\t}\n\n\t/**\n\t * 记录结束时间并当处理时间超过预定的阈值时发出警告信息，最后清除\n\t * @param logger\n\t * @param key\n\t * @param threshold 阈值（单位：ms）\n\t*  @param context 需要记录的context信息，如请求的json等\n\t */\n\tpublic static void endWithSlowLog(Logger logger, String key, long threshold, String context) {\n\t\tslowLog(logger, key, end(key), threshold, context);\n\t}\n\n\tprivate static Timer getTimer(String key) {\n\t\tMap<String, Timer> map = localTimerMap.get();\n\t\tTimer timer = map.get(key);\n\t\tif (timer == null) {\n\t\t\ttimer = new Timer();\n\t\t\tmap.put(key, timer);\n\t\t}\n\t\treturn timer;\n\t}\n\n\n\tstatic class Timer {\n\t\tprivate long start;\n\n\t\tpublic void start() {\n\t\t\tstart = System.currentTimeMillis();\n\t\t}\n\n\t\tpublic long duration() {\n\t\t\treturn System.currentTimeMillis() - start;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/mapper/BeanMapper.java",
    "content": "package com.vip.vjtools.vjkit.mapper;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.dozer.DozerBeanMapper;\nimport org.dozer.Mapper;\n\nimport com.vip.vjtools.vjkit.collection.ArrayUtil;\n\n/**\n * 实现深度的BeanOfClassA<->BeanOfClassB复制\n * \n * 不要使用Apache Common BeanUtils进行类复制，每次就行反射查询对象的属性列表, 非常缓慢.\n * \n * orika性能比Dozer快近十倍，也不需要Getter函数与无参构造函数\n * \n * 但我们内部修复了的bug，社区版没有修复: https://github.com/orika-mapper/orika/issues/252 \n * \n * 如果应用启动时有并发流量进入，可能导致两个不同类型的同名属性间(如Order的User user属性，与OrderVO的UserVO user)的复制失败，只有重启才能解决。\n * \n * 因此安全起见，在vjkit的开源版本中仍然使用Dozer。\n * \n * Dozer最新是6.x版，但only for JDK8，为兼容JDK7这里仍使用5.x版本。\n * \n * 注意: 需要参考POM文件，显式引用Dozer.\n */\npublic class BeanMapper {\n\n\tprivate static Mapper mapper = new DozerBeanMapper();\n\n\t/**\n\t * 简单的复制出新类型对象.\n\t */\n\tpublic static <S, D> D map(S source, Class<D> destinationClass) {\n\t\treturn mapper.map(source, destinationClass);\n\t}\n\n\t/**\n\t * 简单的复制出新对象ArrayList\n\t */\n\tpublic static <S, D> List<D> mapList(Iterable<S> sourceList, Class<D> destinationClass) {\n\t\tList<D> destinationList = new ArrayList<D>();\n\t\tfor (S source : sourceList) {\n\t\t\tif (source != null) {\n\t\t\t\tdestinationList.add(mapper.map(source, destinationClass));\n\t\t\t}\n\t\t}\n\t\treturn destinationList;\n\t}\n\n\t/**\n\t * 简单复制出新对象数组\n\t */\n\tpublic static <S, D> D[] mapArray(final S[] sourceArray, final Class<D> destinationClass) {\n\t\tD[] destinationArray = ArrayUtil.newArray(destinationClass, sourceArray.length);\n\n\t\tint i = 0;\n\t\tfor (S source : sourceArray) {\n\t\t\tif (source != null) {\n\t\t\t\tdestinationArray[i] = mapper.map(sourceArray[i], destinationClass);\n\t\t\t\ti++;\n\t\t\t}\n\t\t}\n\n\t\treturn destinationArray;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/mapper/JsonMapper.java",
    "content": "package com.vip.vjtools.vjkit.mapper;\n\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.Map;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.fasterxml.jackson.annotation.JsonInclude.Include;\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.JavaType;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.SerializationFeature;\nimport com.fasterxml.jackson.databind.util.JSONPObject;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 简单封装Jackson，实现JSON String<->Java Object转换的Mapper.\n * \n * 可以直接使用公共示例JsonMapper.INSTANCE, 也可以使用不同的builder函数创建实例，封装不同的输出风格,\n * \n * 不要使用GSON, 在对象稍大时非常缓慢.\n * \n * 注意: 需要参考本模块的POM文件，显式引用jackson.\n * \n * @author calvin\n */\npublic class JsonMapper {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(JsonMapper.class);\n\n\tpublic static final JsonMapper INSTANCE = new JsonMapper();\n\n\tprivate ObjectMapper mapper;\n\n\tpublic JsonMapper() {\n\t\tthis(null);\n\t}\n\n\tpublic JsonMapper(Include include) {\n\t\tmapper = new ObjectMapper();\n\t\t// 设置输出时包含属性的风格\n\t\tif (include != null) {\n\t\t\tmapper.setSerializationInclusion(include);\n\t\t}\n\t\t// 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性\n\t\tmapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);\n\t}\n\n\t/**\n\t * 创建只输出非Null的属性到Json字符串的Mapper.\n\t */\n\tpublic static JsonMapper nonNullMapper() {\n\t\treturn new JsonMapper(Include.NON_NULL);\n\t}\n\n\t/**\n\t * 创建只输出非Null且非Empty(如List.isEmpty)的属性到Json字符串的Mapper.\n\t * \n\t * 注意，要小心使用, 特别留意empty的情况.\n\t */\n\tpublic static JsonMapper nonEmptyMapper() {\n\t\treturn new JsonMapper(Include.NON_EMPTY);\n\t}\n\n\t/**\n\t * 默认的全部输出的Mapper, 区别于INSTANCE，可以做进一步的配置\n\t */\n\tpublic static JsonMapper defaultMapper() {\n\t\treturn new JsonMapper();\n\t}\n\n\t/**\n\t * Object可以是POJO，也可以是Collection或数组。 如果对象为Null, 返回\"null\". 如果集合为空集合, 返回\"[]\".\n\t */\n\tpublic String toJson(Object object) {\n\n\t\ttry {\n\t\t\treturn mapper.writeValueAsString(object);\n\t\t} catch (IOException e) {\n\t\t\tlogger.warn(\"write to json string error:\" + object, e);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * 反序列化POJO或简单Collection如List<String>.\n\t * \n\t * 如果JSON字符串为Null或\"null\"字符串, 返回Null. 如果JSON字符串为\"[]\", 返回空集合.\n\t * \n\t * 如需反序列化复杂Collection如List<MyBean>, 请使用fromJson(String, JavaType)\n\t * \n\t * @see #fromJson(String, JavaType)\n\t */\n\tpublic <T> T fromJson(@Nullable String jsonString, Class<T> clazz) {\n\t\tif (StringUtils.isEmpty(jsonString)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\treturn mapper.readValue(jsonString, clazz);\n\t\t} catch (IOException e) {\n\t\t\tlogger.warn(\"parse json string error:\" + jsonString, e);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * 反序列化复杂Collection如List<Bean>, constructCollectionType()或constructMapType()构造类型, 然后调用本函数.\n\t * \n\t * @see #createCollectionType(Class, Class...)\n\t */\n\tpublic <T> T fromJson(@Nullable String jsonString, JavaType javaType) {\n\t\tif (StringUtils.isEmpty(jsonString)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\treturn (T) mapper.readValue(jsonString, javaType);\n\t\t} catch (IOException e) {\n\t\t\tlogger.warn(\"parse json string error:\" + jsonString, e);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * 构造Collection类型.\n\t */\n\tpublic JavaType buildCollectionType(Class<? extends Collection> collectionClass, Class<?> elementClass) {\n\t\treturn mapper.getTypeFactory().constructCollectionType(collectionClass, elementClass);\n\t}\n\n\t/**\n\t * 构造Map类型.\n\t */\n\tpublic JavaType buildMapType(Class<? extends Map> mapClass, Class<?> keyClass, Class<?> valueClass) {\n\t\treturn mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass);\n\t}\n\n\t/**\n\t * 当JSON里只含有Bean的部分属性時，更新一個已存在Bean，只覆盖該部分的属性.\n\t */\n\tpublic void update(String jsonString, Object object) {\n\t\ttry {\n\t\t\tmapper.readerForUpdating(object).readValue(jsonString);\n\t\t} catch (JsonProcessingException e) {\n\t\t\tlogger.warn(\"update json string:\" + jsonString + \" to object:\" + object + \" error.\", e);\n\t\t} catch (IOException e) {\n\t\t\tlogger.warn(\"update json string:\" + jsonString + \" to object:\" + object + \" error.\", e);\n\t\t}\n\t}\n\n\t/**\n\t * 輸出JSONP格式數據.\n\t */\n\tpublic String toJsonP(String functionName, Object object) {\n\t\treturn toJson(new JSONPObject(functionName, object));\n\t}\n\n\t/**\n\t * 設定是否使用Enum的toString函數來讀寫Enum, 為False時時使用Enum的name()函數來讀寫Enum, 默認為False. 注意本函數一定要在Mapper創建後, 所有的讀寫動作之前調用.\n\t */\n\tpublic void enableEnumUseToString() {\n\t\tmapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);\n\t\tmapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);\n\t}\n\n\t/**\n\t * 取出Mapper做进一步的设置或使用其他序列化API.\n\t */\n\tpublic ObjectMapper getMapper() {\n\t\treturn mapper;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/mapper/XmlMapper.java",
    "content": "package com.vip.vjtools.vjkit.mapper;\n\nimport java.io.StringReader;\nimport java.io.StringWriter;\nimport java.util.Collection;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\nimport javax.xml.bind.JAXBContext;\nimport javax.xml.bind.JAXBElement;\nimport javax.xml.bind.JAXBException;\nimport javax.xml.bind.Marshaller;\nimport javax.xml.bind.Unmarshaller;\nimport javax.xml.bind.annotation.XmlAnyElement;\nimport javax.xml.namespace.QName;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Validate;\n\nimport com.vip.vjtools.vjkit.base.ExceptionUtil;\nimport com.vip.vjtools.vjkit.reflect.ClassUtil;\n\n\n/**\n * 使用Jaxb2.0实现XML<->Java Object的Mapper.\n * \n * 在创建时需要设定所有需要序列化的Root对象的Class.\n * 特别支持Root对象是Collection的情形.\n * \n * @author calvin\n */\npublic class XmlMapper {\n\n\tprivate static ConcurrentMap<Class, JAXBContext> jaxbContexts = new ConcurrentHashMap<Class, JAXBContext>();\n\n\t/**\n\t * Java Object->Xml without encoding.\n\t */\n\tpublic static String toXml(Object root) {\n\t\tClass clazz = ClassUtil.unwrapCglib(root);\n\t\treturn toXml(root, clazz, null);\n\t}\n\n\t/**\n\t * Java Object->Xml with encoding.\n\t */\n\tpublic static String toXml(Object root, String encoding) {\n\t\tClass clazz = ClassUtil.unwrapCglib(root);\n\t\treturn toXml(root, clazz, encoding);\n\t}\n\n\t/**\n\t * Java Object->Xml with encoding.\n\t */\n\tpublic static String toXml(Object root, Class clazz, String encoding) {\n\t\ttry {\n\t\t\tStringWriter writer = new StringWriter();\n\t\t\tcreateMarshaller(clazz, encoding).marshal(root, writer);\n\t\t\treturn writer.toString();\n\t\t} catch (JAXBException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * Java Collection->Xml without encoding, 特别支持Root Element是Collection的情形.\n\t */\n\tpublic static String toXml(Collection<?> root, String rootName, Class clazz) {\n\t\treturn toXml(root, rootName, clazz, null);\n\t}\n\n\t/**\n\t * Java Collection->Xml with encoding, 特别支持Root Element是Collection的情形.\n\t */\n\tpublic static String toXml(Collection<?> root, String rootName, Class clazz, String encoding) {\n\t\ttry {\n\t\t\tCollectionWrapper wrapper = new CollectionWrapper();\n\t\t\twrapper.collection = root;\n\n\t\t\tJAXBElement<CollectionWrapper> wrapperElement = new JAXBElement<CollectionWrapper>(new QName(rootName),\n\t\t\t\t\tCollectionWrapper.class, wrapper);\n\n\t\t\tStringWriter writer = new StringWriter();\n\t\t\tcreateMarshaller(clazz, encoding).marshal(wrapperElement, writer);\n\n\t\t\treturn writer.toString();\n\t\t} catch (JAXBException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * Xml->Java Object.\n\t */\n\tpublic static <T> T fromXml(String xml, Class<T> clazz) {\n\t\ttry {\n\t\t\tStringReader reader = new StringReader(xml);\n\t\t\treturn (T) createUnmarshaller(clazz).unmarshal(reader);\n\t\t} catch (JAXBException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * 创建Marshaller并设定encoding(可为null).\n\t * 线程不安全，需要每次创建或pooling。\n\t */\n\tpublic static Marshaller createMarshaller(Class clazz, String encoding) {\n\t\ttry {\n\t\t\tJAXBContext jaxbContext = getJaxbContext(clazz);\n\n\t\t\tMarshaller marshaller = jaxbContext.createMarshaller();\n\n\t\t\tmarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);\n\n\t\t\tif (StringUtils.isNotBlank(encoding)) {\n\t\t\t\tmarshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);\n\t\t\t}\n\n\t\t\treturn marshaller;\n\t\t} catch (JAXBException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * 创建UnMarshaller.\n\t * 线程不安全，需要每次创建或pooling。\n\t */\n\tpublic static Unmarshaller createUnmarshaller(Class clazz) {\n\t\ttry {\n\t\t\tJAXBContext jaxbContext = getJaxbContext(clazz);\n\t\t\treturn jaxbContext.createUnmarshaller();\n\t\t} catch (JAXBException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\tprotected static JAXBContext getJaxbContext(Class clazz) {\n\t\tValidate.notNull(clazz, \"'clazz' must not be null\");\n\t\tJAXBContext jaxbContext = jaxbContexts.get(clazz);\n\t\tif (jaxbContext == null) {\n\t\t\ttry {\n\t\t\t\tjaxbContext = JAXBContext.newInstance(clazz, CollectionWrapper.class);\n\t\t\t\tjaxbContexts.putIfAbsent(clazz, jaxbContext);\n\t\t\t} catch (JAXBException ex) {\n\t\t\t\tthrow new RuntimeException(\n\t\t\t\t\t\t\"Could not instantiate JAXBContext for class [\" + clazz + \"]: \" + ex.getMessage(), ex);\n\t\t\t}\n\t\t}\n\t\treturn jaxbContext;\n\t}\n\n\t/**\n\t * 封装Root Element 是 Collection的情况.\n\t */\n\tpublic static class CollectionWrapper {\n\n\t\t@XmlAnyElement\n\t\tprotected Collection<?> collection;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/net/IPUtil.java",
    "content": "package com.vip.vjtools.vjkit.net;\n\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.List;\n\nimport com.google.common.net.InetAddresses;\nimport com.vip.vjtools.vjkit.number.NumberUtil;\nimport com.vip.vjtools.vjkit.text.MoreStringUtil;\n\n/**\n * InetAddress工具类，基于Guava的InetAddresses.\n * \n * 主要包含int, String/IPV4String, InetAdress/Inet4Address之间的互相转换\n * \n * 先将字符串传换为byte[]再用InetAddress.getByAddress(byte[])，避免了InetAddress.getByName(ip)可能引起的DNS访问.\n * \n * InetAddress与String的转换其实消耗不小，如果是有限的地址，建议进行缓存.\n */\npublic class IPUtil {\n\n\t/**\n\t * 从InetAddress转化到int, 传输和存储时, 用int代表InetAddress是最小的开销.\n\t * \n\t * InetAddress可以是IPV4或IPV6，都会转成IPV4.\n\t * \n\t * @see com.google.common.net.InetAddresses#coerceToInteger(InetAddress)\n\t */\n\tpublic static int toInt(InetAddress address) {\n\t\treturn InetAddresses.coerceToInteger(address);\n\t}\n\n\t/**\n\t * InetAddress转换为String.\n\t * \n\t * InetAddress可以是IPV4或IPV6. 其中IPV4直接调用getHostAddress()\n\t * \n\t * @see com.google.common.net.InetAddresses#toAddrString(InetAddress)\n\t */\n\tpublic static String toIpString(InetAddress address) {\n\t\treturn InetAddresses.toAddrString(address);\n\t}\n\n\t/**\n\t * 从int转换为Inet4Address(仅支持IPV4)\n\t */\n\tpublic static Inet4Address fromInt(int address) {\n\t\treturn InetAddresses.fromInteger(address);\n\t}\n\n\t/**\n\t * 从String转换为InetAddress.\n\t * \n\t * IpString可以是ipv4 或 ipv6 string, 但不可以是域名.\n\t * \n\t * 先字符串传换为byte[]再调getByAddress(byte[])，避免了调用getByName(ip)可能引起的DNS访问.\n\t */\n\tpublic static InetAddress fromIpString(String address) {\n\t\treturn InetAddresses.forString(address);\n\t}\n\n\t/**\n\t * 从IPv4String转换为InetAddress.\n\t * \n\t * IpString如果确定ipv4, 使用本方法减少字符分析消耗 .\n\t * \n\t * 先字符串传换为byte[]再调getByAddress(byte[])，避免了调用getByName(ip)可能引起的DNS访问.\n\t */\n\tpublic static Inet4Address fromIpv4String(String address) {\n\t\tbyte[] bytes = ip4StringToBytes(address);\n\t\tif (bytes == null) {\n\t\t\treturn null;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\treturn (Inet4Address) Inet4Address.getByAddress(bytes);\n\t\t\t} catch (UnknownHostException e) {\n\t\t\t\tthrow new AssertionError(e);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * int转换到IPV4 String, from Netty NetUtil\n\t */\n\tpublic static String intToIpv4String(int i) {\n\t\treturn new StringBuilder(15).append((i >> 24) & 0xff).append('.').append((i >> 16) & 0xff).append('.')\n\t\t\t\t.append((i >> 8) & 0xff).append('.').append(i & 0xff).toString();\n\t}\n\n\t/**\n\t * Ipv4 String 转换到int\n\t */\n\tpublic static int ipv4StringToInt(String ipv4Str) {\n\t\tbyte[] byteAddress = ip4StringToBytes(ipv4Str);\n\t\tif (byteAddress == null) {\n\t\t\treturn 0;\n\t\t} else {\n\t\t\treturn NumberUtil.toInt(byteAddress);\n\t\t}\n\t}\n\n\t/**\n\t * Ipv4 String 转换到byte[]\n\t */\n\tprivate static byte[] ip4StringToBytes(String ipv4Str) {\n\t\tif (ipv4Str == null) {\n\t\t\treturn null;\n\t\t}\n\n\t\tList<String> it = MoreStringUtil.split(ipv4Str, '.', 4);\n\t\tif (it.size() != 4) {\n\t\t\treturn null;\n\t\t}\n\n\t\tbyte[] byteAddress = new byte[4];\n\t\tfor (int i = 0; i < 4; i++) {\n\t\t\tint tempInt = Integer.parseInt(it.get(i));\n\t\t\tif (tempInt > 255) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tbyteAddress[i] = (byte) tempInt;\n\t\t}\n\t\treturn byteAddress;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/net/NetUtil.java",
    "content": "package com.vip.vjtools.vjkit.net;\n\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.net.ServerSocket;\nimport java.net.SocketException;\nimport java.net.UnknownHostException;\nimport java.util.Enumeration;\nimport java.util.Map;\nimport java.util.Random;\n\nimport javax.net.ServerSocketFactory;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.google.common.annotations.Beta;\nimport com.vip.vjtools.vjkit.base.Platforms;\nimport com.vip.vjtools.vjkit.base.SystemPropertiesUtil;\nimport com.vip.vjtools.vjkit.collection.MapUtil;\n\n/**\n * 关于网络的工具类.\n * \n * 1. 获取本机IP地址与HostName\n * \n * 2. 查找空闲端口\n */\n@Beta\npublic class NetUtil {\n\tprivate static Logger logger = LoggerFactory.getLogger(NetUtil.class);\n\n\tpublic static final int PORT_RANGE_MIN = 1024;\n\tpublic static final int PORT_RANGE_MAX = 65535;\n\n\tprivate static Random random = new Random();\n\n\t/////// LocalAddress //////\n\n\t/**\n\t * 获得本地地址\n\t */\n\tpublic static InetAddress getLocalAddress() {\n\t\treturn LocalAddressHoler.INSTANCE.localInetAddress;\n\t}\n\n\t/**\n\t * 获得本地Ip地址\n\t */\n\tpublic static String getLocalHost() {\n\t\treturn LocalAddressHoler.INSTANCE.localHost;\n\t}\n\n\t/**\n\t * 获得本地HostName\n\t */\n\tpublic static String getHostName() {\n\t\treturn LocalAddressHoler.INSTANCE.hostName;\n\t}\n\n\t/**\n\t * 懒加载进行探测\n\t */\n\tprivate static class LocalAddressHoler {\n\t\tstatic final LocalAddress INSTANCE = new LocalAddress();\n\t}\n\n\tprivate static class LocalAddress {\n\n\t\tprivate InetAddress localInetAddress;\n\t\tprivate String localHost;\n\t\tprivate String hostName;\n\n\t\tpublic LocalAddress() {\n\t\t\tinitLocalAddress();\n\t\t\t// from Common Lang SystemUtils\n\t\t\thostName = Platforms.IS_WINDOWS ? System.getenv(\"COMPUTERNAME\") : System.getenv(\"HOSTNAME\");\n\t\t}\n\n\t\t/**\n\t\t * 初始化本地地址\n\t\t */\n\t\tprivate void initLocalAddress() {\n\t\t\tNetworkInterface nic = null;\n\t\t\t// 根据命令行执行hostname获得本机hostname， 与/etc/hosts 中该hostname的第一条ip配置，获得ip地址\n\t\t\ttry {\n\t\t\t\tlocalInetAddress = InetAddress.getLocalHost();\n\t\t\t\tnic = NetworkInterface.getByInetAddress(localInetAddress);\n\t\t\t} catch (Exception ignored) { // NOSONAR\n\t\t\t}\n\n\t\t\t// 如果结果为空，或是一个loopback地址(127.0.0.1), 或是ipv6地址，再遍历网卡尝试获取\n\t\t\tif (localInetAddress == null || nic == null || localInetAddress.isLoopbackAddress()\n\t\t\t\t\t|| localInetAddress instanceof Inet6Address) {\n\t\t\t\tInetAddress lookedUpAddr = findLocalAddressViaNetworkInterface();\n\t\t\t\t// 仍然不符合要求，只好使用127.0.0.1\n\t\t\t\ttry {\n\t\t\t\t\tlocalInetAddress = lookedUpAddr != null ? lookedUpAddr : InetAddress.getByName(\"127.0.0.1\");\n\t\t\t\t} catch (UnknownHostException ignored) {// NOSONAR\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlocalHost = IPUtil.toIpString(localInetAddress);\n\n\t\t\tlogger.info(\"localhost is {}\", localHost);\n\t\t}\n\n\t\t/**\n\t\t * 根据preferNamePrefix 与 defaultNicList的配置网卡，找出合适的网卡\n\t\t */\n\t\tprivate static InetAddress findLocalAddressViaNetworkInterface() {\n\t\t\t// 如果hostname +/etc/hosts 得到的是127.0.0.1, 则首选这块网卡\n\t\t\tString preferNamePrefix = SystemPropertiesUtil.getString(\"localhost.prefer.nic.prefix\",\n\t\t\t\t\t\"LOCALHOST_PREFER_NIC_PREFIX\", \"bond0.\");\n\t\t\t// 如果hostname +/etc/hosts 得到的是127.0.0.1, 和首选网卡都不符合要求，则按顺序遍历下面的网卡\n\t\t\tString defaultNicList = SystemPropertiesUtil.getString(\"localhost.default.nic.list\",\n\t\t\t\t\t\"LOCALHOST_DEFAULT_NIC_LIST\", \"bond0,eth0,em0,br0\");\n\n\t\t\tInetAddress resultAddress = null;\n\t\t\tMap<String, NetworkInterface> candidateInterfaces = MapUtil.newHashMap();\n\n\t\t\t// 遍历所有网卡，找出所有可用网卡，尝试找出符合prefer前缀的网卡\n\t\t\ttry {\n\t\t\t\tfor (Enumeration<NetworkInterface> allInterfaces = NetworkInterface\n\t\t\t\t\t\t.getNetworkInterfaces(); allInterfaces.hasMoreElements();) {\n\t\t\t\t\tNetworkInterface nic = allInterfaces.nextElement();\n\t\t\t\t\t// 检查网卡可用并支持广播\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!nic.isUp() || !nic.supportsMulticast()) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (SocketException ignored) { // NOSONAR\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 检查是否符合prefer前缀\n\t\t\t\t\tString name = nic.getName();\n\t\t\t\t\tif (name.startsWith(preferNamePrefix)) {\n\t\t\t\t\t\t// 检查有否非ipv6 非127.0.0.1的inetaddress\n\t\t\t\t\t\tresultAddress = findAvailableInetAddress(nic);\n\t\t\t\t\t\tif (resultAddress != null) {\n\t\t\t\t\t\t\treturn resultAddress;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// 不是Prefer前缀，先放入可选列表\n\t\t\t\t\t\tcandidateInterfaces.put(name, nic);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor (String nifName : defaultNicList.split(\",\")) {\n\t\t\t\t\tNetworkInterface nic = candidateInterfaces.get(nifName);\n\t\t\t\t\tif (nic != null) {\n\t\t\t\t\t\tresultAddress = findAvailableInetAddress(nic);\n\t\t\t\t\t\tif (resultAddress != null) {\n\t\t\t\t\t\t\treturn resultAddress;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (SocketException e) {// NOSONAR\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\t/**\n\t\t * 检查有否非ipv6，非127.0.0.1的inetaddress\n\t\t */\n\t\tprivate static InetAddress findAvailableInetAddress(NetworkInterface nic) {\n\t\t\tfor (Enumeration<InetAddress> indetAddresses = nic.getInetAddresses(); indetAddresses.hasMoreElements();) {\n\t\t\t\tInetAddress inetAddress = indetAddresses.nextElement();\n\t\t\t\tif (!(inetAddress instanceof Inet6Address) && !inetAddress.isLoopbackAddress()) {\n\t\t\t\t\treturn inetAddress;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/////////// 查找空闲端口 /////////\n\n\t/**\n\t * 测试端口是否空闲可用, from Spring SocketUtils\n\t */\n\tpublic static boolean isPortAvailable(int port) {\n\t\ttry {\n\t\t\tServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1,\n\t\t\t\t\tInetAddress.getByName(\"localhost\"));\n\t\t\tserverSocket.close();\n\t\t\treturn true;\n\t\t} catch (Exception ex) { // NOSONAR\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * 从1024到65535， 随机找一个空闲端口 from Spring SocketUtils\n\t */\n\tpublic static int findRandomAvailablePort() {\n\t\treturn findRandomAvailablePort(PORT_RANGE_MIN, PORT_RANGE_MAX);\n\t}\n\n\t/**\n\t * 在范围里随机找一个空闲端口,from Spring SocketUtils.\n\t * \n\t * @throws IllegalStateException 最多尝试(maxPort-minPort)次，如无空闲端口，抛出此异常.\n\t */\n\tpublic static int findRandomAvailablePort(int minPort, int maxPort) {\n\t\tint portRange = maxPort - minPort;\n\t\tint candidatePort;\n\t\tint searchCounter = 0;\n\n\t\tdo {\n\t\t\tif (++searchCounter > portRange) {\n\t\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\tString.format(\"Could not find an available tcp port in the range [%d, %d] after %d attempts\",\n\t\t\t\t\t\t\t\tminPort, maxPort, searchCounter));\n\t\t\t}\n\t\t\tcandidatePort = minPort + random.nextInt(portRange + 1);\n\t\t} while (!isPortAvailable(candidatePort));\n\n\t\treturn candidatePort;\n\t}\n\n\t/**\n\t * 从某个端口开始，递增直到65535，找一个空闲端口.\n\t * \n\t * @throws IllegalStateException 范围内如无空闲端口，抛出此异常\n\t */\n\tpublic static int findAvailablePortFrom(int minPort) {\n\t\tfor (int port = minPort; port < PORT_RANGE_MAX; port++) {\n\t\t\tif (isPortAvailable(port)) {\n\t\t\t\treturn port;\n\t\t\t}\n\t\t}\n\n\t\tthrow new IllegalStateException(\n\t\t\t\tString.format(\"Could not find an available tcp port in the range [%d, %d]\", minPort, PORT_RANGE_MAX));\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/number/MathUtil.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\nimport java.math.RoundingMode;\n\nimport com.google.common.math.IntMath;\nimport com.google.common.math.LongMath;\n\n/**\n * 数学相关工具类.包括\n * \n * 1. 2的倍数的计算\n * \n * 2. 其他函数如安全的取模，可控制取整方向的相除，乘方，开方等。\n */\npublic class MathUtil {\n\n\t/////// 2 的倍数的计算////\n\n\t/**\n\t * 往上找出最接近的2的倍数，比如15返回16， 17返回32.\n\t * \n\t * @param value 必须为正数，否则抛出异常.\n\t */\n\tpublic static int nextPowerOfTwo(int value) {\n\t\treturn IntMath.ceilingPowerOfTwo(value);\n\t}\n\n\t/**\n\t * 往上找出最接近的2的倍数，比如15返回16， 17返回32.\n\t * \n\t * @param value 必须为正数，否则抛出异常.\n\t */\n\tpublic static long nextPowerOfTwo(long value) {\n\t\treturn LongMath.ceilingPowerOfTwo(value);\n\t}\n\n\t/**\n\t * 往下找出最接近2的倍数，比如15返回8， 17返回16.\n\t * \n\t * @param value 必须为正数，否则抛出异常.\n\t */\n\tpublic static int previousPowerOfTwo(int value) {\n\t\treturn IntMath.floorPowerOfTwo(value);\n\t}\n\n\t/**\n\t * 往下找出最接近2的倍数，比如15返回8， 17返回16.\n\t * \n\t * @param value 必须为正数，否则抛出异常.\n\t */\n\tpublic static long previousPowerOfTwo(long value) {\n\t\treturn LongMath.floorPowerOfTwo(value);\n\t}\n\n\t/**\n\t * 是否2的倍数\n\t * \n\t * @param value 不是正数时总是返回false\n\t */\n\tpublic static boolean isPowerOfTwo(int value) {\n\t\treturn IntMath.isPowerOfTwo(value);\n\t}\n\n\t/**\n\t * 是否2的倍数\n\t * \n\t * @param value <=0 时总是返回false\n\t */\n\tpublic static boolean isPowerOfTwo(long value) {\n\t\treturn LongMath.isPowerOfTwo(value);\n\t}\n\n\t/**\n\t * 当模为2的倍数时，用比取模块更快的方式计算.\n\t * \n\t * @param value 可以为负数，比如 －1 mod 16 = 15\n\t */\n\tpublic static int modByPowerOfTwo(int value, int mod) {\n\t\treturn value & (mod - 1);\n\t}\n\n\t////////////// 其他函数//////////\n\n\t/**\n\t * 保证结果为正数的取模.\n\t * \n\t * 如果(v = x/m) <0，v+=m.\n\t */\n\tpublic static int mod(int x, int m) {\n\t\treturn IntMath.mod(x, m);\n\t}\n\n\t/**\n\t * 保证结果为正数的取模.\n\t * \n\t * 如果(v = x/m) <0，v+=m.\n\t */\n\tpublic static long mod(long x, long m) {\n\t\treturn LongMath.mod(x, m);\n\t}\n\n\t/**\n\t * 保证结果为正数的取模\n\t */\n\tpublic static int mod(long x, int m) {\n\t\treturn LongMath.mod(x, m);\n\t}\n\n\t/**\n\t * 能控制rounding方向的int相除.\n\t * \n\t * jdk的'/'运算符，直接向下取整\n\t */\n\tpublic static int divide(int p, int q, RoundingMode mode) {\n\t\treturn IntMath.divide(p, q, mode);\n\t}\n\n\t/**\n\t * 能控制rounding方向的long相除\n\t * \n\t * jdk的'/'运算符，直接向下取整\n\t */\n\tpublic static long divide(long p, long q, RoundingMode mode) {\n\t\treturn LongMath.divide(p, q, mode);\n\t}\n\n\t/**\n\t * 平方\n\t * \n\t * @param k 平方次数, 不能为负数, k=0时返回1.\n\t */\n\tpublic static int pow(int b, int k) {\n\t\treturn IntMath.pow(b, k);\n\t}\n\n\t/**\n\t * 平方\n\t * \n\t * @param k 平方次数,不能为负数, k=0时返回1.\n\t */\n\tpublic static long pow(long b, int k) {\n\t\treturn LongMath.pow(b, k);\n\t}\n\n\t/**\n\t * 开方\n\t */\n\tpublic static int sqrt(int x, RoundingMode mode) {\n\t\treturn IntMath.sqrt(x, mode);\n\t}\n\n\t/**\n\t * 开方\n\t */\n\tpublic static long sqrt(long x, RoundingMode mode) {\n\t\treturn LongMath.sqrt(x, mode);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/number/MoneyUtil.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.text.DecimalFormat;\nimport java.text.ParseException;\n\nimport org.apache.commons.lang3.StringUtils;\n\n/**\n * 货币工具类.\n * \n * 1.元和分之间的转换\n * 2.货币格式化成字符串\n * 3.字符串转换成货币\n *\n */\npublic class MoneyUtil {\n\n\tprivate static final ThreadLocal<DecimalFormat> DEFAULT_FORMAT = createThreadLocalNumberformat(\"0.00\");\n\n\tprivate static final ThreadLocal<DecimalFormat> PRETTY_FORMAT = createThreadLocalNumberformat(\"#,##0.00\");\n\n\t// ThreadLocal重用MessageDigest\n\tprivate static ThreadLocal<DecimalFormat> createThreadLocalNumberformat(final String pattern) {\n\t\treturn new ThreadLocal<DecimalFormat>() {\n\t\t\t@Override\n\t\t\tprotected DecimalFormat initialValue() {\n\t\t\t\tDecimalFormat df = (DecimalFormat) DecimalFormat.getInstance();\n\t\t\t\tdf.applyPattern(pattern);\n\t\t\t\treturn df;\n\n\t\t\t}\n\t\t};\n\t}\n\n\n\t/////////////////// 元和分的转换 ///////////////////\n\n\t/**\n\t * 人民币金额单位转换，分转换成元，取两位小数 例如：150 => 1.5\n\t */\n\tpublic static BigDecimal fen2yuan(BigDecimal num) {\n\t\treturn num.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);\n\t}\n\n\t/**\n\t * 人民币金额单位转换，分转换成元，取两位小数 例如：150 => 1.5\n\t */\n\tpublic static BigDecimal fen2yuan(long num) {\n\t\treturn fen2yuan(new BigDecimal(num));\n\t}\n\n\t/**\n\t * 人民币金额单位转换，分转换成元，取两位小数 例如：150 => 1.5\n\t */\n\tpublic static BigDecimal fen2yuan(String num) {\n\t\tif (StringUtils.isEmpty(num)) {\n\t\t\treturn new BigDecimal(0);\n\t\t}\n\t\treturn fen2yuan(new BigDecimal(num));\n\t}\n\n\t/**\n\t * 人民币金额单位转换，元转换成分，例如：1 => 100\n\t */\n\tpublic static BigDecimal yuan2fen(String y) {\n\t\treturn new BigDecimal(Math.round(new BigDecimal(y).multiply(new BigDecimal(100)).doubleValue()));\n\t}\n\n\t/**\n\t * 人民币金额单位转换，元转换成分，例如：1 => 100\n\t */\n\tpublic static BigDecimal yuan2fen(double y) {\n\t\treturn yuan2fen(String.valueOf(y));\n\t}\n\n\t/**\n\t * 人民币金额单位转换，元转换成分，例如：1 => 100\n\t */\n\tpublic static BigDecimal yuan2fen(BigDecimal y) {\n\t\tif (y != null) {\n\t\t\treturn yuan2fen(y.toString());\n\t\t} else {\n\t\t\treturn new BigDecimal(0);\n\t\t}\n\t}\n\n\t////////////////// 格式化输出 //////////////////\n\t/**\n\t * 格式化金额，例如：1=>1.00\n\t */\n\tpublic static String format(BigDecimal number) {\n\t\treturn format(number.doubleValue());\n\t}\n\n\t/**\n\t * 格式化金额，默认格式：00.0 ,例如：1=>1.00\n\t */\n\tpublic static String format(double number) {\n\t\treturn DEFAULT_FORMAT.get().format(number);\n\t}\n\n\t/**\n\t * 格式化金额，默认格式：#,##0.00 ,例如：33999999932.3333d 输出：33,999,999,932.33\n\t */\n\tpublic static String prettyFormat(BigDecimal number) {\n\t\treturn prettyFormat(number.doubleValue());\n\t}\n\n\t/**\n\t * 格式化金额，默认格式：#,##0.00 ,例如：33999999932.3333d 输出：33,999,999,932.33\n\t */\n\tpublic static String prettyFormat(double number) {\n\t\treturn PRETTY_FORMAT.get().format(number);\n\t}\n\n\t/**\n\t * 格式化金额，当pattern为空时，pattern默认为#,##0.00\n\t */\n\tpublic static String format(BigDecimal number, String pattern) {\n\t\treturn format(number.doubleValue(), pattern);\n\t}\n\n\t/**\n\t * 格式化金额，当pattern为空时，pattern默认为#,##0.00\n\t */\n\tpublic static String format(double number, String pattern) {\n\t\tDecimalFormat df = null;\n\t\tif (StringUtils.isEmpty(pattern)) {\n\t\t\tdf = PRETTY_FORMAT.get();\n\t\t} else {\n\t\t\tdf = (DecimalFormat) DecimalFormat.getInstance();\n\t\t\tdf.applyPattern(pattern);\n\t\t}\n\n\t\treturn df.format(number);\n\t}\n\n\t/////////////// 转换金额字符串为金额//////////\n\n\t/**\n\t * 分析格式为0.00格式的字符串\n\t */\n\tpublic static BigDecimal parseString(String numberStr) throws ParseException {\n\t\treturn new BigDecimal(DEFAULT_FORMAT.get().parse(numberStr).doubleValue());\n\t}\n\n\t/**\n\t * 分析格式为#,##0.00格式的字符串\n\t */\n\tpublic static BigDecimal parsePrettyString(String numberStr) throws ParseException {\n\t\treturn new BigDecimal(PRETTY_FORMAT.get().parse(numberStr).doubleValue());\n\t}\n\n\t/**\n\t * 按格式分析字符串，当pattern为空时，pattern默认为#,##0.00\n\t */\n\tpublic static BigDecimal parseString(String numberStr, String pattern) throws ParseException {\n\t\tDecimalFormat df = null;\n\t\tif (StringUtils.isEmpty(pattern)) {\n\t\t\tdf = PRETTY_FORMAT.get();\n\t\t} else {\n\t\t\tdf = (DecimalFormat) DecimalFormat.getInstance();\n\t\t\tdf.applyPattern(pattern);\n\t\t}\n\n\t\treturn new BigDecimal(df.parse(numberStr).doubleValue());\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/number/NumberUtil.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\nimport java.util.Locale;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\n\nimport com.google.common.primitives.Ints;\nimport com.google.common.primitives.Longs;\nimport com.vip.vjtools.vjkit.base.annotation.NotNull;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 数字的工具类.\n * \n * 1.原始类型数字与byte[]的双向转换(via Guava)\n * \n * 2.判断字符串是否数字, 是否16进制字符串(via Common Lang)\n * \n * 3.10机制/16进制字符串 与 原始类型数字/数字对象 的双向转换(参考Common Lang自写)\n */\npublic class NumberUtil {\n\n\tprivate static final double DEFAULT_DOUBLE_EPSILON = 0.00001d;\n\n\t/**\n\t * 因为double的精度问题, 允许两个double在0.00001内的误差为相等。\n\t */\n\tpublic static boolean equalsWithin(double d1, double d2) {\n\t\treturn Math.abs(d1 - d2) < DEFAULT_DOUBLE_EPSILON;\n\t}\n\n\t/**\n\t * 因为double的精度问题, 允许两个double在epsilon内的误差为相等\n\t */\n\tpublic static boolean equalsWithin(double d1, double d2, double epsilon) {\n\t\treturn Math.abs(d1 - d2) < epsilon;\n\t}\n\n\t///////////// bytes[] 与原始类型数字转换 ///////\n\n\tpublic static byte[] toBytes(int value) {\n\t\treturn Ints.toByteArray(value);\n\t}\n\n\tpublic static byte[] toBytes(long value) {\n\t\treturn Longs.toByteArray(value);\n\t}\n\n\t/**\n\t * copy from ElasticSearch Numbers\n\t */\n\tpublic static byte[] toBytes(double val) {\n\t\treturn toBytes(Double.doubleToRawLongBits(val));\n\t}\n\n\tpublic static int toInt(byte[] bytes) {\n\t\treturn Ints.fromByteArray(bytes);\n\t}\n\n\tpublic static long toLong(byte[] bytes) {\n\t\treturn Longs.fromByteArray(bytes);\n\t}\n\n\t/**\n\t * copy from ElasticSearch Numbers\n\t */\n\tpublic static double toDouble(byte[] bytes) {\n\t\treturn Double.longBitsToDouble(toLong(bytes));\n\t}\n\n\t/////// 判断字符串类型//////////\n\t/**\n\t * 判断字符串是否合法数字\n\t */\n\tpublic static boolean isNumber(@Nullable String str) {\n\t\treturn NumberUtils.isCreatable(str);\n\t}\n\n\t/**\n\t * 判断字符串是否16进制\n\t */\n\tpublic static boolean isHexNumber(@Nullable String value) {\n\t\tif (StringUtils.isEmpty(value)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tint index = value.startsWith(\"-\") ? 1 : 0;\n\t\treturn value.startsWith(\"0x\", index) || value.startsWith(\"0X\", index) || value.startsWith(\"#\", index);\n\t}\n\n\t/////////// 将字符串转化为原始类型数字/////////\n\n\t/**\n\t * 将10进制的String转化为int.\n\t * \n\t * 当str为空或非数字字符串时抛NumberFormatException\n\t */\n\tpublic static int toInt(@NotNull String str) {\n\t\treturn Integer.parseInt(str);\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为int.\n\t * \n\t * 当str为空或非数字字符串时，返回default值.\n\t */\n\tpublic static int toInt(@Nullable String str, int defaultValue) {\n\t\treturn NumberUtils.toInt(str, defaultValue);\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为long.\n\t * \n\t * 当str或非数字字符串时抛NumberFormatException\n\t */\n\tpublic static long toLong(@NotNull String str) {\n\t\treturn Long.parseLong(str);\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为long.\n\t * \n\t * 当str为空或非数字字符串时，返回default值\n\t */\n\tpublic static long toLong(@Nullable String str, long defaultValue) {\n\t\treturn NumberUtils.toLong(str, defaultValue);\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为double.\n\t * \n\t * 当str为空或非数字字符串时抛NumberFormatException\n\t */\n\tpublic static double toDouble(@NotNull String str) {\n\t\t// 统一行为，不要有时候抛NPE，有时候抛NumberFormatException\n\t\tif (str == null) {\n\t\t\tthrow new NumberFormatException(\"null\");\n\t\t}\n\t\treturn Double.parseDouble(str);\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为double.\n\t * \n\t * 当str为空或非数字字符串时，返回default值\n\t */\n\tpublic static double toDouble(@Nullable String str, double defaultValue) {\n\t\treturn NumberUtils.toDouble(str, defaultValue);\n\t}\n\n\t////////////// 10进制字符串 转换对象类型数字/////////////\n\t/**\n\t * 将10进制的String安全的转化为Integer.\n\t * \n\t * 当str为空或非数字字符串时抛NumberFormatException\n\t */\n\tpublic static Integer toIntObject(@NotNull String str) {\n\t\treturn Integer.valueOf(str);\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为Integer.\n\t * \n\t * 当str为空或非数字字符串时，返回default值\n\t */\n\tpublic static Integer toIntObject(@Nullable String str, Integer defaultValue) {\n\t\tif (StringUtils.isEmpty(str)) {\n\t\t\treturn defaultValue;\n\t\t}\n\t\ttry {\n\t\t\treturn Integer.valueOf(str);\n\t\t} catch (final NumberFormatException nfe) {\n\t\t\treturn defaultValue;\n\t\t}\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为Long.\n\t * \n\t * 当str为空或非数字字符串时抛NumberFormatException\n\t */\n\tpublic static Long toLongObject(@NotNull String str) {\n\t\treturn Long.valueOf(str);\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为Long.\n\t * \n\t * 当str为空或非数字字符串时，返回default值\n\t */\n\tpublic static Long toLongObject(@Nullable String str, Long defaultValue) {\n\t\tif (StringUtils.isEmpty(str)) {\n\t\t\treturn defaultValue;\n\t\t}\n\t\ttry {\n\t\t\treturn Long.valueOf(str);\n\t\t} catch (final NumberFormatException nfe) {\n\t\t\treturn defaultValue;\n\t\t}\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为Double.\n\t * \n\t * 当str为空或非数字字符串时抛NumberFormatException\n\t */\n\tpublic static Double toDoubleObject(@NotNull String str) {\n\t\t// 统一行为，不要有时候抛NPE，有时候抛NumberFormatException\n\t\tif (str == null) {\n\t\t\tthrow new NumberFormatException(\"null\");\n\t\t}\n\t\treturn Double.valueOf(str);\n\t}\n\n\t/**\n\t * 将10进制的String安全的转化为Long.\n\t * \n\t * 当str为空或非数字字符串时，返回default值\n\t */\n\tpublic static Double toDoubleObject(@Nullable String str, Double defaultValue) {\n\t\tif (StringUtils.isEmpty(str)) {\n\t\t\treturn defaultValue;\n\t\t}\n\t\ttry {\n\t\t\treturn Double.valueOf(str);\n\t\t} catch (final NumberFormatException nfe) {\n\t\t\treturn defaultValue;\n\t\t}\n\t}\n\n\t//////////// 16进制 字符串转换为数字对象//////////\n\n\t/**\n\t * 将16进制的String转化为Integer.\n\t * \n\t * 当str为空或非数字字符串时抛NumberFormatException\n\t */\n\tpublic static Integer hexToIntObject(@NotNull String str) {\n\t\t// 统一行为，不要有时候抛NPE，有时候抛NumberFormatException\n\t\tif (str == null) {\n\t\t\tthrow new NumberFormatException(\"null\");\n\t\t}\n\t\treturn Integer.decode(str);\n\t}\n\n\t/**\n\t * 将16进制的String转化为Integer，出错时返回默认值.\n\t */\n\tpublic static Integer hexToIntObject(@Nullable String str, Integer defaultValue) {\n\t\tif (StringUtils.isEmpty(str)) {\n\t\t\treturn defaultValue;\n\t\t}\n\t\ttry {\n\t\t\treturn Integer.decode(str);\n\t\t} catch (final NumberFormatException nfe) {\n\t\t\treturn defaultValue;\n\t\t}\n\t}\n\n\t/**\n\t * 将16进制的String转化为Long\n\t * \n\t * 当str为空或非数字字符串时抛NumberFormatException\n\t */\n\tpublic static Long hexToLongObject(@NotNull String str) {\n\t\t// 统一行为，不要有时候抛NPE，有时候抛NumberFormatException\n\t\tif (str == null) {\n\t\t\tthrow new NumberFormatException(\"null\");\n\t\t}\n\t\treturn Long.decode(str);\n\t}\n\n\t/**\n\t * 将16进制的String转化为Long，出错时返回默认值.\n\t */\n\tpublic static Long hexToLongObject(@Nullable String str, Long defaultValue) {\n\t\tif (StringUtils.isEmpty(str)) {\n\t\t\treturn defaultValue;\n\t\t}\n\t\ttry {\n\t\t\treturn Long.decode(str);\n\t\t} catch (final NumberFormatException nfe) {\n\t\t\treturn defaultValue;\n\t\t}\n\t}\n\n\t/////// toString ///////\n\t// 定义了原子类型与对象类型的参数，保证不会用错函数会导致额外AutoBoxing转换//\n\n\tpublic static String toString(int i) {\n\t\treturn Integer.toString(i);\n\t}\n\n\tpublic static String toString(@NotNull Integer i) {\n\t\treturn i.toString();\n\t}\n\n\tpublic static String toString(long l) {\n\t\treturn Long.toString(l);\n\t}\n\n\tpublic static String toString(@NotNull Long l) {\n\t\treturn l.toString();\n\t}\n\n\tpublic static String toString(double d) {\n\t\treturn Double.toString(d);\n\t}\n\n\tpublic static String toString(@NotNull Double d) {\n\t\treturn d.toString();\n\t}\n\n\t/**\n\t * 输出格式化为小数后两位的double字符串\n\t */\n\tpublic static String to2DigitString(double d) {\n\t\treturn String.format(Locale.ROOT, \"%.2f\", d);\n\t}\n\n\t/////////// 杂项 ///////\n\n\t/**\n\t * 安全的将小于Integer.MAX的long转为int，否则抛出IllegalArgumentException异常\n\t */\n\tpublic static int toInt32(long x) {\n\t\tif ((int) x == x) {\n\t\t\treturn (int) x;\n\t\t}\n\t\tthrow new IllegalArgumentException(\"Int \" + x + \" out of range\");\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/number/RandomUtil.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\nimport java.security.NoSuchAlgorithmException;\nimport java.security.SecureRandom;\nimport java.util.Random;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.apache.commons.lang3.Validate;\n\nimport com.vip.vjtools.vjkit.base.MoreValidate;\n\n/**\n * 随机数工具集.\n * \n * 1. 获取无锁的ThreadLocalRandom, 性能较佳的SecureRandom\n * \n * 2. 保证没有负数陷阱，也能更精确设定范围的nextInt/nextLong/nextDouble\n *  (copy from Common Lang RandomUtils，但默认使用性能较优的ThreadLocalRandom，并可配置其他的Random)\n * \n * 3. 随机字符串 (via Common Lang RandomStringUtils)\n * \n * @author calvin\n */\npublic class RandomUtil {\n\n\t/////////////////// 获取Random实例//////////////\n\t/**\n\t * 返回无锁的ThreadLocalRandom\n\t */\n\tpublic static Random threadLocalRandom() {\n\t\treturn ThreadLocalRandom.current();\n\t}\n\n\n\t/**\n\t * SecureRandom使用性能更好的SHA1PRNG, Tomcat的sessionId生成也用此算法.\n\t * \n\t * 但JDK7中，需要在启动参数加入 -Djava.security=file:/dev/./urandom （中间那个点很重要）\n\t * \n\t * 详见：《SecureRandom的江湖偏方与真实效果》http://calvin1978.blogcn.com/articles/securerandom.html\n\t */\n\tpublic static SecureRandom secureRandom() {\n\t\ttry {\n\t\t\treturn SecureRandom.getInstance(\"SHA1PRNG\");\n\t\t} catch (NoSuchAlgorithmException e) {// NOSONAR\n\t\t\treturn new SecureRandom();\n\t\t}\n\t}\n\n\t////////////////// nextInt 相关/////////\n\t/**\n\t * 返回0到Intger.MAX_VALUE的随机Int, 使用ThreadLocalRandom.\n\t */\n\tpublic static int nextInt() {\n\t\treturn nextInt(ThreadLocalRandom.current());\n\t}\n\n\t/**\n\t * 返回0到Intger.MAX_VALUE的随机Int, 可传入ThreadLocalRandom或SecureRandom\n\t */\n\tpublic static int nextInt(Random random) {\n\t\tint n = random.nextInt();\n\t\tif (n == Integer.MIN_VALUE) {\n\t\t\tn = 0; // corner case\n\t\t} else {\n\t\t\tn = Math.abs(n);\n\t\t}\n\n\t\treturn n;\n\t}\n\n\t/**\n\t * 返回0到max的随机Int, 使用ThreadLocalRandom.\n\t */\n\tpublic static int nextInt(int max) {\n\t\treturn nextInt(ThreadLocalRandom.current(), max);\n\t}\n\n\t/**\n\t * 返回0到max的随机Int, 可传入SecureRandom或ThreadLocalRandom\n\t */\n\tpublic static int nextInt(Random random, int max) {\n\t\treturn random.nextInt(max);\n\t}\n\n\t/**\n\t * 返回min到max的随机Int, 使用ThreadLocalRandom.\n\t * \n\t * min必须大于0.\n\t */\n\tpublic static int nextInt(int min, int max) {\n\t\treturn nextInt(ThreadLocalRandom.current(), min, max);\n\t}\n\n\t/**\n\t * 返回min到max的随机Int,可传入SecureRandom或ThreadLocalRandom.\n\t * \n\t * min必须大于0.\n\t * \n\t * JDK本身不具有控制两端范围的nextInt，因此参考Commons Lang RandomUtils的实现, 不直接复用是因为要传入Random实例\n\t * \n\t * @see org.apache.commons.lang3.RandomUtils#nextInt(int, int)\n\t */\n\tpublic static int nextInt(Random random, int min, int max) {\n\t\tValidate.isTrue(max >= min, \"Start value must be smaller or equal to end value.\");\n\t\tMoreValidate.nonNegative(\"min\", min);\n\n\t\tif (min == max) {\n\t\t\treturn min;\n\t\t}\n\n\t\treturn min + random.nextInt(max - min);\n\t}\n\n\t////////////////// long 相关/////////\n\t/**\n\t * 返回0－Long.MAX_VALUE间的随机Long, 使用ThreadLocalRandom.\n\t */\n\tpublic static long nextLong() {\n\t\treturn nextLong(ThreadLocalRandom.current());\n\t}\n\n\t/**\n\t * 返回0－Long.MAX_VALUE间的随机Long, 可传入SecureRandom或ThreadLocalRandom\n\t */\n\tpublic static long nextLong(Random random) {\n\t\tlong n = random.nextLong();\n\t\tif (n == Long.MIN_VALUE) {\n\t\t\tn = 0; // corner case\n\t\t} else {\n\t\t\tn = Math.abs(n);\n\t\t}\n\t\treturn n;\n\t}\n\n\t/**\n\t * 返回0－max间的随机Long, 使用ThreadLocalRandom.\n\t */\n\tpublic static long nextLong(long max) {\n\t\treturn nextLong(ThreadLocalRandom.current(), 0, max);\n\t}\n\n\t/**\n\t * 返回0-max间的随机Long, 可传入SecureRandom或ThreadLocalRandom\n\t */\n\tpublic static long nextLong(Random random, long max) {\n\t\treturn nextLong(random, 0, max);\n\t}\n\n\t/**\n\t * 返回min－max间的随机Long, 使用ThreadLocalRandom.\n\t * \n\t * min必须大于0.\n\t */\n\tpublic static long nextLong(long min, long max) {\n\t\treturn nextLong(ThreadLocalRandom.current(), min, max);\n\t}\n\n\t/**\n\t * 返回min-max间的随机Long,可传入SecureRandom或ThreadLocalRandom.\n\t * \n\t * min必须大于0.\n\t * \n\t * JDK本身不具有控制两端范围的nextLong，因此参考Commons Lang RandomUtils的实现, 不直接复用是因为要传入Random实例\n\t *\n\t * @see org.apache.commons.lang3.RandomUtils#nextLong(long, long)\n\t */\n\tpublic static long nextLong(Random random, long min, long max) {\n\t\tValidate.isTrue(max >= min, \"Start value must be smaller or equal to end value.\");\n\t\tMoreValidate.nonNegative(\"min\", min);\n\n\t\tif (min == max) {\n\t\t\treturn min;\n\t\t}\n\n\t\treturn (long) (min + ((max - min) * random.nextDouble()));\n\t}\n\n\t///////// Double //////\n\t/**\n\t * 返回0-之间的double, 使用ThreadLocalRandom\n\t */\n\tpublic static double nextDouble() {\n\t\treturn nextDouble(ThreadLocalRandom.current(), 0, Double.MAX_VALUE);\n\t}\n\n\t/**\n\t * 返回0-Double.MAX之间的double\n\t */\n\tpublic static double nextDouble(Random random) {\n\t\treturn nextDouble(random, 0, Double.MAX_VALUE);\n\t}\n\n\t/**\n\t * 返回0-max之间的double, 使用ThreadLocalRandom\n\t * \n\t * 注意：与JDK默认返回0-1的行为不一致.\n\t */\n\tpublic static double nextDouble(double max) {\n\t\treturn nextDouble(ThreadLocalRandom.current(), 0, max);\n\t}\n\n\t/**\n\t * 返回0-max之间的double\n\t */\n\tpublic static double nextDouble(Random random, double max) {\n\t\treturn nextDouble(random, 0, max);\n\t}\n\n\t/**\n\t * 返回min-max之间的double,ThreadLocalRandom\n\t */\n\tpublic static double nextDouble(final double min, final double max) {\n\t\treturn nextDouble(ThreadLocalRandom.current(), min, max);\n\t}\n\n\t/**\n\t * 返回min-max之间的double\n\t */\n\tpublic static double nextDouble(Random random, final double min, final double max) {\n\t\tValidate.isTrue(max >= min, \"Start value must be smaller or equal to end value.\");\n\t\tMoreValidate.nonNegative(\"min\", min);\n\n\t\tif (Double.compare(min, max) == 0) {\n\t\t\treturn min;\n\t\t}\n\n\t\treturn min + ((max - min) * random.nextDouble());\n\t}\n\t//////////////////// String/////////\n\n\t/**\n\t * 随机字母或数字，固定长度\n\t */\n\tpublic static String randomStringFixLength(int length) {\n\t\treturn RandomStringUtils.random(length, 0, 0, true, true, null, threadLocalRandom());\n\t}\n\n\t/**\n\t * 随机字母或数字，固定长度\n\t */\n\tpublic static String randomStringFixLength(Random random, int length) {\n\t\treturn RandomStringUtils.random(length, 0, 0, true, true, null, random);\n\t}\n\n\t/**\n\t * 随机字母或数字，随机长度\n\t */\n\tpublic static String randomStringRandomLength(int minLength, int maxLength) {\n\t\treturn RandomStringUtils.random(nextInt(minLength, maxLength), 0, 0, true, true, null, threadLocalRandom());\n\t}\n\n\t/**\n\t * 随机字母或数字，随机长度\n\t */\n\tpublic static String randomStringRandomLength(Random random, int minLength, int maxLength) {\n\t\treturn RandomStringUtils.random(nextInt(random, minLength, maxLength), 0, 0, true, true, null, random);\n\t}\n\n\t/**\n\t * 随机字母，固定长度\n\t */\n\tpublic static String randomLetterFixLength(int length) {\n\t\treturn RandomStringUtils.random(length, 0, 0, true, false, null, threadLocalRandom());\n\t}\n\n\t/**\n\t * 随机字母，固定长度\n\t */\n\tpublic static String randomLetterFixLength(Random random, int length) {\n\t\treturn RandomStringUtils.random(length, 0, 0, true, false, null, random);\n\t}\n\n\t/**\n\t * 随机字母，随机长度\n\t */\n\tpublic static String randomLetterRandomLength(int minLength, int maxLength) {\n\t\treturn RandomStringUtils.random(nextInt(minLength, maxLength), 0, 0, true, false, null, threadLocalRandom());\n\t}\n\n\t/**\n\t * 随机字母，随机长度\n\t */\n\tpublic static String randomLetterRandomLength(Random random, int minLength, int maxLength) {\n\t\treturn RandomStringUtils.random(nextInt(random, minLength, maxLength), 0, 0, true, false, null, random);\n\t}\n\n\t/**\n\t * 随机ASCII字符(含字母，数字及其他符号)，固定长度\n\t */\n\tpublic static String randomAsciiFixLength(int length) {\n\t\treturn RandomStringUtils.random(length, 32, 127, false, false, null, threadLocalRandom());\n\t}\n\n\t/**\n\t * 随机ASCII字符(含字母，数字及其他符号)，固定长度\n\t */\n\tpublic static String randomAsciiFixLength(Random random, int length) {\n\t\treturn RandomStringUtils.random(length, 32, 127, false, false, null, random);\n\t}\n\n\t/**\n\t * 随机ASCII字符(含字母，数字及其他符号)，随机长度\n\t */\n\tpublic static String randomAsciiRandomLength(int minLength, int maxLength) {\n\t\treturn RandomStringUtils.random(nextInt(minLength, maxLength), 32, 127, false, false, null,\n\t\t\t\tthreadLocalRandom());\n\t}\n\n\t/**\n\t * 随机ASCII字符(含字母，数字及其他符号)，随机长度\n\t */\n\tpublic static String randomAsciiRandomLength(Random random, int minLength, int maxLength) {\n\t\treturn RandomStringUtils.random(nextInt(random, minLength, maxLength), 32, 127, false, false, null, random);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/number/SizeUnit.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\n/**\n * Representation of basic size units，just like TimeUnit.\n * \n * Usage example:\n * assertTrue(SizeUnit.BYTES.toMegaBytes(1024 * 1024) == 1.0);<br/>\n * assertTrue(SizeUnit.GIGABYTES.toBytes(1) == 1024.0 * 1024.0 * 1024.0);\n */\npublic enum SizeUnit {\n\t/** Smallest memory unit. */\n\tBYTES,\n\t/** \"One thousand\" (1024) bytes. */\n\tKILOBYTES,\n\t/** \"One million\" (1024x1024) bytes. */\n\tMEGABYTES,\n\t/** \"One billion\" (1024x1024x1024) bytes. */\n\tGIGABYTES;\n\t/** Number of bytes in a kilobyte. */\n\tprivate final double BYTES_PER_KILOBYTE = 1024.0;\n\t/** Number of kilobytes in a megabyte. */\n\tprivate final double KILOBYTES_PER_MEGABYTE = 1024.0;\n\t/** Number of megabytes per gigabyte. */\n\tprivate final double MEGABYTES_PER_GIGABYTE = 1024.0;\n\n\t/**\n\t * Returns the number of bytes corresponding to the provided input for a particular unit of memory.\n\t *\n\t * @param input Number of units of memory.\n\t * @return Number of bytes corresponding to the provided number of particular memory units.\n\t */\n\tpublic double toBytes(final long input) {\n\t\tdouble bytes;\n\t\tswitch (this) {\n\t\t\tcase BYTES:\n\t\t\t\tbytes = input;\n\t\t\t\tbreak;\n\t\t\tcase KILOBYTES:\n\t\t\t\tbytes = input * BYTES_PER_KILOBYTE;\n\t\t\t\tbreak;\n\t\t\tcase MEGABYTES:\n\t\t\t\tbytes = input * BYTES_PER_KILOBYTE * KILOBYTES_PER_MEGABYTE;\n\t\t\t\tbreak;\n\t\t\tcase GIGABYTES:\n\t\t\t\tbytes = input * BYTES_PER_KILOBYTE * KILOBYTES_PER_MEGABYTE * MEGABYTES_PER_GIGABYTE;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new RuntimeException(\"No value '\" + this + \"' recognized for enum MemoryUnit.\");\n\t\t}\n\t\treturn bytes;\n\t}\n\n\t/**\n\t * Returns the number of kilobytes corresponding to the provided input for a particular unit of memory.\n\t *\n\t * @param input Number of units of memory.\n\t * @return Number of kilobytes corresponding to the provided number of particular memory units.\n\t */\n\tpublic double toKiloBytes(final long input) {\n\t\tdouble kilobytes;\n\t\tswitch (this) {\n\t\t\tcase BYTES:\n\t\t\t\tkilobytes = input / BYTES_PER_KILOBYTE;\n\t\t\t\tbreak;\n\t\t\tcase KILOBYTES:\n\t\t\t\tkilobytes = input;\n\t\t\t\tbreak;\n\t\t\tcase MEGABYTES:\n\t\t\t\tkilobytes = input * KILOBYTES_PER_MEGABYTE;\n\t\t\t\tbreak;\n\t\t\tcase GIGABYTES:\n\t\t\t\tkilobytes = input * KILOBYTES_PER_MEGABYTE * MEGABYTES_PER_GIGABYTE;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new RuntimeException(\"No value '\" + this + \"' recognized for enum MemoryUnit.\");\n\t\t}\n\t\treturn kilobytes;\n\t}\n\n\t/**\n\t * Returns the number of megabytes corresponding to the provided input for a particular unit of memory.\n\t *\n\t * @param input Number of units of memory.\n\t * @return Number of megabytes corresponding to the provided number of particular memory units.\n\t */\n\tpublic double toMegaBytes(final long input) {\n\t\tdouble megabytes;\n\t\tswitch (this) {\n\t\t\tcase BYTES:\n\t\t\t\tmegabytes = input / BYTES_PER_KILOBYTE / KILOBYTES_PER_MEGABYTE;\n\t\t\t\tbreak;\n\t\t\tcase KILOBYTES:\n\t\t\t\tmegabytes = input / KILOBYTES_PER_MEGABYTE;\n\t\t\t\tbreak;\n\t\t\tcase MEGABYTES:\n\t\t\t\tmegabytes = input;\n\t\t\t\tbreak;\n\t\t\tcase GIGABYTES:\n\t\t\t\tmegabytes = input * MEGABYTES_PER_GIGABYTE;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new RuntimeException(\"No value '\" + this + \"' recognized for enum MemoryUnit.\");\n\t\t}\n\t\treturn megabytes;\n\t}\n\n\t/**\n\t * Returns the number of gigabytes corresponding to the provided input for a particular unit of memory.\n\t *\n\t * @param input Number of units of memory.\n\t * @return Number of gigabytes corresponding to the provided number of particular memory units.\n\t */\n\tpublic double toGigaBytes(final long input) {\n\t\tdouble gigabytes;\n\t\tswitch (this) {\n\t\t\tcase BYTES:\n\t\t\t\tgigabytes = input / BYTES_PER_KILOBYTE / KILOBYTES_PER_MEGABYTE / MEGABYTES_PER_GIGABYTE;\n\t\t\t\tbreak;\n\t\t\tcase KILOBYTES:\n\t\t\t\tgigabytes = input / KILOBYTES_PER_MEGABYTE / MEGABYTES_PER_GIGABYTE;\n\t\t\t\tbreak;\n\t\t\tcase MEGABYTES:\n\t\t\t\tgigabytes = input / MEGABYTES_PER_GIGABYTE;\n\t\t\t\tbreak;\n\t\t\tcase GIGABYTES:\n\t\t\t\tgigabytes = input;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new RuntimeException(\"No value '\" + this + \"' recognized for enum MemoryUnit.\");\n\t\t}\n\t\treturn gigabytes;\n\t}\n}"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/number/UnitConverter.java",
    "content": "/*\n * Copyright (C) 2012 Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.vip.vjtools.vjkit.number;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * 1.将带单位的时间，大小字符串转换为数字. copy from Facebook\n * https://github.com/facebook/jcommon/blob/master/config/src/main/java/com/facebook/config/ConfigUtil.java\n *\n * 2.将数字转为带单位的字符串\n */\npublic class UnitConverter {\n\n\tprivate static final long K = 1024L;\n\tprivate static final long M = K * 1024;\n\tprivate static final long G = M * 1024;\n\tprivate static final long T = G * 1024;\n\n\tprivate static final long MILLIS_PER_SECOND = 1000L;\n\tprivate static final long MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60;\n\tprivate static final long MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60;\n\tprivate static final long MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;\n\n\tprivate static final Pattern NUMBER_AND_UNIT = Pattern.compile(\"(\\\\d+)([a-zA-Z]+)?\");\n\n\t/**\n\t * 将带单位的时间字符串转化为毫秒数.\n\t * \n\t * 单位包括不分大小写的ms(毫秒),s(秒),m(分钟),h(小时),d(日),y(年)\n\t * \n\t * 不带任何单位的话，默认单位是毫秒\n\t */\n\tpublic static long toDurationMillis(String duration) {\n\t\tMatcher matcher = NUMBER_AND_UNIT.matcher(duration);\n\n\t\tif (!matcher.matches()) {\n\t\t\tthrow new IllegalArgumentException(\"malformed duration string: \" + duration);\n\t\t}\n\n\t\tlong number = Long.parseLong(matcher.group(1));\n\t\tString unitStr = matcher.group(2);\n\t\tif (unitStr == null) {\n\t\t\treturn number;\n\t\t}\n\n\t\tchar unit = unitStr.toLowerCase().charAt(0);\n\n\t\tswitch (unit) {\n\t\t\tcase 's':\n\t\t\t\treturn number * MILLIS_PER_SECOND;\n\t\t\tcase 'm':\n\t\t\t\t// if it's an m, could be 'minutes' or 'millis'. default minutes\n\t\t\t\tif (unitStr.length() >= 2 && unitStr.charAt(1) == 's') {\n\t\t\t\t\treturn number;\n\t\t\t\t}\n\n\t\t\t\treturn number * MILLIS_PER_MINUTE;\n\t\t\tcase 'h':\n\t\t\t\treturn number * MILLIS_PER_HOUR;\n\t\t\tcase 'd':\n\t\t\t\treturn number * MILLIS_PER_DAY;\n\t\t\tdefault:\n\t\t\t\tthrow new IllegalArgumentException(\"unknown time unit :\" + unit);\n\t\t}\n\t}\n\n\t/**\n\t * 将带单位的大小字符串转化为字节数.\n\t * \n\t * 单位包括不分大小写的b(b),k(kb),m(mb),g(gb),t(tb)\n\t * \n\t * 不带任何单位的话，默认单位是b\n\t */\n\tpublic static long toBytes(String size) {\n\t\tMatcher matcher = NUMBER_AND_UNIT.matcher(size);\n\n\t\tif (matcher.matches()) {\n\t\t\tlong number = Long.parseLong(matcher.group(1));\n\n\t\t\tString unitStr = matcher.group(2);\n\t\t\tif (unitStr != null) {\n\t\t\t\tchar unit = unitStr.toLowerCase().charAt(0);\n\n\t\t\t\tswitch (unit) {\n\t\t\t\t\tcase 'b':\n\t\t\t\t\t\treturn number;\n\t\t\t\t\tcase 'k':\n\t\t\t\t\t\treturn number * K;\n\t\t\t\t\tcase 'm':\n\t\t\t\t\t\treturn number * M;\n\t\t\t\t\tcase 'g':\n\t\t\t\t\t\treturn number * G;\n\t\t\t\t\tcase 't':\n\t\t\t\t\t\treturn number * T;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tthrow new IllegalArgumentException(\"unknown size unit :\" + unit);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn number;\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new IllegalArgumentException(\"malformed size string: \" + size);\n\t\t}\n\t}\n\n\t/**\n\t * 从bytes转换为带单位的字符串, 单位最大只支持到G级别，四舍五入\n\t * \n\t * @param scale 小数后的精度\n\t */\n\tpublic static String toSizeUnit(Long bytes, int scale) {\n\t\tif (bytes == null) {\n\t\t\treturn \"n/a\";\n\t\t}\n\t\tif (bytes < K) {\n\t\t\treturn String.format(\"%4d\", bytes);\n\t\t}\n\n\t\tif (bytes < M) {\n\t\t\treturn String.format(\"%\" + (scale == 0 ? 4 : 5 + scale) + '.' + scale + \"fk\", bytes * 1d / K);\n\t\t}\n\n\t\tif (bytes < G) {\n\t\t\treturn String.format(\"%\" + (scale == 0 ? 4 : 5 + scale) + '.' + scale + \"fm\", bytes * 1d / M);\n\t\t}\n\n\t\tif (bytes < T) {\n\t\t\treturn String.format(\"%\" + (scale == 0 ? 4 : 5 + scale) + '.' + scale + \"fg\", bytes * 1d / G);\n\t\t}\n\n\t\treturn String.format(\"%\" + (scale == 0 ? 4 : 5 + scale) + '.' + scale + \"ft\", bytes * 1d / T);\n\t}\n\n\t/**\n\t * 转换毫秒为带时间单位的字符串，单位最大到day级别，四舍五入\n\t * \n\t * @param scale 小数后的精度\n\t */\n\tpublic static String toTimeUnit(long millis, int scale) {\n\t\tif (millis < MILLIS_PER_SECOND) {\n\t\t\treturn String.format(\"%4dms\", millis);\n\t\t}\n\n\t\tif (millis < MILLIS_PER_MINUTE) {\n\t\t\treturn String.format(\"%\" + (scale == 0 ? 2 : 3 + scale) + '.' + scale + \"fs\",\n\t\t\t\t\tmillis * 1d / MILLIS_PER_SECOND);\n\t\t}\n\n\t\tif (millis < MILLIS_PER_HOUR) {\n\t\t\treturn String.format(\"%\" + (scale == 0 ? 2 : 3 + scale) + '.' + scale + \"fm\",\n\t\t\t\t\tmillis * 1d / MILLIS_PER_MINUTE);\n\t\t}\n\n\t\tif (millis < MILLIS_PER_DAY) {\n\t\t\treturn String.format(\"%\" + (scale == 0 ? 2 : 3 + scale) + '.' + scale + \"fh\",\n\t\t\t\t\tmillis * 1d / MILLIS_PER_HOUR);\n\t\t}\n\n\t\treturn String.format(\"%\" + (scale == 0 ? 2 : 3 + scale) + '.' + scale + \"fd\", millis * 1d / MILLIS_PER_DAY);\n\t}\n\n\t/**\n\t * 转换毫秒为带时间单位的字符串，会同时带下一级的单位，四舍五入\n\t */\n\tpublic static String toTimeWithMinorUnit(long millis) {\n\t\tif (millis < MILLIS_PER_SECOND) {\n\t\t\treturn String.format(\"%4dms\", millis);\n\t\t}\n\n\t\tif (millis < MILLIS_PER_MINUTE) {\n\t\t\treturn String.format(\"%02ds\", millis / MILLIS_PER_SECOND);\n\t\t}\n\n\t\tif (millis < MILLIS_PER_HOUR) {\n\t\t\treturn String.format(\"%02dm%02ds\", millis / MILLIS_PER_MINUTE, (millis / MILLIS_PER_SECOND) % 60);\n\t\t}\n\n\t\tif (millis < MILLIS_PER_DAY) {\n\t\t\treturn String.format(\"%02dh%02dm\", millis / MILLIS_PER_HOUR, (millis / MILLIS_PER_MINUTE) % 60);\n\t\t}\n\n\t\treturn String.format(\"%dd%02dh\", millis / MILLIS_PER_DAY, (millis / MILLIS_PER_HOUR) % 24);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/AnnotationUtil.java",
    "content": "package com.vip.vjtools.vjkit.reflect;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.apache.commons.lang3.ClassUtils;\n\n/**\n * Annotation的工具类\n * \n * 1.获得类的全部Annotation\n * \n * 2.获取类的标注了annotation的所有属性和方法\n */\npublic class AnnotationUtil {\n\n\t/**\n\t * 递归Class所有的Annotation，一个最彻底的实现.\n\t * \n\t * 包括所有基类，所有接口的Annotation，同时支持Spring风格的Annotation继承的父Annotation，\n\t */\n\tpublic static Set<Annotation> getAllAnnotations(final Class<?> cls) {\n\t\tList<Class<?>> allTypes = ClassUtil.getAllSuperclasses(cls);\n\t\tallTypes.addAll(ClassUtil.getAllInterfaces(cls));\n\t\tallTypes.add(cls);\n\n\t\tSet<Annotation> anns = new HashSet<Annotation>();\n\t\tfor (Class<?> type : allTypes) {\n\t\t\tanns.addAll(Arrays.asList(type.getDeclaredAnnotations()));\n\t\t}\n\n\t\tSet<Annotation> superAnnotations = new HashSet<Annotation>();\n\t\tfor (Annotation ann : anns) {\n\t\t\tgetSuperAnnotations(ann.annotationType(), superAnnotations);\n\t\t}\n\n\t\tanns.addAll(superAnnotations);\n\n\t\treturn anns;\n\t}\n\n\tprivate static <A extends Annotation> void getSuperAnnotations(Class<A> annotationType, Set<Annotation> visited) {\n\t\tAnnotation[] anns = annotationType.getDeclaredAnnotations();\n\n\t\tfor (Annotation ann : anns) {\n\t\t\tif (!ann.annotationType().getName().startsWith(\"java.lang\") && visited.add(ann)) {\n\t\t\t\tgetSuperAnnotations(ann.annotationType(), visited);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * 找出所有标注了该annotation的公共属性，循环遍历父类.\n\t * \n\t * 暂未支持Spring风格Annotation继承Annotation\n\t * \n\t * copy from org.unitils.util.AnnotationUtils\n\t */\n\tpublic static <T extends Annotation> Set<Field> getAnnotatedPublicFields(Class<? extends Object> clazz,\n\t\t\tClass<T> annotation) {\n\n\t\tif (Object.class.equals(clazz)) {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\tSet<Field> annotatedFields = new HashSet<Field>();\n\t\tField[] fields = clazz.getFields();\n\n\t\tfor (Field field : fields) {\n\t\t\tif (field.getAnnotation(annotation) != null) {\n\t\t\t\tannotatedFields.add(field);\n\t\t\t}\n\t\t}\n\n\t\treturn annotatedFields;\n\t}\n\n\t/**\n\t * 找出所有标注了该annotation的属性，循环遍历父类，包含private属性.\n\t * \n\t * 暂未支持Spring风格Annotation继承Annotation\n\t * \n\t * copy from org.unitils.util.AnnotationUtils\n\t */\n\tpublic static <T extends Annotation> Set<Field> getAnnotatedFields(Class<? extends Object> clazz,\n\t\t\tClass<T> annotation) {\n\t\tif (Object.class.equals(clazz)) {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\t\tSet<Field> annotatedFields = new HashSet<Field>();\n\t\tField[] fields = clazz.getDeclaredFields();\n\t\tfor (Field field : fields) {\n\t\t\tif (field.getAnnotation(annotation) != null) {\n\t\t\t\tannotatedFields.add(field);\n\t\t\t}\n\t\t}\n\t\tannotatedFields.addAll(getAnnotatedFields(clazz.getSuperclass(), annotation));\n\t\treturn annotatedFields;\n\t}\n\n\t/**\n\t * 找出所有标注了该annotation的公共方法(含父类的公共函数)，循环其接口.\n\t * \n\t * 暂未支持Spring风格Annotation继承Annotation\n\t * \n\t * 另，如果子类重载父类的公共函数，父类函数上的annotation不会继承，只有接口上的annotation会被继承.\n\t */\n\tpublic static <T extends Annotation> Set<Method> getAnnotatedPublicMethods(Class<?> clazz, Class<T> annotation) {\n\t\t// 已递归到Objebt.class, 停止递归\n\t\tif (Object.class.equals(clazz)) {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\tList<Class<?>> ifcs = ClassUtils.getAllInterfaces(clazz);\n\t\tSet<Method> annotatedMethods = new HashSet<Method>();\n\n\t\t// 遍历当前类的所有公共方法\n\t\tMethod[] methods = clazz.getMethods();\n\n\t\tfor (Method method : methods) {\n\t\t\t// 如果当前方法有标注，或定义了该方法的所有接口有标注\n\t\t\tif (method.getAnnotation(annotation) != null || searchOnInterfaces(method, annotation, ifcs)) {\n\t\t\t\tannotatedMethods.add(method);\n\t\t\t}\n\t\t}\n\n\t\treturn annotatedMethods;\n\t}\n\n\tprivate static <T extends Annotation> boolean searchOnInterfaces(Method method, Class<T> annotationType,\n\t\t\tList<Class<?>> ifcs) {\n\t\tfor (Class<?> iface : ifcs) {\n\t\t\ttry {\n\t\t\t\tMethod equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes());\n\t\t\t\tif (equivalentMethod.getAnnotation(annotationType) != null) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch (NoSuchMethodException ex) { // NOSONAR\n\t\t\t\t// Skip this interface - it doesn't have the method...\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/ClassLoaderUtil.java",
    "content": "package com.vip.vjtools.vjkit.reflect;\n\npublic class ClassLoaderUtil {\n\n\t/**\n\t * Copy from Spring, 按顺序获取默认ClassLoader\n\t * \n\t * 1. Thread.currentThread().getContextClassLoader()\n\t * \n\t * 2. ClassLoaderUtil的加载ClassLoader\n\t * \n\t * 3. SystemClassLoader\n\t */\n\tpublic static ClassLoader getDefaultClassLoader() {\n\t\tClassLoader cl = null;\n\t\ttry {\n\t\t\tcl = Thread.currentThread().getContextClassLoader();\n\t\t} catch (Throwable ex) { // NOSONAR\n\t\t\t// Cannot access thread context ClassLoader - falling back...\n\t\t}\n\t\tif (cl == null) {\n\t\t\t// No thread context class loader -> use class loader of this class.\n\t\t\tcl = ClassLoaderUtil.class.getClassLoader();\n\t\t\tif (cl == null) {\n\t\t\t\t// getClassLoader() returning null indicates the bootstrap ClassLoader\n\t\t\t\ttry {\n\t\t\t\t\tcl = ClassLoader.getSystemClassLoader();\n\t\t\t\t} catch (Throwable ex) { // NOSONAR\n\t\t\t\t\t// Cannot access system ClassLoader - oh well, maybe the caller can live with null...\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn cl;\n\t}\n\n\t/**\n\t * 探测类是否存在classpath中\n\t */\n\tpublic static boolean isPresent(String className, ClassLoader classLoader) {\n\t\ttry {\n\t\t\tclassLoader.loadClass(className);\n\t\t\treturn true;\n\t\t} catch (Throwable ex) { // NOSONAR\n\t\t\t// Class or one of its dependencies is not present...\n\t\t\treturn false;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/ClassUtil.java",
    "content": "package com.vip.vjtools.vjkit.reflect;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.List;\n\nimport org.apache.commons.lang3.ClassUtils;\nimport org.apache.commons.lang3.Validate;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * 获取Class信息的工具类\n * \n * 1. 获取类名，包名，循环向上的全部父类，全部接口\n * \n * 2. 其他便捷函数\n */\npublic class ClassUtil {\n\n\tprivate static final String CGLIB_CLASS_SEPARATOR = \"$$\";\n\n\tprivate static Logger logger = LoggerFactory.getLogger(ClassUtil.class);\n\n\t////// Short class and Package Name ////...\n\t/**\n\t * 返回短Class名, 不包含PackageName.\n\t * \n\t * 内部类的话，返回\"主类.内部类\"\n\t */\n\tpublic static String getShortClassName(final Class<?> cls) {\n\t\treturn ClassUtils.getShortClassName(cls);\n\t}\n\n\t/**\n\t * 返回Class名，不包含PackageName\n\t * \n\t * 内部类的话，返回\"主类.内部类\"\n\t */\n\tpublic static String getShortClassName(final String className) {\n\t\treturn ClassUtils.getShortClassName(className);\n\t}\n\n\t/**\n\t * 返回PackageName\n\t */\n\tpublic static String getPackageName(final Class<?> cls) {\n\t\treturn ClassUtils.getPackageName(cls);\n\t}\n\n\t/**\n\t * 返回PackageName\n\t */\n\tpublic static String getPackageName(final String className) {\n\t\treturn ClassUtils.getPackageName(className);\n\t}\n\n\t////////// 获取全部父类，全部接口//////////\n\t/**\n\t * 递归返回所有的SupperClasses，包含Object.class\n\t */\n\tpublic static List<Class<?>> getAllSuperclasses(final Class<?> cls) {\n\t\treturn ClassUtils.getAllSuperclasses(cls);\n\t}\n\n\t/**\n\t * 递归返回本类及所有基类继承的接口，及接口继承的接口，比Spring中的相同实现完整\n\t */\n\tpublic static List<Class<?>> getAllInterfaces(final Class<?> cls) {\n\t\treturn ClassUtils.getAllInterfaces(cls);\n\t}\n\n\t/////////// 杂项 /////////\n\n\t/**\n\t * https://github.com/linkedin/linkedin-utils/blob/master/org.linkedin.util-core/src/main/java/org/linkedin/util/reflect/ReflectUtils.java\n\t * \n\t * The purpose of this method is somewhat to provide a better naming / documentation than the javadoc of\n\t * <code>Class.isAssignableFrom</code> method.\n\t * \n\t * @return <code>true</code> if subclass is a subclass or sub interface of superclass\n\t */\n\tpublic static boolean isSubClassOrInterfaceOf(Class subclass, Class superclass) {\n\t\treturn superclass.isAssignableFrom(subclass);\n\t}\n\n\t/**\n\t * 获取CGLib处理过后的实体的原Class.\n\t */\n\tpublic static Class<?> unwrapCglib(Object instance) {\n\t\tValidate.notNull(instance, \"Instance must not be null\");\n\t\tClass<?> clazz = instance.getClass();\n\t\tif ((clazz != null) && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {\n\t\t\tClass<?> superClass = clazz.getSuperclass();\n\t\t\tif ((superClass != null) && !Object.class.equals(superClass)) {\n\t\t\t\treturn superClass;\n\t\t\t}\n\t\t}\n\t\treturn clazz;\n\t}\n\n\t/**\n\t * 通过反射, 获得Class定义中声明的泛型参数的类型,\n\t * \n\t * 注意泛型必须定义在父类处. 这是唯一可以通过反射从泛型获得Class实例的地方.\n\t * \n\t * 如无法找到, 返回Object.class.\n\t * \n\t * eg. public UserDao extends HibernateDao<User>\n\t * \n\t * @param clazz The class to introspect\n\t * @return the first generic declaration, or Object.class if cannot be determined\n\t */\n\tpublic static <T> Class<T> getClassGenericType(final Class clazz) {\n\t\treturn getClassGenericType(clazz, 0);\n\t}\n\n\t/**\n\t * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.\n\t * \n\t * 注意泛型必须定义在父类处. 这是唯一可以通过反射从泛型获得Class实例的地方.\n\t * \n\t * 如无法找到, 返回Object.class.\n\t * \n\t * 如public UserDao extends HibernateDao<User,Long>\n\t * \n\t * @param clazz clazz The class to introspect\n\t * @param index the Index of the generic declaration, start from 0.\n\t * @return the index generic declaration, or Object.class if cannot be determined\n\t */\n\tpublic static Class getClassGenericType(final Class clazz, final int index) {\n\n\t\tType genType = clazz.getGenericSuperclass();\n\n\t\tif (!(genType instanceof ParameterizedType)) {\n\t\t\tlogger.warn(clazz.getSimpleName() + \"'s superclass not ParameterizedType\");\n\t\t\treturn Object.class;\n\t\t}\n\n\t\tType[] params = ((ParameterizedType) genType).getActualTypeArguments();\n\n\t\tif ((index >= params.length) || (index < 0)) {\n\t\t\tlogger.warn(\"Index: \" + index + \", Size of \" + clazz.getSimpleName() + \"'s Parameterized Type: \"\n\t\t\t\t\t+ params.length);\n\t\t\treturn Object.class;\n\t\t}\n\t\tif (!(params[index] instanceof Class)) {\n\t\t\tlogger.warn(clazz.getSimpleName() + \" not set the actual class on superclass generic parameter\");\n\t\t\treturn Object.class;\n\t\t}\n\n\t\treturn (Class) params[index];\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/ReflectionUtil.java",
    "content": "package com.vip.vjtools.vjkit.reflect;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\n\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.ClassUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Validate;\nimport org.apache.commons.lang3.reflect.ConstructorUtils;\nimport org.apache.commons.lang3.reflect.MethodUtils;\n\nimport com.vip.vjtools.vjkit.base.ExceptionUtil;\nimport com.vip.vjtools.vjkit.base.ObjectUtil;\nimport com.vip.vjtools.vjkit.base.type.UncheckedException;\n\n/**\n * 反射工具类.\n * \n * 所有反射均无视modifier的范围限制，同时将反射的Checked异常转为UnChecked异常。\n * \n * 需要平衡性能较差的一次性调用，以及高性能的基于预先获取的Method/Filed对象反复调用两种用法\n * \n * 1. 获取方法与属性 (兼容了原始类型/接口/抽象类的参数, 并默认将方法与属性设为可访问)\n * \n * 2. 方法调用.\n * \n * 3. 构造函数.\n */\n@SuppressWarnings(\"unchecked\")\npublic class ReflectionUtil {\n\n\tprivate static final String SETTER_PREFIX = \"set\";\n\tprivate static final String GETTER_PREFIX = \"get\";\n\tprivate static final String IS_PREFIX = \"is\";\n\n\t///////// 获取方法对象 ////////\n\t/**\n\t * 循环遍历，按属性名获取前缀为set的函数，并设为可访问\n\t */\n\tpublic static Method getSetterMethod(Class<?> clazz, String propertyName, Class<?> parameterType) {\n\t\tString setterMethodName = SETTER_PREFIX + StringUtils.capitalize(propertyName);\n\t\treturn getMethod(clazz, setterMethodName, parameterType);\n\t}\n\n\t/**\n\t * 循环遍历，按属性名获取前缀为get或is的函数，并设为可访问\n\t */\n\tpublic static Method getGetterMethod(Class<?> clazz, String propertyName) {\n\t\tString getterMethodName = GETTER_PREFIX + StringUtils.capitalize(propertyName);\n\n\t\tMethod method = getMethod(clazz, getterMethodName);\n\n\t\t// retry on another name\n\t\tif (method == null) {\n\t\t\tgetterMethodName = IS_PREFIX + StringUtils.capitalize(propertyName);\n\t\t\tmethod = getMethod(clazz, getterMethodName);\n\t\t}\n\t\treturn method;\n\t}\n\n\t/**\n\t * 循环向上转型, 获取对象的DeclaredMethod, 并强制设置为可访问.\n\t * \n\t * 如向上转型到Object仍无法找到, 返回null.\n\t * \n\t * 匹配函数名+参数类型.\n\t * \n\t * 方法需要被多次调用时，先使用本函数先取得Method，然后调用Method.invoke(Object obj, Object... args)\n\t * \n\t * 因为getMethod() 不能获取父类的private函数, 因此采用循环向上的getDeclaredMethod();\n\t */\n\tpublic static Method getMethod(final Class<?> clazz, final String methodName, Class<?>... parameterTypes) {\n\t\tMethod method = MethodUtils.getMatchingMethod(clazz, methodName, parameterTypes);\n\t\tif (method != null) {\n\t\t\tmakeAccessible(method);\n\t\t}\n\t\treturn method;\n\t}\n\n\t/**\n\t * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.\n\t * \n\t * 如向上转型到Object仍无法找到, 返回null.\n\t * \n\t * 只匹配函数名, 如果有多个同名函数返回第一个\n\t * \n\t * 方法需要被多次调用时，先使用本函数先取得Method，然后调用Method.invoke(Object obj, Object... args)\n\t * \n\t * 因为getMethod() 不能获取父类的private函数, 因此采用循环向上的getDeclaredMethods()\n\t */\n\tpublic static Method getAccessibleMethodByName(final Class clazz, final String methodName) {\n\t\tValidate.notNull(clazz, \"clazz can't be null\");\n\t\tValidate.notEmpty(methodName, \"methodName can't be blank\");\n\n\t\tfor (Class<?> searchType = clazz; searchType != Object.class; searchType = searchType.getSuperclass()) {\n\t\t\tMethod[] methods = searchType.getDeclaredMethods();\n\t\t\tfor (Method method : methods) {\n\t\t\t\tif (method.getName().equals(methodName)) {\n\t\t\t\t\tmakeAccessible(method);\n\t\t\t\t\treturn method;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t//////////// 获取Field对象///////////\n\t/**\n\t * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.\n\t * \n\t * 如向上转型到Object仍无法找到, 返回null.\n\t * \n\t * 因为getFiled()不能获取父类的private属性, 因此采用循环向上的getDeclaredField();\n\t */\n\tpublic static Field getField(final Class clazz, final String fieldName) {\n\t\tValidate.notNull(clazz, \"clazz can't be null\");\n\t\tValidate.notEmpty(fieldName, \"fieldName can't be blank\");\n\t\tfor (Class<?> superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) {\n\t\t\ttry {\n\t\t\t\tField field = superClass.getDeclaredField(fieldName);\n\t\t\t\tmakeAccessible(field);\n\t\t\t\treturn field;\n\t\t\t} catch (NoSuchFieldException e) {// NOSONAR\n\t\t\t\t// Field不在当前类定义,继续向上转型\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t/////////// 获取或设置属性相关函数 ///////////\n\t/**\n\t * 调用Getter方法, 无视private/protected修饰符.\n\t * \n\t * 性能较差, 用于单次调用的场景\n\t */\n\tpublic static <T> T invokeGetter(Object obj, String propertyName) {\n\t\tMethod method = getGetterMethod(obj.getClass(), propertyName);\n\t\tif (method == null) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Could not find getter method [\" + propertyName + \"] on target [\" + obj + ']');\n\t\t}\n\t\treturn invokeMethod(obj, method);\n\t}\n\n\t/**\n\t * 调用Setter方法, 无视private/protected修饰符, 按传入value的类型匹配函数.\n\t * \n\t * 性能较差, 用于单次调用的场景\n\t */\n\tpublic static void invokeSetter(Object obj, String propertyName, Object value) {\n\t\tMethod method = getSetterMethod(obj.getClass(), propertyName, value.getClass());\n\t\tif (method == null) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Could not find getter method [\" + propertyName + \"] on target [\" + obj + ']');\n\t\t}\n\t\tinvokeMethod(obj, method, value);\n\t}\n\n\t/**\n\t * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.\n\t * \n\t * 性能较差, 用于单次调用的场景\n\t */\n\tpublic static <T> T getFieldValue(final Object obj, final String fieldName) {\n\t\tField field = getField(obj.getClass(), fieldName);\n\t\tif (field == null) {\n\t\t\tthrow new IllegalArgumentException(\"Could not find field [\" + fieldName + \"] on target [\" + obj + ']');\n\t\t}\n\t\treturn getFieldValue(obj, field);\n\t}\n\n\t/**\n\t * 使用已获取的Field, 直接读取对象属性值, 不经过getter函数.\n\t * \n\t * 用于反复调用的场景.\n\t */\n\tpublic static <T> T getFieldValue(final Object obj, final Field field) {\n\t\ttry {\n\t\t\treturn (T) field.get(obj);\n\t\t} catch (Exception e) {\n\t\t\tthrow convertReflectionExceptionToUnchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.\n\t * \n\t * 性能较差, 用于单次调用的场景\n\t */\n\tpublic static void setFieldValue(final Object obj, final String fieldName, final Object value) {\n\t\tField field = getField(obj.getClass(), fieldName);\n\t\tif (field == null) {\n\t\t\tthrow new IllegalArgumentException(\"Could not find field [\" + fieldName + \"] on target [\" + obj + ']');\n\t\t}\n\t\tsetField(obj, field, value);\n\t}\n\n\t/**\n\t * 使用预先获取的Field, 直接读取对象属性值, 不经过setter函数.\n\t * \n\t * 用于反复调用的场景.\n\t */\n\tpublic static void setField(final Object obj, Field field, final Object value) {\n\t\ttry {\n\t\t\tfield.set(obj, value);\n\t\t} catch (Exception e) {\n\t\t\tthrow convertReflectionExceptionToUnchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * 先尝试用Getter函数读取, 如果不存在则直接读取变量.\n\t * \n\t * 性能较差, 用于单次调用的场景\n\t */\n\tpublic static <T> T getProperty(Object obj, String propertyName) {\n\t\tMethod method = getGetterMethod(obj.getClass(), propertyName);\n\t\tif (method != null) {\n\t\t\treturn invokeMethod(obj, method);\n\t\t} else {\n\t\t\treturn getFieldValue(obj, propertyName);\n\t\t}\n\t}\n\n\t/**\n\t * 先尝试用Setter函数写入, 如果不存在则直接写入变量, 按传入value的类型匹配函数.\n\t * \n\t * 性能较差, 用于单次调用的场景\n\t */\n\tpublic static void setProperty(Object obj, String propertyName, final Object value) {\n\t\tMethod method = getSetterMethod(obj.getClass(), propertyName, value.getClass());\n\t\tif (method != null) {\n\t\t\tinvokeMethod(obj, method, value);\n\t\t} else {\n\t\t\tsetFieldValue(obj, propertyName, value);\n\t\t}\n\t}\n\n\t/////////// 方法相关函数 ////////////\n\t/**\n\t * 反射调用对象方法, 无视private/protected修饰符.\n\t * \n\t * 根据传入参数的实际类型进行匹配, 支持方法参数定义是接口，父类，原子类型等情况\n\t * \n\t * 性能较差，仅用于单次调用.\n\t */\n\tpublic static <T> T invokeMethod(Object obj, String methodName, Object... args) {\n\t\tObject[] theArgs = ArrayUtils.nullToEmpty(args);\n\t\tfinal Class<?>[] parameterTypes = ClassUtils.toClass(theArgs);\n\t\treturn invokeMethod(obj, methodName, theArgs, parameterTypes);\n\t}\n\n\t/**\n\t * 反射调用对象方法, 无视private/protected修饰符.\n\t * \n\t * 根据参数类型参数进行匹配, 支持方法参数定义是接口，父类，原子类型等情况\n\t * \n\t * 性能较低，仅用于单次调用.\n\t */\n\tpublic static <T> T invokeMethod(final Object obj, final String methodName, final Object[] args,\n\t\t\tfinal Class<?>[] parameterTypes) {\n\t\tMethod method = getMethod(obj.getClass(), methodName, parameterTypes);\n\t\tif (method == null) {\n\t\t\tthrow new IllegalArgumentException(\"Could not find method [\" + methodName + \"] with parameter types:\"\n\t\t\t\t\t+ ObjectUtil.toPrettyString(parameterTypes) + \" on class [\" + obj.getClass() + ']');\n\t\t}\n\t\treturn invokeMethod(obj, method, args);\n\t}\n\n\t/**\n\t * 反射调用对象方法, 无视private/protected修饰符\n\t * \n\t * 只匹配函数名，如果有多个同名函数调用第一个. 用于确信只有一个同名函数, 但参数类型不确定的情况.\n\t * \n\t * 性能较低，仅用于单次调用.\n\t */\n\tpublic static <T> T invokeMethodByName(final Object obj, final String methodName, final Object[] args) {\n\t\tMethod method = getAccessibleMethodByName(obj.getClass(), methodName);\n\t\tif (method == null) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Could not find method [\" + methodName + \"] on class [\" + obj.getClass() + ']');\n\t\t}\n\t\treturn invokeMethod(obj, method, args);\n\t}\n\n\t/**\n\t * 调用预先获取的Method，用于反复调用的场景\n\t */\n\tpublic static <T> T invokeMethod(final Object obj, Method method, Object... args) {\n\t\ttry {\n\t\t\treturn (T) method.invoke(obj, args);\n\t\t} catch (Exception e) {\n\t\t\tthrow ExceptionUtil.unwrapAndUnchecked(e);\n\t\t}\n\t}\n\n\t////////// 构造函数 ////////\n\t// TODO:更多函数的封装\n\t/**\n\t * 调用构造函数.\n\t */\n\tpublic static <T> T invokeConstructor(final Class<T> cls, Object... args) {\n\t\ttry {\n\t\t\treturn ConstructorUtils.invokeConstructor(cls, args);\n\t\t} catch (Exception e) {\n\t\t\tthrow ExceptionUtil.unwrapAndUnchecked(e);\n\t\t}\n\t}\n\n\t/////// 辅助函数 ////////\n\n\t/**\n\t * 改变private/protected的方法为可访问，尽量不进行改变，避免JDK的SecurityManager抱怨。\n\t */\n\tpublic static void makeAccessible(Method method) {\n\t\tif (!method.isAccessible() && (!Modifier.isPublic(method.getModifiers())\n\t\t\t\t|| !Modifier.isPublic(method.getDeclaringClass().getModifiers()))) {\n\t\t\tmethod.setAccessible(true);\n\t\t}\n\t}\n\n\t/**\n\t * 改变private/protected的成员变量为可访问，尽量不进行改变，避免JDK的SecurityManager抱怨。\n\t */\n\tpublic static void makeAccessible(Field field) {\n\t\tif (!field.isAccessible() && (!Modifier.isPublic(field.getModifiers())\n\t\t\t\t|| !Modifier.isPublic(field.getDeclaringClass().getModifiers())\n\t\t\t\t|| Modifier.isFinal(field.getModifiers()))) {\n\t\t\tfield.setAccessible(true);\n\t\t}\n\t}\n\n\t/**\n\t * 将反射时的checked exception转换为unchecked exception.\n\t */\n\tpublic static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {\n\t\tif ((e instanceof IllegalAccessException) || (e instanceof NoSuchMethodException)) {\n\t\t\treturn new IllegalArgumentException(e);\n\t\t} else if (e instanceof InvocationTargetException) {\n\t\t\treturn new RuntimeException(((InvocationTargetException) e).getTargetException());\n\t\t} else if (e instanceof RuntimeException) {\n\t\t\treturn (RuntimeException) e;\n\t\t}\n\t\treturn new UncheckedException(e);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/security/CryptoUtil.java",
    "content": "package com.vip.vjtools.vjkit.security;\n\nimport java.security.GeneralSecurityException;\nimport java.security.SecureRandom;\nimport java.util.Arrays;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.KeyGenerator;\nimport javax.crypto.Mac;\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.IvParameterSpec;\nimport javax.crypto.spec.SecretKeySpec;\n\nimport com.vip.vjtools.vjkit.base.ExceptionUtil;\nimport com.vip.vjtools.vjkit.number.RandomUtil;\nimport com.vip.vjtools.vjkit.text.Charsets;\n\n/**\n * 支持HMAC-SHA1消息签名 及 DES/AES对称加密的工具类.\n * \n * 支持Hex与Base64两种编码方式.\n */\npublic class CryptoUtil {\n\n\tprivate static final String AES_ALG = \"AES\";\n\tprivate static final String AES_CBC_ALG = \"AES/CBC/PKCS5Padding\";\n\tprivate static final String HMACSHA1_ALG = \"HmacSHA1\";\n\n\tprivate static final int DEFAULT_HMACSHA1_KEYSIZE = 160; // RFC2401\n\tprivate static final int DEFAULT_AES_KEYSIZE = 128;\n\tprivate static final int DEFAULT_IVSIZE = 16;\n\n\tprivate static SecureRandom random = RandomUtil.secureRandom();\n\n\t// -- HMAC-SHA1 funciton --//\n\t/**\n\t * 使用HMAC-SHA1进行消息签名, 返回字节数组,长度为20字节.\n\t * \n\t * @param input 原始输入字符数组\n\t * @param key HMAC-SHA1密钥\n\t */\n\tpublic static byte[] hmacSha1(byte[] input, byte[] key) {\n\t\ttry {\n\t\t\tSecretKey secretKey = new SecretKeySpec(key, HMACSHA1_ALG);\n\t\t\tMac mac = Mac.getInstance(HMACSHA1_ALG);\n\t\t\tmac.init(secretKey);\n\t\t\treturn mac.doFinal(input);\n\t\t} catch (GeneralSecurityException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * 校验HMAC-SHA1签名是否正确.\n\t * \n\t * @param expected 已存在的签名\n\t * @param input 原始输入字符串\n\t * @param key 密钥\n\t */\n\tpublic static boolean isMacValid(byte[] expected, byte[] input, byte[] key) {\n\t\tbyte[] actual = hmacSha1(input, key);\n\t\treturn Arrays.equals(expected, actual);\n\t}\n\n\t/**\n\t * 生成HMAC-SHA1密钥,返回字节数组,长度为160位(20字节). HMAC-SHA1算法对密钥无特殊要求, RFC2401建议最少长度为160位(20字节).\n\t */\n\tpublic static byte[] generateHmacSha1Key() {\n\t\ttry {\n\t\t\tKeyGenerator keyGenerator = KeyGenerator.getInstance(HMACSHA1_ALG);\n\t\t\tkeyGenerator.init(DEFAULT_HMACSHA1_KEYSIZE);\n\t\t\tSecretKey secretKey = keyGenerator.generateKey();\n\t\t\treturn secretKey.getEncoded();\n\t\t} catch (GeneralSecurityException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\t///////////// -- AES funciton --//////////\n\t/**\n\t * 使用AES加密原始字符串.\n\t * \n\t * @param input 原始输入字符数组\n\t * @param key 符合AES要求的密钥\n\t */\n\tpublic static byte[] aesEncrypt(byte[] input, byte[] key) {\n\t\treturn aes(input, key, Cipher.ENCRYPT_MODE);\n\t}\n\n\t/**\n\t * 使用AES加密原始字符串.\n\t * \n\t * @param input 原始输入字符数组\n\t * @param key 符合AES要求的密钥\n\t * @param iv 初始向量\n\t */\n\tpublic static byte[] aesEncrypt(byte[] input, byte[] key, byte[] iv) {\n\t\treturn aes(input, key, iv, Cipher.ENCRYPT_MODE);\n\t}\n\n\t/**\n\t * 使用AES解密字符串, 返回原始字符串.\n\t * \n\t * @param input Hex编码的加密字符串\n\t * @param key 符合AES要求的密钥\n\t */\n\tpublic static String aesDecrypt(byte[] input, byte[] key) {\n\t\tbyte[] decryptResult = aes(input, key, Cipher.DECRYPT_MODE);\n\t\treturn new String(decryptResult, Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 使用AES解密字符串, 返回原始字符串.\n\t * \n\t * @param input Hex编码的加密字符串\n\t * @param key 符合AES要求的密钥\n\t * @param iv 初始向量\n\t */\n\tpublic static String aesDecrypt(byte[] input, byte[] key, byte[] iv) {\n\t\tbyte[] decryptResult = aes(input, key, iv, Cipher.DECRYPT_MODE);\n\t\treturn new String(decryptResult, Charsets.UTF_8);\n\t}\n\n\t/**\n\t * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果.\n\t * \n\t * @param input 原始字节数组\n\t * @param key 符合AES要求的密钥\n\t * @param mode Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE\n\t */\n\tprivate static byte[] aes(byte[] input, byte[] key, int mode) {\n\t\ttry {\n\t\t\tSecretKey secretKey = new SecretKeySpec(key, AES_ALG);\n\t\t\tCipher cipher = Cipher.getInstance(AES_ALG);\n\t\t\tcipher.init(mode, secretKey);\n\t\t\treturn cipher.doFinal(input);\n\t\t} catch (GeneralSecurityException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果.\n\t * \n\t * @param input 原始字节数组\n\t * @param key 符合AES要求的密钥\n\t * @param iv 初始向量\n\t * @param mode Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE\n\t */\n\tprivate static byte[] aes(byte[] input, byte[] key, byte[] iv, int mode) {\n\t\ttry {\n\t\t\tSecretKey secretKey = new SecretKeySpec(key, AES_ALG);\n\t\t\tIvParameterSpec ivSpec = new IvParameterSpec(iv);\n\t\t\tCipher cipher = Cipher.getInstance(AES_CBC_ALG);\n\t\t\tcipher.init(mode, secretKey, ivSpec);\n\t\t\treturn cipher.doFinal(input);\n\t\t} catch (GeneralSecurityException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * 生成AES密钥,返回字节数组, 默认长度为128位(16字节).\n\t */\n\tpublic static byte[] generateAesKey() {\n\t\treturn generateAesKey(DEFAULT_AES_KEYSIZE);\n\t}\n\n\t/**\n\t * 生成AES密钥,可选长度为128,192,256位.\n\t */\n\tpublic static byte[] generateAesKey(int keysize) {\n\t\ttry {\n\t\t\tKeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALG);\n\t\t\tkeyGenerator.init(keysize);\n\t\t\tSecretKey secretKey = keyGenerator.generateKey();\n\t\t\treturn secretKey.getEncoded();\n\t\t} catch (GeneralSecurityException e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n\n\t/**\n\t * 生成随机向量,默认大小为cipher.getBlockSize(), 16字节.\n\t */\n\tpublic static byte[] generateIV() {\n\t\tbyte[] bytes = new byte[DEFAULT_IVSIZE];\n\t\trandom.nextBytes(bytes);\n\t\treturn bytes;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/text/Charsets.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * \n * 尽量使用Charsets.UTF8而不是\"UTF-8\"，减少JDK里的Charset查找消耗.\n * \n * 使用JDK7的StandardCharsets，同时留了标准名称的字符串\n * \n * @author calvin\n */\npublic class Charsets {\n\n\tpublic static final Charset UTF_8 = StandardCharsets.UTF_8;\n\tpublic static final Charset US_ASCII = StandardCharsets.US_ASCII;\n\tpublic static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;\n\n\tpublic static final String UTF_8_NAME = StandardCharsets.UTF_8.name();\n\tpublic static final String ASCII_NAME = StandardCharsets.US_ASCII.name();\n\tpublic static final String ISO_8859_1_NAME = StandardCharsets.ISO_8859_1.name();\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/text/CsvUtil.java",
    "content": "// Copyright (c) 2003-present, Jodd Team (http://jodd.org)\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n//\n// 1. Redistributions of source code must retain the above copyright notice,\n// this list of conditions and the following disclaimer.\n//\n// 2. Redistributions in binary form must reproduce the above copyright\n// notice, this list of conditions and the following disclaimer in the\n// documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n// POSSIBILITY OF SUCH DAMAGE.\n\npackage com.vip.vjtools.vjkit.text;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.apache.commons.lang3.StringUtils;\n\n/**\n * 从Jodd移植\n * \n * https://github.com/oblac/jodd/blob/master/jodd-core/src/main/java/jodd/util/CsvUtil.java\n * \n * Helps with CSV strings. See: http://en.wikipedia.org/wiki/Comma-separated_values\n */\npublic class CsvUtil {\n\n\tprotected static final char FIELD_SEPARATOR = ',';\n\tprotected static final char FIELD_QUOTE = '\"';\n\tprotected static final String DOUBLE_QUOTE = \"\\\"\\\"\";\n\tprotected static final String SPECIAL_CHARS = \"\\r\\n\";\n\tprotected static final String SPACE = \" \";\n\tprotected static final String QUOTE = \"\\\"\";\n\n\t/**\n\t * Parse fields as csv string,\n\t */\n\tpublic static String toCsvString(Object... elements) {\n\t\tStringBuilder line = new StringBuilder();\n\t\tint last = elements.length - 1;\n\t\tfor (int i = 0; i < elements.length; i++) {\n\t\t\tif (elements[i] == null) {\n\t\t\t\tif (i != last) {\n\t\t\t\t\tline.append(FIELD_SEPARATOR);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tString field = elements[i].toString();\n\n\t\t\t// check for special cases\n\t\t\tint ndx = field.indexOf(FIELD_SEPARATOR);\n\t\t\tif (ndx == -1) {\n\t\t\t\tndx = field.indexOf(FIELD_QUOTE);\n\t\t\t}\n\t\t\tif (ndx == -1 && (field.startsWith(SPACE) || field.endsWith(SPACE))) {\n\t\t\t\tndx = 1;\n\t\t\t}\n\t\t\tif (ndx == -1) {\n\t\t\t\tndx = StringUtils.indexOf(field, SPECIAL_CHARS);\n\t\t\t}\n\n\t\t\t// add field\n\t\t\tif (ndx != -1) {\n\t\t\t\tline.append(FIELD_QUOTE);\n\t\t\t}\n\t\t\tfield = StringUtils.replace(field, QUOTE, DOUBLE_QUOTE);\n\t\t\tline.append(field);\n\t\t\tif (ndx != -1) {\n\t\t\t\tline.append(FIELD_QUOTE);\n\t\t\t}\n\n\t\t\t// last\n\t\t\tif (i != last) {\n\t\t\t\tline.append(FIELD_SEPARATOR);\n\t\t\t}\n\t\t}\n\t\treturn line.toString();\n\t}\n\n\t/**\n\t * Converts CSV line to string array.\n\t */\n\tpublic static String[] fromCsvString(String line) {\n\t\tList<String> row = new ArrayList<String>();\n\n\t\tboolean inQuotedField = false;\n\t\tint fieldStart = 0;\n\n\t\tfinal int len = line.length();\n\t\tfor (int i = 0; i < len; i++) {\n\t\t\tchar c = line.charAt(i);\n\t\t\tif (c == FIELD_SEPARATOR) {\n\t\t\t\tif (!inQuotedField) { // ignore we are quoting\n\t\t\t\t\taddField(row, line, fieldStart, i, inQuotedField);\n\t\t\t\t\tfieldStart = i + 1;\n\t\t\t\t}\n\t\t\t} else if (c == FIELD_QUOTE) {\n\t\t\t\tif (inQuotedField) {\n\t\t\t\t\tif (i + 1 == len || line.charAt(i + 1) == FIELD_SEPARATOR) { // we are already quoting - peek to see\n\t\t\t\t\t\t// if this is the end of the field\n\t\t\t\t\t\taddField(row, line, fieldStart, i, inQuotedField);\n\t\t\t\t\t\tfieldStart = i + 2;\n\t\t\t\t\t\ti++; // and skip the comma\n\t\t\t\t\t\tinQuotedField = false;\n\t\t\t\t\t}\n\t\t\t\t} else if (fieldStart == i) {\n\t\t\t\t\tinQuotedField = true; // this is a beginning of a quote\n\t\t\t\t\tfieldStart++; // move field start\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// add last field - but only if string was not empty\n\t\tif (len > 0 && fieldStart <= len) {\n\t\t\taddField(row, line, fieldStart, len, inQuotedField);\n\t\t}\n\t\treturn row.toArray(new String[row.size()]);\n\t}\n\n\tprivate static void addField(List<String> row, String line, int startIndex, int endIndex, boolean inQuoted) {\n\t\tString field = line.substring(startIndex, endIndex);\n\t\tif (inQuoted) {\n\t\t\tfield = StringUtils.replace(field, DOUBLE_QUOTE, \"\\\"\");\n\t\t}\n\t\trow.add(field);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/text/EncodeUtil.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport com.google.common.io.BaseEncoding;\n\n/**\n * string/url -> hex/base64 编解码工具集(via guava BaseEncoding)\n */\npublic class EncodeUtil {\n\n\t/**\n\t * Hex编码, 将byte[]编码为String，默认为ABCDEF为大写字母.\n\t */\n\tpublic static String encodeHex(byte[] input) {\n\t\treturn BaseEncoding.base16().encode(input);\n\t}\n\n\t/**\n\t * Hex解码, 将String解码为byte[].\n\t * \n\t * 字符串有异常时抛出IllegalArgumentException.\n\t */\n\tpublic static byte[] decodeHex(CharSequence input) {\n\t\treturn BaseEncoding.base16().decode(input);\n\t}\n\n\t/**\n\t * Base64编码.\n\t */\n\tpublic static String encodeBase64(byte[] input) {\n\t\treturn BaseEncoding.base64().encode(input);\n\t}\n\n\t/**\n\t * Base64解码.\n\t * \n\t * 如果字符不合法，抛出IllegalArgumentException\n\t */\n\tpublic static byte[] decodeBase64(CharSequence input) {\n\t\treturn BaseEncoding.base64().decode(input);\n\t}\n\n\t/**\n\t * Base64编码, URL安全.(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548).\n\t */\n\tpublic static String encodeBase64UrlSafe(byte[] input) {\n\t\treturn BaseEncoding.base64Url().encode(input);\n\t}\n\n\t/**\n\t * Base64解码, URL安全(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548).\n\t * \n\t * 如果字符不合法，抛出IllegalArgumentException\n\t */\n\tpublic static byte[] decodeBase64UrlSafe(CharSequence input) {\n\t\treturn BaseEncoding.base64Url().decode(input);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/text/EscapeUtil.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\n\nimport org.apache.commons.lang3.StringEscapeUtils;\n\n/**\n * 转义工具集.\n * \n * 1.URL 转义，转义后的URL可作为URL中的参数 (via JDK)\n * \n * 2.xml/html 转义(via Commons-Lang StringEscapeUtils ,但已被废弃, 建议用Common-Text）\n * \n * 比如 \"bread\" & \"butter\" 转化为 &quot;bread&quot; &amp; &quot;butter&quot;\n */\npublic class EscapeUtil {\n\n\t/**\n\t * URL 编码, Encode默认为UTF-8.\n\t * \n\t * 转义后的URL可作为URL中的参数\n\t */\n\tpublic static String urlEncode(String part) {\n\t\ttry {\n\t\t\treturn URLEncoder.encode(part, Charsets.UTF_8_NAME);\n\t\t} catch (UnsupportedEncodingException ignored) { // NOSONAR\n\t\t\t// this exception is only for detecting and handling invalid inputs\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * URL 解码, Encode默认为UTF-8. 转义后的URL可作为URL中的参数\n\t */\n\tpublic static String urlDecode(String part) {\n\t\ttry {\n\t\t\treturn URLDecoder.decode(part, Charsets.UTF_8_NAME);\n\t\t} catch (UnsupportedEncodingException e) { // NOSONAR\n\t\t\t// this exception is only for detecting and handling invalid inputs\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Xml转码，将字符串转码为符合XML1.1格式的字符串.\n\t * \n\t * 比如 \"bread\" & \"butter\" 转化为 &quot;bread&quot; &amp; &quot;butter&quot;\n\t */\n\tpublic static String escapeXml(String xml) {\n\t\treturn StringEscapeUtils.escapeXml11(xml);\n\t}\n\n\t/**\n\t * Xml转码，XML格式的字符串解码为普通字符串.\n\t * \n\t * 比如 &quot;bread&quot; &amp; &quot;butter&quot; 转化为\"bread\" & \"butter\"\n\t */\n\tpublic static String unescapeXml(String xml) {\n\t\treturn StringEscapeUtils.unescapeXml(xml);\n\t}\n\n\t/**\n\t * Html转码，将字符串转码为符合HTML4格式的字符串.\n\t * \n\t * 比如 \"bread\" & \"butter\" 转化为 &quot;bread&quot; &amp; &quot;butter&quot;\n\t */\n\tpublic static String escapeHtml(String html) {\n\t\treturn StringEscapeUtils.escapeHtml4(html);\n\t}\n\n\t/**\n\t * Html解码，将HTML4格式的字符串转码解码为普通字符串.\n\t * \n\t * 比如 &quot;bread&quot; &amp; &quot;butter&quot;转化为\"bread\" & \"butter\"\n\t */\n\tpublic static String unescapeHtml(String html) {\n\t\treturn StringEscapeUtils.unescapeHtml4(html);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/text/HashUtil.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.SecureRandom;\nimport java.util.zip.CRC32;\n\nimport org.apache.commons.lang3.Validate;\n\nimport com.google.common.hash.Hashing;\nimport com.vip.vjtools.vjkit.base.annotation.NotNull;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 封装各种Hash算法的工具类\n * \n * 1.SHA-1, 安全性较高, 返回byte[](可用Encodes进一步被编码为Hex, Base64)\n * \n * 性能优化，使用ThreadLocal的MessageDigest(from ElasticSearch)\n * \n * 支持带salt并且进行迭代达到更高的安全性.\n * \n * MD5的安全性较低, 只在文件Checksum时支持.\n * \n * 2.crc32, murmur32这些不追求安全性, 性能较高, 返回int.\n * \n * 其中crc32基于JDK, murmurhash基于guava\n */\npublic class HashUtil {\n\n\tpublic static final int MURMUR_SEED = 1_318_007_700;\n\n\tprivate static final ThreadLocal<MessageDigest> MD5_DIGEST = createThreadLocalMessageDigest(\"MD5\");\n\tprivate static final ThreadLocal<MessageDigest> SHA_1_DIGEST = createThreadLocalMessageDigest(\"SHA-1\");\n\n\tprivate static SecureRandom random = new SecureRandom();\n\n\t// ThreadLocal重用MessageDigest\n\tprivate static ThreadLocal<MessageDigest> createThreadLocalMessageDigest(final String digest) {\n\t\treturn new ThreadLocal<MessageDigest>() {\n\t\t\t@Override\n\t\t\tprotected MessageDigest initialValue() {\n\t\t\t\ttry {\n\t\t\t\t\treturn MessageDigest.getInstance(digest);\n\t\t\t\t} catch (NoSuchAlgorithmException e) {\n\t\t\t\t\tthrow new RuntimeException(\n\t\t\t\t\t\t\t\"unexpected exception creating MessageDigest instance for [\" + digest + ']', e);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\t////////////////// SHA1 ///////////////////\n\t/**\n\t * 对输入字符串进行sha1散列.\n\t */\n\tpublic static byte[] sha1(@NotNull byte[] input) {\n\t\treturn digest(input, get(SHA_1_DIGEST), null, 1);\n\t}\n\n\t/**\n\t * 对输入字符串进行sha1散列, 编码默认为UTF8.\n\t */\n\tpublic static byte[] sha1(@NotNull String input) {\n\t\treturn digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), null, 1);\n\t}\n\n\t/**\n\t * 对输入字符串进行sha1散列，带salt达到更高的安全性.\n\t */\n\tpublic static byte[] sha1(@NotNull byte[] input, @Nullable byte[] salt) {\n\t\treturn digest(input, get(SHA_1_DIGEST), salt, 1);\n\t}\n\n\t/**\n\t * 对输入字符串进行sha1散列，带salt达到更高的安全性.\n\t */\n\tpublic static byte[] sha1(@NotNull String input, @Nullable byte[] salt) {\n\t\treturn digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), salt, 1);\n\t}\n\n\t/**\n\t * 对输入字符串进行sha1散列，带salt而且迭代达到更高更高的安全性.\n\t * \n\t * @see #generateSalt(int)\n\t */\n\tpublic static byte[] sha1(@NotNull byte[] input, @Nullable byte[] salt, int iterations) {\n\t\treturn digest(input, get(SHA_1_DIGEST), salt, iterations);\n\t}\n\n\t/**\n\t * 对输入字符串进行sha1散列，带salt而且迭代达到更高更高的安全性.\n\t * \n\t * @see #generateSalt(int)\n\t */\n\tpublic static byte[] sha1(@NotNull String input, @Nullable byte[] salt, int iterations) {\n\t\treturn digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), salt, iterations);\n\t}\n\n\tprivate static MessageDigest get(ThreadLocal<MessageDigest> messageDigest) {\n\t\tMessageDigest instance = messageDigest.get();\n\t\tinstance.reset();\n\t\treturn instance;\n\t}\n\n\t/**\n\t * 对字符串进行散列, 支持md5与sha1算法.\n\t */\n\tprivate static byte[] digest(@NotNull byte[] input, MessageDigest digest, byte[] salt, int iterations) {\n\t\t// 带盐\n\t\tif (salt != null) {\n\t\t\tdigest.update(salt);\n\t\t}\n\n\t\t// 第一次散列\n\t\tbyte[] result = digest.digest(input);\n\n\t\t// 如果迭代次数>1，进一步迭代散列\n\t\tfor (int i = 1; i < iterations; i++) {\n\t\t\tdigest.reset();\n\t\t\tresult = digest.digest(result);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * 用SecureRandom生成随机的byte[]作为salt.\n\t * \n\t * @param numBytes salt数组的大小\n\t */\n\tpublic static byte[] generateSalt(int numBytes) {\n\t\tValidate.isTrue(numBytes > 0, \"numBytes argument must be a positive integer (1 or larger)\", numBytes);\n\n\t\tbyte[] bytes = new byte[numBytes];\n\t\trandom.nextBytes(bytes);\n\t\treturn bytes;\n\t}\n\n\t/**\n\t * 对文件进行sha1散列.\n\t */\n\tpublic static byte[] sha1File(InputStream input) throws IOException {\n\t\treturn digestFile(input, get(SHA_1_DIGEST));\n\t}\n\n\t/**\n\t * 对文件进行md5散列，被破解后MD5已较少人用.\n\t */\n\tpublic static byte[] md5File(InputStream input) throws IOException {\n\t\treturn digestFile(input, get(MD5_DIGEST));\n\t}\n\n\tprivate static byte[] digestFile(InputStream input, MessageDigest messageDigest) throws IOException {\n\t\tint bufferLength = 8 * 1024;\n\t\tbyte[] buffer = new byte[bufferLength];\n\t\tint read = input.read(buffer, 0, bufferLength);\n\n\t\twhile (read > -1) {\n\t\t\tmessageDigest.update(buffer, 0, read);\n\t\t\tread = input.read(buffer, 0, bufferLength);\n\t\t}\n\n\t\treturn messageDigest.digest();\n\t}\n\n\t////////////////// 基于JDK的CRC32 ///////////////////\n\n\t/**\n\t * 对输入字符串进行crc32散列返回int, 返回值有可能是负数.\n\t * \n\t * Guava也有crc32实现, 但返回值无法返回long，所以统一使用JDK默认实现\n\t */\n\tpublic static int crc32AsInt(@NotNull String input) {\n\t\treturn crc32AsInt(input.getBytes(Charsets.UTF_8));\n\t}\n\n\t/**\n\t * 对输入字符串进行crc32散列返回int, 返回值有可能是负数.\n\t * \n\t * Guava也有crc32实现, 但返回值无法返回long，所以统一使用JDK默认实现\n\t */\n\tpublic static int crc32AsInt(@NotNull byte[] input) {\n\t\tCRC32 crc32 = new CRC32();\n\t\tcrc32.update(input);\n\t\t// CRC32 只是 32bit int，为了CheckSum接口强转成long，此处再次转回来\n\t\treturn (int) crc32.getValue();\n\t}\n\n\t/**\n\t * 对输入字符串进行crc32散列，与php兼容，在64bit系统下返回永远是正数的long\n\t * \n\t * Guava也有crc32实现, 但返回值无法返回long，所以统一使用JDK默认实现\n\t */\n\tpublic static long crc32AsLong(@NotNull String input) {\n\t\treturn crc32AsLong(input.getBytes(Charsets.UTF_8));\n\t}\n\n\t/**\n\t * 对输入字符串进行crc32散列，与php兼容，在64bit系统下返回永远是正数的long\n\t * \n\t * Guava也有crc32实现, 但返回值无法返回long，所以统一使用JDK默认实现\n\t */\n\tpublic static long crc32AsLong(@NotNull byte[] input) {\n\t\tCRC32 crc32 = new CRC32();\n\t\tcrc32.update(input);\n\t\treturn crc32.getValue();\n\t}\n\n\t////////////////// 基于Guava的MurMurHash ///////////////////\n\t/**\n\t * 对输入字符串进行murmur32散列, 返回值可能是负数\n\t */\n\tpublic static int murmur32AsInt(@NotNull byte[] input) {\n\t\treturn Hashing.murmur3_32(MURMUR_SEED).hashBytes(input).asInt();\n\t}\n\n\t/**\n\t * 对输入字符串进行murmur32散列, 返回值可能是负数\n\t */\n\tpublic static int murmur32AsInt(@NotNull String input) {\n\t\treturn Hashing.murmur3_32(MURMUR_SEED).hashString(input, Charsets.UTF_8).asInt();\n\t}\n\n\t/**\n\t * 对输入字符串进行murmur128散列, 返回值可能是负数\n\t */\n\tpublic static long murmur128AsLong(@NotNull byte[] input) {\n\t\treturn Hashing.murmur3_128(MURMUR_SEED).hashBytes(input).asLong();\n\t}\n\n\t/**\n\t * 对输入字符串进行murmur128散列, 返回值可能是负数\n\t */\n\tpublic static long murmur128AsLong(@NotNull String input) {\n\t\treturn Hashing.murmur3_128(MURMUR_SEED).hashString(input, Charsets.UTF_8).asLong();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/text/MoreStringUtil.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.google.common.base.CharMatcher;\nimport com.google.common.base.Splitter;\nimport com.google.common.base.Utf8;\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\nimport com.vip.vjtools.vjkit.collection.ListUtil;\n\n/**\n * 尽量使用Common Lang StringUtils, 基本覆盖了所有类库的StringUtils\n * \n * 本类仅补充少量额外方法, 尤其是针对char的运算\n * \n * 1. split char/chars\n * \n * 2. 针对char的replace first/last, startWith,endWith 等\n * \n * @author calvin\n */\npublic class MoreStringUtil {\n\n\t/////////// split char 相关 ////////\n\n\t/**\n\t * 高性能的Split，针对char的分隔符号，比JDK String自带的高效.\n\t * \n\t * copy from Commons Lang 3.5 StringUtils 并做优化\n\t * \n\t * @see #split(String, char, int)\n\t */\n\tpublic static List<String> split(@Nullable final String str, final char separatorChar) {\n\t\treturn split(str, separatorChar, 10);\n\t}\n\n\t/**\n\t * 高性能的Split，针对char的分隔符号，比JDK String自带的高效.\n\t * \n\t * copy from Commons Lang 3.5 StringUtils, 做如下优化:\n\t * \n\t * 1. 最后不做数组转换，直接返回List.\n\t * \n\t * 2. 可设定List初始大小.\n\t * \n\t * 3. preserveAllTokens 取默认值false\n\t * \n\t * @param expectParts 预估分割后的List大小，初始化数据更精准\n\t * \n\t * @return 如果为null返回null, 如果为\"\"返回空数组\n\t */\n\tpublic static List<String> split(@Nullable final String str, final char separatorChar, int expectParts) {\n\t\tif (str == null) {\n\t\t\treturn null;\n\t\t}\n\n\t\tfinal int len = str.length();\n\t\tif (len == 0) {\n\t\t\treturn ListUtil.emptyList();\n\t\t}\n\n\t\tfinal List<String> list = new ArrayList<String>(expectParts);\n\t\tint i = 0;\n\t\tint start = 0;\n\t\tboolean match = false;\n\t\twhile (i < len) {\n\t\t\tif (str.charAt(i) == separatorChar) {\n\t\t\t\tif (match) {\n\t\t\t\t\tlist.add(str.substring(start, i));\n\t\t\t\t\tmatch = false;\n\t\t\t\t}\n\t\t\t\tstart = ++i;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tmatch = true;\n\t\t\ti++;\n\t\t}\n\t\tif (match) {\n\t\t\tlist.add(str.substring(start, i));\n\t\t}\n\t\treturn list;\n\t}\n\n\t/**\n\t * 使用多个可选的char作为分割符, 还可以设置omitEmptyStrings,trimResults等配置\n\t * \n\t * 设置后的Splitter进行重用，不要每次创建\n\t * \n\t * @param separatorChars 比如Unix/Windows的路径分割符 \"/\\\\\"\n\t * \n\t * @see com.google.common.base.Splitter\n\t */\n\tpublic static Splitter charsSplitter(final String separatorChars) {\n\t\treturn Splitter.on(CharMatcher.anyOf(separatorChars));\n\t}\n\n\t////////// 其他 char 相关 ///////////\n\t/**\n\t * String 有replace(char,char)，但缺少单独replace first/last的\n\t */\n\tpublic static String replaceFirst(@Nullable String s, char sub, char with) {\n\t\tif (s == null) {\n\t\t\treturn null;\n\t\t}\n\t\tint index = s.indexOf(sub);\n\t\tif (index == -1) {\n\t\t\treturn s;\n\t\t}\n\t\tchar[] str = s.toCharArray();\n\t\tstr[index] = with;\n\t\treturn new String(str);\n\t}\n\n\t/**\n\t * String 有replace(char,char)替换全部char，但缺少单独replace first/last\n\t */\n\tpublic static String replaceLast(@Nullable String s, char sub, char with) {\n\t\tif (s == null) {\n\t\t\treturn null;\n\t\t}\n\n\t\tint index = s.lastIndexOf(sub);\n\t\tif (index == -1) {\n\t\t\treturn s;\n\t\t}\n\t\tchar[] str = s.toCharArray();\n\t\tstr[index] = with;\n\t\treturn new String(str);\n\t}\n\n\t/**\n\t * 判断字符串是否以字母开头\n\t * \n\t * 如果字符串为Null或空，返回false\n\t */\n\tpublic static boolean startWith(@Nullable CharSequence s, char c) {\n\t\tif (StringUtils.isEmpty(s)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn s.charAt(0) == c;\n\t}\n\n\t/**\n\t * 判断字符串是否以字母结尾\n\t * \n\t * 如果字符串为Null或空，返回false\n\t */\n\tpublic static boolean endWith(@Nullable CharSequence s, char c) {\n\t\tif (StringUtils.isEmpty(s)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn s.charAt(s.length() - 1) == c;\n\t}\n\n\t/**\n\t * 如果结尾字符为c, 去除掉该字符.\n\t */\n\tpublic static String removeEnd(final String s, final char c) {\n\t\tif (endWith(s, c)) {\n\t\t\treturn s.substring(0, s.length() - 1);\n\t\t}\n\t\treturn s;\n\t}\n\n\t///////////// 其他 ////////////\n\t/**\n\t * 计算字符串被UTF8编码后的字节数 via guava\n\t * \n\t * @see Utf8#encodedLength(CharSequence)\n\t */\n\tpublic static int utf8EncodedLength(@Nullable CharSequence sequence) {\n\t\tif (StringUtils.isEmpty(sequence)) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn Utf8.encodedLength(sequence);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/text/StringBuilderHolder.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\n/**\n * 参考Netty的InternalThreadLocalMap 与 BigDecimal, 放在threadLocal中重用的StringBuilder, 节约StringBuilder内部的char[]\n * \n * 参考文章：《StringBuilder在高性能场景下的正确用法》http://calvin1978.blogcn.com/articles/stringbuilder.html\n * \n * 不过仅在String对象较大时才有明显效果，否则抵不上访问ThreadLocal的消耗.\n * \n * 当StringBuilder在使用过程中，会调用其他可能也使用StringBuilderHolder的子函数时，需要创建独立的Holder, 否则会共同使用公共的Holder\n * \n * 注意：在Netty环境中，使用Netty提供的基于FastThreadLocal的版本\n */\npublic class StringBuilderHolder {\n\n\t// 全局公共的ThreadLocal StringBuilder\n\tprivate static ThreadLocal<StringBuilder> globalStringBuilder = new ThreadLocal<StringBuilder>() {\n\t\t@Override\n\t\tprotected StringBuilder initialValue() {\n\t\t\treturn new StringBuilder(512);\n\t\t}\n\t};\n\n\t// 独立创建的ThreadLocal的StringBuilder\n\tprivate ThreadLocal<StringBuilder> stringBuilder = new ThreadLocal<StringBuilder>() {\n\t\t@Override\n\t\tprotected StringBuilder initialValue() {\n\t\t\treturn new StringBuilder(initSize);\n\t\t}\n\t};\n\n\tprivate int initSize;\n\n\t/**\n\t * 创建独立的Holder.\n\t * \n\t * 用于StringBuilder在使用过程中，会调用其他可能也使用StringBuilderHolder的子函数.\n\t * \n\t * @param initSize StringBuilder的初始大小, 建议512,如果容量不足将进行扩容，扩容后的数组将一直保留.\n\t */\n\tpublic StringBuilderHolder(int initSize) {\n\t\tthis.initSize = initSize;\n\t}\n\n\t/**\n\t * 获取公共Holder的StringBuilder.\n\t * \n\t * 当StringBuilder会被连续使用，期间不会调用其他可能也使用StringBuilderHolder的子函数时使用.\n\t * \n\t * 重置StringBuilder内部的writerIndex, 而char[]保留不动.\n\t */\n\tpublic static StringBuilder getGlobal() {\n\t\tStringBuilder sb = globalStringBuilder.get();\n\t\tsb.setLength(0);\n\t\treturn sb;\n\t}\n\n\t/**\n\t * 获取独立Holder的StringBuilder.\n\t * \n\t * 重置StringBuilder内部的writerIndex, 而char[]保留不动.\n\t */\n\tpublic StringBuilder get() {\n\t\tStringBuilder sb = stringBuilder.get();\n\t\tsb.setLength(0);\n\t\treturn sb;\n\t}\n}"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/text/TextValidator.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport java.util.regex.Pattern;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.vip.vjtools.vjkit.base.annotation.Nullable;\n\n/**\n * 通过正则表达判断是否正确的URL， 邮箱，手机号，固定电话，身份证，邮箱等.\n * \n * 从AndroidUtilCode的RegexUtils移植, 性能优化将正则表达式为预编译, 并修改了TEL的正则表达式.\n * \n * https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/src/main/java/com/blankj/utilcode/util/RegexUtils.java\n * https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/src/main/java/com/blankj/utilcode/constant/RegexConstants.java\n */\npublic class TextValidator {\n\n\t/**\n\t * 正则：手机号（简单）, 1字头＋10位数字即可.\n\t */\n\tprivate static final String REGEX_MOBILE_SIMPLE = \"^[1]\\\\d{10}$\";\n\tprivate static final Pattern PATTERN_REGEX_MOBILE_SIMPLE = Pattern.compile(REGEX_MOBILE_SIMPLE);\n\n\t/**\n\t * 正则：手机号（精确）, 已知3位前缀＋8位数字\n\t * <p>\n\t * 移动：134(0-8)、135、136、137、138、139、147、150、151、152、157、158、159、178、182、183、184、187、188、198\n\t * </p>\n\t * <p>\n\t * 联通：130、131、132、145、155、156、166、171、175、176、185、186\n\t * </p>\n\t * <p>\n\t * 电信：133、153、173、177、180、181、189、199\n\t * </p>\n\t * <p>\n\t * 全球星：1349\n\t * </p>\n\t * <p>\n\t * 虚拟运营商：170\n\t * </p>\n\t */\n\tpublic 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}$\";\n\tprivate static final Pattern PATTERN_REGEX_MOBILE_EXACT = Pattern.compile(REGEX_MOBILE_EXACT);\n\n\t/**\n\t * 正则：固定电话号码，可带区号，然后6至少8位数字\n\t */\n\tprivate static final String REGEX_TEL = \"^(\\\\d{3,4}-)?\\\\d{6,8}$\";\n\tprivate static final Pattern PATTERN_REGEX_TEL = Pattern.compile(REGEX_TEL);\n\n\t/**\n\t * 正则：身份证号码15位, 数字且关于生日的部分必须正确\n\t */\n\tprivate static final String REGEX_ID_CARD15 = \"^[1-9]\\\\d{7}((0\\\\d)|(1[0-2]))(([0|1|2]\\\\d)|3[0-1])\\\\d{3}$\";\n\tprivate static final Pattern PATTERN_REGEX_ID_CARD15 = Pattern.compile(REGEX_ID_CARD15);\n\n\t/**\n\t * 正则：身份证号码18位, 数字且关于生日的部分必须正确\n\t */\n\tprivate 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])$\";\n\tprivate static final Pattern PATTERN_REGEX_ID_CARD18 = Pattern.compile(REGEX_ID_CARD18);\n\n\t/**\n\t * 正则：邮箱, 有效字符(不支持中文), 且中间必须有@，后半部分必须有.\n\t */\n\tprivate static final String REGEX_EMAIL = \"^\\\\w+([-+.]\\\\w+)*@\\\\w+([-.]\\\\w+)*\\\\.\\\\w+([-.]\\\\w+)*$\";\n\tprivate static final Pattern PATTERN_REGEX_EMAIL = Pattern.compile(REGEX_EMAIL);\n\n\t/**\n\t * 正则：URL, 必须有\"://\",前面必须是英文，后面不能有空格\n\t */\n\tprivate static final String REGEX_URL = \"[a-zA-z]+://[^\\\\s]*\";\n\tprivate static final Pattern PATTERN_REGEX_URL = Pattern.compile(REGEX_URL);\n\n\t/**\n\t * 正则：yyyy-MM-dd格式的日期校验，已考虑平闰年\n\t */\n\tprivate 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)$\";\n\tprivate static final Pattern PATTERN_REGEX_DATE = Pattern.compile(REGEX_DATE);\n\n\t/**\n\t * 正则：IP地址\n\t */\n\tprivate 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?)\";\n\tprivate static final Pattern PATTERN_REGEX_IP = Pattern.compile(REGEX_IP);\n\n\t/////////////////\n\t/**\n\t * 验证手机号（简单）\n\t */\n\tpublic static boolean isMobileSimple(@Nullable CharSequence input) {\n\t\treturn isMatch(PATTERN_REGEX_MOBILE_SIMPLE, input);\n\t}\n\n\t/**\n\t * 验证手机号（精确）\n\t */\n\tpublic static boolean isMobileExact(@Nullable CharSequence input) {\n\t\treturn isMatch(PATTERN_REGEX_MOBILE_EXACT, input);\n\t}\n\n\t/**\n\t * 验证固定电话号码\n\t */\n\tpublic static boolean isTel(@Nullable CharSequence input) {\n\t\treturn isMatch(PATTERN_REGEX_TEL, input);\n\t}\n\n\t/**\n\t * 验证15或18位身份证号码\n\t */\n\tpublic static boolean isIdCard(@Nullable CharSequence input) {\n\t\treturn isMatch(PATTERN_REGEX_ID_CARD15, input) || isMatch(PATTERN_REGEX_ID_CARD18, input);\n\t}\n\n\t/**\n\t * 验证邮箱\n\t */\n\tpublic static boolean isEmail(@Nullable CharSequence input) {\n\t\treturn isMatch(PATTERN_REGEX_EMAIL, input);\n\t}\n\n\t/**\n\t * 验证URL\n\t */\n\tpublic static boolean isUrl(@Nullable CharSequence input) {\n\t\treturn isMatch(PATTERN_REGEX_URL, input);\n\t}\n\n\t/**\n\t * 验证yyyy-MM-dd格式的日期校验，已考虑平闰年\n\t */\n\tpublic static boolean isDate(@Nullable CharSequence input) {\n\t\treturn isMatch(PATTERN_REGEX_DATE, input);\n\t}\n\n\t/**\n\t * 验证IP地址\n\t */\n\tpublic static boolean isIp(@Nullable CharSequence input) {\n\t\treturn isMatch(PATTERN_REGEX_IP, input);\n\t}\n\n\tpublic static boolean isMatch(Pattern pattern, CharSequence input) {\n\t\treturn StringUtils.isNotEmpty(input) && pattern.matcher(input).matches();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/text/WildcardMatcher.java",
    "content": "// Copyright (c) 2003-present, Jodd Team (http://jodd.org)\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n//\n// 1. Redistributions of source code must retain the above copyright notice,\n// this list of conditions and the following disclaimer.\n//\n// 2. Redistributions in binary form must reproduce the above copyright\n// notice, this list of conditions and the following disclaimer in the\n// documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n// POSSIBILITY OF SUCH DAMAGE.\n\npackage com.vip.vjtools.vjkit.text;\n\nimport java.util.List;\n\nimport com.google.common.base.CharMatcher;\nimport com.google.common.base.Splitter;\n\n/**\n * \n * 从Jodd移植，匹配以通配符比较字符串（比正则表达式简单），以及Ant Path风格如比较目录Path\n * \n * https://github.com/oblac/jodd/blob/master/jodd-core/src/main/java/jodd/util/Wildcard.java\n * \n * Checks whether a string or path matches a given wildcard pattern. Possible patterns allow to match single characters\n * ('?') or any count of characters ('*'). Wildcard characters can be escaped (by an '\\'). When matching path, deep tree\n * wildcard also can be used ('**').\n * <p>\n * This method uses recursive matching, as in linux or windows. regexp works the same. This method is very fast,\n * comparing to similar implementations.\n */\npublic class WildcardMatcher {\n\n\t/**\n\t * Checks whether a string matches a given wildcard pattern.\n\t *\n\t * @param string input string\n\t * @param pattern pattern to match\n\t * @return <code>true</code> if string matches the pattern, otherwise <code>false</code>\n\t */\n\tpublic static boolean match(CharSequence string, CharSequence pattern) {\n\t\treturn match(string, pattern, 0, 0);\n\t}\n\n\t/**\n\t * Internal matching recursive function.\n\t */\n\tprivate static boolean match(CharSequence string, CharSequence pattern, final int sNdxConst, final int pNdxConst) {\n\n\t\tint pLen = pattern.length();\n\t\tif (pLen == 1) {\n\t\t\tif (pattern.charAt(0) == '*') { // speed-up\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tint sLen = string.length();\n\t\tboolean nextIsNotWildcard = false;\n\n\t\tint sNdx = sNdxConst;\n\t\tint pNdx = pNdxConst;\n\t\twhile (true) {\n\n\t\t\t// check if end of string and/or pattern occurred\n\t\t\tif ((sNdx >= sLen)) { // end of string still may have pending '*' in pattern\n\t\t\t\twhile ((pNdx < pLen) && (pattern.charAt(pNdx) == '*')) {\n\t\t\t\t\tpNdx++;\n\t\t\t\t}\n\t\t\t\treturn pNdx >= pLen;\n\t\t\t}\n\t\t\tif (pNdx >= pLen) { // end of pattern, but not end of the string\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tchar p = pattern.charAt(pNdx); // pattern char\n\n\t\t\t// perform logic\n\t\t\tif (!nextIsNotWildcard) {\n\n\t\t\t\tif (p == '\\\\') {\n\t\t\t\t\tpNdx++;\n\t\t\t\t\tnextIsNotWildcard = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (p == '?') {\n\t\t\t\t\tsNdx++;\n\t\t\t\t\tpNdx++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (p == '*') {\n\t\t\t\t\tchar pNext = 0; // next pattern char\n\t\t\t\t\tif (pNdx + 1 < pLen) {\n\t\t\t\t\t\tpNext = pattern.charAt(pNdx + 1);\n\t\t\t\t\t}\n\t\t\t\t\tif (pNext == '*') { // double '*' have the same effect as one '*'\n\t\t\t\t\t\tpNdx++;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tint i;\n\t\t\t\t\tpNdx++;\n\n\t\t\t\t\t// find recursively if there is any substring from the end of the\n\t\t\t\t\t// line that matches the rest of the pattern !!!\n\t\t\t\t\tfor (i = string.length(); i >= sNdx; i--) {\n\t\t\t\t\t\tif (match(string, pattern, i, pNdx)) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnextIsNotWildcard = false;\n\t\t\t}\n\n\t\t\t// check if pattern char and string char are equals\n\t\t\tif (p != string.charAt(sNdx)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// everything matches for now, continue\n\t\t\tsNdx++;\n\t\t\tpNdx++;\n\t\t}\n\t}\n\n\t// ---------------------------------------------------------------- utilities\n\n\t/**\n\t * Matches string to at least one pattern. Returns index of matched pattern, or <code>-1</code> otherwise.\n\t * @see #match(CharSequence, CharSequence)\n\t */\n\tpublic static int matchOne(String src, String... patterns) {\n\t\tfor (int i = 0; i < patterns.length; i++) {\n\t\t\tif (match(src, patterns[i])) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t}\n\n\t// ---------------------------------------------------------------- path\n\n\tprotected static final String PATH_MATCH = \"**\";\n\tprotected static final Splitter PATH_SPLITTER = Splitter.on(CharMatcher.anyOf(\"/\\\\\"));\n\n\t/**\n\t * Matches path to at least one pattern. Returns index of matched pattern or <code>-1</code> otherwise.\n\t * @see #matchPath\n\t */\n\tpublic static int matchPathOne(String platformDependentPath, String... patterns) {\n\t\tfor (int i = 0; i < patterns.length; i++) {\n\t\t\tif (matchPath(platformDependentPath, patterns[i])) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t}\n\n\t/**\n\t * Matches path against pattern using *, ? and ** wildcards. Both path and the pattern are tokenized on path\n\t * separators (both \\ and /). '**' represents deep tree wildcard, as in Ant. The separator should match the\n\t * corresponding path\n\t */\n\tpublic static boolean matchPath(String path, String pattern) {\n\t\tList<String> pathElements = PATH_SPLITTER.splitToList(path);\n\t\tList<String> patternElements = PATH_SPLITTER.splitToList(pattern);\n\t\treturn matchTokens(pathElements.toArray(new String[0]), patternElements.toArray(new String[0]));\n\t}\n\n\t/**\n\t * Match tokenized string and pattern.\n\t */\n\tprotected static boolean matchTokens(String[] tokens, String[] patterns) {\n\t\tint patNdxStart = 0;\n\t\tint patNdxEnd = patterns.length - 1;\n\t\tint tokNdxStart = 0;\n\t\tint tokNdxEnd = tokens.length - 1;\n\n\t\twhile ((patNdxStart <= patNdxEnd) && (tokNdxStart <= tokNdxEnd)) { // find first **\n\t\t\tString patDir = patterns[patNdxStart];\n\t\t\tif (patDir.equals(PATH_MATCH)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!match(tokens[tokNdxStart], patDir)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tpatNdxStart++;\n\t\t\ttokNdxStart++;\n\t\t}\n\t\tif (tokNdxStart > tokNdxEnd) {\n\t\t\tfor (int i = patNdxStart; i <= patNdxEnd; i++) { // string is finished\n\t\t\t\tif (!patterns[i].equals(PATH_MATCH)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (patNdxStart > patNdxEnd) {\n\t\t\treturn false; // string is not finished, but pattern is\n\t\t}\n\n\t\twhile ((patNdxStart <= patNdxEnd) && (tokNdxStart <= tokNdxEnd)) { // to the last **\n\t\t\tString patDir = patterns[patNdxEnd];\n\t\t\tif (patDir.equals(PATH_MATCH)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!match(tokens[tokNdxEnd], patDir)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tpatNdxEnd--;\n\t\t\ttokNdxEnd--;\n\t\t}\n\t\tif (tokNdxStart > tokNdxEnd) {\n\t\t\tfor (int i = patNdxStart; i <= patNdxEnd; i++) { // string is finished\n\t\t\t\tif (!patterns[i].equals(PATH_MATCH)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\twhile ((patNdxStart != patNdxEnd) && (tokNdxStart <= tokNdxEnd)) {\n\t\t\tint patIdxTmp = -1;\n\t\t\tfor (int i = patNdxStart + 1; i <= patNdxEnd; i++) {\n\t\t\t\tif (patterns[i].equals(PATH_MATCH)) {\n\t\t\t\t\tpatIdxTmp = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (patIdxTmp == patNdxStart + 1) {\n\t\t\t\tpatNdxStart++; // skip **/** situation\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// find the pattern between padIdxStart & padIdxTmp in str between strIdxStart & strIdxEnd\n\t\t\tint patLength = (patIdxTmp - patNdxStart - 1);\n\t\t\tint strLength = (tokNdxEnd - tokNdxStart + 1);\n\t\t\tint ndx = -1;\n\t\t\tstrLoop: for (int i = 0; i <= strLength - patLength; i++) {\n\t\t\t\tfor (int j = 0; j < patLength; j++) {\n\t\t\t\t\tString subPat = patterns[patNdxStart + j + 1];\n\t\t\t\t\tString subStr = tokens[tokNdxStart + i + j];\n\t\t\t\t\tif (!match(subStr, subPat)) {\n\t\t\t\t\t\tcontinue strLoop;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tndx = tokNdxStart + i;\n\t\t\t\tbreak;\n\t\t\t\t// this is a double-loop, cannot be refactor to break statement directly\n\t\t\t}\n\n\t\t\tif (ndx == -1) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tpatNdxStart = patIdxTmp;\n\t\t\ttokNdxStart = ndx + patLength;\n\t\t}\n\n\t\tfor (int i = patNdxStart; i <= patNdxEnd; i++) {\n\t\t\tif (!patterns[i].equals(PATH_MATCH)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/time/CachingDateFormatter.java",
    "content": "package com.vip.vjtools.vjkit.time;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport org.apache.commons.lang3.time.FastDateFormat;\n\n/**\n * DateFormat.format()消耗较大，如果时间戳是递增的，而且同一单位内有多次format()，使用用本类减少重复调用.\n * \n * copy from Log4j2 DatePatternConverter，进行了优化，根据输出格式是否毫秒级，决定缓存在秒级还是毫秒级.\n * \n * 注意如果输出格式为毫秒级的话，根据QPS决定性价比\n * \n * see https://github.com/apache/logging-log4j2/blob/master/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java#L272\n * \n * @author calvin\n */\npublic class CachingDateFormatter {\n\tprivate FastDateFormat fastDateFormat;\n\tprivate AtomicReference<CachedTime> cachedTime;\n\tprivate boolean onSecond;// 根据时间格式，决定缓存在秒级还是毫秒级\n\n\tpublic CachingDateFormatter(String pattern) {\n\t\tthis(FastDateFormat.getInstance(pattern));\n\t}\n\n\tpublic CachingDateFormatter(FastDateFormat fastDateFormat) {\n\t\tthis.fastDateFormat = fastDateFormat;\n\t\tonSecond = fastDateFormat.getPattern().indexOf(\"SSS\") == -1;\n\n\t\tlong current = System.currentTimeMillis();\n\t\tthis.cachedTime = new AtomicReference<CachedTime>(new CachedTime(current, fastDateFormat.format(current)));\n\t}\n\n\tpublic String format(final long timestampMillis) {\n\t\tCachedTime cached = cachedTime.get();\n\n\t\tlong timestamp = onSecond ? timestampMillis / 1000 : timestampMillis;\n\n\t\tif (timestamp != cached.timestamp) {\n\t\t\tfinal CachedTime newCachedTime = new CachedTime(timestamp, fastDateFormat.format(timestampMillis));\n\t\t\t// 尝试放入cachedTime\n\t\t\tcachedTime.compareAndSet(cached, newCachedTime);\n\t\t\t// 与log4j2做法不同，无论是否放入成功，都使用自己的值\n\t\t\tcached = newCachedTime;\n\t\t}\n\n\t\treturn cached.formatted;\n\t}\n\n\tstatic final class CachedTime {\n\t\tpublic long timestamp;\n\t\tpublic String formatted;\n\n\t\tpublic CachedTime(final long timestamp, String formatted) {\n\t\t\tthis.timestamp = timestamp;\n\t\t\tthis.formatted = formatted;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/time/ClockUtil.java",
    "content": "package com.vip.vjtools.vjkit.time;\n\nimport java.util.Date;\n\n/**\n * 日期提供者, 使用它而不是直接取得系统时间, 方便测试.\n * \n * 平时使用DEFAULT，测试时替换为DummyClock，可准确控制时间变化而不用Thread.sleep()等待时间流逝.\n */\npublic class ClockUtil {\n\n\tprivate static Clock instance = new DefaultClock();\n\n\t/**\n\t * 计算流逝的时间\n\t */\n\tpublic static long elapsedTime(long beginTime) {\n\t\treturn currentTimeMillis() - beginTime;\n\t}\n\n\t/**\n\t * 切换为DummyClock，使用系统时间为初始时间, 单个测试完成后需要调用useDefaultClock()切换回去.\n\t */\n\tpublic static synchronized DummyClock useDummyClock() {\n\t\tinstance = new DummyClock();\n\t\treturn (DummyClock) instance;\n\t}\n\n\t/**\n\t * 切换为DummyClock，单个测试完成后需要调用useDefaultClock()切换回去.\n\t */\n\tpublic static synchronized DummyClock useDummyClock(long timeStampMills) {\n\t\tinstance = new DummyClock(timeStampMills);\n\t\treturn (DummyClock) instance;\n\t}\n\n\t/**\n\t * 切换为DummyClock，单个测试完成后需要调用useDefaultClock()切换回去.\n\t */\n\tpublic static synchronized DummyClock useDummyClock(Date date) {\n\t\tinstance = new DummyClock(date);\n\t\treturn (DummyClock) instance;\n\t}\n\n\t/**\n\t * 重置为默认Clock\n\t */\n\tpublic static synchronized void useDefaultClock() {\n\t\tinstance = new DefaultClock();\n\t}\n\n\t/**\n\t * 系统当前时间\n\t */\n\tpublic static Date currentDate() {\n\t\treturn instance.currentDate();\n\t}\n\n\t/**\n\t * 系统当前时间戳\n\t */\n\tpublic static long currentTimeMillis() {\n\t\treturn instance.currentTimeMillis();\n\t}\n\n\t/**\n\t * 操作系统启动到现在的纳秒数，与系统时间是完全独立的两个时间体系\n\t */\n\tpublic static long nanoTime() {\n\t\treturn instance.nanoTime();\n\t}\n\n\tpublic interface Clock {\n\n\t\t/**\n\t\t * 系统当前时间\n\t\t */\n\t\tDate currentDate();\n\n\t\t/**\n\t\t * 系统当前时间戳\n\t\t */\n\t\tlong currentTimeMillis();\n\n\t\t/**\n\t\t * 操作系统启动到现在的纳秒数，与系统时间是完全独立的两个时间体系\n\t\t */\n\t\tlong nanoTime();\n\t}\n\n\t/**\n\t * 默认时间提供者，返回当前的时间，线程安全。\n\t */\n\tpublic static class DefaultClock implements Clock {\n\n\t\t@Override\n\t\tpublic Date currentDate() {\n\t\t\treturn new Date();\n\t\t}\n\n\t\t@Override\n\t\tpublic long currentTimeMillis() {\n\t\t\treturn System.currentTimeMillis();\n\t\t}\n\n\t\t@Override\n\t\tpublic long nanoTime() {\n\t\t\treturn System.nanoTime();\n\t\t}\n\t}\n\n\t/**\n\t * 可配置的时间提供者，用于测试.\n\t */\n\tpublic static class DummyClock implements Clock {\n\n\t\tprivate long time;\n\t\tprivate long nanoTme;\n\n\t\tpublic DummyClock() {\n\t\t\tthis(System.currentTimeMillis());\n\t\t}\n\n\t\tpublic DummyClock(Date date) {\n\t\t\tthis(date.getTime());\n\t\t}\n\n\t\tpublic DummyClock(long time) {\n\t\t\tthis.time = time;\n\t\t\tthis.nanoTme = System.nanoTime();\n\t\t}\n\n\t\t@Override\n\t\tpublic Date currentDate() {\n\t\t\treturn new Date(time);\n\t\t}\n\n\t\t@Override\n\t\tpublic long currentTimeMillis() {\n\t\t\treturn time;\n\t\t}\n\n\t\t/**\n\t\t * 获取nanoTime\n\t\t */\n\t\t@Override\n\t\tpublic long nanoTime() {\n\t\t\treturn nanoTme;\n\t\t}\n\n\t\t/**\n\t\t * 重新设置日期.\n\t\t */\n\t\tpublic void updateNow(Date newDate) {\n\t\t\ttime = newDate.getTime();\n\t\t}\n\n\t\t/**\n\t\t * 重新设置时间.\n\t\t */\n\t\tpublic void updateNow(long newTime) {\n\t\t\tthis.time = newTime;\n\t\t}\n\n\t\t/**\n\t\t * 滚动时间.\n\t\t */\n\t\tpublic void increaseTime(int millis) {\n\t\t\ttime += millis;\n\t\t}\n\n\t\t/**\n\t\t * 滚动时间.\n\t\t */\n\t\tpublic void decreaseTime(int millis) {\n\t\t\ttime -= millis;\n\t\t}\n\n\t\t/**\n\t\t * 设置nanoTime.\n\t\t */\n\t\tpublic void setNanoTime(long nanoTime) {\n\t\t\tthis.nanoTme = nanoTime;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/time/DateFormatUtil.java",
    "content": "package com.vip.vjtools.vjkit.time;\n\nimport java.text.ParseException;\nimport java.util.Date;\n\nimport org.apache.commons.lang3.time.DurationFormatUtils;\nimport org.apache.commons.lang3.time.FastDateFormat;\n\nimport com.vip.vjtools.vjkit.base.annotation.NotNull;\n\n/**\n * Date的parse()与format(), 采用Apache Common Lang中线程安全, 性能更佳的FastDateFormat\n * \n * 注意Common Lang版本，3.5版才使用StringBuilder，3.4及以前使用StringBuffer.\n * \n * 1. 常用格式的FastDateFormat定义, 常用格式直接使用这些FastDateFormat\n * \n * 2. 日期格式不固定时的String<->Date 转换函数.\n * \n * 3. 打印时间间隔，如\"01:10:10\"，以及用户友好的版本，比如\"刚刚\"，\"10分钟前\"\n * \n * @see FastDateFormat#parse(String)\n * @see FastDateFormat#format(java.util.Date)\n * @see FastDateFormat#format(long)\n */\npublic class DateFormatUtil {\n\n\t// 以T分隔日期和时间，并带时区信息，符合ISO8601规范\n\tpublic static final String PATTERN_ISO = \"yyyy-MM-dd'T'HH:mm:ss.SSSZZ\";\n\tpublic static final String PATTERN_ISO_ON_SECOND = \"yyyy-MM-dd'T'HH:mm:ssZZ\";\n\tpublic static final String PATTERN_ISO_ON_DATE = \"yyyy-MM-dd\";\n\n\t// 以空格分隔日期和时间，不带时区信息\n\tpublic static final String PATTERN_DEFAULT = \"yyyy-MM-dd HH:mm:ss.SSS\";\n\tpublic static final String PATTERN_DEFAULT_ON_SECOND = \"yyyy-MM-dd HH:mm:ss\";\n\n\t// 使用工厂方法FastDateFormat.getInstance(), 从缓存中获取实例\n\n\t// 以T分隔日期和时间，并带时区信息，符合ISO8601规范\n\tpublic static final FastDateFormat ISO_FORMAT = FastDateFormat.getInstance(PATTERN_ISO);\n\tpublic static final FastDateFormat ISO_ON_SECOND_FORMAT = FastDateFormat.getInstance(PATTERN_ISO_ON_SECOND);\n\tpublic static final FastDateFormat ISO_ON_DATE_FORMAT = FastDateFormat.getInstance(PATTERN_ISO_ON_DATE);\n\n\t// 以空格分隔日期和时间，不带时区信息\n\tpublic static final FastDateFormat DEFAULT_FORMAT = FastDateFormat.getInstance(PATTERN_DEFAULT);\n\tpublic static final FastDateFormat DEFAULT_ON_SECOND_FORMAT = FastDateFormat.getInstance(PATTERN_DEFAULT_ON_SECOND);\n\n\t/**\n\t * 分析日期字符串, 仅用于pattern不固定的情况.\n\t * \n\t * 否则直接使用DateFormats中封装好的FastDateFormat.\n\t * \n\t * FastDateFormat.getInstance()已经做了缓存，不会每次创建对象，但直接使用对象仍然能减少在缓存中的查找.\n\t */\n\tpublic static Date parseDate(@NotNull String pattern, @NotNull String dateString) throws ParseException {\n\t\treturn FastDateFormat.getInstance(pattern).parse(dateString);\n\t}\n\n\t/**\n\t * 格式化日期, 仅用于pattern不固定的情况.\n\t * \n\t * 否则直接使用本类中封装好的FastDateFormat.\n\t * \n\t * FastDateFormat.getInstance()已经做了缓存，不会每次创建对象，但直接使用对象仍然能减少在缓存中的查找.\n\t */\n\tpublic static String formatDate(@NotNull String pattern, @NotNull Date date) {\n\t\treturn FastDateFormat.getInstance(pattern).format(date);\n\t}\n\n\t/**\n\t * 格式化日期, 仅用于不固定pattern不固定的情况.\n\t * \n\t * 否否则直接使用本类中封装好的FastDateFormat.\n\t * \n\t * FastDateFormat.getInstance()已经做了缓存，不会每次创建对象，但直接使用对象仍然能减少在缓存中的查找.\n\t */\n\tpublic static String formatDate(@NotNull String pattern, long date) {\n\t\treturn FastDateFormat.getInstance(pattern).format(date);\n\t}\n\n\t/////// 格式化间隔时间/////////\n\t/**\n\t * 按HH:mm:ss.SSS格式，格式化时间间隔.\n\t * \n\t * endDate必须大于startDate，间隔可大于1天，\n\t * \n\t * @see DurationFormatUtils\n\t */\n\tpublic static String formatDuration(@NotNull Date startDate, @NotNull Date endDate) {\n\t\treturn DurationFormatUtils.formatDurationHMS(endDate.getTime() - startDate.getTime());\n\t}\n\n\t/**\n\t * 按HH:mm:ss.SSS格式，格式化时间间隔\n\t * \n\t * 单位为毫秒，必须大于0，可大于1天\n\t * \n\t * @see DurationFormatUtils\n\t */\n\tpublic static String formatDuration(long durationMillis) {\n\t\treturn DurationFormatUtils.formatDurationHMS(durationMillis);\n\t}\n\n\t/**\n\t * 按HH:mm:ss格式，格式化时间间隔\n\t * \n\t * endDate必须大于startDate，间隔可大于1天\n\t * \n\t * @see DurationFormatUtils\n\t */\n\tpublic static String formatDurationOnSecond(@NotNull Date startDate, @NotNull Date endDate) {\n\t\treturn DurationFormatUtils.formatDuration(endDate.getTime() - startDate.getTime(), \"HH:mm:ss\");\n\t}\n\n\t/**\n\t * 按HH:mm:ss格式，格式化时间间隔\n\t * \n\t * 单位为毫秒，必须大于0，可大于1天\n\t * \n\t * @see DurationFormatUtils\n\t */\n\tpublic static String formatDurationOnSecond(long durationMillis) {\n\t\treturn DurationFormatUtils.formatDuration(durationMillis, \"HH:mm:ss\");\n\t}\n\n\t//////// 打印用于页面显示的用户友好，与当前时间比的时间差\n\t/**\n\t * 打印用户友好的，与当前时间相比的时间差，如刚刚，5分钟前，今天XXX，昨天XXX\n\t * \n\t * copy from AndroidUtilCode\n\t */\n\tpublic static String formatFriendlyTimeSpanByNow(@NotNull Date date) {\n\t\treturn formatFriendlyTimeSpanByNow(date.getTime());\n\t}\n\n\t/**\n\t * 打印用户友好的，与当前时间相比的时间差，如刚刚，5分钟前，今天XXX，昨天XXX\n\t * \n\t * copy from AndroidUtilCode\n\t */\n\tpublic static String formatFriendlyTimeSpanByNow(long timeStampMillis) {\n\t\tlong now = ClockUtil.currentTimeMillis();\n\t\tlong span = now - timeStampMillis;\n\t\tif (span < 0) {\n\t\t\t// 'c' 日期和时间，被格式化为 \"%ta %tb %td %tT %tZ %tY\"，例如 \"Sun Jul 20 16:17:00 EDT 1969\"。\n\t\t\treturn String.format(\"%tc\", timeStampMillis);\n\t\t}\n\t\tif (span < DateUtil.MILLIS_PER_SECOND) {\n\t\t\treturn \"刚刚\";\n\t\t} else if (span < DateUtil.MILLIS_PER_MINUTE) {\n\t\t\treturn String.format(\"%d秒前\", span / DateUtil.MILLIS_PER_SECOND);\n\t\t} else if (span < DateUtil.MILLIS_PER_HOUR) {\n\t\t\treturn String.format(\"%d分钟前\", span / DateUtil.MILLIS_PER_MINUTE);\n\t\t}\n\t\t// 获取当天00:00\n\t\tlong wee = DateUtil.beginOfDate(new Date(now)).getTime();\n\t\tif (timeStampMillis >= wee) {\n\t\t\t// 'R' 24 小时制的时间，被格式化为 \"%tH:%tM\"\n\t\t\treturn String.format(\"今天%tR\", timeStampMillis);\n\t\t} else if (timeStampMillis >= wee - DateUtil.MILLIS_PER_DAY) {\n\t\t\treturn String.format(\"昨天%tR\", timeStampMillis);\n\t\t} else {\n\t\t\t// 'F' ISO 8601 格式的完整日期，被格式化为 \"%tY-%tm-%td\"。\n\t\t\treturn String.format(\"%tF\", timeStampMillis);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/java/com/vip/vjtools/vjkit/time/DateUtil.java",
    "content": "package com.vip.vjtools.vjkit.time;\n\nimport java.util.Calendar;\nimport java.util.Date;\n\nimport org.apache.commons.lang3.Validate;\nimport org.apache.commons.lang3.time.DateUtils;\n\nimport com.vip.vjtools.vjkit.base.annotation.NotNull;\n\n/**\n * 日期工具类.\n * \n * 在不方便使用joda-time时，使用本类降低Date处理的复杂度与性能消耗, 封装Common Lang及移植Jodd的最常用日期方法\n */\npublic class DateUtil {\n\n\tpublic static final long MILLIS_PER_SECOND = 1000; // Number of milliseconds in a standard second.\n\tpublic static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND; // Number of milliseconds in a standard minute.\n\tpublic static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE; // Number of milliseconds in a standard hour.\n\tpublic static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR; // Number of milliseconds in a standard day.\n\n\tprivate static final int[] MONTH_LENGTH = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };\n\n\t//////// 日期比较 ///////////\n\t/**\n\t * 是否同一天.\n\t * \n\t * @see DateUtils#isSameDay(Date, Date)\n\t */\n\tpublic static boolean isSameDay(@NotNull final Date date1, @NotNull final Date date2) {\n\t\treturn DateUtils.isSameDay(date1, date2);\n\t}\n\n\t/**\n\t * 是否同一时刻.\n\t */\n\tpublic static boolean isSameTime(@NotNull final Date date1, @NotNull final Date date2) {\n\t\t// date.getMillisOf() 比date.getTime()快\n\t\treturn date1.compareTo(date2) == 0;\n\t}\n\n\t/**\n\t * 判断日期是否在范围内，包含相等的日期\n\t */\n\tpublic static boolean isBetween(@NotNull final Date date, @NotNull final Date start, @NotNull final Date end) {\n\t\tif (date == null || start == null || end == null || start.after(end)) {\n\t\t\tthrow new IllegalArgumentException(\"some date parameters is null or dateBein after dateEnd\");\n\t\t}\n\t\treturn !date.before(start) && !date.after(end);\n\t}\n\n\t//////////// 往前往后滚动时间//////////////\n\n\t/**\n\t * 加一月\n\t */\n\tpublic static Date addMonths(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addMonths(date, amount);\n\t}\n\n\t/**\n\t * 减一月\n\t */\n\tpublic static Date subMonths(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addMonths(date, -amount);\n\t}\n\n\t/**\n\t * 加一周\n\t */\n\tpublic static Date addWeeks(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addWeeks(date, amount);\n\t}\n\n\t/**\n\t * 减一周\n\t */\n\tpublic static Date subWeeks(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addWeeks(date, -amount);\n\t}\n\n\t/**\n\t * 加一天\n\t */\n\tpublic static Date addDays(@NotNull final Date date, final int amount) {\n\t\treturn DateUtils.addDays(date, amount);\n\t}\n\n\t/**\n\t * 减一天\n\t */\n\tpublic static Date subDays(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addDays(date, -amount);\n\t}\n\n\t/**\n\t * 加一小时\n\t */\n\tpublic static Date addHours(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addHours(date, amount);\n\t}\n\n\t/**\n\t * 减一小时\n\t */\n\tpublic static Date subHours(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addHours(date, -amount);\n\t}\n\n\t/**\n\t * 加一分钟\n\t */\n\tpublic static Date addMinutes(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addMinutes(date, amount);\n\t}\n\n\t/**\n\t * 减一分钟\n\t */\n\tpublic static Date subMinutes(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addMinutes(date, -amount);\n\t}\n\n\t/**\n\t * 终于到了，续一秒.\n\t */\n\tpublic static Date addSeconds(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addSeconds(date, amount);\n\t}\n\n\t/**\n\t * 减一秒.\n\t */\n\tpublic static Date subSeconds(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.addSeconds(date, -amount);\n\t}\n\n\t//////////// 直接设置时间//////////////\n\n\t/**\n\t * 设置年份, 公元纪年.\n\t */\n\tpublic static Date setYears(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.setYears(date, amount);\n\t}\n\n\t/**\n\t * 设置月份, 1-12.\n\t */\n\tpublic static Date setMonths(@NotNull final Date date, int amount) {\n\t\tif (amount < 1 || amount > 12) {\n\t\t\tthrow new IllegalArgumentException(\"monthOfYear must be in the range[ 1, 12]\");\n\t\t}\n\t\treturn DateUtils.setMonths(date, amount - 1);\n\t}\n\n\t/**\n\t * 设置日期, 1-31.\n\t */\n\tpublic static Date setDays(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.setDays(date, amount);\n\t}\n\n\t/**\n\t * 设置小时, 0-23.\n\t */\n\tpublic static Date setHours(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.setHours(date, amount);\n\t}\n\n\t/**\n\t * 设置分钟, 0-59.\n\t */\n\tpublic static Date setMinutes(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.setMinutes(date, amount);\n\t}\n\n\t/**\n\t * 设置秒, 0-59.\n\t */\n\tpublic static Date setSeconds(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.setSeconds(date, amount);\n\t}\n\n\t/**\n\t * 设置毫秒.\n\t */\n\tpublic static Date setMilliseconds(@NotNull final Date date, int amount) {\n\t\treturn DateUtils.setMilliseconds(date, amount);\n\t}\n\n\t///// 获取日期的位置//////\n\t/**\n\t * 获得日期是一周的第几天. 已改为中国习惯，1 是Monday，而不是Sunday.\n\t */\n\tpublic static int getDayOfWeek(@NotNull final Date date) {\n\t\tint result = getWithMondayFirst(date, Calendar.DAY_OF_WEEK);\n\t\treturn result == 1 ? 7 : result - 1;\n\t}\n\n\t/**\n\t * 获得日期是一年的第几天，返回值从1开始\n\t */\n\tpublic static int getDayOfYear(@NotNull final Date date) {\n\t\treturn get(date, Calendar.DAY_OF_YEAR);\n\t}\n\n\t/**\n\t * 获得日期是一月的第几周，返回值从1开始.\n\t * \n\t * 开始的一周，只要有一天在那个月里都算. 已改为中国习惯，1 是Monday，而不是Sunday\n\t */\n\tpublic static int getWeekOfMonth(@NotNull final Date date) {\n\t\treturn getWithMondayFirst(date, Calendar.WEEK_OF_MONTH);\n\t}\n\n\t/**\n\t * 获得日期是一年的第几周，返回值从1开始.\n\t * \n\t * 开始的一周，只要有一天在那一年里都算.已改为中国习惯，1 是Monday，而不是Sunday\n\t */\n\tpublic static int getWeekOfYear(@NotNull final Date date) {\n\t\treturn getWithMondayFirst(date, Calendar.WEEK_OF_YEAR);\n\t}\n\n\tprivate static int get(final Date date, int field) {\n\t\tValidate.notNull(date, \"The date must not be null\");\n\t\tCalendar cal = Calendar.getInstance();\n\t\tcal.setTime(date);\n\n\t\treturn cal.get(field);\n\t}\n\n\tprivate static int getWithMondayFirst(final Date date, int field) {\n\t\tValidate.notNull(date, \"The date must not be null\");\n\t\tCalendar cal = Calendar.getInstance();\n\t\tcal.setFirstDayOfWeek(Calendar.MONDAY);\n\t\tcal.setTime(date);\n\t\treturn cal.get(field);\n\t}\n\n\t///// 获得往前往后的日期//////\n\n\t/**\n\t * 2016-11-10 07:33:23, 则返回2016-1-1 00:00:00\n\t */\n\tpublic static Date beginOfYear(@NotNull final Date date) {\n\t\treturn DateUtils.truncate(date, Calendar.YEAR);\n\t}\n\n\t/**\n\t * 2016-11-10 07:33:23, 则返回2016-12-31 23:59:59.999\n\t */\n\tpublic static Date endOfYear(@NotNull final Date date) {\n\t\treturn new Date(nextYear(date).getTime() - 1);\n\t}\n\n\t/**\n\t * 2016-11-10 07:33:23, 则返回2017-1-1 00:00:00\n\t */\n\tpublic static Date nextYear(@NotNull final Date date) {\n\t\treturn DateUtils.ceiling(date, Calendar.YEAR);\n\t}\n\n\t/**\n\t * 2016-11-10 07:33:23, 则返回2016-11-1 00:00:00\n\t */\n\tpublic static Date beginOfMonth(@NotNull final Date date) {\n\t\treturn DateUtils.truncate(date, Calendar.MONTH);\n\t}\n\n\t/**\n\t * 2016-11-10 07:33:23, 则返回2016-11-30 23:59:59.999\n\t */\n\tpublic static Date endOfMonth(@NotNull final Date date) {\n\t\treturn new Date(nextMonth(date).getTime() - 1);\n\t}\n\n\t/**\n\t * 2016-11-10 07:33:23, 则返回2016-12-1 00:00:00\n\t */\n\tpublic static Date nextMonth(@NotNull final Date date) {\n\t\treturn DateUtils.ceiling(date, Calendar.MONTH);\n\t}\n\n\t/**\n\t * 2017-1-20 07:33:23, 则返回2017-1-16 00:00:00\n\t */\n\tpublic static Date beginOfWeek(@NotNull final Date date) {\n\t\treturn DateUtils.truncate(DateUtil.subDays(date, DateUtil.getDayOfWeek(date) - 1), Calendar.DATE);\n\t}\n\n\t/**\n\t * 2017-1-20 07:33:23, 则返回2017-1-22 23:59:59.999\n\t */\n\tpublic static Date endOfWeek(@NotNull final Date date) {\n\t\treturn new Date(nextWeek(date).getTime() - 1);\n\t}\n\n\t/**\n\t * 2017-1-23 07:33:23, 则返回2017-1-22 00:00:00\n\t */\n\tpublic static Date nextWeek(@NotNull final Date date) {\n\t\treturn DateUtils.truncate(DateUtil.addDays(date, 8 - DateUtil.getDayOfWeek(date)), Calendar.DATE);\n\t}\n\n\t/**\n\t * 2016-11-10 07:33:23, 则返回2016-11-10 00:00:00\n\t */\n\tpublic static Date beginOfDate(@NotNull final Date date) {\n\t\treturn DateUtils.truncate(date, Calendar.DATE);\n\t}\n\n\t/**\n\t * 2017-1-23 07:33:23, 则返回2017-1-23 23:59:59.999\n\t */\n\tpublic static Date endOfDate(@NotNull final Date date) {\n\t\treturn new Date(nextDate(date).getTime() - 1);\n\t}\n\n\t/**\n\t * 2016-11-10 07:33:23, 则返回2016-11-11 00:00:00\n\t */\n\tpublic static Date nextDate(@NotNull final Date date) {\n\t\treturn DateUtils.ceiling(date, Calendar.DATE);\n\t}\n\n\t/**\n\t * 2016-12-10 07:33:23, 则返回2016-12-10 07:00:00\n\t */\n\tpublic static Date beginOfHour(@NotNull final Date date) {\n\t\treturn DateUtils.truncate(date, Calendar.HOUR_OF_DAY);\n\t}\n\n\t/**\n\t * 2017-1-23 07:33:23, 则返回2017-1-23 07:59:59.999\n\t */\n\tpublic static Date endOfHour(@NotNull final Date date) {\n\t\treturn new Date(nextHour(date).getTime() - 1);\n\t}\n\n\t/**\n\t * 2016-12-10 07:33:23, 则返回2016-12-10 08:00:00\n\t */\n\tpublic static Date nextHour(@NotNull final Date date) {\n\t\treturn DateUtils.ceiling(date, Calendar.HOUR_OF_DAY);\n\t}\n\n\t/**\n\t * 2016-12-10 07:33:23, 则返回2016-12-10 07:33:00\n\t */\n\tpublic static Date beginOfMinute(@NotNull final Date date) {\n\t\treturn DateUtils.truncate(date, Calendar.MINUTE);\n\t}\n\n\t/**\n\t * 2017-1-23 07:33:23, 则返回2017-1-23 07:33:59.999\n\t */\n\tpublic static Date endOfMinute(@NotNull final Date date) {\n\t\treturn new Date(nextMinute(date).getTime() - 1);\n\t}\n\n\t/**\n\t * 2016-12-10 07:33:23, 则返回2016-12-10 07:34:00\n\t */\n\tpublic static Date nextMinute(@NotNull final Date date) {\n\t\treturn DateUtils.ceiling(date, Calendar.MINUTE);\n\t}\n\n\t////// 闰年及每月天数///////\n\t/**\n\t * 是否闰年.\n\t */\n\tpublic static boolean isLeapYear(@NotNull final Date date) {\n\t\treturn isLeapYear(get(date, Calendar.YEAR));\n\t}\n\n\t/**\n\t * 是否闰年，copy from Jodd Core的TimeUtil\n\t * \n\t * 参数是公元计数, 如2016\n\t */\n\tpublic static boolean isLeapYear(int y) {\n\t\tboolean result = false;\n\n\t\tif (((y % 4) == 0) && // must be divisible by 4...\n\t\t\t\t((y < 1582) || // and either before reform year...\n\t\t\t\t\t\t((y % 100) != 0) || // or not a century...\n\t\t\t\t\t\t((y % 400) == 0))) { // or a multiple of 400...\n\t\t\tresult = true; // for leap year.\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * 获取某个月有多少天, 考虑闰年等因数, 移植Jodd Core的TimeUtil\n\t */\n\tpublic static int getMonthLength(@NotNull final Date date) {\n\t\tint year = get(date, Calendar.YEAR);\n\t\tint month = get(date, Calendar.MONTH);\n\t\treturn getMonthLength(year, month);\n\t}\n\n\t/**\n\t * 获取某个月有多少天, 考虑闰年等因数, 移植Jodd Core的TimeUtil\n\t */\n\tpublic static int getMonthLength(int year, int month) {\n\n\t\tif ((month < 1) || (month > 12)) {\n\t\t\tthrow new IllegalArgumentException(\"Invalid month: \" + month);\n\t\t}\n\t\tif (month == 2) {\n\t\t\treturn isLeapYear(year) ? 29 : 28;\n\t\t}\n\n\t\treturn MONTH_LENGTH[month];\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/main/resources/sys_data_mask.properties",
    "content": "Name=chineseName,userName\nPhone=phone,phoneNum,mobile,tel,telephone\nIDCard=IDCard,IdNo\nBankCard=bankCard\nAddress=address,addr\nEmail=mail,email\nCaptcha=captcha\nPassport=passport\nAccount=account\nPassword=password,passwd\n\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/test/data/RandomData.java",
    "content": "package com.vip.vjtools.test.data;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Random;\n\n/**\n * 随机测试数据生成工具类.\n */\npublic class RandomData {\n\n\tprivate static Random random = new Random();\n\n\t/**\n\t * 返回随机ID.\n\t */\n\tpublic static long randomId() {\n\t\treturn random.nextLong();\n\t}\n\n\t/**\n\t * 返回随机名称, prefix字符串+5位随机数字.\n\t */\n\tpublic static String randomName(String prefix) {\n\t\treturn prefix + random.nextInt(10000);\n\t}\n\n\t/**\n\t * 从输入list中随机返回一个对象.\n\t */\n\tpublic static <T> T randomOne(List<T> list) {\n\t\tCollections.shuffle(list);\n\t\treturn list.get(0);\n\t}\n\n\t/**\n\t * 从输入list中随机返回n个对象.\n\t */\n\tpublic static <T> List<T> randomSome(List<T> list, int n) {\n\t\tCollections.shuffle(list);\n\t\treturn list.subList(0, n);\n\t}\n\n\t/**\n\t * 从输入list中随机返回随机个对象.\n\t */\n\tpublic static <T> List<T> randomSome(List<T> list) {\n\t\tint size = random.nextInt(list.size());\n\t\tif (size == 0) {\n\t\t\tsize = 1;\n\t\t}\n\t\treturn randomSome(list, size);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/test/log/LogbackListAppender.java",
    "content": "package com.vip.vjtools.test.log;\n\nimport java.util.List;\n\nimport org.slf4j.LoggerFactory;\n\nimport com.google.common.collect.Iterables;\nimport com.google.common.collect.Lists;\n\nimport ch.qos.logback.classic.Logger;\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.core.UnsynchronizedAppenderBase;\n\n/**\n * 在List中保存日志的Appender, 用于测试Logback的日志输出.\n * \n * 在测试开始前, 使用任意一种addToLogger()方法将此appender添加到需要侦听的logger中.\n */\npublic class LogbackListAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {\n\n\tprivate final List<ILoggingEvent> logs = Lists.newArrayList();\n\n\tpublic LogbackListAppender() {\n\t\tstart();\n\t}\n\n\tpublic static LogbackListAppender create(Class<?> loggerClass) {\n\t\tLogbackListAppender appender = new LogbackListAppender();\n\t\tappender.addToLogger(loggerClass);\n\t\treturn appender;\n\t}\n\n\tpublic static LogbackListAppender create(String loggerName) {\n\t\tLogbackListAppender appender = new LogbackListAppender();\n\t\tappender.addToLogger(loggerName);\n\t\treturn appender;\n\t}\n\n\t@Override\n\tprotected void append(ILoggingEvent e) {\n\t\tlogs.add(e);\n\t}\n\n\t/**\n\t * 返回之前append的第一个log.\n\t */\n\tpublic ILoggingEvent getFirstLog() {\n\t\tif (logs.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn logs.get(0);\n\t}\n\n\t/**\n\t * 返回之前append的第一个log的内容.\n\t */\n\tpublic String getFirstMessage() {\n\t\tif (logs.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn getFirstLog().getFormattedMessage();\n\t}\n\n\t/**\n\t * 返回之前append的最后一个log.\n\t */\n\tpublic ILoggingEvent getLastLog() {\n\t\tif (logs.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn Iterables.getLast(logs);\n\t}\n\n\t/**\n\t * 返回之前append的最后一个log的内容.\n\t */\n\tpublic String getLastMessage() {\n\t\tif (logs.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn getLastLog().getFormattedMessage();\n\t}\n\n\t/**\n\t * 返回之前append的所有log.\n\t */\n\tpublic List<ILoggingEvent> getAllLogs() {\n\t\treturn logs;\n\t}\n\n\t/**\n\t * 返回Log的数量。\n\t */\n\tpublic int getLogsCount() {\n\t\treturn logs.size();\n\t}\n\n\t/**\n\t * 判断是否有log.\n\t */\n\tpublic boolean isEmpty() {\n\t\treturn logs.isEmpty();\n\t}\n\n\t/**\n\t * 清除之前append的所有log.\n\t */\n\tpublic void clearLogs() {\n\t\tlogs.clear();\n\t}\n\n\t/**\n\t * 将此appender添加到logger中.\n\t */\n\tpublic void addToLogger(String loggerName) {\n\t\tLogger logger = (Logger) LoggerFactory.getLogger(loggerName);\n\t\tlogger.addAppender(this);\n\t}\n\n\t/**\n\t * 将此appender添加到logger中.\n\t */\n\tpublic void addToLogger(Class<?> loggerClass) {\n\t\tLogger logger = (Logger) LoggerFactory.getLogger(loggerClass);\n\t\tlogger.addAppender(this);\n\t}\n\n\t/**\n\t * 将此appender添加到root logger中.\n\t */\n\tpublic void addToRootLogger() {\n\t\tLogger logger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);\n\t\tlogger.addAppender(this);\n\t}\n\n\t/**\n\t * 将此appender从logger中移除.\n\t */\n\tpublic void removeFromLogger(String loggerName) {\n\t\tLogger logger = (Logger) LoggerFactory.getLogger(loggerName);\n\t\tlogger.detachAppender(this);\n\t}\n\n\t/**\n\t * 将此appender从logger中移除.\n\t */\n\tpublic void removeFromLogger(Class<?> loggerClass) {\n\t\tLogger logger = (Logger) LoggerFactory.getLogger(loggerClass);\n\t\tlogger.detachAppender(this);\n\t}\n\n\t/**\n\t * 将此appender从root logger中移除.\n\t */\n\tpublic void removeFromRootLogger() {\n\t\tLogger logger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);\n\t\tlogger.detachAppender(this);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/test/log/LogbackListAppenderTest.java",
    "content": "package com.vip.vjtools.test.log;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class LogbackListAppenderTest {\n\n\t@Test\n\tpublic void normal() {\n\t\tString testString1 = \"Hello\";\n\t\tString testString2 = \"World\";\n\t\tLogbackListAppender appender = new LogbackListAppender();\n\t\tappender.addToLogger(LogbackListAppenderTest.class);\n\n\t\t// null\n\t\tassertThat(appender.getFirstLog()).isNull();\n\t\tassertThat(appender.getLastLog()).isNull();\n\t\tassertThat(appender.getFirstMessage()).isNull();\n\t\tassertThat(appender.getFirstMessage()).isNull();\n\n\t\tLogger logger = LoggerFactory.getLogger(LogbackListAppenderTest.class);\n\t\tlogger.warn(testString1);\n\t\tlogger.warn(testString2);\n\n\t\t// getFirstLog/getLastLog\n\t\tassertThat(appender.getFirstLog().getMessage()).isEqualTo(testString1);\n\t\tassertThat(appender.getLastLog().getMessage()).isEqualTo(testString2);\n\n\t\tassertThat(appender.getFirstMessage()).isEqualTo(testString1);\n\t\tassertThat(appender.getLastMessage()).isEqualTo(testString2);\n\n\t\t// getAllLogs\n\t\tassertThat(appender.getLogsCount()).isEqualTo(2);\n\t\tassertThat(appender.getAllLogs()).hasSize(2);\n\t\tassertThat(appender.getAllLogs().get(1).getMessage()).isEqualTo(testString2);\n\n\t\t// clearLogs\n\t\tappender.clearLogs();\n\t\tassertThat(appender.getFirstLog()).isNull();\n\t\tassertThat(appender.getLastLog()).isNull();\n\t}\n\n\t@Test\n\tpublic void addAndRemoveAppender() {\n\t\tString testString = \"Hello\";\n\t\tLogger logger = LoggerFactory.getLogger(LogbackListAppenderTest.class);\n\t\tLogbackListAppender appender = new LogbackListAppender();\n\t\t// class\n\t\tappender.addToLogger(LogbackListAppenderTest.class);\n\t\tlogger.warn(testString);\n\t\tassertThat(appender.getFirstLog()).isNotNull();\n\n\t\tappender.clearLogs();\n\t\tappender.removeFromLogger(LogbackListAppenderTest.class);\n\t\tlogger.warn(testString);\n\t\tassertThat(appender.getFirstLog()).isNull();\n\n\t\t// name\n\t\tappender.clearLogs();\n\t\tappender.addToLogger(\"com.vip.vjtools.test.log\");\n\t\tlogger.warn(testString);\n\t\tassertThat(appender.getFirstLog()).isNotNull();\n\n\t\tappender.clearLogs();\n\t\tappender.removeFromLogger(\"com.vip.vjtools.test.log\");\n\t\tlogger.warn(testString);\n\t\tassertThat(appender.getFirstLog()).isNull();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/test/rule/TestProgress.java",
    "content": "package com.vip.vjtools.test.rule;\n\nimport org.junit.rules.TestWatcher;\nimport org.junit.runner.Description;\n\n/**\n * 在Console里打印Case的开始与结束，更容易分清Console里的日志归属于哪个Case.\n * \n * @author calvin\n */\npublic class TestProgress extends TestWatcher {\n\n\t@Override\n\tprotected void starting(Description description) {\n\t\tSystem.out.println(\"\\n[Test Case starting] \" + description.getTestClass().getSimpleName() + \".\"\n\t\t\t\t+ description.getMethodName() + \"()\\n\");\n\t}\n\n\t@Override\n\tprotected void finished(Description description) {\n\t\tSystem.out.println(\"\\n[Test Case finished] \" + description.getTestClass().getSimpleName() + \".\"\n\t\t\t\t+ description.getMethodName() + \"()\\n\");\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/BooleanUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class BooleanUtilTest {\n\n\t@Test\n\tpublic void test() {\n\t\tassertThat(BooleanUtil.toBoolean(\"True\")).isTrue();\n\t\tassertThat(BooleanUtil.toBoolean(\"tre\")).isFalse();\n\t\tassertThat(BooleanUtil.toBoolean(null)).isFalse();\n\n\t\tassertThat(BooleanUtil.toBooleanObject(\"True\")).isTrue();\n\t\tassertThat(BooleanUtil.toBooleanObject(\"tre\")).isFalse();\n\t\tassertThat(BooleanUtil.toBooleanObject(null)).isNull();\n\n\t\tassertThat(BooleanUtil.parseGeneralString(\"1\", false)).isFalse();\n\t\tassertThat(BooleanUtil.parseGeneralString(\"y\", false)).isTrue();\n\t\tassertThat(BooleanUtil.parseGeneralString(\"y\")).isTrue();\n\t\tassertThat(BooleanUtil.parseGeneralString(\"x\")).isNull();\n\t}\n\n\t@Test\n\tpublic void logic() {\n\t\tassertThat(BooleanUtil.negate(Boolean.TRUE)).isFalse();\n\t\tassertThat(BooleanUtil.negate(Boolean.FALSE)).isTrue();\n\n\t\tassertThat(BooleanUtil.negate(true)).isFalse();\n\t\tassertThat(BooleanUtil.negate(false)).isTrue();\n\n\t\tassertThat(BooleanUtil.or(Boolean.TRUE, Boolean.TRUE, Boolean.FALSE)).isTrue();\n\t\tassertThat(BooleanUtil.or(false, false, false)).isFalse();\n\n\t\tassertThat(BooleanUtil.and(Boolean.TRUE, Boolean.TRUE, Boolean.FALSE)).isFalse();\n\t\tassertThat(BooleanUtil.and(true, true, true)).isTrue();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/EnumUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.collection.ListUtil;\n\npublic class EnumUtilTest {\n\n\tpublic enum Options {\n\t\tA, B, C, D;\n\t}\n\n\t@Test\n\tpublic void testBits() {\n\t\tassertThat(EnumUtil.generateBits(Options.class, Options.A)).isEqualTo(1);\n\t\tassertThat(EnumUtil.generateBits(Options.class, Options.A, Options.B)).isEqualTo(3);\n\n\t\tassertThat(EnumUtil.generateBits(Options.class, ListUtil.newArrayList(Options.A))).isEqualTo(1);\n\t\tassertThat(EnumUtil.generateBits(Options.class, ListUtil.newArrayList(Options.A, Options.B))).isEqualTo(3);\n\n\t\tassertThat(EnumUtil.processBits(Options.class, 3)).hasSize(2).containsExactly(Options.A, Options.B);\n\t\tassertThat(EnumUtil.processBits(Options.class,\n\t\t\t\tEnumUtil.generateBits(Options.class, Options.A, Options.C, Options.D))).hasSize(3)\n\t\t\t\t\t\t.containsExactly(Options.A, Options.C, Options.D);\n\n\t}\n\n\t@Test\n\tpublic void testString() {\n\t\tassertThat(EnumUtil.toString(Options.A)).isEqualTo(\"A\");\n\n\t\tassertThat(EnumUtil.fromString(Options.class, \"B\")).isEqualTo(Options.B);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/ExceptionUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.io.IOException;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.concurrent.ExecutionException;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.base.type.CloneableException;\nimport com.vip.vjtools.vjkit.base.type.CloneableRuntimeException;\nimport com.vip.vjtools.vjkit.base.type.UncheckedException;\n\npublic class ExceptionUtilTest {\n\n\tprivate static RuntimeException TIMEOUT_EXCEPTION = ExceptionUtil.setStackTrace(new RuntimeException(\"Timeout\"),\n\t\t\tExceptionUtilTest.class, \"hello\");\n\n\tprivate static CloneableException TIMEOUT_EXCEPTION2 = new CloneableException(\"Timeout\")\n\t\t\t.setStackTrace(ExceptionUtilTest.class, \"hello\");\n\n\tprivate static CloneableRuntimeException TIMEOUT_EXCEPTION3 = new CloneableRuntimeException(\"Timeout\")\n\t\t\t.setStackTrace(ExceptionUtilTest.class, \"hello\");\n\n\t@Test\n\tpublic void unchecked() {\n\t\t// convert Exception to RuntimeException with cause\n\t\tException exception = new Exception(\"my exception\");\n\t\ttry {\n\t\t\tExceptionUtil.unchecked(exception);\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t.getCause()).isSameAs(exception);\n\t\t}\n\n\t\t// do nothing of Error\n\t\tError error = new LinkageError();\n\t\ttry {\n\t\t\tExceptionUtil.unchecked(error);\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isSameAs(error);\n\t\t}\n\n\t\t// do nothing of RuntimeException\n\t\tRuntimeException runtimeException = new RuntimeException(\"haha\");\n\t\ttry {\n\t\t\tExceptionUtil.unchecked(runtimeException);\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isSameAs(runtimeException);\n\t\t}\n\n\t}\n\n\t@Test\n\tpublic void unwrap() {\n\t\tRuntimeException re = new RuntimeException(\"my runtime\");\n\t\tassertThat(ExceptionUtil.unwrap(re)).isSameAs(re);\n\n\t\tExecutionException ee = new ExecutionException(re);\n\t\tassertThat(ExceptionUtil.unwrap(ee)).isSameAs(re);\n\n\t\tInvocationTargetException ie = new InvocationTargetException(re);\n\t\tassertThat(ExceptionUtil.unwrap(ie)).isSameAs(re);\n\n\t\tException e = new Exception(\"my exception\");\n\t\tExecutionException ee2 = new ExecutionException(e);\n\t\ttry {\n\t\t\tExceptionUtil.unwrapAndUnchecked(ee2);\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UncheckedException.class).hasCauseExactlyInstanceOf(Exception.class);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void getStackTraceAsString() {\n\t\tException exception = new Exception(\"my exception\");\n\t\tRuntimeException runtimeException = new RuntimeException(exception);\n\n\t\tString stack = ExceptionUtil.stackTraceText(runtimeException);\n\t\tSystem.out.println(stack);\n\t}\n\n\t@Test\n\tpublic void cause() {\n\t\tIOException ioexception = new IOException(\"my exception\");\n\t\tIllegalStateException illegalStateException = new IllegalStateException(ioexception);\n\t\tRuntimeException runtimeException = new RuntimeException(illegalStateException);\n\n\t\tassertThat(ExceptionUtil.isCausedBy(runtimeException, IOException.class)).isTrue();\n\t\tassertThat(ExceptionUtil.isCausedBy(runtimeException, IllegalStateException.class, IOException.class)).isTrue();\n\t\tassertThat(ExceptionUtil.isCausedBy(runtimeException, Exception.class)).isTrue();\n\t\tassertThat(ExceptionUtil.isCausedBy(runtimeException, IllegalAccessException.class)).isFalse();\n\n\t\tassertThat(ExceptionUtil.findCause(runtimeException, IllegalStateException.class))\n\t\t\t\t.isSameAs(illegalStateException);\n\t\tassertThat(ExceptionUtil.findCause(runtimeException, IOException.class)).isSameAs(ioexception);\n\t\tassertThat(ExceptionUtil.findCause(runtimeException, UncheckedException.class)).isNull();\n\t}\n\n\t@Test\n\tpublic void getRootCause() {\n\t\tIOException ioexception = new IOException(\"my exception\");\n\t\tIllegalStateException illegalStateException = new IllegalStateException(ioexception);\n\t\tRuntimeException runtimeException = new RuntimeException(illegalStateException);\n\n\t\tassertThat(ExceptionUtil.getRootCause(runtimeException)).isSameAs(ioexception);\n\t\t// 无cause\n\t\tassertThat(ExceptionUtil.getRootCause(ioexception)).isSameAs(ioexception);\n\t}\n\n\t@Test\n\tpublic void buildMessage() {\n\t\tIOException ioexception = new IOException(\"my exception\");\n\t\tassertThat(ExceptionUtil.toStringWithShortName(ioexception)).isEqualTo(\"IOException: my exception\");\n\t\tassertThat(ExceptionUtil.toStringWithShortName(null)).isEqualTo(\"\");\n\n\t\tRuntimeException runtimeExcetpion = new RuntimeException(\"my runtimeException\", ioexception);\n\t\tassertThat(ExceptionUtil.toStringWithRootCause(runtimeExcetpion))\n\t\t\t\t.isEqualTo(\"RuntimeException: my runtimeException; <---IOException: my exception\");\n\n\t\tassertThat(ExceptionUtil.toStringWithRootCause(null)).isEqualTo(\"\");\n\t\t// 无cause\n\t\tassertThat(ExceptionUtil.toStringWithRootCause(ioexception)).isEqualTo(\"IOException: my exception\");\n\t}\n\n\t@Test\n\tpublic void clearStackTrace() {\n\t\tIOException ioexception = new IOException(\"my exception\");\n\t\tRuntimeException runtimeException = new RuntimeException(ioexception);\n\n\t\tSystem.out.println(ExceptionUtil.stackTraceText(ExceptionUtil.clearStackTrace(runtimeException)));\n\n\t}\n\n\t@Test\n\tpublic void staticException() {\n\t\tassertThat(ExceptionUtil.stackTraceText(TIMEOUT_EXCEPTION)).hasLineCount(2)\n\t\t\t\t.contains(\"java.lang.RuntimeException: Timeout\")\n\t\t\t\t.contains(\"at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)\");\n\n\t\tassertThat(ExceptionUtil.stackTraceText(TIMEOUT_EXCEPTION2)).hasLineCount(2)\n\t\t\t\t.contains(\"com.vip.vjtools.vjkit.base.type.CloneableException: Timeout\")\n\t\t\t\t.contains(\"at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)\");\n\n\t\tCloneableException timeoutException = TIMEOUT_EXCEPTION2.clone(\"Timeout for 30ms\");\n\t\tassertThat(ExceptionUtil.stackTraceText(timeoutException)).hasLineCount(2)\n\t\t\t\t.contains(\"com.vip.vjtools.vjkit.base.type.CloneableException: Timeout for 30ms\")\n\t\t\t\t.contains(\"at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)\");\n\n\t\tassertThat(ExceptionUtil.stackTraceText(TIMEOUT_EXCEPTION3)).hasLineCount(2)\n\t\t\t\t.contains(\"com.vip.vjtools.vjkit.base.type.CloneableRuntimeException: Timeout\")\n\t\t\t\t.contains(\"at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)\");\n\n\t\tCloneableRuntimeException timeoutRuntimeException = TIMEOUT_EXCEPTION3.clone(\"Timeout for 40ms\");\n\t\tassertThat(ExceptionUtil.stackTraceText(timeoutRuntimeException)).hasLineCount(2)\n\t\t\t\t.contains(\"com.vip.vjtools.vjkit.base.type.CloneableRuntimeException: Timeout for 40ms\")\n\t\t\t\t.contains(\"at com.vip.vjtools.vjkit.base.ExceptionUtilTest.hello(Unknown Source)\");\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/MoreValidateTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nimport org.junit.Test;\n\npublic class MoreValidateTest {\n\n\t@Test\n\tpublic void test() {\n\t\t// int\n\t\tint a = MoreValidate.nonNegative(\"x\", 0);\n\t\tassertThat(a).isEqualTo(0);\n\t\ta = MoreValidate.nonNegative(\"x\", 1);\n\t\tassertThat(a).isEqualTo(1);\n\n\t\ta = MoreValidate.positive(\"x\", 1);\n\t\tassertThat(a).isEqualTo(1);\n\n\t\t// Integer\n\t\tInteger c = MoreValidate.nonNegative(\"x\", Integer.valueOf(0));\n\t\tassertThat(c).isEqualTo(0);\n\t\tc = MoreValidate.nonNegative(\"x\", Integer.valueOf(21));\n\t\tassertThat(c).isEqualTo(21);\n\n\t\tc = MoreValidate.positive(\"x\", Integer.valueOf(1));\n\t\tassertThat(c).isEqualTo(1);\n\n\t\t// long\n\t\tlong b = MoreValidate.nonNegative(\"x\", 0l);\n\t\tassertThat(b).isEqualTo(0);\n\n\t\tb = MoreValidate.nonNegative(\"x\", 11l);\n\t\tassertThat(b).isEqualTo(11);\n\n\t\tb = MoreValidate.positive(\"x\", 1l);\n\t\tassertThat(b).isEqualTo(1);\n\n\t\tdouble e = MoreValidate.nonNegative(\"x\", 0l);\n\t\tassertThat(e).isEqualTo(0);\n\n\t\te = MoreValidate.nonNegative(\"x\", 11d);\n\t\tassertThat(e).isEqualTo(11);\n\n\t\te = MoreValidate.positive(\"x\", 1.1d);\n\t\tassertThat(e).isEqualTo(1.1);\n\n\t\t// Long\n\t\tLong d = MoreValidate.nonNegative(\"x\", Long.valueOf(0));\n\t\tassertThat(d).isEqualTo(0);\n\n\t\td = MoreValidate.positive(\"x\", Long.valueOf(1));\n\t\tassertThat(d).isEqualTo(1);\n\n\t\td = MoreValidate.nonNegative(\"x\", Long.valueOf(11));\n\t\tassertThat(d).isEqualTo(11);\n\n\t\t// int\n\t\ttry {\n\t\t\tMoreValidate.nonNegative(\"x\", -1);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).hasMessage(\"x (-1) must be >= 0\");\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tMoreValidate.nonNegative(null, -1);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).hasMessage(\"null (-1) must be >= 0\");\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\n\t\t}\n\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", -1);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", 0);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\t// long\n\t\ttry {\n\t\t\tMoreValidate.nonNegative(\"x\", -1l);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", -1l);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", 0l);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\t// Long\n\t\ttry {\n\t\t\tMoreValidate.nonNegative(\"x\", Long.valueOf(-1));\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", Long.valueOf(-1));\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", Long.valueOf(0));\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\t// Integer\n\t\ttry {\n\t\t\tMoreValidate.nonNegative(\"x\", Integer.valueOf(-1));\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", Integer.valueOf(-1));\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", Integer.valueOf(0));\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\t// double\n\t\ttry {\n\t\t\tMoreValidate.nonNegative(\"x\", -9999.2d);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", -1.2d);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\t\ttry {\n\t\t\tMoreValidate.positive(\"x\", 0d);\n\t\t\tfail(\"fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/ObjectUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.collection.ListUtil;\n\npublic class ObjectUtilTest {\n\n\t@Test\n\tpublic void hashCodeTest() {\n\t\tassertThat(ObjectUtil.hashCode(\"a\", \"b\") - ObjectUtil.hashCode(\"a\", \"a\")).isEqualTo(1);\n\t}\n\n\t@Test\n\tpublic void toPrettyString() {\n\t\tassertThat(ObjectUtil.toPrettyString(null)).isEqualTo(\"null\");\n\t\tassertThat(ObjectUtil.toPrettyString(1)).isEqualTo(\"1\");\n\n\t\tassertThat(ObjectUtil.toPrettyString(new int[] { 1, 2 })).isEqualTo(\"[1, 2]\");\n\t\tassertThat(ObjectUtil.toPrettyString(new long[] { 1, 2 })).isEqualTo(\"[1, 2]\");\n\t\tassertThat(ObjectUtil.toPrettyString(new double[] { 1.1d, 2.2d })).isEqualTo(\"[1.1, 2.2]\");\n\t\tassertThat(ObjectUtil.toPrettyString(new float[] { 1.1f, 2.2f })).isEqualTo(\"[1.1, 2.2]\");\n\t\tassertThat(ObjectUtil.toPrettyString(new boolean[] { true, false })).isEqualTo(\"[true, false]\");\n\t\tassertThat(ObjectUtil.toPrettyString(new short[] { 1, 2 })).isEqualTo(\"[1, 2]\");\n\t\tassertThat(ObjectUtil.toPrettyString(new byte[] { 1, 2 })).isEqualTo(\"[1, 2]\");\n\n\t\tassertThat(ObjectUtil.toPrettyString(new Integer[] { 1, 2 })).isEqualTo(\"[1, 2]\");\n\t\tassertThat(ObjectUtil.toPrettyString(ListUtil.newArrayList(\"1\", \"2\"))).isEqualTo(\"{1,2}\");\n\n\t\tSystem.out.println(new Integer[] { 1, 2 }.toString());\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/PairTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.base.type.Pair;\nimport com.vip.vjtools.vjkit.base.type.Triple;\n\npublic class PairTest {\n\n\t@Test\n\tpublic void pairTest() {\n\t\tPair<String, Integer> pair = Pair.of(\"haha\", 1);\n\t\tPair<String, Integer> pair2 = Pair.of(\"haha\", 2);\n\t\tPair<String, Integer> pair3 = Pair.of(\"kaka\", 1);\n\n\t\tassertThat(pair.equals(pair2)).isFalse();\n\t\tassertThat(pair.equals(pair3)).isFalse();\n\t\tassertThat(pair.hashCode() != pair2.hashCode()).isTrue();\n\t\tassertThat(pair.toString()).isEqualTo(\"Pair [left=haha, right=1]\");\n\n\t\tassertThat(pair.getLeft()).isEqualTo(\"haha\");\n\t\tassertThat(pair.getRight()).isEqualTo(1);\n\t}\n\n\t@Test\n\tpublic void tripleTest() {\n\t\tTriple<String, String, Integer> triple = Triple.of(\"haha\", \"hehe\", 1);\n\t\tTriple<String, String, Integer> triple2 = Triple.of(\"haha\", \"hehe\", 2);\n\t\tTriple<String, String, Integer> triple3 = Triple.of(\"haha\", \"lala\", 2);\n\t\tTriple<String, String, Integer> triple4 = Triple.of(\"kaka\", \"lala\", 2);\n\n\n\t\tPair<String, Integer> pair = Pair.of(\"haha\", 1);\n\t\tassertThat(triple.equals(triple2)).isFalse();\n\t\tassertThat(triple.equals(triple3)).isFalse();\n\t\tassertThat(triple.equals(triple4)).isFalse();\n\t\tassertThat(triple.equals(pair)).isFalse();\n\t\tassertThat(triple.hashCode() != triple2.hashCode()).isTrue();\n\t\tassertThat(triple.toString()).isEqualTo(\"Triple [left=haha, middle=hehe, right=1]\");\n\n\t\tassertThat(triple.getLeft()).isEqualTo(\"haha\");\n\t\tassertThat(triple.getMiddle()).isEqualTo(\"hehe\");\n\t\tassertThat(triple.getRight()).isEqualTo(1);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/PlatformsTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class PlatformsTest {\n\n\t@Test\n\tpublic void PlatformTest() {\n\n\t\tif (Platforms.IS_WINDOWS) {\n\t\t\tassertThat(Platforms.FILE_PATH_SEPARATOR).isEqualTo(\"\\\\\");\n\t\t\tassertThat(Platforms.FILE_PATH_SEPARATOR_CHAR).isEqualTo('\\\\');\n\t\t} else {\n\t\t\tassertThat(Platforms.FILE_PATH_SEPARATOR).isEqualTo(\"/\");\n\t\t\tassertThat(Platforms.FILE_PATH_SEPARATOR_CHAR).isEqualTo('/');\n\t\t}\n\n\t\tSystem.out.println(\"OS_NAME:\" + Platforms.OS_NAME);\n\t\tSystem.out.println(\"OS_VERSION:\" + Platforms.OS_VERSION);\n\t\tSystem.out.println(\"OS_ARCH:\" + Platforms.OS_ARCH);\n\t\tSystem.out.println(\"JAVA_SPECIFICATION_VERSION:\" + Platforms.JAVA_SPECIFICATION_VERSION);\n\t\tSystem.out.println(\"JAVA_VERSION:\" + Platforms.JAVA_VERSION);\n\t\tSystem.out.println(\"JAVA_HOME:\" + Platforms.JAVA_HOME);\n\t\tSystem.out.println(\"USER_HOME:\" + Platforms.USER_HOME);\n\t\tSystem.out.println(\"TMP_DIR:\" + Platforms.TMP_DIR);\n\t\tSystem.out.println(\"WORKING_DIR:\" + Platforms.WORKING_DIR);\n\n\t\tif (Platforms.IS_JAVA7) {\n\t\t\tassertThat(Platforms.IS_ATLEASET_JAVA7).isTrue();\n\t\t\tassertThat(Platforms.IS_ATLEASET_JAVA8).isFalse();\n\t\t}\n\n\t\tif (Platforms.IS_JAVA8) {\n\t\t\tassertThat(Platforms.IS_ATLEASET_JAVA7).isTrue();\n\t\t\tassertThat(Platforms.IS_ATLEASET_JAVA8).isTrue();\n\t\t}\n\n\t}\n\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/PropertiesUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.Properties;\n\nimport org.junit.Test;\n\npublic class PropertiesUtilTest {\n\n\t@Test\n\tpublic void loadProperties() {\n\t\tProperties p1 = PropertiesUtil.loadFromFile(\"classpath:application.properties\");\n\t\tassertThat(p1.get(\"springside.min\")).isEqualTo(\"1\");\n\t\tassertThat(p1.get(\"springside.max\")).isEqualTo(\"10\");\n\n\t\tProperties p2 = PropertiesUtil.loadFromString(\"springside.min=1\\nspringside.max=10\\nisOpen=true\");\n\t\tassertThat(PropertiesUtil.getInt(p2, \"springside.min\", 0)).isEqualTo(1);\n\t\tassertThat(PropertiesUtil.getInt(p2, \"springside.max\", 0)).isEqualTo(10);\n\t\tassertThat(PropertiesUtil.getInt(p2, \"springside.maxA\", 0)).isEqualTo(0);\n\n\t\tassertThat(PropertiesUtil.getLong(p2, \"springside.min\", 0L)).isEqualTo(1);\n\t\tassertThat(PropertiesUtil.getLong(p2, \"springside.max\", 0L)).isEqualTo(10);\n\t\tassertThat(PropertiesUtil.getLong(p2, \"springside.maxA\", 0L)).isEqualTo(0);\n\n\t\tassertThat(PropertiesUtil.getDouble(p2, \"springside.min\", 0d)).isEqualTo(1);\n\t\tassertThat(PropertiesUtil.getDouble(p2, \"springside.max\", 0d)).isEqualTo(10);\n\t\tassertThat(PropertiesUtil.getDouble(p2, \"springside.maxA\", 0d)).isEqualTo(0);\n\n\t\tassertThat(PropertiesUtil.getString(p2, \"springside.min\", \"\")).isEqualTo(\"1\");\n\t\tassertThat(PropertiesUtil.getString(p2, \"springside.max\", \"\")).isEqualTo(\"10\");\n\t\tassertThat(PropertiesUtil.getString(p2, \"springside.maxA\", \"\")).isEqualTo(\"\");\n\n\t\tassertThat(PropertiesUtil.getBoolean(p2, \"isOpen\", false)).isTrue();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/RuntimeUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class RuntimeUtilTest {\n\t@Test\n\tpublic void testRuntime() {\n\t\tSystem.out.println(\"pid:\" + RuntimeUtil.getPid());\n\t\tassertThat(RuntimeUtil.getPid()).isNotEqualTo(-1);\n\t\tSystem.out.println(\"vmargs:\" + RuntimeUtil.getVmArguments());\n\t\tRuntimeUtil.addShutdownHook(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSystem.out.println(\"systemShutdowning\");\n\t\t\t}\n\t\t});\n\n\t\tassertThat(RuntimeUtil.getCores()).isGreaterThan(1);\n\n\t\tSystem.out.println(\"uptime\" + RuntimeUtil.getUpTime());\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/SystemPropertiesUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.base.SystemPropertiesUtil.PropertiesListener;\nimport com.vip.vjtools.vjkit.number.RandomUtil;\n\npublic class SystemPropertiesUtilTest {\n\n\t@Test\n\tpublic void systemProperty() {\n\t\tString name = \"ss.test\" + RandomUtil.nextInt();\n\n\t\tBoolean result0 = SystemPropertiesUtil.getBoolean(name);\n\t\tassertThat(result0).isNull();\n\n\t\tBoolean result1 = SystemPropertiesUtil.getBoolean(name, null);\n\t\tassertThat(result1).isNull();\n\n\t\tBoolean result3 = SystemPropertiesUtil.getBoolean(name, Boolean.TRUE);\n\t\tassertThat(result3).isTrue();\n\n\t\tSystem.setProperty(name, \"true\");\n\n\t\tBoolean result5 = SystemPropertiesUtil.getBoolean(name, Boolean.FALSE);\n\t\tassertThat(result5).isTrue();\n\n\t\tSystem.clearProperty(name);\n\n\t\t/// int\n\t\tInteger result6 = SystemPropertiesUtil.getInteger(name);\n\t\tassertThat(result6).isNull();\n\n\t\tresult6 = SystemPropertiesUtil.getInteger(name, 1);\n\t\tassertThat(result6).isEqualTo(1);\n\n\t\tSystem.setProperty(name, \"2\");\n\t\tresult6 = SystemPropertiesUtil.getInteger(name, 1);\n\t\tassertThat(result6).isEqualTo(2);\n\n\t\tSystem.clearProperty(name);\n\n\t\t///// long\n\t\tLong result7 = SystemPropertiesUtil.getLong(name);\n\t\tassertThat(result7).isNull();\n\n\t\tresult7 = SystemPropertiesUtil.getLong(name, 1L);\n\t\tassertThat(result7).isEqualTo(1L);\n\n\t\tSystem.setProperty(name, \"2\");\n\t\tresult7 = SystemPropertiesUtil.getLong(name, 1L);\n\t\tassertThat(result7).isEqualTo(2L);\n\n\t\tSystem.clearProperty(name);\n\n\t\t///// doulbe\n\t\tDouble result8 = SystemPropertiesUtil.getDouble(name);\n\t\tassertThat(result8).isNull();\n\n\t\tresult8 = SystemPropertiesUtil.getDouble(name, 1.1);\n\t\tassertThat(result8).isEqualTo(1.1);\n\n\t\tSystem.setProperty(name, \"2.1\");\n\t\tresult8 = SystemPropertiesUtil.getDouble(name, 1.1);\n\t\tassertThat(result8).isEqualTo(2.1);\n\n\t\tSystem.clearProperty(name);\n\n\t\t///// String\n\t\tString result9 = SystemPropertiesUtil.getString(name);\n\t\tassertThat(result9).isNull();\n\n\t\tresult9 = SystemPropertiesUtil.getString(name, \"1.1\");\n\t\tassertThat(result9).isEqualTo(\"1.1\");\n\n\t\tSystem.setProperty(name, \"2.1\");\n\t\tresult9 = SystemPropertiesUtil.getString(name, \"1.1\");\n\t\tassertThat(result9).isEqualTo(\"2.1\");\n\n\t\tSystem.clearProperty(name);\n\n\t}\n\n\t@Test\n\tpublic void stringSystemProperty() {\n\t\tString name = \"ss.test\" + RandomUtil.nextInt();\n\t\tString envName = \"ss_test\" + RandomUtil.nextInt();\n\n\t\t// default 值\n\t\tString result = SystemPropertiesUtil.getString(name, envName, \"123\");\n\t\tassertThat(result).isEqualTo(\"123\");\n\n\t\t// env值\n\t\tString result2 = SystemPropertiesUtil.getString(name, \"PATH\", \"123\");\n\t\tassertThat(result2).isNotEqualTo(\"123\");\n\n\t\t// system properties值\n\t\tSystem.setProperty(name, \"456\");\n\t\tString result3 = SystemPropertiesUtil.getString(name, envName, \"123\");\n\t\tassertThat(result3).isEqualTo(\"456\");\n\n\t\ttry {\n\t\t\t// 非法字符\n\t\t\tString result4 = SystemPropertiesUtil.getString(name, name, \"123\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Exception e) {\n\t\t\tassertThat(e).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\tSystem.clearProperty(name);\n\t}\n\n\t@Test\n\tpublic void intSystemProperty() {\n\t\tString name = \"ss.test\" + RandomUtil.nextInt();\n\t\tString envName = \"ss_test\" + RandomUtil.nextInt();\n\n\t\t// default 值\n\t\tint result = SystemPropertiesUtil.getInteger(name, envName, 123);\n\t\tassertThat(result).isEqualTo(123);\n\n\t\t// env值没有数字类型的，忽略\n\n\t\t// system properties值\n\t\tSystem.setProperty(name, \"456\");\n\t\tint result3 = SystemPropertiesUtil.getInteger(name, envName, 123);\n\t\tassertThat(result3).isEqualTo(456);\n\n\t\tSystem.clearProperty(name);\n\t}\n\n\t@Test\n\tpublic void longSystemProperty() {\n\t\tString name = \"ss.test\" + RandomUtil.nextInt();\n\t\tString envName = \"ss_test\" + RandomUtil.nextInt();\n\n\t\t// default 值\n\t\tlong result = SystemPropertiesUtil.getLong(name, envName, 123L);\n\t\tassertThat(result).isEqualTo(123L);\n\n\t\t// env值没有数字类型的，忽略\n\n\t\t// system properties值\n\t\tSystem.setProperty(name, \"456\");\n\t\tlong result3 = SystemPropertiesUtil.getLong(name, envName, 123L);\n\t\tassertThat(result3).isEqualTo(456L);\n\n\t\tSystem.clearProperty(name);\n\t}\n\n\t@Test\n\tpublic void doubleSystemProperty() {\n\t\tString name = \"ss.test\" + RandomUtil.nextInt();\n\t\tString envName = \"ss_test\" + RandomUtil.nextInt();\n\n\t\t// default 值\n\t\tdouble result = SystemPropertiesUtil.getDouble(name, envName, 123d);\n\t\tassertThat(result).isEqualTo(123d);\n\n\t\t// env值没有数字类型的，忽略\n\n\t\t// system properties值\n\t\tSystem.setProperty(name, \"456\");\n\t\tdouble result3 = SystemPropertiesUtil.getDouble(name, envName, 123d);\n\t\tassertThat(result3).isEqualTo(456d);\n\n\t\tSystem.clearProperty(name);\n\t}\n\n\t@Test\n\tpublic void booleanSystemProperty() {\n\t\tString name = \"ss.test\" + RandomUtil.nextInt();\n\t\tString envName = \"ss_test\" + RandomUtil.nextInt();\n\n\t\t// default 值\n\t\tboolean result = SystemPropertiesUtil.getBoolean(name, envName, true);\n\t\tassertThat(result).isTrue();\n\n\t\t// env值没有boolean类型的，忽略\n\n\t\t// system properties值\n\t\tSystem.setProperty(name, \"true\");\n\t\tboolean result3 = SystemPropertiesUtil.getBoolean(name, envName, false);\n\t\tassertThat(result3).isTrue();\n\n\t\tSystem.clearProperty(name);\n\t}\n\n\t@Test\n\tpublic void listenableProperties() {\n\t\tString name = \"ss.test\" + RandomUtil.nextInt();\n\n\t\tTestPropertiesListener listener = new TestPropertiesListener(name);\n\t\tSystemPropertiesUtil.registerSystemPropertiesListener(listener);\n\n\t\tSystem.setProperty(name, \"haha\");\n\n\t\tassertThat(listener.newValue).isEqualTo(\"haha\");\n\t}\n\n\tpublic static class TestPropertiesListener extends PropertiesListener {\n\n\t\tpublic TestPropertiesListener(String propertyName) {\n\t\t\tsuper(propertyName);\n\t\t}\n\n\t\tpublic String newValue;\n\n\t\t@Override\n\t\tpublic void onChange(String propertyName, String value) {\n\t\t\tnewValue = value;\n\t\t}\n\n\t};\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/base/ValueValidatorTest.java",
    "content": "package com.vip.vjtools.vjkit.base;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.base.ValueValidator.Validator;\n\npublic class ValueValidatorTest {\n\t@Test\n\tpublic void testValidator() {\n\t\tassertThat(ValueValidator.checkAndGet(-1, 1, Validator.INTEGER_GT_ZERO_VALIDATOR)).isEqualTo(1);\n\t\tassertThat(ValueValidator.checkAndGet(\"testUnEmpty\", \"isEmpty\", Validator.STRING_EMPTY_VALUE_VALIDATOR))\n\t\t\t\t.isEqualTo(\"testUnEmpty\");\n\t\tassertThat(ValueValidator.checkAndGet(\"flase\", \"true\", Validator.STRICT_BOOL_VALUE_VALIDATOR))\n\t\t\t\t.isEqualTo(\"true\");\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/collection/ArrayUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\nimport static org.junit.Assert.assertFalse;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.number.RandomUtil;\n\npublic class ArrayUtilTest {\n\n\t@Test\n\tpublic void shuffle() {\n\t\tString[] arrays = new String[] { \"d\", \"a\", \"c\", \"b\", \"e\", \"i\", \"g\" };\n\t\tString[] arraysClone = Arrays.copyOf(arrays, arrays.length);\n\t\tArrays.sort(arrays);\n\t\tassertThat(arrays).containsExactly(\"a\", \"b\", \"c\", \"d\", \"e\", \"g\", \"i\");\n\t\tArrayUtil.shuffle(arrays);\n\t\tassertFalse(\"should not be equal to origin array\", Arrays.equals(arrays, arraysClone));\n\t\t// System.out.println(Arrays.toString(arrays));\n\t\tArrays.sort(arrays);\n\t\tArrayUtil.shuffle(arrays, RandomUtil.secureRandom());\n\t\tassertFalse(\"should not be equal to origin array\", Arrays.equals(arrays, arraysClone));\n\t}\n\n\t@Test\n\tpublic void asList() {\n\t\tList<String> list = ArrayUtil.asList(\"d\", \"a\", \"c\", \"b\", \"e\", \"i\", \"g\");\n\t\tassertThat(list).hasSize(7).containsExactly(\"d\", \"a\", \"c\", \"b\", \"e\", \"i\", \"g\");\n\n\t\ttry {\n\t\t\tlist.add(\"a\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\n\t\tList<Integer> list3 = ArrayUtil.intAsList(1, 2, 3);\n\t\tassertThat(list3).hasSize(3).containsExactly(1, 2, 3);\n\n\t\tList<Long> list4 = ArrayUtil.longAsList(1L, 2L, 3L);\n\t\tassertThat(list4).hasSize(3).containsExactly(1L, 2L, 3L);\n\n\t\tList<Double> list5 = ArrayUtil.doubleAsList(1.1d, 2.2d, 3.3d);\n\t\tassertThat(list5).hasSize(3).containsExactly(1.1d, 2.2d, 3.3d);\n\t}\n\n\t@Test\n\tpublic void contact() {\n\t\tString[] array = new String[] { \"d\", \"a\", \"c\" };\n\t\tassertThat(ArrayUtil.concat(\"z\", array)).containsExactly(\"z\", \"d\", \"a\", \"c\");\n\t\tassertThat(ArrayUtil.concat(array, \"z\")).containsExactly(\"d\", \"a\", \"c\", \"z\");\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/collection/CollectionUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport org.junit.Test;\n\nimport com.google.common.collect.Ordering;\n\npublic class CollectionUtilTest {\n\n\t@Test\n\tpublic void test() {\n\t\tList<String> list1 = ListUtil.newArrayList();\n\n\t\tList<String> list2 = ListUtil.newArrayList(\"a\", \"b\", \"c\");\n\t\tList<String> list3 = ListUtil.newArrayList(\"a\");\n\n\t\tSet<String> set1 = SetUtil.newSortedSet();\n\t\tset1.add(\"a\");\n\t\tset1.add(\"b\");\n\t\tset1.add(\"c\");\n\n\t\tSet<String> set2 = SetUtil.newSortedSet();\n\t\tset2.add(\"a\");\n\n\t\tassertThat(CollectionUtil.isEmpty(list1)).isTrue();\n\t\tassertThat(CollectionUtil.isEmpty(null)).isTrue();\n\t\tassertThat(CollectionUtil.isEmpty(list2)).isFalse();\n\n\t\tassertThat(CollectionUtil.isNotEmpty(list1)).isFalse();\n\t\tassertThat(CollectionUtil.isNotEmpty(null)).isFalse();\n\t\tassertThat(CollectionUtil.isNotEmpty(list2)).isTrue();\n\n\t\tassertThat(CollectionUtil.getFirst(list2)).isEqualTo(\"a\");\n\t\tassertThat(CollectionUtil.getLast(list2)).isEqualTo(\"c\");\n\n\t\tassertThat(CollectionUtil.getFirst(set1)).isEqualTo(\"a\");\n\t\tassertThat(CollectionUtil.getLast(set1)).isEqualTo(\"c\");\n\n\t\tassertThat(CollectionUtil.getFirst(list3)).isEqualTo(\"a\");\n\t\tassertThat(CollectionUtil.getLast(list3)).isEqualTo(\"a\");\n\n\t\tassertThat(CollectionUtil.getFirst(set2)).isEqualTo(\"a\");\n\t\tassertThat(CollectionUtil.getLast(set2)).isEqualTo(\"a\");\n\n\t\tassertThat(CollectionUtil.getFirst(list1)).isNull();\n\t\tassertThat((List<String>) CollectionUtil.getFirst(null)).isNull();\n\t\tassertThat(CollectionUtil.getLast(list1)).isNull();\n\t\tassertThat((List<String>) CollectionUtil.getLast(null)).isNull();\n\t}\n\n\t@Test\n\tpublic void minAndMax() {\n\t\tList<Integer> list = ListUtil.newArrayList(4, 1, 9, 100, 20, 101, 40);\n\n\t\tassertThat(CollectionUtil.min(list)).isEqualTo(1);\n\t\tassertThat(CollectionUtil.min(list, Ordering.natural())).isEqualTo(1);\n\t\tassertThat(CollectionUtil.max(list)).isEqualTo(101);\n\t\tassertThat(CollectionUtil.max(list, Ordering.natural())).isEqualTo(101);\n\n\t\tassertThat(CollectionUtil.minAndMax(list).getLeft()).isEqualTo(1);\n\t\tassertThat(CollectionUtil.minAndMax(list).getRight()).isEqualTo(101);\n\n\t\tassertThat(CollectionUtil.minAndMax(list, Ordering.natural()).getLeft()).isEqualTo(1);\n\t\tassertThat(CollectionUtil.minAndMax(list, Ordering.natural()).getRight()).isEqualTo(101);\n\t}\n\n\t@Test\n\tpublic void listCompare() {\n\t\tList<String> list1 = ArrayUtil.asList(\"d\", \"a\", \"c\", \"b\", \"e\", \"i\", \"g\");\n\t\tList<String> list2 = ArrayUtil.asList(\"d\", \"a\", \"c\", \"b\", \"e\", \"i\", \"g\");\n\n\t\tList<String> list3 = ArrayUtil.asList(\"d\", \"c\", \"a\", \"b\", \"e\", \"i\", \"g\");\n\t\tList<String> list4 = ArrayUtil.asList(\"d\", \"a\", \"c\", \"b\", \"e\");\n\t\tList<String> list5 = ArrayUtil.asList(\"d\", \"a\", \"c\", \"b\", \"e\", \"i\", \"g\", \"x\");\n\n\t\tassertThat(CollectionUtil.elementsEqual(list1, list1)).isTrue();\n\t\tassertThat(CollectionUtil.elementsEqual(list1, list2)).isTrue();\n\n\t\tassertThat(CollectionUtil.elementsEqual(list1, list3)).isFalse();\n\t\tassertThat(CollectionUtil.elementsEqual(list1, list4)).isFalse();\n\t\tassertThat(CollectionUtil.elementsEqual(list1, list5)).isFalse();\n\t}\n\n\t@Test\n\tpublic void topNAndBottomN() {\n\t\tList<Integer> list = ArrayUtil.asList(3, 5, 7, 4, 2, 6, 9);\n\n\t\tassertThat(CollectionUtil.topN(list, 3)).containsExactly(9, 7, 6);\n\t\tassertThat(CollectionUtil.topN(list, 3, Ordering.natural().reverse())).containsExactly(2, 3, 4);\n\t\tassertThat(CollectionUtil.bottomN(list, 3)).containsExactly(2, 3, 4);\n\t\tassertThat(CollectionUtil.bottomN(list, 3, Ordering.natural().reverse())).containsExactly(9, 7, 6);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/collection/ListUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.List;\nimport java.util.Random;\n\nimport org.junit.Test;\n\nimport com.google.common.collect.Ordering;\n\npublic class ListUtilTest {\n\t@Test\n\tpublic void guavaBuildList() {\n\t\tList<String> list1 = ListUtil.newArrayList();\n\n\t\tList<String> list2 = ListUtil.newArrayList(\"a\", \"b\");\n\t\tassertThat(list2).hasSize(2).containsExactly(\"a\", \"b\");\n\n\t\tList<String> list3 = ListUtil.newArrayList(SetUtil.newHashSet(\"a\", \"b\"));\n\t\tassertThat(list2).hasSize(2).containsExactly(\"a\", \"b\");\n\n\t\tList<String> list4 = ListUtil.newArrayListWithCapacity(10);\n\n\t\tList<String> list5 = ListUtil.newCopyOnWriteArrayList();\n\n\t\tList<String> list6 = ListUtil.newCopyOnWriteArrayList(\"a\", \"b\");\n\t\tassertThat(list6).hasSize(2).containsExactly(\"a\", \"b\");\n\n\t\tList<String> list7 = ListUtil.newLinkedList();\n\t}\n\n\t@Test\n\tpublic void jdkBuild() {\n\t\tList<String> list1 = ListUtil.emptyList();\n\n\t\tassertThat(list1).hasSize(0);\n\n\t\tList<String> list2 = ListUtil.emptyListIfNull(null);\n\t\tassertThat(list2).isNotNull().hasSize(0);\n\n\t\tList<String> list3 = ListUtil.emptyListIfNull(list1);\n\t\tassertThat(list3).isSameAs(list1);\n\n\t\ttry {\n\t\t\tlist1.add(\"a\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\t\tList<String> list4 = ListUtil.singletonList(\"1\");\n\t\tassertThat(list4).hasSize(1).contains(\"1\");\n\t\ttry {\n\t\t\tlist4.add(\"a\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\t\tList<String> list5 = ListUtil.newArrayList();\n\t\tList<String> list6 = ListUtil.unmodifiableList(list5);\n\n\t\ttry {\n\t\t\tlist6.add(\"a\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\t\tList<String> list7 = ListUtil.synchronizedList(list6);\n\t}\n\n\t@Test\n\tpublic void general() {\n\t\tList<String> list1 = ListUtil.newArrayList();\n\n\t\tList<String> list2 = ListUtil.newArrayList(\"a\", \"b\", \"c\");\n\t\tList<String> list3 = ListUtil.newArrayList(\"a\");\n\n\t\tassertThat(ListUtil.isEmpty(list1)).isTrue();\n\t\tassertThat(ListUtil.isEmpty(null)).isTrue();\n\t\tassertThat(ListUtil.isEmpty(list2)).isFalse();\n\n\t\tassertThat(ListUtil.isNotEmpty(list1)).isFalse();\n\t\tassertThat(ListUtil.isNotEmpty(null)).isFalse();\n\t\tassertThat(ListUtil.isNotEmpty(list2)).isTrue();\n\n\t\tassertThat(ListUtil.getFirst(list2)).isEqualTo(\"a\");\n\t\tassertThat(ListUtil.getLast(list2)).isEqualTo(\"c\");\n\n\t\tassertThat(ListUtil.getFirst(list3)).isEqualTo(\"a\");\n\t\tassertThat(ListUtil.getLast(list3)).isEqualTo(\"a\");\n\n\t\tassertThat(ListUtil.getFirst(list1)).isNull();\n\t\tassertThat((List<String>) ListUtil.getFirst(null)).isNull();\n\t}\n\n\t@Test\n\tpublic void sortAndSearch() {\n\n\t\tList<String> list = ListUtil.newArrayList(\"d\", \"a\", \"c\", \"b\", \"e\", \"i\", \"g\");\n\t\tListUtil.sort(list);\n\n\t\tassertThat(list).hasSize(7).containsExactly(\"a\", \"b\", \"c\", \"d\", \"e\", \"g\", \"i\");\n\n\t\tListUtil.shuffle(list);\n\t\tListUtil.shuffle(list, new Random());\n\t\tSystem.out.println(\"shuffle list:\" + list);\n\n\t\tListUtil.sort(list, Ordering.natural());\n\n\t\tassertThat(list).hasSize(7).containsExactly(\"a\", \"b\", \"c\", \"d\", \"e\", \"g\", \"i\");\n\n\t\tassertThat(ListUtil.binarySearch(list, \"b\")).isEqualTo(1);\n\t\tassertThat(ListUtil.binarySearch(list, \"b\", Ordering.natural())).isEqualTo(1);\n\t\tassertThat(ListUtil.binarySearch(list, \"x\")).isEqualTo(-8);\n\n\t\t// reverse\n\t\tList list8 = ListUtil.reverse(list);\n\t\tassertThat(list8).hasSize(7).containsExactly(\"i\", \"g\", \"e\", \"d\", \"c\", \"b\", \"a\");\n\n\t\t// sortReverse\n\t\tListUtil.shuffle(list8);\n\t\tListUtil.sortReverse(list8);\n\t\tassertThat(list8).hasSize(7).containsExactly(\"i\", \"g\", \"e\", \"d\", \"c\", \"b\", \"a\");\n\n\t\tListUtil.shuffle(list8);\n\t\tListUtil.sortReverse(list8, Ordering.natural());\n\t\tassertThat(list8).hasSize(7).containsExactly(\"i\", \"g\", \"e\", \"d\", \"c\", \"b\", \"a\");\n\t}\n\n\n\t@Test\n\tpublic void collectionCalc() {\n\t\tList<String> list1 = ListUtil.newArrayList(\"1\", \"2\", \"3\", \"6\", \"6\");\n\t\tList<String> list2 = ListUtil.newArrayList(\"4\", \"5\", \"6\", \"7\", \"6\", \"6\");\n\n\t\tList<String> result = ListUtil.union(list1, list2);\n\t\tassertThat(result).containsExactly(\"1\", \"2\", \"3\", \"6\", \"6\", \"4\", \"5\", \"6\", \"7\", \"6\", \"6\");\n\n\t\tList<String> result2 = ListUtil.intersection(list1, list2);\n\t\tassertThat(result2).containsExactly(\"6\", \"6\");\n\n\t\tList<String> result3 = ListUtil.difference(list2, list1);\n\t\tassertThat(result3).containsExactly(\"4\", \"5\", \"7\", \"6\");\n\n\t\tList<String> result4 = ListUtil.disjoint(list1, list2);\n\t\tassertThat(result4).containsExactly(\"1\", \"2\", \"3\", \"4\", \"5\", \"7\", \"6\");\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/collection/MapUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport static org.assertj.core.api.Assertions.*;\nimport static org.assertj.core.api.Fail.fail;\n\nimport java.util.EnumMap;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.TreeMap;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.concurrent.ConcurrentSkipListMap;\n\nimport org.junit.Test;\n\nimport com.google.common.collect.Ordering;\nimport com.vip.vjtools.vjkit.collection.MapUtil.ValueCreator;\nimport com.vip.vjtools.vjkit.collection.type.MoreMaps;\n\npublic class MapUtilTest {\n\n\t@Test\n\tpublic void buildMap() {\n\t\ttry {\n\t\t\tHashMap<String, Integer> map4 = MapUtil.newHashMap(new String[] { \"1\", \"2\" }, new Integer[] { 1 });\n\t\t\tfail(\"should fail here\");\n\t\t} catch (Exception e) {\n\t\t\tassertThat(e).isInstanceOf(IllegalArgumentException.class);\n\t\t\tassertThat(e).hasMessage(\"keys.length is 2 but values.length is 1\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void generalMethod() {\n\t\tHashMap<String, Integer> map = MapUtil.newHashMap();\n\t\tassertThat(MapUtil.isEmpty(map)).isTrue();\n\t\tassertThat(MapUtil.isEmpty(null)).isTrue();\n\t\tassertThat(MapUtil.isNotEmpty(map)).isFalse();\n\t\tassertThat(MapUtil.isNotEmpty(null)).isFalse();\n\n\t\tmap.put(\"haha\", 1);\n\t\tassertThat(MapUtil.isEmpty(map)).isFalse();\n\t\tassertThat(MapUtil.isNotEmpty(map)).isTrue();\n\n\t\t//////////\n\t\tConcurrentMap<String, Integer> map2 = new ConcurrentHashMap<String, Integer>();\n\t\tassertThat(MapUtil.putIfAbsentReturnLast(map2, \"haha\", 3)).isEqualTo(3);\n\t\tassertThat(MapUtil.putIfAbsentReturnLast(map2, \"haha\", 4)).isEqualTo(3);\n\n\t\tMapUtil.createIfAbsentReturnLast(map2, \"haha\", new ValueCreator<Integer>() {\n\t\t\t@Override\n\t\t\tpublic Integer get() {\n\t\t\t\treturn 5;\n\t\t\t}\n\t\t});\n\n\t\tassertThat(map2).hasSize(1).containsEntry(\"haha\", 3);\n\n\t\tMapUtil.createIfAbsentReturnLast(map2, \"haha2\", new ValueCreator<Integer>() {\n\t\t\t@Override\n\t\t\tpublic Integer get() {\n\t\t\t\treturn 5;\n\t\t\t}\n\t\t});\n\n\t\tassertThat(map2).hasSize(2).containsEntry(\"haha2\", 5);\n\n\t}\n\n\t@Test\n\tpublic void guavaBuildMap() {\n\t\tHashMap<String, Integer> map1 = MapUtil.newHashMap();\n\n\t\tHashMap<String, Integer> map2 = MapUtil.newHashMapWithCapacity(10, 0.5f);\n\t\tmap2 = MapUtil.newHashMapWithCapacity(10, 0.5f);\n\n\t\tHashMap<String, Integer> map3 = MapUtil.newHashMap(\"1\", 1);\n\t\tassertThat(map3).hasSize(1).containsEntry(\"1\", 1);\n\n\t\tHashMap<String, Integer> map4 = MapUtil.newHashMap(new String[] { \"1\", \"2\" }, new Integer[] { 1, 2 });\n\t\tassertThat(map4).hasSize(2).containsEntry(\"1\", 1).containsEntry(\"2\", 2);\n\n\t\tHashMap<String, Integer> map5 = MapUtil.newHashMap(ArrayUtil.asList(\"1\", \"2\", \"3\"), ArrayUtil.asList(1, 2, 3));\n\t\tassertThat(map5).hasSize(3).containsEntry(\"1\", 1).containsEntry(\"2\", 2).containsEntry(\"3\", 3);\n\n\t\tTreeMap<String, Integer> map6 = MapUtil.newSortedMap();\n\n\t\tTreeMap<String, Integer> map7 = MapUtil.newSortedMap(Ordering.natural());\n\n\t\tConcurrentSkipListMap map10 = MapUtil.newConcurrentSortedMap();\n\n\t\tEnumMap map11 = MapUtil.newEnumMap(EnumA.class);\n\t}\n\n\t@Test\n\tpublic void jdkBuildMap() {\n\t\tMap<String, Integer> map1 = MapUtil.emptyMap();\n\t\tassertThat(map1).hasSize(0);\n\n\t\tMap<String, Integer> map2 = MapUtil.emptyMapIfNull(null);\n\t\tassertThat(map2).isNotNull().hasSize(0);\n\n\t\tMap<String, Integer> map3 = MapUtil.emptyMapIfNull(map1);\n\t\tassertThat(map3).isSameAs(map1);\n\n\t\tMap<String, Integer> map4 = MapUtil.singletonMap(\"haha\", 1);\n\t\tassertThat(map4).hasSize(1).containsEntry(\"haha\", 1);\n\t\ttry {\n\t\t\tmap4.put(\"dada\", 2);\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\t\tMap<String, Integer> map5 = MapUtil.newHashMap();\n\t\tMap<String, Integer> map6 = MapUtil.unmodifiableMap(map5);\n\n\t\ttry {\n\t\t\tmap6.put(\"a\", 2);\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void weakMap() {\n\t\tConcurrentMap<MyBean, MyBean> weakKeyMap = MoreMaps.createWeakKeyConcurrentMap(10, 1);\n\t\tinitExpireAllMap(weakKeyMap);\n\t\tSystem.gc();\n\t\tassertThat(weakKeyMap.get(new MyBean(\"A\"))).isNull();\n\t\tassertThat(weakKeyMap).hasSize(1); // key仍然在\n\n\t\tConcurrentMap<MyBean, MyBean> weakKeyMap2 = MoreMaps.createWeakKeyConcurrentMap(10, 1);\n\t\tMyBean value = new MyBean(\"B\");\n\t\tinitExpireKeyMap(weakKeyMap2, value);\n\t\tSystem.gc();\n\t\tassertThat(weakKeyMap2.get(new MyBean(\"A\"))).isNull();\n\n\t\tConcurrentMap<MyBean, MyBean> weakKeyMap3 = MoreMaps.createWeakKeyConcurrentMap(10, 1);\n\t\tMyBean key = new MyBean(\"A\");\n\t\tinitExpireValueMap(weakKeyMap3, key);\n\t\tSystem.gc();\n\t\tassertThat(weakKeyMap3.get(key)).isEqualTo(new MyBean(\"B\"));\n\n\t\t// weak value\n\t\tConcurrentMap<MyBean, MyBean> weakValueMap = MoreMaps.createWeakValueConcurrentMap(10, 1);\n\t\tinitExpireAllMap(weakValueMap);\n\t\tSystem.gc();\n\t\tassertThat(weakValueMap.get(new MyBean(\"A\"))).isNull();\n\n\t\tConcurrentMap<MyBean, MyBean> weakValueMap2 = MoreMaps.createWeakValueConcurrentMap(10, 1);\n\t\tMyBean value2 = new MyBean(\"B\");\n\t\tinitExpireKeyMap(weakValueMap2, value2);\n\t\tSystem.gc();\n\t\tassertThat(weakValueMap2.get(new MyBean(\"A\"))).isEqualTo(new MyBean(\"B\"));\n\n\t\tConcurrentMap<MyBean, MyBean> weakValueMap3 = MoreMaps.createWeakValueConcurrentMap(10, 1);\n\t\tMyBean key3 = new MyBean(\"A\");\n\t\tinitExpireValueMap(weakValueMap3, key3);\n\t\tSystem.gc();\n\t\tassertThat(weakValueMap3.get(new MyBean(\"A\"))).isNull();\n\t}\n\n\t// 抽出子函数，使得Key/Value的生命周期过期\n\tprivate void initExpireAllMap(ConcurrentMap<MyBean, MyBean> weakKeyMap) {\n\t\tMyBean key = new MyBean(\"A\");\n\t\tMyBean value = new MyBean(\"B\");\n\t\tweakKeyMap.put(key, value);\n\t\tassertThat(weakKeyMap.get(key)).isEqualTo(value);\n\t}\n\n\t// 抽出子函数，使得key过期，value不过期\n\tprivate void initExpireKeyMap(ConcurrentMap<MyBean, MyBean> weakKeyMap, MyBean value) {\n\t\tMyBean key = new MyBean(\"A\");\n\t\tweakKeyMap.put(key, value);\n\t\tassertThat(weakKeyMap.get(key)).isEqualTo(value);\n\t}\n\n\t// 抽出子函数，使得key不过期，value过期\n\tprivate void initExpireValueMap(ConcurrentMap<MyBean, MyBean> weakKeyMap, MyBean key) {\n\t\tMyBean value = new MyBean(\"B\");\n\t\tweakKeyMap.put(key, value);\n\t\tassertThat(weakKeyMap.get(key)).isEqualTo(value);\n\t}\n\n\t// 抽出子函数，使得Key/Value的生命周琦过期\n\tprivate void initWeakValue(ConcurrentMap<MyBean, MyBean> weakKeyMap) {\n\t\tMyBean key = new MyBean(\"A\");\n\t\tMyBean value = new MyBean(\"B\");\n\t\tweakKeyMap.put(key, value);\n\t\tassertThat(weakKeyMap.get(new MyBean(\"A\"))).isEqualTo(value);\n\t}\n\n\tpublic static class MyBean {\n\t\tString name;\n\n\t\tpublic MyBean(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\tfinal int prime = 31;\n\t\t\tint result = 1;\n\t\t\tresult = prime * result + ((name == null) ? 0 : name.hashCode());\n\t\t\treturn result;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object obj) {\n\t\t\tif (this == obj) return true;\n\t\t\tif (obj == null) return false;\n\t\t\tif (getClass() != obj.getClass()) return false;\n\t\t\tMyBean other = (MyBean) obj;\n\t\t\tif (name == null) {\n\t\t\t\tif (other.name != null) return false;\n\t\t\t} else if (!name.equals(other.name)) return false;\n\t\t\treturn true;\n\t\t}\n\n\t}\n\n\tpublic enum EnumA {\n\t\tA, B, C\n\t}\n\n\t@Test\n\tpublic void sortAndTop() {\n\t\tMap<String, Integer> map = MapUtil.newHashMap(new String[] { \"A\", \"B\", \"C\" }, new Integer[] { 3, 1, 2 });\n\t\t// sort\n\t\tMap<String, Integer> resultMap = MapUtil.sortByValue(map, false);\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{B=1, C=2, A=3}\");\n\t\tresultMap = MapUtil.sortByValue(map, true);\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{A=3, C=2, B=1}\");\n\n\t\tresultMap = MapUtil.sortByValue(map, Ordering.natural());\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{B=1, C=2, A=3}\");\n\t\tresultMap = MapUtil.sortByValue(map, Ordering.natural().reverse());\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{A=3, C=2, B=1}\");\n\n\t\t// Top n\n\t\tresultMap = MapUtil.topNByValue(map, false, 2);\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{B=1, C=2}\");\n\t\tresultMap = MapUtil.topNByValue(map, true, 2);\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{A=3, C=2}\");\n\n\t\tresultMap = MapUtil.topNByValue(map, Ordering.natural(), 2);\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{B=1, C=2}\");\n\t\tresultMap = MapUtil.topNByValue(map, Ordering.natural().reverse(), 2);\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{A=3, C=2}\");\n\n\t\t// top Size > array Size\n\t\tresultMap = MapUtil.topNByValue(map, false, 4);\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{B=1, C=2, A=3}\");\n\t\tresultMap = MapUtil.topNByValue(map, true, 4);\n\t\tassertThat(resultMap.toString()).isEqualTo(\"{A=3, C=2, B=1}\");\n\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/collection/QueueUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.ArrayDeque;\nimport java.util.Deque;\nimport java.util.LinkedList;\nimport java.util.Queue;\nimport java.util.concurrent.ArrayBlockingQueue;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport java.util.concurrent.LinkedBlockingDeque;\nimport java.util.concurrent.LinkedBlockingQueue;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.collection.type.MoreQueues;\n\npublic class QueueUtilTest {\n\n\t@Test\n\tpublic void guavaBuildSet() {\n\t\tArrayDeque<String> queue1 = QueueUtil.newArrayDeque(16);\n\t\tLinkedList<String> queue2 = QueueUtil.newLinkedDeque();\n\n\t\tConcurrentLinkedQueue<String> queue3 = QueueUtil.newConcurrentNonBlockingQueue();\n\t\tDeque<String> queue7 = QueueUtil.newConcurrentNonBlockingDeque();\n\n\t\tLinkedBlockingQueue<String> queue4 = QueueUtil.newBlockingUnlimitQueue();\n\t\tLinkedBlockingDeque<String> queue8 = QueueUtil.newBlockingUnlimitDeque();\n\n\t\tLinkedBlockingQueue<String> queue5 = QueueUtil.newLinkedBlockingQueue(100);\n\t\tArrayBlockingQueue<String> queue6 = QueueUtil.newArrayBlockingQueue(100);\n\t\tLinkedBlockingDeque<String> queue9 = QueueUtil.newBlockingDeque(100);\n\t}\n\n\t@Test\n\tpublic void stack() {\n\n\t\tQueue<String> stack = MoreQueues.createStack(10);\n\t\tQueue<String> stack2 = MoreQueues.createConcurrentStack();\n\n\t\tstack.offer(\"1\");\n\t\tstack.offer(\"2\");\n\n\t\tassertThat(stack.poll()).isEqualTo(\"2\");\n\t\tassertThat(stack.poll()).isEqualTo(\"1\");\n\n\t\tstack2.offer(\"1\");\n\t\tstack2.offer(\"2\");\n\n\t\tassertThat(stack2.poll()).isEqualTo(\"2\");\n\t\tassertThat(stack2.poll()).isEqualTo(\"1\");\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/collection/SetUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.collection;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.TreeSet;\n\nimport org.junit.Test;\n\nimport com.google.common.collect.Ordering;\nimport com.vip.vjtools.vjkit.collection.type.ConcurrentHashSet;\n\npublic class SetUtilTest {\n\n\t@Test\n\tpublic void guavaBuildSet() {\n\t\tHashSet<String> set1 = SetUtil.newHashSet();\n\n\t\tHashSet<String> set2 = SetUtil.newHashSetWithCapacity(10);\n\n\t\tHashSet<String> set3 = SetUtil.newHashSet(\"1\", \"2\", \"2\");\n\n\t\tassertThat(set3).hasSize(2).contains(\"1\", \"2\");\n\n\t\tHashSet<String> set4 = SetUtil.newHashSet(ListUtil.newArrayList(\"1\", \"2\", \"2\"));\n\t\tassertThat(set4).hasSize(2).contains(\"1\", \"2\");\n\n\t\tTreeSet<String> set5 = SetUtil.newSortedSet();\n\n\t\tTreeSet<String> set6 = SetUtil.newSortedSet(Ordering.natural());\n\n\t\tConcurrentHashSet set7 = SetUtil.newConcurrentHashSet();\n\t}\n\n\t@Test\n\tpublic void jdkBuildSet() {\n\t\tSet<String> set1 = SetUtil.emptySet();\n\t\tassertThat(set1).hasSize(0);\n\n\t\tSet<String> set2 = SetUtil.emptySetIfNull(null);\n\t\tassertThat(set2).isNotNull().hasSize(0);\n\n\t\tSet<String> set3 = SetUtil.emptySetIfNull(set1);\n\t\tassertThat(set3).isSameAs(set1);\n\n\t\ttry {\n\t\t\tset1.add(\"a\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\t\tSet<String> set4 = SetUtil.singletonSet(\"1\");\n\t\tassertThat(set4).hasSize(1).contains(\"1\");\n\t\ttry {\n\t\t\tset4.add(\"a\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\t\tSet<String> set5 = SetUtil.newHashSet();\n\t\tSet<String> set6 = SetUtil.unmodifiableSet(set5);\n\n\t\ttry {\n\t\t\tset6.add(\"a\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\t\tSet<String> set7 = SetUtil.newSetFromMap(MapUtil.<String, Boolean>newConcurrentSortedMap());\n\t}\n\n\t@Test\n\tpublic void collectionCaculate() {\n\t\tHashSet<String> set1 = SetUtil.newHashSet(\"1\", \"2\", \"3\", \"6\");\n\t\tHashSet<String> set2 = SetUtil.newHashSet(\"4\", \"5\", \"6\", \"7\");\n\n\t\tSet<String> set3 = SetUtil.unionView(set1, set2);\n\t\tassertThat(set3).hasSize(7).contains(\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\");\n\n\t\tSet<String> set4 = SetUtil.intersectionView(set1, set2);\n\t\tassertThat(set4).hasSize(1).contains(\"6\");\n\n\t\tSet<String> set5 = SetUtil.differenceView(set1, set2);\n\t\tassertThat(set5).hasSize(3).contains(\"1\", \"2\", \"3\");\n\n\t\tSet<String> set6 = SetUtil.disjointView(set1, set2);\n\t\tassertThat(set6).hasSize(6).contains(\"1\", \"2\", \"3\", \"4\", \"5\", \"7\");\n\n\t\ttry {\n\t\t\tset6.add(\"a\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/collection/type/ConcurrentHashSetTest.java",
    "content": "package com.vip.vjtools.vjkit.collection.type;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.collection.SetUtil;\n\npublic class ConcurrentHashSetTest {\n\n\t@Test\n\tpublic void concurrentHashSet() {\n\t\tConcurrentHashSet<String> conrrentHashSet = SetUtil.newConcurrentHashSet();\n\t\tconrrentHashSet.add(\"a\");\n\t\tconrrentHashSet.add(\"b\");\n\t\tconrrentHashSet.add(\"c\");\n\n\t\tassertThat(conrrentHashSet.isEmpty()).isFalse();\n\t\tassertThat(conrrentHashSet.contains(\"a\")).isTrue();\n\t\tassertThat(conrrentHashSet.contains(\"d\")).isFalse();\n\n\t\tassertThat(conrrentHashSet).hasSize(3).contains(\"a\", \"b\", \"c\");\n\n\t\tfor (String key : conrrentHashSet) {\n\t\t\tSystem.out.print(key + \",\");\n\t\t}\n\n\t\tconrrentHashSet.remove(\"c\");\n\t\tassertThat(conrrentHashSet).hasSize(2);\n\n\t\tObject[] strings = conrrentHashSet.toArray();\n\t\tassertThat(strings).hasSize(2).contains(\"a\", \"b\");\n\n\t\tconrrentHashSet.toArray(new String[conrrentHashSet.size()]);\n\t\tconrrentHashSet.hashCode();\n\t\tconrrentHashSet.toString();\n\n\t\tConcurrentHashSet<String> conrrentHashSet2 = SetUtil.newConcurrentHashSet();\n\t\tconrrentHashSet2.add(\"a\");\n\n\t\tassertThat(conrrentHashSet.equals(conrrentHashSet)).isTrue();\n\t\tassertThat(conrrentHashSet.equals(conrrentHashSet2)).isFalse();\n\n\t\tassertThat(conrrentHashSet.containsAll(conrrentHashSet2)).isTrue();\n\n\t\tconrrentHashSet.retainAll(conrrentHashSet2);\n\t\tassertThat(conrrentHashSet).hasSize(1).contains(\"a\");\n\t\tassertThat(conrrentHashSet.equals(conrrentHashSet2)).isTrue();\n\n\t\tconrrentHashSet.removeAll(conrrentHashSet2);\n\t\tassertThat(conrrentHashSet.isEmpty()).isTrue();\n\n\t\tconrrentHashSet2.clear();\n\t\tassertThat(conrrentHashSet2.isEmpty()).isTrue();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/collection/type/SortedArrayListTest.java",
    "content": "package com.vip.vjtools.vjkit.collection.type;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.google.common.collect.Ordering;\nimport com.vip.vjtools.vjkit.collection.ListUtil;\n\npublic class SortedArrayListTest {\n\n\t@Test\n\tpublic void sortedArrayList() {\n\t\tSortedArrayList<String> list = MoreLists.createSortedArrayList();\n\t\tlist.add(\"9\");\n\t\tlist.add(\"1\");\n\t\tlist.add(\"6\");\n\t\tlist.add(\"9\");\n\t\tlist.add(\"3\");\n\n\t\tassertThat(list).containsExactly(\"1\", \"3\", \"6\", \"9\", \"9\");\n\n\t\tlist.remove(2);\n\t\tassertThat(list).containsExactly(\"1\", \"3\", \"9\", \"9\");\n\n\t\tassertThat(list.contains(\"3\")).isTrue();\n\t\tassertThat(list.contains(\"2\")).isFalse();\n\n\t\ttry {\n\t\t\tlist.add(1, \"2\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tlist.set(1, \"2\");\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(UnsupportedOperationException.class);\n\t\t}\n\n\t\tSortedArrayList<String> list2 = MoreLists.createSortedArrayList(Ordering.natural());\n\t\tlist2.addAll(ListUtil.newArrayList(\"3\", \"1\", \"2\"));\n\t\tassertThat(list2).containsExactly(\"1\", \"2\", \"3\");\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/ConcurrentsTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.concurrent.jsr166e.LongAdder;\n\npublic class ConcurrentsTest {\n\n\t@Test\n\tpublic void longAdder() {\n\t\tLongAdder counter = Concurrents.longAdder();\n\t\tcounter.increment();\n\t\tcounter.add(2);\n\t\tassertThat(counter.longValue()).isEqualTo(3L);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/ThreadDumpperTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.test.log.LogbackListAppender;\nimport com.vip.vjtools.vjkit.concurrent.threadpool.ThreadPoolBuilder;\n\npublic class ThreadDumpperTest {\n\n\tpublic static class LongRunTask implements Runnable {\n\n\t\tprivate CountDownLatch countDownLatch;\n\n\t\tpublic LongRunTask(CountDownLatch countDownLatch) {\n\t\t\tthis.countDownLatch = countDownLatch;\n\t\t}\n\n\t\t@Override\n\t\tpublic void run() {\n\t\t\tcountDownLatch.countDown();\n\t\t\tThreadUtil.sleep(5, TimeUnit.SECONDS);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void test() throws InterruptedException {\n\t\tExecutorService executor = ThreadPoolBuilder.fixedPool().setPoolSize(10).build();\n\t\tCountDownLatch countDownLatch = Concurrents.countDownLatch(10);\n\t\tfor (int i = 0; i < 10; i++) {\n\t\t\texecutor.execute(new LongRunTask(countDownLatch));\n\t\t}\n\t\tcountDownLatch.await();\n\n\t\tThreadDumpper dumpper = new ThreadDumpper();\n\t\tdumpper.tryThreadDump();\n\n\t\tLogbackListAppender appender = new LogbackListAppender();\n\t\tappender.addToLogger(ThreadDumpper.class);\n\n\t\t// 设置最少间隔,不输出\n\t\tdumpper.setLeastInterval(1800);\n\n\t\tdumpper.tryThreadDump(); // 重置间隔会重置上一次写日志的时间,因此要调一次把新增的次数用完\n\n\t\tdumpper.tryThreadDump();\n\t\tassertThat(appender.getAllLogs()).hasSize(3);\n\t\texecutor.shutdownNow();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/ThreadUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.base.ObjectUtil;\nimport com.vip.vjtools.vjkit.base.RuntimeUtil;\n\npublic class ThreadUtilTest {\n\t@Test\n\tpublic void testCaller() {\n\t\thello();\n\t\tnew MyClass().hello();\n\t\tassertThat(RuntimeUtil.getCurrentClass()).isEqualTo(\"com.vip.vjtools.vjkit.concurrent.ThreadUtilTest\");\n\t\tassertThat(RuntimeUtil.getCurrentMethod())\n\t\t\t\t.isEqualTo(\"com.vip.vjtools.vjkit.concurrent.ThreadUtilTest.testCaller()\");\n\n\t}\n\n\tprivate void hello() {\n\t\tStackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();\n\t\tSystem.out.println(ObjectUtil.toPrettyString(stacktrace));\n\n\t\tassertThat(RuntimeUtil.getCallerClass()).isEqualTo(\"com.vip.vjtools.vjkit.concurrent.ThreadUtilTest\");\n\t\tassertThat(RuntimeUtil.getCallerMethod())\n\t\t\t\t.isEqualTo(\"com.vip.vjtools.vjkit.concurrent.ThreadUtilTest.testCaller()\");\n\t}\n\n\tpublic static class MyClass {\n\t\tpublic void hello() {\n\t\t\tStackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();\n\t\t\tSystem.out.println(ObjectUtil.toPrettyString(stacktrace));\n\n\t\t\tassertThat(RuntimeUtil.getCallerClass()).isEqualTo(\"com.vip.vjtools.vjkit.concurrent.ThreadUtilTest\");\n\t\t\tassertThat(RuntimeUtil.getCallerMethod())\n\t\t\t\t\t.isEqualTo(\"com.vip.vjtools.vjkit.concurrent.ThreadUtilTest.testCaller()\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/limiter/RateLimiterUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.limiter;\n\nimport java.lang.reflect.Field;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport com.google.common.util.concurrent.RateLimiter;\nimport com.vip.vjtools.vjkit.concurrent.limiter.RateLimiterUtil;\n\npublic class RateLimiterUtilTest {\n\t@Test\n\tpublic void testCreate() throws Exception {\n\t\tRateLimiter rateLimiter = RateLimiterUtil.create(20000, 0.1);\n\n\t\tClass superClass = rateLimiter.getClass().getSuperclass();\n\t\tField field = superClass.getDeclaredField(\"storedPermits\");\n\t\tfield.setAccessible(true);\n\n\t\tAssert.assertEquals(2000, (int) field.getDouble(rateLimiter));\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/limiter/SamplerTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.limiter;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.concurrent.limiter.Sampler;\nimport com.vip.vjtools.vjkit.concurrent.limiter.Sampler.AlwaysSampler;\nimport com.vip.vjtools.vjkit.concurrent.limiter.Sampler.NeverSampler;\n\npublic class SamplerTest {\n\n\t@Test\n\tpublic void test() {\n\t\tSampler sampler = Sampler.create(10.5);\n\t\tint hits = 0;\n\t\tfor (int i = 0; i < 10000; i++) {\n\t\t\tif (sampler.select()) {\n\t\t\t\thits++;\n\t\t\t}\n\t\t}\n\t\tSystem.out.println(\"sample 10.5% in 10000 hits should close to 1050, actual is \" + hits);\n\n\t\tassertThat(hits).isBetween(900, 1200);\n\t\t//////////\n\t\tSampler sampler2 = Sampler.create(0.5);\n\n\t\thits = 0;\n\t\tfor (int i = 0; i < 10000; i++) {\n\t\t\tif (sampler2.select()) {\n\t\t\t\thits++;\n\t\t\t}\n\t\t}\n\t\tSystem.out.println(\"sample 0.5% in 10000 hits should close to 50, actual is \" + hits);\n\t\tassertThat(hits).isBetween(20, 100);\n\n\t}\n\n\t@Test\n\tpublic void always() {\n\t\tSampler sampler = Sampler.create(0d);\n\t\tassertThat(sampler).isInstanceOf(NeverSampler.class);\n\t\tsampler = Sampler.create(100d);\n\t\tassertThat(sampler).isInstanceOf(AlwaysSampler.class);\n\n\t\ttry {\n\t\t\tsampler = Sampler.create(101d);\n\t\t\tfail(\"shoud fail before\");\n\t\t} catch (Exception e) {\n\t\t\tassertThat(e).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tsampler = Sampler.create(-2.2);\n\t\t\tfail(\"shoud fail before\");\n\t\t} catch (Exception e) {\n\t\t\tassertThat(e).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/limiter/TimeIntervalLimiterTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.limiter;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.concurrent.Concurrents;\nimport com.vip.vjtools.vjkit.concurrent.limiter.TimeIntervalLimiter;\n\npublic class TimeIntervalLimiterTest {\n\n\t@Test\n\tpublic void testTryAcquire() throws Exception {\n\t\tint interval = 100;\n\t\tTimeUnit timeUnit = TimeUnit.MILLISECONDS;\n\n\t\tTimeIntervalLimiter limiter = Concurrents.timeIntervalLimiter(interval, timeUnit);\n\n\t\tassertThat(limiter.tryAcquire()).isTrue();\n\t\tassertThat(limiter.tryAcquire()).isFalse();\n\n\t\ttimeUnit.sleep(interval);\n\t\tassertThat(limiter.tryAcquire()).isTrue();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/threadpool/AbortPolicyWithReportTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.threadpool;\n\nimport org.junit.Test;\n\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.RejectedExecutionException;\nimport java.util.concurrent.ThreadPoolExecutor;\n\npublic class AbortPolicyWithReportTest {\n\n    @Test\n    public void jStackDumpTest() throws InterruptedException {\n        AbortPolicyWithReport abortPolicyWithReport = new AbortPolicyWithReport(\"test\");\n\n        try {\n            abortPolicyWithReport.rejectedExecution(new Runnable() {\n                @Override\n                public void run() {\n                    System.out.println(\"hello\");\n                }\n            }, (ThreadPoolExecutor) Executors.newFixedThreadPool(1));\n        } catch (RejectedExecutionException rj) {\n            // ignore\n        }\n\n        Thread.sleep(1000);\n\n    }\n}"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/threadpool/QueuableCachedThreadPoolTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.threadpool;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.concurrent.RejectedExecutionException;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.concurrent.ThreadUtil;\n\npublic class QueuableCachedThreadPoolTest {\n\n\tpublic static class LongRunTask implements Runnable {\n\t\t@Override\n\t\tpublic void run() {\n\t\t\tThreadUtil.sleep(5, TimeUnit.SECONDS);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void test() {\n\t\tQueuableCachedThreadPool threadPool = null;\n\t\ttry {\n\t\t\tthreadPool = ThreadPoolBuilder.queuableCachedPool().setMinSize(0).setMaxSize(10).setQueueSize(10).build();\n\t\t\t// 线程满\n\t\t\tfor (int i = 0; i < 10; i++) {\n\t\t\t\tthreadPool.submit(new LongRunTask());\n\t\t\t}\n\n\t\t\tassertThat(threadPool.getActiveCount()).isEqualTo(10);\n\t\t\tassertThat(threadPool.getQueue().size()).isEqualTo(0);\n\n\t\t\t// queue 满\n\t\t\tfor (int i = 0; i < 10; i++) {\n\t\t\t\tthreadPool.submit(new LongRunTask());\n\t\t\t}\n\t\t\tassertThat(threadPool.getActiveCount()).isEqualTo(10);\n\t\t\tassertThat(threadPool.getQueue().size()).isEqualTo(10);\n\n\t\t\t// 爆\n\t\t\ttry {\n\t\t\t\tthreadPool.submit(new LongRunTask());\n\t\t\t\tfail(\"should fail before\");\n\t\t\t} catch (Throwable t) {\n\t\t\t\tassertThat(t).isInstanceOf(RejectedExecutionException.class);\n\t\t\t}\n\n\t\t} finally {\n\t\t\tThreadPoolUtil.gracefulShutdown(threadPool, 1000);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/threadpool/ThreadPoolBuilderTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.threadpool;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.concurrent.ArrayBlockingQueue;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.concurrent.threadpool.QueuableCachedThreadPool.ControllableQueue;\n\npublic class ThreadPoolBuilderTest {\n\n\t@Test\n\tpublic void fixPool() {\n\t\tThreadPoolExecutor singlePool = ThreadPoolBuilder.fixedPool().build();\n\t\tassertThat(singlePool.getCorePoolSize()).isEqualTo(1);\n\t\tassertThat(singlePool.getMaximumPoolSize()).isEqualTo(1);\n\t\tassertThat(singlePool.getQueue()).isInstanceOf(LinkedBlockingQueue.class);\n\t\tsinglePool.shutdown();\n\n\t\tThreadPoolExecutor fixPoolWithUnlimitQueue = ThreadPoolBuilder.fixedPool().setPoolSize(10).build();\n\t\tassertThat(fixPoolWithUnlimitQueue.getCorePoolSize()).isEqualTo(10);\n\t\tassertThat(fixPoolWithUnlimitQueue.getMaximumPoolSize()).isEqualTo(10);\n\t\tfixPoolWithUnlimitQueue.shutdown();\n\n\t\tThreadPoolExecutor fixPoolWithlimitQueue = ThreadPoolBuilder.fixedPool().setPoolSize(10).setQueueSize(100)\n\t\t\t\t.setThreadFactory(ThreadPoolUtil.buildThreadFactory(\"kaka\")).build();\n\n\t\tassertThat(fixPoolWithlimitQueue.getQueue()).isInstanceOf(ArrayBlockingQueue.class);\n\t\tThread thread = fixPoolWithlimitQueue.getThreadFactory().newThread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t}\n\t\t});\n\t\tassertThat(thread.getName()).startsWith(\"kaka\");\n\n\t\tfixPoolWithlimitQueue.shutdown();\n\n\t\tThreadPoolExecutor fixPoolWithNamePrefix = ThreadPoolBuilder.fixedPool().setPoolSize(10)\n\t\t\t\t.setThreadNamePrefix(\"fixPool\").build();\n\t\tThread thread2 = fixPoolWithNamePrefix.getThreadFactory().newThread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t}\n\t\t});\n\t\tassertThat(thread2.getName()).startsWith(\"fixPool\");\n\t\tassertThat(thread2.isDaemon()).isFalse();\n\t\tfixPoolWithNamePrefix.shutdown();\n\n\t\tThreadPoolExecutor fixPoolWithNamePrefixAndDaemon = ThreadPoolBuilder.fixedPool().setPoolSize(10)\n\t\t\t\t.setThreadNamePrefix(\"fixPoolDaemon\").setDaemon(true).build();\n\t\tThread thread3 = fixPoolWithNamePrefixAndDaemon.getThreadFactory().newThread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t}\n\t\t});\n\t\tassertThat(thread3.getName()).startsWith(\"fixPoolDaemon\");\n\t\tassertThat(thread3.isDaemon()).isTrue();\n\t\tfixPoolWithNamePrefixAndDaemon.shutdown();\n\t}\n\n\t@Test\n\tpublic void cachedPool() {\n\t\tThreadPoolExecutor singlePool = ThreadPoolBuilder.cachedPool().build();\n\t\tassertThat(singlePool.getCorePoolSize()).isEqualTo(0);\n\t\tassertThat(singlePool.getMaximumPoolSize()).isEqualTo(Integer.MAX_VALUE);\n\t\tassertThat(singlePool.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(10);\n\t\tassertThat(singlePool.getQueue()).isInstanceOf(SynchronousQueue.class);\n\t\tsinglePool.shutdown();\n\n\t\tThreadPoolExecutor sizeablePool = ThreadPoolBuilder.cachedPool().setMinSize(10).setMaxSize(100)\n\t\t\t\t.setKeepAliveSecs(20).build();\n\t\tassertThat(sizeablePool.getCorePoolSize()).isEqualTo(10);\n\t\tassertThat(sizeablePool.getMaximumPoolSize()).isEqualTo(100);\n\t\tassertThat(sizeablePool.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(20);\n\t\tsizeablePool.shutdown();\n\n\t\tThreadPoolExecutor fixPoolWithNamePrefix = ThreadPoolBuilder.cachedPool().setThreadNamePrefix(\"cachedPool\")\n\t\t\t\t.build();\n\t\tThread thread = fixPoolWithNamePrefix.getThreadFactory().newThread(new Runnable() {\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t}\n\t\t});\n\t\tassertThat(thread.getName()).startsWith(\"cachedPool\");\n\t\tfixPoolWithNamePrefix.shutdown();\n\t}\n\n\t@Test\n\tpublic void scheduledPool() {\n\t\tScheduledThreadPoolExecutor singlePool = ThreadPoolBuilder.scheduledPool().build();\n\t\tassertThat(singlePool.getCorePoolSize()).isEqualTo(1);\n\t\tassertThat(singlePool.getMaximumPoolSize()).isEqualTo(Integer.MAX_VALUE);\n\t\tsinglePool.shutdown();\n\n\t\tScheduledThreadPoolExecutor sizeablePool = ThreadPoolBuilder.scheduledPool().setPoolSize(2).build();\n\t\tassertThat(sizeablePool.getCorePoolSize()).isEqualTo(2);\n\t\tassertThat(sizeablePool.getMaximumPoolSize()).isEqualTo(Integer.MAX_VALUE);\n\t\tsizeablePool.shutdown();\n\n\t\tThreadPoolExecutor fixPoolWithNamePrefix = ThreadPoolBuilder.scheduledPool()\n\t\t\t\t.setThreadNamePrefix(\"scheduledPool\").build();\n\t\tThread thread = fixPoolWithNamePrefix.getThreadFactory().newThread(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t}\n\t\t});\n\t\tassertThat(thread.getName()).startsWith(\"scheduledPool\");\n\t\tfixPoolWithNamePrefix.shutdown();\n\t}\n\n\t@Test\n\tpublic void quequablePool() {\n\t\tThreadPoolExecutor singlePool = ThreadPoolBuilder.queuableCachedPool().build();\n\t\tassertThat(singlePool.getCorePoolSize()).isEqualTo(0);\n\t\tassertThat(singlePool.getMaximumPoolSize()).isEqualTo(Integer.MAX_VALUE);\n\t\tassertThat(singlePool.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(10);\n\t\tassertThat(singlePool.getQueue()).isInstanceOf(ControllableQueue.class);\n\t\tsinglePool.shutdown();\n\n\t\tThreadPoolExecutor sizeablePool = ThreadPoolBuilder.queuableCachedPool().setMinSize(10).setMaxSize(100)\n\t\t\t\t.setKeepAliveSecs(20).build();\n\t\tassertThat(sizeablePool.getCorePoolSize()).isEqualTo(10);\n\t\tassertThat(sizeablePool.getMaximumPoolSize()).isEqualTo(100);\n\t\tassertThat(sizeablePool.getKeepAliveTime(TimeUnit.SECONDS)).isEqualTo(20);\n\t\tsizeablePool.shutdown();\n\n\t\tThreadPoolExecutor fixPoolWithNamePrefix = ThreadPoolBuilder.queuableCachedPool()\n\t\t\t\t.setThreadNamePrefix(\"queuableCachedPool\").build();\n\t\tThread thread = fixPoolWithNamePrefix.getThreadFactory().newThread(new Runnable() {\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t}\n\t\t});\n\t\tassertThat(thread.getName()).startsWith(\"queuableCachedPool\");\n\t\tfixPoolWithNamePrefix.shutdown();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/threadpool/ThreadPoolUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.threadpool;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.junit.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.vip.vjtools.test.log.LogbackListAppender;\nimport com.vip.vjtools.vjkit.concurrent.ThreadUtil;\n\npublic class ThreadPoolUtilTest {\n\t@Test\n\tpublic void buildThreadFactory() {\n\n\t\tRunnable testRunnable = new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t}\n\t\t};\n\t\t// 测试name格式\n\t\tThreadFactory threadFactory = ThreadPoolUtil.buildThreadFactory(\"example\");\n\t\tThread thread = threadFactory.newThread(testRunnable);\n\n\t\tassertThat(thread.getName()).isEqualTo(\"example-0\");\n\t\tassertThat(thread.isDaemon()).isFalse();\n\n\t\t// 测试daemon属性设置\n\t\tthreadFactory = ThreadPoolUtil.buildThreadFactory(\"example\", true);\n\t\tThread thread2 = threadFactory.newThread(testRunnable);\n\n\t\tassertThat(thread.getName()).isEqualTo(\"example-0\");\n\t\tassertThat(thread2.isDaemon()).isTrue();\n\t}\n\n\t@Test\n\tpublic void gracefulShutdown() throws InterruptedException {\n\n\t\tLogger logger = LoggerFactory.getLogger(\"test\");\n\t\tLogbackListAppender appender = new LogbackListAppender();\n\t\tappender.addToLogger(\"test\");\n\n\t\t// time enough to shutdown\n\t\tExecutorService pool = Executors.newSingleThreadExecutor();\n\t\tRunnable task = new Task(logger, 200, 0);\n\t\tpool.execute(task);\n\t\tThreadPoolUtil.gracefulShutdown(pool, 1000, TimeUnit.MILLISECONDS);\n\t\tassertThat(pool.isTerminated()).isTrue();\n\t\tassertThat(appender.getFirstLog()).isNull();\n\n\t\t// time not enough to shutdown,call shutdownNow\n\t\tappender.clearLogs();\n\t\tpool = Executors.newSingleThreadExecutor();\n\t\ttask = new Task(logger, 1000, 0);\n\t\tpool.execute(task);\n\t\tThreadPoolUtil.gracefulShutdown(pool, 500, TimeUnit.MILLISECONDS);\n\t\tassertThat(pool.isTerminated()).isTrue();\n\t\tassertThat(appender.getFirstLog().getMessage()).isEqualTo(\"InterruptedException\");\n\n\t\t// self thread interrupt while calling gracefulShutdown\n\t\tappender.clearLogs();\n\n\t\tfinal ExecutorService self = Executors.newSingleThreadExecutor();\n\t\ttask = new Task(logger, 100000, 0);\n\t\tself.execute(task);\n\n\t\tfinal CountDownLatch lock = new CountDownLatch(1);\n\t\tThread thread = new Thread(new Runnable() {\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tlock.countDown();\n\t\t\t\tThreadPoolUtil.gracefulShutdown(self, 200000, TimeUnit.MILLISECONDS);\n\t\t\t}\n\t\t});\n\t\tthread.start();\n\t\tlock.await();\n\t\tthread.interrupt();\n\t\tThreadUtil.sleep(500);\n\t\tassertThat(appender.getFirstLog().getMessage()).isEqualTo(\"InterruptedException\");\n\n\t\tThreadPoolUtil.gracefulShutdown(null, 1000);\n\t\tThreadPoolUtil.gracefulShutdown(null, 1000, TimeUnit.MILLISECONDS);\n\t}\n\n\t@Test\n\tpublic void wrapException() {\n\t\tScheduledThreadPoolExecutor executor = ThreadPoolBuilder.scheduledPool().build();\n\t\tExceptionTask task = new ExceptionTask();\n\t\texecutor.scheduleAtFixedRate(task, 0, 100, TimeUnit.MILLISECONDS);\n\n\t\tThreadUtil.sleep(500);\n\n\t\t// 线程第一次跑就被中断\n\t\tassertThat(task.counter.get()).isEqualTo(1);\n\t\tThreadPoolUtil.gracefulShutdown(executor, 1000);\n\n\t\t////////\n\t\texecutor = ThreadPoolBuilder.scheduledPool().build();\n\t\tExceptionTask newTask = new ExceptionTask();\n\t\tRunnable wrapTask = ThreadPoolUtil.safeRunnable(newTask);\n\t\texecutor.scheduleAtFixedRate(wrapTask, 0, 100, TimeUnit.MILLISECONDS);\n\n\t\tThreadUtil.sleep(500);\n\t\tassertThat(newTask.counter.get()).isGreaterThan(2);\n\t\tSystem.out.println(\"-------actual run:\" + task.counter.get());\n\t\tThreadPoolUtil.gracefulShutdown(executor, 1000);\n\n\t}\n\n\tstatic class ExceptionTask implements Runnable {\n\t\tpublic AtomicInteger counter = new AtomicInteger(0);\n\n\t\t@Override\n\t\tpublic void run() {\n\t\t\tcounter.incrementAndGet();\n\t\t\tthrow new RuntimeException(\"fail\");\n\t\t}\n\n\t}\n\n\tstatic class Task implements Runnable {\n\t\tprivate final Logger logger;\n\n\t\tprivate int runTime = 0;\n\n\t\tprivate final int sleepTime;\n\n\t\tTask(Logger logger, int sleepTime, int runTime) {\n\t\t\tthis.logger = logger;\n\t\t\tthis.sleepTime = sleepTime;\n\t\t\tthis.runTime = runTime;\n\t\t}\n\n\t\t@Override\n\t\tpublic void run() {\n\t\t\tSystem.out.println(\"start task\");\n\t\t\tif (runTime > 0) {\n\t\t\t\tlong start = System.currentTimeMillis();\n\t\t\t\twhile ((System.currentTimeMillis() - start) < runTime) {\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tThread.sleep(sleepTime);\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\tlogger.warn(\"InterruptedException\");\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/type/BasicFutureTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.type;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.base.ExceptionUtil;\nimport com.vip.vjtools.vjkit.concurrent.type.BasicFuture;\n\npublic class BasicFutureTest {\n\n\tpublic static class MyFuture<T> extends BasicFuture<T> {\n\n\t\t@Override\n\t\tprotected void onCompleted(T result) {\n\t\t\tSystem.out.println(\"onCompleted:\" + result);\n\t\t}\n\n\t\t@Override\n\t\tprotected void onFailed(Exception ex) {\n\t\t\tSystem.out.println(\"onFailed:\" + ex.getMessage());\n\t\t}\n\n\t\t@Override\n\t\tprotected void onCancelled() {\n\t\t\tSystem.out.println(\"onCancelled\");\n\t\t}\n\t}\n\n\tprivate static class Tasks {\n\n\t\tpublic static void success(MyFuture<String> future) {\n\t\t\tfuture.completed(\"haha\");\n\t\t}\n\n\t\tpublic static void fail(MyFuture<String> future) {\n\t\t\tfuture.failed(new RuntimeException(\"wuwu\"));\n\t\t}\n\n\t\tpublic static void cancel(MyFuture<String> future) {\n\t\t\tfuture.cancel(true);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void test() throws InterruptedException, ExecutionException {\n\t\tMyFuture<String> future = new MyFuture<String>();\n\t\tTasks.success(future);\n\t\tString result = future.get();\n\t\tassertThat(result).isEqualTo(\"haha\");\n\n\t\t// 无人设置返回值\n\t\ttry {\n\t\t\tMyFuture<String> future2 = new MyFuture<String>();\n\t\t\tfuture2.get(10, TimeUnit.MILLISECONDS);\n\t\t\tfail(\"should fail before\");\n\t\t} catch (TimeoutException e) {\n\t\t\tassertThat(e).isInstanceOf(TimeoutException.class);\n\t\t}\n\n\t\t// 失败\n\t\ttry {\n\t\t\tMyFuture<String> future3 = new MyFuture<String>();\n\t\t\tTasks.fail(future3);\n\t\t\tfuture3.get();\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(ExceptionUtil.unwrap(t)).hasMessage(\"wuwu\");\n\t\t}\n\n\t\t// 取消\n\t\tMyFuture<String> future4 = new MyFuture<String>();\n\t\tTasks.cancel(future4);\n\t\tassertThat(future4.isCancelled()).isTrue();\n\t\ttry {\n\t\t\tString result4 = future4.get();\n\t\t\tfail(\"should fail here\");\n\t\t} catch (CancellationException cae) {\n\n\t\t}\n\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/concurrent/type/ThreadLocalContextTest.java",
    "content": "package com.vip.vjtools.vjkit.concurrent.type;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.CyclicBarrier;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.concurrent.Concurrents;\nimport com.vip.vjtools.vjkit.concurrent.ThreadUtil;\nimport com.vip.vjtools.vjkit.concurrent.type.ThreadLocalContext;\nimport com.vip.vjtools.vjkit.number.RandomUtil;\n\npublic class ThreadLocalContextTest {\n\n\t@Test\n\tpublic void test() throws InterruptedException {\n\n\t\tfinal CountDownLatch countdown = Concurrents.countDownLatch(10);\n\t\tfinal CyclicBarrier barrier = Concurrents.cyclicBarrier(10);\n\n\t\tRunnable runnable = new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\ttry {\n\t\t\t\t\tbarrier.await();\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t\tThreadLocalContext.put(\"myname\", Thread.currentThread().getName());\n\t\t\t\tThreadUtil.sleep(RandomUtil.nextLong(100, 300));\n\t\t\t\tSystem.out.println((String) ThreadLocalContext.get(\"myname\"));\n\t\t\t\tThreadLocalContext.reset();\n\t\t\t\tSystem.out.println(\n\t\t\t\t\t\t\"shoud null for \" + Thread.currentThread().getName() + \":\" + ThreadLocalContext.get(\"myname\"));\n\t\t\t\tcountdown.countDown();\n\t\t\t}\n\t\t};\n\n\t\tfor (int i = 0; i < 10; i++) {\n\t\t\tThread thread = new Thread(runnable);\n\t\t\tthread.start();\n\t\t}\n\n\t\tcountdown.await();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/DataMaskJsonFilterTest.java",
    "content": "package com.vip.vjtools.vjkit.datamasking;\n\nimport com.alibaba.fastjson.JSON;\nimport com.vip.vjtools.vjkit.datamasking.data.TestData;\nimport com.vip.vjtools.vjkit.datamasking.data.TestUserMapingData;\nimport org.junit.Test;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n *\n * @author ken\n */\npublic class DataMaskJsonFilterTest {\n\n\t@Test\n\tpublic void testProcess() {\n\t\tDataMaskJsonFilter filter = new DataMaskJsonFilter();\n\n\t\tTestUserMapingData test1 = new TestUserMapingData();\n\t\ttest1.setTel(\"13590908322\");\n\t\ttest1.setTest(\"test\");\n\n\t\t//测试String[] 和 List\n\t\ttest1.setStrArr(new String[]{\"test1\", \"test2\"});\n\n\t\tArrayList<String> strList = new ArrayList<>();\n\t\tstrList.add(\"test1\");\n\t\tstrList.add(\"test2\");\n\t\ttest1.setStrList(strList);\n\n\t\t//测试set\n\t\tSet<String> setStr = new HashSet<>();\n\t\tsetStr.add(\"test1\");\n\t\ttest1.setSet(setStr);\n\n\n\t\tString json = JSON.toJSONString(test1, filter);\n\t\t//没有annotation的\n\t\ttest1 = JSON.parseObject(json, TestUserMapingData.class);\n\t\tassertThat(test1.getTel()).isEqualTo(\"135*****322\");\n\t\tassertThat(test1.getTest()).isEqualTo(\"test\");\n\n\t\tassertThat(test1.getStrArr()).contains(\"t****\");\n\t\tassertThat(test1.getStrList()).contains(\"t****\");\n\t\tassertThat(test1.getSet()).contains(\"t****\");\n\n\t\t//有annotation的\n\t\tTestData test2 = new TestData();\n\t\ttest2.setName(\"name\");\n\t\ttest2.setPhone(\"13655555555\");\n\t\ttest2.setAccount(\"苹果\");\n\t\tjson = JSON.toJSONString(test2, filter);\n\t\ttest2 = JSON.parseObject(json, TestData.class);\n\n\t\tassertThat(test2.getName()).isEqualTo(\"**me\");\n\t\tassertThat(test2.getPhone()).isEqualTo(\"13*******55\");\n\t\tassertThat(test2.getAccount()).isEqualTo(\"苹*\");\n\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/DataMaskTest.java",
    "content": "package com.vip.vjtools.vjkit.datamasking;\n\nimport com.alibaba.fastjson.JSON;\nimport com.vip.vjtools.vjkit.datamasking.data.TestChild;\nimport com.vip.vjtools.vjkit.datamasking.data.TestData;\nimport com.vip.vjtools.vjkit.datamasking.data.TestParent;\nimport com.vip.vjtools.vjkit.datamasking.data.TestUserMapingData;\nimport com.vip.vjtools.vjkit.datamasking.strategy.HashMask;\nimport com.vip.vjtools.vjkit.text.EncodeUtil;\nimport com.vip.vjtools.vjkit.text.HashUtil;\nimport org.junit.Test;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n *\n * @author ken\n */\npublic class DataMaskTest {\n\n\t@Test\n\tpublic void testMaskByType() {\n\t\tassertThat(DataMask.mask(\"王守仁\", SensitiveType.Name)).isEqualTo(\"**仁\");\n\t\tassertThat(DataMask.mask(\"13599090990\", SensitiveType.Phone)).isEqualTo(\"135*****990\");\n\t\tassertThat(DataMask.mask(\"441421199902132221\", SensitiveType.IDCard)).isEqualTo(\"44142***********21\");\n\t\tassertThat(DataMask.mask(\"441421199902132221\", SensitiveType.BankCard)).isEqualTo(\"4414************21\");\n\t\tassertThat(DataMask.mask(\"广东省广州市荔湾区花地湾1号\", SensitiveType.Address)).isEqualTo(\"广东省广州市荔湾区*****\");\n\t\tassertThat(DataMask.mask(\"test@vipshop.com\", SensitiveType.Email)).isEqualTo(\"t**t@vipshop.com\");\n\t\tassertThat(DataMask.mask(\"9527\", SensitiveType.Captcha)).isEqualTo(\"9**7\");\n\t\tassertThat(DataMask.mask(\"441421199902132221\", SensitiveType.Passport)).isEqualTo(\"44**************21\");\n\t\tassertThat(DataMask.mask(\"9527\", SensitiveType.Password)).isEqualTo(\"****\");\n\t\tassertThat(DataMask.mask(\"account\", SensitiveType.Account)).isEqualTo(\"a*****t\");\n\t\tassertThat(DataMask.mask(\"default\", SensitiveType.Default)).isEqualTo(\"d******\");\n\t\tassertThat(DataMask.mask(\"test\", SensitiveType.Hash)).isEqualTo(new HashMask().mask(\"test\", null));\n\t}\n\n\t@Test\n\tpublic void testMask() {\n\t\tassertThat(DataMask.mask(\"test\")).isEqualTo(\"t***\");\n\t}\n\n\t@Test\n\tpublic void testSha1Mask() throws Exception {\n\t\tString hash = DataMask.mask(\"test\", SensitiveType.Hash);\n\t\tSystem.out.println(hash);\n\n\t\tString salt = HashMask.getSalt();\n\t\tString encrypt =EncodeUtil.encodeHex(HashUtil.sha1(\"test\"+salt));\n\n\t\tassertThat(hash).isNotNull().isEqualTo(encrypt);\n\t}\n\n\t@Test\n\tpublic void testToJson() {\n\t\tTestData testData = new TestData();\n\t\ttestData.setName(\"123\");\n\t\ttestData.setPhone(\"1234567\");\n\t\ttestData.setAccount(\"test\");\n\t\ttestData.setHash(\"hash\");\n\t\ttestData.setTest(\"123456\");\n\n\t\tString mask = DataMask.toJSONString(testData);\n\t\tSystem.out.println(mask);\n\t\t//直接用json转的\n\t\ttestData.setName(\"**3\");\n\t\ttestData.setPhone(\"12***67\");\n\t\ttestData.setAccount(\"t***\");\n\t\ttestData.setTest(\"1**456\");\n\t\ttestData.setHash(new HashMask().mask(\"hash\", null));\n\t\tassertThat(mask).isEqualTo(JSON.toJSONString(testData));\n\t}\n\n\t@Test\n\tpublic void testToString() {\n\t\tTestData testData = new TestData();\n\t\ttestData.setName(\"123\");\n\t\ttestData.setPhone(\"1234567\");\n\t\ttestData.setAccount(\"test\");\n\t\ttestData.setHash(\"hash\");\n\t\ttestData.setTest(\"123456\");\n\t\tString mask = DataMask.toString(testData);\n\n\t\tSystem.out.println(mask);\n\n\t\ttestData.setName(\"**3\");\n\t\ttestData.setPhone(\"12***67\");\n\t\ttestData.setAccount(\"t***\");\n\t\ttestData.setTest(\"1**456\");\n\t\ttestData.setHash(new HashMask().mask(\"hash\", null));\n\t\tassertThat(mask).isEqualTo(testData.toString());\n\t}\n\n\t@Test\n\tpublic void testMapping() {\n\t\tTestUserMapingData data = new TestUserMapingData();\n\t\tdata.setNickName(\"nick\");\n\t\tdata.setTel(\"13590909090\");\n\n\t\tString mask = DataMask.toString(data);\n\t\tSystem.out.println(mask);\n\n\t\tdata.setNickName(\"**ck\");\n\t\tdata.setTel(\"135*****090\");\n\n\t\tassertThat(mask).isEqualTo(data.toString());\n\n\t}\n\n\t//继承嵌套测试\n\t@Test\n\tpublic void testInherit() {\n\t\tTestChild child = new TestChild();\n\t\tchild.setArr(new String[]{\"test11111\"});\n\t\tchild.setStr(\"test11111\");\n\t\tList<String> list = new ArrayList<>();\n\t\tlist.add(\"test11111\");\n\t\tchild.setList(list);\n\t\tSet<String> set = new HashSet<>();\n\t\tset.add(\"test11111\");\n\t\tchild.setSet(set);\n\n\t\tTestParent parent = new TestParent();\n\t\tparent.setChild(child);\n\t\tparent.setOther(parent);\n\n\t\tchild = new TestChild();\n\t\tchild.setArr(new String[]{\"test11111\"});\n\t\tchild.setStr(\"test11111\");\n\t\tlist = new ArrayList<>();\n\t\tlist.add(\"test11111\");\n\t\tchild.setList(list);\n\t\tset = new HashSet<>();\n\t\tset.add(\"test11111\");\n\t\tchild.setSet(set);\n\t\tparent.setChildren(Arrays.asList(child));\n\n\t\tString json = DataMask.toJSONString(parent);\n\t\tSystem.out.println(json);\n\n\t\t//普通的子类\n\t\tparent = JSON.parseObject(json, TestParent.class);\n\t\tassertThat(parent.getChild().getStr()).contains(\"*\");\n\t\tassertThat(parent.getChild().getArr()).contains(\"56C082E77E2924421F909BA262AA25BA80626323\");\n\t\tassertThat(parent.getChild().getList()).contains(\"t********\");\n\t\tassertThat(parent.getChild().getSet()).contains(\"t********\");\n\t\t//子类list\n\t\tassertThat(parent.getChildren().get(0).getStr()).contains(\"*\");\n\t\tassertThat(parent.getChildren().get(0).getArr()).contains(\"56C082E77E2924421F909BA262AA25BA80626323\");\n\t\tassertThat(parent.getChildren().get(0).getList()).contains(\"t********\");\n\t\tassertThat(parent.getChildren().get(0).getSet()).contains(\"t********\");\n\n\t\t//验证下toString\n\t\tparent.setOther(null);//去掉循环\n\t\tassertThat(DataMask.toString(\n\t\t\t\t\"TestParent{child=TestChild{str='t********', arr=[5489afe19ca3744d918d2821ed921e7bbc2b824b], list=[t********], set=[t********]}, children=[TestChild{str='t********', arr=[5489afe19ca3744d918d2821ed921e7bbc2b824b], list=[t********], set=[t********]}], other=null}\"));\n\t\tSystem.out.println(DataMask.toString(parent));\n\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/MaskMappingTest.java",
    "content": "package com.vip.vjtools.vjkit.datamasking;\n\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n *\n * @author ken\n */\npublic class MaskMappingTest {\n\n\t@Test\n\tpublic void test() {\n\t\t//系统定义的\n\t\tassertThat(MaskMapping.getMaskTypeMapping(\"tel\")).isNotNull();\n\t\t//自定义添加的\n\t\tassertThat(MaskMapping.getMaskTypeMapping(\"nickName\")).isNotNull();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/data/TestChild.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.data;\n\nimport com.vip.vjtools.vjkit.datamasking.Sensitive;\nimport com.vip.vjtools.vjkit.datamasking.SensitiveType;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n *\n * @author ken\n */\npublic class TestChild {\n\n\t@Sensitive\n\tprivate String str;\n\t@Sensitive(type = SensitiveType.Hash)\n\tprivate String[] arr;\n\t@Sensitive(type = SensitiveType.Address)\n\tprivate List<String> list;\n\t@Sensitive(type = SensitiveType.Account)\n\tprivate Set<String> set;\n\n\tpublic String getStr() {\n\t\treturn str;\n\t}\n\n\tpublic void setStr(String str) {\n\t\tthis.str = str;\n\t}\n\n\tpublic String[] getArr() {\n\t\treturn arr;\n\t}\n\n\tpublic void setArr(String[] arr) {\n\t\tthis.arr = arr;\n\t}\n\n\tpublic List<String> getList() {\n\t\treturn list;\n\t}\n\n\tpublic void setList(List<String> list) {\n\t\tthis.list = list;\n\t}\n\n\tpublic Set<String> getSet() {\n\t\treturn set;\n\t}\n\n\tpublic void setSet(Set<String> set) {\n\t\tthis.set = set;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"TestChild{\" + \"str='\" + str + '\\'' + \", arr=\" + Arrays.toString(arr) + \", list=\" + list + \", set=\" + set\n\t\t\t\t+ '}';\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/data/TestData.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.data;\n\nimport com.vip.vjtools.vjkit.datamasking.Sensitive;\nimport com.vip.vjtools.vjkit.datamasking.SensitiveType;\n\n/**\n *\n * @author ken\n */\npublic class TestData {\n\n\t@Sensitive(type = SensitiveType.Name)\n\tprivate String name;\n\n\t@Sensitive(keepChars = 2)\n\tprivate String phone;\n\n\t@Sensitive(type = SensitiveType.Hash)\n\tprivate String hash;\n\n\t@Sensitive\n\tprivate String account;\n\n\t@Sensitive(keepChars = {1, 3})\n\tprivate String test;\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic String getPhone() {\n\t\treturn phone;\n\t}\n\n\tpublic void setPhone(String phone) {\n\t\tthis.phone = phone;\n\t}\n\n\tpublic String getAccount() {\n\t\treturn account;\n\t}\n\n\tpublic void setAccount(String account) {\n\t\tthis.account = account;\n\t}\n\n\tpublic String getHash() {\n\t\treturn hash;\n\t}\n\n\tpublic void setHash(String hash) {\n\t\tthis.hash = hash;\n\t}\n\n\tpublic String getTest() {\n\t\treturn test;\n\t}\n\n\tpublic void setTest(String test) {\n\t\tthis.test = test;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"TestData{\" + \"name='\" + name + '\\'' + \", phone='\" + phone + '\\'' + \", hash='\" + hash + '\\''\n\t\t\t\t+ \", account='\" + account + '\\'' + \", test='\" + test + '\\'' + '}';\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/data/TestParent.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.data;\n\nimport java.util.List;\n\n/**\n *\n * @author ken\n */\npublic class TestParent {\n\n\tprivate TestChild child;\n\n\tprivate List<TestChild> children;\n\n\tprivate TestParent other;\n\n\tpublic TestChild getChild() {\n\t\treturn child;\n\t}\n\n\tpublic void setChild(TestChild child) {\n\t\tthis.child = child;\n\t}\n\n\tpublic List<TestChild> getChildren() {\n\t\treturn children;\n\t}\n\n\tpublic void setChildren(List<TestChild> children) {\n\t\tthis.children = children;\n\t}\n\n\tpublic TestParent getOther() {\n\t\treturn other;\n\t}\n\n\tpublic void setOther(TestParent other) {\n\t\tthis.other = other;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"TestParent{\" + \"child=\" + child + \", children=\" + children + \", other=\" + other + '}';\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/data/TestUserMapingData.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.data;\n\nimport com.vip.vjtools.vjkit.datamasking.Sensitive;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n *\n * @author ken\n */\npublic class TestUserMapingData {\n\n\tprivate String nickName;//自定义的\n\n\tprivate String tel;//系统配置的\n\n\tprivate String test;//默认的\n\n\t@Sensitive\n\tprivate String[] strArr;\n\n\t@Sensitive\n\tprivate List<String> strList;\n\n\t@Sensitive\n\tprivate Set<String> set;\n\n\t@Sensitive\n\tprivate Set<Integer> setInt;\n\n\tpublic String getTel() {\n\t\treturn tel;\n\t}\n\n\tpublic void setTel(String tel) {\n\t\tthis.tel = tel;\n\t}\n\n\tpublic String getNickName() {\n\t\treturn nickName;\n\t}\n\n\tpublic void setNickName(String nickName) {\n\t\tthis.nickName = nickName;\n\t}\n\n\n\tpublic String getTest() {\n\t\treturn test;\n\t}\n\n\tpublic void setTest(String test) {\n\t\tthis.test = test;\n\t}\n\n\n\tpublic String[] getStrArr() {\n\t\treturn strArr;\n\t}\n\n\tpublic void setStrArr(String[] strArr) {\n\t\tthis.strArr = strArr;\n\t}\n\n\tpublic List<String> getStrList() {\n\t\treturn strList;\n\t}\n\n\tpublic void setStrList(List<String> strList) {\n\t\tthis.strList = strList;\n\t}\n\n\n\tpublic Set<String> getSet() {\n\t\treturn set;\n\t}\n\n\tpublic void setSet(Set<String> set) {\n\t\tthis.set = set;\n\t}\n\n\tpublic Set<Integer> getSetInt() {\n\t\treturn setInt;\n\t}\n\n\tpublic void setSetInt(Set<Integer> setInt) {\n\t\tthis.setInt = setInt;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"TestUserMapingData{\" + \"nickName='\" + nickName + '\\'' + \", tel='\" + tel + '\\'' + \", test='\" + test\n\t\t\t\t+ '\\'' + \", strArr=\" + Arrays.toString(strArr) + \", strList=\" + strList + \", set=\" + set + \", setInt=\"\n\t\t\t\t+ setInt + '}';\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/strategy/EmailMaskTest.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.strategy;\n\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n *\n * @author ken\n */\npublic class EmailMaskTest {\n\n\t@Test\n\tpublic void testMask() {\n\t\tEmailMask mask = new EmailMask();\n\t\tassertThat(mask.mask(null, null)).isNull();\n\t\tassertThat(mask.mask(\"\", null)).isEmpty();\n\n\t\tassertThat(mask.mask(\"test\", null)).isEqualTo(\"t***\");\n\t\tassertThat(mask.mask(\"@test\", null)).isEqualTo(\"@****\");\n\n\t\tassertThat(mask.mask(\"123@test\", null)).isEqualTo(\"1*3@test\");\n\t\tassertThat(mask.mask(\"13@test\", null)).isEqualTo(\"1*@test\");\n\t\tassertThat(mask.mask(\"1@test\", null)).isEqualTo(\"*@test\");\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/strategy/HashMaskTest.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.strategy;\n\nimport com.vip.vjtools.vjkit.text.EncodeUtil;\nimport com.vip.vjtools.vjkit.text.HashUtil;\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n *\n * @author ken\n */\npublic class HashMaskTest {\n\n\t@Test\n\tpublic void testMask() throws Exception {\n\n\t\tHashMask mask = new HashMask();\n\t\tassertThat(mask.mask(null, null)).isNull();\n\n\t\tassertThat(mask.mask(\"\", null)).isEmpty();\n\n\t\tString salt = HashMask.getSalt();\n\t\tString encrypt = EncodeUtil.encodeHex(HashUtil.sha1(\"test\" + salt));\n\t\tassertThat(mask.mask(\"test\", null)).isEqualTo(encrypt);\n\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/strategy/NameMaskTest.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.strategy;\n\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n *\n * @author ken\n */\npublic class NameMaskTest {\n\n\t@Test\n\tpublic void testMask() {\n\t\tNameMask mask = new NameMask();\n\t\tassertThat(mask.mask(null, null)).isNull();\n\t\tassertThat(mask.mask(\"\", null)).isEmpty();\n\n\t\tassertThat(mask.mask(\"1\", null)).isEqualTo(\"*\");\n\t\tassertThat(mask.mask(\"中文\", null)).isEqualTo(\"*文\");\n\t\tassertThat(mask.mask(\"中文3\", null)).isEqualTo(\"**3\");\n\t\tassertThat(mask.mask(\"中文四个\", null)).isEqualTo(\"**四个\");\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/datamasking/strategy/PartMaskTest.java",
    "content": "package com.vip.vjtools.vjkit.datamasking.strategy;\n\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n *\n * @author ken\n */\npublic class PartMaskTest {\n\n\t@Test\n\tpublic void testMask() {\n\t\tPartMask mask = new PartMask();\n\n\t\tassertThat(mask.mask(null, new int[]{1})).isNull();\n\t\tassertThat(mask.mask(\"\", new int[]{1})).isEmpty();\n\t\tassertThat(mask.mask(\"1\", new int[]{1})).isEqualTo(\"*\");\n\n\t\tassertThat(mask.mask(\"123\", new int[]{1})).isEqualTo(\"1*3\");\n\t\tassertThat(mask.mask(\"12345\", new int[]{2})).isEqualTo(\"12*45\");\n\t\tassertThat(mask.mask(\"1234\", new int[]{1, 2})).isEqualTo(\"1*34\");\n\t\tassertThat(mask.mask(\"1234\", new int[]{1, 0})).isEqualTo(\"1***\");\n\t\tassertThat(mask.mask(\"1234\", new int[]{0, 1})).isEqualTo(\"***4\");\n\n\t\tassertThat(mask.mask(\"1234\", new int[]{0, 0})).isEqualTo(\"****\");\n\n\t\t//验证不通过的\n\t\tassertThat(mask.mask(\"12\", new int[]{1, 1})).isEqualTo(\"1*\");\n\t\tassertThat(mask.mask(\"1234\", new int[]{4, 1})).isEqualTo(\"1***\");\n\t\tassertThat(mask.mask(\"1234\", new int[]{5, 1})).isEqualTo(\"1***\");\n\t\tassertThat(mask.mask(\"1234\", new int[]{1, 4})).isEqualTo(\"1***\");\n\t\tassertThat(mask.mask(\"1234\", new int[]{1, 5})).isEqualTo(\"1***\");\n\t\tassertThat(mask.mask(\"1234\", new int[]{3, 2})).isEqualTo(\"1***\");\n\t\tassertThat(mask.mask(\"1234\", new int[]{2, 2})).isEqualTo(\"1***\");\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/id/IdUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.id;\n\nimport java.util.UUID;\n\nimport org.junit.Test;\n\npublic class IdUtilTest {\n\n\t@Test\n\tpublic void normal() {\n\t\tUUID id1 = IdUtil.fastUUID();\n\t\tUUID id2 = IdUtil.fastUUID();\n\t\tSystem.out.println(\"UUID1:\" + id1);\n\t\tSystem.out.println(\"UUID2:\" + id2);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/io/FilePathUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.google.common.io.Files;\nimport com.vip.vjtools.vjkit.base.Platforms;\n\npublic class FilePathUtilTest {\n\n\tchar sep = Platforms.FILE_PATH_SEPARATOR_CHAR;\n\n\t@Test\n\tpublic void pathName() {\n\t\tString filePath = FilePathUtil.concat(sep + \"abc\", \"ef\");\n\t\tassertThat(filePath).isEqualTo(FilePathUtil.normalizePath(\"/abc/ef\"));\n\n\t\tString filePath2 = FilePathUtil.concat(sep + \"stuv\" + sep, \"xy\");\n\t\tassertThat(filePath2).isEqualTo(FilePathUtil.normalizePath(\"/stuv/xy\"));\n\n\t\tassertThat(FilePathUtil.simplifyPath(\"../dd/../abc\")).isEqualTo(\"../abc\");\n\t\tassertThat(FilePathUtil.simplifyPath(\"../../dd/../abc\")).isEqualTo(\"../../abc\");\n\t\tassertThat(FilePathUtil.simplifyPath(\"./abc\")).isEqualTo(\"abc\");\n\n\t\tassertThat(FilePathUtil.getParentPath(FilePathUtil.normalizePath(\"/abc/dd/efg/\")))\n\t\t\t\t.isEqualTo(FilePathUtil.normalizePath(\"/abc/dd/\"));\n\n\t\tassertThat(FilePathUtil.getParentPath(FilePathUtil.normalizePath(\"/abc/dd/efg.txt\")))\n\t\t\t\t.isEqualTo(FilePathUtil.normalizePath(\"/abc/dd/\"));\n\t}\n\n\t@Test\n\tpublic void getJarPath() {\n\t\tSystem.out.println(\"the jar file contains Files.class\" + FilePathUtil.getJarPath(Files.class));\n\t\tassertThat(FilePathUtil.getJarPath(Files.class)).endsWith(\"guava-20.0.jar\");\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/io/FileTreeWalkerTest.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.number.RandomUtil;\n\npublic class FileTreeWalkerTest {\n\n\t@Test\n\tpublic void listFile() throws IOException {\n\t\tFile tmpDir = FileUtil.createTempDir().toFile();\n\n\t\tList<File> all = FileTreeWalker.listAll(tmpDir);\n\t\tassertThat(all).hasSize(1);\n\n\t\tList<File> files = FileTreeWalker.listFile(tmpDir);\n\t\tassertThat(files).hasSize(0);\n\n\t\tFileUtil.touch(FilePathUtil.concat(tmpDir.getAbsolutePath(), \"tmp-\" + RandomUtil.nextInt()) + \".tmp\");\n\t\tFileUtil.touch(FilePathUtil.concat(tmpDir.getAbsolutePath(), \"tmp-\" + RandomUtil.nextInt()) + \".abc\");\n\n\t\tString childDir = FilePathUtil.concat(tmpDir.getAbsolutePath(), \"tmp-\" + RandomUtil.nextInt());\n\t\tFileUtil.makesureDirExists(childDir);\n\n\t\tFileUtil.touch(FilePathUtil.concat(childDir, \"tmp-\" + RandomUtil.nextInt()) + \".tmp\");\n\n\t\tall = FileTreeWalker.listAll(tmpDir);\n\t\tassertThat(all).hasSize(5);\n\n\t\tfiles = FileTreeWalker.listFile(tmpDir);\n\t\tassertThat(files).hasSize(3);\n\n\t\t// extension\n\t\tfiles = FileTreeWalker.listFileWithExtension(tmpDir, \"tmp\");\n\t\tassertThat(files).hasSize(2);\n\n\t\tfiles = FileTreeWalker.listFileWithExtension(tmpDir, \"tp\");\n\t\tassertThat(files).hasSize(0);\n\n\t\t// wildcard\n\t\tfiles = FileTreeWalker.listFileWithWildcardFileName(tmpDir, \"*.tmp\");\n\t\tassertThat(files).hasSize(2);\n\t\tfiles = FileTreeWalker.listFileWithWildcardFileName(tmpDir, \"*.tp\");\n\t\tassertThat(files).hasSize(0);\n\n\t\t// regex\n\t\tfiles = FileTreeWalker.listFileWithRegexFileName(tmpDir, \".*\\\\.tmp\");\n\t\tassertThat(files).hasSize(2);\n\t\tfiles = FileTreeWalker.listFileWithRegexFileName(tmpDir, \".*\\\\.tp\");\n\t\tassertThat(files).hasSize(0);\n\n\n\t\t// antpath\n\t\tfiles = FileTreeWalker.listFileWithAntPath(tmpDir, \"**\" + File.separator + \"*.tmp\");\n\t\tassertThat(files).hasSize(2);\n\n\t\tfiles = FileTreeWalker.listFileWithAntPath(tmpDir, \"*\" + File.separator + \"*.tmp\");\n\t\tassertThat(files).hasSize(1);\n\n\t\tfiles = FileTreeWalker.listFileWithAntPath(tmpDir, \"*.tp\");\n\t\tassertThat(files).hasSize(0);\n\n\t\tFileUtil.deleteDir(tmpDir);\n\n\t\tassertThat(FileUtil.isDirExists(tmpDir)).isFalse();\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/io/FileUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.file.Path;\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport com.google.common.io.Files;\nimport com.vip.vjtools.vjkit.base.Platforms;\nimport com.vip.vjtools.vjkit.number.RandomUtil;\nimport com.vip.vjtools.vjkit.text.Charsets;\n\npublic class FileUtilTest {\n\n\t@Test\n\tpublic void readWrite() throws IOException {\n\t\tFile file = FileUtil.createTempFile(\"abc\", \".tmp\").toFile();\n\t\ttry {\n\t\t\tString content = \"haha\\nhehe\";\n\t\t\tFileUtil.write(content, file);\n\n\t\t\tString result = FileUtil.toString(file);\n\t\t\tassertThat(result).isEqualTo(content);\n\t\t\tList<String> lines = FileUtil.toLines(file);\n\t\t\tassertThat(lines).containsExactly(\"haha\", \"hehe\");\n\n\t\t\tFileUtil.append(\"kaka\", file);\n\t\t\tassertThat(new String(FileUtil.toByteArray(file), Charsets.UTF_8)).isEqualTo(\"haha\\nhehekaka\");\n\t\t} finally {\n\t\t\tFileUtil.deleteFile(file);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void opFiles() throws IOException {\n\t\tFile file = new File(FilePathUtil.concat(Platforms.TMP_DIR, \"testFile\" + RandomUtil.nextInt()));\n\t\tFileUtil.touch(file);\n\t\tassertThat(FileUtil.isFileExists(file)).isTrue();\n\t\tFileUtil.touch(file);\n\n\t\tString content = \"haha\\nhehe\";\n\t\tFileUtil.write(content, file);\n\t\tassertThat(FileUtil.toString(file)).isEqualTo(content);\n\n\t\tFile newFile = new File(FilePathUtil.concat(Platforms.TMP_DIR, \"testFile\" + RandomUtil.nextInt()));\n\t\tFile newFile2 = new File(FilePathUtil.concat(Platforms.TMP_DIR, \"testFile\" + RandomUtil.nextInt()));\n\n\t\tFileUtil.copyFile(file, newFile);\n\t\tassertThat(FileUtil.isFileExists(newFile)).isTrue();\n\t\tassertThat(FileUtil.toString(newFile)).isEqualTo(content);\n\n\t\tFileUtil.moveFile(newFile, newFile2);\n\t\tassertThat(FileUtil.toString(newFile2)).isEqualTo(\"haha\\nhehe\");\n\n\n\t}\n\n\t@Test\n\tpublic void opDir() throws IOException {\n\t\tString fileName = \"testFile\" + RandomUtil.nextInt();\n\t\tFile dir = new File(FilePathUtil.concat(Platforms.TMP_DIR, \"testDir\"));\n\n\t\tFile file = new File(FilePathUtil.concat(Platforms.TMP_DIR, \"testDir\", fileName));\n\t\tString content = \"haha\\nhehe\";\n\t\tFileUtil.makesureDirExists(dir);\n\t\tFileUtil.write(content, file);\n\n\t\tFile dir2 = new File(FilePathUtil.concat(Platforms.TMP_DIR, \"testDir2\"));\n\t\tFileUtil.copyDir(dir, dir2);\n\t\tFile file2 = new File(FilePathUtil.concat(Platforms.TMP_DIR, \"testDir2\", fileName));\n\t\tassertThat(FileUtil.toString(file2)).isEqualTo(\"haha\\nhehe\");\n\n\t\tFile dir3 = new File(FilePathUtil.concat(Platforms.TMP_DIR, \"testDir3\"));\n\t\tFileUtil.moveDir(dir, dir3);\n\t\tFile file3 = new File(FilePathUtil.concat(Platforms.TMP_DIR, \"testDir3\", fileName));\n\t\tassertThat(FileUtil.toString(file3)).isEqualTo(\"haha\\nhehe\");\n\t\tassertThat(FileUtil.isDirExists(dir)).isFalse();\n\n\t}\n\n\t@Test\n\tpublic void fileExist() throws IOException {\n\t\tassertThat(FileUtil.isDirExists(Platforms.TMP_DIR)).isTrue();\n\t\tassertThat(FileUtil.isDirExists(Platforms.TMP_DIR + RandomUtil.nextInt())).isFalse();\n\n\t\tFile tmpFile = null;\n\t\ttry {\n\t\t\ttmpFile = FileUtil.createTempFile().toFile();\n\t\t\tassertThat(FileUtil.isFileExists(tmpFile)).isTrue();\n\n\t\t\tassertThat(FileUtil.isFileExists(tmpFile.getAbsolutePath() + RandomUtil.nextInt())).isFalse();\n\n\t\t} finally {\n\t\t\tFileUtil.deleteFile(tmpFile);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void getName() {\n\n\t\tassertThat(FileUtil.getFileName(FilePathUtil.normalizePath(\"/a/d/b/abc.txt\"))).isEqualTo(\"abc.txt\");\n\t\tassertThat(FileUtil.getFileName(\"abc.txt\")).isEqualTo(\"abc.txt\");\n\n\t\tassertThat(FileUtil.getFileExtension(FilePathUtil.normalizePath(\"a/d/b/abc.txt\"))).isEqualTo(\"txt\");\n\t\tassertThat(FileUtil.getFileExtension(FilePathUtil.normalizePath(\"/a/d/b/abc\"))).isEqualTo(\"\");\n\t\tassertThat(FileUtil.getFileExtension(FilePathUtil.normalizePath(\"/a/d/b/abc.\"))).isEqualTo(\"\");\n\n\t}\n\n\t@Test\n\tpublic void testAsInputStream() throws Exception {\n\t\tPath tempPath = FileUtil.createTempFile();\n\t\ttry (InputStream is = FileUtil.asInputStream(tempPath.toString());) {\n\t\t\tassertThat(is).isNotNull();\n\t\t}\n\n\t\ttry (InputStream is = FileUtil.asInputStream(tempPath);) {\n\t\t\tassertThat(is).isNotNull();\n\t\t}\n\n\t\ttry (InputStream is = FileUtil.asInputStream(tempPath.toFile());) {\n\t\t\tassertThat(is).isNotNull();\n\t\t}\n\n\t}\n\n\t@Test\n\tpublic void testAsOututStream() throws Exception {\n\n\t\tPath tempPath = FileUtil.createTempFile();\n\t\ttry (OutputStream os = FileUtil.asOutputStream(tempPath.toString())) {\n\t\t\tassertThat(os).isNotNull();\n\t\t}\n\n\t\ttry (OutputStream os = FileUtil.asOutputStream(tempPath);) {\n\t\t\tassertThat(os).isNotNull();\n\t\t}\n\n\t\ttry (OutputStream os = FileUtil.asOutputStream(tempPath.toFile())) {\n\t\t\tassertThat(os).isNotNull();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAsBufferedReader() throws Exception {\n\n\t\tPath tempPath = FileUtil.createTempFile();\n\t\ttry (BufferedReader br = FileUtil.asBufferedReader(tempPath.toString())) {\n\t\t\tassertThat(br).isNotNull();\n\t\t}\n\n\t\ttry (BufferedReader br = FileUtil.asBufferedReader(tempPath)) {\n\t\t\tassertThat(br).isNotNull();\n\t\t}\n\n\t}\n\n\t@Test\n\tpublic void testAsBufferedWriter() throws Exception {\n\n\t\tPath tempPath = FileUtil.createTempFile();\n\t\ttry (BufferedWriter bw = FileUtil.asBufferedWriter(tempPath.toString())) {\n\t\t\tassertThat(bw).isNotNull();\n\t\t}\n\t\ttry (BufferedWriter bw = FileUtil.asBufferedWriter(tempPath)) {\n\t\t\tassertThat(bw).isNotNull();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testCopy() throws Exception {\n\t\tPath dir = FileUtil.createTempDir();\n\n\t\tassertThat(dir).exists();\n\n\t\tString srcFileName = \"src\";\n\t\tFile srcFile = dir.resolve(srcFileName).toFile();\n\t\tFileUtil.touch(srcFile);\n\n\t\tassertThat(srcFile).exists();\n\n\t\tFileUtil.write(\"test\", srcFile);\n\n\t\tString destFileName = \"dest\";\n\n\t\tFile destFile = new File(dir.toFile(), \"parent1/parent2/\" + destFileName);\n\t\tFileUtil.makesureParentDirExists(destFile);\n\n\t\tFileUtil.copy(srcFile, destFile);\n\n\t\tassertThat(Files.readFirstLine(destFile, Charsets.UTF_8)).isEqualTo(\"test\");\n\t}\n\n\t@Test\n\tpublic void testMakesureDirExists() throws Exception {\n\t\tPath dir = FileUtil.createTempDir();\n\t\tString child1 = \"child1\";\n\n\t\tPath child1Dir = dir.resolve(child1);\n\t\tFileUtil.makesureDirExists(child1Dir.toString());\n\t\tassertThat(child1Dir).exists();\n\n\t\tString child2 = \"child2\";\n\t\tPath child2Dir = dir.resolve(child2);\n\t\tFileUtil.makesureDirExists(child2Dir);\n\t\tassertThat(child2Dir).exists();\n\n\t\tString child3 = \"child3\";\n\t\tPath child3Dir = dir.resolve(child3);\n\t\tFileUtil.makesureDirExists(child3Dir.toFile());\n\t\tassertThat(child3Dir).exists();\n\t}\n\n\t@Test\n\tpublic void testIsFileExists() throws Exception {\n\t\tassertThat(FileUtil.isFileExists((String) null)).isFalse();\n\t\tassertThat(FileUtil.isFileExists((File) null)).isFalse();\n\n\t\tPath dir = FileUtil.createTempDir();\n\t\tFileUtil.touch(dir + \"/\" + \"test\");\n\n\t\tassertThat(FileUtil.isFileExists(dir + \"/\" + \"test\")).isTrue();\n\n\t\tassertThat(FileUtil.isFileExists(dir.resolve(\"test\").toFile())).isTrue();\n\n\t}\n\n\t@Test\n\tpublic void testGetFileExtension() throws Exception {\n\t\tPath path = FileUtil.createTempFile(\"aaa\", \".txt\");\n\n\t\tassertThat(FileUtil.getFileExtension(path.toFile())).isEqualTo(\"txt\");\n\t\tassertThat(FileUtil.getFileExtension(path.toString())).isEqualTo(\"txt\");\n\t}\n\n\t@Test\n\tpublic void testIsDirExists() throws Exception {\n\t\tassertThat(FileUtil.isDirExists((String) null)).isFalse();\n\t\tassertThat(FileUtil.isDirExists((File) null)).isFalse();\n\t\tassertThat(FileUtil.isDirExists((Path) null)).isFalse();\n\n\t\tPath dir = FileUtil.createTempDir();\n\n\t\tassertThat(FileUtil.isDirExists(dir)).isTrue();\n\t\tassertThat(FileUtil.isDirExists(dir.toString())).isTrue();\n\t\tassertThat(FileUtil.isDirExists(dir.toFile())).isTrue();\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/io/IOUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.Closeable;\nimport java.io.IOException;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.io.type.StringBuilderWriter;\nimport com.vip.vjtools.vjkit.text.Charsets;\n\npublic class IOUtilTest {\n\n\t@Test\n\tpublic void read() throws IOException {\n\t\tassertThat(IOUtil.toString(ResourceUtil.asStream(\"test.txt\"))).isEqualTo(\"ABCDEFG\\nABC\");\n\t\tassertThat(IOUtil.toLines(ResourceUtil.asStream(\"test.txt\"))).hasSize(2).containsExactly(\"ABCDEFG\", \"ABC\");\n\t}\n\n\t@Test\n\tpublic void write() throws IOException {\n\t\tStringBuilderWriter sw = new StringBuilderWriter();\n\t\tIOUtil.write(\"hahahaha\", sw);\n\t\tassertThat(sw.toString()).isEqualTo(\"hahahaha\");\n\n\t\tByteArrayOutputStream out = new ByteArrayOutputStream();\n\t\tIOUtil.write(\"hahahaha\", out);\n\t\tassertThat(new String(out.toByteArray(), Charsets.UTF_8)).isEqualTo(\"hahahaha\");\n\n\t\tIOUtil.closeQuietly(out);\n\t\tIOUtil.closeQuietly((Closeable) null);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/io/ResourceUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.io.IOException;\nimport java.util.jar.JarFile;\n\nimport org.junit.Test;\n\nimport com.google.common.io.Files;\n\npublic class ResourceUtilTest {\n\n\t@Test\n\tpublic void test() throws IOException {\n\t\t// getResoruce\n\t\tassertThat(ResourceUtil.toString(\"test.txt\")).contains(\"ABCDEFG\");\n\t\tassertThat(ResourceUtil.toString(ResourceUtilTest.class, \"/test.txt\")).contains(\"ABCDEFG\");\n\t\tassertThat(ResourceUtil.toLines(\"test.txt\")).containsExactly(\"ABCDEFG\", \"ABC\");\n\t\tassertThat(ResourceUtil.toLines(ResourceUtilTest.class, \"/test.txt\")).containsExactly(\"ABCDEFG\", \"ABC\");\n\n\t\t// getResoruce 处理重复的资源\n\t\tSystem.out.println(ResourceUtil.asUrl(\"META-INF/MANIFEST.MF\"));\n\t\tassertThat(ResourceUtil.toString(\"META-INF/MANIFEST.MF\")).contains(\"Manifest\");\n\n\t\t// getResources\n\t\tassertThat(ResourceUtil.getResourcesQuietly(\"META-INF/MANIFEST.MF\").size()).isGreaterThan(1);\n\n\t\tSystem.out.println(ResourceUtil.getResourcesQuietly(\"META-INF/MANIFEST.MF\"));\n\n\t\tassertThat(ResourceUtil.getResourcesQuietly(\"META-INF/MANIFEST.MF\", ResourceUtilTest.class.getClassLoader())\n\t\t\t\t.size()).isGreaterThan(1);\n\n\t}\n\n\t@Test\n\tpublic void resourceNameTest() throws IOException {\n\t\tJarFile guavaFile = new JarFile(FilePathUtil.getJarPath(Files.class));\n\t\tassertThat(guavaFile.getEntry(\"META-INF/MANIFEST.MF\")).isNotNull();\n\t\tassertThat(guavaFile.getEntry(\"/META-INF/MANIFEST.MF\")).isNull();\n\t\tguavaFile.close();\n\t}\n\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/io/URLResourceTest.java",
    "content": "package com.vip.vjtools.vjkit.io;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport org.junit.Test;\n\npublic class URLResourceTest {\n\n\t@Test\n\tpublic void resource() throws IOException {\n\t\tFile file = URLResourceUtil.asFile(\"classpath:application.properties\");\n\t\tassertThat(FileUtil.toString(file)).isEqualTo(\"springside.min=1\\nspringside.max=10\");\n\n\t\tInputStream is = URLResourceUtil.asStream(\"classpath:application.properties\");\n\t\tassertThat(IOUtil.toString(is)).isEqualTo(\"springside.min=1\\nspringside.max=10\");\n\t\tIOUtil.closeQuietly(is);\n\n\t\ttry {\n\t\t\tURLResourceUtil.asFile(\"classpath:notexist.properties\");\n\t\t\tfail(\"should fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tURLResourceUtil.asStream(\"classpath:notexist.properties\");\n\t\t\tfail(\"should fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t}\n\n\t@Test\n\tpublic void file() throws IOException {\n\t\tFile file = FileUtil.createTempFile().toFile();\n\t\tFileUtil.write(\"haha\", file);\n\n\t\ttry {\n\t\t\tFile file2 = URLResourceUtil.asFile(\"file://\" + file.getAbsolutePath());\n\t\t\tassertThat(FileUtil.toString(file2)).isEqualTo(\"haha\");\n\n\t\t\tFile file2NotExist = URLResourceUtil.asFile(\"file://\" + file.getAbsolutePath() + \".noexist\");\n\t\t\tassertThat(file2NotExist.exists()).isFalse();\n\n\t\t\tFile file3 = URLResourceUtil.asFile(file.getAbsolutePath());\n\t\t\tassertThat(FileUtil.toString(file3)).isEqualTo(\"haha\");\n\t\t\tFile file3NotExist = URLResourceUtil.asFile(file.getAbsolutePath() + \".noexist\");\n\t\t\tassertThat(file3NotExist.exists()).isFalse();\n\t\t} finally {\n\t\t\tFileUtil.deleteFile(file);\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/logging/PerformanceUtilsTest.java",
    "content": "package com.vip.vjtools.vjkit.logging;\n\nimport org.junit.Test;\nimport org.slf4j.LoggerFactory;\n\nimport ch.qos.logback.classic.Logger;\n\npublic class PerformanceUtilsTest {\n\n\tLogger logger = (Logger) LoggerFactory.getLogger(PerformanceUtilsTest.class);\n\n\t@Test\n\tpublic void test() throws InterruptedException {\n\t\tPerformanceUtil.start();\n\t\tPerformanceUtil.start(\"test\");\n\t\tThread.sleep(1000L);// NOSONAR\n\n\t\tPerformanceUtil.endWithSlowLog(logger, 100L);\n\t\tPerformanceUtil.endWithSlowLog(logger, \"test\", 100L);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/mapper/BeanMapperTest.java",
    "content": "package com.vip.vjtools.vjkit.mapper;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.collection.ListUtil;\n\npublic class BeanMapperTest {\n\n\t@Test\n\tpublic void copySingleObject() {\n\t\tStudent student = new Student(\"zhang3\", 20, new Teacher(\"li4\"), ListUtil.newArrayList(\"chinese\", \"english\"));\n\n\t\tStudentVO studentVo = BeanMapper.map(student, StudentVO.class);\n\n\t\tassertThat(studentVo.name).isEqualTo(\"zhang3\");\n\t\tassertThat(studentVo.getAge()).isEqualTo(20);\n\t\tassertThat(studentVo.getTeacher().getName()).isEqualTo(\"li4\");\n\t\tassertThat(studentVo.getCourse()).containsExactly(\"chinese\", \"english\");\n\n\t}\n\n\t@Test\n\tpublic void copyListObject() {\n\t\tStudent student1 = new Student(\"zhang3\", 20, new Teacher(\"li4\"), ListUtil.newArrayList(\"chinese\", \"english\"));\n\t\tStudent student2 = new Student(\"zhang4\", 30, new Teacher(\"li5\"), ListUtil.newArrayList(\"chinese2\", \"english4\"));\n\t\tStudent student3 = new Student(\"zhang5\", 40, new Teacher(\"li6\"), ListUtil.newArrayList(\"chinese3\", \"english5\"));\n\t\tList<Student> studentList = ListUtil.newArrayList(student1, student2, student3);\n\n\t\tList<StudentVO> studentVoList = BeanMapper.mapList(studentList, StudentVO.class);\n\t\tassertThat(studentVoList).hasSize(3);\n\t\tStudentVO studentVo = studentVoList.get(0);\n\n\t\tassertThat(studentVo.name).isEqualTo(\"zhang3\");\n\t\tassertThat(studentVo.getAge()).isEqualTo(20);\n\t\tassertThat(studentVo.getTeacher().getName()).isEqualTo(\"li4\");\n\t\tassertThat(studentVo.getCourse()).containsExactly(\"chinese\", \"english\");\n\n\t}\n\n\t@Test\n\tpublic void copyArrayObject() {\n\t\tStudent student1 = new Student(\"zhang3\", 20, new Teacher(\"li4\"), ListUtil.newArrayList(\"chinese\", \"english\"));\n\t\tStudent student2 = new Student(\"zhang4\", 30, new Teacher(\"li5\"), ListUtil.newArrayList(\"chinese2\", \"english4\"));\n\t\tStudent student3 = new Student(\"zhang5\", 40, new Teacher(\"li6\"), ListUtil.newArrayList(\"chinese3\", \"english5\"));\n\t\tStudent[] studentList = new Student[] { student1, student2, student3 };\n\t\tStudentVO[] studentVoList = BeanMapper.mapArray(studentList, StudentVO.class);\n\t\tassertThat(studentVoList).hasSize(3);\n\t\tStudentVO studentVo = studentVoList[0];\n\n\t\tassertThat(studentVo.name).isEqualTo(\"zhang3\");\n\t\tassertThat(studentVo.getAge()).isEqualTo(20);\n\t\tassertThat(studentVo.getTeacher().getName()).isEqualTo(\"li4\");\n\t\tassertThat(studentVo.getCourse()).containsExactly(\"chinese\", \"english\");\n\n\t}\n\n\t@Test\n\tpublic void copy2Map() {\n\t\tTeacher teacher = new Teacher(\"zhang\");\n\t\tMap map = BeanMapper.map(teacher, Map.class);\n\t\tassertThat(map).containsKeys(\"name\").containsValues(\"zhang\");\n\n\t\tStudent student = new Student(\"zhang3\", 20, new Teacher(\"li4\"), ListUtil.newArrayList(\"chinese\", \"english\"));\n\t\tMap mapStu = BeanMapper.map(student, Map.class);\n\t\tassertThat(mapStu.containsKey(\"teacher\"));\n\t\tassertThat(mapStu.get(\"teacher\")).hasFieldOrProperty(\"name\");\n\t}\n\n\tpublic static class Student {\n\t\tpublic String name;\n\t\tprivate int age;\n\t\tprivate Teacher teacher;\n\t\tprivate List<String> course = ListUtil.newArrayList();\n\n\t\tpublic Student() {\n\n\t\t}\n\n\t\tpublic Student(String name, int age, Teacher teacher, List<String> course) {\n\t\t\tthis.name = name;\n\t\t\tthis.age = age;\n\t\t\tthis.teacher = teacher;\n\t\t\tthis.course = course;\n\t\t}\n\n\t\tpublic List<String> getCourse() {\n\t\t\treturn course;\n\t\t}\n\n\t\tpublic void setCourse(List<String> course) {\n\t\t\tthis.course = course;\n\t\t}\n\n\t\tpublic int getAge() {\n\t\t\treturn age;\n\t\t}\n\n\t\tpublic void setAge(int age) {\n\t\t\tthis.age = age;\n\t\t}\n\n\t\tpublic Teacher getTeacher() {\n\t\t\treturn teacher;\n\t\t}\n\n\t\tpublic void setTeacher(Teacher teacher) {\n\t\t\tthis.teacher = teacher;\n\t\t}\n\n\t\tpublic String getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\tpublic void setName(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\n\t}\n\n\tpublic static class Teacher {\n\t\tprivate String name;\n\n\t\tpublic Teacher() {\n\n\t\t}\n\n\t\tpublic Teacher(String name) {\n\t\t\tsuper();\n\t\t\tthis.name = name;\n\t\t}\n\n\t\tpublic String getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\tpublic void setName(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\t}\n\n\tpublic static class StudentVO {\n\t\tpublic String name;\n\t\tprivate int age;\n\t\tprivate TeacherVO teacher;\n\t\tprivate List<String> course = ListUtil.newArrayList();\n\n\t\tpublic StudentVO() {\n\n\t\t}\n\n\t\tpublic StudentVO(String name, int age, TeacherVO teacher, List<String> course) {\n\t\t\tthis.name = name;\n\t\t\tthis.age = age;\n\t\t\tthis.teacher = teacher;\n\t\t\tthis.course = course;\n\t\t}\n\n\t\tpublic List<String> getCourse() {\n\t\t\treturn course;\n\t\t}\n\n\t\tpublic void setCourse(List<String> course) {\n\t\t\tthis.course = course;\n\t\t}\n\n\t\tpublic int getAge() {\n\t\t\treturn age;\n\t\t}\n\n\t\tpublic void setAge(int age) {\n\t\t\tthis.age = age;\n\t\t}\n\n\t\tpublic TeacherVO getTeacher() {\n\t\t\treturn teacher;\n\t\t}\n\n\t\tpublic void setTeacher(TeacherVO teacher) {\n\t\t\tthis.teacher = teacher;\n\t\t}\n\n\t\tpublic String getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\tpublic void setName(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\t}\n\n\tpublic static class TeacherVO {\n\t\tprivate String name;\n\n\t\tpublic TeacherVO() {\n\n\t\t}\n\n\t\tpublic TeacherVO(String name) {\n\t\t\tsuper();\n\t\t\tthis.name = name;\n\t\t}\n\n\t\tpublic String getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\tpublic void setName(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/mapper/JsonMapperTest.java",
    "content": "package com.vip.vjtools.vjkit.mapper;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nimport org.junit.Test;\n\nimport com.google.common.collect.Lists;\nimport com.google.common.collect.Maps;\nimport org.json.JSONException;\nimport org.skyscreamer.jsonassert.JSONAssert;\n\n/**\n * 测试Jackson对Object,Map,List,数组,枚举,日期类等的持久化. 更多测试见showcase中的JsonDemo.\n */\npublic class JsonMapperTest {\n\n\tpublic void assertJSONEqual(String s1, String s2) throws JSONException {\n\t\tJSONAssert.assertEquals(s1, s2, false);\n\t}\n\t/**\n\t * 序列化对象/集合到Json字符串.\n\t */\n\t@Test\n\tpublic void toJson() throws Exception {\n\t\t// Bean\n\t\tTestBean bean = new TestBean(\"A\");\n\t\tString beanString = JsonMapper.INSTANCE.toJson(bean);\n\t\tSystem.out.println(\"Bean:\" + beanString);\n\t\tassertJSONEqual(beanString,\n\t\t\t\t\"{\\\"name\\\":\\\"A\\\",\\\"defaultValue\\\":\\\"hello\\\",\\\"nullValue\\\":null,\\\"emptyValue\\\":[]}\");\n\n\t\t// Map\n\t\tMap<String, Object> map = Maps.newLinkedHashMap();\n\t\tmap.put(\"name\", \"A\");\n\t\tmap.put(\"age\", 2);\n\t\tString mapString = JsonMapper.INSTANCE.toJson(map);\n\t\tSystem.out.println(\"Map:\" + mapString);\n\t\tassertThat(mapString).isEqualTo(\"{\\\"name\\\":\\\"A\\\",\\\"age\\\":2}\");\n\n\t\t// List<String>\n\t\tList<String> stringList = Lists.newArrayList(\"A\", \"B\", \"C\");\n\t\tString listString = JsonMapper.INSTANCE.toJson(stringList);\n\t\tSystem.out.println(\"String List:\" + listString);\n\t\tassertThat(listString).isEqualTo(\"[\\\"A\\\",\\\"B\\\",\\\"C\\\"]\");\n\n\t\t// List<Bean>\n\t\tList<TestBean> beanList = Lists.newArrayList(new TestBean(\"A\"), new TestBean(\"B\"));\n\t\tString beanListString = JsonMapper.INSTANCE.toJson(beanList);\n\t\tSystem.out.println(\"Bean List:\" + beanListString);\n\t\tassertJSONEqual(beanListString,\n\t\t\t\t\"[{\\\"name\\\":\\\"A\\\",\\\"defaultValue\\\":\\\"hello\\\",\\\"nullValue\\\":null,\\\"emptyValue\\\":[]},{\\\"name\\\":\\\"B\\\",\\\"defaultValue\\\":\\\"hello\\\",\\\"nullValue\\\":null,\\\"emptyValue\\\":[]}]\");\n\n\t\t// Bean[]\n\t\tTestBean[] beanArray = new TestBean[] { new TestBean(\"A\"), new TestBean(\"B\") };\n\t\tString beanArrayString = JsonMapper.INSTANCE.toJson(beanArray);\n\t\tSystem.out.println(\"Array List:\" + beanArrayString);\n\t\tassertJSONEqual(beanArrayString,\n\t\t\t\t\"[{\\\"name\\\":\\\"A\\\",\\\"defaultValue\\\":\\\"hello\\\",\\\"nullValue\\\":null,\\\"emptyValue\\\":[]},{\\\"name\\\":\\\"B\\\",\\\"defaultValue\\\":\\\"hello\\\",\\\"nullValue\\\":null,\\\"emptyValue\\\":[]}]\");\n\t}\n\n\t/**\n\t * 从Json字符串反序列化对象/集合.\n\t */\n\t@Test\n\tpublic void fromJson() throws Exception {\n\t\t// Bean\n\t\tString beanString = \"{\\\"name\\\":\\\"A\\\"}\";\n\t\tTestBean bean = JsonMapper.INSTANCE.fromJson(beanString, TestBean.class);\n\t\tSystem.out.println(\"Bean:\" + bean);\n\n\t\t// Map\n\t\tString mapString = \"{\\\"name\\\":\\\"A\\\",\\\"age\\\":2}\";\n\t\tMap<String, Object> map = JsonMapper.INSTANCE.fromJson(mapString, HashMap.class);\n\t\tSystem.out.println(\"Map:\");\n\t\tfor (Entry<String, Object> entry : map.entrySet()) {\n\t\t\tSystem.out.println(entry.getKey() + \" \" + entry.getValue());\n\t\t}\n\n\t\t// List<String>\n\t\tString listString = \"[\\\"A\\\",\\\"B\\\",\\\"C\\\"]\";\n\t\tList<String> stringList = JsonMapper.INSTANCE.fromJson(listString, List.class);\n\t\tSystem.out.println(\"String List:\");\n\t\tfor (String element : stringList) {\n\t\t\tSystem.out.println(element);\n\t\t}\n\n\t\t// List<Bean>\n\t\tString beanListString = \"[{\\\"name\\\":\\\"A\\\"},{\\\"name\\\":\\\"B\\\"}]\";\n\t\tList<TestBean> beanList = JsonMapper.INSTANCE.fromJson(beanListString,\n\t\t\t\tJsonMapper.INSTANCE.buildCollectionType(List.class, TestBean.class));\n\t\tSystem.out.println(\"Bean List:\");\n\t\tfor (TestBean element : beanList) {\n\t\t\tSystem.out.println(element);\n\t\t}\n\t}\n\n\t/**\n\t * 测试传入空对象,空字符串,Empty的集合,\"null\"字符串的结果.\n\t */\n\t@Test\n\tpublic void nullAndEmpty() {\n\t\t// toJson测试 //\n\n\t\t// Null Bean\n\t\tTestBean nullBean = null;\n\t\tString nullBeanString = JsonMapper.INSTANCE.toJson(nullBean);\n\t\tassertThat(nullBeanString).isEqualTo(\"null\");\n\n\n\t\t// Empty List\n\t\tList<String> emptyList = Lists.newArrayList();\n\t\tString emptyListString = JsonMapper.INSTANCE.toJson(emptyList);\n\t\tassertThat(emptyListString).isEqualTo(\"[]\");\n\n\t\t// fromJson测试 //\n\n\t\t// Null String for Bean\n\t\tTestBean nullBeanResult = JsonMapper.INSTANCE.fromJson(null, TestBean.class);\n\t\tassertThat(nullBeanResult).isNull();\n\n\t\tnullBeanResult = JsonMapper.INSTANCE.fromJson(\"null\", TestBean.class);\n\t\tassertThat(nullBeanResult).isNull();\n\n\t\tnullBeanResult = JsonMapper.INSTANCE.fromJson(\"\", TestBean.class);\n\t\tassertThat(nullBeanResult).isNull();\n\n\t\tnullBeanResult = JsonMapper.INSTANCE.fromJson(\"{}\", TestBean.class);\n\t\tassertThat(nullBeanResult).isNotNull();\n\t\tassertThat(nullBeanResult.getDefaultValue()).isEqualTo(\"hello\");\n\n\n\t\t// Null/Empty String for List\n\t\tList nullListResult = JsonMapper.INSTANCE.fromJson(null, List.class);\n\t\tassertThat(nullListResult).isNull();\n\n\t\tnullListResult = JsonMapper.INSTANCE.fromJson(\"null\", List.class);\n\t\tassertThat(nullListResult).isNull();\n\n\t\tnullListResult = JsonMapper.INSTANCE.fromJson(\"[]\", List.class);\n\t\tassertThat(nullListResult).isEmpty();\n\t}\n\n\t/**\n\t * 测试三种不同的Mapper.\n\t */\n\t@Test\n\tpublic void threeTypeMappers() throws JSONException {\n\t\t// 打印全部属性\n\t\tJsonMapper normalBinder = JsonMapper.defaultMapper();\n\t\tTestBean bean = new TestBean(\"A\");\n\t\tassertJSONEqual(normalBinder.toJson(bean), \"{\\\"name\\\":\\\"A\\\",\\\"defaultValue\\\":\\\"hello\\\",\\\"nullValue\\\":null,\\\"emptyValue\\\":[]}\");\n\n\t\t// 不打印nullValue属性\n\t\tJsonMapper nonNullMapper = JsonMapper.nonNullMapper();\n\t\tassertJSONEqual(nonNullMapper.toJson(bean),\"{\\\"name\\\":\\\"A\\\",\\\"defaultValue\\\":\\\"hello\\\",\\\"emptyValue\\\":[]}\");\n\n\t\t// 不打印nullValue与empty的属性\n\t\tJsonMapper nonEmptyMapper = JsonMapper.nonEmptyMapper();\n\t\tassertJSONEqual(nonEmptyMapper.toJson(bean),\"{\\\"name\\\":\\\"A\\\",\\\"defaultValue\\\":\\\"hello\\\"}\");\n\n\t\tTestBean nonEmptyBean = nonEmptyMapper.fromJson(\"{\\\"name\\\":\\\"A\\\",\\\"defaultValue\\\":\\\"hello\\\"}\", TestBean.class);\n\t\tassertThat(nonEmptyBean.getEmptyValue()).isEmpty();\n\t}\n\n\t@Test\n\tpublic void jsonp() throws JSONException{\n\t\tTestBean bean = new TestBean(\"A\");\n\t\tString jsonp = JsonMapper.nonEmptyMapper().toJsonP(\"haha\", bean);\n\t\tString testJSON=\"haha({\\\"name\\\":\\\"A\\\",\\\"defaultValue\\\":\\\"hello\\\"})\";\n\t\tString expected = testJSON.substring(testJSON.indexOf(\"(\")+1,testJSON.indexOf(\")\"));\n\t\tString test = jsonp.substring(jsonp.indexOf(\"(\")+1, jsonp.indexOf(\")\"));\n\t\tassertThat(jsonp.replace(test,\"\")).isEqualTo(testJSON.replace(expected,\"\"));\n\t\tassertJSONEqual(expected, test);\n\t}\n\n\t@Test\n\tpublic void update() {\n\t\tTestBean bean = new TestBean(\"A\");\n\t\tbean.setDefaultValue(\"lalala\");\n\t\tJsonMapper.INSTANCE.update(\"{\\\"name\\\":\\\"B\\\"}\", bean);\n\t\tassertThat(bean.getName()).isEqualTo(\"B\");\n\t\tassertThat(bean.getDefaultValue()).isEqualTo(\"lalala\");\n\t}\n\n\tpublic static class TestBean {\n\n\t\tprivate String name;\n\t\tprivate String defaultValue = \"hello\";\n\t\tprivate String nullValue = null;\n\t\tprivate List<String> emptyValue = new ArrayList();\n\n\t\tpublic TestBean() {\n\t\t}\n\n\t\tpublic TestBean(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\t\tpublic String getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\tpublic void setName(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\t\tpublic String getDefaultValue() {\n\t\t\treturn defaultValue;\n\t\t}\n\n\t\tpublic void setDefaultValue(String defaultValue) {\n\t\t\tthis.defaultValue = defaultValue;\n\t\t}\n\n\t\tpublic String getNullValue() {\n\t\t\treturn nullValue;\n\t\t}\n\n\t\tpublic void setNullValue(String nullValue) {\n\t\t\tthis.nullValue = nullValue;\n\t\t}\n\n\t\tpublic List<String> getEmptyValue() {\n\t\t\treturn emptyValue;\n\t\t}\n\n\t\tpublic void setEmptyValue(List<String> emptyValue) {\n\t\t\tthis.emptyValue = emptyValue;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"TestBean [name=\" + name + \", defaultValue=\" + defaultValue + \", nullValue=\" + nullValue\n\t\t\t\t\t+ \", emptyValue=\" + emptyValue + \"]\";\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/mapper/XmlMapperTest.java",
    "content": "package com.vip.vjtools.vjkit.mapper;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.List;\n\nimport javax.xml.bind.annotation.XmlAttribute;\nimport javax.xml.bind.annotation.XmlElement;\nimport javax.xml.bind.annotation.XmlElementWrapper;\nimport javax.xml.bind.annotation.XmlRootElement;\nimport javax.xml.bind.annotation.XmlTransient;\nimport javax.xml.bind.annotation.XmlType;\n\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport org.dom4j.Document;\nimport org.dom4j.DocumentException;\nimport org.dom4j.DocumentHelper;\nimport org.dom4j.Element;\nimport org.junit.Test;\n\nimport com.google.common.collect.Lists;\n\n/**\n * 演示基于JAXB2.0的Java对象-XML转换及Dom4j的使用.\n * \n * 演示用xml如下:\n * \n * <pre>\n * <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n * <user id=\"1\">\n * \t<name>calvin</name>\n * \t<interests>\n * \t\t<interest>movie</interest>\n * \t\t<interest>sports</interest>\n * \t</interests>\n * </user>\n * </pre>\n */\npublic class XmlMapperTest {\n\n\t@Test\n\tpublic void objectToXml() {\n\t\tUser user = new User();\n\t\tuser.setId(1L);\n\t\tuser.setName(\"calvin\");\n\n\t\tuser.getInterests().add(\"movie\");\n\t\tuser.getInterests().add(\"sports\");\n\n\t\tString xml = XmlMapper.toXml(user, \"UTF-8\");\n\t\tSystem.out.println(\"Jaxb Object to Xml result:\\n\" + xml);\n\t\tassertXmlByDom4j(xml);\n\t}\n\n\t@Test\n\tpublic void xmlToObject() {\n\t\tString xml = generateXmlByDom4j();\n\t\tUser user = XmlMapper.fromXml(xml, User.class);\n\n\t\tSystem.out.println(\"Jaxb Xml to Object result:\\n\" + user);\n\n\t\tassertThat(user.getId()).isEqualTo(1L);\n\t\tassertThat(user.getInterests()).containsOnly(\"movie\", \"sports\");\n\t}\n\n\t/**\n\t * 测试以List对象作为根节点时的XML输出\n\t */\n\t@Test\n\tpublic void toXmlWithListAsRoot() {\n\t\tUser user1 = new User();\n\t\tuser1.setId(1L);\n\t\tuser1.setName(\"calvin\");\n\n\t\tUser user2 = new User();\n\t\tuser2.setId(2L);\n\t\tuser2.setName(\"kate\");\n\n\t\tList<User> userList = Lists.newArrayList(user1, user2);\n\n\t\tString xml = XmlMapper.toXml(userList, \"userList\", User.class, \"UTF-8\");\n\t\tSystem.out.println(\"Jaxb Object List to Xml result:\\n\" + xml);\n\t}\n\n\t/**\n\t * 使用Dom4j生成测试用的XML文档字符串.\n\t */\n\tprivate static String generateXmlByDom4j() {\n\t\tDocument document = DocumentHelper.createDocument();\n\n\t\tElement root = document.addElement(\"user\").addAttribute(\"id\", \"1\");\n\n\t\troot.addElement(\"name\").setText(\"calvin\");\n\n\t\t// List<String>\n\t\tElement interests = root.addElement(\"interests\");\n\t\tinterests.addElement(\"interest\").addText(\"movie\");\n\t\tinterests.addElement(\"interest\").addText(\"sports\");\n\n\t\treturn document.asXML();\n\t}\n\n\t/**\n\t * 使用Dom4j验证Jaxb所生成XML的正确性.\n\t */\n\tprivate static void assertXmlByDom4j(String xml) {\n\t\tDocument doc = null;\n\t\ttry {\n\t\t\tdoc = DocumentHelper.parseText(xml);\n\t\t} catch (DocumentException e) {\n\t\t\tfail(e.getMessage());\n\t\t}\n\t\tElement user = doc.getRootElement();\n\t\tassertThat(user.attribute(\"id\").getValue()).isEqualTo(\"1\");\n\n\t\tElement interests = (Element) doc.selectSingleNode(\"//interests\");\n\t\tassertThat(interests.elements()).hasSize(2);\n\t\tassertThat(((Element) interests.elements().get(0)).getText()).isEqualTo(\"movie\");\n\t}\n\n\t@XmlRootElement\n\t// 指定子节点的顺序\n\t@XmlType(propOrder = { \"name\", \"interests\" })\n\tprivate static class User {\n\n\t\tprivate Long id;\n\t\tprivate String name;\n\t\tprivate String password;\n\n\t\tprivate List<String> interests = Lists.newArrayList();\n\n\t\t// 设置转换为xml节点中的属性\n\t\t@XmlAttribute\n\t\tpublic Long getId() {\n\t\t\treturn id;\n\t\t}\n\n\t\tpublic void setId(Long id) {\n\t\t\tthis.id = id;\n\t\t}\n\n\t\tpublic String getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\tpublic void setName(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\t\t// 设置不转换为xml\n\t\t@XmlTransient\n\t\tpublic String getPassword() {\n\t\t\treturn password;\n\t\t}\n\n\t\tpublic void setPassword(String password) {\n\t\t\tthis.password = password;\n\t\t}\n\n\t\t// 设置对List<String>的映射, xml为<interests><interest>movie</interest></interests>\n\t\t@XmlElementWrapper(name = \"interests\")\n\t\t@XmlElement(name = \"interest\")\n\t\tpublic List<String> getInterests() {\n\t\t\treturn interests;\n\t\t}\n\n\t\tpublic void setInterests(List<String> interests) {\n\t\t\tthis.interests = interests;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn ToStringBuilder.reflectionToString(this);\n\t\t}\n\t}\n}"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/net/IPUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.net;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class IPUtilTest {\n\n\t@Test\n\tpublic void stringAndInt() {\n\n\t\tassertThat(IPUtil.ipv4StringToInt(\"192.168.0.1\")).isEqualTo(-1062731775);\n\t\tassertThat(IPUtil.ipv4StringToInt(\"192.168.0.2\")).isEqualTo(-1062731774);\n\n\t\tassertThat(IPUtil.intToIpv4String(-1062731775)).isEqualTo(\"192.168.0.1\");\n\t\tassertThat(IPUtil.intToIpv4String(-1062731774)).isEqualTo(\"192.168.0.2\");\n\t}\n\n\t@Test\n\tpublic void inetAddress() {\n\n\t\tassertThat(IPUtil.fromInt(-1062731775).getHostAddress()).isEqualTo(\"192.168.0.1\");\n\t\tassertThat(IPUtil.fromInt(-1062731774).getHostAddress()).isEqualTo(\"192.168.0.2\");\n\n\t\tassertThat(IPUtil.fromIpString(\"192.168.0.1\").getHostAddress()).isEqualTo(\"192.168.0.1\");\n\t\tassertThat(IPUtil.fromIpString(\"192.168.0.2\").getHostAddress()).isEqualTo(\"192.168.0.2\");\n\t\tassertThat(IPUtil.fromIpv4String(\"192.168.0.1\").getHostAddress()).isEqualTo(\"192.168.0.1\");\n\t\tassertThat(IPUtil.fromIpv4String(\"192.168.0.2\").getHostAddress()).isEqualTo(\"192.168.0.2\");\n\n\t\tassertThat(IPUtil.toInt(IPUtil.fromIpString(\"192.168.0.1\"))).isEqualTo(-1062731775);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/net/NetUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.net;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.ServerSocket;\nimport java.net.UnknownHostException;\n\nimport javax.net.ServerSocketFactory;\n\nimport org.junit.Test;\nimport org.mockito.internal.util.io.IOUtil;\n\npublic class NetUtilTest {\n\n\t@Test\n\tpublic void localhost() {\n\t\tassertThat(NetUtil.getLocalHost()).isNotEqualTo(\"127.0.0.1\");\n\t\tassertThat(NetUtil.getLocalAddress().getHostAddress()).isNotEqualTo(\"127.0.0.1\");\n\t}\n\n\t@Test\n\tpublic void portDetect() throws UnknownHostException, IOException {\n\t\tint port = NetUtil.findRandomAvailablePort(20000, 20100);\n\t\tassertThat(port).isBetween(20000, 20100);\n\t\tSystem.out.println(\"random port:\" + port);\n\n\t\tassertThat(NetUtil.isPortAvailable(port)).isTrue();\n\n\t\tint port2 = NetUtil.findAvailablePortFrom(port);\n\t\tassertThat(port2).isEqualTo(port);\n\n\t\tint port3 = NetUtil.findRandomAvailablePort();\n\n\t\tassertThat(port3).isBetween(NetUtil.PORT_RANGE_MIN, NetUtil.PORT_RANGE_MAX);\n\t\tSystem.out.println(\"random port:\" + port3);\n\n\t\t// 尝试占住一个端口\n\t\tServerSocket serverSocket = null;\n\t\ttry {\n\t\t\tserverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1,\n\t\t\t\t\tInetAddress.getByName(\"localhost\"));\n\n\t\t\tassertThat(NetUtil.isPortAvailable(port)).isFalse();\n\n\t\t\tint port4 = NetUtil.findAvailablePortFrom(port);\n\t\t\tassertThat(port4).isEqualTo(port + 1);\n\n\t\t\ttry {\n\t\t\t\tint port5 = NetUtil.findRandomAvailablePort(port, port);\n\t\t\t\tfail(\"should fail before\");\n\t\t\t} catch (Throwable t) {\n\t\t\t\tassertThat(t).isInstanceOf(IllegalStateException.class);\n\t\t\t}\n\n\t\t} finally {\n\t\t\tIOUtil.close(serverSocket);\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/number/MathUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.math.RoundingMode;\n\nimport org.junit.Test;\n\npublic class MathUtilTest {\n\n\t@Test\n\tpublic void power2() {\n\t\ttry {\n\t\t\tassertThat(MathUtil.nextPowerOfTwo(-5)).isEqualTo(8);\n\t\t\tfail(\"should fail here\");\n\t\t} catch (Exception e) {\n\t\t\tassertThat(e).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\t\tassertThat(MathUtil.nextPowerOfTwo(5)).isEqualTo(8);\n\t\tassertThat(MathUtil.nextPowerOfTwo(99)).isEqualTo(128);\n\n\t\tassertThat(MathUtil.previousPowerOfTwo(5)).isEqualTo(4);\n\t\tassertThat(MathUtil.previousPowerOfTwo(99)).isEqualTo(64);\n\n\t\tassertThat(MathUtil.isPowerOfTwo(32)).isTrue();\n\t\tassertThat(MathUtil.isPowerOfTwo(31)).isFalse();\n\n\t\tassertThat(MathUtil.nextPowerOfTwo(5L)).isEqualTo(8L);\n\t\tassertThat(MathUtil.nextPowerOfTwo(99L)).isEqualTo(128L);\n\n\t\tassertThat(MathUtil.previousPowerOfTwo(5L)).isEqualTo(4L);\n\t\tassertThat(MathUtil.previousPowerOfTwo(99L)).isEqualTo(64L);\n\n\t\tassertThat(MathUtil.isPowerOfTwo(32L)).isTrue();\n\t\tassertThat(MathUtil.isPowerOfTwo(31L)).isFalse();\n\t\tassertThat(MathUtil.isPowerOfTwo(-2)).isFalse();\n\n\t\tassertThat(MathUtil.modByPowerOfTwo(0, 16)).isEqualTo(0);\n\t\tassertThat(MathUtil.modByPowerOfTwo(1, 16)).isEqualTo(1);\n\t\tassertThat(MathUtil.modByPowerOfTwo(31, 16)).isEqualTo(15);\n\t\tassertThat(MathUtil.modByPowerOfTwo(32, 16)).isEqualTo(0);\n\t\tassertThat(MathUtil.modByPowerOfTwo(65, 16)).isEqualTo(1);\n\t\tassertThat(MathUtil.modByPowerOfTwo(-1, 16)).isEqualTo(15);\n\n\t}\n\n\t@Test\n\tpublic void caculate() {\n\t\tassertThat(MathUtil.mod(15, 10)).isEqualTo(5);\n\t\tassertThat(MathUtil.mod(-15, 10)).isEqualTo(5);\n\t\tassertThat(MathUtil.mod(-5, 3)).isEqualTo(1);\n\n\t\tassertThat(MathUtil.mod(15l, 10l)).isEqualTo(5);\n\t\tassertThat(MathUtil.mod(-15l, 10l)).isEqualTo(5);\n\t\tassertThat(MathUtil.mod(-5l, 3l)).isEqualTo(1);\n\n\t\tassertThat(MathUtil.mod(15l, 10)).isEqualTo(5);\n\t\tassertThat(MathUtil.mod(-15l, 10)).isEqualTo(5);\n\t\tassertThat(MathUtil.mod(-5l, 3)).isEqualTo(1);\n\n\t\tassertThat(MathUtil.pow(2, 3)).isEqualTo(8);\n\t\tassertThat(MathUtil.pow(2, 0)).isEqualTo(1);\n\n\t\tassertThat(MathUtil.pow(2l, 3)).isEqualTo(8);\n\t\tassertThat(MathUtil.pow(2l, 0)).isEqualTo(1);\n\n\t\tassertThat(MathUtil.sqrt(15, RoundingMode.HALF_UP)).isEqualTo(4);\n\t\tassertThat(MathUtil.sqrt(16, RoundingMode.HALF_UP)).isEqualTo(4);\n\t\tassertThat(MathUtil.sqrt(10l, RoundingMode.HALF_UP)).isEqualTo(3);\n\t}\n\n\t@Test\n\tpublic void divide() {\n\t\tassertThat(11 / 4).isEqualTo(2);\n\t\tassertThat(10L / 4).isEqualTo(2);\n\t\tassertThat(MathUtil.divide(10, 4, RoundingMode.HALF_UP)).isEqualTo(3);\n\t\tassertThat(MathUtil.divide(10L, 4L, RoundingMode.HALF_DOWN)).isEqualTo(2);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/number/MoneyUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.math.BigDecimal;\nimport java.text.ParseException;\n\nimport org.junit.Test;\n\npublic class MoneyUtilTest {\n\n\t@Test\n\tpublic void amountConvertTest() {\n\t\t// 金额分转换成元\n\t\tassertThat(MoneyUtil.fen2yuan(100).doubleValue()).isEqualTo(new BigDecimal(1.00d).doubleValue());\n\t\tassertThat(MoneyUtil.fen2yuan(\"100\").doubleValue()).isEqualTo(new BigDecimal(1.00d).doubleValue());\n\t\tassertThat(MoneyUtil.fen2yuan(BigDecimal.valueOf(100d)).doubleValue())\n\t\t\t\t.isEqualTo(new BigDecimal(1.00d).doubleValue());\n\n\t\t// 金额元转换成分\n\t\tassertThat(MoneyUtil.yuan2fen(BigDecimal.valueOf(1d)).doubleValue())\n\t\t\t\t.isEqualTo(new BigDecimal(100d).doubleValue());\n\t\tassertThat(MoneyUtil.yuan2fen(1L).doubleValue()).isEqualTo(new BigDecimal(100d).doubleValue());\n\t}\n\n\t@Test\n\tpublic void format() {\n\t\tassertThat(MoneyUtil.format(1111.111)).isEqualTo(\"1111.11\");\n\t\tassertThat(MoneyUtil.prettyFormat(1111.111)).isEqualTo(\"1,111.11\");\n\t\tassertThat(MoneyUtil.format(1111.111, \"0.0\")).isEqualTo(\"1111.1\");\n\t}\n\n\t@Test\n\tpublic void parse() throws ParseException {\n\t\tassertThat(MoneyUtil.parseString(\"1111.11\")).isEqualTo(new BigDecimal(1111.11));\n\t\tassertThat(MoneyUtil.parsePrettyString(\"1,111.11\")).isEqualTo(new BigDecimal(1111.11));\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/number/NumberUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class NumberUtilTest {\n\n\t@Test\n\tpublic void equalsWithin() {\n\t\tfloat f = 0.15f;\n\t\tfloat f2 = 0.45f / 3;\n\t\tfloat f3 = 0.46f / 3;\n\t\tassertThat(NumberUtil.equalsWithin(f, f2)).isTrue();\n\t\tassertThat(NumberUtil.equalsWithin(f, f3)).isFalse();\n\t\tassertThat(NumberUtil.equalsWithin(f, f2, 0.0000001)).isTrue();\n\t}\n\n\t@Test\n\tpublic void toBytes() {\n\t\tbyte[] bytes = NumberUtil.toBytes(1);\n\t\tassertThat(bytes).hasSize(4).containsSequence((byte) 0, (byte) 0, (byte) 0, (byte) 1);\n\n\t\tbytes = NumberUtil.toBytes(257);\n\t\tassertThat(bytes).containsSequence((byte) 0, (byte) 0, (byte) 1, (byte) 1);\n\t\tassertThat(NumberUtil.toInt(bytes)).isEqualTo(257);\n\n\t\t// long\n\t\tbyte[] bytes2 = NumberUtil.toBytes(1L);\n\t\tassertThat(bytes2).hasSize(8);\n\n\t\tbytes2 = NumberUtil.toBytes(257L);\n\t\tassertThat(bytes2).containsSequence((byte) 0, (byte) 0, (byte) 1, (byte) 1);\n\t\tassertThat(NumberUtil.toLong(bytes2)).isEqualTo(257L);\n\n\t\t// dobule\n\t\tbyte[] bytes3 = NumberUtil.toBytes(1.123d);\n\t\tassertThat(NumberUtil.toDouble(bytes3)).isEqualTo(1.123d);\n\n\t\t// toInt32\n\t\tassertThat(NumberUtil.toInt32(123l)).isEqualTo(123);\n\n\t\ttry {\n\t\t\tNumberUtil.toInt32(Long.valueOf(Integer.MAX_VALUE + 1l));\n\t\t\tfail(\"should fail here\");\n\t\t} catch (Exception e) {\n\t\t\tassertThat(e).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void isNumber() {\n\t\tassertThat(NumberUtil.isNumber(\"123\")).isTrue();\n\t\tassertThat(NumberUtil.isNumber(\"-123.1\")).isTrue();\n\t\tassertThat(NumberUtil.isNumber(\"-1a3.1\")).isFalse();\n\n\t\tassertThat(NumberUtil.isHexNumber(\"0x12F\")).isTrue();\n\t\tassertThat(NumberUtil.isHexNumber(\"-0x12A3\")).isTrue();\n\t\tassertThat(NumberUtil.isHexNumber(\"12A3\")).isFalse();\n\t}\n\n\t@Test\n\tpublic void toNumber() {\n\t\tassertThat(NumberUtil.toInt(\"122\")).isEqualTo(122);\n\t\ttry {\n\t\t\tNumberUtil.toInt(\"12A\");\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\t\ttry {\n\t\t\tNumberUtil.toInt((String) null);\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\t\tassertThat(NumberUtil.toInt(\"12A\", 123)).isEqualTo(123);\n\n\t\tassertThat(NumberUtil.toLong(\"122\")).isEqualTo(122L);\n\t\ttry {\n\t\t\tNumberUtil.toLong(\"12A\");\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (\n\n\t\tNumberFormatException e) {\n\n\t\t}\n\t\ttry {\n\t\t\tNumberUtil.toLong((String) null);\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\t\tassertThat(NumberUtil.toLong(\"12A\", 123)).isEqualTo(123L);\n\n\t\tassertThat(NumberUtil.toDouble(\"122.1\")).isEqualTo(122.1);\n\n\t\ttry {\n\t\t\tNumberUtil.toDouble(\"12A\");\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\n\t\ttry {\n\t\t\tNumberUtil.toDouble((String) null);\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\t\tassertThat(NumberUtil.toDouble(\"12A\", 123.1)).isEqualTo(123.1);\n\n\t\tassertThat(NumberUtil.toIntObject(\"122\")).isEqualTo(122);\n\t\ttry {\n\t\t\tNumberUtil.toIntObject(\"12A\");\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\n\t\tassertThat(NumberUtil.toIntObject(\"12A\", 123)).isEqualTo(123);\n\t\tassertThat(NumberUtil.toIntObject(null, 123)).isEqualTo(123);\n\t\tassertThat(NumberUtil.toIntObject(\"\", 123)).isEqualTo(123);\n\n\t\tassertThat(NumberUtil.toLongObject(\"122\")).isEqualTo(122L);\n\t\ttry {\n\t\t\tNumberUtil.toLongObject(\"12A\");\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\t\tassertThat(NumberUtil.toLongObject(\"12A\", 123L)).isEqualTo(123L);\n\t\tassertThat(NumberUtil.toLongObject(null, 123L)).isEqualTo(123L);\n\n\t\tassertThat(NumberUtil.toDoubleObject(\"122.1\")).isEqualTo(122.1);\n\t\ttry {\n\t\t\tNumberUtil.toDoubleObject(\"12A\");\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\t\tassertThat(NumberUtil.toDoubleObject(\"12A\", 123.1)).isEqualTo(123.1);\n\n\t\tassertThat(NumberUtil.hexToIntObject(\"0x10\")).isEqualTo(16);\n\t\tassertThat(NumberUtil.hexToIntObject(\"0X100\")).isEqualTo(256);\n\t\tassertThat(NumberUtil.hexToIntObject(\"-0x100\")).isEqualTo(-256);\n\t\ttry {\n\t\t\tNumberUtil.hexToIntObject(\"0xHI\");\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\n\t\ttry {\n\t\t\tNumberUtil.hexToIntObject(null);\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\t\tassertThat(NumberUtil.hexToIntObject(\"0xHI\", 123)).isEqualTo(123);\n\n\t\tassertThat(NumberUtil.hexToLongObject(\"0x10\")).isEqualTo(16L);\n\t\tassertThat(NumberUtil.hexToLongObject(\"0X100\")).isEqualTo(256L);\n\t\tassertThat(NumberUtil.hexToLongObject(\"-0x100\")).isEqualTo(-256L);\n\t\ttry {\n\t\t\tNumberUtil.hexToLongObject(\"0xHI\");\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\t\ttry {\n\t\t\tNumberUtil.hexToLongObject(null);\n\t\t\tfail(\"shoud fail here\");\n\t\t} catch (NumberFormatException e) {\n\n\t\t}\n\t\tassertThat(NumberUtil.hexToLongObject(\"0xHI\", 123L)).isEqualTo(123L);\n\n\t}\n\n\t@Test\n\tpublic void toStringTest() {\n\t\tassertThat(NumberUtil.toString(23)).isEqualTo(\"23\");\n\t\tassertThat(NumberUtil.toString(new Integer(23))).isEqualTo(\"23\");\n\t\tassertThat(NumberUtil.toString(23l)).isEqualTo(\"23\");\n\t\tassertThat(NumberUtil.toString(new Long(23))).isEqualTo(\"23\");\n\t\tassertThat(NumberUtil.toString(23l)).isEqualTo(\"23\");\n\n\t\tassertThat(NumberUtil.toString(new Double(23.112d))).isEqualTo(\"23.112\");\n\t\tassertThat(NumberUtil.toString(23.112d)).isEqualTo(\"23.112\");\n\t\tassertThat(NumberUtil.to2DigitString(23.112d)).isEqualTo(\"23.11\");\n\t\tassertThat(NumberUtil.to2DigitString(23.116d)).isEqualTo(\"23.12\");\n\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/number/RandomUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.Test;\n\npublic class RandomUtilTest {\n\n\t@Test\n\tpublic void getRandom() {\n\t\tSystem.out.println(RandomUtil.secureRandom().nextInt());\n\t\tSystem.out.println(RandomUtil.threadLocalRandom().nextInt());\n\t}\n\n\t@Test\n\tpublic void randomNumber() {\n\n\t\tint i = RandomUtil.nextInt();\n\t\tassertThat(i).isBetween(0, Integer.MAX_VALUE);\n\t\ti = RandomUtil.nextInt(RandomUtil.threadLocalRandom());\n\t\tassertThat(i).isBetween(0, Integer.MAX_VALUE);\n\n\t\ti = RandomUtil.nextInt(10);\n\t\tassertThat(i).isBetween(0, 10);\n\t\ti = RandomUtil.nextInt(RandomUtil.threadLocalRandom(), 10);\n\t\tassertThat(i).isBetween(0, 10);\n\n\t\ti = RandomUtil.nextInt(10, 20);\n\t\tassertThat(i).isBetween(10, 20);\n\t\ti = RandomUtil.nextInt(RandomUtil.threadLocalRandom(), 10, 20);\n\t\tassertThat(i).isBetween(10, 20);\n\n\t\tlong l = RandomUtil.nextLong();\n\t\tassertThat(l).isBetween(0L, Long.MAX_VALUE);\n\t\tl = RandomUtil.nextLong(RandomUtil.threadLocalRandom());\n\t\tassertThat(l).isBetween(0L, Long.MAX_VALUE);\n\n\t\tl = RandomUtil.nextLong(10);\n\t\tassertThat(l).isBetween(0L, 10L);\n\t\tl = RandomUtil.nextLong(RandomUtil.threadLocalRandom(), 10L);\n\t\tassertThat(l).isBetween(0L, 10L);\n\n\t\tl = RandomUtil.nextLong(10L, 20L);\n\t\tassertThat(l).isBetween(10L, 20L);\n\t\tl = RandomUtil.nextLong(RandomUtil.threadLocalRandom(), 10, 20);\n\t\tassertThat(l).isBetween(10L, 20L);\n\n\t\tdouble d = RandomUtil.nextDouble();\n\t\tassertThat(d).isBetween(0d, Double.MAX_VALUE);\n\t\td = RandomUtil.nextDouble(RandomUtil.threadLocalRandom());\n\t\tassertThat(d).isBetween(0d, Double.MAX_VALUE);\n\n\t\td = RandomUtil.nextDouble(10);\n\t\tassertThat(d).isBetween(0d, 10d);\n\t\td = RandomUtil.nextDouble(RandomUtil.threadLocalRandom(), 10L);\n\t\tassertThat(d).isBetween(0d, 10d);\n\n\t\td = RandomUtil.nextDouble(10L, 20L);\n\t\tassertThat(d).isBetween(10d, 20d);\n\t\td = RandomUtil.nextDouble(RandomUtil.threadLocalRandom(), 10, 20);\n\t\tassertThat(d).isBetween(10d, 20d);\n\n\t}\n\n\t@Test\n\tpublic void generateString() {\n\t\tSystem.out.println(RandomUtil.randomStringFixLength(5));\n\t\tSystem.out.println(RandomUtil.randomStringRandomLength(5, 10));\n\n\t\tSystem.out.println(RandomUtil.randomStringFixLength(RandomUtil.threadLocalRandom(), 5));\n\t\tSystem.out.println(RandomUtil.randomStringRandomLength(RandomUtil.threadLocalRandom(), 5, 10));\n\n\t\tassertThat(RandomUtil.randomStringFixLength(5)).hasSize(5);\n\t\tassertThat(RandomUtil.randomStringFixLength(RandomUtil.threadLocalRandom(), 5)).hasSize(5);\n\n\t\tSystem.out.println(RandomUtil.randomLetterFixLength(5));\n\t\tSystem.out.println(RandomUtil.randomLetterRandomLength(5, 10));\n\n\t\tSystem.out.println(RandomUtil.randomLetterFixLength(RandomUtil.threadLocalRandom(), 5));\n\t\tSystem.out.println(RandomUtil.randomLetterRandomLength(RandomUtil.threadLocalRandom(), 5, 10));\n\n\t\tassertThat(RandomUtil.randomLetterFixLength(5)).hasSize(5);\n\t\tassertThat(RandomUtil.randomLetterFixLength(RandomUtil.threadLocalRandom(), 5)).hasSize(5);\n\n\t\tSystem.out.println(RandomUtil.randomAsciiFixLength(5));\n\t\tSystem.out.println(RandomUtil.randomAsciiRandomLength(5, 10));\n\n\t\tSystem.out.println(RandomUtil.randomAsciiFixLength(RandomUtil.threadLocalRandom(), 5));\n\t\tSystem.out.println(RandomUtil.randomAsciiRandomLength(RandomUtil.threadLocalRandom(), 5, 10));\n\n\t\tassertThat(RandomUtil.randomAsciiFixLength(5)).hasSize(5);\n\t\tassertThat(RandomUtil.randomAsciiFixLength(RandomUtil.threadLocalRandom(), 5)).hasSize(5);\n\t}\n\n\tpublic void test0() {\n\t\tdouble a = 0.0;\n\t\tdouble b = 1.0;\n\t\tint x = 0x7fffffff;\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/number/UnitConverterTest.java",
    "content": "package com.vip.vjtools.vjkit.number;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class UnitConverterTest {\n\n\t@Test\n\tpublic void convertDurationMillis() {\n\t\tassertThat(UnitConverter.toDurationMillis(\"12345\")).isEqualTo(12345);\n\t\tassertThat(UnitConverter.toDurationMillis(\"12S\")).isEqualTo(12000);\n\t\tassertThat(UnitConverter.toDurationMillis(\"12s\")).isEqualTo(12000);\n\t\tassertThat(UnitConverter.toDurationMillis(\"12ms\")).isEqualTo(12);\n\t\tassertThat(UnitConverter.toDurationMillis(\"12m\")).isEqualTo(12 * 60 * 1000);\n\t\tassertThat(UnitConverter.toDurationMillis(\"12h\")).isEqualTo(12l * 60 * 60 * 1000);\n\t\tassertThat(UnitConverter.toDurationMillis(\"12d\")).isEqualTo(12l * 24 * 60 * 60 * 1000);\n\n\t\ttry {\n\t\t\tassertThat(UnitConverter.toDurationMillis(\"12a\")).isEqualTo(12 * 60 * 1000);\n\t\t\tfail(\"should fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tassertThat(UnitConverter.toDurationMillis(\"a12\")).isEqualTo(12 * 60 * 1000);\n\t\t\tfail(\"should fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void convertSizeBytes() {\n\t\tassertThat(UnitConverter.toBytes(\"12345\")).isEqualTo(12345);\n\t\tassertThat(UnitConverter.toBytes(\"12b\")).isEqualTo(12);\n\t\tassertThat(UnitConverter.toBytes(\"12k\")).isEqualTo(12 * 1024);\n\t\tassertThat(UnitConverter.toBytes(\"12M\")).isEqualTo(12 * 1024 * 1024);\n\n\t\tassertThat(UnitConverter.toBytes(\"12G\")).isEqualTo(12l * 1024 * 1024 * 1024);\n\t\tassertThat(UnitConverter.toBytes(\"12T\")).isEqualTo(12l * 1024 * 1024 * 1024 * 1024);\n\n\t\ttry {\n\t\t\tUnitConverter.toBytes(\"12x\");\n\t\t\tfail(\"should fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t\ttry {\n\t\t\tUnitConverter.toBytes(\"a12\");\n\t\t\tfail(\"should fail\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void convertToSizeUnit() {\n\t\tassertThat(UnitConverter.toSizeUnit(966L, 0)).isEqualTo(\" 966\");\n\n\t\tassertThat(UnitConverter.toSizeUnit(1522L, 0)).isEqualTo(\"   1k\");\n\t\tassertThat(UnitConverter.toSizeUnit(1522L, 1)).isEqualTo(\"   1.5k\");\n\n\t\tassertThat(UnitConverter.toSizeUnit(1024L * 1024 * 2 + 1024 * 200, 0)).isEqualTo(\"   2m\");\n\t\tassertThat(UnitConverter.toSizeUnit(1024L * 1024 * 2 + 1024 * 600, 0)).isEqualTo(\"   3m\");\n\n\t\tassertThat(UnitConverter.toSizeUnit(1024L * 1024 * 2 + 1024 * 140, 1)).isEqualTo(\"   2.1m\");\n\t\tassertThat(UnitConverter.toSizeUnit(1024L * 1024 * 2 + 1024 * 160, 1)).isEqualTo(\"   2.2m\");\n\n\t\tassertThat(UnitConverter.toSizeUnit(1024L * 1024 * 1024 * 2 + 1024 * 1024 * 200, 0)).isEqualTo(\"   2g\");\n\t\tassertThat(UnitConverter.toSizeUnit(1024L * 1024 * 1024 * 2 + 1024 * 1024 * 200, 1)).isEqualTo(\"   2.2g\");\n\n\t\tassertThat(UnitConverter.toSizeUnit(1024L * 1024 * 1024 * 1024 * 2 + 1024L * 1024 * 1024 * 200, 0))\n\t\t\t\t.isEqualTo(\"   2t\");\n\t\tassertThat(UnitConverter.toSizeUnit(1024L * 1024 * 1024 * 1024 * 2 + 1024L * 1024 * 1024 * 200, 1))\n\t\t\t\t.isEqualTo(\"   2.2t\");\n\n\t}\n\n\t@Test\n\tpublic void convertToTimeUnit() {\n\t\tassertThat(UnitConverter.toTimeUnit(1322L, 0)).isEqualTo(\" 1s\");\n\t\tassertThat(UnitConverter.toTimeUnit(1322L, 1)).isEqualTo(\" 1.3s\");\n\n\t\tassertThat(UnitConverter.toTimeUnit(1000L * 62, 0)).isEqualTo(\" 1m\");\n\t\tassertThat(UnitConverter.toTimeUnit(1000L * 90, 0)).isEqualTo(\" 2m\");\n\n\t\tassertThat(UnitConverter.toTimeUnit(1000L * 90, 1)).isEqualTo(\" 1.5m\");\n\n\t\tassertThat(UnitConverter.toTimeUnit(1000L * 60 * 70, 1)).isEqualTo(\" 1.2h\");\n\n\t\tassertThat(UnitConverter.toTimeUnit(1000L * 60 * 60 * 28, 1)).isEqualTo(\" 1.2d\");\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/reflect/ClassUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.reflect;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.Assert.*;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\nimport org.junit.Test;\n\npublic class ClassUtilTest {\n\n\t@Test\n\tpublic void getMessage() {\n\t\tassertThat(ClassUtil.getShortClassName(ClassUtilTest.class)).isEqualTo(\"ClassUtilTest\");\n\t\tassertThat(ClassUtil.getShortClassName(BClass.class)).isEqualTo(\"ClassUtilTest.BClass\");\n\n\t\tassertThat(ClassUtil.getShortClassName(ClassUtilTest.class.getName())).isEqualTo(\"ClassUtilTest\");\n\t\tassertThat(ClassUtil.getShortClassName(BClass.class.getName())).isEqualTo(\"ClassUtilTest.BClass\");\n\n\t\tassertThat(ClassUtil.getPackageName(ClassUtilTest.class)).isEqualTo(\"com.vip.vjtools.vjkit.reflect\");\n\t\tassertThat(ClassUtil.getPackageName(BClass.class)).isEqualTo(\"com.vip.vjtools.vjkit.reflect\");\n\t\tassertThat(ClassUtil.getPackageName(ClassUtilTest.class.getName())).isEqualTo(\"com.vip.vjtools.vjkit.reflect\");\n\t\tassertThat(ClassUtil.getPackageName(BClass.class.getName())).isEqualTo(\"com.vip.vjtools.vjkit.reflect\");\n\n\t}\n\n\t@Test\n\tpublic void getAllClass() {\n\n\t\tassertThat(ClassUtil.getAllInterfaces(BClass.class)).hasSize(4).contains(AInterface.class, BInterface.class,\n\t\t\t\tCInterface.class, DInterface.class);\n\n\t\tassertThat(ClassUtil.getAllSuperclasses(BClass.class)).hasSize(2).contains(AClass.class, Object.class);\n\n\t\tassertThat(AnnotationUtil.getAllAnnotations(BClass.class)).hasSize(4);\n\n\t\tassertThat(AnnotationUtil.getAnnotatedPublicFields(BClass.class, AAnnotation.class)).hasSize(2).contains(\n\t\t\t\tReflectionUtil.getField(BClass.class, \"sfield\"), ReflectionUtil.getField(BClass.class, \"tfield\"));\n\n\t\tassertThat(AnnotationUtil.getAnnotatedFields(BClass.class, EAnnotation.class)).hasSize(3).contains(\n\t\t\t\tReflectionUtil.getField(BClass.class, \"bfield\"), ReflectionUtil.getField(BClass.class, \"efield\"),\n\t\t\t\tReflectionUtil.getField(AClass.class, \"afield\"));\n\n\t\tassertThat(AnnotationUtil.getAnnotatedFields(BClass.class, FAnnotation.class)).hasSize(1)\n\t\t\t\t.contains(ReflectionUtil.getField(AClass.class, \"dfield\"));\n\n\t\tassertThat(AnnotationUtil.getAnnotatedPublicMethods(BClass.class, FAnnotation.class)).hasSize(3).contains(\n\t\t\t\tReflectionUtil.getAccessibleMethodByName(BClass.class, \"hello\"),\n\t\t\t\tReflectionUtil.getAccessibleMethodByName(BClass.class, \"hello3\"),\n\t\t\t\tReflectionUtil.getAccessibleMethodByName(AClass.class, \"hello4\"));\n\t}\n\n\t@Test\n\tpublic void getSuperClassGenericType() {\n\t\t// 获取第1，2个泛型类型\n\t\tassertThat(ClassUtil.getClassGenericType(TestBean.class)).isEqualTo(String.class);\n\t\tassertThat(ClassUtil.getClassGenericType(TestBean.class, 1)).isEqualTo(Long.class);\n\n\t\t// 定义父类时无泛型定义\n\t\tassertThat(ClassUtil.getClassGenericType(TestBean2.class)).isEqualTo(Object.class);\n\n\t\t// 无父类定义\n\t\tassertThat(ClassUtil.getClassGenericType(TestBean3.class)).isEqualTo(Object.class);\n\t}\n\n\tpublic void classPresent() {\n\t\tassertThat(ClassLoaderUtil.isPresent(\"a.b.c\", ClassLoaderUtil.getDefaultClassLoader())).isFalse();\n\t\tassertThat(ClassLoaderUtil.isPresent(\"com.vip.vjtools.vjkit.reflect.ClassUtil\",\n\t\t\t\tClassLoaderUtil.getDefaultClassLoader())).isTrue();\n\t}\n\n\t/**\n\t * Unit test case of {@link com.vip.vjtools.vjkit.reflect.ClassUtil#isSubClassOrInterfaceOf(Class, Class)}\n\t */\n\t@Test\n\tpublic void testIsSubClassOrInterfaceOf() {\n\t\tassertTrue(\"TestBean should be subclass of ParentBean\",\n\t\t\t\tClassUtil.isSubClassOrInterfaceOf(BClass.class, AClass.class));\n\t\tassertTrue(\"BInterface should be subinterface of AInterface\",\n\t\t\t\tClassUtil.isSubClassOrInterfaceOf(BInterface.class, AInterface.class));\n\t\tassertTrue(\"BClass should be an implementation of BInterface\",\n\t\t\t\tClassUtil.isSubClassOrInterfaceOf(BClass.class, BInterface.class));\n\t\tassertTrue(\"BClass should be an implementation of AInterface\",\n\t\t\t\tClassUtil.isSubClassOrInterfaceOf(BClass.class, AInterface.class));\n\t}\n\n\tpublic static class ParentBean<T, ID> {\n\t}\n\n\tpublic static class TestBean extends ParentBean<String, Long> {\n\n\t}\n\n\tpublic static class TestBean2 extends ParentBean {\n\t}\n\n\tpublic static class TestBean3 {\n\n\t}\n\n\tpublic interface AInterface {\n\t}\n\n\t@CAnnotation\n\tpublic interface BInterface extends AInterface {\n\t\t@FAnnotation\n\t\tvoid hello();\n\t}\n\n\tpublic interface CInterface {\n\t}\n\n\tpublic interface DInterface {\n\t}\n\n\t@Retention(RetentionPolicy.RUNTIME)\n\tpublic @interface AAnnotation {\n\t}\n\n\t@Retention(RetentionPolicy.RUNTIME)\n\t@AAnnotation\n\tpublic @interface BAnnotation {\n\t}\n\n\t@Retention(RetentionPolicy.RUNTIME)\n\tpublic @interface CAnnotation {\n\t}\n\n\t@Retention(RetentionPolicy.RUNTIME)\n\tpublic @interface DAnnotation {\n\t}\n\n\t@Retention(RetentionPolicy.RUNTIME)\n\tpublic @interface EAnnotation {\n\t}\n\n\t@Retention(RetentionPolicy.RUNTIME)\n\tpublic @interface FAnnotation {\n\t}\n\n\t@DAnnotation\n\tpublic static class AClass implements DInterface {\n\n\t\t@EAnnotation\n\t\tprivate int afield;\n\n\t\tprivate int cfield;\n\n\t\t@FAnnotation\n\t\tprivate int dfield;\n\n\t\t@AAnnotation\n\t\tpublic int tfield;\n\n\t\t@AAnnotation\n\t\tprotected int vfield;\n\n\t\t// not counted as public annotated method\n\t\tpublic void hello2(int i) {\n\n\t\t}\n\n\t\t// counted as public annotated method\n\t\t@FAnnotation\n\t\tpublic void hello4(int i) {\n\n\t\t}\n\n\t\t// not counted as public annotated method\n\t\t@FAnnotation\n\t\tprotected void hello5(int i) {\n\n\t\t}\n\n\t\t// not counted as public annotated method\n\t\t@FAnnotation\n\t\tprivate void hello6(int i) {\n\n\t\t}\n\n\t\t// not counted as public annotated method, because the child override it\n\t\t@FAnnotation\n\t\tpublic void hello7(int i) {\n\n\t\t}\n\n\t}\n\n\t@BAnnotation\n\tpublic static class BClass extends AClass implements CInterface, BInterface {\n\n\t\t@EAnnotation\n\t\tprivate int bfield;\n\n\t\t@EAnnotation\n\t\tprivate int efield;\n\n\t\t@AAnnotation\n\t\tpublic int sfield;\n\n\t\t@AAnnotation\n\t\tprotected int ufield;\n\n\t\t// counted as public annotated method, BInterface\n\t\t@Override\n\t\t@EAnnotation\n\t\tpublic void hello() {\n\t\t\t// TODO Auto-generated method stub\n\n\t\t}\n\n\t\tpublic void hello2(int i) {\n\n\t\t}\n\n\t\t// counted as public annotated method\n\t\t@FAnnotation\n\t\tpublic void hello3(int i) {\n\n\t\t}\n\n\t\t// not counted as public annotated method\n\t\t@Override\n\t\tpublic void hello7(int i) {\n\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/reflect/ClassloaderUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.reflect;\n\nimport org.junit.Test;\n\npublic class ClassloaderUtilTest {\n\n\t@Test\n\tpublic void test() {\n\t\tClassLoader loader = ClassLoaderUtil.getDefaultClassLoader();\n\t\tClassLoaderUtil.isPresent(\"com.vip.vjtools.vjkit.reflect.ClassUtil\", loader);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/reflect/ReflectionUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.reflect;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.base.type.UncheckedException;\nimport com.vip.vjtools.vjkit.collection.ListUtil;\n\npublic class ReflectionUtilTest {\n\n\t@Test\n\tpublic void getAndSetFieldValue() {\n\t\tTestBean bean = new TestBean();\n\t\t// 无需getter函数, 直接读取privateField\n\t\tassertThat((int) ReflectionUtil.getFieldValue(bean, \"privateField\")).isEqualTo(1);\n\n\t\t// 先尝试getter函数, 然后直接读取privateField\n\t\tassertThat((int) ReflectionUtil.getProperty(bean, \"privateField\")).isEqualTo(1);\n\n\t\t// 绕过将publicField+1的getter函数,直接读取publicField的原始值\n\t\tassertThat((int) ReflectionUtil.getFieldValue(bean, \"publicField\")).isEqualTo(1);\n\t\t// 先尝试getter函数, 成功则补不直接读取publicField\n\t\tassertThat((int) ReflectionUtil.getProperty(bean, \"publicField\")).isEqualTo(2);\n\n\t\tbean = new TestBean();\n\t\t// 无需setter函数, 直接设置privateField\n\t\tReflectionUtil.setFieldValue(bean, \"privateField\", 2);\n\t\tassertThat(bean.inspectPrivateField()).isEqualTo(2);\n\t\tReflectionUtil.setProperty(bean, \"privateField\", 3);\n\t\tassertThat(bean.inspectPrivateField()).isEqualTo(3);\n\n\t\t// 绕过将publicField+1的setter函数,直接设置publicField的原始值\n\t\tReflectionUtil.setFieldValue(bean, \"publicField\", 2);\n\t\tassertThat(bean.inspectPublicField()).isEqualTo(2);\n\n\t\t// 没有绕过将publicField+1的setter函数\n\t\tReflectionUtil.setProperty(bean, \"publicField\", 3);\n\t\tassertThat(bean.inspectPublicField()).isEqualTo(4);\n\n\t\ttry {\n\t\t\tReflectionUtil.getFieldValue(bean, \"notExist\");\n\t\t\tfailBecauseExceptionWasNotThrown(IllegalArgumentException.class);\n\t\t} catch (IllegalArgumentException e) { // NOSONAR\n\t\t}\n\n\t\ttry {\n\t\t\tReflectionUtil.setFieldValue(bean, \"notExist\", 2);\n\t\t\tfailBecauseExceptionWasNotThrown(IllegalArgumentException.class);\n\t\t} catch (IllegalArgumentException e) { // NOSONAR\n\t\t}\n\t}\n\n\t@Test\n\tpublic void invokeGetterAndSetter() {\n\t\tTestBean bean = new TestBean();\n\t\tassertThat((int) ReflectionUtil.invokeGetter(bean, \"publicField\")).isEqualTo(bean.inspectPublicField() + 1);\n\n\t\tbean = new TestBean();\n\t\t// 通过setter的函数将+1\n\t\tReflectionUtil.invokeSetter(bean, \"publicField\", 10);\n\t\tassertThat(bean.inspectPublicField()).isEqualTo(10 + 1);\n\t}\n\n\t@Test\n\tpublic void invokeMethod() {\n\t\tTestBean bean = new TestBean();\n\t\t// 使用函数名+参数类型的匹配, 支持传参数\n\t\tassertThat((String) ReflectionUtil.invokeMethod(bean, \"privateMethod\", new Object[] { \"calvin\" }))\n\t\t\t\t.isEqualTo(\"hello calvin\");\n\n\t\t// 使用函数名+参数类型的匹配\n\t\tassertThat((String) ReflectionUtil.invokeMethod(bean, \"privateMethod\", new Object[] { \"calvin\" },\n\t\t\t\tnew Class[] { String.class })).isEqualTo(\"hello calvin\");\n\n\t\t// 仅匹配函数名\n\t\tassertThat((String) ReflectionUtil.invokeMethodByName(bean, \"privateMethod\", new Object[] { \"calvin\" }))\n\t\t\t\t.isEqualTo(\"hello calvin\");\n\n\t\t// 各种类型\n\t\tassertThat((int) ReflectionUtil.invokeMethod(bean, \"intType\", new Object[] { 1 }, new Class[] { int.class }))\n\t\t\t\t.isEqualTo(1);\n\n\t\tassertThat((int) ReflectionUtil.invokeMethod(bean, \"integerType\", new Object[] { 1 },\n\t\t\t\tnew Class[] { Integer.class })).isEqualTo(1);\n\n\t\tassertThat((int) ReflectionUtil.invokeMethod(bean, \"listType\", new Object[] { ListUtil.newArrayList(\"1\", \"2\") },\n\t\t\t\tnew Class[] { List.class })).isEqualTo(2);\n\n\t\tassertThat((int) ReflectionUtil.invokeMethod(bean, \"intType\", 1)).isEqualTo(1);\n\n\t\tassertThat((int) ReflectionUtil.invokeMethod(bean, \"integerType\", 1)).isEqualTo(1);\n\n\t\tassertThat((int) ReflectionUtil.invokeMethod(bean, \"listType\", ListUtil.newArrayList(\"1\", \"2\"))).isEqualTo(2);\n\n\t\t// 函数名错\n\t\ttry {\n\t\t\tReflectionUtil.invokeMethod(bean, \"notExistMethod\", new Object[] { \"calvin\" },\n\t\t\t\t\tnew Class[] { String.class });\n\t\t\tfailBecauseExceptionWasNotThrown(IllegalArgumentException.class);\n\t\t} catch (IllegalArgumentException e) {\n\n\t\t}\n\n\t\t// 参数类型错\n\t\ttry {\n\t\t\tReflectionUtil.invokeMethod(bean, \"privateMethod\", new Object[] { \"calvin\" },\n\t\t\t\t\tnew Class[] { Integer.class });\n\t\t\tfailBecauseExceptionWasNotThrown(RuntimeException.class);\n\t\t} catch (RuntimeException e) {\n\n\t\t}\n\n\t\t// 函数名错\n\t\ttry {\n\t\t\tReflectionUtil.invokeMethodByName(bean, \"notExistMethod\", new Object[] { \"calvin\" });\n\t\t\tfailBecauseExceptionWasNotThrown(IllegalArgumentException.class);\n\t\t} catch (IllegalArgumentException e) {\n\n\t\t}\n\t}\n\n\t@Test\n\tpublic void invokeConstructor() {\n\t\tTestBean bean = ReflectionUtil.invokeConstructor(TestBean.class);\n\t\tassertThat(bean.getPublicField()).isEqualTo(2);\n\n\t\tTestBean3 bean3 = ReflectionUtil.invokeConstructor(TestBean3.class, 4);\n\t\tassertThat(bean3.getId()).isEqualTo(4);\n\t}\n\n\t@Test\n\tpublic void convertReflectionExceptionToUnchecked() {\n\t\tIllegalArgumentException iae = new IllegalArgumentException();\n\t\t// ReflectionException,normal\n\t\tRuntimeException e = ReflectionUtil.convertReflectionExceptionToUnchecked(iae);\n\t\tassertThat(e).isEqualTo(iae);\n\n\t\t// InvocationTargetException,extract it's target exception.\n\t\tException ex = new Exception();\n\t\te = ReflectionUtil.convertReflectionExceptionToUnchecked(new InvocationTargetException(ex));\n\t\tassertThat(e.getCause()).isEqualTo(ex);\n\n\t\t// UncheckedException, ignore it.\n\t\tRuntimeException re = new RuntimeException(\"abc\");\n\t\te = ReflectionUtil.convertReflectionExceptionToUnchecked(re);\n\t\tassertThat(e).hasMessage(\"abc\");\n\n\t\t// Unexcepted Checked exception.\n\t\te = ReflectionUtil.convertReflectionExceptionToUnchecked(ex);\n\t\tassertThat(e).isInstanceOf(UncheckedException.class);\n\t}\n\n\tpublic static class ParentBean<T, ID> {\n\t}\n\n\tpublic static class TestBean extends ParentBean<String, Long> {\n\t\t/** 没有getter/setter的field */\n\t\tprivate int privateField = 1;\n\t\t/** 有getter/setter的field */\n\t\tprivate int publicField = 1;\n\n\t\t// 通過getter函數會比屬性值+1\n\t\tpublic int getPublicField() {\n\t\t\treturn publicField + 1;\n\t\t}\n\n\t\t// 通過setter函數會被比輸入值加1\n\t\tpublic void setPublicField(int publicField) {\n\t\t\tthis.publicField = publicField + 1;\n\t\t}\n\n\t\tpublic int inspectPrivateField() {\n\t\t\treturn privateField;\n\t\t}\n\n\t\tpublic int inspectPublicField() {\n\t\t\treturn publicField;\n\t\t}\n\n\t\tprivate String privateMethod(String text) {\n\t\t\treturn \"hello \" + text;\n\t\t}\n\n\t\t// 测试原子类型转换\n\t\tpublic Integer integerType(Integer i) {\n\t\t\treturn i;\n\t\t}\n\n\t\t// 测试原子类型转换\n\t\tpublic int intType(int i) {\n\t\t\treturn i;\n\t\t}\n\n\t\t// 测试类型为接口\n\t\tpublic int listType(List<?> list) {\n\t\t\treturn list.size();\n\t\t}\n\t}\n\n\tpublic static class TestBean2 extends ParentBean {\n\t}\n\n\tpublic static class TestBean3 {\n\n\t\tpublic TestBean3() {\n\n\t\t}\n\n\t\tpublic TestBean3(int id) {\n\t\t\tsuper();\n\t\t\tthis.id = id;\n\t\t}\n\n\t\tprivate int id;\n\n\t\tpublic int getId() {\n\t\t\treturn id;\n\t\t}\n\n\t\tpublic void setId(int id) {\n\t\t\tthis.id = id;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/security/CryptoUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.security;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.text.EncodeUtil;\n\n\npublic class CryptoUtilTest {\n\t@Test\n\tpublic void mac() {\n\t\tString input = \"foo message\";\n\n\t\t// key可为任意字符串\n\t\t// byte[] key = \"a foo key\".getBytes();\n\t\tbyte[] key = CryptoUtil.generateHmacSha1Key();\n\t\tassertThat(key).hasSize(20);\n\n\t\tbyte[] macResult = CryptoUtil.hmacSha1(input.getBytes(), key);\n\t\tSystem.out.println(\"hmac-sha1 key in hex      :\" + EncodeUtil.encodeHex(key));\n\t\tSystem.out.println(\"hmac-sha1 in hex result   :\" + EncodeUtil.encodeHex(macResult));\n\n\t\tassertThat(CryptoUtil.isMacValid(macResult, input.getBytes(), key)).isTrue();\n\t}\n\n\t@Test\n\tpublic void aes() {\n\t\tbyte[] key = CryptoUtil.generateAesKey();\n\t\tassertThat(key).hasSize(16);\n\t\tString input = \"foo message\";\n\n\t\tbyte[] encryptResult = CryptoUtil.aesEncrypt(input.getBytes(), key);\n\t\tString descryptResult = CryptoUtil.aesDecrypt(encryptResult, key);\n\n\t\tSystem.out.println(\"aes key in hex            :\" + EncodeUtil.encodeHex(key));\n\t\tSystem.out.println(\"aes encrypt in hex result :\" + EncodeUtil.encodeHex(encryptResult));\n\t\tassertThat(descryptResult).isEqualTo(input);\n\t}\n\n\t@Test\n\tpublic void aesWithIV() {\n\t\tbyte[] key = CryptoUtil.generateAesKey();\n\t\tbyte[] iv = CryptoUtil.generateIV();\n\t\tassertThat(key).hasSize(16);\n\t\tassertThat(iv).hasSize(16);\n\t\tString input = \"foo message\";\n\n\t\tbyte[] encryptResult = CryptoUtil.aesEncrypt(input.getBytes(), key, iv);\n\t\tString descryptResult = CryptoUtil.aesDecrypt(encryptResult, key, iv);\n\n\t\tSystem.out.println(\"aes key in hex            :\" + EncodeUtil.encodeHex(key));\n\t\tSystem.out.println(\"iv in hex                 :\" + EncodeUtil.encodeHex(iv));\n\t\tSystem.out.println(\"aes encrypt in hex result :\" + EncodeUtil.encodeHex(encryptResult));\n\t\tassertThat(descryptResult).isEqualTo(input);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/text/CsvUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class CsvUtilTest {\n\n\t@Test\n\tpublic void toCsvString() {\n\t\tassertThat(CsvUtil.toCsvString(1, 2)).isEqualTo(\"1,2\");\n\n\t\tassertThat(CsvUtil.toCsvString(1, 2, 3, 4)).isEqualTo(\"1,2,3,4\");\n\n\t\t// \"2\" still plain as 2\n\t\tassertThat(CsvUtil.toCsvString(1, \"2\")).isEqualTo(\"1,2\");\n\n\t\t// \"A BC\" still plain as A BC\n\t\tassertThat(CsvUtil.toCsvString(1, \"A BC\")).isEqualTo(\"1,A BC\");\n\n\t\t// \"A,BC\" has ',' as \"A,BC\"\n\t\tassertThat(CsvUtil.toCsvString(1, \"A,BC\")).isEqualTo(\"1,\\\"A,BC\\\"\");\n\n\t\t// \"A\"BC\" has '\"' as \"A\"\"BC\"\n\t\tassertThat(CsvUtil.toCsvString(1, \"A\\\"BC\")).isEqualTo(\"1,\\\"A\\\"\\\"BC\\\"\");\n\n\t\t// \"A,B\"a\"C\" has 2 '\"\"' as \"A,\"\"a\"\"BC\"\n\t\tassertThat(CsvUtil.toCsvString(1, \"A,\\\"a\\\"BC\")).isEqualTo(\"1,\\\"A,\\\"\\\"a\\\"\\\"BC\\\"\");\n\t}\n\n\t@Test\n\tpublic void fromCsvString() {\n\t\tassertThat(CsvUtil.fromCsvString(\"1,2\")).hasSize(2).contains(\"1\").contains(\"2\");\n\t\tassertThat(CsvUtil.fromCsvString(\"1,A BC\")).hasSize(2).contains(\"1\").contains(\"A BC\");\n\t\tassertThat(CsvUtil.fromCsvString(\"1,\\\"A,BC\\\"\")).hasSize(2).contains(\"1\").contains(\"A,BC\");\n\t\tassertThat(CsvUtil.fromCsvString(\"1,\\\"A,\\\"\\\"a\\\"\\\"BC\\\"\")).hasSize(2).contains(\"1\").contains(\"A,\\\"a\\\"BC\");\n\n\t\t// wrong format still work\n\t\tassertThat(CsvUtil.fromCsvString(\"1,\\\"A,\\\"a\\\"\\\"BC\\\"\")).hasSize(2).contains(\"1\").contains(\"A,\\\"a\\\"BC\");\n\t\tassertThat(CsvUtil.fromCsvString(\"1,ABC\\\"\")).hasSize(2).contains(\"1\").contains(\"ABC\\\"\");\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/text/EncodeUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class EncodeUtilTest {\n\n\t@Test\n\tpublic void hexEncode() {\n\t\tString input = \"haha,i am a very long message\";\n\t\tString result = EncodeUtil.encodeHex(input.getBytes());\n\t\tassertThat(new String(EncodeUtil.decodeHex(result), Charsets.UTF_8)).isEqualTo(input);\n\n\t\tbyte[] bytes = new byte[] { 1, 2, 15, 17 };\n\t\tresult = EncodeUtil.encodeHex(bytes);\n\t\tassertThat(result).isEqualTo(\"01020F11\");\n\n\t\tinput = \"01020F11\";\n\t\tassertThat(EncodeUtil.decodeHex(input)).hasSize(4).containsSequence((byte) 1, (byte) 2, (byte) 15, (byte) 17);\n\n\t\ttry {\n\t\t\tinput = \"01020G11\";\n\t\t\tEncodeUtil.decodeHex(input);\n\t\t\tfail(\"should throw exception before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\n\t}\n\n\t@Test\n\tpublic void base64Encode() {\n\t\tString input = \"haha,i am a very long message\";\n\t\tString result = EncodeUtil.encodeBase64(input.getBytes());\n\t\tassertThat(new String(EncodeUtil.decodeBase64(result), Charsets.UTF_8)).isEqualTo(input);\n\n\t\tbyte[] bytes = new byte[] { 5 };\n\t\tresult = EncodeUtil.encodeBase64(bytes);\n\t\tassertThat(result).isEqualTo(\"BQ==\");\n\n\t\tbytes = new byte[] { 1, 2, 15, 17, 127 };\n\t\tresult = EncodeUtil.encodeBase64(bytes);\n\t\tassertThat(result).isEqualTo(\"AQIPEX8=\");\n\t}\n\n\t@Test\n\tpublic void base64UrlSafeEncode() {\n\t\tString input = \"haha,i am a very long message\";\n\t\tString result = EncodeUtil.encodeBase64UrlSafe(input.getBytes());\n\t\tassertThat(new String(EncodeUtil.decodeBase64UrlSafe(result), Charsets.UTF_8)).isEqualTo(input);\n\n\t\ttry {\n\t\t\tassertThat(result).isEqualTo(EncodeUtil.decodeBase64UrlSafe(\"AQIPE+8=\"));\n\t\t\tfail(\"should throw exception before\");\n\t\t} catch (Throwable t) {\n\t\t\tassertThat(t).isInstanceOf(IllegalArgumentException.class);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/text/EscapeUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class EscapeUtilTest {\n\n\t@Test\n\tpublic void urlEncode() {\n\n\t\tString input = \"http://locahost/\";\n\t\tString result = EscapeUtil.urlEncode(input);\n\t\tassertThat(result).isEqualTo(\"http%3A%2F%2Flocahost%2F\");\n\t\tassertThat(EscapeUtil.urlDecode(result)).isEqualTo(input);\n\n\t\tinput = \"http://locahost/?query=中文&t=1\";\n\t\tresult = EscapeUtil.urlEncode(input);\n\t\tSystem.out.println(result);\n\n\t\tassertThat(EscapeUtil.urlDecode(result)).isEqualTo(input);\n\t}\n\n\t@Test\n\tpublic void xmlEncode() {\n\t\tString input = \"1>2\";\n\t\tString result = EscapeUtil.escapeXml(input);\n\t\tassertThat(result).isEqualTo(\"1&gt;2\");\n\t\tassertThat(EscapeUtil.unescapeXml(result)).isEqualTo(input);\n\t}\n\n\t@Test\n\tpublic void html() {\n\t\tString input = \"1>2\";\n\t\tString result = EscapeUtil.escapeHtml(input);\n\t\tassertThat(result).isEqualTo(\"1&gt;2\");\n\t\tassertThat(EscapeUtil.unescapeHtml(result)).isEqualTo(input);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/text/HashUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.io.ResourceUtil;\n\npublic class HashUtilTest {\n\n\n\t@Test\n\tpublic void hashSha1() {\n\t\t// 普通\n\t\tString result = EncodeUtil.encodeBase64(HashUtil.sha1(\"hhahah\"));\n\t\tSystem.out.println(\"sha1:\" + result);\n\t\tassertThat(result).isEqualTo(\"sCtJLx2IJNto032AhdkP64t/os4=\");\n\n\t\tString result2 = EncodeUtil.encodeBase64(HashUtil.sha1(\"hhahah\".getBytes()));\n\t\tassertThat(result).isEqualTo(\"sCtJLx2IJNto032AhdkP64t/os4=\");\n\n\t\t// 带盐, 每次salt值不一样，所以值也不一样。\n\t\tresult = EncodeUtil.encodeBase64(HashUtil.sha1(\"hhahah\", HashUtil.generateSalt(5)));\n\t\tSystem.out.println(\"sha1 with salt:\" + result);\n\n\t\t// 带盐，固定的盐\n\t\tresult = EncodeUtil.encodeBase64(HashUtil.sha1(\"hhahah\", new byte[] { 1, 2, 3 }));\n\t\tassertThat(result).isEqualTo(\"U/7wy5R1sVrjEf3dOTAPz383g2k=\");\n\t\tresult2 = EncodeUtil.encodeBase64(HashUtil.sha1(\"hhahah\".getBytes(), new byte[] { 1, 2, 3 }));\n\t\tassertThat(result).isEqualTo(result2);\n\n\t\t// 带盐迭代, 每次salt值不一样，所以值也不一样。\n\t\tresult = EncodeUtil.encodeBase64(HashUtil.sha1(\"hhahah\", HashUtil.generateSalt(5), 2));\n\t\tSystem.out.println(\"sha1 with salt with iteration:\" + result);\n\n\t\t// 带盐迭代, 固定的盐\n\t\tresult = EncodeUtil.encodeBase64(HashUtil.sha1(\"hhahah\", new byte[] { 1, 2, 3 }, 2));\n\t\tassertThat(result).isEqualTo(\"n9O7laits+ovoK8X8xde+XrsCtM=\");\n\t\tresult2 = EncodeUtil.encodeBase64(HashUtil.sha1(\"hhahah\".getBytes(), new byte[] { 1, 2, 3 }, 2));\n\t\tassertThat(result).isEqualTo(result2);\n\n\t}\n\n\t@Test\n\tpublic void hashFile() throws IOException {\n\t\tInputStream in = ResourceUtil.asStream(\"test.txt\");\n\t\tString result = EncodeUtil.encodeBase64(HashUtil.sha1File(in));\n\t\tassertThat(result).isEqualTo(\"DmSnwK/Fl0Jplrwtm9tfi7cb/js=\");\n\t\tresult = EncodeUtil.encodeBase64(HashUtil.md5File(in));\n\t\tassertThat(result).isEqualTo(\"1B2M2Y8AsgTpgAmY7PhCfg==\");\n\t}\n\n\t@Test\n\tpublic void crc32() {\n\t\tassertThat(HashUtil.crc32AsInt(\"hahhha1\")).isEqualTo(-625925593);\n\t\tassertThat(HashUtil.crc32AsInt(\"hahhha1\".getBytes())).isEqualTo(-625925593);\n\t\tassertThat(HashUtil.crc32AsInt(\"hahhha2\")).isEqualTo(1136161693);\n\n\t\tassertThat(HashUtil.crc32AsLong(\"hahhha1\")).isEqualTo(3669041703L);\n\t\tassertThat(HashUtil.crc32AsLong(\"hahhha1\".getBytes())).isEqualTo(3669041703L);\n\t\tassertThat(HashUtil.crc32AsLong(\"hahhha2\")).isEqualTo(1136161693L);\n\t}\n\n\t@Test\n\tpublic void murmurhash() {\n\t\tassertThat(HashUtil.murmur32AsInt(\"hahhha1\")).isEqualTo(-1920794701);\n\t\tassertThat(HashUtil.murmur32AsInt(\"hahhha1\".getBytes())).isEqualTo(-1920794701);\n\t\tassertThat(HashUtil.murmur32AsInt(\"hahhha2\")).isEqualTo(2065789419);\n\t\tassertThat(HashUtil.murmur32AsInt(\"hahhha3\")).isEqualTo(-293065542);\n\t\tassertThat(HashUtil.murmur32AsInt(\"hahhha4\")).isEqualTo(-2003559207);\n\t\tassertThat(HashUtil.murmur32AsInt(\"hahhha5\")).isEqualTo(-3887993);\n\t\tassertThat(HashUtil.murmur32AsInt(\"hahhha6\")).isEqualTo(-446760132);\n\n\t\tassertThat(HashUtil.murmur128AsLong(\"hahhha6\")).isEqualTo(-5203515929515563680L);\n\t\tassertThat(HashUtil.murmur128AsLong(\"hahhha6\".getBytes(Charsets.UTF_8))).isEqualTo(-5203515929515563680L);\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/text/MoreStringUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport com.google.common.base.Splitter;\n\npublic class MoreStringUtilTest {\n\t@Test\n\tpublic void split() {\n\n\t\tList<String> result = MoreStringUtil.split(\"192.168.0.1\", '.', 4);\n\t\tassertThat(result).hasSize(4).containsSequence(\"192\", \"168\", \"0\", \"1\");\n\n\t\tresult = MoreStringUtil.split(\"192.168..1\", '.', 4);\n\t\tassertThat(result).hasSize(3).containsSequence(\"192\", \"168\", \"1\");\n\n\t\tresult = MoreStringUtil.split(\"192.168.0.\", '.', 4);\n\t\tassertThat(result).hasSize(3).containsSequence(\"192\", \"168\", \"0\");\n\n\t\tassertThat(MoreStringUtil.split(null, '.', 4)).isNull();\n\n\t\tassertThat(MoreStringUtil.split(\"\", '.', 4)).hasSize(0);\n\n\t\tSplitter splitter = MoreStringUtil.charsSplitter(\"/\\\\\").omitEmptyStrings();\n\t\tresult = splitter.splitToList(\"/a/b/c\");\n\t\tassertThat(result).hasSize(3).containsSequence(\"a\", \"b\", \"c\");\n\n\t\tresult = splitter.splitToList(\"\\\\a\\\\b\\\\c\");\n\t\tassertThat(result).hasSize(3).containsSequence(\"a\", \"b\", \"c\");\n\n\t}\n\n\t@Test\n\tpublic void charMatch() {\n\t\tString str = \"abc\";\n\t\tassertThat(MoreStringUtil.startWith(str, 'a')).isTrue();\n\t\tassertThat(MoreStringUtil.startWith(str, 'b')).isFalse();\n\t\tassertThat(MoreStringUtil.startWith(null, 'b')).isFalse();\n\t\tassertThat(MoreStringUtil.startWith(\"\", 'b')).isFalse();\n\n\t\tassertThat(MoreStringUtil.endWith(str, 'c')).isTrue();\n\t\tassertThat(MoreStringUtil.endWith(str, 'b')).isFalse();\n\t\tassertThat(MoreStringUtil.endWith(null, 'b')).isFalse();\n\t\tassertThat(MoreStringUtil.endWith(\"\", 'b')).isFalse();\n\n\t\tassertThat(MoreStringUtil.replaceFirst(\"abbc\", 'b', 'c')).isEqualTo(\"acbc\");\n\t\tassertThat(MoreStringUtil.replaceFirst(\"abcc\", 'c', 'c')).isEqualTo(\"abcc\");\n\t\tassertThat(MoreStringUtil.replaceFirst(\"\", 'c', 'c')).isEqualTo(\"\");\n\t\tassertThat(MoreStringUtil.replaceFirst(null, 'c', 'c')).isNull();\n\n\t\tassertThat(MoreStringUtil.replaceLast(\"abbc\", 'b', 'c')).isEqualTo(\"abcc\");\n\t\tassertThat(MoreStringUtil.replaceLast(\"abcc\", 'c', 'c')).isEqualTo(\"abcc\");\n\t\tassertThat(MoreStringUtil.replaceLast(\"\", 'c', 'c')).isEqualTo(\"\");\n\t\tassertThat(MoreStringUtil.replaceLast(null, 'c', 'c')).isNull();\n\n\t}\n\n\t@Test\n\tpublic void utf8EncodedLength() {\n\t\tassertThat(MoreStringUtil.utf8EncodedLength(\"ab12\")).isEqualTo(4);\n\t\tassertThat(MoreStringUtil.utf8EncodedLength(\"中文\")).isEqualTo(6);\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/text/StringBuilderHolderTest.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.CyclicBarrier;\n\nimport org.junit.Test;\n\npublic class StringBuilderHolderTest {\n\n\t@Test\n\tpublic void test() throws InterruptedException {\n\n\t\tfinal CountDownLatch countdown = new CountDownLatch(10);\n\t\tfinal CyclicBarrier barrier = new CyclicBarrier(10);\n\n\t\tRunnable runnable = new Runnable() {\n\n\t\t\tStringBuilderHolder holder = new StringBuilderHolder(512);\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\ttry {\n\t\t\t\t\tbarrier.await();\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t\tStringBuilder builder = StringBuilderHolder.getGlobal();\n\t\t\t\tbuilder.append(Thread.currentThread().getName() + \"-1\");\n\t\t\t\tSystem.out.println(builder.toString());\n\n\t\t\t\tbuilder = StringBuilderHolder.getGlobal();\n\t\t\t\tbuilder.append(Thread.currentThread().getName() + \"-2\");\n\t\t\t\tSystem.out.println(builder.toString());\n\n\t\t\t\tStringBuilder builder2 = holder.get();\n\t\t\t\tbuilder2.append(Thread.currentThread().getName() + \"-11\");\n\t\t\t\tSystem.out.println(builder2.toString());\n\n\t\t\t\tbuilder2 = holder.get();\n\t\t\t\tbuilder2.append(Thread.currentThread().getName() + \"-22\");\n\t\t\t\tSystem.out.println(builder2.toString());\n\n\t\t\t\tcountdown.countDown();\n\t\t\t}\n\t\t};\n\n\t\tfor (int i = 0; i < 10; i++) {\n\t\t\tThread thread = new Thread(runnable);\n\t\t\tthread.start();\n\t\t}\n\n\t\tcountdown.await();\n\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/text/TextValidatorTest.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class TextValidatorTest {\n\n\t@Test\n\tpublic void isMobileSimple() {\n\t\tassertThat(TextValidator.isMobileSimple(null)).isFalse();\n\t\tassertThat(TextValidator.isMobileSimple(\"\")).isFalse();\n\t\tassertThat(TextValidator.isMobileSimple(\"1234a\")).isFalse();\n\t\tassertThat(TextValidator.isMobileSimple(\"1234561\")).isFalse();\n\t\tassertThat(TextValidator.isMobileSimple(\"11170998762\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void isMobileExact() {\n\t\tassertThat(TextValidator.isMobileExact(\"1234a\")).isFalse();\n\t\tassertThat(TextValidator.isMobileExact(\"11170998762\")).isFalse();\n\t\tassertThat(TextValidator.isMobileExact(\"13970998762\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void isTel() {\n\t\t// 含字母\n\t\tassertThat(TextValidator.isTel(\"8802973a\")).isFalse();\n\t\t// 太长\n\t\tassertThat(TextValidator.isTel(\"8908222222\")).isFalse();\n\t\t// 太短\n\t\tassertThat(TextValidator.isTel(\"89081\")).isFalse();\n\n\t\tassertThat(TextValidator.isTel(\"89019739\")).isTrue();\n\t\tassertThat(TextValidator.isTel(\"020-89019739\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void isIdCard() {\n\t\t// 含字母\n\t\tassertThat(TextValidator.isIdCard(\"440101198987754ab\")).isFalse();\n\t\t// 月份不对\n\t\tassertThat(TextValidator.isIdCard(\"440101198987754122\")).isFalse();\n\t\t// 日期不对\n\t\tassertThat(TextValidator.isIdCard(\"440101891232451\")).isFalse();\n\n\t\t// 18位正确\n\t\tassertThat(TextValidator.isIdCard(\"440101198909204518\")).isTrue();\n\t\t// 15位正确\n\t\tassertThat(TextValidator.isIdCard(\"440101891231451\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void isEmail() {\n\t\tassertThat(TextValidator.isEmail(\"abc\")).isFalse();\n\t\tassertThat(TextValidator.isEmail(\"abc@a\")).isFalse();\n\t\tassertThat(TextValidator.isEmail(\"中文@a.com\")).isFalse();\n\n\t\tassertThat(TextValidator.isEmail(\"abc@abc.com\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void isUrl() {\n\t\tassertThat(TextValidator.isUrl(\"abc.com\")).isFalse();\n\t\tassertThat(TextValidator.isUrl(\"http://abc.c om\")).isFalse();\n\t\tassertThat(TextValidator.isUrl(\"http2://abc.com\")).isFalse();\n\n\t\tassertThat(TextValidator.isUrl(\"http://abc.com\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void isDate() {\n\t\tassertThat(TextValidator.isDate(\"2011-02-29\")).isFalse();\n\t\tassertThat(TextValidator.isDate(\"201a-02-30\")).isFalse();\n\t\tassertThat(TextValidator.isDate(\"2011-0211\")).isFalse();\n\n\t\tassertThat(TextValidator.isDate(\"2011-03-11\")).isTrue();\n\t\tassertThat(TextValidator.isDate(\"2012-02-29\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void isIp() {\n\t\tassertThat(TextValidator.isIp(\"192.168.0.300\")).isFalse();\n\t\tassertThat(TextValidator.isIp(\"192.168.300.1\")).isFalse();\n\t\tassertThat(TextValidator.isIp(\"192.168.300\")).isFalse();\n\t\tassertThat(TextValidator.isIp(\"192.168.A3.1\")).isFalse();\n\n\t\tassertThat(TextValidator.isIp(\"192.168.0.1\")).isTrue();\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/text/WildcardMatcherTest.java",
    "content": "package com.vip.vjtools.vjkit.text;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport org.junit.Test;\n\npublic class WildcardMatcherTest {\n\n\t@Test\n\tpublic void matchString() {\n\t\tassertThat(WildcardMatcher.match(\"abc\", \"*\")).isTrue();\n\t\tassertThat(WildcardMatcher.match(\"abc\", \"*c\")).isTrue();\n\t\tassertThat(WildcardMatcher.match(\"abc\", \"a*\")).isTrue();\n\t\tassertThat(WildcardMatcher.match(\"abc\", \"a*c\")).isTrue();\n\n\t\tassertThat(WildcardMatcher.match(\"abc\", \"a?c\")).isTrue();\n\t\tassertThat(WildcardMatcher.match(\"abcd\", \"a?c?\")).isTrue();\n\t\tassertThat(WildcardMatcher.match(\"abcd\", \"a??d\")).isTrue();\n\n\t\tassertThat(WildcardMatcher.match(\"abcde\", \"a*d?\")).isTrue();\n\n\t\tassertThat(WildcardMatcher.match(\"abcde\", \"a*d\")).isFalse();\n\t\tassertThat(WildcardMatcher.match(\"abcde\", \"a*x\")).isFalse();\n\t\tassertThat(WildcardMatcher.match(\"abcde\", \"a*df\")).isFalse();\n\n\t\tassertThat(WildcardMatcher.match(\"abcde\", \"?abcd\")).isFalse();\n\n\t\tassertThat(WildcardMatcher.match(\"ab\\\\\\\\*cde\", \"ab\\\\\\\\*c*\")).isTrue();\n\t\tassertThat(WildcardMatcher.match(\"ab\\\\\\\\*cde\", \"ab\\\\\\\\*?de\")).isTrue();\n\n\t\t// matchOne\n\t\tassertThat(WildcardMatcher.matchOne(\"abcde\", new String[] { \"a*d?\", \"abde?\" })).isEqualTo(0);\n\t\tassertThat(WildcardMatcher.matchOne(\"abcde\", new String[] { \"?abcd\", \"a*d?\" })).isEqualTo(1);\n\t\tassertThat(WildcardMatcher.matchOne(\"abcde\", new String[] { \"?abcd\", \"xyz*\" })).isEqualTo(-1);\n\t}\n\n\t@Test\n\tpublic void matchPath() {\n\t\tassertThat(WildcardMatcher.matchPath(\"/a/b/dd\", \"**\")).isTrue();\n\n\t\tassertThat(WildcardMatcher.matchPath(\"/a/b/dd\", \"**/dd\")).isTrue();\n\t\tassertThat(WildcardMatcher.matchPath(\"/a/b/c/dd\", \"/a/**/dd\")).isTrue();\n\t\tassertThat(WildcardMatcher.matchPath(\"/a/b/dd\", \"/a/*/dd\")).isTrue();\n\t\tassertThat(WildcardMatcher.matchPath(\"/a/b/dd\", \"/a/*/d?\")).isTrue();\n\t\tassertThat(WildcardMatcher.matchPath(\"/a/b/ddxxa\", \"/a/*/dd*\")).isTrue();\n\t\tassertThat(WildcardMatcher.matchPath(\"/a/b/ddxxa\", \"/a/?/dd*\")).isTrue();\n\t\tassertThat(WildcardMatcher.matchPath(\"a/b/ddxxa\", \"a/?/dd*\")).isTrue();\n\t\tassertThat(WildcardMatcher.matchPath(\"a/b/dd\", \"**/dd\")).isTrue();\n\n\t\tassertThat(WildcardMatcher.matchPath(\"/a/b/c/dd\", \"/a/*/dd\")).isFalse();\n\t\tassertThat(WildcardMatcher.matchPath(\"\\\\a\\\\b\\\\c\\\\dd\", \"/a/*/dd\")).isFalse();\n\t\tassertThat(WildcardMatcher.matchPath(\"/a/b/c/dd\", \"/a/*/dd\")).isFalse();\n\n\t\t// matchOne\n\t\tassertThat(WildcardMatcher.matchPathOne(\"/a/b/c/dd\", new String[] { \"/a/*/dd\", \"**/dd\" })).isEqualTo(1);\n\t\tassertThat(WildcardMatcher.matchPathOne(\"/a/b/c/dd\", new String[] { \"/a/**/dd\", \"**/dd\" })).isEqualTo(0);\n\t\tassertThat(WildcardMatcher.matchPathOne(\"/a/b/c/dd\", new String[] { \"/b/d\", \"/a/c/*\" })).isEqualTo(-1);\n\n\t\tassertThat(WildcardMatcher.matchPathOne(\"\\\\a\\\\b\\\\c\\\\dd\", new String[] { \"/a/*/dd\", \"**\\\\dd\" })).isEqualTo(1);\n\t\tassertThat(WildcardMatcher.matchPathOne(\"\\\\a\\\\b\\\\c\\\\dd\", new String[] { \"\\\\a\\\\**\\\\dd\", \"**\\\\dd\" }))\n\t\t\t\t.isEqualTo(0);\n\t\tassertThat(WildcardMatcher.matchPathOne(\"\\\\a\\\\b\\\\c\\\\dd\", new String[] { \"/b/d\", \"/a/c/*\" })).isEqualTo(-1);\n\n\t\tassertThat(WildcardMatcher.matchPathOne(\"/a/b/c/dd\", new String[] { \"/a/*/dd\", \"**/dd\" })).isEqualTo(1);\n\t\tassertThat(WildcardMatcher.matchPathOne(\"/a/b/c/dd\", new String[] { \"/a/**/dd\", \"**/dd\" })).isEqualTo(0);\n\t\tassertThat(WildcardMatcher.matchPathOne(\"/a/b/c/dd\", new String[] { \"/b/d\", \"/a/c/*\" })).isEqualTo(-1);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/time/CachingDatFormatterTest.java",
    "content": "package com.vip.vjtools.vjkit.time;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.Date;\n\nimport org.junit.Test;\n\npublic class CachingDatFormatterTest {\n\n\t@Test\n\tpublic void test() {\n\t\tDate date = new Date(116, 10, 1, 12, 23, 44);\n\n\t\tCachingDateFormatter formatter = new CachingDateFormatter(DateFormatUtil.PATTERN_DEFAULT);\n\t\tassertThat(formatter.format(date.getTime())).isEqualTo(\"2016-11-01 12:23:44.000\");\n\t\tassertThat(formatter.format(date.getTime())).isEqualTo(\"2016-11-01 12:23:44.000\");\n\t\tassertThat(formatter.format(date.getTime() + 2)).isEqualTo(\"2016-11-01 12:23:44.002\");\n\n\t\tCachingDateFormatter formatterOnSecond = new CachingDateFormatter(DateFormatUtil.PATTERN_DEFAULT_ON_SECOND);\n\t\tassertThat(formatterOnSecond.format(date.getTime())).isEqualTo(\"2016-11-01 12:23:44\");\n\t\tassertThat(formatterOnSecond.format(date.getTime())).isEqualTo(\"2016-11-01 12:23:44\");\n\t\tassertThat(formatterOnSecond.format(date.getTime() + 2)).isEqualTo(\"2016-11-01 12:23:44\");\n\t\tassertThat(formatterOnSecond.format(date.getTime() + 1000)).isEqualTo(\"2016-11-01 12:23:45\");\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/time/ClockUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.time;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.Date;\n\nimport org.junit.Test;\n\nimport com.vip.vjtools.vjkit.time.ClockUtil.DummyClock;\n\npublic class ClockUtilTest {\n\n\t@Test\n\tpublic void testDummyClock() {\n\t\tDummyClock clock = new DummyClock();\n\t\tclock.updateNow(111);\n\t\tassertThat(clock.currentTimeMillis()).isEqualTo(111);\n\t\tassertThat(clock.currentDate().getTime()).isEqualTo(111);\n\n\t\tclock.updateNow(new Date(112));\n\t\tassertThat(clock.currentTimeMillis()).isEqualTo(112);\n\t\tclock.increaseTime(200);\n\t\tassertThat(clock.currentTimeMillis()).isEqualTo(312);\n\t\tclock.decreaseTime(100);\n\t\tassertThat(clock.currentTimeMillis()).isEqualTo(212);\n\n\t\tclock.setNanoTime(150);\n\t\tassertThat(clock.nanoTime()).isEqualTo(150);\n\n\t}\n\n\t@Test\n\tpublic void elapsedTime() {\n\t\ttry {\n\t\t\tDummyClock clock = ClockUtil.useDummyClock(2000);\n\t\t\tclock.increaseTime(1000);\n\t\t\tassertThat(ClockUtil.elapsedTime(2000)).isEqualTo(1000);\n\t\t} finally {\n\t\t\tClockUtil.useDefaultClock();\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/time/DateFormatUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.time;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.text.ParseException;\nimport java.util.Date;\n\nimport org.junit.Test;\n\npublic class DateFormatUtilTest {\n\n\t@Test\n\tpublic void isoDateFormat() {\n\t\tDate date = new Date(116, 10, 1, 12, 23, 44);\n\t\tassertThat(DateFormatUtil.ISO_FORMAT.format(date)).contains(\"2016-11-01T12:23:44.000\");\n\t\tassertThat(DateFormatUtil.ISO_ON_SECOND_FORMAT.format(date)).contains(\"2016-11-01T12:23:44\");\n\t\tassertThat(DateFormatUtil.ISO_ON_DATE_FORMAT.format(date)).isEqualTo(\"2016-11-01\");\n\t}\n\n\t@Test\n\tpublic void defaultDateFormat() {\n\t\tDate date = new Date(116, 10, 1, 12, 23, 44);\n\t\tassertThat(DateFormatUtil.DEFAULT_FORMAT.format(date)).isEqualTo(\"2016-11-01 12:23:44.000\");\n\t\tassertThat(DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.format(date)).isEqualTo(\"2016-11-01 12:23:44\");\n\t}\n\n\t@Test\n\tpublic void formatWithPattern() {\n\t\tDate date = new Date(116, 10, 1, 12, 23, 44);\n\t\tassertThat(DateFormatUtil.formatDate(DateFormatUtil.PATTERN_DEFAULT, date))\n\t\t\t\t.isEqualTo(\"2016-11-01 12:23:44.000\");\n\t\tassertThat(DateFormatUtil.formatDate(DateFormatUtil.PATTERN_DEFAULT, date.getTime()))\n\t\t\t\t.isEqualTo(\"2016-11-01 12:23:44.000\");\n\t}\n\n\t@Test\n\tpublic void parseWithPattern() throws ParseException {\n\t\tDate date = new Date(116, 10, 1, 12, 23, 44);\n\t\tDate resultDate = DateFormatUtil.parseDate(DateFormatUtil.PATTERN_DEFAULT, \"2016-11-01 12:23:44.000\");\n\t\tassertThat(resultDate.getTime() == date.getTime()).isTrue();\n\t}\n\n\t@Test\n\tpublic void formatDuration() {\n\t\tassertThat(DateFormatUtil.formatDuration(100)).isEqualTo(\"00:00:00.100\");\n\n\t\tassertThat(DateFormatUtil.formatDuration(new Date(100), new Date(3000))).isEqualTo(\"00:00:02.900\");\n\n\t\tassertThat(DateFormatUtil.formatDuration(DateUtil.MILLIS_PER_DAY * 2 + DateUtil.MILLIS_PER_HOUR * 4))\n\t\t\t\t.isEqualTo(\"52:00:00.000\");\n\n\t\tassertThat(DateFormatUtil.formatDurationOnSecond(new Date(100), new Date(3000))).isEqualTo(\"00:00:02\");\n\n\t\tassertThat(DateFormatUtil.formatDurationOnSecond(2000)).isEqualTo(\"00:00:02\");\n\n\t\tassertThat(DateFormatUtil.formatDurationOnSecond(DateUtil.MILLIS_PER_DAY * 2 + DateUtil.MILLIS_PER_HOUR * 4))\n\t\t\t\t.isEqualTo(\"52:00:00\");\n\t}\n\n\t@Test\n\tpublic void formatFriendlyTimeSpanByNow() throws ParseException {\n\t\ttry {\n\t\t\tDate now = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse(\"2016-12-11 23:30:00\");\n\n\t\t\tClockUtil.useDummyClock(now);\n\n\t\t\tDate lessOneSecond = DateFormatUtil.DEFAULT_FORMAT.parse(\"2016-12-11 23:29:59.500\");\n\t\t\tassertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(lessOneSecond)).isEqualTo(\"刚刚\");\n\n\t\t\tDate lessOneMinute = DateFormatUtil.DEFAULT_FORMAT.parse(\"2016-12-11 23:29:55.000\");\n\t\t\tassertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(lessOneMinute)).isEqualTo(\"5秒前\");\n\n\t\t\tDate lessOneHour = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse(\"2016-12-11 23:00:00\");\n\t\t\tassertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(lessOneHour)).isEqualTo(\"30分钟前\");\n\n\t\t\tDate today = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse(\"2016-12-11 1:00:00\");\n\t\t\tassertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(today)).isEqualTo(\"今天01:00\");\n\n\t\t\tDate yesterday = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse(\"2016-12-10 1:00:00\");\n\t\t\tassertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(yesterday)).isEqualTo(\"昨天01:00\");\n\n\t\t\tDate threeDayBefore = DateFormatUtil.DEFAULT_ON_SECOND_FORMAT.parse(\"2016-12-09 1:00:00\");\n\t\t\tassertThat(DateFormatUtil.formatFriendlyTimeSpanByNow(threeDayBefore)).isEqualTo(\"2016-12-09\");\n\n\t\t} finally {\n\n\t\t\tClockUtil.useDefaultClock();\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "vjkit/src/test/java/com/vip/vjtools/vjkit/time/DateUtilTest.java",
    "content": "package com.vip.vjtools.vjkit.time;\n\nimport static org.assertj.core.api.Assertions.*;\n\nimport java.util.Date;\n\nimport org.junit.Test;\n\npublic class DateUtilTest {\n\n\t@Test\n\tpublic void isSameDay() {\n\t\tDate date1 = new Date(106, 10, 1);\n\t\tDate date2 = new Date(106, 10, 1, 12, 23, 44);\n\t\tassertThat(DateUtil.isSameDay(date1, date2)).isTrue();\n\n\t\tDate date3 = new Date(106, 10, 1);\n\t\tassertThat(DateUtil.isSameTime(date1, date3)).isTrue();\n\n\t\tDate date5 = new Date(106, 10, 2);\n\t\tassertThat(DateUtil.isSameTime(date1, date5)).isFalse();\n\n\t\tDate date4 = new Date(106, 10, 1, 12, 23, 43);\n\t\tassertThat(DateUtil.isBetween(date3, date1, date2)).isTrue();\n\t\tassertThat(DateUtil.isBetween(date4, date1, date2)).isTrue();\n\n\t\ttry {\n\t\t\tDateUtil.isBetween(null, date1, date2);\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Exception e) {\n\n\t\t}\n\n\t\ttry {\n\t\t\tDateUtil.isBetween(date3, date2, date1);\n\t\t\tfail(\"should fail before\");\n\t\t} catch (Exception e) {\n\n\t\t}\n\n\t\tassertThat(DateUtil.isBetween(date5, date1, date2)).isFalse();\n\t}\n\n\t@Test\n\tpublic void truncateAndCelling() {\n\t\t// Sat Jan 21 12:12:12 CST 2017\n\t\tDate date = new Date(117, 0, 21, 12, 12, 12);\n\n\t\tDate beginYear = new Date(117, 0, 1, 0, 0, 0);\n\t\tDate endYear = new Date(new Date(117, 11, 31, 23, 59, 59).getTime() + 999);\n\t\tDate nextYear = new Date(118, 0, 1, 0, 0, 0);\n\n\t\tDate beginMonth = new Date(117, 0, 1);\n\t\tDate endMonth = new Date(new Date(117, 0, 31, 23, 59, 59).getTime() + 999);\n\t\tDate nextMonth = new Date(117, 1, 1);\n\n\t\tDate beginWeek = new Date(117, 0, 16);\n\t\tDate endWeek = new Date(new Date(117, 0, 22, 23, 59, 59).getTime() + 999);\n\t\tDate nextWeek = new Date(117, 0, 23);\n\n\t\tDate beginDate = new Date(117, 0, 21);\n\t\tDate endDate = new Date(new Date(117, 0, 21, 23, 59, 59).getTime() + 999);\n\t\tDate nextDate = new Date(117, 0, 22);\n\n\t\tDate beginHour = new Date(117, 0, 21, 12, 0, 0);\n\t\tDate endHour = new Date(new Date(117, 0, 21, 12, 59, 59).getTime() + 999);\n\t\tDate nextHour = new Date(117, 0, 21, 13, 0, 0);\n\n\t\tDate beginMinute = new Date(117, 0, 21, 12, 12, 0);\n\t\tDate endMinute = new Date(new Date(117, 0, 21, 12, 12, 59).getTime() + 999);\n\t\tDate nextMinute = new Date(117, 0, 21, 12, 13, 0);\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.beginOfYear(date), beginYear)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.endOfYear(date), endYear)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.nextYear(date), nextYear)).isTrue();\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.beginOfMonth(date), beginMonth)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.endOfMonth(date), endMonth)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.nextMonth(date), nextMonth)).isTrue();\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.beginOfWeek(date), beginWeek)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.endOfWeek(date), endWeek)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.nextWeek(date), nextWeek)).isTrue();\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.beginOfDate(date), beginDate)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.endOfDate(date), endDate)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.nextDate(date), nextDate)).isTrue();\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.beginOfHour(date), beginHour)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.endOfHour(date), endHour)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.nextHour(date), nextHour)).isTrue();\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.beginOfMinute(date), beginMinute)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.endOfMinute(date), endMinute)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.nextMinute(date), nextMinute)).isTrue();\n\t}\n\n\t@Test\n\tpublic void changeDay() {\n\t\tDate date = new Date(106, 10, 1, 12, 23, 44);\n\t\tDate expectDate1 = new Date(106, 10, 3);\n\t\tDate expectDate2 = new Date(106, 9, 31);\n\t\tDate expectDate3 = new Date(106, 11, 1);\n\t\tDate expectDate4 = new Date(106, 7, 1);\n\t\tDate expectDate5 = new Date(106, 10, 1, 13, 23, 44);\n\t\tDate expectDate6 = new Date(106, 10, 1, 10, 23, 44);\n\t\tDate expectDate7 = new Date(106, 10, 1, 12, 24, 44);\n\t\tDate expectDate8 = new Date(106, 10, 1, 12, 21, 44);\n\n\t\tDate expectDate9 = new Date(106, 10, 1, 12, 23, 45);\n\t\tDate expectDate10 = new Date(106, 10, 1, 12, 23, 42);\n\n\t\tDate expectDate11 = new Date(106, 10, 8);\n\t\tDate expectDate12 = new Date(106, 9, 25);\n\n\t\tassertThat(DateUtil.isSameDay(DateUtil.addDays(date, 2), expectDate1)).isTrue();\n\t\tassertThat(DateUtil.isSameDay(DateUtil.subDays(date, 1), expectDate2)).isTrue();\n\n\t\tassertThat(DateUtil.isSameDay(DateUtil.addWeeks(date, 1), expectDate11)).isTrue();\n\t\tassertThat(DateUtil.isSameDay(DateUtil.subWeeks(date, 1), expectDate12)).isTrue();\n\n\t\tassertThat(DateUtil.isSameDay(DateUtil.addMonths(date, 1), expectDate3)).isTrue();\n\t\tassertThat(DateUtil.isSameDay(DateUtil.subMonths(date, 3), expectDate4)).isTrue();\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.addHours(date, 1), expectDate5)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.subHours(date, 2), expectDate6)).isTrue();\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.addMinutes(date, 1), expectDate7)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.subMinutes(date, 2), expectDate8)).isTrue();\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.addSeconds(date, 1), expectDate9)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.subSeconds(date, 2), expectDate10)).isTrue();\n\n\t}\n\n\t@Test\n\tpublic void setDay() {\n\t\tDate date = new Date(116, 10, 1, 10, 10, 1);\n\t\tDate expectedDate = new Date(116, 10, 3);\n\t\tDate expectedDate2 = new Date(116, 10, 1);\n\t\tDate expectedDate3 = new Date(117, 10, 1);\n\t\tDate expectedDate4 = new Date(116, 10, 1, 9, 10, 1);\n\t\tDate expectedDate5 = new Date(116, 10, 1, 10, 9, 1);\n\t\tDate expectedDate6 = new Date(116, 10, 1, 10, 10, 10);\n\n\t\tassertThat(DateUtil.isSameDay(DateUtil.setDays(date, 3), expectedDate)).isTrue();\n\t\tassertThat(DateUtil.isSameDay(DateUtil.setMonths(date, 11), expectedDate2)).isTrue();\n\t\tassertThat(DateUtil.isSameDay(DateUtil.setYears(date, 2017), expectedDate3)).isTrue();\n\n\t\tassertThat(DateUtil.isSameTime(DateUtil.setHours(date, 9), expectedDate4)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.setMinutes(date, 9), expectedDate5)).isTrue();\n\t\tassertThat(DateUtil.isSameTime(DateUtil.setSeconds(date, 10), expectedDate6)).isTrue();\n\n\t}\n\n\t@Test\n\tpublic void getDayOfWeek() {\n\t\t// 2017-01-09\n\t\tDate date = new Date(117, 0, 9);\n\t\tassertThat(DateUtil.getDayOfWeek(date)).isEqualTo(1);\n\n\t\tDate date2 = new Date(117, 0, 15);\n\t\tassertThat(DateUtil.getDayOfWeek(date2)).isEqualTo(7);\n\t}\n\n\t@Test\n\tpublic void isLeapYear() {\n\t\t// 2008-01-09,整除4年, true\n\t\tDate date = new Date(108, 0, 9);\n\t\tassertThat(DateUtil.isLeapYear(date)).isTrue();\n\n\t\t// 2000-01-09,整除400年，true\n\t\tdate = new Date(100, 0, 9);\n\t\tassertThat(DateUtil.isLeapYear(date)).isTrue();\n\n\t\t// 1900-01-09，整除100年，false\n\t\tdate = new Date(0, 0, 9);\n\t\tassertThat(DateUtil.isLeapYear(date)).isFalse();\n\t}\n\n\t@Test\n\tpublic void getXXofXX() {\n\t\t// 2008-02-09, 整除4年, 闰年\n\t\tDate date = new Date(108, 2, 9);\n\t\tassertThat(DateUtil.getMonthLength(date)).isEqualTo(29);\n\n\t\t// 2009-02-09, 整除4年, 非闰年\n\t\tDate date2 = new Date(109, 2, 9);\n\t\tassertThat(DateUtil.getMonthLength(date2)).isEqualTo(28);\n\n\t\tDate date3 = new Date(108, 8, 9);\n\t\tassertThat(DateUtil.getMonthLength(date3)).isEqualTo(31);\n\n\t\tDate date4 = new Date(109, 11, 30);\n\t\tassertThat(DateUtil.getDayOfYear(date4)).isEqualTo(364);\n\n\t\tDate date5 = new Date(117, 0, 12);\n\t\tassertThat(DateUtil.getWeekOfMonth(date5)).isEqualTo(3);\n\t\tassertThat(DateUtil.getWeekOfYear(date5)).isEqualTo(3);\n\t}\n}\n"
  },
  {
    "path": "vjkit/src/test/resources/application.properties",
    "content": "springside.min=1\nspringside.max=10"
  },
  {
    "path": "vjkit/src/test/resources/data_mask.properties",
    "content": "Name=nickName"
  },
  {
    "path": "vjkit/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n\t<appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t<encoder>\n\t\t\t<pattern>%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n\n\t\t\t</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<logger name=\"com.vip\" level=\"INFO\" />\n\n\t<root level=\"WARN\">\n\t\t<appender-ref ref=\"console\" />\n\t</root>\n</configuration>"
  },
  {
    "path": "vjkit/src/test/resources/test.txt",
    "content": "ABCDEFG\nABC"
  },
  {
    "path": "vjmap/README.md",
    "content": "# 1. 概述\n\n分代版的jmap（新生代，存活区，老生代），是排查内存缓慢泄露，老生代增长过快原因的利器。因为`jmap -histo PID`  打印的是整个Heap的对象统计信息，而为了定位上面的问题，我们需要专门查看OldGen对象，和Survivor区大龄对象的工具。\n\nvjmap的原始思路来源于R大的[TBJMap](https://github.com/alibaba/TBJMap) ，翻新后支持JDK8，支持Survivor区大龄对象过滤，以及大天秤对输出结果不要看歪脖子的执着。\n\n\n这里有一篇实战：[【唯实践】JVM老生代增长过快问题排查](https://mp.weixin.qq.com/s/6cJ5JuEgEWmMBzJFBDsSMg)，最后定位到是Jedis的锅。\n\n\n注意：因为vjmap的原理，只支持CMS和ParallelGC，不支持G1。\n\n\n# 2.使用说明\n\n[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)\n\n# 2.1 注意事项\n\n注意：vjmap在执行过程中，会完全停止应用一段时间，必须摘流量执行！！！！\n\n1. JAVA_HOME定义\n\nvjmap使用的java为JAVA_HOME/bin/java, 需要至少JDK7，且与目标应用的JVM使用相同的JDK大版本。\n\nvjmap需要依赖JAVA_HOME/lib/sa-jdi.jar\n\nJAVA_HOME的定位，通过读取环境变量JAVA_HOME，如果没有定义，则尝试通过\"which java\"定位java从而获得相对路径。\n\n2. 权限说明\n \n需要root权限 (sudo -E vjmap.sh ...，)，权限与jmap -heap pid相同.\n\n如果无法联通进程时，可尝试执行jstack -F pid, jmap -heap pid 自行比对。\n\n如果在容器中运行，需要打开ptrace权限。\n\n\n    \n## 2.2 常用指令\n\n针对活着的进程，PID为进程号\n\n```\n// 打印整个堆中对象的统计信息，按对象的total size排序:\n./vjmap.sh -all PID > /tmp/histo.log\n\n// 推荐，打印老年代的对象统计信息，按对象的oldgen size排序，比-all快很多，暂时只支持CMS:\n./vjmap.sh -old PID > /tmp/histo-old.log\n\n// 推荐，打印Survivor区的对象统计信息，默认age>=3\n./vjmap.sh -sur PID > /tmp/histo-sur.log\n\n// 推荐，打印Survivor区的对象统计信息，查看age>=4的对象\n./vjmap.sh -sur:minage=4 PID > /tmp/histo-sur.log\n\n// 推荐，打印Survivor区的对象统计信息，单独查看age＝4的对象:\n./vjmap.sh -sur:age=4 PID > /tmp/histo-sur.log\n```\n\n\n针对CoreDump文件\n\n```\n./vjmap.sh -old ${path_to_java} ${path_to_coredump}\n\n```\n\n## 2.3 仅输出存活的对象\n\n原理为正式统计前先执行一次full gc\n\n```\n./vjmap.sh -old:live PID > /tmp/histo-old－live.log\n```\n\n## 2.4 过滤对象大小，不显示过小的对象:\n\n```\n// 按对象的oldgen size进行过滤，只打印OldGen占用超过1K的数据\n./vjmap.sh -old:minsize=1024 PID > /tmp/histo-old.log\n```\n\n## 2.5 按class name排序，配合大小过滤， 生成用于两次结果比较的报表:\n\n```\n./vjmap.sh -all:minsize=1024,byname PID > /tmp/histo.log\n```\n\n\n## 2.6 其他注意事项\n\n\n1. 意外停止\n\nvjmap的运行需要一段时间，如果中途需要停止执行，请使用ctrl＋c，或者kill vjmap的PID，让vjmap从目标进程退出。\n\n如果错用了kill -9 ，目标java进程会保持在阻塞状态不再工作，此时必须执行两次 kill -SIGCONT $目标进程PID，重新唤醒目标java进程。\n\n2. OldGen碎片\n\n如果很久没都有进行过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 等。\n\n# 3.输出示例\n\n\n## 3.1 Survivor区年龄大于N的对象统计\n\n```\nSurvivor Object Histogram:\n\n #num  #count     #bytes #Class description\n-----------------------------------------------------------------------------------\n   1:      37         1k io.netty.buffer.PoolThreadCache$MemoryRegionCache$Entry\n   2:       2         64 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node\nTotal: 39/    1k over age 2\n\nHeap traversal took 1.3 seconds.\n```\n\n# 4. 使用Eclipse MAT进一步分析\n\n如果只依靠对象统计信息，不足以定位问题，需要使用完整HeapDump，计算对象关联关系来进一步分析时，可以在MAT中使用OQL过滤出老生代的对象。\n\n假设，OldGen地址范围是\"0xfbd4c000\" ～ \"0xfce94050\"\n\n```\nSELECT * FROM INSTANCEOF java.lang.Object t WHERE toHex(t.@objectAddress) <= \"0xfce94050\" AND toHex(t.@objectAddress) >= \"0xfbd4c000\"\n```\n\n注意，MAT要在偏好设置中 勾选 \"Keep unreachable object\"\n\n用如下方式可获得老生代地址：\n\n第一种方式是在启动参数增加 -XX:+PrintHeapAtGC，每次GC都打印地址\n\n第二种方式是使用vjmap的命令，在-old, -sur, -address 中，都会打印出该区间的地址\n\n第三种方式，使用vjmap的address命令，快速打印各代地址，不会造成过长时间停顿\n\n```\n./vjmap.sh -address PID\n``` \n\n输出如下：\n```\n  eden [0x0000000119000000,0x0000000119c4a258,0x0000000121880000) space capacity = 143130624, 9.003395387977907 used\n  from [0x0000000121880000,0x0000000121880000,0x0000000122990000) space capacity = 17891328, 0.0 used\n  to   [0x0000000122990000,0x0000000122990000,0x0000000123aa0000) space capacity = 17891328, 0.0 used\nconcurrent mark-sweep generation\nfree-list-space[ 0x0000000123aa0000 , 0x0000000139000000 ) space capacity = 357957632 used(4%)= 17024696 free= 340932936\n```\n\n上例中的 0x123aa0000  即为OldGen的下界。 注意OQL中使用时要把数值前的那串0去掉。\n\n\n# 5. 打印加载的Class列表\n\n```\n./vjmap.sh -class PID\n``` \n\n为了兼容JDK8，不再打印Class所在的Jar包\n\n\n\n# 6. 与TBJMap的对比\n\n* 兼容JDK8\n* 新功能：Survivor区 age大于N的对象统计\n* 新功能：打印各分代的地址区间，用于MAT进一步分析\n* 性能提升：直接访问Survivor或OldGen区，而不是以Heap Visitor回调的方式访问整个Heap\n* 新配置项：按对象的占用内存进行过滤，不显示过小的对象\n* 新配置项：按对象的名称进行排序，可用于两次统计结果的比对\n* 输出改进：报表数字的单位化(k,m,g)与对齐，OldGen报表默认按对象在OldGen的大小排序\n\n```"
  },
  {
    "path": "vjmap/README_EN.md",
    "content": "# VJMap\n\nVJMap 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.\n\n# 1. Introduction\n\nJmap 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.\n\nInitially inspired by [tbjmap](https://github.com/alibaba/TBJMap), JDK8 compatibility was added as well as query on aged \nsurvivor objects.\n\n**[Note]**: G1 is unsupported.  Use it with CMS and ParallelGC only.\n\n# 2. Getting Started\n\n[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)\n\n**[Important]**: VJMap DOES cause stop-of-the-world of the target app. Make sure the target app is isolated from user \naccess before you start using VJMap in production.\n\nRun VJMap under **the same user who started the target process**. If access errors are still met, try again with \nroot user.\n\nVJMap may take quite some time to finish. Use `kill <PID_OF_VJMap>` to allow for a graceful exit. If `kill -9 <PID_OF_VJMap>` \nis mistakenly issued to the VJMap process, the target app will end up in blocked state, in which case you will have to \nexecute `kill -18 <PID_OF_TARGET_APP>` TWICE to awaken the target app.\n\n## 2.1 Commands\n\n```\n// Prints object stats of all gens, ordered by their respective size in total.\n./vjmap.sh -all PID > /tmp/histo.log\n\n// Prints oldgen object stats, ordered by size in OldGen. Only CMS is supported for this option. \n./vjmap.sh -old PID > /tmp/histo-old.log\n\n\n// Prints survivor objects over the age of 3.\n./vjmap.sh -sur PID > /tmp/histo-sur.log\n\n\n// Prints survivor objects over the age of 10, as desinated by the argument -sur:minage=10\n// When the promotion threshold -XX:MaxTenuringThreshold is lifted, objects with a high age value will be bound \nfor the CMS oldgen\n./vjmap.sh -sur:minage=10 PID > /tmp/histo-sur.log\n```\n\n> PID is the process ID of target java application\n\n## 2.2 Display Larger Objects, Leaving Smaller Ones Out\n\n```\n// Shows objects with sizes over 1KB over the whole heap\n./vjmap.sh -all:minsize=1024 PID > /tmp/histo.log\n\n// shows objects with sizes over 1KB in OldGen specifically \n./vjmap.sh -old:minsize=1024 PID > /tmp/histo-old.log\n\n// shows objects with sizes over 1KB in survivor space \n./vjmap.sh -sur:minsize=1024 PID > /tmp/histo-sur.log\n```\n\n## 2.3 Order by Classname and Filter by Size for Periodic Comparisons\n\n```\n./vjmap.sh -all:minsize=1024,byname PID > /tmp/histo.log\n\n./vjmap.sh -old:minsize=1024,byname PID > /tmp/histo-old.log\n\n./vjmap.sh -sur:minsize=1024,byname PID > /tmp/histo-sur.log\n```\n\n## 2.4 Prints object stats of old gen, live objects only:\n\n```\n./vjmap.sh -old:live PID > /tmp/histo-old.log\n```\n\n# 3.Outputs\n\n## 3.1 Count Survivor Objects over the Age of 3.\n\n```\nSurvivor Object Histogram:\n\n #num  #count     #bytes #Class description\n-----------------------------------------------------------------------------------\n   1:      37         1k io.netty.buffer.PoolThreadCache$MemoryRegionCache$Entry\n   2:       2         64 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node\nTotal: 39/    1k over age 2\n\nHeap traversal took 1.3 seconds.\n```\n\n\n\n# 4. Eclipse MAT\n\n如果只依靠对象统计信息，不足以定位问题，需要使用完整HeapDump，计算对象关联关系来进一步分析时，可以在MAT中使用OQL过滤出老生代的对象。\n\n假设，OldGen地址范围是\"0xfbd4c000\" ～ \"0xfce94050\"\n\n```\nSELECT * FROM INSTANCEOF java.lang.Object t WHERE (toHex(t.@objectAddress) >= \"0xfbd4c000\" AND toHex(t.@objectAddress) <= \"0xfce94050\")\n```\n\n用如下方式可获得老生代地址：\n\n第一种方式是在启动参数增加 -XX:+PrintHeapAtGC\n\n第二种方式是使用vjmap的命令，在-old, -sur, -address 中，都会打印出区间的地址。 \n\n```\n./vjmap.sh -address PID\n\n``` \n\n输出如下：\n```\n  eden [0x0000000119000000,0x0000000119c4a258,0x0000000121880000) space capacity = 143130624, 9.003395387977907 used\n  from [0x0000000121880000,0x0000000121880000,0x0000000122990000) space capacity = 17891328, 0.0 used\n  to   [0x0000000122990000,0x0000000122990000,0x0000000123aa0000) space capacity = 17891328, 0.0 used\nconcurrent mark-sweep generation\nfree-list-space[ 0x0000000123aa0000 , 0x0000000139000000 ) space capacity = 357957632 used(4%)= 17024696 free= 340932936\n```\n\n\n# 5. Enhancements over TBJMap\n* Added JDK8 Support.\n* Added Display: survivor objects over the specified age.\n* Performance Boost: by accessing Survivor and OldGen directly instead of by accessing the whole heap with Heap Visitor callbacks.\n* New config Arg: order objects by size and leave out small ones.\n* New Config Arg: order objects by name for periodic comparison.\n* Reading Friendliness: output by the unit of (k, m, g) and fix alignment, order objects in OldGen by size in OldGen view by default. \n"
  },
  {
    "path": "vjmap/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<groupId>com.vip.vjtools</groupId>\n\t<artifactId>vjmap</artifactId>\n\t<version>1.0.9-SNAPSHOT</version>\n\t<name>vjmap</name>\n\t<description>jmap with per generation object stats</description>\n\n\t<properties>\n\t\t<!-- change it to point to your sa-jdi.jar from your JDK -->\n\t\t<sajdijar>${java.home}/../lib/sa-jdi.jar</sajdijar>\n\t\t<toolsjar>${java.home}/../lib/tools.jar</toolsjar>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<java.version>1.7</java.version>\n\t\t<maven.compiler.source>${java.version}</maven.compiler.source>\n\t\t<maven.compiler.target>${java.version}</maven.compiler.target>\n\t</properties>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>com.sun</groupId>\n\t\t\t<artifactId>sa-jdi</artifactId>\n\t\t\t<version>1.0</version>\n\t\t\t<scope>system</scope>\n\t\t\t<systemPath>${sajdijar}</systemPath>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.sun</groupId>\n\t\t\t<artifactId>tools</artifactId>\n\t\t\t<version>${java.version}</version>\n\t\t\t<scope>system</scope>\n\t\t\t<systemPath>${toolsjar}</systemPath>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-assembly-plugin</artifactId>\n\t\t\t\t<version>2.6</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<descriptors>\n\t\t\t\t\t\t<descriptor>src/main/assembly/distribution.xml</descriptor>\n\t\t\t\t\t</descriptors>\n\t\t\t\t\t<appendAssemblyId>false</appendAssemblyId>\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>assembly</id>\n\t\t\t\t\t\t<phase>package</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>single</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>release</id>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<!-- javadoc attach plugin -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-javadoc-plugin</artifactId>\n\t\t\t\t\t\t<version>2.10.4</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<failOnError>false</failOnError>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-javadocs</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<!-- source attach plugin -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-source-plugin</artifactId>\n\t\t\t\t\t\t<version>3.0.1</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-sources</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-release-plugin</artifactId>\n\t\t\t\t\t\t<version>2.5.3</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<tagNameFormat>v.@{project.version}</tagNameFormat>\n\t\t\t\t\t\t </configuration>\n\t\t\t\t\t</plugin>\n\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-gpg-plugin</artifactId>\n\t\t\t\t\t\t<version>1.6</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>sign-artifacts</id>\n\t\t\t\t\t\t\t\t<phase>verify</phase>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>sign</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t</profiles>\n\n\t<distributionManagement>\n\t\t<snapshotRepository>\n\t\t\t<id>sonatype-nexus-snapshots</id>\n\t\t\t<url>https://oss.sonatype.org/content/repositories/snapshots</url>\n\t\t</snapshotRepository>\n\t\t<repository>\n\t\t\t<id>sonatype-nexus-releases</id>\n\t\t\t<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>\n\t\t</repository>\n\t</distributionManagement>\n\n\t<url>https://github.com/vipshop/vjtools</url>\n\n\t<licenses>\n\t\t<license>\n\t\t\t<name>Apache License 2.0</name>\n\t\t\t<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n\t\t\t<distribution>repo</distribution>\n\t\t</license>\n\t</licenses>\n\n\t<scm>\n\t\t<connection>scm:git:https://github.com/vipshop/vjtools.git</connection>\n\t\t<developerConnection>scm:git:https://github.com/vipshop/vjtools.git</developerConnection>\n\t\t<url>https://github.com/vipshop/vjtools</url>\n\t  <tag>v.1.0.2</tag>\n  </scm>\n\n\t<developers>\n\t\t<!--not noly me, write a name here just for sonatype requirement -->\n\t\t<developer>\n\t\t\t<id>calvin</id>\n\t\t\t<name>Calvin Xiao</name>\n\t\t\t<email>calvin.xiao at vipshop.com</email>\n\t\t\t<roles>\n\t\t\t\t<role>developer</role>\n\t\t\t</roles>\n\t\t\t<timezone>+8</timezone>\n\t\t</developer>\n\t</developers>\n</project>\n"
  },
  {
    "path": "vjmap/src/main/assembly/distribution.xml",
    "content": "<assembly\n\txmlns=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3\"\n\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd\">\n\n\t<id>zip</id>\n\t<formats>\n\t\t<format>zip</format>\n\t</formats>\n\t<baseDirectory>${project.artifactId}</baseDirectory>\n\t\n\t<dependencySets>\n\t\t<dependencySet>\n\t\t\t<outputDirectory>/</outputDirectory>\n\t\t\t<includes>\n\t\t\t\t<include>com.vip.vjtools:vjmap:*</include>\n\t\t\t</includes>\n\t\t\t<outputFileNameMapping>${artifact.artifactId}.${artifact.extension}</outputFileNameMapping>\n\t\t</dependencySet>\n\t</dependencySets>\n\t\n\t<files>\n\t\t<file>\n\t\t\t<source>src/main/assembly/vjmap.sh</source>\n\t\t\t<outputDirectory></outputDirectory>\n\t\t\t<fileMode>0755</fileMode>\n\t\t\t<lineEnding>unix</lineEnding>\n\t\t</file>\n\t\t<file>\n\t\t\t<source>src/main/assembly/vjmap.bat</source>\n\t\t\t<outputDirectory></outputDirectory>\n\t\t\t<lineEnding>windows</lineEnding>\n\t\t</file>\n\t\t<file>\n\t\t\t<source>README.md</source>\n\t\t\t<outputDirectory></outputDirectory>\n\t\t\t<destName>README.md</destName>\n\t\t\t<lineEnding>unix</lineEnding>\n\t\t</file>\n\t</files>\n</assembly>\n"
  },
  {
    "path": "vjmap/src/main/assembly/vjmap.bat",
    "content": "@echo off\n\nrem check java\nif \"%JAVA_HOME%\" == \"\" goto noJavaHome\n\n\necho WARNING!! STW(Stop-The-World) will be performed on your Java process, if this is NOT wanted, type 'Ctrl+C' to exit.\n\n\nset DIR=%~dp0\nset JAVA_OPTS=-Xms512m -Xmx512m -Xmn400m -XX:+TieredCompilation -XX:+UseConcMarkSweepGC -Xverify:none -XX:AutoBoxCacheMax=20000\n\n\n\"%JAVA_HOME%\\bin\\java\" %JAVA_OPTS% -classpath \"%DIR%\\vjmap.jar;%JAVA_HOME%\\lib\\sa-jdi.jar\" com.vip.vjtools.vjmap.VJMap %*\ngoto end\n\n:noJavaHome\n  echo Please set JAVA_HOME before running this script\n  goto end\n:end\n\npause"
  },
  {
    "path": "vjmap/src/main/assembly/vjmap.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$JAVA_HOME\" ] ; then\n\techo \"JAVA_HOME env doesn't exist, try to find the location of java\"\n\tJAVA_HOME=`readlink -f \\`which java 2>/dev/null\\` 2>/dev/null | \\\n\tsed 's/\\jre\\/bin\\/java//' | sed 's/\\/bin\\/java//'`\nfi\n\nif [ ! -d \"$JAVA_HOME\" ] ; then\n\techo \"Please set JAVA_HOME env before run this script\"\n\texit 1\nfi\n\nSAJDI_PATH=$JAVA_HOME/lib/sa-jdi.jar\n\nif [ ! -f \"$SAJDI_PATH\" ] ; then\n\techo \"$SAJDI_PATH doesn't exist !\" >&2\n\texit 1\nfi\n\nTOOLS_PATH=$JAVA_HOME/lib/tools.jar\n\nif [ ! -f \"$TOOLS_PATH\" ] ; then\n\techo \"$TOOLS_PATH doesn't exist !\" >&2\n\texit 1\nfi\n\necho -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\"\n\nDIR=$( cd $(dirname $0) ; pwd -P )\nJAVA_OPTS=\"-Xms512m -Xmx512m -Xmn400m -XX:+UseConcMarkSweepGC -XX:+TieredCompilation -Xverify:none -XX:AutoBoxCacheMax=20000\"\n\n\n\"$JAVA_HOME\"/bin/java $JAVA_OPTS -classpath $DIR/vjmap.jar:$SAJDI_PATH:$TOOLS_PATH com.vip.vjtools.vjmap.VJMap $*"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/ClassStats.java",
    "content": "package com.vip.vjtools.vjmap;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.PrintStream;\nimport java.util.Comparator;\n\nimport sun.jvm.hotspot.oops.ArrayKlass;\nimport sun.jvm.hotspot.oops.InstanceKlass;\nimport sun.jvm.hotspot.oops.Klass;\nimport sun.jvm.hotspot.oops.ObjArrayKlass;\nimport sun.jvm.hotspot.oops.TypeArrayKlass;\n\npublic class ClassStats {\n\n\tprivate Klass klass;\n\tprivate String description;\n\n\tpublic long count;\n\tpublic long size;\n\tpublic long edenCount;\n\tpublic long edenSize;\n\tpublic long survivorCount;\n\tpublic long survivorSize;\n\tpublic long oldCount;\n\tpublic long oldSize;\n\n\tpublic ClassStats(Klass k) {\n\t\tthis.klass = k;\n\t\tdescription = initDescription();\n\t}\n\n\tpublic String getDescription() {\n\t\treturn description;\n\t}\n\n\t/**\n\t * 参考 JDK8 sun.jvm.hotspot.oops.ObjectHistogramElement\n\t * \n\t * StringBuffer->StringBuilder\n\t */\n\tpublic String initDescription() {\n\t\tKlass k = klass;\n\t\tif (k instanceof InstanceKlass) {\n\t\t\treturn k.getName().asString().replace('/', '.');\n\t\t} else if (k instanceof ArrayKlass) {\n\t\t\tArrayKlass ak = (ArrayKlass) k;\n\t\t\tif (k instanceof TypeArrayKlass) {\n\t\t\t\tTypeArrayKlass tak = (TypeArrayKlass) ak;\n\t\t\t\treturn tak.getElementTypeName() + \"[]\";\n\t\t\t} else if (k instanceof ObjArrayKlass) {\n\t\t\t\tObjArrayKlass oak = (ObjArrayKlass) ak;\n\t\t\t\tKlass bottom = oak.getBottomKlass();\n\t\t\t\tint dim = (int) oak.getDimension();\n\t\t\t\tStringBuilder buf = new StringBuilder(64);\n\t\t\t\tif (bottom instanceof TypeArrayKlass) {\n\t\t\t\t\tbuf.append(((TypeArrayKlass) bottom).getElementTypeName());\n\t\t\t\t} else if (bottom instanceof InstanceKlass) {\n\t\t\t\t\tbuf.append(bottom.getName().asString().replace('/', '.'));\n\t\t\t\t} else {\n\t\t\t\t\tthrow new RuntimeException(\"should not reach here\");\n\t\t\t\t}\n\t\t\t\tfor (int i = 0; i < dim; i++) {\n\t\t\t\t\tbuf.append(\"[]\");\n\t\t\t\t}\n\t\t\t\treturn buf.toString();\n\t\t\t}\n\t\t}\n\t\treturn getInternalName(k);\n\t}\n\n\t/**\n\t * 参考 sun.jvm.hotspot.oops.ObjectHistogramElement\n\t */\n\tprivate String getInternalName(Klass k) {\n\t\tByteArrayOutputStream bos = new ByteArrayOutputStream();\n\t\tklass.printValueOn(new PrintStream(bos));\n\t\t// '*' is used to denote VM internal klasses.\n\t\treturn \"* \" + bos.toString();\n\t}\n\n\tpublic Klass getKlass() {\n\t\treturn this.klass;\n\t}\n\n\tpublic long getCount() {\n\t\treturn this.count;\n\t}\n\n\tpublic long getSize() {\n\t\treturn this.size;\n\t}\n\n\tpublic long getOldCount() {\n\t\treturn this.oldCount;\n\t}\n\n\tpublic long getOldSize() {\n\t\treturn this.oldSize;\n\t}\n\n\tpublic long getSurvivorCount() {\n\t\treturn survivorCount;\n\t}\n\n\tpublic long getSurvivorSize() {\n\t\treturn survivorSize;\n\t}\n\n\tpublic long getEdenCount() {\n\t\treturn edenCount;\n\t}\n\n\tpublic long getEdenSize() {\n\t\treturn edenSize;\n\t}\n\n\tpublic static Comparator<ClassStats> TOTAL_SIZE_COMPARATOR = new Comparator<ClassStats>() {\n\t\t@Override\n\t\tpublic int compare(ClassStats o1, ClassStats o2) {\n\t\t\treturn (int) (o2.getSize() - o1.getSize());\n\t\t}\n\t};\n\n\tpublic static Comparator<ClassStats> OLD_SIZE_COMPARATOR = new Comparator<ClassStats>() {\n\t\t@Override\n\t\tpublic int compare(ClassStats o1, ClassStats o2) {\n\t\t\treturn (int) (o2.getOldSize() - o1.getOldSize());\n\t\t}\n\t};\n\n\tpublic static Comparator<ClassStats> SUR_SIZE_COMPARATOR = new Comparator<ClassStats>() {\n\t\t@Override\n\t\tpublic int compare(ClassStats o1, ClassStats o2) {\n\t\t\treturn (int) (o2.getSurvivorSize() - o1.getSurvivorSize());\n\t\t}\n\t};\n\n\tpublic static Comparator<ClassStats> NAME_COMPARATOR = new Comparator<ClassStats>() {\n\t\t@Override\n\t\tpublic int compare(ClassStats o1, ClassStats o2) {\n\t\t\treturn o1.getDescription().compareTo(o2.getDescription());\n\t\t}\n\t};\n\n}"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/ResultPrinter.java",
    "content": "package com.vip.vjtools.vjmap;\n\nimport java.io.PrintStream;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport com.vip.vjtools.vjmap.utils.FormatUtils;\n\npublic class ResultPrinter {\n\n\t/**\n\t * 打印所有新老生代的结果\n\t */\n\tpublic void printAllGens(PrintStream tty, List<ClassStats> list, boolean orderByName, long minSize) {\n\n\t\tif (orderByName) {\n\t\t\tCollections.sort(list, ClassStats.NAME_COMPARATOR);\n\t\t} else {\n\t\t\tCollections.sort(list, ClassStats.TOTAL_SIZE_COMPARATOR);\n\t\t}\n\t\ttty.println(\"\\nObject Histogram:\");\n\t\ttty.println();\n\t\ttty.printf(\"%6s %15s %15s %15s %15s  %s%n\", \"#num\", \"#all\", \"#eden\", \"#from\", \"#old\", \"#class description\");\n\n\t\ttty.println(\n\t\t\t\t\"--------------------------------------------------------------------------------------------------\");\n\n\t\tIterator<ClassStats> iterator = list.listIterator();\n\t\tint num = 0;\n\t\tint totalCount = 0;\n\t\tint totalSize = 0;\n\t\twhile (iterator.hasNext()) {\n\t\t\tClassStats classStats = iterator.next();\n\t\t\tif (classStats.getSize() > minSize) {\n\t\t\t\tnum++;\n\t\t\t\ttotalCount = (int) (totalCount + classStats.getCount());\n\t\t\t\ttotalSize = (int) (totalSize + classStats.getSize());\n\n\t\t\t\ttty.printf(\"%5d: %7d/%7s %7d/%7s %7d/%7s %7d/%7s  %s%n\", num, classStats.getCount(),\n\t\t\t\t\t\tFormatUtils.toFloatUnit(classStats.getSize()), classStats.getEdenCount(),\n\t\t\t\t\t\tFormatUtils.toFloatUnit(classStats.getEdenSize()), classStats.getSurvivorCount(),\n\t\t\t\t\t\tFormatUtils.toFloatUnit(classStats.getSurvivorSize()), classStats.getOldCount(),\n\t\t\t\t\t\tFormatUtils.toFloatUnit(classStats.getOldSize()), classStats.getDescription());\n\n\t\t\t}\n\t\t}\n\t\ttty.printf(\" Total: %7d/%7s , minSize=%d%n\", totalCount, FormatUtils.toFloatUnit(totalSize), minSize);\n\t}\n\n\t/**\n\t * 打印只包含存活区的结果\n\t */\n\tpublic void printSurvivor(PrintStream tty, List<ClassStats> list, boolean orderByName, long minSize, int age,\n\t\t\tint minAge) {\n\t\tif (orderByName) {\n\t\t\tCollections.sort(list, ClassStats.NAME_COMPARATOR);\n\t\t} else {\n\t\t\tCollections.sort(list, ClassStats.SUR_SIZE_COMPARATOR);\n\t\t}\n\n\t\ttty.println(\"\\nSurvivor Object Histogram:\\n\");\n\t\ttty.printf(\"%6s %7s %7s  %s%n\", \"#num\", \"#count\", \"#bytes\", \"#Class description\");\n\t\ttty.println(\"-----------------------------------------------------------------------------------\");\n\n\t\tIterator<ClassStats> iterator = list.listIterator();\n\t\tint num = 0;\n\t\tlong totalSurCount = 0;\n\t\tlong totalSurSize = 0;\n\t\twhile (iterator.hasNext()) {\n\t\t\tClassStats classStats = iterator.next();\n\t\t\tif (classStats.getSurvivorSize() > minSize) {\n\t\t\t\ttotalSurCount = totalSurCount + classStats.getSurvivorCount();\n\t\t\t\ttotalSurSize = totalSurSize + classStats.getSurvivorSize();\n\t\t\t\tnum++;\n\t\t\t\ttty.printf(\"%5d: %7d %7s  %s%n\", num, classStats.getSurvivorCount(),\n\t\t\t\t\t\tFormatUtils.toFloatUnit(classStats.getSurvivorSize()), classStats.getDescription());\n\t\t\t}\n\t\t}\n\n\t\tif (age != -1) {\n\t\t\ttty.printf(\" Total: %7d %7s, age=%d, minSize=%d%n\", totalSurCount, FormatUtils.toFloatUnit(totalSurSize),\n\t\t\t\t\tage, minSize);\n\t\t} else {\n\t\t\ttty.printf(\" Total: %7d %7s, minAge=%d, minSize=%d%n\", totalSurCount, FormatUtils.toFloatUnit(totalSurSize),\n\t\t\t\t\tminAge, minSize);\n\t\t}\n\t}\n\n\t/**\n\t * 打印只包含老生代的结果\n\t */\n\tpublic void printOldGen(PrintStream tty, List<ClassStats> list, boolean orderByName, long minSize) {\n\t\tif (orderByName) {\n\t\t\tCollections.sort(list, ClassStats.NAME_COMPARATOR);\n\t\t} else {\n\t\t\tCollections.sort(list, ClassStats.OLD_SIZE_COMPARATOR);\n\t\t}\n\n\t\ttty.println(\"\\nOldGen Object Histogram:\\n\");\n\t\ttty.printf(\"%6s %7s %7s  %s%n\", \"#num\", \"#count\", \"#bytes\", \"#class description\");\n\t\ttty.println(\"-----------------------------------------------------------------------------------\");\n\n\t\tIterator<ClassStats> iterator = list.listIterator();\n\t\tint num = 0;\n\t\tlong totalOldCount = 0;\n\t\tlong totalOldSize = 0;\n\t\twhile (iterator.hasNext()) {\n\t\t\tClassStats classStats = iterator.next();\n\t\t\tif (classStats.getOldSize() > minSize) {\n\t\t\t\ttotalOldCount = totalOldCount + classStats.getOldCount();\n\t\t\t\ttotalOldSize = totalOldSize + classStats.getOldSize();\n\t\t\t\tnum++;\n\t\t\t\ttty.printf(\"%5d: %7s %7s  %s%n\", num, classStats.getOldCount(),\n\t\t\t\t\t\tFormatUtils.toFloatUnit(classStats.getOldSize()), classStats.getDescription());\n\t\t\t}\n\t\t}\n\n\t\ttty.printf(\" Total: %7d %7s, minSize=%d%n\", totalOldCount, FormatUtils.toFloatUnit(totalOldSize), minSize);\n\t}\n\n}\n"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/VJMap.java",
    "content": "package com.vip.vjtools.vjmap;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintStream;\nimport java.util.List;\n\nimport com.sun.tools.attach.VirtualMachine;\nimport com.vip.vjtools.vjmap.oops.GenAddressAccessor;\nimport com.vip.vjtools.vjmap.oops.HeapHistogramVisitor;\nimport com.vip.vjtools.vjmap.oops.HeapUtils;\nimport com.vip.vjtools.vjmap.oops.LoadedClassAccessor;\nimport com.vip.vjtools.vjmap.oops.OldgenAccessor;\nimport com.vip.vjtools.vjmap.oops.SurvivorAccessor;\nimport com.vip.vjtools.vjmap.utils.TimeController.TimeoutException;\n\nimport sun.jvm.hotspot.HotSpotAgent;\nimport sun.jvm.hotspot.oops.ObjectHeap;\nimport sun.jvm.hotspot.runtime.VM;\nimport sun.tools.attach.HotSpotVirtualMachine;;\n\npublic class VJMap {\n\n\tpublic static final String VERSION = \"1.0.9\";\n\n\tprivate static PrintStream tty = System.out;\n\t// 用于ctrl－C退出时仍然打印结果\n\tprivate static OldGenProcessor oldGenProcessor;\n\tprivate static HeapProcessor heapProcessor;\n\n\tpublic static void runHeapVisitor(int pid, boolean orderByName, long minSize) {\n\t\tObjectHeap heap = VM.getVM().getObjectHeap();\n\t\theapProcessor = new HeapProcessor(orderByName, minSize);\n\n\t\ttty.println(\"Iterating over heap. This may take a while...\");\n\t\ttty.println(\"Geting live regions...\");\n\n\t\theap.iterate(heapProcessor.visitor);\n\n\t\theapProcessor.printResult();\n\t\theapProcessor = null;\n\t}\n\n\tpublic static class HeapProcessor {\n\t\tHeapHistogramVisitor visitor = new HeapHistogramVisitor();\n\t\tboolean orderByName;\n\t\tlong minSize;\n\n\t\tpublic HeapProcessor(boolean orderByName, long minSize) {\n\t\t\tthis.orderByName = orderByName;\n\t\t\tthis.minSize = minSize;\n\t\t}\n\n\t\tpublic void printResult() {\n\t\t\tList<ClassStats> list = HeapUtils.getClassStatsList(visitor.getClassStatsMap());\n\t\t\tResultPrinter resultPrinter = new ResultPrinter();\n\t\t\tresultPrinter.printAllGens(tty, list, orderByName, minSize);\n\t\t}\n\t}\n\n\tpublic static void runSurviorAccessor(int age, int minAge, boolean orderByName, long minSize) {\n\t\tSurvivorAccessor accessor = new SurvivorAccessor();\n\n\t\ttty.println(\"Iterating over survivor area. This may take a while...\");\n\t\tList<ClassStats> list = accessor.caculateHistogram(age, minAge);\n\n\t\tResultPrinter resultPrinter = new ResultPrinter();\n\t\tresultPrinter.printSurvivor(tty, list, orderByName, minSize, age, minAge);\n\t}\n\n\tpublic static void runOldGenAccessor(boolean orderByName, long minSize) {\n\t\toldGenProcessor = new OldGenProcessor(orderByName, minSize);\n\t\ttty.println(\"Iterating over oldgen area. This may take a while...\");\n\t\toldGenProcessor.accessor.caculateHistogram();\n\t\toldGenProcessor.printResult();\n\t\toldGenProcessor = null;\n\t}\n\n\tpublic static class OldGenProcessor {\n\t\tOldgenAccessor accessor = new OldgenAccessor();\n\t\tboolean orderByName;\n\t\tlong minSize;\n\n\t\tpublic OldGenProcessor(boolean orderByName, long minSize) {\n\t\t\tthis.orderByName = orderByName;\n\t\t\tthis.minSize = minSize;\n\t\t}\n\n\t\tpublic void printResult() {\n\t\t\tList<ClassStats> list = HeapUtils.getClassStatsList(accessor.getClassStatsMap());\n\t\t\tResultPrinter resultPrinter = new ResultPrinter();\n\t\t\tresultPrinter.printOldGen(tty, list, orderByName, minSize);\n\t\t}\n\t}\n\n\tpublic static void printGenAddress() {\n\t\tGenAddressAccessor accessor = new GenAddressAccessor();\n\t\taccessor.printHeapAddress();\n\t}\n\n\tpublic static void printLoadedClass() {\n\t\tLoadedClassAccessor accessor = new LoadedClassAccessor();\n\t\taccessor.pringLoadedClass();\n\t}\n\n\tpublic static void main(String[] args) {\n\t\t// 分析参数\n\t\tboolean orderByName = false;\n\t\tlong minSize = -1;\n\t\tint minAge = 2;\n\t\tint age = -1;\n\t\tboolean live = false;\n\t\t// boolean dead = false;\n\t\tif (!(args.length == 2 || args.length == 3)) {\n\t\t\tprintHelp();\n\t\t\treturn;\n\t\t}\n\n\t\tString modeFlag = args[0];\n\n\t\tString[] modeFlags = modeFlag.split(\":\");\n\t\tif (modeFlags.length > 1) {\n\t\t\tString[] addtionalFlags = modeFlags[1].split(\",\");\n\t\t\tfor (String addtionalFlag : addtionalFlags) {\n\t\t\t\tif (\"byname\".equalsIgnoreCase(addtionalFlag)) {\n\t\t\t\t\torderByName = true;\n\t\t\t\t} else if (addtionalFlag.toLowerCase().startsWith(\"minsize\")) {\n\t\t\t\t\tString[] values = addtionalFlag.split(\"=\");\n\t\t\t\t\tif (values.length == 1) {\n\t\t\t\t\t\ttty.println(\"parameter \" + addtionalFlag + \" is wrong\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tminSize = Long.parseLong(values[1]);\n\t\t\t\t} else if (addtionalFlag.toLowerCase().startsWith(\"minage\")) {\n\t\t\t\t\tString[] values = addtionalFlag.split(\"=\");\n\t\t\t\t\tif (values.length == 1) {\n\t\t\t\t\t\ttty.println(\"parameter \" + addtionalFlag + \" is wrong\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tminAge = Integer.parseInt(values[1]);\n\t\t\t\t} else if (addtionalFlag.toLowerCase().startsWith(\"age\")) {\n\t\t\t\t\tString[] values = addtionalFlag.split(\"=\");\n\t\t\t\t\tif (values.length == 1) {\n\t\t\t\t\t\ttty.println(\"parameter \" + addtionalFlag + \" is wrong\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tage = Integer.parseInt(values[1]);\n\t\t\t\t} else if (addtionalFlag.toLowerCase().startsWith(\"live\")) {\n\t\t\t\t\tlive = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tInteger pid = null;\n\t\tString executablePath = null;\n\t\tString coredumpPath = null;\n\t\tif (args.length == 2) {\n\t\t\tpid = Integer.valueOf(args[1]);\n\t\t} else {\n\t\t\texecutablePath = args[1];\n\t\t\tcoredumpPath = args[2];\n\t\t}\n\n\t\t// 如有需要，执行GC\n\t\tif (live) {\n\t\t\tif (pid == null) {\n\t\t\t\ttty.println(\"only a running vm can be attached when live option is on\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttriggerGc(pid);\n\t\t}\n\n\n\t\t//// 正式执行\n\t\tHotSpotAgent agent = new HotSpotAgent();\n\n\t\tRuntime.getRuntime().addShutdownHook(new Thread() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\n\t\t\t\t// 如果ctrl＋C退出，仍尽量打印结果\n\t\t\t\tif (oldGenProcessor != null) {\n\t\t\t\t\ttty.println(\"VJMap aborted. Below is the incomplete summary: \");\n\t\t\t\t\toldGenProcessor.printResult();\n\t\t\t\t}\n\t\t\t\tif (heapProcessor != null) {\n\t\t\t\t\ttty.println(\"VJMap aborted. Below is the incomplete summary: \");\n\t\t\t\t\theapProcessor.printResult();\n\t\t\t\t}\n\t\t\t\ttty.flush();\n\t\t\t}\n\t\t});\n\n\t\ttry {\n\t\t\tif (args.length == 2) {\n\t\t\t\tagent.attach(pid);\n\t\t\t} else {\n\t\t\t\tagent.attach(executablePath, coredumpPath);\n\t\t\t}\n\n\t\t\tlong startTime = System.currentTimeMillis();\n\t\t\tif (modeFlag.startsWith(\"-all\")) {\n\t\t\t\trunHeapVisitor(pid, orderByName, minSize);\n\t\t\t} else if (modeFlag.startsWith(\"-sur\")) {\n\t\t\t\trunSurviorAccessor(age, minAge, orderByName, minSize);\n\t\t\t} else if (modeFlag.startsWith(\"-old\")) {\n\t\t\t\trunOldGenAccessor(orderByName, minSize);\n\t\t\t} else if (modeFlag.startsWith(\"-address\")) {\n\t\t\t\tprintGenAddress();\n\t\t\t} else if (modeFlag.startsWith(\"-class\")) {\n\t\t\t\tprintLoadedClass();\n\t\t\t} else if (modeFlag.startsWith(\"-version\")) {\n\t\t\t\ttty.println(\"vjmap version:\" + VERSION);\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\tprintHelp();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlong endTime = System.currentTimeMillis();\n\t\t\tdouble secs = (endTime - startTime) / 1000.0d;\n\t\t\ttty.printf(\"%n Heap traversal took %.1f seconds.%n\", secs);\n\t\t\ttty.flush();\n\t\t} catch (TimeoutException e) {\n\t\t\ttty.println(\"\\n\\nVJMap aborted by timeout.\");\n\t\t\ttty.println(\"Try to use live option to reduce the fragments which make progress very slow.\");\n\t\t\ttty.println(\"./vjmap.sh -old:live PID\\n\\n\");\n\t\t} catch (Exception e) {\n\t\t\ttty.println(\"Error Happen:\" + e.getMessage());\n\t\t\tif (e.getMessage() != null && e.getMessage().contains(\"Can't attach to the process\")) {\n\t\t\t\ttty.println(\n\t\t\t\t\t\t\"Please use the same user of the target JVM to run vjmap, or use root user to run it (sudo -E vjmap.sh ...)\");\n\t\t\t}\n\t\t} finally {\n\t\t\tagent.detach();\n\t\t}\n\t}\n\n\t/**\n\t * Trigger a remote gc using HotSpotVirtualMachine, inspired by jcmd's source code.\n\t * \n\t * @param pid\n\t */\n\tprivate static void triggerGc(Integer pid) {\n\t\tVirtualMachine vm = null;\n\t\ttry {\n\t\t\tvm = VirtualMachine.attach(String.valueOf(pid));\n\t\t\tHotSpotVirtualMachine hvm = (HotSpotVirtualMachine) vm;\n\t\t\ttry (InputStream in = hvm.executeJCmd(\"GC.run\");) {\n\t\t\t\tbyte b[] = new byte[256];\n\t\t\t\tint n;\n\t\t\t\tdo {\n\t\t\t\t\tn = in.read(b);\n\t\t\t\t\tif (n > 0) {\n\t\t\t\t\t\tString s = new String(b, 0, n, \"UTF-8\");\n\t\t\t\t\t\ttty.print(s);\n\t\t\t\t\t}\n\t\t\t\t} while (n > 0);\n\t\t\t\ttty.println();\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\ttty.println(e.getMessage());\n\t\t} finally {\n\t\t\tif (vm != null) {\n\t\t\t\ttry {\n\t\t\t\t\tvm.detach();\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\ttty.println(e.getMessage());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static void printHelp() {\n\t\tint leftLength = \"-all:minsize=1024,byname\".length();\n\t\tString format = \" %-\" + leftLength + \"s  %s%n\";\n\t\ttty.println(\"vjmap \" + VERSION\n\t\t\t\t+ \" - prints per GC generation (Eden, Survivor, OldGen) object details of a given process.\");\n\t\ttty.println(\"Usage: vjmap.sh <options> <PID>\");\n\t\ttty.println(\"Usage: vjmap.sh <options> <executable java path> <coredump file path>\");\n\t\ttty.println(\"\");\n\t\ttty.printf(format, \"-all\", \"print all gens histogram, order by total size\");\n\t\ttty.printf(format, \"-all:live\", \"print all gens histogram, live objects only\");\n\t\ttty.printf(format, \"-all:minsize=1024\", \"print all gens histogram, total size>=1024\");\n\t\ttty.printf(format, \"-all:minsize=1024,byname\",\n\t\t\t\t\"print all gens histogram, total size>=1024, order by class name\");\n\n\t\ttty.printf(format, \"-old\", \"print oldgen histogram, order by oldgen size\");\n\t\ttty.printf(format, \"-old:live\", \"print oldgen histogram, live objects only\");\n\t\ttty.printf(format, \"-old:minsize=1024\", \"print oldgen histogram, oldgen size>=1024\");\n\t\ttty.printf(format, \"-old:minsize=1024,byname\",\n\t\t\t\t\"print oldgen histogram, oldgen size>=1024, order by class name\");\n\n\t\ttty.printf(format, \"-sur\", \"print survivor histogram, age>=2\");\n\t\ttty.printf(format, \"-sur:age=4\", \"print survivor histogram, age==4\");\n\t\ttty.printf(format, \"-sur:minage=4\", \"print survivor histogram, age>=4, default is 2\");\n\t\ttty.printf(format, \"-sur:minsize=1024,byname\",\n\t\t\t\t\"print survivor histogram, age>=3, survivor size>=1024, order by class name\");\n\t\ttty.printf(format, \"-address\", \"print address for all gens\");\n\t\ttty.printf(format, \"-class\", \"print all loaded classes\");\n\t}\n}\n"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/oops/GenAddressAccessor.java",
    "content": "package com.vip.vjtools.vjmap.oops;\n\nimport java.io.PrintStream;\n\nimport sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen;\nimport sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen;\nimport sun.jvm.hotspot.gc_interface.CollectedHeap;\nimport sun.jvm.hotspot.memory.ConcurrentMarkSweepGeneration;\nimport sun.jvm.hotspot.memory.DefNewGeneration;\n\n/**\n * 打印各区地址\n */\npublic class GenAddressAccessor {\n\n\tprivate PrintStream tty = System.out;\n\n\tpublic void printHeapAddress() {\n\t\tCollectedHeap heap = HeapUtils.getHeap();\n\n\t\tif (HeapUtils.isCMSGC(heap)) {\n\t\t\tDefNewGeneration youngGen = HeapUtils.getYoungGenForCMS(heap);\n\t\t\tyoungGen.printOn(tty);\n\t\t\ttty.println(\"\");\n\t\t\tConcurrentMarkSweepGeneration cmsGen = HeapUtils.getOldGenForCMS(heap);\n\t\t\tcmsGen.printOn(tty);\n\t\t} else if (HeapUtils.isParallelGC(heap)) {\n\t\t\t// Parallel GC\n\t\t\tPSYoungGen psYoung = HeapUtils.getYongGenForPar(heap);\n\t\t\tpsYoung.printOn(tty);\n\t\t\ttty.println(\"\");\n\t\t\tPSOldGen oldgen = HeapUtils.getOldGenForPar(heap);\n\t\t\toldgen.printOn(tty);\n\t\t} else {\n\t\t\tthrow new IllegalArgumentException(\"Unsupport heap:\" + heap.getClass().getName());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/oops/HeapHistogramVisitor.java",
    "content": "package com.vip.vjtools.vjmap.oops;\n\nimport java.util.HashMap;\n\nimport com.vip.vjtools.vjmap.ClassStats;\nimport com.vip.vjtools.vjmap.utils.ProgressNotifier;\n\nimport sun.jvm.hotspot.debugger.OopHandle;\nimport sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen;\nimport sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen;\nimport sun.jvm.hotspot.gc_implementation.shared.MutableSpace;\nimport sun.jvm.hotspot.gc_interface.CollectedHeap;\nimport sun.jvm.hotspot.memory.ConcurrentMarkSweepGeneration;\nimport sun.jvm.hotspot.memory.ContiguousSpace;\nimport sun.jvm.hotspot.memory.DefNewGeneration;\nimport sun.jvm.hotspot.memory.EdenSpace;\nimport sun.jvm.hotspot.oops.HeapVisitor;\nimport sun.jvm.hotspot.oops.Klass;\nimport sun.jvm.hotspot.oops.Oop;\n\n/**\n * 实现HeapVisitor接口，实现遍历堆的回调方法\n */\npublic class HeapHistogramVisitor implements HeapVisitor {\n\n\n\tprivate CollectedHeap heap;\n\n\tprivate EdenSpace cmsEden;\n\tprivate ContiguousSpace cmsSur;\n\tprivate ConcurrentMarkSweepGeneration cmsOld;\n\n\tprivate MutableSpace parEden;\n\tprivate MutableSpace parSur;\n\tprivate PSOldGen parOld;\n\n\tprivate boolean isCms;\n\n\tprivate HashMap<Klass, ClassStats> classStatsMap;\n\tprivate ProgressNotifier progressNodifier;\n\n\tpublic HeapHistogramVisitor() {\n\t\tclassStatsMap = new HashMap<>(2048, 0.2f);\n\n\t\theap = HeapUtils.getHeap();\n\t\tif (HeapUtils.isCMSGC(heap)) {\n\t\t\tDefNewGeneration youngGen = HeapUtils.getYoungGenForCMS(heap);\n\t\t\tcmsEden = youngGen.eden();\n\t\t\tcmsSur = youngGen.from();\n\t\t\tcmsOld = HeapUtils.getOldGenForCMS(heap);\n\t\t\tisCms = true;\n\t\t} else if (HeapUtils.isParallelGC(heap)) {\n\t\t\tPSYoungGen youngGen = HeapUtils.getYongGenForPar(heap);\n\t\t\tparEden = youngGen.edenSpace();\n\t\t\tparSur = youngGen.fromSpace();\n\t\t\tparOld = HeapUtils.getOldGenForPar(heap);\n\t\t\tisCms = false;\n\t\t} else {\n\t\t\tthrow new RuntimeException(\"Only support CMS and Parallel GC. Unsupport Heap:\" + heap.getClass().getName());\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean doObj(Oop obj) {\n\t\tKlass klass = obj.getKlass();\n\n\t\tClassStats classStats = HeapUtils.getClassStats(klass, classStatsMap);\n\t\tPlace place = isCms ? getCmsLocation(obj) : getParLocation(obj);\n\t\tlong objSize = obj.getObjectSize();\n\n\t\tupdateWith(classStats, objSize, place);\n\n\t\t// 每完成1％ 打印一个.，每完成10% 打印百分比提示\n\t\tprogressNodifier.processingSize += objSize;\n\t\tif (progressNodifier.processingSize > progressNodifier.nextNotificationSize) {\n\t\t\tprogressNodifier.printProgress();\n\t\t}\n\n\t\treturn false;\n\t}\n\n\n\t@Override\n\tpublic void prologue(long size) {\n\t\tprogressNodifier = new ProgressNotifier(size);\n\t\tprogressNodifier.printHead();\n\t}\n\n\t@Override\n\tpublic void epilogue() {\n\t}\n\n\tprivate void updateWith(ClassStats classStats, long objSize, Place place) {\n\t\tclassStats.count++;\n\t\tclassStats.size += objSize;\n\n\t\tswitch (place) {\n\t\t\tcase InEden:\n\t\t\t\tclassStats.edenCount++;\n\t\t\t\tclassStats.edenSize += objSize;\n\t\t\t\tbreak;\n\t\t\tcase InSurvivor:\n\t\t\t\tclassStats.survivorCount++;\n\t\t\t\tclassStats.survivorSize += objSize;\n\t\t\t\tbreak;\n\t\t\tcase InOld:\n\t\t\t\tclassStats.oldCount++;\n\t\t\t\tclassStats.oldSize += objSize;\n\t\t\t\tbreak;\n\t\t\tcase Unknown:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate Place getCmsLocation(Oop obj) {\n\t\tOopHandle handle = obj.getHandle();\n\n\t\tif (cmsEden.contains(handle)) {\n\t\t\treturn Place.InEden;\n\t\t}\n\t\tif (cmsOld.contains(handle)) {\n\t\t\treturn Place.InOld;\n\t\t}\n\t\tif (cmsSur.contains(handle)) {\n\t\t\treturn Place.InSurvivor;\n\t\t}\n\n\t\treturn Place.Unknown;\n\t}\n\n\tpublic Place getParLocation(Oop obj) {\n\t\tOopHandle handle = obj.getHandle();\n\n\t\tif (parEden.contains(handle)) {\n\t\t\treturn Place.InEden;\n\t\t}\n\t\tif (parOld.isIn(handle)) {\n\t\t\treturn Place.InOld;\n\t\t}\n\t\tif (parSur.contains(handle)) {\n\t\t\treturn Place.InSurvivor;\n\t\t}\n\n\t\treturn Place.Unknown;\n\t}\n\n\tpublic HashMap<Klass, ClassStats> getClassStatsMap() {\n\t\treturn classStatsMap;\n\t}\n\n\tpublic enum Place {\n\t\tUnknown, InEden, InSurvivor, InOld;\n\t}\n}"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/oops/HeapUtils.java",
    "content": "package com.vip.vjtools.vjmap.oops;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport com.vip.vjtools.vjmap.ClassStats;\n\nimport sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen;\nimport sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen;\nimport sun.jvm.hotspot.gc_implementation.parallelScavenge.ParallelScavengeHeap;\nimport sun.jvm.hotspot.gc_interface.CollectedHeap;\nimport sun.jvm.hotspot.memory.ConcurrentMarkSweepGeneration;\nimport sun.jvm.hotspot.memory.DefNewGeneration;\nimport sun.jvm.hotspot.memory.GenCollectedHeap;\nimport sun.jvm.hotspot.oops.Klass;\nimport sun.jvm.hotspot.oops.ObjectHeap;\nimport sun.jvm.hotspot.runtime.VM;\n\npublic class HeapUtils {\n\n\tpublic static CollectedHeap getHeap() {\n\t\treturn VM.getVM().getUniverse().heap();\n\t}\n\n\tpublic static ObjectHeap getObjectHeap() {\n\t\treturn VM.getVM().getObjectHeap();\n\t}\n\n\tpublic static boolean isCMSGC(CollectedHeap heap) {\n\t\treturn heap instanceof GenCollectedHeap;\n\t}\n\n\tpublic static boolean isParallelGC(CollectedHeap heap) {\n\t\treturn heap instanceof ParallelScavengeHeap;\n\t}\n\n\tpublic static DefNewGeneration getYoungGenForCMS(CollectedHeap heap) {\n\t\treturn (DefNewGeneration) ((GenCollectedHeap) heap).getGen(0);\n\t}\n\n\tpublic static ConcurrentMarkSweepGeneration getOldGenForCMS(CollectedHeap heap) {\n\t\treturn (ConcurrentMarkSweepGeneration) ((GenCollectedHeap) heap).getGen(1);\n\t}\n\n\tpublic static PSYoungGen getYongGenForPar(CollectedHeap heap) {\n\t\treturn ((ParallelScavengeHeap) heap).youngGen();\n\t}\n\n\tpublic static PSOldGen getOldGenForPar(CollectedHeap heap) {\n\t\treturn ((ParallelScavengeHeap) heap).oldGen();\n\t}\n\n\tpublic static List<ClassStats> getClassStatsList(HashMap<Klass, ClassStats> classStatsMap) {\n\t\tList<ClassStats> list = new ArrayList<>(classStatsMap.size());\n\t\tlist.addAll(classStatsMap.values());\n\t\treturn list;\n\t}\n\n\tpublic static ClassStats getClassStats(Klass klass, HashMap<Klass, ClassStats> classStatsMap) {\n\t\tClassStats stats = classStatsMap.get(klass);\n\t\tif (stats == null) {\n\t\t\tstats = new ClassStats(klass);\n\t\t\tclassStatsMap.put(klass, stats);\n\t\t}\n\t\treturn stats;\n\t}\n}\n"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/oops/LoadedClassAccessor.java",
    "content": "package com.vip.vjtools.vjmap.oops;\n\nimport java.io.PrintStream;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\n\nimport sun.jvm.hotspot.debugger.AddressException;\nimport sun.jvm.hotspot.memory.SystemDictionary;\nimport sun.jvm.hotspot.oops.InstanceKlass;\nimport sun.jvm.hotspot.oops.Klass;\nimport sun.jvm.hotspot.oops.Oop;\nimport sun.jvm.hotspot.runtime.VM;\n\npublic class LoadedClassAccessor {\n\n\tprivate PrintStream tty = System.out;\n\n\tpublic void pringLoadedClass() {\n\t\ttty.println(\"Finding classes in System Dictionary..\");\n\n\t\ttry {\n\t\t\tfinal ArrayList<InstanceKlass> klasses = new ArrayList<>(128);\n\n\t\t\tSystemDictionary dict = VM.getVM().getSystemDictionary();\n\t\t\tdict.classesDo(new SystemDictionary.ClassVisitor() {\n\t\t\t\t@Override\n\t\t\t\tpublic void visit(Klass k) {\n\t\t\t\t\tif (k instanceof InstanceKlass) {\n\t\t\t\t\t\tklasses.add((InstanceKlass) k);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tCollections.sort(klasses, new Comparator<InstanceKlass>() {\n\t\t\t\t@Override\n\t\t\t\tpublic int compare(InstanceKlass x, InstanceKlass y) {\n\t\t\t\t\treturn x.getName().asString().compareTo(y.getName().asString());\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttty.println(\"#class             #loader\");\n\t\t\ttty.println(\"-----------------------------------------------\");\n\t\t\tfor (InstanceKlass k : klasses) {\n\t\t\t\ttty.printf(\"%s, %s\\n\", getClassNameFrom(k), getClassLoaderOopFrom(k));\n\t\t\t}\n\t\t} catch (AddressException e) {\n\t\t\ttty.println(\"Error accessing address 0x\" + Long.toHexString(e.getAddress()));\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\n\tprivate static String getClassLoaderOopFrom(InstanceKlass klass) {\n\t\tOop loader = klass.getClassLoader();\n\t\treturn loader != null ? getClassNameFrom((InstanceKlass) loader.getKlass()) + \" @ \" + loader.getHandle()\n\t\t\t\t: \"<bootstrap>\";\n\t}\n\n\tprivate static String getClassNameFrom(InstanceKlass klass) {\n\t\treturn klass != null ? klass.getName().asString().replace('/', '.') : null;\n\t}\n}\n"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/oops/OldgenAccessor.java",
    "content": "package com.vip.vjtools.vjmap.oops;\n\nimport java.io.PrintStream;\nimport java.util.HashMap;\n\nimport com.vip.vjtools.vjmap.ClassStats;\nimport com.vip.vjtools.vjmap.utils.ProgressNotifier;\n\nimport sun.jvm.hotspot.debugger.Address;\nimport sun.jvm.hotspot.gc_interface.CollectedHeap;\nimport sun.jvm.hotspot.memory.CMSCollector;\nimport sun.jvm.hotspot.memory.CompactibleFreeListSpace;\nimport sun.jvm.hotspot.memory.ConcurrentMarkSweepGeneration;\nimport sun.jvm.hotspot.memory.FreeChunk;\nimport sun.jvm.hotspot.oops.Klass;\nimport sun.jvm.hotspot.oops.ObjectHeap;\nimport sun.jvm.hotspot.oops.Oop;\nimport sun.jvm.hotspot.oops.UnknownOopException;\nimport sun.jvm.hotspot.runtime.VM;\nimport sun.jvm.hotspot.runtime.VMObjectFactory;\n\n/**\n * 使用主动访问堆的方式统计OldGen的对象信息, only support CMS GC.\n * \n * 迭代分区的代码，来自于 sun.jvm.hotspot.memory.CompactibleFreeListSpace.getLiveRegions()\n * sun.jvm.hotspot.oops.ObjectHeap.iterateLiveRegions()\n * \n * 第一版全抄iterateLiveRegions()，后来发现getLiveRegions()本身已经遍历了一次堆，所以改为在其基础上修改。\n */\npublic class OldgenAccessor {\n\n\tprivate PrintStream tty = System.out;\n\tprivate Address cur;\n\tprivate Address regionStart;\n\tprivate int liveRegions = 0;\n\tprivate HashMap<Klass, ClassStats> classStatsMap = new HashMap<>(2048, 0.2f);\n\n\tpublic HashMap<Klass, ClassStats> getClassStatsMap() {\n\t\treturn classStatsMap;\n\t}\n\n\tpublic void caculateHistogram() {\n\t\tObjectHeap objectHeap = HeapUtils.getObjectHeap();\n\t\tCollectedHeap heap = checkHeapType();\n\t\tConcurrentMarkSweepGeneration cmsGen = HeapUtils.getOldGenForCMS(heap);\n\n\t\tCompactibleFreeListSpace cmsSpace = cmsGen.cmsSpace();\n\t\tCMSCollector cmsCollector = cmsSpace.collector();\n\t\tcur = cmsSpace.bottom();\n\t\tregionStart = cur;\n\t\tAddress limit = cmsSpace.end();\n\n\t\tprintGenSummary(cmsGen);\n\n\t\tProgressNotifier progressNotifier = new ProgressNotifier(cmsGen.used());\n\t\tprogressNotifier.printHead();\n\n\t\tfinal long addressSize = VM.getVM().getAddressSize();\n\n\t\tfor (; cur.lessThan(limit);) {\n\t\t\tAddress k = cur.getAddressAt(addressSize);\n\t\t\tif (FreeChunk.indicatesFreeChunk(cur)) {\n\t\t\t\tskipFreeChunk(addressSize);\n\t\t\t} else if (k != null) {\n\t\t\t\tOop obj = null;\n\t\t\t\ttry {\n\t\t\t\t\tobj = objectHeap.newOop(cur.addOffsetToAsOopHandle(0));\n\t\t\t\t} catch (UnknownOopException ignored) {\n\t\t\t\t\t// ignored\n\t\t\t\t}\n\n\t\t\t\tif (obj == null) {\n\t\t\t\t\tcontinueNextAddress(cmsCollector);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlong objectSize = obj.getObjectSize();\n\n\t\t\t\tClassStats stats = HeapUtils.getClassStats(obj.getKlass(), classStatsMap);\n\t\t\t\tstats.oldCount++;\n\t\t\t\tstats.oldSize += objectSize;\n\n\t\t\t\tprogressNotifier.processingSize += objectSize;\n\t\t\t\tif (progressNotifier.processingSize > progressNotifier.nextNotificationSize) {\n\t\t\t\t\tprogressNotifier.printProgress();\n\t\t\t\t}\n\n\t\t\t\tcur = cur.addOffsetTo(CompactibleFreeListSpace.adjustObjectSizeInBytes(objectSize));\n\t\t\t} else {\n\t\t\t\tcontinueNextAddress(cmsCollector);\n\t\t\t}\n\t\t}\n\n\t\ttty.println(\"\\ntotal live regions:\" + liveRegions);\n\t}\n\n\tprivate CollectedHeap checkHeapType() {\n\t\tCollectedHeap heap = HeapUtils.getHeap();\n\n\t\tif (!HeapUtils.isCMSGC(heap)) {\n\t\t\tthrow new IllegalArgumentException(\"Only support CMS GC. Unsupport heap:\" + heap.getClass().getName());\n\t\t}\n\t\treturn heap;\n\t}\n\n\tprivate void printGenSummary(ConcurrentMarkSweepGeneration cmsGen) {\n\t\tcmsGen.printOn(tty);\n\t\ttty.println(\"\");\n\t}\n\n\tprivate void skipFreeChunk(final long addressSize) {\n\t\tif (!cur.equals(regionStart)) {\n\t\t\tliveRegions++;\n\t\t}\n\n\t\tFreeChunk fc = (FreeChunk) VMObjectFactory.newObject(FreeChunk.class, cur);\n\t\tlong chunkSize = fc.size();\n\t\tcur = cur.addOffsetTo(chunkSize * addressSize);\n\t}\n\n\tprivate void continueNextAddress(CMSCollector cmsCollector) {\n\t\tlong size = cmsCollector.blockSizeUsingPrintezisBits(cur);\n\t\tif (size <= 0L) {\n\t\t\tthrow new UnknownOopException();\n\t\t}\n\t\tcur = cur.addOffsetTo(CompactibleFreeListSpace.adjustObjectSizeInBytes(size));\n\t}\n}\n"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/oops/SurvivorAccessor.java",
    "content": "package com.vip.vjtools.vjmap.oops;\n\nimport java.io.PrintStream;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport com.vip.vjtools.vjmap.ClassStats;\nimport com.vip.vjtools.vjmap.utils.FormatUtils;\n\nimport sun.jvm.hotspot.debugger.Address;\nimport sun.jvm.hotspot.debugger.OopHandle;\nimport sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen;\nimport sun.jvm.hotspot.gc_implementation.shared.MutableSpace;\nimport sun.jvm.hotspot.gc_interface.CollectedHeap;\nimport sun.jvm.hotspot.memory.ContiguousSpace;\nimport sun.jvm.hotspot.memory.DefNewGeneration;\nimport sun.jvm.hotspot.oops.Klass;\nimport sun.jvm.hotspot.oops.ObjectHeap;\nimport sun.jvm.hotspot.oops.Oop;\nimport sun.jvm.hotspot.oops.UnknownOopException;\n\n/**\n * 使用主动访问堆的方式统计Survivor区的对象信息, only support CMS and Parallel GC.\n * \n * 迭代分区的代码，copy from sun.jvm.hotspot.oops.ObjectHeap.iterateLiveRegions()\n */\npublic class SurvivorAccessor {\n\n\tprivate PrintStream tty = System.out;\n\n\tpublic List<ClassStats> caculateHistogram(int excactAge, int minAge) {\n\t\tHashMap<Klass, ClassStats> classStatsMap = new HashMap<>(2048, 0.2f);\n\t\tCollectedHeap heap = HeapUtils.getHeap();\n\t\tObjectHeap objectHeap = HeapUtils.getObjectHeap();\n\n\t\t// 获取Survivor区边界\n\t\tAddress fromBottom = null;\n\t\tAddress fromTop = null;\n\n\t\tif (HeapUtils.isCMSGC(heap)) {\n\t\t\tDefNewGeneration youngGen = HeapUtils.getYoungGenForCMS(heap);\n\t\t\tContiguousSpace from = youngGen.from();\n\t\t\tfromBottom = from.bottom();\n\t\t\tfromTop = from.top();\n\n\t\t\tfrom.printOn(tty);\n\t\t\ttty.println(\"\");\n\t\t} else if (HeapUtils.isParallelGC(heap)) {\n\t\t\tPSYoungGen psYoung = HeapUtils.getYongGenForPar(heap);\n\t\t\tMutableSpace from = psYoung.fromSpace();\n\t\t\tfromBottom = from.bottom();\n\t\t\tfromTop = from.top();\n\n\t\t\tfrom.printOn(tty);\n\t\t\ttty.println(\"\");\n\t\t} else {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Only support CMS and Parallel GC. Unsupport heap:\" + heap.getClass().getName());\n\t\t}\n\n\t\t// 记录分年龄统计\n\t\tlong[] ageSize = new long[50];\n\t\tint[] ageCount = new int[50];\n\t\tint maxAge = 1;\n\n\t\t// 遍历Survivor区\n\t\tOopHandle handle = fromBottom.addOffsetToAsOopHandle(0);\n\n\t\twhile (handle.lessThan(fromTop)) {\n\t\t\tOop obj = null;\n\t\t\ttry {\n\t\t\t\tobj = objectHeap.newOop(handle);\n\t\t\t} catch (UnknownOopException ex) {\n\t\t\t\t// ok\n\t\t\t}\n\n\t\t\tif (obj == null) {\n\t\t\t\tthrow new UnknownOopException();\n\t\t\t}\n\n\t\t\tlong objectSize = obj.getObjectSize();\n\n\t\t\t// handle指针指向下一个对象，后面的处理如果失败，直接进入下一个循环\n\t\t\thandle = handle.addOffsetToAsOopHandle(objectSize);\n\n\t\t\tKlass klass = obj.getKlass();\n\t\t\tif (klass == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tint age = obj.getMark().age();\n\n\t\t\tageCount[age]++;\n\t\t\tageSize[age] += objectSize;\n\t\t\tif (age > maxAge) {\n\t\t\t\tmaxAge = age;\n\t\t\t}\n\n\t\t\t// 如果设定了精确匹配age\n\t\t\tif (excactAge != -1) {\n\t\t\t\tif (age != excactAge) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else if (age < minAge) {\n\t\t\t\t// 否则判断age>=minAge\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tClassStats stats = HeapUtils.getClassStats(klass, classStatsMap);\n\t\t\tstats.survivorCount++;\n\t\t\tstats.survivorSize += objectSize;\n\t\t}\n\n\t\ttty.printf(\"%n#age    #count  #bytes%n\");\n\n\t\tfor (int i = 1; i <= maxAge; i++) {\n\t\t\ttty.printf(\"%3d: %9d %7s%n\", i, ageCount[i], FormatUtils.toFloatUnit(ageSize[i]));\n\t\t}\n\n\t\treturn HeapUtils.getClassStatsList(classStatsMap);\n\t}\n}\n"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/utils/FormatUtils.java",
    "content": "package com.vip.vjtools.vjmap.utils;\n\npublic class FormatUtils {\n\n\tprivate static final long BYTE_UNIT_KILO = 1024;\n\tprivate static final long BYTE_UNIT_MEGA = BYTE_UNIT_KILO * 1024;\n\tprivate static final long BYTE_UNIT_GIGA = BYTE_UNIT_MEGA * 1024;\n\tprivate static final long BYTE_UNIT_TERA = BYTE_UNIT_GIGA * 1024;\n\n\t/**\n\t * 转换成带单位的字符串，转换时保留一位小数\n\t */\n\tpublic static String toFloatUnit(long size) {\n\t\tif (size < BYTE_UNIT_KILO) {\n\t\t\treturn String.format(\"%5d\", size);\n\t\t}\n\n\t\tif (size < BYTE_UNIT_MEGA) {\n\t\t\treturn String.format(\"%5.1fk\", size / (1d * BYTE_UNIT_KILO));\n\t\t}\n\n\t\tif (size < BYTE_UNIT_GIGA) {\n\t\t\treturn String.format(\"%5.1fm\", size / (1d * BYTE_UNIT_MEGA));\n\t\t}\n\n\t\tif (size < BYTE_UNIT_TERA) {\n\t\t\treturn String.format(\"%5.1fg\", size / (1d * BYTE_UNIT_GIGA));\n\t\t}\n\n\t\treturn String.format(\"%5.1ft\", size / (1d * BYTE_UNIT_TERA));\n\t}\n}"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/utils/ProgressNotifier.java",
    "content": "package com.vip.vjtools.vjmap.utils;\n\nimport java.io.PrintStream;\n\npublic class ProgressNotifier {\n\tpublic long nextNotificationSize;\n\tpublic long processingSize;\n\n\tprivate int processingPercent;\n\tprivate long onePercentSize;\n\tprivate long totalSize;\n\n\tprivate PrintStream tty = System.out;\n\n\tprivate TimeController timeController = new TimeController();\n\n\tpublic ProgressNotifier(long totalSize) {\n\t\tthis.totalSize = totalSize;\n\t\tonePercentSize = totalSize / 100;\n\t\tnextNotificationSize = onePercentSize;\n\t\tprocessingPercent = 0;\n\t\tprocessingSize = 0;\n\t}\n\n\tpublic void printHead() {\n\t\ttty.println(\"Total live size to process: \" + FormatUtils.toFloatUnit(totalSize));\n\t\ttty.print(\" 0%:\");\n\t}\n\n\tpublic void printProgress() {\n\t\ttimeController.checkTimedOut();\n\t\ttty.print(\".\");\n\t\tprocessingPercent++;\n\t\tnextNotificationSize += onePercentSize;\n\t\tif (processingPercent % 10 == 0) {\n\t\t\ttty.print(\"\\n\" + processingPercent + \"%:\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjmap/src/main/java/com/vip/vjtools/vjmap/utils/TimeController.java",
    "content": "package com.vip.vjtools.vjmap.utils;\n\n/**\n * 最多等待15分钟，超时后打印当前结果并退出，建议用户使用live参数\n */\npublic class TimeController {\n\n\tlong start = System.currentTimeMillis();\n\n\t// 15 minutes\n\tlong maxTime = Long.parseLong(System.getProperty(\"vjmap.timeout\", String.valueOf(60 * 1000 * 15)));\n\n\tpublic void checkTimedOut() {\n\t\tif (System.currentTimeMillis() - start > maxTime) {\n\t\t\tthrow new TimeoutException();\n\t\t}\n\t}\n\n\tpublic static class TimeoutException extends RuntimeException {\n\n\t}\n}\n"
  },
  {
    "path": "vjmxcli/README.md",
    "content": "\n# 1. 概述\n\n在cmdline-jmxclient项目上定制，增加功能\n\n* 支持以pid接入JVM，不需要原JVM在启动参数中打开了JMX选项\n* 完全模拟`jstat -gcutil`输出的`gcutil`，用于jstat不能使用的情况， 或者jstat计算使用百分比时，用“已申请大小”，而不是“Max大小”作为分母，不能反映内存是否真正不足的情况。\n\n因为每调度一次`java -jar vjmxclient.jar`，其实是创建了一个新的JVM，因此在vjmxcli.sh 加上了一系列JVM参数减少消耗。\n\n\n[Download vjmxcli-1.0.8.zip](http://repo1.maven.org/maven2/com/vip/vjtools/vjmxcli/1.0.8/vjmxcli-1.0.8.zip)\n\n\n必须与目标JVM使用相同的JDK版本运行。\n\n# 2. 获取MBean属性值\n\n```\n// 以host:port接入\n./vjmxcli.sh - 127.0.0.1:8060 java.lang:type=Memory HeapMemoryUsage\n\n// 以pid接入\n./vjmxcli.sh - 98583 java.lang:type=Memory HeapMemoryUsage\n```\n\n参数解释\n\n* `-` : 无密码\n* `127.0.0.1:8060` or `98582` : 应用地址及jmx端口, 或pid\n* `java.lang:type=Memory`:MBean名\n* `HeapMemoryUsage`:Attribute名\n\n\n# 3. 模拟并改进jstat gcutil输出\n\njstat有时候会不可使用，比如目标JVM使用-Djava.tmp.dir 重定义了临时目录，或者使用了-XX:+PerfDisableSharedMem禁止了perfdata。此时，可以用vjmxcli代替jstat。\n\n另一种情况，jstat中的分母是已申请的内存，而不是允许的最大内存，因此如果按此百分比进行内存不足的告警，会造成大量误报，比如该区当前使用了95M内存，当前申请内存是100M，而最大内存其实是1G的情况，就不应该促发该区内存使用量超过了90%的告警。\n\n因此vjmxCli的算法是，如果有设置该区内存的最大值，使用最大值做分母，没有设置时才使用该区已申请内存。\n\n为什么jstat不是这样算呢？因为如果Max未设置时，从JMX会返回－1， 而PerfData则会返回一个很没准的大值，因此只读PerfData的jstat完全无法使用Max值做计算。\n\n```\n//一次性输出\n./vjmxcli.sh - 127.0.0.1:7001 gcutil\n\n//间隔5秒连续输出\n./vjmxcli.sh - 127.0.0.1:7001 gcutil 5\n\n// 以pid连入，间隔5秒连续输出\n./vjmxcli.sh - 98583 gcutil 5\n\n\n```\nJDK7 示例输出\n\n```\nS\t      S\t      E\t     O\t     P\t   \tYGC\tYGCT\tFGC\tFGCT\tGCT\t\n41.25\t41.25\t2.25\t0.00\t0.48\t2\t0.025\t0\t0.0\t   0.025\n```\n\nJDK8 示例输出\n```\nS\t      S\t      E\t     O\t      M\t   CCS\tYGC\tYGCT\tFGC\tFGCT\tGCT\t\n41.25\t41.25\t2.25\t0.00\t0.48\t0\t2\t0.025\t0\t0.0\t   0.025\n```\n\n\n# 4. 附录 \n\n## 4.1 常用JMX条目\n\n| 条目 | Object Name | Attribute Name|\n| -------- | -------- | -------- |\n| 堆内存    |  java.lang:type=Memory | HeapMemoryUsage     |\n| 非堆内存(不包含堆外内存)    |  java.lang:type=Memory | NonHeapMemoryUsage     |\n| 堆外内存(不包含新版Netty申请的堆外内存)    |  java.nio:type=BufferPool,name=direct |MemoryUsed |\n| 线程数    |  java.lang:type=Threading | ThreadCount |\n| 守护线程数    |  java.lang:type=Threading | DaemonThreadCount |\n| 分代内存及GC  |  不同JDK的值不一样 | 不同JDK的值不一样  |\n\n## 4.2 启用JMX的启动参数\n\n```\n-Dcom.sun.management.jmxremote\n-Dcom.sun.management.jmxremote.port=7001  \n-Dcom.sun.management.jmxremote.authenticate=false \n-Dcom.sun.management.jmxremote.ssl=false\n-Djava.rmi.server.hostname=127.0.0.1\n```\n\n以pid连入时不需要预先定义上述参数"
  },
  {
    "path": "vjmxcli/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<packaging>jar</packaging>\n\t<groupId>com.vip.vjtools</groupId>\n\t<artifactId>vjmxcli</artifactId>\n\t<name>vjmxcli</name>\n\t<version>1.0.9-SNAPSHOT</version>\n\t<description>jmx command line client</description>\n\n\t<properties>\n\t\t<toolsjar>${java.home}/../lib/tools.jar</toolsjar>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<java.version>1.7</java.version>\n\t\t<maven.compiler.source>${java.version}</maven.compiler.source>\n\t\t<maven.compiler.target>${java.version}</maven.compiler.target>\n\t</properties>\n\n\t<dependencies>\n\t\t<!-- <dependency>\n\t\t\t<groupId>com.sun</groupId>\n\t\t\t<artifactId>tools</artifactId>\n\t\t\t<version>${java.version}</version>\n\t\t\t<scope>system</scope>\n\t\t\t<systemPath>${toolsjar}</systemPath>\n\t\t</dependency> -->\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-jar-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<archive>\n\t\t\t\t\t\t<manifest>\n\t\t\t\t\t\t\t<addClasspath>true</addClasspath>\n\t\t\t\t\t\t\t<classpathPrefix />\n\t\t\t\t\t\t\t<mainClass>com.vip.vjtools.jmx.Client</mainClass>\n\t\t\t\t\t\t</manifest>\n\t\t\t\t\t</archive>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-assembly-plugin</artifactId>\n\t\t\t\t<version>2.6</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<descriptors>\n\t\t\t\t\t\t<descriptor>src/main/assembly/distribution.xml</descriptor>\n\t\t\t\t\t</descriptors>\n\t\t\t\t\t<appendAssemblyId>false</appendAssemblyId>\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>assembly</id>\n\t\t\t\t\t\t<phase>package</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>single</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>release</id>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-javadoc-plugin</artifactId>\n\t\t\t\t\t\t<version>2.10.4</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<failOnError>false</failOnError>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-javadocs</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<!-- source attach plugin -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-source-plugin</artifactId>\n\t\t\t\t\t\t<version>3.0.1</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-sources</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-release-plugin</artifactId>\n\t\t\t\t\t\t<version>2.5.3</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<tagNameFormat>v.@{project.version}</tagNameFormat>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</plugin>\n\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-gpg-plugin</artifactId>\n\t\t\t\t\t\t<version>1.6</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>sign-artifacts</id>\n\t\t\t\t\t\t\t\t<phase>verify</phase>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>sign</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t\n\t\n\t\t<profile>\n\t      <id>jdk9</id>\n\t      <activation>\n\t        <jdk>[1.9,)</jdk>\n\t      </activation>\n\t      <dependencies/>\n\t    </profile>\n\t    <profile>\n\t      <id>default-jdk</id>\n\t      <activation>\n\t        <jdk>(,1.8]</jdk>\n\t      </activation>\n\t      <dependencies>\n\t        <dependency>\n\t\t\t\t<groupId>com.sun</groupId>\n\t\t\t\t<artifactId>tools</artifactId>\n\t\t\t\t<version>${java.version}</version>\n\t\t\t\t<scope>system</scope>\n\t\t\t\t<systemPath>${toolsjar}</systemPath>\n\t\t\t</dependency>\n\t      </dependencies>\n\t    </profile>\n\t\n\t</profiles>\n\n\t<distributionManagement>\n\t\t<snapshotRepository>\n\t\t\t<id>sonatype-nexus-snapshots</id>\n\t\t\t<url>https://oss.sonatype.org/content/repositories/snapshots</url>\n\t\t</snapshotRepository>\n\t\t<repository>\n\t\t\t<id>sonatype-nexus-releases</id>\n\t\t\t<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>\n\t\t</repository>\n\t</distributionManagement>\n\n\t<url>https://github.com/vipshop/vjtools</url>\n\n\t<licenses>\n\t\t<license>\n\t\t\t<name>Apache License 2.0</name>\n\t\t\t<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n\t\t\t<distribution>repo</distribution>\n\t\t</license>\n\t</licenses>\n\n\t<scm>\n\t\t<connection>scm:git:https://github.com/vipshop/vjtools.git</connection>\n\t\t<developerConnection>scm:git:https://github.com/vipshop/vjtools.git</developerConnection>\n\t\t<url>https://github.com/vipshop/vjtools</url>\n\t</scm>\n\n\t<developers>\n\t\t<!--not just me, write a name here just for sonatype requirement -->\n\t\t<developer>\n\t\t\t<id>calvin</id>\n\t\t\t<name>Calvin Xiao</name>\n\t\t\t<email>calvin.xiao at vipshop.com</email>\n\t\t\t<roles>\n\t\t\t\t<role>developer</role>\n\t\t\t</roles>\n\t\t\t<timezone>+8</timezone>\n\t\t</developer>\n\t</developers>\n</project>\n"
  },
  {
    "path": "vjmxcli/src/main/assembly/distribution.xml",
    "content": "<assembly\n\txmlns=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3\"\n\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd\">\n\n\t<id>zip</id>\n\t<baseDirectory>${project.artifactId}</baseDirectory>\n\t<formats>\n\t\t<format>zip</format>\n\t</formats>\n\t\n\t<dependencySets>\n\t\t<dependencySet>\n\t\t\t<outputDirectory>/</outputDirectory>\n\t\t\t<includes>\n\t\t\t\t<include>com.vip.vjtools:vjmxcli:*</include>\n\t\t\t</includes>\n\t\t\t<outputFileNameMapping>${artifact.artifactId}.${artifact.extension}</outputFileNameMapping>\n\t\t</dependencySet>\n\t</dependencySets>\n\t\n\t<files>\n\t\t<file>\n\t\t\t<source>src/main/assembly/vjmxcli.sh</source>\n\t\t\t<outputDirectory></outputDirectory>\n\t\t\t<destName>vjmxcli.sh</destName>\n\t\t\t<fileMode>0755</fileMode>\n\t\t\t<filtered>true</filtered>\n\t\t\t<lineEnding>unix</lineEnding>\n\t\t</file>\n\t\t<file>\n\t\t\t<source>src/main/assembly/vjmxcli.bat</source>\n\t\t\t<outputDirectory></outputDirectory>\n\t\t\t<lineEnding>windows</lineEnding>\n\t\t</file>\n\t\t<file>\n\t\t\t<source>README.md</source>\n\t\t\t<outputDirectory></outputDirectory>\n\t\t\t<destName>README.md</destName>\n\t\t\t<lineEnding>unix</lineEnding>\n\t\t</file>\n\t</files>\n</assembly>\n"
  },
  {
    "path": "vjmxcli/src/main/assembly/vjmxcli.bat",
    "content": "@echo off\n\n\nrem check java\nif \"%JAVA_HOME%\" == \"\" goto noJavaHome\n\n\nset DIR=%~dp0\nset JAVA_OPTS=-Xms96m -Xmx96m -Xmn64m -Xss256k -XX:+UseSerialGC -Djava.compiler=NONE -Xverify:none -XX:AutoBoxCacheMax=20000\n\n\"%JAVA_HOME%\\bin\\java\" %JAVA_OPTS% -cp \"%DIR%/vjmxcli.jar;%JAVA_HOME%/lib/tools.jar\" com.vip.vjtools.jmx.Client %*\ngoto end\n\n:noJavaHome\n  echo Please set JAVA_HOME before running this script\n  goto end\n:end\n\npause"
  },
  {
    "path": "vjmxcli/src/main/assembly/vjmxcli.sh",
    "content": "#!/bin/sh\n\nif [ -z \"$JAVA_HOME\" ] ; then\n\techo \"JAVA_HOME env doesn't exist, try to find the location of java\"\n    JAVA_HOME=`readlink -f \\`which java 2>/dev/null\\` 2>/dev/null | \\\n    sed 's/\\jre\\/bin\\/java//' | sed 's/\\/bin\\/java//'`\nfi\n\nif [ ! -d \"$JAVA_HOME\" ] ; then\n\techo \"Please set JAVA_HOME env before run this script\"\n\texit 1\nfi\n\n\n\n# returns the JDK version.\n# 8 for 1.8.0_nn, 9 for 9-ea etc, and \"no_java\" for undetected\nGET_JDK_VERSION() {\n  local result\n  local java_cmd\n  if [[ -n $(type -p java) ]]\n  then\n    java_cmd=java\n  elif [[ (-n \"$JAVA_HOME\") && (-x \"$JAVA_HOME/bin/java\") ]]\n  then\n    java_cmd=\"$JAVA_HOME/bin/java\"\n  fi\n  local IFS=$'\\n'\n  # remove \\r for Cygwin\n  local lines=$(\"$java_cmd\" -Xms32M -Xmx32M -version 2>&1 | tr '\\r' '\\n')\n  if [[ -z $java_cmd ]]\n  then\n    result=no_java\n  else\n    for line in $lines; do\n      if [[ (-z $result) && ($line = *\"version \\\"\"*) ]]\n      then\n        local ver=$(echo $line | sed -e 's/.*version \"\\(.*\\)\"\\(.*\\)/\\1/; 1q')\n        # on macOS, sed doesn't support '?'\n        if [[ $ver = \"1.\"* ]]\n        then\n          result=$(echo $ver | sed -e 's/1\\.\\([0-9]*\\)\\(.*\\)/\\1/; 1q')\n        else\n          result=$(echo $ver | sed -e 's/\\([0-9]*\\)\\(.*\\)/\\1/; 1q')\n        fi\n      fi\n    done\n  fi\n  echo \"$result\"\n}\n\nJDK_VERSION=$(GET_JDK_VERSION)\necho \"JDK_VERSION : $JDK_VERSION\"\n\n# jdk 8 and before\nif [[ $JDK_VERSION -le 8 ]]; then\n    TOOLSJAR=\"$JAVA_HOME/lib/tools.jar\"\n    if [ ! -f \"$TOOLSJAR\" ] ; then\n        echo \"$TOOLSJAR doesn't exist\" >&2\n        exit 1\n    fi\n    JAVA_OPTS=\"-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -Xverify:none -XX:AutoBoxCacheMax=20000\"\nelse\n    # jdk 9 or later\n    JAVA_OPTS=\"-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -XX:AutoBoxCacheMax=20000\"\nfi\n\n\nDIR=$( cd $(dirname $0) ; pwd -P )\n\n\"$JAVA_HOME\"/bin/java $JAVA_OPTS -cp \"$DIR/vjmxcli.jar:$TOOLSJAR\" com.vip.vjtools.jmx.Client $*"
  },
  {
    "path": "vjmxcli/src/main/java/com/vip/vjtools/jmx/Client.java",
    "content": "package com.vip.vjtools.jmx;\n\nimport java.io.File;\n/*\n * Client\n *\n * $Id$\n *\n * Created on Nov 12, 2004\n *\n * Copyright (C) 2004 Internet Archive.\n *\n * This file is part of the Heritrix web crawler (crawler.archive.org).\n *\n * Heritrix is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser Public License\n * as published by the Free Software Foundation; either version 2.1 of the License, or any later version.\n *\n * Heritrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser Public License for more details.\n *\n * You should have received a copy of the GNU Lesser Public License along with Heritrix; if not, write to the Free\n * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n */\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.text.FieldPosition;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.logging.ConsoleHandler;\nimport java.util.logging.Handler;\nimport java.util.logging.LogRecord;\nimport java.util.logging.Logger;\nimport java.util.logging.SimpleFormatter;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport javax.management.Attribute;\nimport javax.management.AttributeList;\nimport javax.management.InstanceNotFoundException;\nimport javax.management.IntrospectionException;\nimport javax.management.MBeanAttributeInfo;\nimport javax.management.MBeanFeatureInfo;\nimport javax.management.MBeanInfo;\nimport javax.management.MBeanOperationInfo;\nimport javax.management.MBeanParameterInfo;\nimport javax.management.MBeanServerConnection;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.ObjectInstance;\nimport javax.management.ObjectName;\nimport javax.management.ReflectionException;\nimport javax.management.openmbean.CompositeData;\nimport javax.management.openmbean.TabularData;\nimport javax.management.remote.JMXConnector;\nimport javax.management.remote.JMXConnectorFactory;\nimport javax.management.remote.JMXServiceURL;\n\nimport com.sun.tools.attach.AgentInitializationException;\nimport com.sun.tools.attach.AgentLoadException;\nimport com.sun.tools.attach.AttachNotSupportedException;\nimport com.sun.tools.attach.VirtualMachine;\n\n/**\n * A Simple Command-Line JMX Client. Tested against the JDK 1.5.0 JMX Agent. See\n * <a href=\"http://java.sun.com/j2se/1.5.0/docs/guide/management/agent.html\">Monitoring and Management Using JMX</a>.\n * <p>\n * Can supply credentials and do primitive string representation of tabular and composite openmbeans.\n * @author stack\n */\npublic class Client {\n\tprivate static final Logger logger = Logger.getLogger(Client.class.getName());\n\n\tpublic static final String V_GCUTIL_BEAN_NAME = \"gcutil\";\n\n\tprivate static final String LOCAL_CONNECTOR_ADDRESS_PROP = \"com.sun.management.jmxremote.localConnectorAddress\";\n\n\t/**\n\t * Usage string.\n\t */\n\tprivate static final String USAGE = \"See README.md\";\n\t/**\n\t * Pattern that matches a command name followed by an optional equals and optional comma-delimited list of\n\t * arguments.\n\t */\n\tprotected static final Pattern CMD_LINE_ARGS_PATTERN = Pattern.compile(\"^([^=]+)(?:(?:\\\\=)(.+))?$\");\n\n\tprivate static final String CREATE_CMD_PREFIX = \"create=\";\n\n\tpublic static void main(String[] args) {\n\t\tClient client = new Client();\n\t\t// Set the logger to use our all-on-one-line formatter.\n\t\tLogger l = Logger.getLogger(\"\");\n\t\tHandler[] hs = l.getHandlers();\n\t\tfor (int i = 0; i < hs.length; i++) {\n\t\t\tHandler h = hs[0];\n\t\t\tif (h instanceof ConsoleHandler) {\n\t\t\t\th.setFormatter(client.new OneLineSimpleLogger());\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tclient.execute(args);\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\n\tprotected static void usage() {\n\t\tusage(0, null);\n\t}\n\n\tprotected static void usage(int exitCode, String message) {\n\t\tif (message != null && message.length() > 0) {\n\t\t\tSystem.out.println(message);\n\t\t}\n\t\tSystem.out.println(USAGE);\n\t\tSystem.exit(exitCode);\n\t}\n\n\t/**\n\t * Constructor.\n\t */\n\tpublic Client() {\n\t\tsuper();\n\t}\n\n\t/**\n\t * Parse a 'login:password' string. Assumption is that no colon in the login name.\n\t * @param userpass\n\t * @return Array of strings with login in first position.\n\t */\n\tprotected String[] parseUserpass(final String userpass) {\n\t\tif (userpass == null || userpass.equals(\"-\")) {\n\t\t\treturn null;\n\t\t}\n\t\tint index = userpass.indexOf(':');\n\t\tif (index <= 0) {\n\t\t\tthrow new RuntimeException(\"Unable to parse: \" + userpass);\n\t\t}\n\t\treturn new String[]{userpass.substring(0, index), userpass.substring(index + 1)};\n\t}\n\n\t/**\n\t * @param login\n\t * @param password\n\t * @return Credentials as map for RMI.\n\t */\n\tprotected static Map formatCredentials(final String login, final String password) {\n\t\tMap env = null;\n\t\tString[] creds = new String[]{login, password};\n\t\tenv = new HashMap(1);\n\t\tenv.put(JMXConnector.CREDENTIALS, creds);\n\t\treturn env;\n\t}\n\n\t/**\n\t * 扩展支持以pid or host-port两种方式接入\n\t */\n\tpublic static JMXConnector connect(final String hostportOrPid, final String login, final String password)\n\t\t\tthrows IOException {\n\t\t// ./vjmxcli.sh - 127.0.0.1:8060 gcutil\n\t\tif (hostportOrPid.contains(\":\")) {\n\t\t\tJMXServiceURL rmiurl = new JMXServiceURL(\n\t\t\t\t\t\"service:jmx:rmi://\" + hostportOrPid + \"/jndi/rmi://\" + hostportOrPid + \"/jmxrmi\");\n\t\t\treturn JMXConnectorFactory.connect(rmiurl, formatCredentials(login, password));\n\t\t} else {\n\t\t\t// ./vjmxcli.sh - 112222 gcutil\n\t\t\tString localAddress = getLocalConnectorAddress(hostportOrPid);\n\t\t\tJMXServiceURL localRmiurl = new JMXServiceURL(localAddress);\n\t\t\treturn JMXConnectorFactory.connect(localRmiurl);\n\t\t}\n\t}\n\n\t/**\n\t * VirtualMachine保证JMX Agent已启动, 并向JMXClient提供连接地址\n\t * \n\t * 地址样例：service:jmx:rmi://127.0.0.1/stub/rO0ABXN9AAAAAQAl...\n\t */\n\tpublic static String getLocalConnectorAddress(String pid) throws IOException {// NOSONAR\n\t\tVirtualMachine vm = null;\n\t\t// 1. attach vm\n\t\ttry {\n\t\t\tvm = VirtualMachine.attach(pid);\n\t\t} catch (AttachNotSupportedException x) {\n\t\t\tIOException ioe = new IOException(x.getMessage());\n\t\t\tioe.initCause(x);\n\t\t\tthrow ioe;\n\t\t}\n\n\t\ttry {\n\t\t\t// 2. 检查smartAgent是否已启动\n\t\t\tProperties agentProps = vm.getAgentProperties();\n\t\t\tString address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP);\n\n\t\t\tif (address != null) {\n\t\t\t\treturn address;\n\t\t\t}\n\n\t\t\t// 3. 未启动，尝试启动\n\t\t\tint version = getJavaMajorVersion(vm.getSystemProperties().getProperty(\"java.specification.version\"));\n\t\t\tif (version >= 8) {\n\t\t\t\tvm.startLocalManagementAgent();\n\t\t\t\tagentProps = vm.getAgentProperties();\n\t\t\t\taddress = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP);\n\t\t\t} else {\n\t\t\t\t// JDK8后有更直接的vm.startLocalManagementAgent()方法\n\t\t\t\tString home = vm.getSystemProperties().getProperty(\"java.home\");\n\t\t\t\t// Normally in ${java.home}/jre/lib/management-agent.jar but might\n\t\t\t\t// be in ${java.home}/lib in build environments.\n\t\t\t\tString agentPath = home + File.separator + \"jre\" + File.separator + \"lib\" + File.separator\n\t\t\t\t\t\t+ \"management-agent.jar\";\n\n\t\t\t\tFile f = new File(agentPath);\n\t\t\t\tif (!f.exists()) {\n\t\t\t\t\tagentPath = home + File.separator + \"lib\" + File.separator + \"management-agent.jar\";\n\t\t\t\t\tf = new File(agentPath);\n\t\t\t\t\tif (!f.exists()) {\n\t\t\t\t\t\tthrow new IOException(\"Management agent not found\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tagentPath = f.getCanonicalPath();\n\t\t\t\ttry {\n\t\t\t\t\tvm.loadAgent(agentPath, \"com.sun.management.jmxremote\");\n\t\t\t\t} catch (AgentLoadException x) {\n\t\t\t\t\t// 高版本 attach 低版本jdk 抛异常：com.sun.tools.attach.AgentLoadException: 0，实际上是成功的;\n\t\t\t\t\t// 根因： HotSpotVirtualMachine.loadAgentLibrary 高版本jdk实现不一样了\n\t\t\t\t\tif (!\"0\".equals(x.getMessage())) {\n\t\t\t\t\t\tIOException ioe = new IOException(x.getMessage());\n\t\t\t\t\t\tioe.initCause(x);\n\t\t\t\t\t\tthrow ioe;\n\t\t\t\t\t}\n\t\t\t\t} catch (AgentInitializationException x) {\n\t\t\t\t\tIOException ioe = new IOException(x.getMessage());\n\t\t\t\t\tioe.initCause(x);\n\t\t\t\t\tthrow ioe;\n\t\t\t\t}\n\n\t\t\t\t// 4. 再次获取connector address\n\t\t\t\tagentProps = vm.getAgentProperties();\n\t\t\t\taddress = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP);\n\t\t\t}\n\n\t\t\tif (address == null) {\n\t\t\t\tthrow new IOException(\"Fails to find connector address\");\n\t\t\t}\n\n\t\t\treturn address;\n\t\t} finally {\n\t\t\tvm.detach();\n\t\t}\n\t}\n\n\t/**\n\t * Version of execute called from the cmdline. Prints out result of execution on stdout. Parses cmdline args. Then\n\t * calls {@link #execute(String, String, String, String, String[], boolean)}.\n\t * @param args Cmdline args.\n\t * @throws Exception\n\t */\n\tprotected void execute(final String[] args) throws Exception {\n\t\t// Process command-line.\n\t\tif (args.length == 0 || args.length == 1) {\n\t\t\tusage();\n\t\t}\n\t\tString userpass = args[0];\n\t\tString hostportOrPid = args[1];\n\t\tString beanname = null;// vGCutil\n\t\tString[] command = null;\n\t\tif (args.length > 2) {\n\t\t\tbeanname = args[2];\n\t\t}\n\t\tif (args.length > 3) {\n\t\t\tcommand = new String[args.length - 3];\n\t\t\tfor (int i = 3; i < args.length; i++) {\n\t\t\t\tcommand[i - 3] = args[i];\n\t\t\t}\n\t\t}\n\t\tString[] loginPassword = parseUserpass(userpass);\n\n\t\t// 模拟GC Util命令的扩展\n\t\tif (V_GCUTIL_BEAN_NAME.equalsIgnoreCase(beanname)) {\n\t\t\t// 支持配置interval 固定事件间隔连续输出\n\t\t\tint interval = 0;\n\t\t\tif (command != null && command.length > 0) {\n\t\t\t\ttry {\n\t\t\t\t\tinterval = Math.abs(Integer.parseInt(command[0]));// 拿绝对值, 避免负数的情况\n\t\t\t\t} catch (NumberFormatException e) {// NOSONAR\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tExtraCommand extraCommand = new ExtraCommand();\n\t\t\textraCommand.execute(hostportOrPid, ((loginPassword == null) ? null : loginPassword[0]),\n\t\t\t\t\t((loginPassword == null) ? null : loginPassword[1]), beanname, interval);\n\t\t\treturn;\n\t\t}\n\n\t\tObject[] result = execute(hostportOrPid, ((loginPassword == null) ? null : loginPassword[0]),\n\t\t\t\t((loginPassword == null) ? null : loginPassword[1]), beanname, command);\n\t\tif (result != null) {\n\t\t\tfor (int i = 0; i < result.length; i++) {\n\t\t\t\tif (result[i] != null && result[i].toString().length() > 0) {\n\t\t\t\t\tif (command != null) {\n\t\t\t\t\t\tlogger.info(command[i] + \": \" + result[i]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.info(\"\\n\" + result[i].toString());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected Object[] execute(final String hostport, final String login, final String password, final String beanname,\n\t\t\tfinal String[] command) throws Exception {\n\t\treturn execute(hostport, login, password, beanname, command, false);\n\t}\n\n\tpublic Object[] executeOneCmd(final String hostport, final String login, final String password,\n\t\t\tfinal String beanname, final String command) throws Exception {\n\t\treturn execute(hostport, login, password, beanname, new String[]{command}, true);\n\t}\n\n\t/**\n\t * Execute command against remote JMX agent.\n\t * @param hostportOrPid 'host:port' combination.\n\t * @param login RMI login to use.\n\t * @param password RMI password to use.\n\t * @param beanname Name of remote bean to run command against.\n\t * @param command Array of commands to run.\n\t * @param oneBeanOnly Set true if passed <code>beanname</code> is an exact name and the query for a bean is only\n\t * supposed to return one bean instance. If not, we raise an exception (Otherwise, if false, then we deal with\n\t * possibility of multiple bean instances coming back from query). Set to true when want to get an attribute or run\n\t * an operation.\n\t * @return Array of results -- one per command.\n\t * @throws Exception\n\t */\n\tprotected Object[] execute(final String hostportOrPid, final String login, final String password,\n\t\t\tfinal String beanname, String[] command, final boolean oneBeanOnly) throws Exception {\n\t\tJMXConnector jmxc = connect(hostportOrPid, login, password);\n\t\tObject[] result = null;\n\n\t\ttry {\n\t\t\tMBeanServerConnection mbsc = jmxc.getMBeanServerConnection();\n\t\t\tresult = doBeans(mbsc, getObjectName(beanname), command, oneBeanOnly);\n\t\t} finally {\n\t\t\tjmxc.close();\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static ObjectName getObjectName(final String beanname)\n\t\t\tthrows MalformedObjectNameException, NullPointerException {\n\t\treturn notEmpty(beanname) ? new ObjectName(beanname) : null;\n\t}\n\n\tpublic static boolean notEmpty(String s) {\n\t\treturn s != null && s.length() > 0;\n\t}\n\n\tprotected static Object[] doBeans(final MBeanServerConnection mbsc, final ObjectName objName,\n\t\t\tfinal String[] command, final boolean oneBeanOnly) throws Exception {\n\t\tObject[] result = null;\n\t\tSet beans = mbsc.queryMBeans(objName, null);\n\t\tif (beans.isEmpty()) {\n\t\t\t// No bean found. Check if we are to create a bean?\n\t\t\tif (command.length == 1 && notEmpty(command[0]) && command[0].startsWith(CREATE_CMD_PREFIX)) {\n\t\t\t\tString className = command[0].substring(CREATE_CMD_PREFIX.length());\n\t\t\t\tmbsc.createMBean(className, objName);\n\t\t\t} else {\n\t\t\t\t// TODO: Is there a better JMX exception that RE for this\n\t\t\t\t// scenario?\n\t\t\t\tthrow new RuntimeException(objName.getCanonicalName() + \" not registered.\");\n\t\t\t}\n\t\t} else if (beans.size() == 1) {\n\t\t\tresult = doBean(mbsc, (ObjectInstance) beans.iterator().next(), command);\n\t\t} else {\n\t\t\tif (oneBeanOnly) {\n\t\t\t\tthrow new RuntimeException(\"Only supposed to be one bean \" + \"query result\");\n\t\t\t}\n\t\t\t// This is case of multiple beans in query results.\n\t\t\t// Print name of each into a StringBuffer. Return as one\n\t\t\t// result.\n\t\t\tStringBuffer buffer = new StringBuffer();\n\t\t\tfor (Iterator i = beans.iterator(); i.hasNext();) {\n\t\t\t\tObject obj = i.next();\n\t\t\t\tif (obj instanceof ObjectName) {\n\t\t\t\t\tbuffer.append((((ObjectName) obj).getCanonicalName()));\n\t\t\t\t} else if (obj instanceof ObjectInstance) {\n\t\t\t\t\tbuffer.append((((ObjectInstance) obj).getObjectName().getCanonicalName()));\n\t\t\t\t} else {\n\t\t\t\t\tthrow new RuntimeException(\"Unexpected object type: \" + obj);\n\t\t\t\t}\n\t\t\t\tbuffer.append(\"\\n\");\n\t\t\t}\n\t\t\tresult = new String[]{buffer.toString()};\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get attribute or run operation against passed bean <code>instance</code>.\n\t * \n\t * @param mbsc Server connection.\n\t * @param instance Bean instance we're to get attributes from or run operation against.\n\t * @param command Command to run (May be null).\n\t * @return Result. If multiple commands, multiple results.\n\t * @throws Exception\n\t */\n\tprotected static Object[] doBean(MBeanServerConnection mbsc, ObjectInstance instance, String[] command)\n\t\t\tthrows Exception {\n\t\t// If no command, then print out list of attributes and operations.\n\t\tif (command == null || command.length <= 0) {\n\t\t\treturn new String[]{listOptions(mbsc, instance)};\n\t\t}\n\n\t\t// Maybe multiple attributes/operations listed on one command line.\n\t\tObject[] result = new Object[command.length];\n\t\tfor (int i = 0; i < command.length; i++) {\n\t\t\tresult[i] = doSubCommand(mbsc, instance, command[i]);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static Object doSubCommand(MBeanServerConnection mbsc, ObjectInstance instance, String subCommand)\n\t\t\tthrows Exception {\n\t\t// First, handle special case of our being asked to destroy a bean.\n\t\tif (subCommand.equals(\"destroy\")) {\n\t\t\tmbsc.unregisterMBean(instance.getObjectName());\n\t\t\treturn null;\n\t\t} else if (subCommand.startsWith(CREATE_CMD_PREFIX)) {\n\t\t\tthrow new IllegalArgumentException(\"You cannot call create \" + \"on an already existing bean.\");\n\t\t}\n\n\t\t// Get attribute and operation info.\n\t\tMBeanAttributeInfo[] attributeInfo = mbsc.getMBeanInfo(instance.getObjectName()).getAttributes();\n\t\tMBeanOperationInfo[] operationInfo = mbsc.getMBeanInfo(instance.getObjectName()).getOperations();\n\t\t// Now, bdbje JMX bean doesn't follow the convention of attributes\n\t\t// having uppercase first letter and operations having lowercase\n\t\t// first letter. But most beans do. Be prepared to handle the bdbje\n\t\t// case.\n\t\tObject result = null;\n\t\tif (Character.isUpperCase(subCommand.charAt(0))) {\n\t\t\t// Probably an attribute.\n\t\t\tif (!isFeatureInfo(attributeInfo, subCommand) && isFeatureInfo(operationInfo, subCommand)) {\n\t\t\t\t// Its not an attribute name. Looks like its name of an\n\t\t\t\t// operation. Try it.\n\t\t\t\tresult = doBeanOperation(mbsc, instance, subCommand, operationInfo);\n\t\t\t} else {\n\t\t\t\t// Then it is an attribute OR its not an attribute name nor\n\t\t\t\t// operation name and the below invocation will throw a\n\t\t\t\t// AttributeNotFoundException.\n\t\t\t\tresult = doAttributeOperation(mbsc, instance, subCommand, attributeInfo);\n\t\t\t}\n\t\t} else {\n\t\t\t// Must be an operation.\n\t\t\tif (!isFeatureInfo(operationInfo, subCommand) && isFeatureInfo(attributeInfo, subCommand)) {\n\t\t\t\t// Its not an operation name but looks like it could be an\n\t\t\t\t// attribute name. Try it.\n\t\t\t\tresult = doAttributeOperation(mbsc, instance, subCommand, attributeInfo);\n\t\t\t} else {\n\t\t\t\t// Its an operation name OR its neither operation nor attribute\n\t\t\t\t// name and the below will throw a NoSuchMethodException.\n\t\t\t\tresult = doBeanOperation(mbsc, instance, subCommand, operationInfo);\n\t\t\t}\n\t\t}\n\n\t\t// Look at the result. Is it of composite or tabular type?\n\t\t// If so, convert to a String representation.\n\t\tif (result instanceof CompositeData) {\n\t\t\tresult = recurseCompositeData(new StringBuffer(\"\\n\"), \"\", \"\", (CompositeData) result);\n\t\t} else if (result instanceof TabularData) {\n\t\t\tresult = recurseTabularData(new StringBuffer(\"\\n\"), \"\", \"\", (TabularData) result);\n\t\t} else if (result instanceof String[]) {\n\t\t\tString[] strs = (String[]) result;\n\t\t\tStringBuffer buffer = new StringBuffer(\"\\n\");\n\t\t\tfor (int i = 0; i < strs.length; i++) {\n\t\t\t\tbuffer.append(strs[i]);\n\t\t\t\tbuffer.append(\"\\n\");\n\t\t\t}\n\t\t\tresult = buffer;\n\t\t} else if (result instanceof AttributeList) {\n\t\t\tAttributeList list = (AttributeList) result;\n\t\t\tif (list.isEmpty()) {\n\t\t\t\tresult = null;\n\t\t\t} else {\n\t\t\t\tStringBuffer buffer = new StringBuffer(\"\\n\");\n\t\t\t\tfor (Iterator ii = list.iterator(); ii.hasNext();) {\n\t\t\t\t\tAttribute a = (Attribute) ii.next();\n\t\t\t\t\tbuffer.append(a.getName());\n\t\t\t\t\tbuffer.append(\": \");\n\t\t\t\t\tbuffer.append(a.getValue());\n\t\t\t\t\tbuffer.append(\"\\n\");\n\t\t\t\t}\n\t\t\t\tresult = buffer;\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\tprotected static boolean isFeatureInfo(MBeanFeatureInfo[] infos, String cmd) {\n\t\treturn getFeatureInfo(infos, cmd) != null;\n\t}\n\n\tprotected static MBeanFeatureInfo getFeatureInfo(MBeanFeatureInfo[] infos, String cmd) {\n\t\t// Cmd may be carrying arguments. Don't count them in the compare.\n\t\tint index = cmd.indexOf('=');\n\t\tString name = (index > 0) ? cmd.substring(0, index) : cmd;\n\t\tfor (int i = 0; i < infos.length; i++) {\n\t\t\tif (infos[i].getName().equals(name)) {\n\t\t\t\treturn infos[i];\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprotected static StringBuffer recurseTabularData(StringBuffer buffer, String indent, String name,\n\t\t\tTabularData data) {\n\t\taddNameToBuffer(buffer, indent, name);\n\t\tjava.util.Collection c = data.values();\n\t\tfor (Iterator i = c.iterator(); i.hasNext();) {\n\t\t\tObject obj = i.next();\n\t\t\tif (obj instanceof CompositeData) {\n\t\t\t\trecurseCompositeData(buffer, indent + \" \", \"\", (CompositeData) obj);\n\t\t\t} else if (obj instanceof TabularData) {\n\t\t\t\trecurseTabularData(buffer, indent, \"\", (TabularData) obj);\n\t\t\t} else {\n\t\t\t\tbuffer.append(obj);\n\t\t\t}\n\t\t}\n\t\treturn buffer;\n\t}\n\n\tprotected static StringBuffer recurseCompositeData(StringBuffer buffer, String indent, String name,\n\t\t\tCompositeData data) {\n\t\tindent = addNameToBuffer(buffer, indent, name);\n\t\tfor (Iterator i = data.getCompositeType().keySet().iterator(); i.hasNext();) {\n\t\t\tString key = (String) i.next();\n\t\t\tObject o = data.get(key);\n\t\t\tif (o instanceof CompositeData) {\n\t\t\t\trecurseCompositeData(buffer, indent + \" \", key, (CompositeData) o);\n\t\t\t} else if (o instanceof TabularData) {\n\t\t\t\trecurseTabularData(buffer, indent, key, (TabularData) o);\n\t\t\t} else {\n\t\t\t\tbuffer.append(indent);\n\t\t\t\tbuffer.append(key);\n\t\t\t\tbuffer.append(\": \");\n\t\t\t\tbuffer.append(o);\n\t\t\t\tbuffer.append(\"\\n\");\n\t\t\t}\n\t\t}\n\t\treturn buffer;\n\t}\n\n\tprotected static String addNameToBuffer(StringBuffer buffer, String indent, String name) {\n\t\tif (name == null || name.length() == 0) {\n\t\t\treturn indent;\n\t\t}\n\t\tbuffer.append(indent);\n\t\tbuffer.append(name);\n\t\tbuffer.append(\":\\n\");\n\t\t// Move all that comes under this 'name' over by one space.\n\t\treturn indent + \" \";\n\t}\n\n\t/**\n\t * Class that parses commandline arguments. Expected format is 'operationName=arg0,arg1,arg2...'. We are assuming no\n\t * spaces nor comma's in argument values.\n\t */\n\tprotected static class CommandParse {\n\t\tprivate String cmd;\n\t\tprivate String[] args;\n\n\t\tprotected CommandParse(String command) throws ParseException {\n\t\t\tparse(command);\n\t\t}\n\n\t\tprivate void parse(String command) throws ParseException {\n\t\t\tMatcher m = CMD_LINE_ARGS_PATTERN.matcher(command);\n\t\t\tif (m == null || !m.matches()) {\n\t\t\t\tthrow new ParseException(\"Failed parse of \" + command, 0);\n\t\t\t}\n\n\t\t\tthis.cmd = m.group(1);\n\t\t\tif (m.group(2) != null && m.group(2).length() > 0) {\n\t\t\t\tthis.args = m.group(2).split(\",\");\n\t\t\t} else {\n\t\t\t\tthis.args = null;\n\t\t\t}\n\t\t}\n\n\t\tprotected String getCmd() {\n\t\t\treturn this.cmd;\n\t\t}\n\n\t\tprotected String[] getArgs() {\n\t\t\treturn this.args;\n\t\t}\n\t}\n\n\tprotected static Object doAttributeOperation(MBeanServerConnection mbsc, ObjectInstance instance, String command,\n\t\t\tMBeanAttributeInfo[] infos) throws Exception {\n\t\t// Usually we get attributes. If an argument, then we're being asked\n\t\t// to set attribute.\n\t\tCommandParse parse = new CommandParse(command);\n\t\tif (parse.getArgs() == null || parse.getArgs().length == 0) {\n\t\t\t// Special-casing. If the subCommand is 'Attributes', then return\n\t\t\t// list of all attributes.\n\t\t\tif (command.equals(\"Attributes\")) {\n\t\t\t\tString[] names = new String[infos.length];\n\t\t\t\tfor (int i = 0; i < infos.length; i++) {\n\t\t\t\t\tnames[i] = infos[i].getName();\n\t\t\t\t}\n\t\t\t\treturn mbsc.getAttributes(instance.getObjectName(), names);\n\t\t\t}\n\t\t\treturn mbsc.getAttribute(instance.getObjectName(), parse.getCmd());\n\t\t}\n\t\tif (parse.getArgs().length != 1) {\n\t\t\tthrow new IllegalArgumentException(\"One only argument setting \" + \"attribute values: \" + parse.getArgs());\n\t\t}\n\t\t// Get first attribute of name 'cmd'. Assumption is no method\n\t\t// overrides. Then, look at the attribute and use its type.\n\t\tMBeanAttributeInfo info = (MBeanAttributeInfo) getFeatureInfo(infos, parse.getCmd());\n\t\tjava.lang.reflect.Constructor c = Class.forName(info.getType()).getConstructor(new Class[]{String.class});\n\t\tAttribute a = new Attribute(parse.getCmd(), c.newInstance(new Object[]{parse.getArgs()[0]}));\n\t\tmbsc.setAttribute(instance.getObjectName(), a);\n\t\treturn null;\n\t}\n\n\tprotected static Object doBeanOperation(MBeanServerConnection mbsc, ObjectInstance instance, String command,\n\t\t\tMBeanOperationInfo[] infos) throws Exception {\n\t\t// Parse command line.\n\t\tCommandParse parse = new CommandParse(command);\n\n\t\t// Get first method of name 'cmd'. Assumption is no method\n\t\t// overrides. Then, look at the method and use its signature\n\t\t// to make sure client sends over parameters of the correct type.\n\t\tMBeanOperationInfo op = (MBeanOperationInfo) getFeatureInfo(infos, parse.getCmd());\n\t\tObject result = null;\n\t\tif (op == null) {\n\t\t\tresult = \"Operation \" + parse.getCmd() + \" not found.\";\n\t\t} else {\n\t\t\tMBeanParameterInfo[] paraminfos = op.getSignature();\n\t\t\tint paraminfosLength = (paraminfos == null) ? 0 : paraminfos.length;\n\t\t\tint objsLength = (parse.getArgs() == null) ? 0 : parse.getArgs().length;\n\t\t\tif (paraminfosLength != objsLength) {\n\t\t\t\tresult = \"Passed param count does not match signature count\";\n\t\t\t} else {\n\t\t\t\tString[] signature = new String[paraminfosLength];\n\t\t\t\tObject[] params = (paraminfosLength == 0) ? null : new Object[paraminfosLength];\n\t\t\t\tfor (int i = 0; i < paraminfosLength; i++) {\n\t\t\t\t\tMBeanParameterInfo paraminfo = paraminfos[i];\n\t\t\t\t\tjava.lang.reflect.Constructor c = Class.forName(paraminfo.getType())\n\t\t\t\t\t\t\t.getConstructor(new Class[]{String.class});\n\t\t\t\t\tparams[i] = c.newInstance(new Object[]{parse.getArgs()[i]});\n\t\t\t\t\tsignature[i] = paraminfo.getType();\n\t\t\t\t}\n\t\t\t\tresult = mbsc.invoke(instance.getObjectName(), parse.getCmd(), params, signature);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\tprotected static String listOptions(MBeanServerConnection mbsc, ObjectInstance instance)\n\t\t\tthrows InstanceNotFoundException, IntrospectionException, ReflectionException, IOException {\n\t\tStringBuffer result = new StringBuffer();\n\t\tMBeanInfo info = mbsc.getMBeanInfo(instance.getObjectName());\n\t\tMBeanAttributeInfo[] attributes = info.getAttributes();\n\t\tif (attributes.length > 0) {\n\t\t\tresult.append(\"Attributes:\");\n\t\t\tresult.append(\"\\n\");\n\t\t\tfor (int i = 0; i < attributes.length; i++) {\n\t\t\t\tresult.append(' ' + attributes[i].getName() + \": \" + attributes[i].getDescription() + \" (type=\"\n\t\t\t\t\t\t+ attributes[i].getType() + \")\");\n\t\t\t\tresult.append(\"\\n\");\n\t\t\t}\n\t\t}\n\t\tMBeanOperationInfo[] operations = info.getOperations();\n\t\tif (operations.length > 0) {\n\t\t\tresult.append(\"Operations:\");\n\t\t\tresult.append(\"\\n\");\n\t\t\tfor (int i = 0; i < operations.length; i++) {\n\t\t\t\tMBeanParameterInfo[] params = operations[i].getSignature();\n\t\t\t\tStringBuffer paramsStrBuffer = new StringBuffer();\n\t\t\t\tif (params != null) {\n\t\t\t\t\tfor (int j = 0; j < params.length; j++) {\n\t\t\t\t\t\tparamsStrBuffer.append(\"\\n   name=\");\n\t\t\t\t\t\tparamsStrBuffer.append(params[j].getName());\n\t\t\t\t\t\tparamsStrBuffer.append(\" type=\");\n\t\t\t\t\t\tparamsStrBuffer.append(params[j].getType());\n\t\t\t\t\t\tparamsStrBuffer.append(\" \");\n\t\t\t\t\t\tparamsStrBuffer.append(params[j].getDescription());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresult.append(' ' + operations[i].getName() + \": \" + operations[i].getDescription() + \"\\n  Parameters \"\n\t\t\t\t\t\t+ params != null ? params.length\n\t\t\t\t\t\t\t\t: 0 + \", return type=\" + operations[i].getReturnType() + paramsStrBuffer.toString());\n\t\t\t\tresult.append(\"\\n\");\n\t\t\t}\n\t\t}\n\t\treturn result.toString();\n\t}\n\n\t/**\n\t * Logger that writes entry on one line with less verbose date. Modelled on the OneLineSimpleLogger from Heritrix.\n\t * \n\t * @author stack\n\t * @version $Revision$, $Date$\n\t */\n\tprivate class OneLineSimpleLogger extends SimpleFormatter {\n\t\t/**\n\t\t * Date instance.\n\t\t * \n\t\t * Keep around instance of date.\n\t\t */\n\t\tprivate Date date = new Date();\n\n\t\t/**\n\t\t * Field position instance.\n\t\t * \n\t\t * Keep around this instance.\n\t\t */\n\t\tprivate FieldPosition position = new FieldPosition(0);\n\n\t\t/**\n\t\t * MessageFormatter for date.\n\t\t */\n\t\tprivate SimpleDateFormat formatter = new SimpleDateFormat(\"MM/dd/yyyy HH:mm:ss Z\");\n\n\t\t/**\n\t\t * Persistent buffer in which we conjure the log.\n\t\t */\n\t\tprivate StringBuffer buffer = new StringBuffer();\n\n\t\tpublic OneLineSimpleLogger() {\n\t\t\tsuper();\n\t\t}\n\n\t\t@Override\n\t\tpublic synchronized String format(LogRecord record) {\n\t\t\tthis.buffer.setLength(0);\n\t\t\tthis.date.setTime(record.getMillis());\n\t\t\tthis.position.setBeginIndex(0);\n\t\t\tthis.formatter.format(this.date, this.buffer, this.position);\n\t\t\tthis.buffer.append(' ');\n\t\t\tif (record.getSourceClassName() != null) {\n\t\t\t\tthis.buffer.append(record.getSourceClassName());\n\t\t\t} else {\n\t\t\t\tthis.buffer.append(record.getLoggerName());\n\t\t\t}\n\t\t\tthis.buffer.append(' ');\n\t\t\tthis.buffer.append(formatMessage(record));\n\t\t\tthis.buffer.append(System.getProperty(\"line.separator\"));\n\t\t\tif (record.getThrown() != null) {\n\t\t\t\ttry {\n\t\t\t\t\tStringWriter writer = new StringWriter();\n\t\t\t\t\tPrintWriter printer = new PrintWriter(writer);\n\t\t\t\t\trecord.getThrown().printStackTrace(printer);\n\t\t\t\t\twriter.close();\n\t\t\t\t\tthis.buffer.append(writer.toString());\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tthis.buffer.append(\"Failed to get stack trace: \" + e.getMessage());\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn this.buffer.toString();\n\t\t}\n\t}\n\n\tprivate static int getJavaMajorVersion(String javaSpecificationVersion) {\n\t\tif (javaSpecificationVersion.startsWith(\"1.8\")) {\n\t\t\treturn 8;\n\t\t} else if (javaSpecificationVersion.startsWith(\"1.7\")) {\n\t\t\treturn 7;\n\t\t} else if (javaSpecificationVersion.startsWith(\"1.6\")) {\n\t\t\treturn 6;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\treturn Integer.parseInt(javaSpecificationVersion);\n\t\t\t} catch (NumberFormatException e) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "vjmxcli/src/main/java/com/vip/vjtools/jmx/ExtraCommand.java",
    "content": "package com.vip.vjtools.jmx;\n\nimport javax.management.MBeanServerConnection;\nimport javax.management.remote.JMXConnector;\n\npublic class ExtraCommand {\n\n\tpublic void execute(final String hostportOrPid, final String login, final String password, final String beanname,\n\t\t\tint interval) throws Exception {\n\t\tJMXConnector jmxc = Client.connect(hostportOrPid, login, password);\n\n\t\ttry {\n\t\t\tMBeanServerConnection mbsc = jmxc.getMBeanServerConnection();\n\n\t\t\tgcUtilCommand(mbsc, interval);\n\t\t} finally {\n\t\t\tjmxc.close();\n\t\t}\n\t}\n\n\tprivate void gcUtilCommand(MBeanServerConnection mbsc, int interval) throws Exception {\n\t\tGCutilExpression gcE = new GCutilExpression(mbsc);\n\n\t\tString[] commands;\n\n\t\tif (getJavaVersion(mbsc) > 7) {\n\t\t\tcommands = new String[]{\"S\", \"S\", \"E\", \"O\", \"M\", \"CCS\", \"YGC\", \"YGCT\", \"FGC\", \"FGCT\", \"GCT\"};\n\t\t} else {\n\t\t\tcommands = new String[]{\"S\", \"S\", \"E\", \"O\", \"P\", \"YGC\", \"YGCT\", \"FGC\", \"FGCT\", \"GCT\"};\n\t\t}\n\n\t\tfor (String commmand : commands) {\n\t\t\tSystem.out.print(commmand + \"\\t\");\n\t\t}\n\t\tSystem.out.print(\"\\n\");\n\n\t\twhile (true) {\n\t\t\tObject[] results = executGCutil(commands, gcE);\n\t\t\tfor (Object result : results) {\n\t\t\t\tSystem.out.print(result.toString() + \"\\t\");\n\t\t\t}\n\t\t\tSystem.out.print(\"\\n\");\n\t\t\tif (interval == 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tThread.sleep(interval * 1000);\n\t\t}\n\t}\n\n\tprivate Object[] executGCutil(final String[] commands, GCutilExpression gcE) throws Exception {\n\t\tObject[] result = new Object[commands.length];\n\n\t\tfor (int i = 0; i < commands.length; i++) {\n\t\t\tString command = commands[i];\n\t\t\tif (\"S\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getS();\n\t\t\t} else if (\"E\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getE();\n\t\t\t} else if (\"O\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getO();\n\t\t\t} else if (\"M\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getP();\n\t\t\t} else if (\"P\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getP();\n\t\t\t} else if (\"CCS\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getCCS();\n\t\t\t} else if (\"YGC\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getYGC();\n\t\t\t} else if (\"YGCT\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getYGCT();\n\t\t\t} else if (\"FGC\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getFGC();\n\t\t\t} else if (\"FGCT\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getFGCT();\n\t\t\t} else if (\"GCT\".equals(command)) {\n\t\t\t\tresult[i] = gcE.getGCT();\n\t\t\t} else {\n\t\t\t\tthrow new RuntimeException(\"Unknown Command:\" + command);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int getJavaVersion(final MBeanServerConnection mbsc) throws Exception {\n\t\tObject version = mbsc.getAttribute(Client.getObjectName(\"java.lang:type=Runtime\"), \"SpecVersion\");\n\t\tString javaVersion = version.toString();\n\t\tif (javaVersion.startsWith(\"1.8\")) {\n\t\t\treturn 8;\n\t\t} else if (javaVersion.startsWith(\"1.7\")) {\n\t\t\treturn 7;\n\t\t} else if (javaVersion.startsWith(\"1.6\")) {\n\t\t\treturn 6;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\treturn Integer.parseInt(javaVersion);\n\t\t\t} catch (NumberFormatException e) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjmxcli/src/main/java/com/vip/vjtools/jmx/GCutilExpression.java",
    "content": "package com.vip.vjtools.jmx;\n\nimport java.text.DecimalFormat;\nimport java.util.Set;\n\nimport javax.management.MBeanServerConnection;\nimport javax.management.ObjectInstance;\nimport javax.management.ObjectName;\nimport javax.management.openmbean.CompositeDataSupport;\n\npublic class GCutilExpression {\n\n\t// MBean Name\n\tprivate static final String GARBAGE_COLLECTORS = \"java.lang:type=GarbageCollector,name=*\";\n\tprivate static final String MEM_POOL_PREFIX = \"java.lang:type=MemoryPool,name=\";\n\n\t// Collector的Attribute Name\n\tprivate static final String COLLECTION_TIME_ATTRIBUTE = \"CollectionTime\";\n\tprivate static final String COLLECTION_COUNT_ATTRIBUTE = \"CollectionCount\";\n\n\tprivate static final DecimalFormat DF = new DecimalFormat(\"0.00\");\n\n\tprivate MBeanServerConnection mbsc;\n\n\tprivate ObjectName ygcCollector;\n\tprivate ObjectName fgcCollector;\n\tprivate ObjectName eden;\n\tprivate ObjectName old;\n\tprivate ObjectName sur;\n\tprivate ObjectName perm;\n\tprivate ObjectName ccs;\n\n\tpublic GCutilExpression(MBeanServerConnection mbsc) throws Exception {\n\t\tthis.mbsc = mbsc;\n\t\tmappingCollctors();\n\t\tmappingPools();\n\t}\n\n\tprivate void mappingCollctors() throws Exception {\n\t\tSet<ObjectInstance> beans = mbsc.queryMBeans(Client.getObjectName(GARBAGE_COLLECTORS), null);\n\n\t\tfor (ObjectInstance collector : beans) {\n\t\t\tObjectName collectorObjName = collector.getObjectName();\n\t\t\tString collectorName = getAttribute(collectorObjName, \"Name\");\n\t\t\tif (\"Copy\".equals(collectorName) || \"PS Scavenge\".equals(collectorName) || \"ParNew\".equals(collectorName)\n\t\t\t\t\t|| \"G1 Young Generation\".equals(collectorName)) {\n\t\t\t\tygcCollector = collectorObjName;\n\t\t\t} else if (\"MarkSweepCompact\".equals(collectorName) || \"PS MarkSweep\".equals(collectorName)\n\t\t\t\t\t|| \"ConcurrentMarkSweep\".equals(collectorName) || \"G1 Old Generation\".equals(collectorName)) {\n\t\t\t\tfgcCollector = collectorObjName;\n\t\t\t} else {\n\t\t\t\tygcCollector = collectorObjName;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void mappingPools() throws Exception {\n\t\tSet<ObjectInstance> beans = mbsc.queryMBeans(Client.getObjectName(MEM_POOL_PREFIX + \"*\"), null);\n\t\tfor (ObjectInstance pool : beans) {\n\t\t\tObjectName poolObjName = pool.getObjectName();\n\t\t\tString poolName = getAttribute(poolObjName, \"Name\");\n\t\t\tpoolName = poolName.trim().toLowerCase();\n\t\t\tif (poolName.contains(\"eden\")) {\n\t\t\t\teden = poolObjName;\n\t\t\t} else if (poolName.contains(\"survivor\")) {\n\t\t\t\tsur = poolObjName;\n\t\t\t} else if (poolName.contains(\"old\") || poolName.contains(\"tenured\")) {\n\t\t\t\told = poolObjName;\n\t\t\t} else if (poolName.contains(\"perm\") || poolName.contains(\"metaspace\")) {\n\t\t\t\tperm = poolObjName;\n\t\t\t} else if (poolName.contains(\"compressed class space\")) {\n\t\t\t\tccs = poolObjName;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic String getE() throws Exception {\n\t\treturn usedPercentage(eden);\n\t}\n\n\tpublic String getS() throws Exception {\n\t\treturn usedPercentage(sur);\n\t}\n\n\tpublic String getO() throws Exception {\n\t\treturn usedPercentage(old);\n\t}\n\n\tpublic String getP() throws Exception {\n\t\treturn usedPercentage(perm);\n\t}\n\n\tpublic String getCCS() throws Exception {\n\t\treturn usedPercentage(ccs);\n\t}\n\n\tpublic Object getYGC() throws Exception {\n\t\treturn getAttribute(ygcCollector, COLLECTION_COUNT_ATTRIBUTE);\n\t}\n\n\tpublic Double getYGCT() throws Exception {\n\t\treturn Double.parseDouble(getAttribute(ygcCollector, COLLECTION_TIME_ATTRIBUTE).toString()) / 1000;\n\t}\n\n\tpublic Object getFGC() throws Exception {\n\t\tif (fgcCollector == null) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn getAttribute(fgcCollector, COLLECTION_COUNT_ATTRIBUTE);\n\t}\n\n\tpublic Double getFGCT() throws Exception {\n\t\tif (fgcCollector == null) {\n\t\t\treturn 0.0;\n\t\t}\n\t\treturn Double.parseDouble(getAttribute(fgcCollector, COLLECTION_TIME_ATTRIBUTE).toString()) / 1000;\n\t}\n\n\tpublic Object getGCT() throws Exception {\n\t\treturn getFGCT() + getYGCT();\n\t}\n\n\tprivate <T> T getAttribute(ObjectName beanName, String attributeName) throws Exception {\n\t\tif (beanName != null) {\n\t\t\treturn (T) mbsc.getAttribute(beanName, attributeName);\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate String usedPercentage(ObjectName poolObjectName) throws Exception {\n\t\tif (poolObjectName == null) {\n\t\t\treturn DF.format(0.0d);\n\t\t}\n\n\t\tCompositeDataSupport usage = getAttribute(poolObjectName, \"Usage\");\n\t\tdouble max = Double.parseDouble(usage.get(\"max\").toString());\n\n\t\t// 如果Max没有设置或GC算法原因没有max，则以committed为准\n\t\tif (max < 0) {\n\t\t\tmax = Double.parseDouble(usage.get(\"committed\").toString());\n\t\t}\n\n\t\tif (max > 0.0d) {\n\t\t\tdouble used = Double.parseDouble(usage.get(\"used\").toString()) / max * 100;\n\t\t\treturn DF.format(used);\n\t\t} else {\n\t\t\treturn DF.format(0.0d);\n\t\t}\n\t}\n}"
  },
  {
    "path": "vjstar/README.md",
    "content": "# 1. 概述\n\n服务化应用的性能、可用性的最佳实践封装，主要体现思路。\n\n# 2. 实践列表\n\n## 2.1 JVM启动参数\n\n参数兼顾性能及排查问题的便捷性的JVM启动参数推荐， 其中一些参数需要根据JDK版本适配。\n\n源码： [jvm-options](https://github.com/vipshop/vjtools/blob/master/vjstar/src/main/script/jvm-options)\n解读：[《关键业务系统的JVM参数推荐》](http://calvin1978.blogcn.com/?p=1602)\n\n## 2.2 容器中JVM获取CPU核数的通用补丁\n\n容器中的JVM，获取的仍然是宿主机的CPU核数，从而引起GC线程数，Netty线程数等一系列混乱。据说JDK8的最新版解决了这个问题，但其他版本的JDK则建议使用此补丁。\n基于[libsysconfcpus](https://github.com/obmarg/libsysconfcpus)，详见[docker-cpus](https://github.com/vipshop/vjtools/blob/master/vjstar/src/main/script/docker-cpus)。\n\n## 2.3 闲时主动GC\n\nCMS GC 始终对流量有一定的影响。\n\n因此我们希望在夜半闲时，如果检测到老生代已经达到50%， 则主动进行一次GC。\n\n简单的定时器让应用固定在可设定的闲时（如半夜）进行清理动作。 为了避免服务的所有实例同时清理造成服务不可用，加入了随机值。\n\n详见[Proactive GC](https://github.com/vipshop/vjtools/tree/master/vjstar/src/main/java/com/vip/vjstar/gc)\n\n## 2.4 滑动窗口计数器（试验）\n\n滑动窗口的计数器（比如任意时刻的最近一分钟请求数）在熔断计算等方面的使用很广泛，但没有比较标准且抽象成通用类库的实现，我们在考察了几家实现的实现。\n\n详见[Sliding Window](https://github.com/vipshop/vjtools/tree/master/vjstar/src/main/java/com/vip/vjstar/window)(试验性的新方案，还没替换生产上的旧方案)\n\n## 2.5 动态隔离线程池(TODO)\n\n我们希望一个业务方法缓慢时，不会把整个线程池塞爆导致所有方法都不能响应。\n\n但是为每个方法配置独立线程池又存在配置困难和浪费问题，因此我们希望简单实现一个线程池，在平时使用公共池，当某个方法出现问题时对其进行隔离，当问题消失时又自动恢复到公共池。\n\n\n"
  },
  {
    "path": "vjstar/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<groupId>com.vip.vjtools</groupId>\n\t<artifactId>vjstar</artifactId>\n\t<version>1.0.9-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\t<name>vjstar</name>\n\t<description>VIP's best practice libraries</description>\n\n\t<properties>\n\t\t<junit.version>4.12</junit.version>\n\t\t<assertj.version>2.6.0</assertj.version>\n\t\t<mockito.version>2.18.3</mockito.version>\n\t\t<logback.version>1.1.8</logback.version>\n\t\t<!-- Plugin的属性 -->\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<java.version>1.7</java.version>\n\t\t<maven.compiler.source>${java.version}</maven.compiler.source>\n\t\t<maven.compiler.target>${java.version}</maven.compiler.target>\n\t\t<maven-gpg-plugin.version>1.6</maven-gpg-plugin.version>\n\t</properties>\n\n\t<!-- 尽量少的引入依赖包，方便单独重用本模块 -->\n\t<dependencies>\n\n\t\t<!-- Util -->\n\t\t<dependency>\n\t\t\t<groupId>com.vip.vjtools</groupId>\n\t\t\t<artifactId>vjkit</artifactId>\n\t\t\t<version>${project.version}</version>\n\t\t</dependency>\n\t\t\n\n\t\t<!-- Test -->\n\t\t<dependency>\n\t\t\t<groupId>junit</groupId>\n\t\t\t<artifactId>junit</artifactId>\n\t\t\t<version>${junit.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.assertj</groupId>\n\t\t\t<artifactId>assertj-core</artifactId>\n\t\t\t<version>${assertj.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.mockito</groupId>\n\t\t\t<artifactId>mockito-core</artifactId>\n\t\t\t<version>${mockito.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>ch.qos.logback</groupId>\n\t\t\t<artifactId>logback-classic</artifactId>\n\t\t\t<version>${logback.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>release</id>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<!-- javadoc attach plugin -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-javadoc-plugin</artifactId>\n\t\t\t\t\t\t<version>2.10.4</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-javadocs</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<!-- source attach plugin -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-source-plugin</artifactId>\n\t\t\t\t\t\t<version>3.0.1</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-sources</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-gpg-plugin</artifactId>\n\t\t\t\t\t\t<version>${maven-gpg-plugin.version}</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>sign-artifacts</id>\n\t\t\t\t\t\t\t\t<phase>verify</phase>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>sign</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\n\t\t<profile>\n\t\t\t<id>sonar</id>\n\t\t\t<properties>\n\t\t\t\t<sonar.java.source>1.7</sonar.java.source>\n\t\t\t\t<sonar.exclusions></sonar.exclusions>\n\t\t\t</properties>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.jacoco</groupId>\n\t\t\t\t\t\t<artifactId>jacoco-maven-plugin</artifactId>\n\t\t\t\t\t\t<version>0.7.2.201409121644</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<append>true</append>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>agent-for-ut</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.sonarsource.scanner.maven</groupId>\n\t\t\t\t\t\t<artifactId>sonar-maven-plugin</artifactId>\n\t\t\t\t\t\t<version>3.4.0.905</version>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t</profiles>\n\n\t<distributionManagement>\n\t\t<snapshotRepository>\n\t\t\t<id>sonatype-nexus-snapshots</id>\n\t\t\t<url>https://oss.sonatype.org/content/repositories/snapshots</url>\n\t\t</snapshotRepository>\n\t\t<repository>\n\t\t\t<id>sonatype-nexus-releases</id>\n\t\t\t<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>\n\t\t</repository>\n\t</distributionManagement>\n\n\t<url>https://github.com/vipshop/vjtools</url>\n\n\t<licenses>\n\t\t<license>\n\t\t\t<name>Apache License 2.0</name>\n\t\t\t<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n\t\t\t<distribution>repo</distribution>\n\t\t</license>\n\t</licenses>\n\n\t<scm>\n\t\t<connection>scm:git:https://github.com/vipshop/vjtools.git</connection>\n\t\t<developerConnection>scm:git:https://github.com/vipshop/vjtools.git</developerConnection>\n\t\t<url>https://github.com/vipshop/vjtools</url>\n\t</scm>\n\n\t<developers>\n\t\t<developer>\n\t\t\t<id>calvin</id>\n\t\t\t<name>Calvin Xiao</name>\n\t\t\t<email>calvin.xiao at vipshop.com</email>\n\t\t\t<roles>\n\t\t\t\t<role>developer</role>\n\t\t\t</roles>\n\t\t\t<timezone>+8</timezone>\n\t\t</developer>\n\t\t<developer>\n\t\t\t<id>dehuizheng</id>\n\t\t\t<name>Dehui Zheng</name>\n\t\t\t<email>dehui.zheng at vipshop.com</email>\n\t\t\t<roles>\n\t\t\t\t<role>developer</role>\n\t\t\t</roles>\n\t\t\t<timezone>+8</timezone>\n\t\t</developer>\n\t\t<developer>\n\t\t\t<id>lixuanbin</id>\n\t\t\t<name>Xuanbin.Li</name>\n\t\t\t<email>ben04.li at vipshop.com</email>\n\t\t\t<roles>\n\t\t\t\t<role>developer</role>\n\t\t\t</roles>\n\t\t\t<timezone>+8</timezone>\n\t\t</developer>\n\t</developers>\n</project>\n"
  },
  {
    "path": "vjstar/src/main/java/com/vip/vjstar/.gitkeep",
    "content": ""
  },
  {
    "path": "vjstar/src/main/java/com/vip/vjstar/gc/CleanUpScheduler.java",
    "content": "package com.vip.vjstar.gc;\n\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.time.DateUtils;\nimport org.apache.commons.lang3.time.FastDateFormat;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.vip.vjtools.vjkit.base.ExceptionUtil;\nimport com.vip.vjtools.vjkit.concurrent.threadpool.ThreadPoolUtil;\nimport com.vip.vjtools.vjkit.number.RandomUtil;\n\npublic class CleanUpScheduler {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(CleanUpScheduler.class);\n\n\tprivate ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1,\n\t\t\tThreadPoolUtil.buildThreadFactory(\"cleanup\", true));\n\n\tpublic void schedule(String schedulePlans, Runnable task) {\n\t\tList<Long> delayTimes = getDelayMillsList(schedulePlans);\n\t\tfor (long delayTime : delayTimes) {\n\t\t\tscheduler.schedule(task, delayTime, TimeUnit.MILLISECONDS);\n\t\t}\n\t}\n\n\tpublic void reschedule(Runnable task) {\n\t\tif (!scheduler.isShutdown()) {\n\t\t\ttry {\n\t\t\t\tscheduler.schedule(task, 24, TimeUnit.HOURS);\n\t\t\t} catch (Exception e) {\n\t\t\t\tlogger.error(e.getMessage(), e);\n\t\t\t}\n\t\t}\n\t}\n\t\n\tpublic void shutdown(){\n\t\tscheduler.shutdown();\n\t}\n\n\t/**\n\t * Generate delay millis list by given plans string, separated by comma.<br/>\n\t * eg, 03:00-05:00,13:00-14:00\n\t */\n\tpublic static List<Long> getDelayMillsList(String schedulePlans) {\n\t\tList<Long> result = new ArrayList<>();\n\t\tString[] plans = StringUtils.split(schedulePlans, ',');\n\t\tfor (String plan : plans) {\n\t\t\tresult.add(getDelayMillis(plan));\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get scheduled delay for proactive gc task，cross-day setting is supported.<br/>\n\t * 01:30-02:40，some time between 01:30-02:40；<br/>\n\t * 180000，180 seconds later.\n\t */\n\tpublic static long getDelayMillis(String time) {\n\t\tString pattern = \"HH:mm\";\n\t\tDate now = new Date();\n\t\tif (StringUtils.contains(time, \"-\")) {\n\t\t\tString start = time.split(\"-\")[0];\n\t\t\tString end = time.split(\"-\")[1];\n\t\t\tif (StringUtils.contains(start, \":\") && StringUtils.contains(end, \":\")) {\n\t\t\t\tDate d1 = getCurrentDateByPlan(start, pattern);\n\t\t\t\tDate d2 = getCurrentDateByPlan(end, pattern);\n\t\t\t\twhile (d1.before(now)) {\n\t\t\t\t\td1 = DateUtils.addDays(d1, 1);\n\t\t\t\t}\n\n\t\t\t\twhile (d2.before(d1)) {\n\t\t\t\t\td2 = DateUtils.addDays(d2, 1);\n\t\t\t\t}\n\n\t\t\t\treturn RandomUtil.nextLong(d1.getTime() - now.getTime(), d2.getTime() - now.getTime());\n\t\t\t}\n\t\t} else if (StringUtils.isNumeric(time)) {\n\t\t\treturn Long.parseLong(time);\n\t\t}\n\n\t\t// default\n\t\treturn getDelayMillis(\"02:00-05:00\");\n\t}\n\n\t/**\n\t * return current date time by specified hour:minute\n\t * \n\t * @param plan format: hh:mm\n\t */\n\tpublic static Date getCurrentDateByPlan(String plan, String pattern) {\n\t\ttry {\n\t\t\tFastDateFormat format = FastDateFormat.getInstance(pattern);\n\t\t\tDate end = format.parse(plan);\n\t\t\tCalendar today = Calendar.getInstance();\n\t\t\tend = DateUtils.setYears(end, (today.get(Calendar.YEAR)));\n\t\t\tend = DateUtils.setMonths(end, today.get(Calendar.MONTH));\n\t\t\tend = DateUtils.setDays(end, today.get(Calendar.DAY_OF_MONTH));\n\t\t\treturn end;\n\t\t} catch (Exception e) {\n\t\t\tthrow ExceptionUtil.unchecked(e);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjstar/src/main/java/com/vip/vjstar/gc/ProactiveGcTask.java",
    "content": "package com.vip.vjstar.gc;\n\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.MemoryPoolMXBean;\nimport java.lang.management.MemoryUsage;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.vip.vjtools.vjkit.number.UnitConverter;\n\n/**\n * Detect old gen usage of current jvm periodically and trigger a cms gc if necessary.<br/>\n * In order to enable this feature, add these options to your target jvm:<br/>\n * -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+ExplicitGCInvokesConcurrent<br/>\n * You can alter this class to work on a remote jvm using jmx.\n */\npublic class ProactiveGcTask implements Runnable {\n\tprivate static Logger logger = LoggerFactory.getLogger(ProactiveGcTask.class);\n\n\tprotected CleanUpScheduler scheduler;\n\tprotected int oldGenOccupancyFraction;\n\n\tprotected MemoryPoolMXBean oldGenMemoryPool;\n\tprotected long maxOldGenBytes;\n\tprotected boolean valid;\n\n\tpublic ProactiveGcTask(CleanUpScheduler scheduler, int oldGenOccupancyFraction) {\n\t\tthis.scheduler = scheduler;\n\t\tthis.oldGenOccupancyFraction = oldGenOccupancyFraction;\n\t\tthis.oldGenMemoryPool = getOldGenMemoryPool();\n\n\t\tif (oldGenMemoryPool != null && oldGenMemoryPool.isValid()) {\n\t\t\tthis.maxOldGenBytes = getMemoryPoolMaxOrCommitted(oldGenMemoryPool);\n\t\t\tthis.valid = true;\n\t\t} else {\n\t\t\tthis.valid = false;\n\t\t}\n\t}\n\n\tpublic void run() {\n\t\tif (!valid) {\n\t\t\tlogger.warn(\"OldMemoryPool is not valid, task stop.\");\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tlong usedOldGenBytes = logOldGenStatus(\"checking oldgen status\");\n\n\t\t\tif (needTriggerGc(maxOldGenBytes, usedOldGenBytes, oldGenOccupancyFraction)) {\n\t\t\t\tpreGc();\n\t\t\t\tdoGc();\n\t\t\t\tpostGc();\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\tlogger.error(e.getMessage(), e);\n\t\t} finally {\n\t\t\tscheduler.reschedule(this);\n\t\t}\n\t}\n\n\t/**\n\t * Determine whether or not to trigger gc.\n\t */\n\tprivate boolean needTriggerGc(long capacityBytes, long usedBytes, int occupancyFraction) {\n\t\treturn (occupancyFraction * capacityBytes / 100) < usedBytes;\n\t}\n\n\t/**\n\t * Suggests gc.\n\t */\n\tprotected void doGc() {\n\t\tSystem.gc(); // NOSONAR\n\t}\n\n\t/**\n\t * Stuff before gc. You can override this method to do your own stuff, for example, cache clean up, deregister from register center.\n\t */\n\tprotected void preGc() {\n\t\tlogger.warn(\"old gen is occupied larger than occupancy fraction[{}], trying to trigger gc...\",\n\t\t\t\toldGenOccupancyFraction);\n\t}\n\n\t/**\n\t * Stuff after gc. You can override this method to do your own stuff, for example, cache warmup, reregister to register center.\n\t */\n\tprotected void postGc() {\n\t\tlogOldGenStatus(\"post gc\");\n\t}\n\n\tprotected long logOldGenStatus(String hints) {\n\t\tlong usedOldBytes = oldGenMemoryPool.getUsage().getUsed();\n\t\tlogger.info(String.format(\"%s, max old gen:%s, used old gen:%s, current fraction: %.2f%%, gc fraction: %d%%\",\n\t\t\t\thints, UnitConverter.toSizeUnit(maxOldGenBytes, 2), UnitConverter.toSizeUnit(usedOldBytes, 2),\n\t\t\t\tusedOldBytes * 100d / maxOldGenBytes, oldGenOccupancyFraction));\n\t\treturn usedOldBytes;\n\t}\n\n\tprivate MemoryPoolMXBean getOldGenMemoryPool() {\n\t\tString OLD = \"old\";\n\t\tString TENURED = \"tenured\";\n\n\t\tMemoryPoolMXBean oldGenMemoryPool = null;\n\t\tList<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getPlatformMXBeans(MemoryPoolMXBean.class);\n\t\tfor (MemoryPoolMXBean memoryPool : memoryPoolMXBeans) {\n\t\t\tString name = memoryPool.getName().trim().toLowerCase();\n\t\t\tif (name.contains(OLD) || name.contains(TENURED)) {\n\t\t\t\toldGenMemoryPool = memoryPool;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn oldGenMemoryPool;\n\t}\n\n\tprivate long getMemoryPoolMaxOrCommitted(MemoryPoolMXBean memoryPool) {\n\t\tMemoryUsage usage = memoryPool.getUsage();\n\t\tlong max = usage.getMax();\n\t\treturn max < 0 ? usage.getCommitted() : max;\n\t}\n}\n"
  },
  {
    "path": "vjstar/src/main/java/com/vip/vjstar/window/AtomicBitSet.java",
    "content": "package com.vip.vjstar.window;\n\nimport java.util.concurrent.atomic.AtomicIntegerArray;\n\n/**\n * @author huangyunbin\n *         thread safe bitset\n */\npublic class AtomicBitSet {\n    private final AtomicIntegerArray atomicIntegerArray;\n    private final int size;\n    \n    public AtomicBitSet(int size) {\n        this.size = size;\n        int intLength = (size + 31) / 32;\n        atomicIntegerArray = new AtomicIntegerArray(intLength);\n    }\n    \n    public void set(long n, boolean flag) {\n        int bit = 1 << n;\n        int idx = (int) (n >>> 5);\n        while (true) {\n            int num = atomicIntegerArray.get(idx);\n            int num2;\n            if (flag) {\n                num2 = num | bit;\n            } else {\n                num2 = num & ~bit;\n            }\n            if (num == num2 || atomicIntegerArray.compareAndSet(idx, num, num2)) {\n                return;\n            }\n        }\n    }\n    \n    public boolean get(long n) {\n        int bit = 1 << n;\n        int idx = (int) (n >>> 5);\n        int num = atomicIntegerArray.get(idx);\n        return (num & bit) != 0;\n    }\n    \n    public int cardinality() {\n        int result = 0;\n        for (int i = 0; i < size; i++) {\n            if (get(i)) {\n                result++;\n            }\n        }\n        return result;\n    }\n}"
  },
  {
    "path": "vjstar/src/main/java/com/vip/vjstar/window/RequestSlidingWindow.java",
    "content": "package com.vip.vjstar.window;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author huangyunbin\n *         SlidingWindow for request\n */\npublic class RequestSlidingWindow {\n    \n    /**\n     * thread safe bitset，a request is a bit\n     * success use 1\n     * fail use 0\n     */\n    private final AtomicBitSet bitSet;\n    /**\n     * window size\n     */\n    private final int size;\n    /**\n     * current position\n     */\n    private volatile AtomicInteger index = new AtomicInteger();\n    /**\n     * current  request num\n     */\n    private volatile AtomicInteger capacity = new AtomicInteger();\n    \n    public RequestSlidingWindow(int size) {\n        this.size = size;\n        bitSet = new AtomicBitSet(size);\n    }\n    \n    \n    public void success() {\n        processCapacity();\n        setNext(true);\n    }\n    \n    public void fail() {\n        processCapacity();\n        setNext(false);\n    }\n    \n    private void setNext(boolean flag) {\n        int target = index.getAndIncrement() % size;\n        bitSet.set(target, flag);\n    }\n    \n    private void processCapacity() {\n        if (capacity.get() < size) {\n            capacity.getAndIncrement();\n        }\n    }\n    \n    \n    public long getSucessNum() {\n        return bitSet.cardinality();\n    }\n    \n    public long getFailNum() {\n        if (capacity.get() >= size) {\n            return size - bitSet.cardinality();\n        } else {\n            return capacity.get() - bitSet.cardinality();\n        }\n        \n    }\n}"
  },
  {
    "path": "vjstar/src/main/java/com/vip/vjstar/window/TimeSlidingWindow.java",
    "content": "package com.vip.vjstar.window;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicIntegerArray;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * @author huangyunbin\n *         SlidingWindow for time\n */\npublic class TimeSlidingWindow {\n    private final int size;\n    private volatile AtomicIntegerArray counts;\n    private volatile AtomicLong lastTime = new AtomicLong();\n    \n    public TimeSlidingWindow(int size) {\n        this.size = size;\n        counts = new AtomicIntegerArray(size);\n    }\n    \n    /**\n     * add request\n     */\n    public void add() {\n        long currentSecond = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime());\n        add(currentSecond);\n    }\n    \n    \n    void add(long time) {\n        clear(time);\n        \n        int index = (int) (time % size);\n        long current = lastTime.longValue();\n        \n        if (time > current) {\n            int oldValue = counts.get(index);\n            if (lastTime.compareAndSet(current, time)) {\n                counts.getAndAdd(index, 1 - oldValue);\n            } else {\n                counts.getAndIncrement(index);\n            }\n            \n        } else {\n            counts.getAndIncrement(index);\n        }\n    }\n    \n    /**\n     * clear prev data\n     */\n    private void clear(long time) {\n        if (time < lastTime.get() + size) {\n            return;\n        }\n        int index = (int) (time % size);\n        for (int i = 0; i < size; i++) {\n            if (i != index) {\n                counts.set(i, 0);\n            }\n        }\n    }\n    \n    /**\n     * count request num of time window\n     */\n    public long count() {\n        long currentSecond = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime());\n        return count(currentSecond);\n    }\n    \n    long count(long time) {\n        if (time >= lastTime.get() + size) {\n            return 0;\n        }\n        long result = 0;\n        for (int i = 0; i < size; i++) {\n            result += counts.get(i);\n        }\n        return result;\n    }\n    \n    \n}"
  },
  {
    "path": "vjstar/src/main/script/docker-cpus/README.md",
    "content": "# 容器中JVM获取真实的CPU核数\n\n基于 [libsysconfcpus](https://github.com/obmarg/libsysconfcpus)的方案，可以为各个版本的JDK提供一个通用的解决方案。\n\nlibsysconfcpus.so的原理是截获JVM获取CPU核数所用的系统调用sysconf(_SC_NPROCESSORS_CONF)，改为读取环境变量LIBSYSCONFCPUS返回。\n\n首先，从[libsysconfcpus](https://github.com/obmarg/libsysconfcpus)获取并编译so文件，放入镜像中。\n\n然后，编写类似的脚本，完成两件事情：\n\n1. 定义环境变量LD_PRELOAD，将libsysconfcpus.so放在最前面达到截获的目的。\n\n2. 我们的系统在部署容器的时候，会额外传入一个环境变量\"CONTAINER_CORE_LIMIT\"代表分配的CPU核数(需按自己的情况修改)，脚本将其转换为libsysconfcpus所需的环境变量。\n\n注意：当JVM是以-server启动时，至少需要2核，否则在启动时会被死锁。\n\n```\n#!/bin/sh\n\nif [ \"x$CONTAINER_CORE_REQUEST\" != \"x\" ]; then\n   LIBSYSCONFCPUS=\"$CONTAINER_CORE_REQUEST\"  \n   if [ ${LIBSYSCONFCPUS} -lt 2 ]; then\n      LIBSYSCONFCPUS=2\n   fi\n   export LIBSYSCONFCPUS      \nfi\nexport LD_PRELOAD=\"/usr/local/lib/libsysconfcpus.so:$LD_PRELOAD\"\n```\n\n\n"
  },
  {
    "path": "vjstar/src/main/script/jvm-options/jvm-options.sh",
    "content": "#!/bin/bash\n\n# 使用指南：\n# 1. 修改本文件中的LOGDIR 和 APPID变量\n# 2. 根据实际情况需求，反注释掉一些参数。\n# 3. 修改应用启动脚本，增加 \"source ./jvm-options.sh\"，或者将本文件内容复制进应用启动脚本里.\n# 4. 修改应用启动脚本，使用输出的JAVA_OTPS变量，如java -jar xxx的应用启动语句，修改为 java $JAVA_OPTS -jar xxx。\n\n\n# change the jvm error log and backup gc log dir here\nLOGDIR=\"./logs\"\n\n# change the appid for gc log name here\nAPPID=\"myapp\"\n\nJAVA_VERSION=$(java -version 2>&1 | awk -F '\"' '/version/ {print $2}')\n\n\n# Enable coredump\nulimit -c unlimited\n\n## Memory Options##\n\nMEM_OPTS=\"-Xms4g -Xmx4g -XX:NewRatio=1\"\n\nif [[ \"$JAVA_VERSION\" < \"1.8\" ]]; then\n  MEM_OPTS=\"$MEM_OPTS -XX:PermSize=128m -XX:MaxPermSize=512m\"\nelse         \n  MEM_OPTS=\"$MEM_OPTS -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m\"\nfi\n\n# 启动时预申请内存\nMEM_OPTS=\"$MEM_OPTS -XX:+AlwaysPreTouch\"\n\n# 如果线程数较多，函数的递归较少，线程栈内存可以调小节约内存，默认1M。\n#MEM_OPTS=\"$MEM_OPTS -Xss256k\"\n\n# 堆外内存的最大值默认约等于堆大小，可以显式将其设小，获得一个比较清晰的内存总量预估\n#MEM_OPTS=\"$MEM_OPTS -XX:MaxDirectMemorySize=2g\"\n\n# 根据JMX/VJTop的观察，调整二进制代码区大小避免满了之后不能再JIT，JDK7/8，是否打开多层编译的默认值都不一样\n#MEM_OPTS=\"$MEM_OPTS -XX:ReservedCodeCacheSize=240M\"\n\n\n## GC Options##\n\nGC_OPTS=\"-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly\"\n\n# System.gc() 使用CMS算法\nGC_OPTS=\"$GC_OPTS -XX:+ExplicitGCInvokesConcurrent\"\n\n# CMS中的下列阶段并发执行\nGC_OPTS=\"$GC_OPTS -XX:+ParallelRefProcEnabled -XX:+CMSParallelInitialMarkEnabled\"\n\n# 根据应用的对象生命周期设定，减少事实上的老生代对象在新生代停留时间，加快YGC速度\nGC_OPTS=\"$GC_OPTS -XX:MaxTenuringThreshold=3\"\n\n# 如果OldGen较大，加大YGC时扫描OldGen关联的卡片，加快YGC速度，默认值256较低\nGC_OPTS=\"$GC_OPTS -XX:+UnlockDiagnosticVMOptions -XX:ParGCCardsPerStrideChunk=1024\"\n\n# 如果JVM并不独占机器，机器上有其他较繁忙的进程在运行，将GC线程数设置得比默认值(CPU核数＊5/8 )更低以减少竞争，反而会大大加快YGC速度。\n# 另建议CMS GC线程数简单改为YGC线程数一半.\n#GC_OPTS=\"$GC_OPTS -XX:ParallelGCThreads=12 -XX:ConcGCThreads=6\"\n\n# 如果CMS GC时间很长，并且明显受新生代存活对象数量影响时打开，但会导致每次CMS GC与一次YGC连在一起执行，加大了事实上JVM停顿的时间。\n#GC_OPTS=\"$GC_OPTS -XX:+CMSScavengeBeforeRemark\"\n\n# 如果永久代使用不会增长，关闭CMS时ClassUnloading，降低CMS GC时出现缓慢的几率\n#if [[ \"$JAVA_VERSION\" > \"1.8\" ]]; then     \n#  GC_OPTS=\"$GC_OPTS -XX:-CMSClassUnloadingEnabled\"\n#fi\n\n\n## GC log Options, only for JDK7/JDK8 ##\n\n# 默认使用/dev/shm 内存文件系统避免在高IO场景下写GC日志时被阻塞导致STW时间延长\nif [ -d /dev/shm/ ]; then\n    GC_LOG_FILE=/dev/shm/gc-${APPID}.log\nelse\n    GC_LOG_FILE=${LOGDIR}/gc-${APPID}.log\nfi\n\n\nif [ -f ${GC_LOG_FILE} ]; then\n  GC_LOG_BACKUP=${LOGDIR}/gc-${APPID}-$(date +'%Y%m%d_%H%M%S').log\n  echo \"saving gc log ${GC_LOG_FILE} to ${GC_LOG_BACKUP}\"\n  mv ${GC_LOG_FILE} ${GC_LOG_BACKUP}\nfi\n\n#打印GC日志，包括时间戳，晋升老生代失败原因，应用实际停顿时间(含GC及其他原因)\nGCLOG_OPTS=\"-Xloggc:${GC_LOG_FILE} -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintPromotionFailure -XX:+PrintGCApplicationStoppedTime\"\n\n\n#打印GC原因，JDK8默认打开\nif [[ \"$JAVA_VERSION\" < \"1.8\" ]]; then\n    GCLOG_OPTS=\"$GCLOG_OPTS -XX:+PrintGCCause\"\nfi\n\n\n# 打印GC前后的各代大小\n#GCLOG_OPTS=\"$GCLOG_OPTS -XX:+PrintHeapAtGC\"\n\n# 打印存活区每段年龄的大小\n#GCLOG_OPTS=\"$GCLOG_OPTS -XX:+PrintTenuringDistribution\"\n\n# 如果发生晋升失败，观察老生代的碎片\n#GCLOG_OPTS=\"$GCLOG_OPTS -XX:+UnlockDiagnosticVMOptions -XX:PrintFLSStatistics=2\"\n\n\n# 打印安全点日志，找出GC日志里非GC的停顿的原因\n#GCLOG_OPTS=\"$GCLOG_OPTS -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+UnlockDiagnosticVMOptions -XX:-DisplayVMOutput -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm-${APPID}.log\"\n\n\n## Optimization Options##\n\nOPTIMIZE_OPTS=\"-XX:-UseBiasedLocking -XX:AutoBoxCacheMax=20000 -Djava.security.egd=file:/dev/./urandom\"\n\n\n# 关闭PerfData写入，避免高IO场景GC时因为写PerfData文件被阻塞，但会使得jstats，jps不能使用。\n#OPTIMIZE_OPTS=\"$OPTIMIZE_OPTS -XX:+PerfDisableSharedMem\"\n\n# 关闭多层编译，减少应用刚启动时的JIT导致的可能超时，以及避免部分函数C1编译后最终没被C2编译。 但导致函数没有被初始C1编译。\n#if [[ \"$JAVA_VERSION\" > \"1.8\" ]]; then     \n#  OPTIMIZE_OPTS=\"$OPTIMIZE_OPTS -XX:-TieredCompilation\"\n#fi\n\n# 如果希望无论函数的热度如何，最终JIT所有函数，关闭GC时将函数调用次数减半。\n#OPTIMIZE_OPTS=\"$OPTIMIZE_OPTS -XX:-UseCounterDecay\"\n\n## Trouble shooting Options##\n\nSHOOTING_OPTS=\"-XX:+PrintCommandLineFlags -XX:-OmitStackTraceInFastThrow -XX:ErrorFile=${LOGDIR}/hs_err_%p.log\"\n\n\n# OOM 时进行HeapDump，但此时会产生较高的连续IO，如果是容器环境，有可能会影响他的容器\n#SHOOTING_OPTS=\"$SHOOTING_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/\"\n\n# async-profiler 火焰图效果更好的参数\n#SHOOTING_OPTS=\"$SHOOTING_OPTS -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints\"\n\n# 在非生产环境，打开JFR进行性能记录（生产环境要收License的哈）\n#SHOOTING_OPTS=\"$SHOOTING_OPTS -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints\"\n\n\n\n## JMX Options##\n\n#开放JMX本地访问，设定端口号\nJMX_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\"\n\n\n## Other Options##\n\nOTHER_OPTS=\"-Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8\"\n\n\n## All together ##\n\nexport JAVA_OPTS=\"$MEM_OPTS $GC_OPTS $GCLOG_OPTS $OPTIMIZE_OPTS $SHOOTING_OPTS $JMX_OPTS $OTHER_OPTS\"\n\necho JAVA_OPTS=$JAVA_OPTS\n"
  },
  {
    "path": "vjstar/src/test/java/com/vip/vjstar/.gitkeep",
    "content": ""
  },
  {
    "path": "vjstar/src/test/java/com/vip/vjstar/gc/Enchanter.java",
    "content": "package com.vip.vjstar.gc;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport com.vip.vjtools.vjkit.number.RandomUtil;\n\npublic class Enchanter {\n\tprivate static final Logger log = LoggerFactory.getLogger(Enchanter.class);\n\tprivate static final String[] ha = { \"苟利国家生死以%d岂因祸福趋避之\", \"煮豆燃豆萁豆在釜中泣%d本是同根生相煎何太急\", \"利欲驱人万火牛%d江湖浪迹一沙鸥\",\n\t\t\t\"且持梦笔书奇景%d日破云涛万里红\", \"春来我不先开口%d哪个虫儿敢作声\" };\n\n\tprivate List<String> garbage = new ArrayList<>();\n\n\tpublic void makeGarbage(String val) {\n\t\tlog.info(\"trying to occupy oldGen\");\n\t\tint size = Integer.parseInt(val);\n\t\tStringBuilder sb = new StringBuilder();\n\t\tfor (int i = 0; i < size; i++) {\n\t\t\t// randomize a little bit to avoid duplicate strings\n\t\t\tsb.append(String.format(ha[RandomUtil.nextInt(i % ha.length + 1)], i));\n\t\t}\n\t\t// 大对象直接进old gen，用list hold住不释放\n\t\tgarbage.add(sb.toString());\n\t\tlog.info(\"Enchanter is littering around, garbage size: {}\", sb.length());\n\t}\n\n\tpublic void clearGarbage() {\n\t\t// 清空list以便cms可以回收\n\t\tgarbage.clear();\n\t\tlog.info(\"garbage cleared from list...\");\n\t}\n\n}"
  },
  {
    "path": "vjstar/src/test/java/com/vip/vjstar/gc/ProactiveGcTaskDemo.java",
    "content": "package com.vip.vjstar.gc;\n\nimport java.io.IOException;\n\npublic class ProactiveGcTaskDemo {\n\t\n\tpublic static void main(String[] args) throws IOException {\n\t\t//// 真实代码示例 ////\n\t\tCleanUpScheduler scheduler = new CleanUpScheduler();\n\t\tProactiveGcTask task = new ProactiveGcTask(scheduler, 50);\n\t\tscheduler.schedule(\"03:30-04:30\", task);\n\t\t// ....\n\t\tscheduler.shutdown();\n\n\t\t///// 演示用代码 ////\n\t\t// 模拟内存占用, 根据运行环境调整到可以占用一半以上老生代\n\t\tfinal Enchanter enchanter = new Enchanter();\n\t\tenchanter.makeGarbage(\"10000000\");\n\n\t\t// 直接运行看效果\n\t\ttask.run();\n\n\t\tSystem.out.println(\"hit ENTER to stop\");\n\t\tSystem.in.read();\n\t\tenchanter.clearGarbage();\n\t\t////////////////\n\n\t}\n}\n"
  },
  {
    "path": "vjstar/src/test/java/com/vip/vjstar/window/RequestSlidingWindowTest.java",
    "content": "package com.vip.vjstar.window;\n\nimport org.junit.Test;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.CyclicBarrier;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author huangyunbin\n *         test for RequestSlidingWindow\n */\npublic class RequestSlidingWindowTest {\n    \n    @Test\n    public void notOverWindowTest() {\n        RequestSlidingWindow slidingWindow = new RequestSlidingWindow(10);\n        slidingWindow.success();\n        slidingWindow.success();\n        slidingWindow.fail();\n        slidingWindow.success();\n        slidingWindow.fail();\n        assertThat(slidingWindow.getSucessNum()).isEqualTo(3);\n        assertThat(slidingWindow.getFailNum()).isEqualTo(2);\n    }\n    \n    @Test\n    public void overWindowTest() {\n        RequestSlidingWindow slidingWindow = new RequestSlidingWindow(3);\n        \n        slidingWindow.success();\n        slidingWindow.fail();\n        slidingWindow.success();\n        \n        //The following is valid\n        slidingWindow.fail();\n        slidingWindow.success();\n        slidingWindow.fail();\n        \n        assertThat(slidingWindow.getSucessNum()).isEqualTo(1);\n        assertThat(slidingWindow.getFailNum()).isEqualTo(2);\n    }\n    \n    @Test\n    public void mutiThreadNotOverWindowTest() throws Exception {\n        int threadNum = 10;\n        final int num = 100;\n        final RequestSlidingWindow slidingWindow = new RequestSlidingWindow(threadNum * num * 2);\n        final CyclicBarrier barrier = new CyclicBarrier(threadNum);\n        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);\n        \n        for (int i = 0; i < threadNum; i++) {\n            new Thread() {\n                @Override\n                public void run() {\n                    try {\n                        barrier.await();\n                        \n                        for (int j = 0; j < num; j++) {\n                            slidingWindow.fail();\n                            slidingWindow.success();\n                        }\n                        \n                        countDownLatch.countDown();\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                    }\n                }\n            }.start();\n            \n        }\n        countDownLatch.await();\n        \n        assertThat(slidingWindow.getSucessNum()).isEqualTo(threadNum * num);\n        assertThat(slidingWindow.getFailNum()).isEqualTo(threadNum * num);\n    }\n    \n    \n    @Test\n    public void mutiThreadOverWindowTest() throws Exception {\n        int threadNum = 2;\n        final int num = 100;\n        final int size = 20;\n        final AtomicInteger index = new AtomicInteger();\n        final RequestSlidingWindow slidingWindow = new RequestSlidingWindow(size);\n        final CyclicBarrier barrier = new CyclicBarrier(threadNum);\n        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);\n        \n        for (int i = 0; i < threadNum; i++) {\n            new Thread() {\n                @Override\n                public void run() {\n                    try {\n                        barrier.await();\n                        \n                        for (int j = 0; j < num; j++) {\n                            if (index.getAndIncrement() % 2 == 0) {\n                                slidingWindow.success();\n                            } else {\n                                slidingWindow.fail();\n                            }\n                        }\n                        \n                        countDownLatch.countDown();\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                    }\n                }\n            }.start();\n            \n        }\n        countDownLatch.await();\n        \n        assertThat(slidingWindow.getSucessNum()).isEqualTo(size / 2);\n        assertThat(slidingWindow.getFailNum()).isEqualTo(size / 2);\n    }\n    \n}"
  },
  {
    "path": "vjstar/src/test/java/com/vip/vjstar/window/TimeSlidingWindowTest.java",
    "content": "package com.vip.vjstar.window;\n\nimport org.junit.Test;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.CyclicBarrier;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author huangyunbin\n *         test for TimeSlidingWindow\n */\npublic class TimeSlidingWindowTest {\n    \n    @Test\n    public void notOverWindowTest() {\n        TimeSlidingWindow slidingWindow = new TimeSlidingWindow(1);\n        slidingWindow.add();\n        slidingWindow.add();\n        long count = slidingWindow.count();\n        assertThat(count).isEqualTo(2);\n    }\n    \n    @Test\n    public void overWindowTest() throws Exception {\n        TimeSlidingWindow slidingWindow = new TimeSlidingWindow(1);\n        slidingWindow.add();\n        slidingWindow.add();\n        Thread.sleep(1000);\n        slidingWindow.add();\n        long count = slidingWindow.count();\n        assertThat(count).isEqualTo(1);\n    }\n    \n    \n    @Test\n    public void mutiThreadNotOverWindowTest() {\n        int threadNum = 10;\n        final int num = 100;\n        final int circle = 30;\n        final TimeSlidingWindow slidingWindow = new TimeSlidingWindow(10);\n        final CyclicBarrier barrier = new CyclicBarrier(threadNum);\n        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);\n        \n        for (int i = 0; i < threadNum; i++) {\n            new Thread() {\n                @Override\n                public void run() {\n                    try {\n                        barrier.await();\n                        \n                        for (int i = 0; i < circle; i++) {\n                            for (int j = 0; j < num; j++) {\n                                slidingWindow.add();\n                            }\n                            \n                            TimeUnit.MILLISECONDS.sleep(10);\n                        }\n                        \n                        countDownLatch.countDown();\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                    }\n                }\n            }.start();\n            \n        }\n        try {\n            countDownLatch.await();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        long count = slidingWindow.count();\n        assertThat(count).isEqualTo(circle * threadNum * num);\n    }\n    \n    \n    @Test\n    public void mutiThreadOverWindowTest() {\n        int threadNum = 2;\n        final int num = 100;\n        final int circle = 3;\n        final int size = 2;\n        final TimeSlidingWindow slidingWindow = new TimeSlidingWindow(size);\n        final CyclicBarrier barrier = new CyclicBarrier(threadNum);\n        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);\n        \n        for (int i = 0; i < threadNum; i++) {\n            new Thread() {\n                @Override\n                public void run() {\n                    try {\n                        barrier.await();\n                        \n                        for (int i = 0; i < circle; i++) {\n                            for (int j = 0; j < num; j++) {\n                                slidingWindow.add();\n                            }\n                            \n                            if (i != circle - 1) {\n                                TimeUnit.MILLISECONDS.sleep(1000);\n                            }\n                        }\n                        \n                        countDownLatch.countDown();\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                    }\n                }\n            }.start();\n            \n        }\n        try {\n            countDownLatch.await();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        long count = slidingWindow.count();\n        assertThat(count).isEqualTo(size * threadNum * num);\n    }\n    \n    \n}"
  },
  {
    "path": "vjtop/README.md",
    "content": "# 1. 概述\n\n对应于观看“OS指标及繁忙进程”的top，vjtop就是观察“JVM进程指标及其繁忙线程”的首选工具。\n\n**JVM进程信息**：收集了进程在OS层面和JVM层面的所有重要指标。大家为什么喜欢用[dstat](http://dag.wiee.rs/home-made/dstat/)看OS状态，因为它将你想看的数据全都收集呈现眼前了，vjtop也是这样的风格。\n\n**繁忙线程信息**： 对比于“先top -H 列出线程，再执行jstack拿到全部线程，再手工换算十与十六进制的线程号”的繁琐过程，vjtop既方便，又可以连续跟踪，更不会因为jstack造成JVM停顿。\n\n对于超出正常范围的值，vjtop还很贴心的进行了变色显示。\n\n运行时不造成应用停顿，可在线上安全使用。\n\n**常用场景：**\n\n1. 性能问题快速定位，用vjtop显示出CPU繁忙或内存消耗大的线程，再实时交互翻查该线程的stack trace。\n\n2. 压测场景，用vjtop实时反馈JVM进程状态，类似于用dstast对操作系统指标的监控。\n\n3. 生产环境，当应用出现问题时，用vjtop快速了解进程的状态。还可与监控系统结合，发现指标(如CPU、超时请求数)超出阈值时，用钩子脚本调用vjtop来纪录事发地的状况。\n\n\n在[jvmtop](https://github.com/patric-r/jvmtop) 的基础上二次开发，结合 [SJK](https://github.com/aragozin/jvm-tools)的优点，从/proc ， PerfData，JMX等处，以更高的性能，获取更多的信息。\n\n\n# 2. 使用说明\n\n\n## 2.1 概述\n\n[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)\n\nvjtop运行所需权限与jstack相同，必须与目标JVM使用相同的JDK版本运行，必须与目标JVM使用相同用户运行。如果仍有问题，请看后面的执行问题排查章节。\n\n```\n// 占用CPU最多的线程\n./vjtop.sh <PID> \n\n// 打印选项，每个版本的参数会有变动，特别是模式参数，以help信息为准\n./vjtop.sh -h\n\n```\n\n## 2.2 找出CPU最繁忙的线程\n\n\n### 2.2.1 命令参数\n\n```\n// 按时间区间内，线程占用的CPU排序，默认显示前10的线程，默认每10秒打印一次\n./vjtop.sh <PID>\n\n// 按时间区间内，线程占用的SYS CPU排序\n./vjtop.sh -m syscpu <PID>\n\n// 按线程从启动以来的总占用CPU来排序\n./vjtop.sh -m totalcpu <PID>\n\n// 按线程从启动以来的总SYS CPU排序\n./vjtop.sh -m totalsyscpu <PID>\n```\n\n\n### 2.2.2 输出示例：\n\n```\n PID: 191082 - 17:43:12 JVM: 1.7.0_79 USER: calvin UPTIME: 2d02h\n PROCESS: 685.00% cpu(28.54% of 24 core), 787 thread\n MEMORY: 6626m rss, 6711m peak, 0m swap | DISK: 0B read, 13mB write\n THREAD: 756 live, 749 daemon, 1212 peak, 0 new | CLASS: 15176 loaded, 161 unloaded, 0 new\n HEAP: 630m/1638m eden, 5m/204m sur, 339m/2048m old\n NON-HEAP: 80m/256m/512m perm, 13m/13m/240m codeCache\n OFF-HEAP: 0m/0m direct(max=2048m), 0m/0m map(count=0), 756m threadStack\n GC: 6/66ms/11ms ygc, 0/0ms fgc | SAFE-POINT: 6 count, 66ms time, 5ms syncTime\n\n\n    TID NAME                                                      STATE    CPU SYSCPU  TOTAL TOLSYS\n     23 AsyncAppender-Worker-ACCESSFILE-ASYNC                   WAITING 23.56%  6.68%  2.73%  0.72%\n    560 OSP-Server-Worker-4-5                                  RUNNABLE 22.58% 10.67%  1.08%  0.48%\n   9218 OSP-Server-Worker-4-14                                 RUNNABLE 22.37% 11.45%  0.84%  0.40%\n   8290 OSP-Server-Worker-4-10                                 RUNNABLE 22.36% 11.24%  0.88%  0.41%\n   8425 OSP-Server-Worker-4-12                                 RUNNABLE 22.24% 10.72%  0.98%  0.47%\n   8132 OSP-Server-Worker-4-9                                  RUNNABLE 22.00% 10.68%  0.90%  0.42%\n   8291 OSP-Server-Worker-4-11                                 RUNNABLE 21.80% 10.09%  0.89%  0.41%\n   8131 OSP-Server-Worker-4-8                                  RUNNABLE 21.68%  9.77%  0.93%  0.44%\n   9219 OSP-Server-Worker-4-15                                 RUNNABLE 21.56% 10.43%  0.90%  0.41%\n   8426 OSP-Server-Worker-4-13                                 RUNNABLE 21.35% 10.42%  0.66%  0.31%\n\n Total  : 668.56% cpu(user=473.25%, sys=195.31%) by 526 atcive threads(which cpu>0.05%)\n Setting: top 10 threads order by CPU, flush every 10s\n Input command (h for help):\n```\n进程区数据解释:\n\n* `PROCESS`: `thread`: 进程的操作系统线程数, `cxtsw`为主动与被动的线程上下文切换数\n* `MEMORY`: `rss` 为 Resident Set Size, 进程实际占用的物理内存; `peak`为最峰值的rss; `swap`为进程被交换到磁盘的虚拟内存。\n* `DISK`: 真正达到物理存储层的读/写的速度。\n* `THREAD`: Java线程数, `active`为当前线程数, `daemon`为active线程中的daemon线程数, `new`为刷新周期内新创建的线程数。\n* `CLASS`: `loaded`为当前加载的类数量，`unloaded`为总卸载掉的类数量，`new`为刷新周期内新加载的类数量。\n* `HEAP`: 1.0.3版开始每一项有三个数字, 分别为1.当前使用内存, 2.当前已申请内存, 3.最大内存; 如果后两个数字相同时则合并。\n* `sur`: 当前存活区的大小，注意实际有from, to 两个存活区。\n* `NON-HEAP`: 数字含义同`HEAP`\n* `codeCache`: JIT编译的二进制代码的存放区，满后将不能编译新的代码。\n* `direct`: 堆外内存，三个数字含义同`HEAP`, 未显式设置最大内存时，约等于堆内存大小。注意新版Netty不经过JDK API所分配的堆外内存未在此统计。\n* `map`: 映射文件内存，三个数字分别为1. map数量，2.当前使用内存，3.当前已申请内存，没有最大值数据。\n* `threadStack`: Java线程所占的栈内存总和，但不包含VM线程。(since 1.0.3)\n* `ygc`: YoungGC, 三个数字分别为次数／总停顿时间／平均停顿时间\n* `fgc`: OldGC ＋ FullGC， 两个数字分别为次数／总执行时间，注意此时间仅为执行时间，非JVM停顿时间。\n* `SAFE-POINT`: PerfData开启时可用，JVM真正的停顿次数及停顿时间，以及等待所有线程进入安全点所消耗的时间。\n\n\n线程区数据解释:\n\n* `CPU`: 线程在打印间隔内使用的CPU百分比(按单个核计算)\n* `SYSCPU`: 线程在打印间隔内使用的SYS CPU百分比(按单个核计算)\n* `TOTAL`: 从进程启动到现在，线程的总CPU时间/进程的总CPU时间的百分比\n* `TOLSYS`: 从进程启动到现在，线程的总SYS CPU时间/进程的总CPU时间的百分比\n\n底部数据解释:\n\n* 如果该线程的平均使用CPU少于单核的0.1%，这条线程将不参与排序显示，减少消耗。 \n\n\n## 2.3 找出内存分配最频繁的线程\n\n\n### 2.3.1 命令参数\n\n```\n// 线程分配内存的速度排序，默认显示前10的线程，默认每10秒打印一次\n./vjtop.sh -m 5 <PID>\n\n// 按线程的总内存分配而不是打印间隔内的内存分配来排序\n./vjtop.sh -m 6 <PID>\n```\n\n### 2.3.2 输出示例\n\n```\n(忽略头信息)\n THREADS-MEMORY:   30k/s allocation rate\n\n    TID NAME                                                 STATE         MEMORY         TOTAL-ALLOCATED\n  47636 RMI TCP Connection(583)-127.0.0.1                 RUNNABLE   27k/s(88.76%)    17m( 0.00%)\n      1 main                                              RUNNABLE    2k/s( 8.44%)   370g(83.16%)\n  47845 JMX server connection timeout 47845             TIMED_WAIT   251/s( 0.80%)    21k( 0.00%)\n  46607 Worker-501                                      TIMED_WAIT    60/s( 0.19%)   934m( 0.20%)\n  46609 Worker-502                                      TIMED_WAIT    60/s( 0.19%)   822m( 0.18%)\n  46610 Worker-503                                      TIMED_WAIT    60/s( 0.19%)   737m( 0.16%)\n  46763 Worker-504                                      TIMED_WAIT    60/s( 0.19%)   696m( 0.15%)\n  46764 Worker-505                                      TIMED_WAIT    60/s( 0.19%)   743m( 0.16%)\n  47149 Worker-506                                      TIMED_WAIT    60/s( 0.19%)   288m( 0.06%)\n  46551 Worker-500                                      TIMED_WAIT    60/s( 0.19%)   757m( 0.17%)\n```\n进程区数据解释:\n* `allocation rate`: 所有线程在打印间隔内每秒分配的内存\n\n线程区数据解释:\n\n* `STATE`: 该线程当前的状态\n* `MEMORY`: 该线程分配内存的瞬时值，即该线程在打印间隔内每秒分配的内存空间(该线程每秒分配的内存占所有线程在该秒分配的总内存的百分比)\n* `TOTAL-ALLOCATED`: 该线程分配内存的历史累计值，即从进程启动到现在，该线程分配的总内存大小，该总内存大小包括已回收的对象的内存(该线程分配的总内存大小占所有线程分配的总内存大小的百分比)。\n\n如果该线程的平均内存分配速度少于1K/s，这条线程将不参与排序显示，减少消耗。 \n\n\n## 2.4 命令行参数\n\n```\n// 打印其他选项\n./vjtop.sh -h\n\n// 结果输出到文件\n./vjtop.sh <PID> > /tmp/vjtop.log\n\n// 每5秒打印一次（默认10秒）\n./vjtop.sh -i 5 <PID>\n\n// 打印20次后退出\n./vjtop.sh -n 20 <PID>\n\n// 显示前100的线程（默认10）\n./vjtop.sh -l 100 <PID>\n\n// 不带变色与换页控制码的console模式，适合不支持控制码的终端。在Windows及输出到文件时将默认使用次此模式\n./vjtop.sh -o clean <PID>\n\n// key:value式的文本模式，适用于第三方工具采集vjtop的输出结果\n./vjtop.sh -o text <PID>\n\n// 只采集JVM信息，不采集繁忙线程信息\n./vjtop.sh -c jvm <PID>\n\n// 只采集繁忙线程信息，不采集JVM信息\n./vjtop.sh -c thread <PID>\n\n// 只显示线程名包含worker字样的线程，在热点线程与实时交互打印线程时都会过滤（1.0.6版开始忽略大小写）\n./vjtop.sh -f worker <PID>\n\n// 更宽的120字节的屏幕 （默认100）\n./vjtop.sh -w 120 <PID> > /tmp/vjtop.log\n```\n\n\n## 2.5 实时交互\n\n### 2.5.1 打印线程Stack Trace\n\n1.在页面中输入t，再输入线程号，可打印线程的Stack Trace，看繁忙的线程在忙什么。\n\n会引入暂停，但只取一条线程信息时停顿非常短\n\n```\n Input command (h for help):s\n Input TID for stack:4161\n\tat java.lang.Object.wait(Native Method)\n\tat org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)\n\tat org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)\n\tat org.eclipse.core.internal.jobs.Worker.run(Worker.java:52)\n```\n\n上例子也可以直接输入 \"s 4161\"\n\n2. 打印Top繁忙线程的stack trace\n\n会引入暂停，但只取若干条线程信息时停顿非常短\n\n```\n Input command (h for help):t\n Stack trace of top 10 threads:\n 15: \"RMI TCP Connection(15)-10.100.150.221\"\n   java.lang.Thread.State: RUNNABLE\n\tat sun.management.ThreadImpl.getThreadInfo1(Native Method)\n\tat sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:178)\n\t...\n```\n\n3. 打印所有状态为BLOCKED的线程的stack trace\n\n会引入暂停，取所有线程信息，时间比前面两个命令略长。但因为不获取锁信息，也不拿JNI global references等数据，所以仍比jstack快。\n\n```\n Input command (h for help):b\n\n Stack trace of blocked threads:\n ...\n\n```\n\n4. 打印所有线程的TID和线程名\n\n不引入暂停\n\n```\nThread Id and name of all live threads:\n 16\t: \"JMX server connection timeout 16\" (TIMED_WAITING)\n 15\t: \"RMI TCP Connection(15)-10.100.150.221\" (RUNNABLE)\n 13\t: \"RMI Scheduler(0)\" (TIMED_WAITING)\n 11\t: \"RMI TCP Accept-0\" (RUNNABLE)\n 9\t: \"Attach Listener\" (RUNNABLE)\n 4\t: \"Signal Dispatcher\" (RUNNABLE)\n```\n\n### 2.5.2 实时切换显示模式\n\n1.改变显示和排序模式，在页面中输入m\n```\n Input command (h for help):m\n Input number of Display Mode(1.cpu, 2.syscpu 3.total cpu 4.total syscpu 5.memory 6.total memory, current cpu): 5\n```\n\n2.改变显示间隔\n``` \n Input command (h for help):i\n Input flush interval seconds(current 10):20\n Flush interval change to 20 seconds\n```\n也可以直接输入\"i 20\" 切换 \n\n\n3. 设定显示的线程数 \n\n```\n Input command (h for help):l\n Input number of threads to display :20\n Number of threads to display changed to 20 for next flush\n```\n也可以直接输入\"l 20\" 切换 \n\n4.设定按线程名过滤线程，在打印繁忙线程和全部线程时，线程名都必须包含filter字符串，大小写不敏感，不支持正则匹配和匹配符匹配。\n\n```\n Input command (h for help):f\n Input filter of thread name (current null):Worker\n thread name filter change to \"Worker\" for next flush (3s later)\n```\n\n\n\n# 3. 原理\n\n## 3.1 进程区数据来源\n\n* 从/proc/PID/* 文件中获取进程数据, 详见[proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html)\n* 从JDK的PerfData文件中获取JVM数据(JDK每秒写入/tmp/hsperfdata_$userid/$pid文件的统计数据)\n* 使用目标JVM的JMX中获取JVM数据（如果目标JVM还没启动JMX，通过attach方式动态加载）\n\n如果数据同时在PerfData和JMX存在，优先使用PerfData。 \n网络流量数据在/proc/PID/*中未能按进程区分，因此不再监控。\n\n## 3.2 线程区数据来源 \n\n使用ThreadMxBean操作：\n\n1. getAllThreadIds()获得所有Thread Id\n2. getThreadCpuTime(tids)获得所有线程的CPU时间 (以及SYS CPU，内存分配)\n3. 排序后，用getThreadInfo(tids)获得前10名线程的信息，因为不取线程的StackTrace，不会堵塞应用。\n\n# 4. 监控值变色告警规则\n\n* 进程CPU：服务器总CPU50%黄， 70%红\n* 进程线程：如果服务器少于8核，线程数为核数＊150 黄，核数＊225 红。 如果大于8核，核数＊100 黄, 核数＊150红。\n* 进程内存： swap一旦使用即为红\n* 进程磁盘IO: 读写磁盘30MB/s 黄，100MB/s 红\n* CLASS: 新加载类时为黄色, 当前加载类8万为黄，15万为红\n* JVM内存: 老生代，永久代，CodeCache，如果设置了Max，则Max的85%黄，95%红\n* JVM线程: active规则同进程线程，new规则：新建1条线程为黄，每秒创建2条为红。\n* YGC: YGC次数每秒1次以上黄，2次以上红，平均耗时100ms黄，200ms红, 总耗时达到了应用运行时间的5%黄，10%红\n* FGC: 周期内的次数：1次黄，2次红\n* SAFEPOINT: 安全点次数每秒2次黄，4次红，总耗时达到了应用运行时间的5%黄，10%红\n\n\n\n# 5. 执行问题排查\n\n1. JDK版本错误或tools.jar不存在\n\nvjtop使用的java为JAVA_HOME/bin/java, 需要JDK7及以上，但\"不要求\"与目标应用的JVM使用相同的JDK版本。\n\nvjtop需要依赖JAVA_HOME/lib/tools.jar\n\nJAVA_HOME的定位，通过读取环境变量JAVA_HOME，如果没有定义，则尝试通过\"which java\"定位java从而获得相对路径。\n\n2. 不能连入目标jvm\n\n再次，vjtop 使用JVM attach机制 连入PID 并获得JMX的本地连接地址，所需权限与jstack相同， attach失败时出现如下报错\n\n```\nERROR: Could not attach to process.\n```\n\n可以先执行jstack PID对比一下效果。\n\n可能的原因有：\n\n1. PID写错，进程不存在\n\n2. VM Attach时，会强制检查执行vjtop的用户，与目标JMV的用户一致，否则会抛出\"well-known file is not secure\"之类的异常。\n\n如果用户有sudo权限，可以尝试切换到目标用户，并把JAVA_HOME等环境变量带到新用户。\n```\nsudo -E su - <targetUser>\n```\n\n3. /tmp/.java_pid$PID 文件在首次连接时会生成，但如果生成之后被大家的文件清理脚本错误删除，JVM将不再能连入，只能重启应用。\n\n4. 目标JVM使用启动参数-Djava.io.tmpdir，重定向了tmp目录路径，导致读不到/tmp/.java_pid$PID 文件。\n\n5. 目标JVM使用启动参数-XX:+DisableAttachMechanism禁止了attach。\n\n如果实在没有办法attach，可以考虑在原目标进程中配置JMX启动参数，设定JMX的地址与端口，然后在vjtop中指定该地址\n\n目标进程的JVM参数：\n```\n-Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote\n -Dcom.sun.management.jmxremote.X=false -Dcom.sun.management.jmxremote.ssl=false\n```\n\nvjtop的命令(since 1.0.3):\n\n```\n./vjtop.sh -j 127.0.0.1:7001 <PID>\n```\n\n\n# 6. 与jvmtop相比的改进点\n\n### 6.1 进程概览\n\n* 新功能：进程的物理内存，SWAP，IO，物理线程信息。\n* 新功能：将内存信息与GC信息拆开不同分代独立显示\n* 新功能：CodeCache与堆外内存，Thread Stack内存信息，SafePoint等信息\n* 新功能：对偏离正常范围的值，进行变色提示\n\n\n### 6.2 热点线程\n\n* 新功能：线程内存分配速度的展示与排序 (from SJK)\n* 新功能：线程SYS CPU的展示与排序，应用启动以来线程的总CPU间的排序 (from SJK)\n* 新配置项：打印间隔，展示线程数\n\n### 6.3 实时交互\n\n* 新功能： 选择打印某条线程的线程栈，所有TopN繁忙线程的栈，所有Blocked状态线程的栈\n* 新功能： 打印全部的线程名\n* 新功能： 实时切换显示模式和排序，刷新频率和显示线程数\n\n### 6.4 为在生产环境运行优化\n\n* 删除jvmtop会造成应用停顿的Profile页面\n* 删除jvmtop获取所有Java进程信息，有着不确定性的Overview页面\n* 默认打印间隔调整到10s\n* 进程信息尝试从PerfData而不是JMX读取数据，减少消耗\n* 线程信息减少了几倍的耗时，通过批量获取线程CPU时间(from SJK)等方法\n* 支持输出文本格式给第三方监控工具使用\n* 支持只输出JVM信息或繁忙线程信息\n* 支持vm attach总是失败时，直接配置JMX的方式连入\n"
  },
  {
    "path": "vjtop/README_EN.md",
    "content": "# VJtop \n\nOut of date...\n\nVJtop 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.\n\n# 1. Introduction\n\nVJtop 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. \n\n[jvmtop](https://github.com/patric-r/jvmtop)\n\n[SJK](https://github.com/aragozin/jvm-tools)\n\nVJtop is built as NON stop-the-world and is considered ready for production diagnostics.\n\n\n# 2. Getting Started\n\n## 2.1 How to run\n\n[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)\n\nRun the following command under **the same user who started the target process**.\n\n```\n// showing threads consuming the most cpu\n./vjtop.sh <PID>\n```\n\n## 2.2 How it works\n\n### 2.21 Sources of Process Stats\n\nProcess data are retrieved\n\n*   from /proc/PID/*\n*   from /tmp/hsperfxxxx, where stats are written by JDK every other second\n*   from JMX of the targeted VM. (If JMX isn't started at the time\nVJtop will try to attach to the process to start JMX).\n\n[Note] If the same items appear in both PerfData and JMX, the one from PerfData is perferred. Item in JMX is used instead when \nPerfData is unavailable.\n\n\n### 2.2.2 Sources of Thread Stats\n\nWith ThreadMxBean:\n\n1. getAllThreadIds() is called to collect Thread Ids\n2. getThreadCpuTime(tids) is called to get all thread cpu time as well as sys cpu time and memory allocation.\n3. getThreadInfo(tids) is called, top 10 threads are shown. StackTrace is not fetched thus the application will not halt.\n\n## 2.3 Spot the Busiest Threads\n\n### 2.3.1 Commands\n\n```\n// ranks threads by their cpu time, by default, the top 10 are shown and refreshed in every 10 secs\n./vjtop.sh <PID>\n\n// ranks threads by total cpu time since startup, differentiated from cpu time within the interval\n./vjtop.sh --totalcpu <PID>\n\n// ranks threads by sys cpu\n./vjtop.sh --syscpu <PID>\n\n// ranks threads by total sys cpu\n./vjtop.sh --totalsyscpu <PID>\n```\n\n### 2.3.2 Outputs\n\n```\n VJTop 1.0.0 - 11:38:02, UPTIME: 3d01h\n PID: 127197, JVM: 1.7.0_79, USER: even.liang\n PROCESS:  0.99% cpu ( 0.04% of 24 core), 2491m rss,   0m swap\n IO:   24k rchar,    1k wchar,    0 read_bytes,    0 write_bytes\n THREAD:   97 active,   89 daemon,   99 peak,  461 created, CLASS: 12243 loaded, 0 unloaded\n HEAP: 160m/819m eden, 0m/102m sur, 43m/1024m old\n NON-HEAP: 55m/256m cms perm gen, 8m/96m codeCache\n OFF-HEAP: 0m/0m direct, 0m/0m map\n GC: 0/0ms ygc, 0/0ms fgc, SAFE-POINT: 6 count, 1ms time, 1ms syncTime\n THREADS-CPU:  1.01% (user= 0.31%, sys= 0.70%)\n\n    TID NAME                                                      STATE    CPU SYSCPU  TOTAL TOLSYS\n     43 metrics-mercury-metric-logger-1-thread-1             TIMED_WAIT  0.38%  0.28% 25.48%  9.13%\n    110 metrics-mercury-metric-logger-2-thread-1             TIMED_WAIT  0.38%  0.18% 25.43%  9.10%\n    496 RMI TCP Connection(365)-192.168.200.87                 RUNNABLE  0.05%  0.05%  0.00%  0.00%\n     82 Proxy-Worker-5-10                                      RUNNABLE  0.01%  0.01%  0.93%  0.30%\n    120 threadDeathWatcher-6-1                               TIMED_WAIT  0.00%  0.00%  0.26%  0.09%\n     98 Proxy-Worker-5-16                                      RUNNABLE  0.00%  0.00%  0.80%  0.26%\n     99 Proxy-Worker-5-17                                      RUNNABLE  0.00%  0.00%  0.92%  0.31%\n     63 Proxy-Worker-5-2                                       RUNNABLE  0.00%  0.00%  1.07%  0.37%\n     70 Proxy-Worker-5-5                                       RUNNABLE  0.00%  0.00%  0.78%  0.26%\n    102 Proxy-Worker-5-20                                      RUNNABLE  0.00%  0.00%  0.80%  0.27%\n\n Note: Only top 10 threads (according cpu load) are shown!\n Cost time:  46ms, CPU time:  60ms\n```\nProcess Region Explained:\n\n* `rss`: `Resident Set Size`, size of all the pages, fetched from /proc/\\<pid\\>/status, for definition see [proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html)\n* `swap`: Size of pages that are swapped out, fetched from /proc/\\<pid\\>/status, for definition see [proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html)\n* `rchar/wchar`: Number of bytes read/written with system calls, fetched from /proc/\\<pid\\>/io, for definition see [proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html)\n* `read_bytes/write_bytes`: Bytes read from/written to the actual storage layer, fetched from/proc/\\<pid\\>/io, for definition see [proc filesystem](http://man7.org/linux/man-pages/man5/proc.5.html)\n* `codeCache`: Cache size holding binaries as result of JIT compilation. JIT Compilation will cease when code cache is fully occupied.\n* `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.\n* `SAFE-POINT`: JVM real stop counts and stop time, collected only when PerfData is available. \n\nThread Region Explained: \n\n* `CPU`: cpu time by percentage within the output interval (100% per core).\n* `SYSCPU`: sys cpu time by percentage within the output interval (100% per core).\n* `TOTAL`:  thread/process total cpu time by percentage since startup.  \n* `TOLSYS`: total sys cpu time by percentage since startup.\n\nBottom Region Explained :\n\n* `Cost time`:  time cost for data gathering & outputting for this watch.\n* `CPU time`:  cpu time cost for data gathering & outputting for this watch.\n\n## 2.4 Spot Threads Allocating the Most Memory\n\n### 2.4.1 Commands\n\n```\n// ranks threads by memory allocation rates, by default, the top 10 are shown and refreshed in every 10 secs\n./vjtop.sh --memory <PID>\n\n// ranks Threads by total memory allcoation rates since startup (instead of by output interval)\n./vjtop.sh --totalmemory <PID>\n```\n\n### 2.4.2 Outputs\n\n```\n(headers omitted)\n THREADS-MEMORY:   30k/s allocation rate\n\n    TID NAME                                                 STATE         MEMORY         TOTAL-ALLOCATED\n  47636 RMI TCP Connection(583)-127.0.0.1                 RUNNABLE   27k/s(88.76%)    17m( 0.00%)\n      1 main                                              RUNNABLE    2k/s( 8.44%)   370g(83.16%)\n  47845 JMX server connection timeout 47845             TIMED_WAIT   251/s( 0.80%)    21k( 0.00%)\n  46607 Worker-501                                      TIMED_WAIT    60/s( 0.19%)   934m( 0.20%)\n  46609 Worker-502                                      TIMED_WAIT    60/s( 0.19%)   822m( 0.18%)\n  46610 Worker-503                                      TIMED_WAIT    60/s( 0.19%)   737m( 0.16%)\n  46763 Worker-504                                      TIMED_WAIT    60/s( 0.19%)   696m( 0.15%)\n  46764 Worker-505                                      TIMED_WAIT    60/s( 0.19%)   743m( 0.16%)\n  47149 Worker-506                                      TIMED_WAIT    60/s( 0.19%)   288m( 0.06%)\n  46551 Worker-500                                      TIMED_WAIT    60/s( 0.19%)   757m( 0.17%)\n```\nProcess Region Explained:\n* `allocation rate`: Summed memory allocation speed of all threads.\n\nBottom Region Explained:\n\n* `STATE`: Current thread state\n* `MEMORY`: Instant memory allocation rate by the second (Instant memory allocation by this thread per second, divided by total allocation)\n* `TOTAL-ALLOCATED`: Accumulated memory allocations since startup, including those recycled (Accumulated memory allocation by this thread, divided by total memory allocation)\n\n## 2.5 Common Args\n\n```\n// prints other options\n./vjtop.sh -h\n\n// outputs to file\n./vjtop.sh <PID> > /tmp/vjtop.log\n\n// refreshes in every 5 secs (default is 10 secs)\n./vjtop.sh -d 5 <PID>\n\n// shows the top 20 threads (default is top 10)\n./vjtop.sh -l 20 <PID>\n\n// prints by width of 120 characters(default is 100)\n./vjtop.sh -w 120 <PID> > /tmp/vjtop.log\n\n// quits after 20 output interations\n./vjtop.sh -n 20 <PID>\n```\n\n# 3. Enhancements over jvmtop\n\n### 3.1 Hot Thread Pages\n\n* Added Display: thread memory allocation rankings (from SJK).\n* Added Display: thread sys cpu time rankings, total cpu time since startup rankings (from SJK).\n* Added Display: thread physical memory, swapness, IO stats.\n* Added Display: **per generation memory and GC information display**, CodeCache and off-heap memory display. \n* Added Config Arg: printing interval, the number of threads to display.\n* Performance boost: **massively reduced time cost** by means of fetching thread cpu time in batches (inspired by SJK).   \n\n### 3.2 Optimizations for Production\n* Remove profile page that causes STW from jvmtop.\n* Eliminate the steps to fetch all java threads in jvmtop. Remove Overview page, in which results are sometimes underterministic.\n* Default output interval set to 10 secs.\n* Print **cost incurred by the monitoring tool itself**.\n"
  },
  {
    "path": "vjtop/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<groupId>com.vip.vjtools</groupId>\n\t<artifactId>vjtop</artifactId>\n\t<version>1.0.9-SNAPSHOT</version>\n\t<name>vjtop</name>\n\t<description>Linux top-like JVM info and busy threads monitoring tools</description>\n\n\t<properties>\n\t\t<!-- change it to point to your tools.jar from your JDK -->\n\t\t<toolsjar>${java.home}/../lib/tools.jar</toolsjar>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<java.version>1.7</java.version>\n\t\t<maven.compiler.source>${java.version}</maven.compiler.source>\n\t\t<maven.compiler.target>${java.version}</maven.compiler.target>\n\t\t<maven-gpg-plugin.version>1.6</maven-gpg-plugin.version>\n\t\t<junit.version>4.12</junit.version>\n\t</properties>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>net.sf.jopt-simple</groupId>\n\t\t\t<artifactId>jopt-simple</artifactId>\n\t\t\t<version>4.9</version>\n\t\t</dependency>\n\t\t<!-- <dependency>\n\t\t\t<groupId>com.sun</groupId>\n\t\t\t<artifactId>tools</artifactId>\n\t\t\t<version>${java.version}</version>\n\t\t\t<scope>system</scope>\n\t\t\t<systemPath>${toolsjar}</systemPath>\n\t\t</dependency> -->\n\t\t<dependency>\n\t\t\t<groupId>junit</groupId>\n\t\t\t<artifactId>junit</artifactId>\n\t\t\t<version>${junit.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-assembly-plugin</artifactId>\n\t\t\t\t<version>2.6</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<descriptorRefs>\n\t\t\t\t\t\t<descriptorRef>jar-with-dependencies</descriptorRef>\n\t\t\t\t\t</descriptorRefs>\n\t\t\t\t</configuration>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>assemble-with-dependencies</id>\n\t\t\t\t\t\t<phase>prepare-package</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>single</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<finalName>vjtop</finalName>\n\t\t\t\t\t\t\t<appendAssemblyId>false</appendAssemblyId>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n \t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>assemble-distribution</id>\n\t\t\t\t\t\t<phase>package</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>single</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<appendAssemblyId>false</appendAssemblyId>\n\t\t\t\t\t\t\t<descriptors>\n\t\t\t\t\t\t\t\t<descriptor>src/main/assembly/distribution.xml\n\t\t\t\t\t\t\t\t</descriptor>\n\t\t\t\t\t\t\t</descriptors>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\t\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>release</id>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<!-- javadoc attach plugin -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-javadoc-plugin</artifactId>\n\t\t\t\t\t\t<version>2.10.4</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<failOnError>false</failOnError>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-javadocs</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t\t<!-- source attach plugin -->\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-source-plugin</artifactId>\n\t\t\t\t\t\t<version>3.0.1</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>attach-sources</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-release-plugin</artifactId>\n\t\t\t\t\t\t<version>2.5.3</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<tagNameFormat>v.@{project.version}</tagNameFormat>\n\t\t\t\t \t\t</configuration>\n\t\t\t\t\t</plugin>\n\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-gpg-plugin</artifactId>\n\t\t\t\t\t\t<version>1.6</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>sign-artifacts</id>\n\t\t\t\t\t\t\t\t<phase>verify</phase>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>sign</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t\t\n\t\t\n\t\t<profile>\n\t      <id>jdk9</id>\n\t      <activation>\n\t        <jdk>[1.9,)</jdk>\n\t      </activation>\n\t      <dependencies/>\n\t    </profile>\n\t    <profile>\n\t      <id>default-jdk</id>\n\t      <activation>\n\t        <jdk>(,1.8]</jdk>\n\t      </activation>\n\t      <dependencies>\n\t        <dependency>\n\t\t\t\t<groupId>com.sun</groupId>\n\t\t\t\t<artifactId>tools</artifactId>\n\t\t\t\t<version>${java.version}</version>\n\t\t\t\t<scope>system</scope>\n\t\t\t\t<systemPath>${toolsjar}</systemPath>\n\t\t\t</dependency>\n\t      </dependencies>\n\t    </profile>\n\t\t\n\t</profiles>\n\t\n\t<distributionManagement>\n\t\t<snapshotRepository>\n\t\t\t<id>sonatype-nexus-snapshots</id>\n\t\t\t<url>https://oss.sonatype.org/content/repositories/snapshots</url>\n\t\t</snapshotRepository>\n\t\t<repository>\n\t\t\t<id>sonatype-nexus-releases</id>\n\t\t\t<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>\n\t\t</repository>\n\t</distributionManagement>\n\n\t<url>https://github.com/vipshop/vjtools</url>\n\n\t<licenses>\n\t\t<license>\n\t\t\t<name>Apache License 2.0</name>\n\t\t\t<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n\t\t\t<distribution>repo</distribution>\n\t\t</license>\n\t</licenses>\n\n\t<scm>\n\t\t<connection>scm:git:https://github.com/vipshop/vjtools.git</connection>\n\t\t<developerConnection>scm:git:https://github.com/vipshop/vjtools.git</developerConnection>\n\t\t<url>https://github.com/vipshop/vjtools</url>\n\t  <tag>v.1.0.2</tag>\n  </scm>\n\n\t<developers>\n\t\t<!--not noly me, write a name here just for sonatype requirement -->\n\t\t<developer>\n\t\t\t<id>calvin</id>\n\t\t\t<name>Calvin Xiao</name>\n\t\t\t<email>calvin.xiao at vipshop.com</email>\n\t\t\t<roles>\n\t\t\t\t<role>developer</role>\n\t\t\t</roles>\n\t\t\t<timezone>+8</timezone>\n\t\t</developer>\n\t</developers>\n</project>\n"
  },
  {
    "path": "vjtop/src/main/assembly/distribution.xml",
    "content": "<assembly\n\txmlns=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3\"\n\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd\">\n\t<id>distribution</id>\n\t<formats>\n\t\t<format>zip</format>\n\t</formats>\n\t\n\t<baseDirectory>${project.artifactId}</baseDirectory>\n\t<files>\n\t\t<file>\n\t\t\t<source>src/main/assembly/vjtop.bat</source>\n\t\t\t<outputDirectory></outputDirectory>\n\t\t\t<lineEnding>windows</lineEnding>\n\t\t</file>\n\t\t<file>\n\t\t\t<source>src/main/assembly/vjtop.sh</source>\n\t\t\t<outputDirectory></outputDirectory>\n\t\t\t<fileMode>0755</fileMode>\n\t\t\t<lineEnding>unix</lineEnding>\n\t\t</file>\n\t\t<file>\n\t\t\t<source>README.md</source>\n\t\t\t<outputDirectory></outputDirectory>\n\t\t\t<destName>README.md</destName>\n\t\t\t<lineEnding>unix</lineEnding>\n\t\t</file>\n\t\t<file>\t\n\t\t\t<source>${project.build.directory}/vjtop.jar</source>\t\n\t\t\t<outputDirectory>/</outputDirectory>\t\n\t\t</file>\n\t</files>\n</assembly>\n"
  },
  {
    "path": "vjtop/src/main/assembly/vjtop.bat",
    "content": "@echo off\n\n\nrem check java\nif \"%JAVA_HOME%\" == \"\" goto noJavaHome\n\n\nset DIR=%~dp0\nset JAVA_OPTS=-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -Xverify:none -XX:AutoBoxCacheMax=20000\n\n\"%JAVA_HOME%\\bin\\java\" %JAVA_OPTS% -cp \"%DIR%/vjtop.jar;%JAVA_HOME%/lib/tools.jar\" com.vip.vjtools.vjtop.VJTop %*\ngoto end\n\n:noJavaHome\n  echo Please set JAVA_HOME before running this script\n  goto end\n:end\n\npause"
  },
  {
    "path": "vjtop/src/main/assembly/vjtop.sh",
    "content": "if [ -z \"$JAVA_HOME\" ] ; then\n\techo \"JAVA_HOME env doesn't exist, try to find the location of java\"\n    JAVA_HOME=`readlink -f \\`which java 2>/dev/null\\` 2>/dev/null | \\\n    sed 's/\\jre\\/bin\\/java//' | sed 's/\\/bin\\/java//'`\nfi\n\nif [ ! -d \"$JAVA_HOME\" ] ; then\n\techo \"Please set JAVA_HOME env before run this script\"\n\texit 1\nfi\n\n\n# returns the JDK version.\n# 8 for 1.8.0_nn, 9 for 9-ea etc, and \"no_java\" for undetected\nGET_JDK_VERSION() {\n  local result\n  local java_cmd\n  if [[ -n $(type -p java) ]]\n  then\n    java_cmd=java\n  elif [[ (-n \"$JAVA_HOME\") && (-x \"$JAVA_HOME/bin/java\") ]]\n  then\n    java_cmd=\"$JAVA_HOME/bin/java\"\n  fi\n  local IFS=$'\\n'\n  # remove \\r for Cygwin\n  local lines=$(\"$java_cmd\" -Xms32M -Xmx32M -version 2>&1 | tr '\\r' '\\n')\n  if [[ -z $java_cmd ]]\n  then\n    result=no_java\n  else\n    for line in $lines; do\n      if [[ (-z $result) && ($line = *\"version \\\"\"*) ]]\n      then\n        local ver=$(echo $line | sed -e 's/.*version \"\\(.*\\)\"\\(.*\\)/\\1/; 1q')\n        # on macOS, sed doesn't support '?'\n        if [[ $ver = \"1.\"* ]]\n        then\n          result=$(echo $ver | sed -e 's/1\\.\\([0-9]*\\)\\(.*\\)/\\1/; 1q')\n        else\n          result=$(echo $ver | sed -e 's/\\([0-9]*\\)\\(.*\\)/\\1/; 1q')\n        fi\n      fi\n    done\n  fi\n  echo \"$result\"\n}\n\nJDK_VERSION=$(GET_JDK_VERSION)\necho \"JDK_VERSION : $JDK_VERSION\"\n\n# jdk 8 and before\nif [[ $JDK_VERSION -le 8 ]]; then\n    TOOLSJAR=\"$JAVA_HOME/lib/tools.jar\"\n    if [ ! -f \"$TOOLSJAR\" ] ; then\n        echo \"$TOOLSJAR doesn't exist\" >&2\n        exit 1\n    fi\n    JAVA_OPTS=\"-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -Xverify:none -XX:AutoBoxCacheMax=20000\"\nelse\n    # jdk 9 or later\n    JAVA_OPTS=\"-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:CICompilerCount=2 -XX:AutoBoxCacheMax=20000\"\nfi\n\n\nDIR=$( cd $(dirname $0) ; pwd -P )\n\"$JAVA_HOME\"/bin/java $JAVA_OPTS -cp \"$DIR/vjtop.jar:$TOOLSJAR\" com.vip.vjtools.vjtop.VJTop \"$@\"\n\nexit $?\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/InteractiveTask.java",
    "content": "package com.vip.vjtools.vjtop;\n\nimport java.io.Console;\nimport java.io.IOException;\nimport java.io.PrintStream;\n\nimport com.vip.vjtools.vjtop.VMDetailView.ThreadInfoMode;\n\n/**\n * 与用户交互动态的控制器\n */\npublic class InteractiveTask implements Runnable {\n\tprivate VJTop app;\n\tprivate Console console;\n\tprivate PrintStream tty;\n\tprivate String inputWhenWaitForEnter;\n\n\tpublic InteractiveTask(VJTop app) {\n\t\tthis.app = app;\n\t\ttty = System.err;\n\t\tconsole = System.console();\n\t}\n\n\tpublic boolean inputEnabled() {\n\t\treturn console != null;\n\t}\n\n\t@Override\n\tpublic void run() {\n\t\t// background执行时，console为Null\n\t\tif (console == null) {\n\t\t\treturn;\n\t\t}\n\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tString command;\n\t\t\t\tif (inputWhenWaitForEnter != null && inputWhenWaitForEnter.length() > 0) {\n\t\t\t\t\tcommand = inputWhenWaitForEnter;\n\t\t\t\t\tinputWhenWaitForEnter = null;\n\t\t\t\t} else {\n\t\t\t\t\tcommand = readLine(\"\");\n\t\t\t\t\tif (command == null) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\thandleCommand(command.toLowerCase());\n\t\t\t\tif (!app.view.shouldExit()) {\n\t\t\t\t\ttty.print(\" Input command (h for help):\");\n\t\t\t\t}\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace(tty);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void handleCommand(String command) throws Exception {\n\t\tif (command.equals(\"s\") || command.startsWith(\"s \")) {\n\t\t\tprintStacktrace(command);\n\t\t} else if (command.equals(\"t\")) {\n\t\t\tprintTopThreadsStack();\n\t\t} else if (command.equals(\"b\")) {\n\t\t\tprintBlockedThreadsStack();\n\t\t} else if (command.equals(\"a\")) {\n\t\t\tprintAllThreadsName();\n\t\t} else if (command.equals(\"m\")) {\n\t\t\tchangeDisplayMode();\n\t\t} else if (command.equals(\"i\") || command.startsWith(\"i \")) {\n\t\t\tchangeInterval(command);\n\t\t} else if (command.equals(\"l\") || command.startsWith(\"l \")) {\n\t\t\tchangeThreadLimit(command);\n\t\t} else if (command.equals(\"f\")) {\n\t\t\tchangeThreadFilter();\n\t\t} else if (command.equals(\"q\") || command.equals(\"quit\") || command.equals(\"exit\")) {\n\t\t\tapp.exit();\n\t\t\treturn;\n\t\t} else if (command.equals(\"h\") || command.equals(\"help\")) {\n\t\t\tprintHelp();\n\t\t} else if (command.equals(\"\")) {\n\t\t\t// do nothing\n\t\t} else {\n\t\t\ttty.println(\" Unkown command: \" + command + \", available options:\");\n\t\t\tprintHelp();\n\t\t}\n\t}\n\n\tprivate void printStacktrace(String command) throws IOException {\n\t\tapp.preventFlush();\n\t\tString pidStr;\n\t\tif (command.length() == 1) {\n\t\t\tpidStr = readLine(\" Input TID:\");\n\t\t} else {\n\t\t\tpidStr = command.substring(2);\n\t\t}\n\n\t\ttry {\n\t\t\tlong pid = Long.parseLong(pidStr);\n\t\t\tapp.view.threadPrinter.printStack(pid);\n\t\t\twaitForEnter();\n\t\t} catch (NumberFormatException e) {\n\t\t\ttty.println(\" Wrong number format for pid\");\n\t\t} finally {\n\t\t\tapp.continueFlush();\n\t\t}\n\t}\n\n\tprivate void printTopThreadsStack() throws IOException {\n\t\ttry {\n\t\t\tapp.preventFlush();\n\t\t\tapp.view.threadPrinter.printTopStack();\n\t\t\twaitForEnter();\n\t\t} finally {\n\t\t\tapp.continueFlush();\n\t\t}\n\t}\n\n\tprivate void printAllThreadsName() throws IOException {\n\t\ttry {\n\t\t\tapp.preventFlush();\n\t\t\tapp.view.threadPrinter.printAllThreads();\n\t\t\twaitForEnter();\n\t\t} finally {\n\t\t\tapp.continueFlush();\n\t\t}\n\t}\n\n\tprivate void printBlockedThreadsStack() throws IOException {\n\t\ttry {\n\t\t\tapp.preventFlush();\n\t\t\tapp.view.threadPrinter.printBlockedThreads();\n\t\t\twaitForEnter();\n\t\t} finally {\n\t\t\tapp.continueFlush();\n\t\t}\n\t}\n\n\tprivate void changeDisplayMode() {\n\t\tapp.preventFlush();\n\n\t\tString mode = readLine(\n\t\t\t\t\" Input number of Display Mode(1.cpu, 2.syscpu 3.total cpu 4.total syscpu 5.memory 6.total memory, current \"\n\t\t\t\t\t\t+ app.view.threadInfoMode + \"): \");\n\t\tThreadInfoMode detailMode = ThreadInfoMode.parseInt(mode);\n\n\t\tif (detailMode == null) {\n\t\t\ttty.println(\" Wrong option for display mode(1-6)\");\n\t\t} else if (detailMode == app.view.threadInfoMode) {\n\t\t\ttty.println(\" Nothing be changed\");\n\t\t} else {\n\t\t\tif (app.view.threadInfoMode.isCpuMode != detailMode.isCpuMode) {\n\t\t\t\tapp.view.switchCpuAndMemory();\n\t\t\t\tapp.view.threadInfoMode = detailMode;\n\t\t\t\ttty.println(\" Display mode changed to \" + app.view.threadInfoMode + \" for next flush\");\n\t\t\t\tapp.interruptSleep();\n\t\t\t} else {\n\t\t\t\tapp.view.threadInfoMode = detailMode;\n\t\t\t\ttty.println(\" Display mode changed to \" + app.view.threadInfoMode + \" for next flush(\"\n\t\t\t\t\t\t+ app.nextFlushTime() + \"s later)\");\n\t\t\t}\n\t\t}\n\t\tapp.continueFlush();\n\t}\n\n\tprivate void changeInterval(String command) {\n\t\tapp.preventFlush();\n\n\t\tString intervalStr;\n\t\tif (command.length() == 1) {\n\t\t\tintervalStr = readLine(\" Input flush interval seconds(current \" + app.getInterval() + \"):\");\n\t\t} else {\n\t\t\tintervalStr = command.substring(2);\n\t\t}\n\n\t\ttry {\n\t\t\tint interval = Integer.parseInt(intervalStr);\n\t\t\tif (interval != app.getInterval()) {\n\t\t\t\tapp.updateInterval(interval);\n\t\t\t\ttty.println(\" Flush interval change to \" + interval + \" seconds\");\n\t\t\t\tapp.interruptSleep();\n\t\t\t} else {\n\t\t\t\ttty.println(\" Nothing be changed\");\n\t\t\t}\n\t\t} catch (NumberFormatException e) {\n\t\t\ttty.println(\" Wrong number format for interval\");\n\t\t} finally {\n\t\t\tapp.continueFlush();\n\t\t}\n\t}\n\n\tprivate void changeThreadLimit(String command) {\n\t\tapp.preventFlush();\n\n\t\tString threadLimitStr;\n\t\tif (command.length() == 1) {\n\t\t\tthreadLimitStr = readLine(\" Input number of threads to display(current \" + app.view.threadLimit + \"):\");\n\t\t} else {\n\t\t\tthreadLimitStr = command.substring(2);\n\t\t}\n\n\t\ttry {\n\t\t\tint threadLimit = Integer.parseInt(threadLimitStr);\n\t\t\tif (threadLimit != app.view.threadLimit) {\n\t\t\t\tapp.view.threadLimit = threadLimit;\n\t\t\t\ttty.println(\" Number of threads to display change to \" + threadLimit + \" for next flush(\"\n\t\t\t\t\t\t+ app.nextFlushTime() + \"s later)\");\n\t\t\t} else {\n\t\t\t\ttty.println(\" Nothing be changed\");\n\t\t\t}\n\t\t} catch (NumberFormatException e) {\n\t\t\ttty.println(\" Wrong number format for number of threads\");\n\t\t} finally {\n\t\t\tapp.continueFlush();\n\t\t}\n\t}\n\n\tprivate void changeThreadFilter() {\n\t\tapp.preventFlush();\n\n\t\tString threadNameFilter = readLine(\" Input thread name filter (current \" + app.view.threadNameFilter + \"):\");\n\n\t\tif (threadNameFilter != null && threadNameFilter.trim().length() == 0) {\n\t\t\tthreadNameFilter = null;\n\t\t}\n\n\t\tapp.view.threadNameFilter = threadNameFilter != null ? threadNameFilter.toLowerCase() : null;\n\t\ttty.println(\" Thread name filter change to \" + threadNameFilter + \" for next flush (\" + app.nextFlushTime()\n\t\t\t\t+ \"s later)\");\n\n\t\tapp.continueFlush();\n\t}\n\n\tprivate void printHelp() throws Exception {\n\t\tapp.preventFlush();\n\t\ttty.println(\" s [tid]: print stack trace of the thread you choose\");\n\t\ttty.println(\" t : print stack trace of top \" + app.view.threadLimit + \" threads\");\n\t\ttty.println(\" b : print stack trace of blocked threads\");\n\t\ttty.println(\" a : list id and name of all threads\");\n\t\ttty.println(\" ---------------\");\n\t\ttty.println(\" m : change threads display mode and ordering\");\n\t\ttty.println(\" i [num]: change flush interval seconds\");\n\t\ttty.println(\" l [num]: change number of display threads\");\n\t\ttty.println(\" f [name]: set thread name filter\");\n\t\ttty.println(\" ---------------\");\n\t\ttty.println(\" q : quit\");\n\t\ttty.println(\" h : print help\");\n\t\twaitForEnter();\n\t\tapp.continueFlush();\n\t}\n\n\tprivate void waitForEnter() {\n\t\tinputWhenWaitForEnter = readLine(\" Please hit <ENTER> to continue...\");\n\t}\n\n\tprivate String readLine(String hints) {\n\t\tString result = console.readLine(hints);\n\t\tif (result != null) {\n\t\t\treturn result.trim();\n\t\t}\n\n\t\treturn null;\n\t}\n}"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/ThreadPrinter.java",
    "content": "package com.vip.vjtools.vjtop;\n\nimport java.io.IOException;\nimport java.lang.Thread.State;\nimport java.lang.management.LockInfo;\nimport java.lang.management.ThreadInfo;\n\n/**\n * 打印线程名，线程栈信息\n */\npublic class ThreadPrinter {\n\n\tprivate VMDetailView view;\n\n\tpublic ThreadPrinter(VMDetailView view) {\n\t\tthis.view = view;\n\t}\n\n\t/**\n\t * 打印单条线程的stack strace，会造成停顿，但比获取全部线程的stack trace停顿少\n\t */\n\tpublic void printStack(long tid) throws IOException {\n\t\tSystem.out.printf(\"%n Stack trace of thread %d:%n\", tid);\n\n\t\tThreadInfo info = view.vmInfo.getThreadInfo(tid, 20);\n\t\tif (info == null) {\n\t\t\tSystem.err.println(\" TID not exist:\" + tid);\n\t\t\treturn;\n\t\t}\n\t\tprintSingleThread(info);\n\t\tSystem.out.flush();\n\t}\n\n\t/**\n\t * 打印所有活跃线程的stack strace，会造成停顿，但比获取全部线程的stack trace停顿少\n\t */\n\tpublic void printTopStack() throws IOException {\n\t\tSystem.out.printf(\"%n Stack trace of top %d threads:%n\", view.threadLimit);\n\n\t\tThreadInfo[] infos = view.topThreadInfo.getTopThreadInfo();\n\t\tfor (ThreadInfo info : infos) {\n\t\t\tif (info == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tprintSingleThread(info);\n\t\t}\n\t\tSystem.out.flush();\n\t}\n\n\tpublic StackTraceElement[] printSingleThread(ThreadInfo info) {\n\t\tStackTraceElement[] trace = info.getStackTrace();\n\t\tStringBuilder sb = new StringBuilder(512);\n\n\t\tsb.append(\" \").append(info.getThreadId()).append(\": \\\"\").append(info.getThreadName()).append(\"\\\"\");\n\n\t\tif (view.vmInfo.threadContentionMonitoringSupported) {\n\t\t\tsb.append(\" (blocked:\").append(info.getBlockedCount()).append(\"/\").append(info.getBlockedTime())\n\t\t\t\t\t.append(\"ms, wait:\").append(info.getWaitedCount()).append(\"/\").append(info.getWaitedTime())\n\t\t\t\t\t.append(\"ms\");\n\t\t} else {\n\t\t\tsb.append(\" (blocked:\").append(info.getBlockedCount()).append(\" times, wait:\").append(info.getWaitedCount())\n\t\t\t\t\t.append(\" times\");\n\t\t}\n\n\t\tif (info.isSuspended()) {\n\t\t\tsb.append(\" ,suspended\");\n\t\t}\n\t\tif (info.isInNative()) {\n\t\t\tsb.append(\" ,in native\");\n\t\t}\n\t\tsb.append(\")\\n\");\n\n\t\tsb.append(\"   java.lang.Thread.State: \" + info.getThreadState().toString());\n\t\tLockInfo lockInfo = info.getLockInfo();\n\t\tif (lockInfo != null) {\n\t\t\tsb.append(\"(on \" + lockInfo + \")\");\n\t\t}\n\t\tif (info.getLockOwnerName() != null) {\n\t\t\tsb.append(\" owned by \" + info.getLockOwnerId() + \":\\\"\" + info.getLockOwnerName() + \"\\\"\");\n\t\t}\n\t\tsb.append(\"\\n\");\n\t\tfor (StackTraceElement traceElement : trace) {\n\t\t\tsb.append(\"\\tat \").append(traceElement).append(\"\\n\");\n\t\t}\n\n\t\tSystem.out.print(sb.toString());\n\n\t\treturn trace;\n\t}\n\n\t/**\n\t * 打印所有线程，只获取名称不获取stack，不造成停顿\n\t */\n\tpublic void printAllThreads() throws IOException {\n\t\tint[] stateCounter = new int[6];\n\n\t\tSystem.out.println(\"\\n Thread Id and name of all live threads:\");\n\n\t\tlong tids[] = view.vmInfo.getAllThreadIds();\n\t\tThreadInfo[] threadInfos = view.vmInfo.getThreadInfo(tids);\n\t\tfor (ThreadInfo info : threadInfos) {\n\t\t\tif (info == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tString threadName = info.getThreadName();\n\t\t\tif (view.threadNameFilter != null && !threadName.toLowerCase().contains(view.threadNameFilter)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tSystem.out.println(\n\t\t\t\t\t\" \" + info.getThreadId() + \"\\t: \\\"\" + threadName + \"\\\" (\" + info.getThreadState().toString() + \")\");\n\t\t\tstateCounter[info.getThreadState().ordinal()]++;\n\t\t}\n\n\t\tStringBuilder statesSummary = new StringBuilder(\" Summary: \");\n\t\tfor (State state : State.values()) {\n\t\t\tstatesSummary.append(state.toString()).append(':').append(stateCounter[state.ordinal()]).append(\"  \");\n\t\t}\n\t\tSystem.out.println(statesSummary.append(\"\\n\").toString());\n\n\t\tif (view.threadNameFilter != null) {\n\t\t\tSystem.out.println(\" Thread name filter is:\" + view.threadNameFilter);\n\t\t}\n\t\tSystem.out.flush();\n\t}\n\n\tpublic void printBlockedThreads() throws IOException {\n\t\tSystem.out.println(\"\\n Stack trace of blocked threads:\");\n\t\tint counter = 0;\n\t\tThreadInfo[] threadInfos = view.vmInfo.getAllThreadInfo();\n\t\tfor (ThreadInfo info : threadInfos) {\n\t\t\tif (info == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tString threadName = info.getThreadName();\n\n\t\t\tif (Thread.State.BLOCKED.equals(info.getThreadState())\n\t\t\t\t\t&& (view.threadNameFilter == null || threadName.toLowerCase().contains(view.threadNameFilter))) {\n\t\t\t\tprintSingleThread(info);\n\t\t\t\tcounter++;\n\t\t\t}\n\t\t}\n\n\t\tSystem.out.println(\" Total \" + counter + \" blocked threads\");\n\n\t\tif (view.threadNameFilter != null) {\n\t\t\tSystem.out.println(\" Thread name filter is:\" + view.threadNameFilter);\n\t\t}\n\t\tSystem.out.flush();\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/TopThreadInfo.java",
    "content": "package com.vip.vjtools.vjtop;\n\nimport java.io.IOException;\nimport java.lang.management.ThreadInfo;\n\nimport com.vip.vjtools.vjtop.VMDetailView.ThreadInfoMode;\nimport com.vip.vjtools.vjtop.util.LongObjectHashMap;\nimport com.vip.vjtools.vjtop.util.LongObjectMap;\nimport com.vip.vjtools.vjtop.util.Utils;\n\npublic class TopThreadInfo {\n\tprivate VMInfo vmInfo;\n\tprivate long[] topTidArray;\n\n\tprivate LongObjectMap<Long> lastThreadCpuTotalTimes = new LongObjectHashMap<>();\n\tprivate LongObjectMap<Long> lastThreadSysCpuTotalTimes = new LongObjectHashMap<>();\n\tprivate LongObjectMap<Long> lastThreadMemoryTotalBytes = new LongObjectHashMap<>();\n\n\tpublic TopThreadInfo(VMInfo vmInfo) throws Exception {\n\t\tthis.vmInfo = vmInfo;\n\t}\n\n\tpublic TopCpuResult topCpuThreads(ThreadInfoMode mode, int threadLimit) throws IOException {\n\n\t\tTopCpuResult result = new TopCpuResult();\n\n\t\ttry {\n\t\t\tlong tids[] = vmInfo.getAllThreadIds();\n\n\t\t\tint mapSize = tids.length * 2;\n\t\t\tresult.threadCpuTotalTimes = new LongObjectHashMap<>(mapSize);\n\t\t\tresult.threadCpuDeltaTimes = new LongObjectHashMap<>(mapSize);\n\t\t\tresult.threadSysCpuTotalTimes = new LongObjectHashMap<>(mapSize);\n\t\t\tresult.threadSysCpuDeltaTimes = new LongObjectHashMap<>(mapSize);\n\n\t\t\t// 批量获取CPU times，性能大幅提高。\n\t\t\t// 两次获取之间有间隔，在低流量下可能造成负数\n\t\t\tlong[] threadCpuTotalTimeArray = vmInfo.getThreadCpuTime(tids);\n\t\t\tlong[] threadUserCpuTotalTimeArray = vmInfo.getThreadUserTime(tids);\n\n\t\t\t// 过滤CPU占用太少的线程，每秒0.01%CPU (0.1ms cpu time)\n\t\t\tlong minDeltaCpuTime = (vmInfo.upTimeMills.delta * Utils.NANOS_TO_MILLS / 10000);\n\n\t\t\t// 计算本次CPU Time\n\t\t\t// 此算法第一次不会显示任何数据，保证每次显示都只显示区间内数据\n\t\t\tfor (int i = 0; i < tids.length; i++) {\n\t\t\t\tlong tid = tids[i];\n\t\t\t\tLong threadCpuTotalTime = threadCpuTotalTimeArray[i];\n\t\t\t\tresult.threadCpuTotalTimes.put(tid, threadCpuTotalTime);\n\n\t\t\t\tLong lastTime = lastThreadCpuTotalTimes.get(tid);\n\t\t\t\tif (lastTime != null) {\n\t\t\t\t\tLong deltaThreadCpuTime = threadCpuTotalTime - lastTime;\n\t\t\t\t\tif (deltaThreadCpuTime >= minDeltaCpuTime) {\n\t\t\t\t\t\tresult.threadCpuDeltaTimes.put(tid, deltaThreadCpuTime);\n\t\t\t\t\t\tresult.deltaAllActiveThreadCpu += deltaThreadCpuTime;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult.deltaAllFreeThreadCpu += deltaThreadCpuTime;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 计算本次SYSCPU Time\n\t\t\tfor (int i = 0; i < tids.length; i++) {\n\t\t\t\tlong tid = tids[i];\n\t\t\t\t// 因为totalTime 与 userTime 的获取时间有先后，实际sys接近0时，后取的userTime可能比前一时刻的totalTime高，计算出来的sysTime可为负数\n\t\t\t\tLong threadSysCpuTotalTime = Math.max(0, threadCpuTotalTimeArray[i] - threadUserCpuTotalTimeArray[i]);\n\t\t\t\tresult.threadSysCpuTotalTimes.put(tid, threadSysCpuTotalTime);\n\n\t\t\t\tLong lastTime = lastThreadSysCpuTotalTimes.get(tid);\n\t\t\t\tif (lastTime != null) {\n\t\t\t\t\tLong deltaThreadSysCpuTime = Math.max(0, threadSysCpuTotalTime - lastTime);\n\t\t\t\t\tif (deltaThreadSysCpuTime >= minDeltaCpuTime) {\n\t\t\t\t\t\tresult.threadSysCpuDeltaTimes.put(tid, deltaThreadSysCpuTime);\n\t\t\t\t\t\tresult.deltaAllActiveThreadSysCpu += deltaThreadSysCpuTime;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (lastThreadCpuTotalTimes.isEmpty()) {\n\t\t\t\tlastThreadCpuTotalTimes = result.threadCpuTotalTimes;\n\t\t\t\tlastThreadSysCpuTotalTimes = result.threadSysCpuTotalTimes;\n\t\t\t\tresult.ready = false;\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\t// 按不同类型排序,过滤\n\t\t\tif (mode == ThreadInfoMode.cpu) {\n\t\t\t\ttopTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadCpuDeltaTimes, threadLimit);\n\t\t\t} else if (mode == ThreadInfoMode.syscpu) {\n\t\t\t\ttopTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadSysCpuDeltaTimes, threadLimit);\n\t\t\t} else if (mode == ThreadInfoMode.totalcpu) {\n\t\t\t\ttopTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadCpuTotalTimes, threadLimit);\n\t\t\t} else if (mode == ThreadInfoMode.totalsyscpu) {\n\t\t\t\ttopTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadSysCpuTotalTimes, threadLimit);\n\t\t\t} else {\n\t\t\t\tthrow new RuntimeException(\"unkown mode:\" + mode);\n\t\t\t}\n\n\t\t\tresult.activeThreads = result.threadCpuDeltaTimes.size();\n\n\t\t\t// 获得线程名等信息threadInfo\n\t\t\tresult.threadInfos = vmInfo.getThreadInfo(topTidArray);\n\n\t\t\tlastThreadCpuTotalTimes = result.threadCpuTotalTimes;\n\t\t\tlastThreadSysCpuTotalTimes = result.threadSysCpuTotalTimes;\n\t\t} catch (Exception e) {\n\t\t\tvmInfo.handleJmxFetchDataError(e);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tpublic TopMemoryResult topMemoryThreads(ThreadInfoMode mode, int threadLimit) throws IOException {\n\t\tTopMemoryResult result = new TopMemoryResult();\n\t\ttry {\n\t\t\tlong tids[] = vmInfo.getAllThreadIds();\n\n\t\t\tint mapSize = tids.length * 2;\n\t\t\tresult.threadMemoryTotalBytesMap = new LongObjectHashMap<>(mapSize);\n\t\t\tresult.threadMemoryDeltaBytesMap = new LongObjectHashMap<>(mapSize);\n\n\t\t\t// 批量获取内存分配\n\t\t\tlong[] threadMemoryTotalBytesArray = vmInfo.getThreadAllocatedBytes(tids);\n\n\t\t\t// 此算法第一次不会显示任何数据，保证每次显示都只显示区间内数据\n\t\t\tfor (int i = 0; i < tids.length; i++) {\n\t\t\t\tlong tid = tids[i];\n\t\t\t\tLong threadMemoryTotalBytes = threadMemoryTotalBytesArray[i];\n\t\t\t\tresult.threadMemoryTotalBytesMap.put(tid, threadMemoryTotalBytes);\n\t\t\t\tresult.totalAllThreadBytes += threadMemoryTotalBytes;\n\n\t\t\t\tLong threadMemoryDeltaBytes = 0L;\n\t\t\t\tLong lastBytes = lastThreadMemoryTotalBytes.get(tid);\n\n\t\t\t\tif (lastBytes != null) {\n\t\t\t\t\tthreadMemoryDeltaBytes = threadMemoryTotalBytes - lastBytes;\n\t\t\t\t\tif (threadMemoryDeltaBytes > 0) {\n\t\t\t\t\t\tresult.threadMemoryDeltaBytesMap.put(tid, threadMemoryDeltaBytes);\n\t\t\t\t\t\tresult.deltaAllThreadBytes += threadMemoryDeltaBytes;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (lastThreadMemoryTotalBytes.isEmpty()) {\n\t\t\t\tlastThreadMemoryTotalBytes = result.threadMemoryTotalBytesMap;\n\t\t\t\tresult.ready = false;\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\t// 线程排序\n\t\t\tlong[] topTidArray;\n\t\t\tif (mode == ThreadInfoMode.memory) {\n\t\t\t\ttopTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadMemoryDeltaBytesMap, threadLimit);\n\t\t\t} else {\n\t\t\t\ttopTidArray = Utils.sortAndFilterThreadIdsByValue(result.threadMemoryTotalBytesMap, threadLimit);\n\t\t\t}\n\n\t\t\tresult.activeThreads = result.threadMemoryDeltaBytesMap.size();\n\n\t\t\tresult.threadInfos = vmInfo.getThreadInfo(topTidArray);\n\n\t\t\tlastThreadMemoryTotalBytes = result.threadMemoryTotalBytesMap;\n\t\t} catch (Exception e) {\n\t\t\tvmInfo.handleJmxFetchDataError(e);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic ThreadInfo[] getTopThreadInfo() throws IOException {\n\t\treturn vmInfo.getThreadInfo(topTidArray, 20);\n\t}\n\n\tpublic void cleanupThreadsHistory() {\n\t\tthis.lastThreadCpuTotalTimes.clear();\n\t\tthis.lastThreadSysCpuTotalTimes.clear();\n\t\tthis.lastThreadMemoryTotalBytes.clear();\n\t}\n\n\tpublic static class TopCpuResult {\n\t\tpublic ThreadInfo[] threadInfos;\n\t\tpublic long activeThreads = 0;\n\n\t\tpublic long deltaAllActiveThreadCpu = 0;\n\t\tpublic long deltaAllActiveThreadSysCpu = 0;\n\n\t\tpublic long deltaAllFreeThreadCpu = 0;\n\n\t\tpublic LongObjectMap<Long> threadCpuTotalTimes;\n\t\tpublic LongObjectMap<Long> threadCpuDeltaTimes;\n\t\tpublic LongObjectMap<Long> threadSysCpuTotalTimes;\n\t\tpublic LongObjectMap<Long> threadSysCpuDeltaTimes;\n\n\t\tpublic boolean ready = true;\n\t}\n\n\tpublic static class TopMemoryResult {\n\t\tpublic ThreadInfo[] threadInfos;\n\t\tpublic long activeThreads = 0;\n\n\t\tpublic long deltaAllThreadBytes = 0;\n\t\tpublic long totalAllThreadBytes = 0;\n\n\t\tpublic LongObjectMap<Long> threadMemoryTotalBytesMap;\n\t\tpublic LongObjectMap<Long> threadMemoryDeltaBytesMap;\n\n\t\tpublic boolean ready = true;\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/VJTop.java",
    "content": "package com.vip.vjtools.vjtop;\n\nimport java.io.BufferedOutputStream;\nimport java.io.FileDescriptor;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.PrintStream;\n\nimport com.vip.vjtools.vjtop.VMDetailView.ContentMode;\nimport com.vip.vjtools.vjtop.VMDetailView.OutputFormat;\nimport com.vip.vjtools.vjtop.VMDetailView.ThreadInfoMode;\nimport com.vip.vjtools.vjtop.VMInfo.VMInfoState;\nimport com.vip.vjtools.vjtop.util.Formats;\nimport com.vip.vjtools.vjtop.util.OptionAdvanceParser;\nimport com.vip.vjtools.vjtop.util.Utils;\n\nimport joptsimple.OptionParser;\nimport joptsimple.OptionSet;\n\npublic class VJTop {\n\n\tpublic static final String VERSION = \"1.0.9\";\n\n\tpublic VMDetailView view;\n\tprivate Thread mainThread;\n\n\tprivate Integer interval;\n\tprivate int maxIterations = -1;\n\n\tprivate volatile boolean needMoreInput = false;\n\tprivate long sleepStartTime;\n\n\tpublic static void main(String[] args) {\n\t\ttry {\n\t\t\t// 1. create option parser\n\t\t\tOptionParser parser = OptionAdvanceParser.createOptionParser();\n\t\t\tOptionSet optionSet = parser.parse(args);\n\n\t\t\tif (optionSet.has(\"help\")) {\n\t\t\t\tprintHelper(parser);\n\t\t\t\tSystem.exit(0);\n\t\t\t}\n\n\t\t\t// 2. create vminfo\n\t\t\tString pid = OptionAdvanceParser.parsePid(parser, optionSet);\n\n\t\t\tString jmxHostAndPort = null;\n\t\t\tif (optionSet.hasArgument(\"jmxurl\")) {\n\t\t\t\tjmxHostAndPort = (String) optionSet.valueOf(\"jmxurl\");\n\t\t\t}\n\n\t\t\tVMInfo vminfo = VMInfo.processNewVM(pid, jmxHostAndPort);\n\t\t\tif (vminfo.state != VMInfoState.ATTACHED) {\n\t\t\t\tSystem.out\n\t\t\t\t\t\t.println(\"\\n\" + Formats.red(\"ERROR: Could not attach to process, see the solution in README\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// 3. create view\n\t\t\tThreadInfoMode threadInfoMode = OptionAdvanceParser.parseThreadInfoMode(optionSet);\n\t\t\tOutputFormat format = OptionAdvanceParser.parseOutputFormat(optionSet);\n\t\t\tContentMode contentMode = OptionAdvanceParser.parseContentMode(optionSet);\n\n\t\t\tInteger width = null;\n\t\t\tif (optionSet.hasArgument(\"width\")) {\n\t\t\t\twidth = (Integer) optionSet.valueOf(\"width\");\n\t\t\t}\n\n\t\t\tInteger interval = OptionAdvanceParser.parseInterval(optionSet);\n\n\t\t\tVMDetailView view = new VMDetailView(vminfo, format, contentMode, threadInfoMode, width, interval);\n\n\t\t\tif (optionSet.hasArgument(\"limit\")) {\n\t\t\t\tInteger limit = (Integer) optionSet.valueOf(\"limit\");\n\t\t\t\tview.threadLimit = limit;\n\t\t\t}\n\n\t\t\tif (optionSet.hasArgument(\"filter\")) {\n\t\t\t\tString filter = (String) optionSet.valueOf(\"filter\");\n\t\t\t\tview.threadNameFilter = filter;\n\t\t\t}\n\n\t\t\t// 4. create main application\n\t\t\tVJTop app = new VJTop();\n\t\t\tapp.mainThread = Thread.currentThread();\n\t\t\tapp.view = view;\n\t\t\tapp.updateInterval(interval);\n\n\t\t\tif (optionSet.hasArgument(\"n\")) {\n\t\t\t\tInteger iterations = (Integer) optionSet.valueOf(\"n\");\n\t\t\t\tapp.maxIterations = iterations;\n\t\t\t}\n\n\t\t\t// 5. console/cleanConsole mode start thread to get user input\n\t\t\tif (format != OutputFormat.text) {\n\t\t\t\tInteractiveTask task = new InteractiveTask(app);\n\t\t\t\t// 前台运行，接受用户输入时才启动交互进程\n\t\t\t\tif (task.inputEnabled()) {\n\t\t\t\t\tview.displayCommandHints = true;\n\t\t\t\t\tif (app.maxIterations == -1) {\n\t\t\t\t\t\tThread interactiveThread = new Thread(task, \"InteractiveThread\");\n\t\t\t\t\t\tinteractiveThread.setDaemon(true);\n\t\t\t\t\t\tinteractiveThread.start();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// 后台运行，输出重定向到文件时，转为没有ansi码的干净模式\n\t\t\t\t\tformat = OutputFormat.cleanConsole;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 6. cleanConsole/text mode, 屏蔽ansi码\n\t\t\tif (!format.ansi) {\n\t\t\t\tFormats.disableAnsi();\n\t\t\t\tif (format == OutputFormat.cleanConsole) {\n\t\t\t\t\tFormats.setCleanClearTerminal();\n\t\t\t\t} else {\n\t\t\t\t\tFormats.setTextClearTerminal();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 7. run app\n\t\t\tapp.run(view);\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace(System.out);\n\t\t\tSystem.out.flush();\n\t\t}\n\t}\n\n\tprivate void run(VMDetailView view) throws Exception {\n\t\ttry {\n\t\t\t// System.out 设为Buffered，需要使用System.out.flush刷新\n\t\t\tSystem.setOut(new PrintStream(new BufferedOutputStream(new FileOutputStream(FileDescriptor.out)), false));\n\n\t\t\tint iterations = 0;\n\t\t\twhile (!view.shouldExit()) {\n\t\t\t\twaitForInput();\n\t\t\t\tview.printView();\n\t\t\t\tif (view.shouldExit()) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tSystem.out.flush();\n\n\t\t\t\tif (maxIterations > 0 && iterations >= maxIterations) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// 第一次最多只等待3秒\n\t\t\t\tint sleepSeconds = (iterations == 0) ? Math.min(3, interval) : interval;\n\n\t\t\t\titerations++;\n\t\t\t\tsleepStartTime = System.currentTimeMillis();\n\t\t\t\tUtils.sleep(sleepSeconds * 1000L);\n\t\t\t}\n\t\t\tSystem.out.println(\"\");\n\t\t\tSystem.out.flush();\n\t\t} catch (NoClassDefFoundError e) {\n\t\t\te.printStackTrace(System.out);\n\t\t\tSystem.out.println(Formats.red(\"ERROR: Some JDK classes cannot be found.\"));\n\t\t\tSystem.out.println(\"       Please check if the JAVA_HOME environment variable has been set to a JDK path.\");\n\t\t\tSystem.out.println(\"\");\n\t\t\tSystem.out.flush();\n\t\t}\n\t}\n\n\tpublic static void printHelper(OptionParser parser) {\n\t\ttry {\n\t\t\tSystem.out.println(\"vjtop \" + VERSION + \" - java monitoring for the command-line\");\n\t\t\tSystem.out.println(\"Usage: vjtop.sh [options...] <PID>\");\n\t\t\tSystem.out.println(\"\");\n\t\t\tparser.printHelpOn(System.out);\n\t\t} catch (IOException ignored) {\n\n\t\t}\n\t}\n\n\tpublic void exit() {\n\t\tview.shoulExit();\n\t\tmainThread.interrupt();\n\t}\n\n\tpublic void interruptSleep() {\n\t\tmainThread.interrupt();\n\t}\n\n\tpublic void preventFlush() {\n\t\tneedMoreInput = true;\n\t}\n\n\tpublic void continueFlush() {\n\t\tneedMoreInput = false;\n\t}\n\n\tprivate void waitForInput() {\n\t\twhile (needMoreInput) {\n\t\t\tUtils.sleep(1000);\n\t\t}\n\t}\n\n\tpublic int nextFlushTime() {\n\t\treturn Math.max(0, interval - (int) ((System.currentTimeMillis() - sleepStartTime) / 1000));\n\t}\n\n\tpublic void updateInterval(int interval) {\n\t\tthis.interval = interval;\n\t\tview.interval = interval;\n\t}\n\n\tpublic int getInterval() {\n\t\treturn interval;\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/VMDetailView.java",
    "content": "package com.vip.vjtools.vjtop;\n\nimport java.io.IOException;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.ThreadInfo;\nimport java.util.Date;\n\nimport com.sun.management.OperatingSystemMXBean;\nimport com.vip.vjtools.vjtop.TopThreadInfo.TopCpuResult;\nimport com.vip.vjtools.vjtop.TopThreadInfo.TopMemoryResult;\nimport com.vip.vjtools.vjtop.util.Formats;\nimport com.vip.vjtools.vjtop.util.Utils;\n\n@SuppressWarnings(\"restriction\")\npublic class VMDetailView {\n\tprivate static final int DEFAULT_WIDTH = 100;\n\tprivate static final int MIN_WIDTH = 80;\n\n\tpublic ThreadInfoMode threadInfoMode;\n\tprivate ContentMode contentMode;\n\tprivate OutputFormat format;\n\n\tpublic int threadLimit = 10;\n\tpublic int interval;\n\tpublic String threadNameFilter = null;\n\n\tprivate int width;\n\n\tpublic VMInfo vmInfo;\n\tpublic TopThreadInfo topThreadInfo;\n\tpublic ThreadPrinter threadPrinter;\n\tprivate WarningRule warning;\n\n\t// 纪录vjtop进程本身的消耗\n\tprivate boolean isDebugCost = false;\n\tprivate long lastCpu = 0;\n\n\tprivate boolean shouldExit = false;\n\tprivate boolean firstTime = true;\n\tpublic boolean displayCommandHints = false;\n\n\tpublic VMDetailView(VMInfo vmInfo, OutputFormat format, ContentMode contentMode, ThreadInfoMode threadInfoMode,\n\t\t\tInteger width, Integer interval) throws Exception {\n\t\tthis.vmInfo = vmInfo;\n\t\tthis.topThreadInfo = new TopThreadInfo(vmInfo);\n\t\tthis.threadPrinter = new ThreadPrinter(this);\n\t\tthis.warning = vmInfo.warningRule;\n\n\t\tthis.contentMode = contentMode;\n\t\tthis.threadInfoMode = threadInfoMode;\n\t\tthis.format = format;\n\n\t\tthis.interval = interval;\n\t\tsetWidth(width);\n\n\t\tif (contentMode == ContentMode.all || contentMode == ContentMode.thread) {\n\t\t\tvmInfo.initThreadInfoAbility();\n\t\t}\n\t}\n\n\tpublic void printView() throws Exception {\n\n\t\tFormats.clearTerminal();\n\n\t\t// 计算vjtop自身消耗\n\t\tlong iterationStartTime = 0;\n\t\tlong iterationStartCpu = 0;\n\t\tif (isDebugCost) {\n\t\t\titerationStartTime = System.currentTimeMillis();\n\t\t\titerationStartCpu = ((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean())\n\t\t\t\t\t.getProcessCpuTime();\n\t\t}\n\n\t\tvmInfo.update(contentMode == ContentMode.all || contentMode == ContentMode.jvm);\n\n\t\tif (!checkState()) {\n\t\t\treturn;\n\t\t}\n\n\t\t// 打印进程级别内容\n\t\tif (contentMode == ContentMode.all || contentMode == ContentMode.jvm) {\n\t\t\tif (format == OutputFormat.text) {\n\t\t\t\tprintJvmInfoAsText();\n\t\t\t} else {\n\t\t\t\tprintJvmInfoAsConsole();\n\t\t\t}\n\t\t}\n\n\t\t// JMX更新失败，不打印后续一定需要JMX获取的数据\n\t\tif (!vmInfo.isJmxStateOk()) {\n\t\t\tprintJmxError();\n\t\t\treturn;\n\t\t}\n\n\t\t// 打印繁忙线程级别内容\n\t\tif (contentMode == ContentMode.all || contentMode == ContentMode.thread) {\n\t\t\ttry {\n\t\t\t\tif (threadInfoMode.isCpuMode) {\n\t\t\t\t\tprintTopCpuThreads(threadInfoMode, format != OutputFormat.text);\n\t\t\t\t} else {\n\t\t\t\t\tprintTopMemoryThreads(threadInfoMode, format != OutputFormat.text);\n\t\t\t\t}\n\t\t\t} catch (Exception e) {\n\t\t\t\tSystem.out.println(\"\");\n\t\t\t\te.printStackTrace();\n\t\t\t\tSystem.out.println(Formats.red(\"ERROR: Exception happen when fetch thread information via JMX\"));\n\t\t\t}\n\t\t}\n\n\t\t// 打印vjtop自身消耗\n\t\tif (isDebugCost) {\n\t\t\tprintIterationCost(iterationStartTime, iterationStartCpu);\n\t\t}\n\n\t\tif (displayCommandHints) {\n\t\t\tSystem.out.print(\" Input command (h for help):\");\n\t\t}\n\t}\n\n\tprivate boolean checkState() {\n\t\tif (vmInfo.state != VMInfo.VMInfoState.ATTACHED && vmInfo.state != VMInfo.VMInfoState.ATTACHED_UPDATE_ERROR) {\n\t\t\tSystem.out.println(\"\\n\" + Formats.red(\"ERROR: Could not attach to process, exit now.\"));\n\t\t\tshoulExit();\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tprivate void printJvmInfoAsConsole() {\n\t\tSystem.out.printf(\" %8tT - PID: %s JVM: %s USER: %s UPTIME: %s%n\", new Date(), vmInfo.pid, vmInfo.jvmVersion,\n\t\t\t\tvmInfo.osUser, Formats.toTimeUnit(vmInfo.upTimeMills.current));\n\n\t\tString[] cpuLoadAnsi = Formats.colorAnsi(vmInfo.cpuLoad, warning.cpu);\n\n\t\tSystem.out.printf(\" PROCESS: %.2f%% cpu(%s%.2f%%%s of %d core)\", vmInfo.singleCoreCpuLoad, cpuLoadAnsi[0],\n\t\t\t\tvmInfo.cpuLoad, cpuLoadAnsi[1], vmInfo.processors);\n\n\t\tif (vmInfo.isLinux) {\n\t\t\tSystem.out.printf(\", %s thread%n\", Formats.toColor(vmInfo.osThreads, warning.thread));\n\n\t\t\tSystem.out.printf(\" MEMORY: %s rss, %s peak, %s swap |\", Formats.toMB(vmInfo.rss),\n\t\t\t\t\tFormats.toMB(vmInfo.peakRss), Formats.toMBWithColor(vmInfo.swap, warning.swap));\n\n\t\t\tif (vmInfo.ioDataSupport) {\n\t\t\t\tSystem.out.printf(\" DISK: %sB read, %sB write\",\n\t\t\t\t\t\tFormats.toSizeUnitWithColor(vmInfo.readBytes.ratePerSecond, warning.io),\n\t\t\t\t\t\tFormats.toSizeUnitWithColor(vmInfo.writeBytes.ratePerSecond, warning.io));\n\t\t\t}\n\t\t}\n\t\tSystem.out.println();\n\n\t\tSystem.out.printf(\" THREAD: %s live, %d daemon, %s peak, %s new\",\n\t\t\t\tFormats.toColor(vmInfo.threadActive, warning.thread), vmInfo.threadDaemon, vmInfo.threadPeak,\n\t\t\t\tFormats.toColor(vmInfo.threadNew.delta, warning.newThread));\n\n\t\tSystem.out.printf(\" | CLASS: %s loaded, %d unloaded, %s new%n\",\n\t\t\t\tFormats.toColor(vmInfo.classLoaded.current, warning.loadClass), vmInfo.classUnLoaded,\n\t\t\t\tFormats.toColor(vmInfo.classLoaded.delta, warning.newClass));\n\n\t\tif (vmInfo.ygcStrategy.equals(\"ZGC\")) {\n\t\t\tSystem.out.printf(\" HEAP: %s%n\", Formats.formatUsage(vmInfo.eden));\n\t\t} else {\n\t\t\tSystem.out.printf(\" HEAP: %s eden, %s sur, %s old%n\", Formats.formatUsage(vmInfo.eden),\n\t\t\t\t\tFormats.formatUsage(vmInfo.sur), Formats.formatUsageWithColor(vmInfo.old, warning.old));\n\t\t}\n\n\t\tSystem.out.printf(\" NON-HEAP: %s %s, %s codeCache\", Formats.formatUsageWithColor(vmInfo.perm, warning.perm),\n\t\t\t\tvmInfo.permGenName, Formats.formatUsageWithColor(vmInfo.codeCache, warning.codeCache));\n\t\tif (vmInfo.jvmMajorVersion >= 8 && !vmInfo.ygcStrategy.equals(\"ZGC\")) {\n\t\t\tSystem.out.printf(\", %s ccs\", Formats.formatUsage(vmInfo.ccs));\n\t\t}\n\t\tSystem.out.println(\"\");\n\n\t\tSystem.out.printf(\" OFF-HEAP: %s/%s direct(max=%s), %s/%s map(count=%d), %s threadStack%n\",\n\t\t\t\tFormats.toMB(vmInfo.direct.used), Formats.toMB(vmInfo.direct.committed),\n\t\t\t\tFormats.toMB(vmInfo.direct.max), Formats.toMB(vmInfo.map.used), Formats.toMB(vmInfo.map.committed),\n\t\t\t\tvmInfo.map.max, Formats.toMB(vmInfo.threadStackSize * vmInfo.threadActive));\n\n\t\t// gc strategy\n\t\tSystem.out.printf(\" GC-STRATEGY: %s / %s%n\", vmInfo.ygcStrategy, vmInfo.fullgcStrategy);\n\t\t// gc count\n\t\tlong ygcCount = vmInfo.ygcCount.delta;\n\t\tlong ygcTime = vmInfo.ygcTimeMills.delta;\n\t\tlong avgYgcTime = ygcCount == 0 ? 0 : ygcTime / ygcCount;\n\t\tlong fgcCount = vmInfo.fullgcCount.delta;\n\t\tif (vmInfo.ygcStrategy.equals(\"ZGC\")) {\n\t\t\tSystem.out.printf(\" GC: %s/%sms/%sms zgc\", Formats.toColor(ygcCount, warning.ygcCount),\n\t\t\t\t\tFormats.toColor(ygcTime, warning.ygcTime), Formats.toColor(avgYgcTime, warning.ygcAvgTime));\n\t\t} else {\n\t\t\tSystem.out.printf(\" GC: %s/%sms/%sms ygc, %s/%dms fgc\", Formats.toColor(ygcCount, warning.ygcCount),\n\t\t\t\t\tFormats.toColor(ygcTime, warning.ygcTime), Formats.toColor(avgYgcTime, warning.ygcAvgTime),\n\t\t\t\t\tFormats.toColor(fgcCount, warning.fullgcCount), vmInfo.fullgcTimeMills.delta);\n\t\t}\n\n\t\tif (vmInfo.perfDataSupport) {\n\t\t\tSystem.out.printf(\" | SAFE-POINT: %s count, %sms time, %dms syncTime\",\n\t\t\t\t\tFormats.toColor(vmInfo.safepointCount.delta, warning.safepointCount),\n\t\t\t\t\tFormats.toColor(vmInfo.safepointTimeMills.delta, warning.safepointTime),\n\t\t\t\t\tvmInfo.safepointSyncTimeMills.delta);\n\t\t}\n\t\tSystem.out.println(\"\");\n\t}\n\n\tprivate void printJvmInfoAsText() {\n\t\tSystem.out.printf(\"time:%8tT%npid:%s%njvm:%s%nuser:%s%nuptime:%s%n\", new Date(), vmInfo.pid, vmInfo.jvmVersion,\n\t\t\t\tvmInfo.osUser, vmInfo.upTimeMills.current);\n\n\t\tSystem.out.printf(\"process.cpu.core:%.2f%nprocess.cpu.server:%.2f%nserver.core:%d%n\", vmInfo.singleCoreCpuLoad,\n\t\t\t\tvmInfo.cpuLoad, vmInfo.processors);\n\t\tif (vmInfo.isLinux) {\n\t\t\tSystem.out.printf(\"process.thread:%d%nrss:%d%nrss.peak:%d%nswap:%d%n\", vmInfo.osThreads, vmInfo.rss,\n\t\t\t\t\tvmInfo.peakRss, vmInfo.swap);\n\n\t\t\tif (vmInfo.ioDataSupport) {\n\t\t\t\tSystem.out.printf(\"disk.read:%d%ndisk.write:%d%n\", vmInfo.readBytes.ratePerSecond,\n\t\t\t\t\t\tvmInfo.writeBytes.ratePerSecond, warning.io);\n\t\t\t}\n\t\t}\n\n\t\tSystem.out.printf(\"thread.live:%d%nthread.daemon:%d%nthread.peak:%d%nthread.new:%d%n\", vmInfo.threadActive,\n\t\t\t\tvmInfo.threadDaemon, vmInfo.threadPeak, vmInfo.threadNew.delta);\n\n\t\tSystem.out.printf(\"class.loaded:%d%nclass.unloaded:%d%nclass.new:%d%n\", vmInfo.classLoaded.current,\n\t\t\t\tvmInfo.classUnLoaded, vmInfo.classLoaded.delta);\n\n\t\tSystem.out.printf(\n\t\t\t\t\"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\",\n\t\t\t\tvmInfo.eden.used, vmInfo.eden.committed, vmInfo.eden.max, vmInfo.sur.used, vmInfo.sur.committed,\n\t\t\t\tvmInfo.sur.max, vmInfo.old.used, vmInfo.old.committed, vmInfo.old.max);\n\n\t\tSystem.out.printf(\n\t\t\t\t\"%s.use:%d%n%s.commit:%d%n%s.max:%d%ncodeCache.use:%d%ncodeCache.commit:%d%ncodeCache.max:%d%n\",\n\t\t\t\tvmInfo.permGenName, vmInfo.perm.used, vmInfo.permGenName, vmInfo.perm.committed, vmInfo.permGenName,\n\t\t\t\tvmInfo.perm.max, vmInfo.codeCache.used, vmInfo.codeCache.committed, vmInfo.codeCache.max);\n\t\tif (vmInfo.jvmMajorVersion >= 8) {\n\t\t\tSystem.out.printf(\"ccs.use:%d%nccs.commit:%d%nccs.max:%d%n\", vmInfo.ccs.used, vmInfo.ccs.committed,\n\t\t\t\t\tvmInfo.ccs.max);\n\t\t}\n\n\t\tSystem.out.printf(\n\t\t\t\t\"direct.use:%d%ndirect.commit:%d%ndirect.max:%d%nmap.use:%d%nmap.commit:%d%nmap.count:%d%nthreadStack:%d%n\",\n\t\t\t\tvmInfo.direct.used, vmInfo.direct.committed, vmInfo.direct.max, vmInfo.map.used, vmInfo.map.committed,\n\t\t\t\tvmInfo.map.max, vmInfo.threadStackSize * vmInfo.threadActive);\n\n\t\tlong ygcCount = vmInfo.ygcCount.delta;\n\t\tlong ygcTime = vmInfo.ygcTimeMills.delta;\n\t\tlong avgYgcTime = ygcCount == 0 ? 0 : ygcTime / ygcCount;\n\t\tlong fgcCount = vmInfo.fullgcCount.delta;\n\t\tSystem.out.printf(\"ygc.count:%d%nygc.time:%d%nygc.avgtime::%d%nfgc.count:%d%nfgc.time:%d%n\", ygcCount, ygcTime,\n\t\t\t\tavgYgcTime, fgcCount, vmInfo.fullgcTimeMills.delta);\n\n\t\tif (vmInfo.perfDataSupport) {\n\t\t\tSystem.out.printf(\"safePoint.count:%d%nsafePoint.time:%d%nsafePoint.syncTime:%d%n\",\n\t\t\t\t\tvmInfo.safepointCount.delta, vmInfo.safepointTimeMills.delta, vmInfo.safepointSyncTimeMills.delta);\n\t\t}\n\t}\n\n\tprivate void printTopCpuThreads(ThreadInfoMode mode, boolean console) throws IOException {\n\t\tif (!vmInfo.threadCpuTimeSupported) {\n\t\t\tif (console) {\n\t\t\t\tSystem.out.printf(\"%n -Thread CPU telemetries are not available on the monitored jvm/platform-%n\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tTopCpuResult result = topThreadInfo.topCpuThreads(mode, threadLimit);\n\n\t\t// 第一次无数据时跳过\n\t\tif (!result.ready) {\n\t\t\tif (console) {\n\t\t\t\tprintWelcome();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// 打印线程view的页头\n\t\tString titleFormat = \"%n %6s %-\" + getThreadNameWidth() + \"s %10s %6s %6s %6s %6s%n\";\n\t\tString dataFormat = \" %6d %-\" + getThreadNameWidth() + \"s %10s %5.2f%% %5.2f%% %5.2f%% %5.2f%%%n\";\n\t\tString dataFormatAsText = \"thread-%d:%s %s %.2f %.2f %.2f %.2f%n\";\n\t\tif (console) {\n\t\t\tSystem.out.printf(titleFormat, \"TID\", \"NAME  \", \"STATE\", \"CPU\", \"SYSCPU\", \" TOTAL\", \"TOLSYS\");\n\n\t\t\tif (result.activeThreads == 0) {\n\t\t\t\tSystem.out.printf(\"%n -Every thread use cpu lower than 0.05%%-%n\");\n\t\t\t}\n\t\t}\n\t\t// 打印线程Detail\n\t\tfor (ThreadInfo info : result.threadInfos) {\n\t\t\tif (info == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tLong tid = info.getThreadId();\n\t\t\tString threadName = Formats.shortName(info.getThreadName(), getThreadNameWidth(), 20);\n\t\t\t// 过滤threadName\n\t\t\tif (threadNameFilter != null && !threadName.toLowerCase().contains(threadNameFilter)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// 刷新间隔里，所使用的单核CPU比例\n\t\t\tdouble cpu = Utils.calcLoad(result.threadCpuDeltaTimes.get(tid), vmInfo.upTimeMills.delta,\n\t\t\t\t\tUtils.NANOS_TO_MILLS);\n\n\t\t\tdouble syscpu = Utils.calcLoad(result.threadSysCpuDeltaTimes.get(tid), vmInfo.upTimeMills.delta,\n\t\t\t\t\tUtils.NANOS_TO_MILLS);\n\n\t\t\t// 在进程所有消耗的CPU里，本线程的比例\n\t\t\tdouble totalcpuPercent = Utils.calcLoad(result.threadCpuTotalTimes.get(tid), vmInfo.cpuTimeNanos.current,\n\t\t\t\t\t1);\n\n\t\t\tdouble totalsysPercent = Utils.calcLoad(result.threadSysCpuTotalTimes.get(tid), vmInfo.cpuTimeNanos.current,\n\t\t\t\t\t1);\n\n\t\t\tif (console) {\n\n\t\t\t\tSystem.out.printf(dataFormat, tid, threadName, Formats.leftStr(info.getThreadState().toString(), 10),\n\t\t\t\t\t\tcpu, syscpu, totalcpuPercent, totalsysPercent);\n\t\t\t} else {\n\t\t\t\tSystem.out.printf(dataFormatAsText, tid, threadName, info.getThreadState().toString(), cpu, syscpu,\n\t\t\t\t\t\ttotalcpuPercent, totalsysPercent);\n\t\t\t}\n\n\t\t}\n\n\t\t// 打印线程汇总\n\t\tdouble deltaAllActiveThreadCpuLoad = Utils.calcLoad(result.deltaAllActiveThreadCpu / Utils.NANOS_TO_MILLS,\n\t\t\t\tvmInfo.upTimeMills.delta);\n\t\tdouble deltaAllActiveThreadSysCpuLoad = Utils.calcLoad(result.deltaAllActiveThreadSysCpu / Utils.NANOS_TO_MILLS,\n\t\t\t\tvmInfo.upTimeMills.delta);\n\t\tdouble deltaAllFreeThreadCpuLoad = Utils.calcLoad(result.deltaAllFreeThreadCpu / Utils.NANOS_TO_MILLS,\n\t\t\t\tvmInfo.upTimeMills.delta);\n\t\t// double deltaAllFreeThreadSysCpuLoad = Utils.calcLoad(result.deltaAllFreeThreadSysCpu / Utils.NANOS_TO_MILLS,\n\t\t// vmInfo.upTimeMills.delta);\n\n\t\tif (console) {\n\t\t\tSystem.out.printf(\n\t\t\t\t\t\"%n Total  : %.2f%% cpu(user=%.2f%%, sys=%.2f%%) by %d active java threads, %.2f%% by others%n\",\n\t\t\t\t\tdeltaAllActiveThreadCpuLoad, deltaAllActiveThreadCpuLoad - deltaAllActiveThreadSysCpuLoad,\n\t\t\t\t\tdeltaAllActiveThreadSysCpuLoad, result.activeThreads, deltaAllFreeThreadCpuLoad);\n\n\t\t\tSystem.out.printf(\" Setting: top %d threads order by %s%s, flush every %ds%n\", threadLimit,\n\t\t\t\t\tmode.toString().toUpperCase(), threadNameFilter == null ? \"\" : \" filter by \" + threadNameFilter,\n\t\t\t\t\tinterval);\n\t\t} else {\n\t\t\tSystem.out.printf(\n\t\t\t\t\t\"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\",\n\t\t\t\t\tresult.activeThreads, deltaAllActiveThreadCpuLoad,\n\t\t\t\t\tdeltaAllActiveThreadCpuLoad - deltaAllActiveThreadSysCpuLoad, deltaAllActiveThreadSysCpuLoad,\n\t\t\t\t\tdeltaAllFreeThreadCpuLoad);\n\t\t}\n\t}\n\n\tprivate void printTopMemoryThreads(ThreadInfoMode mode, boolean console) throws IOException {\n\t\tif (!vmInfo.threadMemoryAllocatedSupported) {\n\t\t\tif (console) {\n\t\t\t\tSystem.out.printf(\n\t\t\t\t\t\t\"%n -Thread Memory Allocated telemetries are not available on the monitored jvm/platform-%n\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tTopMemoryResult result = topThreadInfo.topMemoryThreads(mode, threadLimit);\n\n\t\t// 第一次无数据跳过\n\t\tif (!result.ready) {\n\t\t\tif (console) {\n\t\t\t\tprintWelcome();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// 打印线程View的页头\n\t\tString titleFormat = \"%n %6s %-\" + getThreadNameWidth() + \"s %10s %14s %18s%n\";\n\t\tString dataFormat = \" %6d %-\" + getThreadNameWidth() + \"s %10s %5s/s(%5.2f%%) %10s(%5.2f%%)%n\";\n\t\tString dataFormatAsText = \"thread-%d:%s %s %s %.2f %s %.2f%n\";\n\t\tif (console) {\n\t\t\tSystem.out.printf(titleFormat, \"TID\", \"NAME  \", \"STATE\", \"MEMORY\", \"TOTAL-ALLOCATED\");\n\n\t\t\tif (result.activeThreads == 0) {\n\t\t\t\tSystem.out.printf(\"%n -Every thread allocate memory slower than 1k/s-%n\");\n\t\t\t}\n\t\t}\n\n\t\t// 打印线程Detail\n\t\tfor (ThreadInfo info : result.threadInfos) {\n\t\t\tif (info == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tLong tid = info.getThreadId();\n\t\t\tString threadName = Formats.shortName(info.getThreadName(), getThreadNameWidth(), 12);\n\n\t\t\t// 过滤threadName\n\t\t\tif (threadNameFilter != null && !threadName.toLowerCase().contains(threadNameFilter)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tLong threadDelta = result.threadMemoryDeltaBytesMap.get(tid);\n\t\t\tlong allocationRate = threadDelta == null ? 0 : (threadDelta * 1000) / vmInfo.upTimeMills.delta;\n\t\t\tif (console) {\n\t\t\t\tSystem.out.printf(dataFormat, tid, threadName, Formats.leftStr(info.getThreadState().toString(), 10),\n\t\t\t\t\t\tFormats.toFixLengthSizeUnit(allocationRate),\n\t\t\t\t\t\tUtils.calcMemoryUtilization(result.threadMemoryDeltaBytesMap.get(tid),\n\t\t\t\t\t\t\t\tresult.deltaAllThreadBytes),\n\t\t\t\t\t\tFormats.toFixLengthSizeUnit(result.threadMemoryTotalBytesMap.get(tid)),\n\t\t\t\t\t\tUtils.calcMemoryUtilization(result.threadMemoryTotalBytesMap.get(tid),\n\t\t\t\t\t\t\t\tresult.totalAllThreadBytes));\n\t\t\t} else {\n\t\t\t\tSystem.out.printf(dataFormatAsText, tid, threadName, info.getThreadState().toString(), allocationRate,\n\t\t\t\t\t\tUtils.calcMemoryUtilization(result.threadMemoryDeltaBytesMap.get(tid),\n\t\t\t\t\t\t\t\tresult.deltaAllThreadBytes),\n\t\t\t\t\t\tresult.threadMemoryTotalBytesMap.get(tid), Utils.calcMemoryUtilization(\n\t\t\t\t\t\t\t\tresult.threadMemoryTotalBytesMap.get(tid), result.totalAllThreadBytes));\n\t\t\t}\n\t\t}\n\n\t\tif (console) {\n\t\t\t// 打印线程汇总信息，这里因为最后单位是精确到秒，所以bytes除以毫秒以后要乘以1000才是按秒统计\n\t\t\tSystem.out.printf(\"%n Total  : %5s/s memory allocated by %d active threads%n\",\n\t\t\t\t\tFormats.toFixLengthSizeUnit((result.deltaAllThreadBytes * 1000) / vmInfo.upTimeMills.delta),\n\t\t\t\t\tresult.activeThreads);\n\n\t\t\tSystem.out.printf(\" Setting: top %d threads order by %s%s, flush every %ds%n\", threadLimit,\n\t\t\t\t\tmode.toString().toUpperCase(), threadNameFilter == null ? \"\" : \" filter by \" + threadNameFilter,\n\t\t\t\t\tinterval);\n\t\t} else {\n\t\t\tSystem.out.printf(\"sum.active.threadCount:%d%nsum.active.allocateRate:%d%n\", result.activeThreads,\n\t\t\t\t\t(result.deltaAllThreadBytes * 1000) / vmInfo.upTimeMills.delta);\n\t\t}\n\t}\n\n\tprivate void printWelcome() {\n\t\tif (firstTime && contentMode != ContentMode.thread) {\n\t\t\tif (!vmInfo.isLinux) {\n\t\t\t\tSystem.out.printf(\n\t\t\t\t\t\t\"%n\" + Formats.yellow(\" OS isn't linux, Process's MEMORY, THREAD, DISK data will be skipped.\")\n\t\t\t\t\t\t\t\t+ \"%n\");\n\t\t\t}\n\n\t\t\tif (!vmInfo.ioDataSupport) {\n\t\t\t\tSystem.out.printf(\"%n\"\n\t\t\t\t\t\t+ Formats.yellow(\" /proc/%s/io is not readable, Process's DISK data will be skipped.\") + \"%n\",\n\t\t\t\t\t\tvmInfo.pid);\n\t\t\t}\n\n\t\t\tif (!vmInfo.perfDataSupport) {\n\t\t\t\tSystem.out.printf(\n\t\t\t\t\t\t\"%n\" + Formats.yellow(\" Perfdata doesn't support, SAFE-POINT data will be skipped.\") + \"%n\");\n\t\t\t}\n\n\t\t\tSystem.out.printf(\"%n VMARGS: %s%n%n\", vmInfo.vmArgs);\n\n\t\t\tfirstTime = false;\n\n\t\t}\n\t\tSystem.out.printf(\"%n Collecting data, please wait ......%n%n\");\n\t}\n\n\tprivate void printJmxError() {\n\t\tif (!vmInfo.currentGcCause.equals(\"No GC\")) {\n\t\t\tSystem.out.println(\"\\n\" + Formats.red(\n\t\t\t\t\t\"ERROR: Could not fetch data via JMX - Process is doing GC, cause is \" + vmInfo.currentGcCause));\n\t\t} else {\n\t\t\tSystem.out.println(\n\t\t\t\t\tSystem.lineSeparator() + Formats.red(\"ERROR: Could not fetch data via JMX - Process terminated?\"));\n\t\t}\n\t}\n\n\tprivate void printIterationCost(long iterationStartTime, long iterationStartCpu) {\n\t\tlong currentCpu = ((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getProcessCpuTime();\n\t\tlong deltaIterationTime = System.currentTimeMillis() - iterationStartTime;\n\n\t\tlong deltaIterationCpuTime = (currentCpu - iterationStartCpu) / Utils.NANOS_TO_MILLS;\n\t\tlong deltaOtherCpuTime = (iterationStartCpu - lastCpu) / Utils.NANOS_TO_MILLS;\n\t\tlong deltaTotalCpuTime = deltaIterationCpuTime + deltaOtherCpuTime;\n\t\tlastCpu = currentCpu;\n\n\t\tSystem.out.printf(\" Cost %.2f%% cpu in %dms, other is %dms, total is %dms%n\",\n\t\t\t\tdeltaIterationCpuTime * 100d / deltaIterationTime, deltaIterationTime, deltaOtherCpuTime,\n\t\t\t\tdeltaTotalCpuTime);\n\t}\n\n\n\tpublic void switchCpuAndMemory() {\n\t\ttopThreadInfo.cleanupThreadsHistory();\n\t}\n\n\n\tpublic boolean shouldExit() {\n\t\treturn shouldExit;\n\t}\n\n\t/**\n\t * Requests the disposal of this view - it should be called again.\n\t */\n\tpublic void shoulExit() {\n\t\tshouldExit = true;\n\t}\n\n\tprivate void setWidth(Integer width) {\n\t\tif (width == null) {\n\t\t\tthis.width = DEFAULT_WIDTH;\n\t\t} else if (width < MIN_WIDTH) {\n\t\t\tthis.width = MIN_WIDTH;\n\t\t} else {\n\t\t\tthis.width = width;\n\t\t}\n\t}\n\n\tprivate int getThreadNameWidth() {\n\t\treturn this.width - 48;\n\t}\n\n\tpublic enum ThreadInfoMode {\n\t\tcpu(true), totalcpu(true), syscpu(true), totalsyscpu(true), memory(false), totalmemory(false);\n\n\t\tpublic boolean isCpuMode;\n\n\t\tprivate ThreadInfoMode(boolean isCpuMode) {\n\t\t\tthis.isCpuMode = isCpuMode;\n\t\t}\n\n\t\tpublic static ThreadInfoMode parse(String value) {\n\t\t\ttry {\n\t\t\t\treturn ThreadInfoMode.valueOf(value);\n\t\t\t} catch (IllegalArgumentException e) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"wrong option of thread info mode(cpu,syscpu,totalcpu,totalsyscpu,memory,totalmemory)\");\n\t\t\t}\n\t\t}\n\n\t\tpublic static ThreadInfoMode parseInt(String mode) {\n\t\t\tswitch (mode) {\n\t\t\t\tcase \"1\":\n\t\t\t\t\treturn cpu;\n\t\t\t\tcase \"2\":\n\t\t\t\t\treturn syscpu;\n\t\t\t\tcase \"3\":\n\t\t\t\t\treturn totalcpu;\n\t\t\t\tcase \"4\":\n\t\t\t\t\treturn totalsyscpu;\n\t\t\t\tcase \"5\":\n\t\t\t\t\treturn memory;\n\t\t\t\tcase \"6\":\n\t\t\t\t\treturn totalmemory;\n\t\t\t\tdefault:\n\t\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic enum OutputFormat {\n\t\tconsole(true), cleanConsole(false), text(false);\n\n\t\tOutputFormat(boolean ansi) {\n\t\t\tthis.ansi = ansi;\n\t\t}\n\n\t\tpublic boolean ansi;\n\t}\n\n\tpublic enum ContentMode {\n\t\tall, jvm, thread\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/VMInfo.java",
    "content": "package com.vip.vjtools.vjtop;\n\nimport java.io.IOException;\nimport java.lang.management.MemoryPoolMXBean;\nimport java.lang.management.MemoryUsage;\nimport java.lang.management.ThreadInfo;\nimport java.util.Locale;\nimport java.util.Map;\n\nimport com.sun.management.GarbageCollectorMXBean;\nimport com.vip.vjtools.vjtop.data.PerfData;\nimport com.vip.vjtools.vjtop.data.ProcFileData;\nimport com.vip.vjtools.vjtop.data.jmx.JmxClient;\nimport com.vip.vjtools.vjtop.data.jmx.JmxGarbageCollectorManager;\nimport com.vip.vjtools.vjtop.data.jmx.JmxMemoryPoolManager;\nimport com.vip.vjtools.vjtop.util.Formats;\nimport com.vip.vjtools.vjtop.util.Utils;\n\nimport sun.management.counter.Counter;\nimport sun.management.counter.LongCounter;\nimport sun.management.counter.StringCounter;\n\n@SuppressWarnings(\"restriction\")\npublic class VMInfo {\n\tprivate JmxClient jmxClient = null;\n\n\tprivate PerfData perfData = null;\n\tpublic boolean perfDataSupport = false;\n\n\tpublic VMInfoState state = VMInfoState.INIT;\n\tpublic String pid;\n\tprivate int jmxUpdateErrorCount;\n\n\t// 静态数据//\n\tprivate long startTime = 0;\n\tpublic String osUser;\n\tpublic String vmArgs = \"\";\n\tpublic String jvmVersion = \"\";\n\tpublic int jvmMajorVersion;\n\n\tpublic String permGenName;\n\tpublic long threadStackSize;\n\tpublic long maxDirectMemorySize;\n\n\tpublic int processors;\n\tpublic boolean isLinux;\n\tpublic boolean ioDataSupport = true;// 不是同一个用户，不能读/proc/PID/io\n\tpublic boolean processDataSupport = true;\n\tpublic boolean threadCpuTimeSupported;\n\tpublic boolean threadMemoryAllocatedSupported;\n\tpublic boolean threadContentionMonitoringSupported;\n\n\tpublic WarningRule warningRule = new WarningRule();\n\n\t// 动态数据//\n\tpublic Rate upTimeMills = new Rate();\n\tpublic Rate cpuTimeNanos = new Rate();\n\n\tpublic long rss;\n\tpublic long peakRss;\n\tpublic long swap;\n\tpublic long osThreads;\n\n\tpublic Rate readBytes = new Rate();\n\tpublic Rate writeBytes = new Rate();\n\n\tpublic double cpuLoad = 0.0;\n\tpublic double singleCoreCpuLoad = 0.0;\n\n\tpublic String ygcStrategy = \"\";\n\tpublic Rate ygcCount = new Rate();\n\tpublic Rate ygcTimeMills = new Rate();\n\tpublic String fullgcStrategy = \"\";\n\tpublic Rate fullgcCount = new Rate();\n\tpublic Rate fullgcTimeMills = new Rate();\n\tpublic String currentGcCause = \"\";\n\n\tpublic long threadActive;\n\tpublic long threadDaemon;\n\tpublic long threadPeak;\n\tpublic Rate threadNew = new Rate();\n\n\tpublic Rate classLoaded = new Rate();\n\tpublic long classUnLoaded;\n\n\tpublic Rate safepointCount = new Rate();\n\tpublic Rate safepointTimeMills = new Rate();\n\tpublic Rate safepointSyncTimeMills = new Rate();\n\n\tpublic Usage eden;\n\tpublic Usage sur;\n\tpublic Usage old;\n\n\tpublic Usage perm;\n\tpublic Usage codeCache;\n\tpublic Usage ccs;\n\n\tpublic Usage direct;\n\tpublic Usage map;\n\n\tprivate LongCounter threadLiveCounter;\n\tprivate LongCounter threadDaemonCounter;\n\tprivate LongCounter threadPeakCounter;\n\tprivate LongCounter threadStartedCounter;\n\tprivate LongCounter classUnloadCounter;\n\tprivate LongCounter classLoadedCounter;\n\tprivate LongCounter ygcCountCounter;\n\tprivate LongCounter ygcTimeCounter;\n\tprivate LongCounter fullGcCountCounter;\n\tprivate LongCounter fullgcTimeCounter;\n\tprivate LongCounter safepointCountCounter;\n\tprivate LongCounter safepointTimeCounter;\n\tprivate LongCounter safepointSyncTimeCounter;\n\tprivate StringCounter currentGcCauseCounter;\n\n\tpublic VMInfo(JmxClient jmxClient, String vmId) throws Exception {\n\t\tthis.jmxClient = jmxClient;\n\t\tthis.state = VMInfoState.ATTACHED;\n\t\tthis.pid = vmId;\n\n\t\tinit();\n\t}\n\n\tprivate VMInfo() {\n\t}\n\n\t/**\n\t * 创建JMX连接并构造VMInfo实例\n\t */\n\tpublic static VMInfo processNewVM(String pid, String jmxHostAndPort) {\n\t\ttry {\n\t\t\tfinal JmxClient jmxClient = new JmxClient();\n\t\t\tjmxClient.connect(pid, jmxHostAndPort);\n\n\t\t\t// 注册JMXClient注销的钩子\n\t\t\tRuntime.getRuntime().addShutdownHook(new Thread(new Runnable() {\n\t\t\t\t@Override\n\t\t\t\tpublic void run() {\n\t\t\t\t\tjmxClient.disconnect();\n\t\t\t\t}\n\t\t\t}));\n\n\t\t\treturn new VMInfo(jmxClient, pid);\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace(System.out);\n\t\t}\n\n\t\treturn createDeadVM(pid, VMInfoState.ERROR_DURING_ATTACH);\n\t}\n\n\t/**\n\t * Creates a dead VMInfo, representing a jvm in a given state which cannot\n\t * be attached or other monitoring issues occurred.\n\t */\n\tpublic static VMInfo createDeadVM(String pid, VMInfoState state) {\n\t\tVMInfo vmInfo = new VMInfo();\n\t\tvmInfo.state = state;\n\t\tvmInfo.pid = pid;\n\t\treturn vmInfo;\n\t}\n\n\t/**\n\t * 初始化静态数据\n\t */\n\tprivate void init() throws IOException {\n\t\tMap<String, Counter> perfCounters = null;\n\t\ttry {\n\t\t\tperfData = PerfData.connect(Integer.parseInt(pid));\n\t\t\tperfCounters = perfData.getAllCounters();\n\t\t\tinitPerfCounters(perfCounters);\n\t\t\tperfDataSupport = true;\n\t\t} catch (Throwable ignored) {\n\t\t}\n\n\t\tif (perfDataSupport) {\n\t\t\tvmArgs = (String) perfCounters.get(\"java.rt.vmArgs\").getValue();\n\t\t} else {\n\t\t\tvmArgs = Formats.join(jmxClient.getRuntimeMXBean().getInputArguments(), \" \");\n\t\t}\n\n\t\tstartTime = jmxClient.getRuntimeMXBean().getStartTime();\n\n\t\tMap<String, String> taregetVMSystemProperties = jmxClient.getRuntimeMXBean().getSystemProperties();\n\t\tosUser = taregetVMSystemProperties.get(\"user.name\");\n\t\tjvmVersion = taregetVMSystemProperties.get(\"java.version\");\n\t\tjvmMajorVersion = Utils.getJavaMajorVersion(taregetVMSystemProperties.get(\"java.specification.version\"));\n\t\tpermGenName = jvmMajorVersion >= 8 ? \"metaspace\" : \"perm\";\n\t\tthreadStackSize = 1024\n\t\t\t\t* Long.parseLong(jmxClient.getHotSpotDiagnosticMXBean().getVMOption(\"ThreadStackSize\").getValue());\n\t\tmaxDirectMemorySize = Long\n\t\t\t\t.parseLong(jmxClient.getHotSpotDiagnosticMXBean().getVMOption(\"MaxDirectMemorySize\").getValue());\n\t\tmaxDirectMemorySize = maxDirectMemorySize == 0 ? -1 : maxDirectMemorySize;\n\n\t\tprocessors = jmxClient.getOperatingSystemMXBean().getAvailableProcessors();\n\t\twarningRule.updateProcessor(processors);\n\n\t\tisLinux = System.getProperty(\"os.name\").toLowerCase(Locale.US).contains(\"linux\");\n\t}\n\n\tpublic void initThreadInfoAbility() throws IOException {\n\t\tthreadCpuTimeSupported = jmxClient.getThreadMXBean().isThreadCpuTimeSupported();\n\t\tthreadMemoryAllocatedSupported = jmxClient.getThreadMXBean().isThreadAllocatedMemorySupported();\n\t\tthreadContentionMonitoringSupported = jmxClient.getThreadMXBean().isThreadContentionMonitoringEnabled();\n\t}\n\n\t/**\n\t * Updates all jvm metrics to the most recent remote values\n\t */\n\tpublic void update(boolean needJvmInfo) {\n\t\tif (state == VMInfoState.ERROR_DURING_ATTACH || state == VMInfoState.DETACHED) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tint lastJmxErrorCount = jmxUpdateErrorCount;\n\t\t\t// 将UPDTATE_ERROR重置开始新一轮循环\n\t\t\tstate = VMInfoState.ATTACHED;\n\n\t\t\t// 清空JMX内部缓存\n\t\t\tjmxClient.flush();\n\n\t\t\tupdateUpTime();\n\n\t\t\tif (needJvmInfo) {\n\t\t\t\tif (isLinux) {\n\t\t\t\t\tupdateProcessStatus();\n\t\t\t\t\tupdateIO();\n\t\t\t\t}\n\n\t\t\t\tupdateCpu();\n\t\t\t\tupdateThreads();\n\t\t\t\tupdateClassLoader();\n\t\t\t\tupdateMemoryPool();\n\t\t\t\tupdateGC();\n\t\t\t\tupdateSafepoint();\n\t\t\t}\n\n\t\t\t// 无新异常，状态重新判定为正常\n\t\t\tif (jmxUpdateErrorCount == lastJmxErrorCount) {\n\t\t\t\tjmxUpdateErrorCount = 0;\n\t\t\t}\n\t\t} catch (Throwable e) {\n\t\t\t// 其他非JMX异常，直接退出\n\t\t\te.printStackTrace();\n\t\t\tSystem.out.flush();\n\t\t\tstate = VMInfoState.DETACHED;\n\t\t}\n\t}\n\n\tprivate void updateUpTime() {\n\t\tupTimeMills.update(System.currentTimeMillis() - startTime);\n\t\twarningRule.updateInterval(Math.max(1, upTimeMills.delta / 1000));\n\t}\n\n\tprivate void updateProcessStatus() {\n\t\tif (!processDataSupport) {\n\t\t\treturn;\n\t\t}\n\n\t\tMap<String, String> procStatus = ProcFileData.getProcStatus(pid);\n\t\tif (procStatus.isEmpty()) {\n\t\t\tprocessDataSupport = false;\n\t\t\treturn;\n\t\t}\n\t\trss = Formats.parseFromSize(procStatus.get(\"VmRSS\"));\n\t\tpeakRss = Formats.parseFromSize(procStatus.get(\"VmHWM\"));\n\t\tswap = Formats.parseFromSize(procStatus.get(\"VmSwap\"));\n\t\tosThreads = Long.parseLong(procStatus.get(\"Threads\"));\n\t}\n\n\tprivate void updateIO() {\n\t\tif (!ioDataSupport) {\n\t\t\treturn;\n\t\t}\n\n\t\tMap<String, String> procIo = ProcFileData.getProcIO(pid);\n\n\t\tif (procIo.isEmpty()) {\n\t\t\tioDataSupport = false;\n\t\t\treturn;\n\t\t}\n\n\t\treadBytes.update(Formats.parseFromSize(procIo.get(\"read_bytes\")));\n\t\twriteBytes.update(Formats.parseFromSize(procIo.get(\"write_bytes\")));\n\n\t\treadBytes.caculateRatePerSecond(upTimeMills.delta);\n\t\twriteBytes.caculateRatePerSecond(upTimeMills.delta);\n\t}\n\n\tprivate void updateCpu() {\n\t\tif (!isJmxStateOk()) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tcpuTimeNanos.update(jmxClient.getOperatingSystemMXBean().getProcessCpuTime());\n\t\t\tsingleCoreCpuLoad = Utils.calcLoad(cpuTimeNanos.delta / Utils.NANOS_TO_MILLS, upTimeMills.delta);\n\t\t\tcpuLoad = singleCoreCpuLoad / processors;\n\t\t} catch (Exception e) {\n\t\t\thandleJmxFetchDataError(e);\n\t\t}\n\t}\n\n\tprivate void updateThreads() {\n\t\tif (perfDataSupport) {\n\t\t\tthreadActive = threadLiveCounter.longValue();\n\t\t\tthreadDaemon = threadDaemonCounter.longValue();\n\t\t\tthreadPeak = threadPeakCounter.longValue();\n\t\t\tthreadNew.update(threadStartedCounter.longValue());\n\t\t} else if (isJmxStateOk()) {\n\t\t\ttry {\n\t\t\t\tthreadActive = jmxClient.getThreadMXBean().getThreadCount();\n\t\t\t\tthreadDaemon = jmxClient.getThreadMXBean().getDaemonThreadCount();\n\t\t\t\tthreadPeak = jmxClient.getThreadMXBean().getPeakThreadCount();\n\t\t\t\tthreadNew.update(jmxClient.getThreadMXBean().getTotalStartedThreadCount());\n\t\t\t} catch (Exception e) {\n\t\t\t\thandleJmxFetchDataError(e);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void updateClassLoader() {\n\t\t// 优先从perfData取值，注意此处loadedClasses 等于JMX的TotalLoadedClassCount\n\t\tif (perfDataSupport) {\n\t\t\tclassUnLoaded = classUnloadCounter.longValue();\n\t\t\tclassLoaded.update(classLoadedCounter.longValue() - classUnLoaded);\n\t\t} else if (isJmxStateOk()) {\n\t\t\ttry {\n\t\t\t\tclassUnLoaded = jmxClient.getClassLoadingMXBean().getUnloadedClassCount();\n\t\t\t\tclassLoaded.update(jmxClient.getClassLoadingMXBean().getLoadedClassCount());\n\t\t\t} catch (Exception e) {\n\t\t\t\thandleJmxFetchDataError(e);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void updateMemoryPool() {\n\t\tif (!isJmxStateOk()) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tJmxMemoryPoolManager memoryPoolManager = jmxClient.getMemoryPoolManager();\n\n\t\t\tMemoryPoolMXBean edenMXBean = memoryPoolManager.getEdenMemoryPool();\n\t\t\tif (edenMXBean != null) {\n\t\t\t\teden = new Usage(edenMXBean.getUsage());\n\t\t\t} else {\n\t\t\t\teden = new Usage();\n\t\t\t}\n\n\t\t\tMemoryPoolMXBean oldMXBean = memoryPoolManager.getOldMemoryPool();\n\t\t\tif (oldMXBean != null) {\n\t\t\t\told = new Usage(oldMXBean.getUsage());\n\t\t\t\twarningRule.updateOld(old.max);\n\t\t\t} else {\n\t\t\t\told = new Usage();\n\t\t\t}\n\n\t\t\tMemoryPoolMXBean survivorMemoryPool = memoryPoolManager.getSurvivorMemoryPool();\n\t\t\tif (survivorMemoryPool != null) {\n\t\t\t\tsur = new Usage(survivorMemoryPool.getUsage());\n\t\t\t} else {\n\t\t\t\tsur = new Usage();\n\t\t\t}\n\n\t\t\tMemoryPoolMXBean permMXBean = memoryPoolManager.getPermMemoryPool();\n\t\t\tif (permMXBean != null) {\n\t\t\t\tperm = new Usage(permMXBean.getUsage());\n\t\t\t\twarningRule.updatePerm(perm.max);\n\t\t\t} else {\n\t\t\t\tperm = new Usage();\n\t\t\t}\n\n\t\t\tif (jvmMajorVersion >= 8) {\n\t\t\t\tMemoryPoolMXBean compressedClassSpaceMemoryPool = memoryPoolManager.getCompressedClassSpaceMemoryPool();\n\t\t\t\tif (compressedClassSpaceMemoryPool != null) {\n\t\t\t\t\tccs = new Usage(compressedClassSpaceMemoryPool.getUsage());\n\t\t\t\t} else {\n\t\t\t\t\tccs = new Usage();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tMemoryPoolMXBean memoryPoolMXBean = memoryPoolManager.getCodeCacheMemoryPool();\n\t\t\tif (memoryPoolMXBean != null) {\n\t\t\t\tcodeCache = new Usage(memoryPoolMXBean.getUsage());\n\t\t\t} else {\n\t\t\t\tcodeCache = new Usage();\n\t\t\t}\n\n\t\t\tdirect = new Usage(jmxClient.getBufferPoolManager().getDirectBufferPoolUsed(),\n\t\t\t\t\tjmxClient.getBufferPoolManager().getDirectBufferPoolCapacity(), maxDirectMemorySize);\n\n\t\t\t// 取巧用法，将count 放入无用的max中。\n\t\t\tlong mapUsed = jmxClient.getBufferPoolManager().getMappedBufferPoolUsed();\n\t\t\tmap = new Usage(mapUsed, jmxClient.getBufferPoolManager().getMappedBufferPoolCapacity(),\n\t\t\t\t\tmapUsed == 0 ? 0 : jmxClient.getBufferPoolManager().getMappedBufferPoolCount());\n\n\t\t} catch (Exception e) {\n\t\t\thandleJmxFetchDataError(e);\n\t\t}\n\t}\n\n\tprivate void updateGC() {\n\t\tif (perfDataSupport) {\n\t\t\tygcCount.update(ygcCountCounter.longValue());\n\t\t\tygcTimeMills.update(perfData.tickToMills(ygcTimeCounter));\n\t\t\tif (fullGcCountCounter != null) {\n\t\t\t\tfullgcCount.update(fullGcCountCounter.longValue());\n\t\t\t\tfullgcTimeMills.update(perfData.tickToMills(fullgcTimeCounter));\n\t\t\t}\n\t\t} else if (isJmxStateOk()) {\n\t\t\ttry {\n\t\t\t\tJmxGarbageCollectorManager gcManager = jmxClient.getGarbageCollectorManager();\n\n\t\t\t\tGarbageCollectorMXBean ygcMXBean = gcManager.getYoungCollector();\n\t\t\t\tygcStrategy = gcManager.getYgcStrategy();\n\t\t\t\tygcCount.update(ygcMXBean.getCollectionCount());\n\t\t\t\tygcTimeMills.update(ygcMXBean.getCollectionTime());\n\n\t\t\t\tGarbageCollectorMXBean fullgcMXBean = gcManager.getFullCollector();\n\t\t\t\tif (fullgcMXBean != null) {\n\t\t\t\t\tfullgcStrategy = gcManager.getFgcStrategy();\n\t\t\t\t\tfullgcCount.update(fullgcMXBean.getCollectionCount());\n\t\t\t\t\tfullgcTimeMills.update(fullgcMXBean.getCollectionTime());\n\t\t\t\t}\n\t\t\t} catch (Exception e) {\n\t\t\t\thandleJmxFetchDataError(e);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void updateSafepoint() {\n\t\tif (!perfDataSupport) {\n\t\t\treturn;\n\t\t}\n\t\tsafepointCount.update(safepointCountCounter.longValue());\n\t\tsafepointTimeMills.update(perfData.tickToMills(safepointTimeCounter));\n\t\tsafepointSyncTimeMills.update(perfData.tickToMills(safepointSyncTimeCounter));\n\n\t\tcurrentGcCause = (String) currentGcCauseCounter.getValue();\n\t}\n\n\tpublic long[] getAllThreadIds() throws IOException {\n\t\treturn jmxClient.getThreadMXBean().getAllThreadIds();\n\t}\n\n\tpublic long[] getThreadCpuTime(long[] tids) throws IOException {\n\t\treturn jmxClient.getThreadMXBean().getThreadCpuTime(tids);\n\t}\n\n\tpublic long[] getThreadUserTime(long[] tids) throws IOException {\n\t\treturn jmxClient.getThreadMXBean().getThreadUserTime(tids);\n\t}\n\n\tpublic ThreadInfo[] getThreadInfo(long[] tids) throws IOException {\n\t\treturn jmxClient.getThreadMXBean().getThreadInfo(tids);\n\t}\n\n\tpublic ThreadInfo getThreadInfo(long tid, int maxDepth) throws IOException {\n\t\treturn jmxClient.getThreadMXBean().getThreadInfo(tid, maxDepth);\n\t}\n\n\tpublic ThreadInfo[] getThreadInfo(long[] tids, int maxDepth) throws IOException {\n\t\treturn jmxClient.getThreadMXBean().getThreadInfo(tids, maxDepth);\n\t}\n\n\tpublic ThreadInfo[] getAllThreadInfo() throws IOException {\n\t\treturn jmxClient.getThreadMXBean().dumpAllThreads(false, false);\n\t}\n\n\tpublic long[] getThreadAllocatedBytes(long[] tids) throws IOException {\n\t\treturn jmxClient.getThreadMXBean().getThreadAllocatedBytes(tids);\n\t}\n\n\tprivate void initPerfCounters(Map<String, Counter> perfCounters) {\n\t\tthreadLiveCounter = (LongCounter) perfCounters.get(\"java.threads.live\");\n\t\tthreadDaemonCounter = (LongCounter) perfCounters.get(\"java.threads.daemon\");\n\t\tthreadPeakCounter = (LongCounter) perfCounters.get(\"java.threads.livePeak\");\n\t\tthreadStartedCounter = (LongCounter) perfCounters.get(\"java.threads.started\");\n\n\t\tclassUnloadCounter = (LongCounter) perfCounters.get(\"java.cls.unloadedClasses\");\n\t\tclassLoadedCounter = (LongCounter) perfCounters.get(\"java.cls.loadedClasses\");\n\n\t\tygcCountCounter = (LongCounter) perfCounters.get(\"sun.gc.collector.0.invocations\");\n\t\tygcTimeCounter = (LongCounter) perfCounters.get(\"sun.gc.collector.0.time\");\n\t\tfullGcCountCounter = (LongCounter) perfCounters.get(\"sun.gc.collector.1.invocations\");\n\t\tfullgcTimeCounter = (LongCounter) perfCounters.get(\"sun.gc.collector.1.time\");\n\n\t\tsafepointCountCounter = (LongCounter) perfCounters.get(\"sun.rt.safepoints\");\n\t\tsafepointTimeCounter = (LongCounter) perfCounters.get(\"sun.rt.safepointTime\");\n\t\tsafepointSyncTimeCounter = (LongCounter) perfCounters.get(\"sun.rt.safepointSyncTime\");\n\t\tcurrentGcCauseCounter = (StringCounter) perfCounters.get(\"sun.gc.cause\");\n\t}\n\n\tpublic void handleJmxFetchDataError(Throwable e) {\n\t\tSystem.out.println(\"\");\n\t\te.printStackTrace();\n\t\tSystem.out.flush();\n\t\tjmxUpdateErrorCount++;\n\n\t\t// 连续三次刷新周期JMX 获取数据失败则退出\n\t\tif (jmxUpdateErrorCount > 3) {\n\t\t\tstate = VMInfoState.DETACHED;\n\t\t} else {\n\t\t\tstate = VMInfoState.ATTACHED_UPDATE_ERROR;\n\t\t}\n\t}\n\n\tpublic boolean isJmxStateOk() {\n\t\treturn state != VMInfoState.ATTACHED_UPDATE_ERROR && state != VMInfoState.DETACHED;\n\t}\n\n\n\tpublic enum VMInfoState {\n\t\tINIT, ERROR_DURING_ATTACH, ATTACHED, ATTACHED_UPDATE_ERROR, DETACHED\n\t}\n\n\tpublic static class Rate {\n\t\tprivate long last = -1;\n\t\tpublic long current = -1;\n\t\tpublic long delta = 0;\n\t\tpublic long ratePerSecond = 0;\n\n\t\tpublic void update(long current) {\n\t\t\tthis.current = current;\n\t\t\tif (last != -1) {\n\t\t\t\tdelta = current - last;\n\t\t\t}\n\t\t\tlast = current;\n\t\t}\n\n\t\tpublic void caculateRatePerSecond(long deltaTimeMills) {\n\t\t\tif (delta != 0) {\n\t\t\t\tratePerSecond = delta * 1000 / deltaTimeMills;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static class Usage {\n\t\tpublic long used = -1;\n\t\tpublic long committed = -1;\n\t\tpublic long max = -1;\n\n\t\tpublic Usage() {\n\t\t}\n\n\t\tpublic Usage(long used, long committed, long max) {\n\t\t\tthis.used = used;\n\t\t\tthis.committed = committed;\n\t\t\tthis.max = max;\n\t\t}\n\n\t\tpublic Usage(MemoryUsage jmxUsage) {\n\t\t\tthis(jmxUsage.getUsed(), jmxUsage.getCommitted(), jmxUsage.getMax());\n\t\t}\n\t}\n}"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/WarningRule.java",
    "content": "package com.vip.vjtools.vjtop;\n\nimport com.vip.vjtools.vjtop.util.Formats;\n\npublic class WarningRule {\n\tpublic DoubleWarning cpu = new DoubleWarning(50d, 70d);\n\n\tpublic LongWarning swap = new LongWarning(1, 1);\n\tpublic LongWarning thread = new LongWarning();\n\tpublic LongWarning newThread = new LongWarning(1, Long.MAX_VALUE);\n\tpublic LongWarning io = new LongWarning(100 * Formats.MB_SIZE, Long.MAX_VALUE);\n\n\tpublic LongWarning loadClass = new LongWarning(80000, Long.MAX_VALUE);\n\tpublic LongWarning newClass = new LongWarning(1, Long.MAX_VALUE);\n\n\tpublic LongWarning old = new LongWarning();\n\tpublic LongWarning codeCache = new LongWarning();\n\tpublic LongWarning perm = new LongWarning();\n\n\tpublic LongWarning ygcCount = new LongWarning();\n\tpublic LongWarning ygcTime = new LongWarning();\n\tpublic LongWarning ygcAvgTime = new LongWarning(100, 200);\n\tpublic LongWarning fullgcCount = new LongWarning(1, 2);\n\tpublic LongWarning safepointCount = new LongWarning();\n\tpublic LongWarning safepointTime = new LongWarning();\n\n\tpublic void updateProcessor(int processors) {\n\t\tthread.yellow = processors <= 8 ? processors * 150 : Math.max(8 * 150, processors * 100);\n\t\tthread.red = processors <= 8 ? processors * 225 : Math.max(8 * 225, processors * 150);\n\t}\n\n\tpublic void updateInterval(long intervalSeconds) {\n\n\t\tygcTime.yellow = intervalSeconds * 1000 * 5 / 100; // 5% interval\n\t\tygcTime.red = intervalSeconds * 1000 * 10 / 100; // 10% interval\n\n\t\tygcCount.yellow = intervalSeconds + 1;\n\t\tygcCount.red = intervalSeconds * 2 + 1;\n\n\t\tsafepointCount.yellow = intervalSeconds * 2;\n\t\tsafepointCount.red = intervalSeconds * 4;\n\n\t\tsafepointTime.yellow = intervalSeconds * 1000 * 5 / 100; // 5% interval\n\t\tsafepointTime.red = intervalSeconds * 1000 * 10 / 100; // 10% interval\n\t}\n\n\tpublic void updateOld(long max) {\n\t\tif (max != -1) {\n\t\t\told.yellow = max * 85 / 100;\n\t\t\told.red = max * 95 / 100;\n\t\t}\n\t}\n\n\tpublic void updatePerm(long max) {\n\t\tif (max != -1) {\n\t\t\tperm.yellow = max * 85 / 100;\n\t\t\tperm.red = max * 95 / 100;\n\t\t}\n\t}\n\n\tpublic void updateCodeCache(long max) {\n\t\tif (max != -1) {\n\t\t\tcodeCache.yellow = max * 85 / 100;\n\t\t\tcodeCache.red = max * 95 / 100;\n\t\t}\n\t}\n\n\tpublic static class DoubleWarning {\n\t\tpublic double yellow;\n\t\tpublic double red;\n\n\t\tpublic DoubleWarning() {\n\t\t\tyellow = Double.MAX_VALUE;\n\t\t\tred = Double.MAX_VALUE;\n\t\t}\n\n\t\tpublic DoubleWarning(double yellow, double red) {\n\t\t\tthis.yellow = yellow;\n\t\t\tthis.red = red;\n\t\t}\n\t}\n\n\tpublic static class LongWarning {\n\t\tpublic long yellow;\n\t\tpublic long red;\n\n\t\tpublic LongWarning() {\n\t\t\tyellow = Long.MAX_VALUE;\n\t\t\tred = Long.MAX_VALUE;\n\t\t}\n\n\t\tpublic LongWarning(long yellow, long red) {\n\t\t\tthis.yellow = yellow;\n\t\t\tthis.red = red;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/data/PerfData.java",
    "content": "package com.vip.vjtools.vjtop.data;\r\n\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.concurrent.TimeUnit;\r\n\r\nimport com.vip.vjtools.vjtop.util.Utils;\r\n\r\nimport sun.management.counter.Counter;\r\nimport sun.management.counter.LongCounter;\r\nimport sun.management.counter.perf.PerfInstrumentation;\r\nimport sun.misc.Perf;\r\n\r\n@SuppressWarnings(\"restriction\")\r\npublic class PerfData {\r\n\tprivate final PerfInstrumentation instr;\r\n\t// PerfData中的时间相关数据以tick表示，每个tick的时长与计算机频率相关\r\n\tprivate final double nanosPerTick;\r\n\r\n\tprivate final Map<String, Counter> counters;\r\n\r\n\tpublic static PerfData connect(int pid) {\r\n\t\ttry {\r\n\t\t\treturn new PerfData(pid);\r\n\t\t} catch (ThreadDeath e) {\r\n\t\t\tthrow e;\r\n\t\t} catch (OutOfMemoryError e) {\r\n\t\t\tthrow e;\r\n\t\t} catch (Throwable e) {\r\n\t\t\tthrow new RuntimeException(\"Cannot perf data for process \" + pid + \" - \" + e.toString());\r\n\t\t}\r\n\t}\r\n\r\n\tprivate PerfData(int pid) throws IOException {\r\n\t\tByteBuffer bb = Perf.getPerf().attach(pid, \"r\");\r\n\t\tinstr = new PerfInstrumentation(bb);\r\n\t\tcounters = buildAllCounters();\r\n\r\n\t\tlong hz = (Long) counters.get(\"sun.os.hrt.frequency\").getValue();\r\n\t\tnanosPerTick = ((double) TimeUnit.SECONDS.toNanos(1)) / hz;\r\n\t}\r\n\r\n\tprivate Map<String, Counter> buildAllCounters() {\r\n\t\tMap<String, Counter> result = new HashMap<>(512);\r\n\r\n\t\tfor (Counter c : instr.getAllCounters()) {\r\n\t\t\tresult.put(c.getName(), c);\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n\r\n\tpublic Map<String, Counter> getAllCounters() {\r\n\t\treturn counters;\r\n\t}\r\n\r\n\tpublic Counter findCounter(String counterName) {\r\n\t\treturn counters.get(counterName);\r\n\t}\r\n\r\n\tpublic long tickToMills(LongCounter tickCounter) {\r\n\t\tif (tickCounter.getUnits() == sun.management.counter.Units.TICKS) {\r\n\t\t\treturn (long) ((nanosPerTick * tickCounter.longValue()) / Utils.NANOS_TO_MILLS);\r\n\t\t} else {\r\n\t\t\tthrow new IllegalArgumentException(tickCounter.getName() + \" is not a ticket counter\");\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/data/ProcFileData.java",
    "content": "package com.vip.vjtools.vjtop.data;\n\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n// See http://man7.org/linux/man-pages/man5/proc.5.html for /proc file details\npublic class ProcFileData {\n\n\tprivate static final String PROC_SELF_STATUS_FILE_TPL = \"/proc/%s/status\";\n\tprivate static final String PROC_SELF_IO_FILE_TPL = \"/proc/%s/io\";\n\n\tprivate static final String VALUE_SEPARATOR = \":\";\n\n\tpublic static Map<String, String> getProcStatus(String pid) {\n\t\treturn getProcFileAsMap(String.format(PROC_SELF_STATUS_FILE_TPL, pid));\n\t}\n\n\tpublic static Map<String, String> getProcIO(String pid) {\n\t\treturn getProcFileAsMap(String.format(PROC_SELF_IO_FILE_TPL, pid));\n\t}\n\n\tpublic static Map<String, String> getProcFileAsMap(String filePath) {\n\t\ttry {\n\t\t\tFile file = new File(filePath);\n\t\t\tif (!file.exists() || file.isDirectory() || !file.canRead()) {\n\t\t\t\treturn Collections.emptyMap();\n\t\t\t}\n\n\t\t\tList<String> lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);\n\t\t\tMap<String, String> result = new HashMap<>(lines.size() * 2);\n\n\t\t\tfor (String line : lines) {\n\t\t\t\tint index = line.indexOf(VALUE_SEPARATOR);\n\t\t\t\tif (index <= 0 || index >= line.length() - 1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tString key = line.substring(0, index).trim();\n\t\t\t\tString value = line.substring(index + 1).trim();\n\t\t\t\tresult.put(key, value);\n\t\t\t}\n\t\t\treturn result;\n\t\t} catch (Throwable ex) {\n\t\t\tex.printStackTrace();\n\t\t\treturn Collections.emptyMap();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/data/jmx/JmxBufferPoolManager.java",
    "content": "package com.vip.vjtools.vjtop.data.jmx;\n\nimport java.io.IOException;\nimport java.lang.management.BufferPoolMXBean;\nimport java.lang.management.ManagementFactory;\nimport java.util.List;\n\nimport javax.management.MBeanServerConnection;\n\npublic class JmxBufferPoolManager {\n\tprivate static final String DIRECT = \"direct\";\n\tprivate static final String MAPPED = \"mapped\";\n\n\tprivate BufferPoolMXBean directBufferPool = null;\n\tprivate BufferPoolMXBean mappedBufferPool = null;\n\n\tpublic JmxBufferPoolManager(MBeanServerConnection connection) throws IOException {\n\n\t\tList<BufferPoolMXBean> bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(connection,\n\t\t\t\tBufferPoolMXBean.class);\n\t\tif (bufferPoolMXBeans == null) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (BufferPoolMXBean bufferPool : bufferPoolMXBeans) {\n\t\t\tString name = bufferPool.getName().toLowerCase().trim();\n\t\t\tif (name.contains(DIRECT)) {\n\t\t\t\tdirectBufferPool = bufferPool;\n\t\t\t} else if (name.contains(MAPPED)) {\n\t\t\t\tmappedBufferPool = bufferPool;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic long getDirectBufferPoolUsed() {\n\t\treturn directBufferPool != null ? directBufferPool.getMemoryUsed() : 0;\n\t}\n\n\tpublic long getDirectBufferPoolCapacity() {\n\t\treturn directBufferPool != null ? directBufferPool.getTotalCapacity() : 0;\n\t}\n\n\tpublic long getMappedBufferPoolUsed() {\n\t\treturn mappedBufferPool != null ? mappedBufferPool.getMemoryUsed() : 0;\n\t}\n\n\tpublic long getMappedBufferPoolCapacity() {\n\t\treturn mappedBufferPool != null ? mappedBufferPool.getTotalCapacity() : 0;\n\t}\n\n\tpublic long getMappedBufferPoolCount() {\n\t\treturn mappedBufferPool != null ? mappedBufferPool.getCount() : 0;\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/data/jmx/JmxClient.java",
    "content": "/*\n * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES\n * OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public\n * License version 2 only, as published by the Free Software Foundation. Oracle designates this particular file as\n * subject to the \"Classpath\" exception as provided by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License version 2 for\n * more details (a copy is included in the LICENSE file that accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version 2 along with this work; if not, write to\n * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA or visit www.oracle.com if you need\n * additional information or have any questions.\n *\n */\n\npackage com.vip.vjtools.vjtop.data.jmx;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.management.ClassLoadingMXBean;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.RuntimeMXBean;\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Proxy;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.TreeSet;\n\nimport javax.management.Attribute;\nimport javax.management.AttributeList;\nimport javax.management.AttributeNotFoundException;\nimport javax.management.InstanceNotFoundException;\nimport javax.management.JMX;\nimport javax.management.MBeanException;\nimport javax.management.MBeanServerConnection;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.ObjectName;\nimport javax.management.ReflectionException;\nimport javax.management.remote.JMXConnector;\nimport javax.management.remote.JMXConnectorFactory;\nimport javax.management.remote.JMXServiceURL;\n\nimport com.sun.management.HotSpotDiagnosticMXBean;\nimport com.sun.management.OperatingSystemMXBean;\nimport com.sun.management.ThreadMXBean;\nimport com.sun.tools.attach.AgentInitializationException;\nimport com.sun.tools.attach.AgentLoadException;\nimport com.sun.tools.attach.VirtualMachine;\nimport com.vip.vjtools.vjtop.util.Utils;\n\n@SuppressWarnings(\"restriction\")\npublic class JmxClient {\n\tprivate static final String LOCAL_CONNECTOR_ADDRESS_PROP = \"com.sun.management.jmxremote.localConnectorAddress\";\n\n\tprivate boolean hasPlatformMXBeans = false;\n\n\tprivate String pid;\n\n\tprivate MBeanServerConnection mbsc = null;\n\tprivate SnapshotMBeanServerConnection server = null;\n\tprivate JMXConnector jmxc = null;\n\n\tprivate ClassLoadingMXBean classLoadingMBean = null;\n\tprivate OperatingSystemMXBean operatingSystemMBean = null;\n\tprivate RuntimeMXBean runtimeMBean = null;\n\tprivate HotSpotDiagnosticMXBean hotSpotDiagnosticMXBean = null;\n\tprivate ThreadMXBean threadMBean = null;\n\n\tprivate JmxGarbageCollectorManager garbageCollectorManager = null;\n\tprivate JmxMemoryPoolManager memoryPoolManager = null;\n\tprivate JmxBufferPoolManager bufferPoolManager = null;\n\n\tpublic JmxClient() throws IOException {\n\t}\n\n\tpublic void flush() {\n\t\tif (server != null) {\n\t\t\tserver.flush();\n\t\t}\n\t}\n\n\tpublic void connect(String pid, String jmxHostAndPort) throws Exception {\n\t\tthis.pid = pid;\n\n\t\tif (jmxHostAndPort != null) {\n\t\t\tJMXServiceURL jmxUrl = new JMXServiceURL(\n\t\t\t\t\t\"service:jmx:rmi://\" + jmxHostAndPort + \"/jndi/rmi://\" + jmxHostAndPort + \"/jmxrmi\");\n\t\t\tMap credentials = new HashMap(1);\n\t\t\tString[] creds = new String[]{null, null};\n\t\t\tcredentials.put(JMXConnector.CREDENTIALS, creds);\n\n\t\t\tthis.jmxc = JMXConnectorFactory.connect(jmxUrl, credentials);\n\t\t} else {\n\t\t\t// 如果jmx agent未启动，主动attach进JVM后加载\n\t\t\tString address = attachToGetConnectorAddress();\n\n\t\t\tJMXServiceURL jmxUrl = new JMXServiceURL(address);\n\t\t\tthis.jmxc = JMXConnectorFactory.connect(jmxUrl);// NOSONAR\n\t\t}\n\n\t\tthis.mbsc = jmxc.getMBeanServerConnection();\n\t\tthis.server = Snapshot.newSnapshot(mbsc);\n\n\t\ttry {\n\t\t\tObjectName on = createBeanName(ManagementFactory.THREAD_MXBEAN_NAME);\n\t\t\tthis.hasPlatformMXBeans = server.isRegistered(on);\n\t\t} catch (Exception e) {\n\t\t\t// should not reach here\n\t\t\tthrow new InternalError(e.getMessage());\n\t\t}\n\n\t\tif (hasPlatformMXBeans) {\n\t\t\t// WORKAROUND for bug 5056632\n\t\t\t// Check if the access role is correct by getting a RuntimeMXBean\n\t\t\tgetRuntimeMXBean();\n\t\t}\n\t}\n\n\t/**\n\t * 因为已在退出JVM，因此仅关闭jmx连接即可\n\t */\n\tpublic void disconnect() {\n\t\t// Close MBeanServer connection\n\t\tif (jmxc != null) {\n\t\t\ttry {\n\t\t\t\tjmxc.close();\n\t\t\t} catch (IOException e) {\n\t\t\t\t// Ignore ???\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic synchronized ClassLoadingMXBean getClassLoadingMXBean() throws IOException {\n\t\tif (hasPlatformMXBeans && classLoadingMBean == null) {\n\t\t\tclassLoadingMBean = ManagementFactory.newPlatformMXBeanProxy(server,\n\t\t\t\t\tManagementFactory.CLASS_LOADING_MXBEAN_NAME, ClassLoadingMXBean.class);\n\t\t}\n\t\treturn classLoadingMBean;\n\t}\n\n\tpublic synchronized RuntimeMXBean getRuntimeMXBean() throws IOException {\n\t\tif (hasPlatformMXBeans && runtimeMBean == null) {\n\t\t\truntimeMBean = ManagementFactory.newPlatformMXBeanProxy(server, ManagementFactory.RUNTIME_MXBEAN_NAME,\n\t\t\t\t\tRuntimeMXBean.class);\n\t\t}\n\t\treturn runtimeMBean;\n\t}\n\n\tpublic synchronized OperatingSystemMXBean getOperatingSystemMXBean() throws IOException {\n\t\tif (hasPlatformMXBeans && operatingSystemMBean == null) {\n\t\t\toperatingSystemMBean = ManagementFactory.newPlatformMXBeanProxy(server,\n\t\t\t\t\tManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME, OperatingSystemMXBean.class);\n\t\t}\n\t\treturn operatingSystemMBean;\n\t}\n\n\tpublic synchronized HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException {\n\t\tif (hasPlatformMXBeans && hotSpotDiagnosticMXBean == null) {\n\t\t\thotSpotDiagnosticMXBean = ManagementFactory.newPlatformMXBeanProxy(server,\n\t\t\t\t\t\"com.sun.management:type=HotSpotDiagnostic\", HotSpotDiagnosticMXBean.class);\n\t\t}\n\t\treturn hotSpotDiagnosticMXBean;\n\t}\n\n\tpublic synchronized ThreadMXBean getThreadMXBean() throws IOException {\n\t\tif (hasPlatformMXBeans && threadMBean == null) {\n\t\t\tthreadMBean = JMX.newMXBeanProxy(server, createBeanName(ManagementFactory.THREAD_MXBEAN_NAME),\n\t\t\t\t\tThreadMXBean.class);\n\t\t}\n\t\treturn threadMBean;\n\t}\n\n\tpublic synchronized JmxMemoryPoolManager getMemoryPoolManager() throws IOException {\n\t\tif (hasPlatformMXBeans && memoryPoolManager == null) {\n\t\t\tmemoryPoolManager = new JmxMemoryPoolManager(server);\n\t\t}\n\t\treturn memoryPoolManager;\n\t}\n\n\tpublic synchronized JmxBufferPoolManager getBufferPoolManager() throws IOException {\n\t\tif (hasPlatformMXBeans && bufferPoolManager == null) {\n\t\t\tbufferPoolManager = new JmxBufferPoolManager(server);\n\t\t}\n\n\t\treturn bufferPoolManager;\n\t}\n\n\tpublic synchronized JmxGarbageCollectorManager getGarbageCollectorManager() throws IOException {\n\t\tif (hasPlatformMXBeans && garbageCollectorManager == null) {\n\t\t\tgarbageCollectorManager = new JmxGarbageCollectorManager(server);\n\t\t}\n\t\treturn garbageCollectorManager;\n\t}\n\n\tprivate ObjectName createBeanName(String beanName) {\n\t\ttry {\n\t\t\treturn new ObjectName(beanName);\n\t\t} catch (MalformedObjectNameException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"JMX Client for PID:\" + pid;\n\t}\n\n\t/**\n\t * VirtualMachine保证JMX Agent已启动,\n\t * 并向JMXClient提供连接地址地址样例：service:jmx:rmi://127.0\n\t * .0.1/stub/rO0ABXN9AAAAAQAl...\n\t */\n\tpublic String attachToGetConnectorAddress() throws Exception {\n\t\tVirtualMachine vm = null;\n\n\t\t// 1. attach vm\n\t\tvm = VirtualMachine.attach(pid);\n\n\t\ttry {\n\t\t\t// 2. 检查smartAgent是否已启动\n\t\t\tProperties agentProps = vm.getAgentProperties();\n\t\t\tString address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP);\n\n\t\t\tif (address != null) {\n\t\t\t\treturn address;\n\t\t\t}\n\n\t\t\t// 3. 未启动，尝试启动\n\t\t\tString home = vm.getSystemProperties().getProperty(\"java.home\");\n\t\t\tint version = Utils.getJavaMajorVersion(vm.getSystemProperties().getProperty(\"java.specification.version\"));\n\n\t\t\tif (version >= 8) {\n\t\t\t\tvm.startLocalManagementAgent();\n\t\t\t\tagentProps = vm.getAgentProperties();\n\t\t\t\taddress = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP);\n\t\t\t\tif (address != null) {\n\t\t\t\t\treturn address;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Normally in ${java.home}/jre/lib/management-agent.jar but might\n\t\t\t\t// be in ${java.home}/lib in build environments.\n\t\t\t\tString agentPath = home + File.separator + \"jre\" + File.separator + \"lib\" + File.separator\n\t\t\t\t\t\t+ \"management-agent.jar\";\n\t\t\t\tFile f = new File(agentPath);\n\t\t\t\tif (!f.exists()) {\n\t\t\t\t\tagentPath = home + File.separator + \"lib\" + File.separator + \"management-agent.jar\";\n\t\t\t\t\tf = new File(agentPath);\n\t\t\t\t\tif (!f.exists()) {\n\t\t\t\t\t\tthrow new IOException(\"Management agent not found\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tagentPath = f.getCanonicalPath();\n\t\t\t\ttry {\n\t\t\t\t\tvm.loadAgent(agentPath, \"com.sun.management.jmxremote\");\n\t\t\t\t} catch (AgentLoadException x) {\n\t\t\t\t\t// 高版本 attach 低版本jdk 抛异常：com.sun.tools.attach.AgentLoadException: 0，实际上是成功的;\n\t\t\t\t\t// 根因： HotSpotVirtualMachine.loadAgentLibrary 高版本jdk实现不一样了\n\t\t\t\t\tif (!\"0\".equals(x.getMessage())) {\n\t\t\t\t\t\tIOException ioe = new IOException(x.getMessage());\n\t\t\t\t\t\tioe.initCause(x);\n\t\t\t\t\t\tthrow ioe;\n\t\t\t\t\t}\n\t\t\t\t} catch (AgentInitializationException x) {\n\t\t\t\t\tIOException ioe = new IOException(x.getMessage());\n\t\t\t\t\tioe.initCause(x);\n\t\t\t\t\tthrow ioe;\n\t\t\t\t}\n\n\n\t\t\t\t// 4. 再次获取connector address\n\t\t\t\tagentProps = vm.getAgentProperties();\n\t\t\t\taddress = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP);\n\n\t\t\t\tif (address == null) {\n\t\t\t\t\tthrow new IOException(\"Fails to find connector address\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tagentProps = vm.getAgentProperties();\n\t\t\taddress = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP);\n\n\n\t\t\treturn address;\n\t\t} finally {\n\t\t\tvm.detach();\n\t\t}\n\t}\n\n\t// JDK自带的Snapshot实现，在flush前缓存值\n\n\t// Snapshot MBeanServerConnection:\n\t//\n\t// This is an object that wraps an existing MBeanServerConnection and adds\n\t// caching to it, as follows:\n\t//\n\t// - The first time an attribute is called in a given MBean, the result is\n\t// cached. Every subsequent time getAttribute is called for that attribute\n\t// the cached result is returned.\n\t//\n\t// - Before every call to VMPanel.update() or when the Refresh button in the\n\t// Attributes table is pressed down the attributes cache is flushed. Then\n\t// any subsequent call to getAttribute will retrieve all the values for\n\t// the attributes that are known to the cache.\n\t//\n\t// - The attributes cache uses a learning approach and only the attributes\n\t// that are in the cache will be retrieved between two subsequent updates.\n\t//\n\n\tpublic interface SnapshotMBeanServerConnection extends MBeanServerConnection {\n\t\t/**\n\t\t * Flush all cached values of attributes.\n\t\t */\n\t\tvoid flush();\n\t}\n\n\tpublic static class Snapshot {\n\t\tprivate Snapshot() {\n\t\t}\n\n\t\tpublic static SnapshotMBeanServerConnection newSnapshot(MBeanServerConnection mbsc) {\n\t\t\tfinal InvocationHandler ih = new SnapshotInvocationHandler(mbsc);\n\t\t\treturn (SnapshotMBeanServerConnection) Proxy.newProxyInstance(Snapshot.class.getClassLoader(),\n\t\t\t\t\tnew Class[]{SnapshotMBeanServerConnection.class}, ih);\n\t\t}\n\t}\n\n\tstatic class SnapshotInvocationHandler implements InvocationHandler {\n\n\t\tprivate final MBeanServerConnection conn;\n\t\tprivate Map<ObjectName, NameValueMap> cachedValues = newMap();\n\t\tprivate Map<ObjectName, Set<String>> cachedNames = newMap();\n\n\t\t@SuppressWarnings(\"serial\")\n\t\tprivate static final class NameValueMap extends HashMap<String, Object> {\n\t\t}\n\n\t\tSnapshotInvocationHandler(MBeanServerConnection conn) {\n\t\t\tthis.conn = conn;\n\t\t}\n\n\t\tsynchronized void flush() {\n\t\t\tcachedValues = newMap();\n\t\t}\n\n\t\t@Override\n\t\tpublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n\t\t\tfinal String methodName = method.getName();\n\t\t\tif (methodName.equals(\"getAttribute\")) {\n\t\t\t\treturn getAttribute((ObjectName) args[0], (String) args[1]);\n\t\t\t} else if (methodName.equals(\"getAttributes\")) {\n\t\t\t\treturn getAttributes((ObjectName) args[0], (String[]) args[1]);\n\t\t\t} else if (methodName.equals(\"flush\")) {\n\t\t\t\tflush();\n\t\t\t\treturn null;\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\treturn method.invoke(conn, args);\n\t\t\t\t} catch (InvocationTargetException e) {\n\t\t\t\t\tthrow e.getCause();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tprivate Object getAttribute(ObjectName objName, String attrName) throws MBeanException,\n\t\t\t\tInstanceNotFoundException, AttributeNotFoundException, ReflectionException, IOException {\n\t\t\tfinal NameValueMap values = getCachedAttributes(objName, Collections.singleton(attrName));\n\t\t\tObject value = values.get(attrName);\n\t\t\tif (value != null || values.containsKey(attrName)) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t\t// Not in cache, presumably because it was omitted from the\n\t\t\t// getAttributes result because of an exception. Following\n\t\t\t// call will probably provoke the same exception.\n\t\t\treturn conn.getAttribute(objName, attrName);\n\t\t}\n\n\t\tprivate AttributeList getAttributes(ObjectName objName, String[] attrNames)\n\t\t\t\tthrows InstanceNotFoundException, ReflectionException, IOException {\n\t\t\tfinal NameValueMap values = getCachedAttributes(objName, new TreeSet<String>(Arrays.asList(attrNames)));\n\t\t\tfinal AttributeList list = new AttributeList();\n\t\t\tfor (String attrName : attrNames) {\n\t\t\t\tfinal Object value = values.get(attrName);\n\t\t\t\tif (value != null || values.containsKey(attrName)) {\n\t\t\t\t\tlist.add(new Attribute(attrName, value));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn list;\n\t\t}\n\n\t\tprivate synchronized NameValueMap getCachedAttributes(ObjectName objName, Set<String> attrNames)\n\t\t\t\tthrows InstanceNotFoundException, ReflectionException, IOException {\n\t\t\tNameValueMap values = cachedValues.get(objName);\n\t\t\tif (values != null && values.keySet().containsAll(attrNames)) {\n\t\t\t\treturn values;\n\t\t\t}\n\t\t\tattrNames = new TreeSet<String>(attrNames);\n\t\t\tSet<String> oldNames = cachedNames.get(objName);\n\t\t\tif (oldNames != null) {\n\t\t\t\tattrNames.addAll(oldNames);\n\t\t\t}\n\t\t\tvalues = new NameValueMap();\n\t\t\tfinal AttributeList attrs = conn.getAttributes(objName, attrNames.toArray(new String[attrNames.size()]));\n\t\t\tfor (Attribute attr : attrs.asList()) {\n\t\t\t\tvalues.put(attr.getName(), attr.getValue());\n\t\t\t}\n\t\t\tcachedValues.put(objName, values);\n\t\t\tcachedNames.put(objName, attrNames);\n\t\t\treturn values;\n\t\t}\n\n\t\t// See http://www.artima.com/weblogs/viewpost.jsp?thread=79394\n\t\tprivate static <K, V> Map<K, V> newMap() {\n\t\t\treturn new HashMap<K, V>();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/data/jmx/JmxGarbageCollectorManager.java",
    "content": "package com.vip.vjtools.vjtop.data.jmx;\n\nimport java.io.IOException;\nimport java.lang.management.ManagementFactory;\nimport java.util.List;\n\nimport javax.management.MBeanServerConnection;\n\nimport com.sun.management.GarbageCollectorMXBean;\n\npublic class JmxGarbageCollectorManager {\n\n\tprivate GarbageCollectorMXBean ygcMXBean = null;\n\tprivate GarbageCollectorMXBean fgcMXBean = null;\n\tprivate String ygcStrategy = null;\n\tprivate String fgcStrategy = null;\n\n\tpublic static String getByGcName(String gcName, String defaultName) {\n\n\t\treturn defaultName;\n\t}\n\n\tpublic JmxGarbageCollectorManager(MBeanServerConnection connection) throws IOException {\n\t\tif (ygcMXBean != null || fgcMXBean != null) {\n\t\t\treturn;\n\t\t}\n\n\t\tList<GarbageCollectorMXBean> gcMXBeans = ManagementFactory.getPlatformMXBeans(connection,\n\t\t\t\tGarbageCollectorMXBean.class);\n\n\t\tfor (GarbageCollectorMXBean gcMXBean : gcMXBeans) {\n\t\t\tString gcName = gcMXBean.getName();\n\t\t\tif (\"Copy\".equals(gcName) || \"PS Scavenge\".equals(gcName) || \"ParNew\".equals(gcName)\n\t\t\t\t\t|| \"G1 Young Generation\".equals(gcName) || \"ZGC\".equals(gcName)) {\n\t\t\t\tygcMXBean = gcMXBean;\n\t\t\t\tygcStrategy = gcName;\n\t\t\t} else if (\"MarkSweepCompact\".equals(gcName) || \"PS MarkSweep\".equals(gcName)\n\t\t\t\t\t|| \"ConcurrentMarkSweep\".equals(gcName) || \"G1 Old Generation\".equals(gcName)) {\n\t\t\t\tfgcMXBean = gcMXBean;\n\t\t\t\tfgcStrategy = gcName;\n\t\t\t} else {\n\t\t\t\t// default\n\t\t\t\tygcMXBean = gcMXBean;\n\t\t\t\tygcStrategy = gcName;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic synchronized GarbageCollectorMXBean getYoungCollector() {\n\t\treturn ygcMXBean;\n\t}\n\n\tpublic synchronized GarbageCollectorMXBean getFullCollector() {\n\t\treturn fgcMXBean;\n\t}\n\n\tpublic String getYgcStrategy() {\n\t\treturn ygcStrategy;\n\t}\n\n\tpublic String getFgcStrategy() {\n\t\treturn fgcStrategy;\n\t}\n\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/data/jmx/JmxMemoryPoolManager.java",
    "content": "package com.vip.vjtools.vjtop.data.jmx;\n\nimport java.io.IOException;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.MemoryPoolMXBean;\nimport java.util.List;\n\nimport javax.management.MBeanServerConnection;\n\npublic class JmxMemoryPoolManager {\n\tpublic static final String EDEN = \"eden\";\n\tpublic static final String SURVIVOR = \"survivor\";\n\tpublic static final String OLD = \"old\";\n\tpublic static final String TENURED = \"tenured\"; // 并行GC算法老生代的名称\n\tpublic static final String PERM = \"perm\";\n\tpublic static final String METASPACE = \"metaspace\";// JDK8永久代名称\n\tpublic static final String CODE_CACHE = \"code cache\";\n\tpublic static final String CODEHEAP = \"codeheap\";\n\tpublic static final String CODECACHE = \"codecache\";\n\tpublic static final String ZHEAP = \"zheap\"; // for zgc\n\tpublic static final String COMPRESSED_CLASS_SPACE = \"compressed class space\"; // zgc not support until jdk15\n\n\tprivate MemoryPoolMXBean survivorMemoryPool = null;\n\tprivate MemoryPoolMXBean edenMemoryPool = null;\n\tprivate MemoryPoolMXBean oldMemoryPool = null;\n\tprivate MemoryPoolMXBean permMemoryPool = null;\n\tprivate MemoryPoolMXBean codeCacheMemoryPool = null;\n\tprivate MemoryPoolMXBean compressedClassSpaceMemoryPool = null;\n\n\tpublic JmxMemoryPoolManager(MBeanServerConnection connection) throws IOException {\n\t\tList<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getPlatformMXBeans(connection,\n\t\t\t\tMemoryPoolMXBean.class);\n\t\tfor (MemoryPoolMXBean memoryPool : memoryPoolMXBeans) {\n\t\t\tString name = memoryPool.getName().trim();\n\t\t\tString lowerCaseName = name.toLowerCase();\n\t\t\tif (lowerCaseName.contains(SURVIVOR)) {\n\t\t\t\tsurvivorMemoryPool = memoryPool;\n\t\t\t} else if (lowerCaseName.contains(EDEN) || lowerCaseName.contains(ZHEAP)) {\n\t\t\t\tedenMemoryPool = memoryPool;\n\t\t\t} else if (lowerCaseName.contains(OLD) || lowerCaseName.contains(TENURED)) {\n\t\t\t\toldMemoryPool = memoryPool;\n\t\t\t} else if (lowerCaseName.contains(PERM) || lowerCaseName.contains(METASPACE)) {\n\t\t\t\tpermMemoryPool = memoryPool;\n\t\t\t} else if (lowerCaseName.contains(CODE_CACHE) || lowerCaseName.contains(CODEHEAP)\n\t\t\t\t\t|| lowerCaseName.contains(CODECACHE)) {\n\t\t\t\tcodeCacheMemoryPool = memoryPool;\n\t\t\t} else if (lowerCaseName.contains(COMPRESSED_CLASS_SPACE)) {\n\t\t\t\tcompressedClassSpaceMemoryPool = memoryPool;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic MemoryPoolMXBean getSurvivorMemoryPool() {\n\t\treturn survivorMemoryPool;\n\t}\n\n\tpublic MemoryPoolMXBean getEdenMemoryPool() {\n\t\treturn edenMemoryPool;\n\t}\n\n\tpublic MemoryPoolMXBean getOldMemoryPool() {\n\t\treturn oldMemoryPool;\n\t}\n\n\tpublic MemoryPoolMXBean getPermMemoryPool() {\n\t\treturn permMemoryPool;\n\t}\n\n\tpublic MemoryPoolMXBean getCodeCacheMemoryPool() {\n\t\treturn codeCacheMemoryPool;\n\t}\n\n\tpublic MemoryPoolMXBean getCompressedClassSpaceMemoryPool() {\n\t\treturn compressedClassSpaceMemoryPool;\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/util/Formats.java",
    "content": "package com.vip.vjtools.vjtop.util;\n\nimport java.util.List;\nimport java.util.Locale;\n\nimport com.vip.vjtools.vjtop.VMInfo.Usage;\nimport com.vip.vjtools.vjtop.WarningRule.DoubleWarning;\nimport com.vip.vjtools.vjtop.WarningRule.LongWarning;\n\npublic class Formats {\n\n\tprivate static final long BYTE_SIZE = 1;\n\tprivate static final long KB_SIZE = BYTE_SIZE * 1024;\n\tpublic static final long MB_SIZE = KB_SIZE * 1024;\n\tprivate static final long GB_SIZE = MB_SIZE * 1024;\n\tprivate static final long TB_SIZE = GB_SIZE * 1024;\n\n\tprivate static String[] RED_ANSI = new String[] { \"\\033[31m\\033[01m\", \"\\033[0m\" };\n\tprivate static String[] YELLOW_ANSI = new String[] { \"\\033[33m\\033[01m\", \"\\033[0m\" };\n\tprivate static final String[] NORMAL_ANSI = new String[] { \"\", \"\" };\n\tprivate static String CLEAR_TERMINAL_ANSI_CMD = new String(\n\t\t\tnew byte[] { (byte) 0x1b, (byte) 0x5b, (byte) 0x32, (byte) 0x4a, (byte) 0x1b, (byte) 0x5b, (byte) 0x48 });\n\n\tpublic static boolean isWindows = System.getProperty(\"os.name\").toLowerCase(Locale.US).contains(\"windows\");\n\t{\n\t\tif (isWindows) {\n\t\t\tdisableAnsi();\n\t\t\tCLEAR_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\";\n\t\t}\n\t}\n\n\tpublic static void disableAnsi() {\n\t\tRED_ANSI = NORMAL_ANSI;\n\t\tYELLOW_ANSI = NORMAL_ANSI;\n\t}\n\n\tpublic static void setCleanClearTerminal() {\n\t\tCLEAR_TERMINAL_ANSI_CMD = \"%n%n\";\n\t}\n\n\tpublic static void setTextClearTerminal() {\n\t\tCLEAR_TERMINAL_ANSI_CMD = \"%n\";\n\t}\n\n\tpublic static String toMBWithColor(long bytes, LongWarning warning) {\n\t\tString[] ansi = colorAnsi(bytes, warning);\n\t\treturn ansi[0] + toMB(bytes) + ansi[1];\n\t}\n\n\tpublic static String toColor(long value, LongWarning warning) {\n\t\tString[] ansi = colorAnsi(value, warning);\n\t\treturn ansi[0] + value + ansi[1];\n\t}\n\n\tpublic static String red(String value) {\n\t\treturn RED_ANSI[0] + value + RED_ANSI[1];\n\t}\n\n\tpublic static String yellow(String value) {\n\t\treturn YELLOW_ANSI[0] + value + YELLOW_ANSI[1];\n\t}\n\n\t/**\n\t * Formats a long value containing \"number of bytes\" to its megabyte representation. If the value is negative, \"n/a\"\n\t * will be returned.\n\t */\n\tpublic static String toMB(long bytes) {\n\t\tif (bytes < 0) {\n\t\t\treturn \"NaN\";\n\t\t}\n\t\tlong mb = bytes / MB_SIZE;\n\n\t\tif (mb < 9999) {\n\t\t\treturn mb + \"m\";\n\t\t} else {\n\t\t\treturn toSizeUnit(bytes).trim();\n\t\t}\n\t}\n\n\tpublic static String toSizeUnitWithColor(Long size, LongWarning warning) {\n\t\tString[] ansi = colorAnsi(size, warning);\n\t\treturn ansi[0] + toSizeUnit(size) + ansi[1];\n\t}\n\n\tpublic static String toSizeUnit(Long size) {\n\t\tif (size == null) {\n\t\t\treturn \"NaN\";\n\t\t}\n\t\tif (size < KB_SIZE) {\n\t\t\treturn size.toString();\n\t\t}\n\n\t\tif (size < MB_SIZE) {\n\t\t\treturn (size / KB_SIZE) + \"k\";\n\t\t}\n\n\t\tif (size < GB_SIZE) {\n\t\t\treturn (size / MB_SIZE) + \"m\";\n\t\t}\n\n\t\tif (size < TB_SIZE) {\n\t\t\treturn (size / GB_SIZE) + \"g\";\n\t\t}\n\n\t\treturn (size / TB_SIZE) + \"t\";\n\t}\n\n\tpublic static String toFixLengthSizeUnit(Long size) {\n\t\tif (size == null) {\n\t\t\treturn \"NaN\";\n\t\t}\n\t\tif (size < KB_SIZE) {\n\t\t\treturn String.format(\"%4d\", size);\n\t\t}\n\n\t\tif (size < MB_SIZE) {\n\t\t\treturn String.format(\"%4dk\", size / KB_SIZE);\n\t\t}\n\n\t\tif (size < GB_SIZE) {\n\t\t\treturn String.format(\"%4dm\", size / MB_SIZE);\n\t\t}\n\n\t\tif (size < TB_SIZE) {\n\t\t\treturn String.format(\"%4dg\", size / GB_SIZE);\n\t\t}\n\n\t\treturn String.format(\"%4dt\", size / TB_SIZE);\n\t}\n\n\tpublic static String toTimeUnit(long millis) {\n\t\tlong seconds = millis / 1000;\n\t\tif (seconds < 60) {\n\t\t\treturn String.format(\"%02ds\", seconds);\n\t\t}\n\n\t\tif (seconds < 3600) {\n\t\t\treturn String.format(\"%02dm%02ds\", seconds / 60, seconds % 60);\n\t\t}\n\n\t\tif (seconds < (24 * 3600)) {\n\t\t\treturn String.format(\"%02dh%02dm\", seconds / 3600, (seconds / 60) % 60);\n\t\t}\n\n\t\treturn String.format(\"%dd%02dh\", seconds / (3600 * 24), (seconds / 3600) % 24);\n\t}\n\n\n\tpublic static String formatUsage(Usage usage) {\n\t\tif (usage.committed == usage.max) {\n\t\t\treturn String.format(\"%s/%s\", toMB(usage.used), toMB(usage.max));\n\t\t} else {\n\t\t\treturn String.format(\"%s/%s/%s\", toMB(usage.used), toMB(usage.committed), toMB(usage.max));\n\t\t}\n\t}\n\n\tpublic static String formatUsageWithColor(Usage usage, LongWarning warning) {\n\t\tString[] ansi = colorAnsi(usage.used, warning);\n\t\treturn ansi[0] + formatUsage(usage) + ansi[1];\n\t}\n\n\tpublic static String[] colorAnsi(long value, LongWarning warning) {\n\t\tif (value < warning.yellow) {\n\t\t\treturn NORMAL_ANSI;\n\t\t} else if (value >= warning.red) {\n\t\t\treturn RED_ANSI;\n\t\t} else {\n\t\t\treturn YELLOW_ANSI;\n\t\t}\n\t}\n\n\tpublic static String[] colorAnsi(double value, DoubleWarning warning) {\n\t\tif (value < warning.yellow) {\n\t\t\treturn NORMAL_ANSI;\n\t\t} else if (value >= warning.red) {\n\t\t\treturn RED_ANSI;\n\t\t} else {\n\t\t\treturn YELLOW_ANSI;\n\t\t}\n\t}\n\n\t/**\n\t * Returns a substring of the given string, representing the 'length' most-right characters\n\t */\n\tpublic static String rightStr(String str, int length) {\n\t\treturn str.substring(Math.max(0, str.length() - length));\n\t}\n\n\t/**\n\t * Returns a substring of the given string, representing the 'length' most-left characters\n\t */\n\tpublic static String leftStr(String str, int length) {\n\t\treturn str.substring(0, Math.min(str.length(), length));\n\t}\n\n\t/**\n\t * shortName(\"123456789\", 8, 3) = \"12...789\"\n\t * \n\t * shortName(\"123456789\", 8, 2) = \"123...89\"\n\t */\n\tpublic static String shortName(String str, int length, int rightLength) {\n\t\tif (str.length() > length) {\n\t\t\tint leftIndex = length - 3 - rightLength;\n\t\t\tstr = str.substring(0, Math.max(0, leftIndex)) + \"...\"\n\t\t\t\t\t+ str.substring(Math.max(0, str.length() - rightLength), str.length());\n\t\t}\n\t\treturn str;\n\t}\n\n\t/**\n\t * Joins the given list of strings using the given delimiter delim\n\t */\n\tpublic static String join(List<String> list, String delim) {\n\n\t\tStringBuilder sb = new StringBuilder();\n\n\t\tString loopDelim = \"\";\n\n\t\tfor (String s : list) {\n\n\t\t\tsb.append(loopDelim);\n\t\t\tsb.append(s);\n\n\t\t\tloopDelim = delim;\n\t\t}\n\n\t\treturn sb.toString();\n\t}\n\n\tpublic static long parseFromSize(String str) {\n\t\tif (str == null || str.isEmpty()) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tstr = str.toLowerCase();\n\t\tlong fromScale = BYTE_SIZE;\n\n\t\ttry {\n\t\t\tif (str.endsWith(\"kb\")) {\n\t\t\t\tstr = str.substring(0, str.length() - 2).trim();\n\t\t\t\tfromScale = KB_SIZE;\n\t\t\t}\n\t\t\tif (str.endsWith(\"k\")) {\n\t\t\t\tstr = str.substring(0, str.length() - 1).trim();\n\t\t\t\tfromScale = KB_SIZE;\n\t\t\t} else if (str.endsWith(\"mb\")) {\n\t\t\t\tstr = str.substring(0, str.length() - 2).trim();\n\t\t\t\tfromScale = MB_SIZE;\n\t\t\t} else if (str.endsWith(\"m\")) {\n\t\t\t\tstr = str.substring(0, str.length() - 1).trim();\n\t\t\t\tfromScale = MB_SIZE;\n\t\t\t} else if (str.endsWith(\"gb\")) {\n\t\t\t\tstr = str.substring(0, str.length() - 2).trim();\n\t\t\t\tfromScale = GB_SIZE;\n\t\t\t} else if (str.endsWith(\"g\")) {\n\t\t\t\tstr = str.substring(0, str.length() - 1).trim();\n\t\t\t\tfromScale = GB_SIZE;\n\t\t\t} else if (str.endsWith(\"tb\")) {\n\t\t\t\tstr = str.substring(0, str.length() - 2).trim();\n\t\t\t\tfromScale = TB_SIZE;\n\t\t\t} else if (str.endsWith(\"t\")) {\n\t\t\t\tstr = str.substring(0, str.length() - 1).trim();\n\t\t\t\tfromScale = TB_SIZE;\n\t\t\t} else if (str.endsWith(\"bytes\")) {\n\t\t\t\tstr = str.substring(0, str.length() - \"bytes\".length()).trim();\n\t\t\t\tfromScale = BYTE_SIZE;\n\t\t\t}\n\n\t\t\tstr = str.replace(\",\", \"\");\n\n\t\t\tlong value = (long) Double.parseDouble(str);\n\t\t\treturn value * fromScale;\n\t\t} catch (Throwable ex) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tpublic static void clearTerminal() {\n\t\tSystem.out.printf(CLEAR_TERMINAL_ANSI_CMD);\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/util/LongObjectHashMap.java",
    "content": "/*\n * Copyright 2014 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License, version 2.0 (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of the License at:\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.vip.vjtools.vjtop.util;\n\nimport java.util.AbstractCollection;\nimport java.util.AbstractSet;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.Set;\n\n/**\n * 移植Netty 4.1.6的Key为原子类型的集合类, 在数据结构上与HashMap不一样，空间占用与读写性能俱比原来更优.\n * \n * 原子类型集合类有多个实现，选择Netty是因为有在实战中使用.\n * \n * A hash map implementation of {@link LongObjectMap} that uses open addressing for keys. To minimize the memory\n * footprint, this class uses open addressing rather than chaining. Collisions are resolved using linear probing.\n * Deletions implement compaction, so cost of remove can approach O(N) for full maps, which makes a small loadFactor\n * recommended.\n *\n * @param <V> The value type stored in the map.\n */\npublic class LongObjectHashMap<V> implements LongObjectMap<V> {\n\n\t/** Default initial capacity. Used if not specified in the constructor */\n\tpublic static final int DEFAULT_CAPACITY = 8;\n\n\t/** Default load factor. Used if not specified in the constructor */\n\tpublic static final float DEFAULT_LOAD_FACTOR = 0.5f;\n\n\t/**\n\t * Placeholder for null values, so we can use the actual null to mean available. (Better than using a placeholder\n\t * for available: less references for GC processing.)\n\t */\n\tprivate static final Object NULL_VALUE = new Object();\n\n\t/** The maximum number of elements allowed without allocating more space. */\n\tprivate int maxSize;\n\n\t/** The load factor for the map. Used to calculate {@link #maxSize}. */\n\tprivate final float loadFactor;\n\n\tprivate long[] keys;\n\tprivate V[] values;\n\tprivate int size;\n\tprivate int mask;\n\n\tprivate final Set<Long> keySet = new KeySet();\n\tprivate final Set<Entry<Long, V>> entrySet = new EntrySet();\n\tprivate final Iterable<PrimitiveEntry<V>> entries = new Iterable<PrimitiveEntry<V>>() {\n\t\t@Override\n\t\tpublic Iterator<PrimitiveEntry<V>> iterator() {\n\t\t\treturn new PrimitiveIterator();\n\t\t}\n\t};\n\n\tpublic LongObjectHashMap() {\n\t\tthis(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);\n\t}\n\n\tpublic LongObjectHashMap(int initialCapacity) {\n\t\tthis(initialCapacity, DEFAULT_LOAD_FACTOR);\n\t}\n\n\tpublic LongObjectHashMap(int initialCapacity, float loadFactor) {\n\t\tif (loadFactor <= 0.0f || loadFactor > 1.0f) {\n\t\t\t// Cannot exceed 1 because we can never store more than capacity elements;\n\t\t\t// using a bigger loadFactor would trigger rehashing before the desired load is reached.\n\t\t\tthrow new IllegalArgumentException(\"loadFactor must be > 0 and <= 1\");\n\t\t}\n\n\t\tthis.loadFactor = loadFactor;\n\n\t\t// Adjust the initial capacity if necessary.\n\t\tint capacity = safeFindNextPositivePowerOfTwo(initialCapacity);\n\t\tmask = capacity - 1;\n\n\t\t// Allocate the arrays.\n\t\tkeys = new long[capacity];\n\t\t@SuppressWarnings({ \"unchecked\", \"SuspiciousArrayCast\" })\n\t\tV[] temp = (V[]) new Object[capacity];\n\t\tvalues = temp;\n\n\t\t// Initialize the maximum size value.\n\t\tmaxSize = calcMaxSize(capacity);\n\t}\n\n\tprivate static <T> T toExternal(T value) {\n\t\tassert value != null : \"null is not a legitimate internal value. Concurrent Modification?\";\n\t\treturn value == NULL_VALUE ? null : value;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <T> T toInternal(T value) {\n\t\treturn value == null ? (T) NULL_VALUE : value;\n\t}\n\n\t@Override\n\tpublic V get(long key) {\n\t\tint index = indexOf(key);\n\t\treturn index == -1 ? null : toExternal(values[index]);\n\t}\n\n\t@Override\n\tpublic V put(long key, V value) {\n\t\tint startIndex = hashIndex(key);\n\t\tint index = startIndex;\n\n\t\tfor (;;) {\n\t\t\tif (values[index] == null) {\n\t\t\t\t// Found empty slot, use it.\n\t\t\t\tkeys[index] = key;\n\t\t\t\tvalues[index] = toInternal(value);\n\t\t\t\tgrowSize();\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (keys[index] == key) {\n\t\t\t\t// Found existing entry with this key, just replace the value.\n\t\t\t\tV previousValue = values[index];\n\t\t\t\tvalues[index] = toInternal(value);\n\t\t\t\treturn toExternal(previousValue);\n\t\t\t}\n\n\t\t\t// Conflict, keep probing ...\n\t\t\tif ((index = probeNext(index)) == startIndex) {\n\t\t\t\t// Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow.\n\t\t\t\tthrow new IllegalStateException(\"Unable to insert\");\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void putAll(Map<? extends Long, ? extends V> sourceMap) {\n\t\tif (sourceMap instanceof LongObjectHashMap) {\n\t\t\t// Optimization - iterate through the arrays.\n\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\tLongObjectHashMap<V> source = (LongObjectHashMap<V>) sourceMap;\n\t\t\tfor (int i = 0; i < source.values.length; ++i) {\n\t\t\t\tV sourceValue = source.values[i];\n\t\t\t\tif (sourceValue != null) {\n\t\t\t\t\tput(source.keys[i], sourceValue);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Otherwise, just add each entry.\n\t\tfor (Entry<? extends Long, ? extends V> entry : sourceMap.entrySet()) {\n\t\t\tput(entry.getKey(), entry.getValue());\n\t\t}\n\t}\n\n\t@Override\n\tpublic V remove(long key) {\n\t\tint index = indexOf(key);\n\t\tif (index == -1) {\n\t\t\treturn null;\n\t\t}\n\n\t\tV prev = values[index];\n\t\tremoveAt(index);\n\t\treturn toExternal(prev);\n\t}\n\n\t@Override\n\tpublic int size() {\n\t\treturn size;\n\t}\n\n\t@Override\n\tpublic boolean isEmpty() {\n\t\treturn size == 0;\n\t}\n\n\t@Override\n\tpublic void clear() {\n\t\tArrays.fill(keys, 0);\n\t\tArrays.fill(values, null);\n\t\tsize = 0;\n\t}\n\n\t@Override\n\tpublic boolean containsKey(long key) {\n\t\treturn indexOf(key) >= 0;\n\t}\n\n\t@Override\n\tpublic boolean containsValue(Object value) {\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tV v1 = toInternal((V) value);\n\t\tfor (V v2 : values) {\n\t\t\t// The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE).\n\t\t\tif (v2 != null && v2.equals(v1)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic Iterable<PrimitiveEntry<V>> entries() {\n\t\treturn entries;\n\t}\n\n\t@Override\n\tpublic Collection<V> values() {\n\t\treturn new AbstractCollection<V>() {\n\t\t\t@Override\n\t\t\tpublic Iterator<V> iterator() {\n\t\t\t\treturn new Iterator<V>() {\n\t\t\t\t\tfinal PrimitiveIterator iter = new PrimitiveIterator();\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic boolean hasNext() {\n\t\t\t\t\t\treturn iter.hasNext();\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic V next() {\n\t\t\t\t\t\treturn iter.next().value();\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void remove() {\n\t\t\t\t\t\tthrow new UnsupportedOperationException();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic int size() {\n\t\t\t\treturn size;\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\t// Hashcode is based on all non-zero, valid keys. We have to scan the whole keys\n\t\t// array, which may have different lengths for two maps of same size(), so the\n\t\t// capacity cannot be used as input for hashing but the size can.\n\t\tint hash = size;\n\t\tfor (long key : keys) {\n\t\t\t// 0 can be a valid key or unused slot, but won't impact the hashcode in either case.\n\t\t\t// This way we can use a cheap loop without conditionals, or hard-to-unroll operations,\n\t\t\t// or the devastatingly bad memory locality of visiting value objects.\n\t\t\t// Also, it's important to use a hash function that does not depend on the ordering\n\t\t\t// of terms, only their values; since the map is an unordered collection and\n\t\t\t// entries can end up in different positions in different maps that have the same\n\t\t\t// elements, but with different history of puts/removes, due to conflicts.\n\t\t\thash ^= hashCode(key);\n\t\t}\n\t\treturn hash;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object obj) {\n\t\tif (this == obj) {\n\t\t\treturn true;\n\t\t}\n\t\tif (!(obj instanceof LongObjectMap)) {\n\t\t\treturn false;\n\t\t}\n\t\t@SuppressWarnings(\"rawtypes\")\n\t\tLongObjectMap other = (LongObjectMap) obj;\n\t\tif (size != other.size()) {\n\t\t\treturn false;\n\t\t}\n\t\tfor (int i = 0; i < values.length; ++i) {\n\t\t\tV value = values[i];\n\t\t\tif (value != null) {\n\t\t\t\tlong key = keys[i];\n\t\t\t\tObject otherValue = other.get(key);\n\t\t\t\tif (value == NULL_VALUE) {\n\t\t\t\t\tif (otherValue != null) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} else if (!value.equals(otherValue)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean containsKey(Object key) {\n\t\treturn containsKey(objectToKey(key));\n\t}\n\n\t@Override\n\tpublic V get(Object key) {\n\t\treturn get(objectToKey(key));\n\t}\n\n\t@Override\n\tpublic V put(Long key, V value) {\n\t\treturn put(objectToKey(key), value);\n\t}\n\n\t@Override\n\tpublic V remove(Object key) {\n\t\treturn remove(objectToKey(key));\n\t}\n\n\t@Override\n\tpublic Set<Long> keySet() {\n\t\treturn keySet;\n\t}\n\n\t@Override\n\tpublic Set<Entry<Long, V>> entrySet() {\n\t\treturn entrySet;\n\t}\n\n\tprivate long objectToKey(Object key) {\n\t\treturn ((Long) key).longValue();\n\t}\n\n\t/**\n\t * Locates the index for the given key. This method probes using double hashing.\n\t *\n\t * @param key the key for an entry in the map.\n\t * @return the index where the key was found, or {@code -1} if no entry is found for that key.\n\t */\n\tprivate int indexOf(long key) {\n\t\tint startIndex = hashIndex(key);\n\t\tint index = startIndex;\n\n\t\tfor (;;) {\n\t\t\tif (values[index] == null) {\n\t\t\t\t// It's available, so no chance that this value exists anywhere in the map.\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif (key == keys[index]) {\n\t\t\t\treturn index;\n\t\t\t}\n\n\t\t\t// Conflict, keep probing ...\n\t\t\tif ((index = probeNext(index)) == startIndex) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns the hashed index for the given key.\n\t */\n\tprivate int hashIndex(long key) {\n\t\t// The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds.\n\t\treturn hashCode(key) & mask;\n\t}\n\n\t/**\n\t * Returns the hash code for the key.\n\t */\n\tprivate static int hashCode(long key) {\n\t\treturn (int) (key ^ (key >>> 32));\n\t}\n\n\t/**\n\t * Get the next sequential index after {@code index} and wraps if necessary.\n\t */\n\tprivate int probeNext(int index) {\n\t\t// The array lengths are always a power of two, so we can use a bitmask to stay inside the array bounds.\n\t\treturn (index + 1) & mask;\n\t}\n\n\t/**\n\t * Grows the map size after an insertion. If necessary, performs a rehash of the map.\n\t */\n\tprivate void growSize() {\n\t\tsize++;\n\n\t\tif (size > maxSize) {\n\t\t\tif (keys.length == Integer.MAX_VALUE) {\n\t\t\t\tthrow new IllegalStateException(\"Max capacity reached at size=\" + size);\n\t\t\t}\n\n\t\t\t// Double the capacity.\n\t\t\trehash(keys.length << 1);\n\t\t}\n\t}\n\n\t/**\n\t * Removes entry at the given index position. Also performs opportunistic, incremental rehashing if necessary to not\n\t * break conflict chains.\n\t *\n\t * @param index the index position of the element to remove.\n\t * @return {@code true} if the next item was moved back. {@code false} otherwise.\n\t */\n\tprivate boolean removeAt(final int index) {\n\t\t--size;\n\t\t// Clearing the key is not strictly necessary (for GC like in a regular collection),\n\t\t// but recommended for security. The memory location is still fresh in the cache anyway.\n\t\tkeys[index] = 0;\n\t\tvalues[index] = null;\n\n\t\t// In the interval from index to the next available entry, the arrays may have entries\n\t\t// that are displaced from their base position due to prior conflicts. Iterate these\n\t\t// entries and move them back if possible, optimizing future lookups.\n\t\t// Knuth Section 6.4 Algorithm R, also used by the JDK's IdentityHashMap.\n\n\t\tint nextFree = index;\n\t\tint i = probeNext(index);\n\t\tfor (V value = values[i]; value != null; value = values[i = probeNext(i)]) {\n\t\t\tlong key = keys[i];\n\t\t\tint bucket = hashIndex(key);\n\t\t\tif (i < bucket && (bucket <= nextFree || nextFree <= i) || bucket <= nextFree && nextFree <= i) {\n\t\t\t\t// Move the displaced entry \"back\" to the first available position.\n\t\t\t\tkeys[nextFree] = key;\n\t\t\t\tvalues[nextFree] = value;\n\t\t\t\t// Put the first entry after the displaced entry\n\t\t\t\tkeys[i] = 0;\n\t\t\t\tvalues[i] = null;\n\t\t\t\tnextFree = i;\n\t\t\t}\n\t\t}\n\t\treturn nextFree != index;\n\t}\n\n\t/**\n\t * Calculates the maximum size allowed before rehashing.\n\t */\n\tprivate int calcMaxSize(int capacity) {\n\t\t// Clip the upper bound so that there will always be at least one available slot.\n\t\tint upperBound = capacity - 1;\n\t\treturn Math.min(upperBound, (int) (capacity * loadFactor));\n\t}\n\n\t/**\n\t * Rehashes the map for the given capacity.\n\t *\n\t * @param newCapacity the new capacity for the map.\n\t */\n\tprivate void rehash(int newCapacity) {\n\t\tlong[] oldKeys = keys;\n\t\tV[] oldVals = values;\n\n\t\tkeys = new long[newCapacity];\n\t\t@SuppressWarnings({ \"unchecked\", \"SuspiciousArrayCast\" })\n\t\tV[] temp = (V[]) new Object[newCapacity];\n\t\tvalues = temp;\n\n\t\tmaxSize = calcMaxSize(newCapacity);\n\t\tmask = newCapacity - 1;\n\n\t\t// Insert to the new arrays.\n\t\tfor (int i = 0; i < oldVals.length; ++i) {\n\t\t\tV oldVal = oldVals[i];\n\t\t\tif (oldVal != null) {\n\t\t\t\t// Inlined put(), but much simpler: we don't need to worry about\n\t\t\t\t// duplicated keys, growing/rehashing, or failing to insert.\n\t\t\t\tlong oldKey = oldKeys[i];\n\t\t\t\tint index = hashIndex(oldKey);\n\n\t\t\t\tfor (;;) {\n\t\t\t\t\tif (values[index] == null) {\n\t\t\t\t\t\tkeys[index] = oldKey;\n\t\t\t\t\t\tvalues[index] = oldVal;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Conflict, keep probing. Can wrap around, but never reaches startIndex again.\n\t\t\t\t\tindex = probeNext(index);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tif (isEmpty()) {\n\t\t\treturn \"{}\";\n\t\t}\n\t\tStringBuilder sb = new StringBuilder(4 * size);\n\t\tsb.append('{');\n\t\tboolean first = true;\n\t\tfor (int i = 0; i < values.length; ++i) {\n\t\t\tV value = values[i];\n\t\t\tif (value != null) {\n\t\t\t\tif (!first) {\n\t\t\t\t\tsb.append(\", \");\n\t\t\t\t}\n\t\t\t\tsb.append(keyToString(keys[i])).append('=').append(value == this ? \"(this Map)\" : toExternal(value));\n\t\t\t\tfirst = false;\n\t\t\t}\n\t\t}\n\t\treturn sb.append('}').toString();\n\t}\n\n\t/**\n\t * Helper method called by {@link #toString()} in order to convert a single map key into a string. This is protected\n\t * to allow subclasses to override the appearance of a given key.\n\t */\n\tprotected String keyToString(long key) {\n\t\treturn Long.toString(key);\n\t}\n\n\t/**\n\t * Set implementation for iterating over the entries of the map.\n\t */\n\tprivate final class EntrySet extends AbstractSet<Entry<Long, V>> {\n\t\t@Override\n\t\tpublic Iterator<Entry<Long, V>> iterator() {\n\t\t\treturn new MapIterator();\n\t\t}\n\n\t\t@Override\n\t\tpublic int size() {\n\t\t\treturn LongObjectHashMap.this.size();\n\t\t}\n\t}\n\n\t/**\n\t * Set implementation for iterating over the keys.\n\t */\n\tprivate final class KeySet extends AbstractSet<Long> {\n\t\t@Override\n\t\tpublic int size() {\n\t\t\treturn LongObjectHashMap.this.size();\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean contains(Object o) {\n\t\t\treturn LongObjectHashMap.this.containsKey(o);\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean remove(Object o) {\n\t\t\treturn LongObjectHashMap.this.remove(o) != null;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean retainAll(Collection<?> retainedKeys) {\n\t\t\tboolean changed = false;\n\t\t\tfor (Iterator<PrimitiveEntry<V>> iter = entries().iterator(); iter.hasNext();) {\n\t\t\t\tPrimitiveEntry<V> entry = iter.next();\n\t\t\t\tif (!retainedKeys.contains(entry.key())) {\n\t\t\t\t\tchanged = true;\n\t\t\t\t\titer.remove();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn changed;\n\t\t}\n\n\t\t@Override\n\t\tpublic void clear() {\n\t\t\tLongObjectHashMap.this.clear();\n\t\t}\n\n\t\t@Override\n\t\tpublic Iterator<Long> iterator() {\n\t\t\treturn new Iterator<Long>() {\n\t\t\t\tprivate final Iterator<Entry<Long, V>> iter = entrySet.iterator();\n\n\t\t\t\t@Override\n\t\t\t\tpublic boolean hasNext() {\n\t\t\t\t\treturn iter.hasNext();\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic Long next() {\n\t\t\t\t\treturn iter.next().getKey();\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic void remove() {\n\t\t\t\t\titer.remove();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Iterator over primitive entries. Entry key/values are overwritten by each call to {@link #next()}.\n\t */\n\tprivate final class PrimitiveIterator implements Iterator<PrimitiveEntry<V>>, PrimitiveEntry<V> {\n\t\tprivate int prevIndex = -1;\n\t\tprivate int nextIndex = -1;\n\t\tprivate int entryIndex = -1;\n\n\t\tprivate void scanNext() {\n\t\t\twhile (++nextIndex != values.length && values[nextIndex] == null) {\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean hasNext() {\n\t\t\tif (nextIndex == -1) {\n\t\t\t\tscanNext();\n\t\t\t}\n\t\t\treturn nextIndex != values.length;\n\t\t}\n\n\t\t@Override\n\t\tpublic PrimitiveEntry<V> next() {\n\t\t\tif (!hasNext()) {\n\t\t\t\tthrow new NoSuchElementException();\n\t\t\t}\n\n\t\t\tprevIndex = nextIndex;\n\t\t\tscanNext();\n\n\t\t\t// Always return the same Entry object, just change its index each time.\n\t\t\tentryIndex = prevIndex;\n\t\t\treturn this;\n\t\t}\n\n\t\t@Override\n\t\tpublic void remove() {\n\t\t\tif (prevIndex == -1) {\n\t\t\t\tthrow new IllegalStateException(\"next must be called before each remove.\");\n\t\t\t}\n\t\t\tif (removeAt(prevIndex)) {\n\t\t\t\t// removeAt may move elements \"back\" in the array if they have been displaced because their spot in the\n\t\t\t\t// array was occupied when they were inserted. If this occurs then the nextIndex is now invalid and\n\t\t\t\t// should instead point to the prevIndex which now holds an element which was \"moved back\".\n\t\t\t\tnextIndex = prevIndex;\n\t\t\t}\n\t\t\tprevIndex = -1;\n\t\t}\n\n\t\t// Entry implementation. Since this implementation uses a single Entry, we coalesce that\n\t\t// into the Iterator object (potentially making loop optimization much easier).\n\n\t\t@Override\n\t\tpublic long key() {\n\t\t\treturn keys[entryIndex];\n\t\t}\n\n\t\t@Override\n\t\tpublic V value() {\n\t\t\treturn toExternal(values[entryIndex]);\n\t\t}\n\n\t\t@Override\n\t\tpublic void setValue(V value) {\n\t\t\tvalues[entryIndex] = toInternal(value);\n\t\t}\n\t}\n\n\t/**\n\t * Iterator used by the {@link Map} interface.\n\t */\n\tprivate final class MapIterator implements Iterator<Entry<Long, V>> {\n\t\tprivate final PrimitiveIterator iter = new PrimitiveIterator();\n\n\t\t@Override\n\t\tpublic boolean hasNext() {\n\t\t\treturn iter.hasNext();\n\t\t}\n\n\t\t@Override\n\t\tpublic Entry<Long, V> next() {\n\t\t\tif (!hasNext()) {\n\t\t\t\tthrow new NoSuchElementException();\n\t\t\t}\n\n\t\t\titer.next();\n\n\t\t\treturn new MapEntry(iter.entryIndex);\n\t\t}\n\n\t\t@Override\n\t\tpublic void remove() {\n\t\t\titer.remove();\n\t\t}\n\t}\n\n\t/**\n\t * A single entry in the map.\n\t */\n\tfinal class MapEntry implements Entry<Long, V> {\n\t\tprivate final int entryIndex;\n\n\t\tMapEntry(int entryIndex) {\n\t\t\tthis.entryIndex = entryIndex;\n\t\t}\n\n\t\t@Override\n\t\tpublic Long getKey() {\n\t\t\tverifyExists();\n\t\t\treturn keys[entryIndex];\n\t\t}\n\n\t\t@Override\n\t\tpublic V getValue() {\n\t\t\tverifyExists();\n\t\t\treturn toExternal(values[entryIndex]);\n\t\t}\n\n\t\t@Override\n\t\tpublic V setValue(V value) {\n\t\t\tverifyExists();\n\t\t\tV prevValue = toExternal(values[entryIndex]);\n\t\t\tvalues[entryIndex] = toInternal(value);\n\t\t\treturn prevValue;\n\t\t}\n\n\t\tprivate void verifyExists() {\n\t\t\tif (values[entryIndex] == null) {\n\t\t\t\tthrow new IllegalStateException(\"The map entry has been removed\");\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static int safeFindNextPositivePowerOfTwo(final int value) {\n\t\treturn value <= 0 ? 1 : value >= 0x40000000 ? 0x40000000 : findNextPositivePowerOfTwo(value);\n\t}\n\n\tpublic static int findNextPositivePowerOfTwo(final int value) {\n\t\tassert value > Integer.MIN_VALUE && value < 0x40000000;\n\t\treturn 1 << (32 - Integer.numberOfLeadingZeros(value - 1));\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/util/LongObjectMap.java",
    "content": "/*\n * Copyright 2014 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License, version 2.0 (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of the License at:\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.vip.vjtools.vjtop.util;\n\nimport java.util.Map;\n\n/**\n * Interface for a primitive map that uses {@code long}s as keys.\n *\n * @param <V> the value type stored in the map.\n */\npublic interface LongObjectMap<V> extends Map<Long, V> {\n\n\t/**\n\t * A primitive entry in the map, provided by the iterator from {@link #entries()}\n\t *\n\t * @param <V> the value type stored in the map.\n\t */\n\tinterface PrimitiveEntry<V> {\n\t\t/**\n\t\t * Gets the key for this entry.\n\t\t */\n\t\tlong key();\n\n\t\t/**\n\t\t * Gets the value for this entry.\n\t\t */\n\t\tV value();\n\n\t\t/**\n\t\t * Sets the value for this entry.\n\t\t */\n\t\tvoid setValue(V value);\n\t}\n\n\t/**\n\t * Gets the value in the map with the specified key.\n\t *\n\t * @param key the key whose associated value is to be returned.\n\t * @return the value or {@code null} if the key was not found in the map.\n\t */\n\tV get(long key);\n\n\t/**\n\t * Puts the given entry into the map.\n\t *\n\t * @param key the key of the entry.\n\t * @param value the value of the entry.\n\t * @return the previous value for this key or {@code null} if there was no previous mapping.\n\t */\n\tV put(long key, V value);\n\n\t/**\n\t * Removes the entry with the specified key.\n\t *\n\t * @param key the key for the entry to be removed from this map.\n\t * @return the previous value for the key, or {@code null} if there was no mapping.\n\t */\n\tV remove(long key);\n\n\t/**\n\t * Gets an iterable to traverse over the primitive entries contained in this map. As an optimization, the\n\t * {@link PrimitiveEntry}s returned by the {@link Iterator} may change as the {@link Iterator} progresses. The\n\t * caller should not rely on {@link PrimitiveEntry} key/value stability.\n\t */\n\tIterable<PrimitiveEntry<V>> entries();\n\n\t/**\n\t * Indicates whether or not this map contains a value for the specified key.\n\t */\n\tboolean containsKey(long key);\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/util/OptionAdvanceParser.java",
    "content": "package com.vip.vjtools.vjtop.util;\n\nimport java.util.Arrays;\n\nimport com.vip.vjtools.vjtop.VJTop;\nimport com.vip.vjtools.vjtop.VMDetailView.ContentMode;\nimport com.vip.vjtools.vjtop.VMDetailView.OutputFormat;\nimport com.vip.vjtools.vjtop.VMDetailView.ThreadInfoMode;\n\nimport joptsimple.OptionParser;\nimport joptsimple.OptionSet;\n\npublic class OptionAdvanceParser {\n\n\tprivate static final int DEFAULT_INTERVAL = 10;\n\n\tpublic static String parsePid(OptionParser parser, OptionSet optionSet) {\n\t\tInteger pid = null;\n\n\t\t// to support PID as non option argument\n\t\tif (optionSet.nonOptionArguments().size() > 0) {\n\t\t\tpid = Integer.valueOf((String) optionSet.nonOptionArguments().get(0));\n\t\t}\n\n\t\tif (pid == null){\n\t\t\tpid = SelectPid.getPidFromJpsList();\n\t\t}\n\n\t\tif (pid == null) {\n\t\t\tSystem.out.println(\"PID can't be empty !!!\");\n\t\t\tVJTop.printHelper(parser);\n\t\t\tSystem.exit(0);\n\t\t}\n\n\t\treturn String.valueOf(pid);\n\t}\n\n\tpublic static OutputFormat parseOutputFormat(OptionSet optionSet) {\n\t\tOutputFormat outputFormat = OutputFormat.console;\n\t\tif (optionSet.hasArgument(\"output\")) {\n\t\t\tString format = (String) optionSet.valueOf(\"output\");\n\t\t\tif (format.equals(\"clean\")) {\n\t\t\t\toutputFormat = OutputFormat.cleanConsole;\n\t\t\t} else if (format.equals(\"text\")) {\n\t\t\t\toutputFormat = OutputFormat.text;\n\t\t\t}\n\t\t}\n\n\t\treturn outputFormat;\n\t}\n\n\n\tpublic static ContentMode parseContentMode(OptionSet optionSet) {\n\t\tContentMode contentMode = ContentMode.all;\n\t\tif (optionSet.hasArgument(\"content\")) {\n\t\t\tString format = (String) optionSet.valueOf(\"content\");\n\t\t\tif (format.equals(\"jvm\")) {\n\t\t\t\tcontentMode = ContentMode.jvm;\n\t\t\t} else if (format.equals(\"thread\")) {\n\t\t\t\tcontentMode = ContentMode.thread;\n\t\t\t}\n\t\t}\n\n\t\treturn contentMode;\n\t}\n\n\tpublic static ThreadInfoMode parseThreadInfoMode(OptionSet optionSet) {\n\t\tThreadInfoMode threadInfoMode = ThreadInfoMode.cpu;\n\t\tif (optionSet.hasArgument(\"mode\")) {\n\t\t\tString mode = (String) optionSet.valueOf(\"mode\");\n\t\t\tthreadInfoMode = ThreadInfoMode.parse(mode);\n\t\t}\n\t\treturn threadInfoMode;\n\t}\n\n\tpublic static OptionParser createOptionParser() {\n\t\tOptionParser parser = new OptionParser();\n\t\t// commmon\n\t\tparser.acceptsAll(Arrays.asList(\"help\", \"?\", \"h\"), \"shows this help\").forHelp();\n\t\tparser.acceptsAll(Arrays.asList(\"n\", \"iteration\"),\n\t\t\t\t\"vjtop will exit after n output iterations  (defaults to unlimit)\").withRequiredArg()\n\t\t\t\t.ofType(Integer.class);\n\t\tparser.acceptsAll(Arrays.asList(\"i\", \"interval\", \"d\"),\n\t\t\t\t\"interval between each output iteration (defaults to 10s)\").withRequiredArg().ofType(Integer.class);\n\t\tparser.acceptsAll(Arrays.asList(\"w\", \"width\"), \"Number of columns for the console display (defaults to 100)\")\n\t\t\t\t.withRequiredArg().ofType(Integer.class);\n\t\tparser.acceptsAll(Arrays.asList(\"l\", \"limit\"), \"Number of threads to display ( default to 10 threads)\")\n\t\t\t\t.withRequiredArg().ofType(Integer.class);\n\t\tparser.acceptsAll(Arrays.asList(\"f\", \"filter\"), \"Thread name filter ( no default)\").withRequiredArg()\n\t\t\t\t.ofType(String.class);\n\n\t\tparser.acceptsAll(Arrays.asList(\"j\", \"jmxurl\"), \"give JMX url like 127.0.0.1:7001 when VM attach doesn't work\")\n\t\t\t\t.withRequiredArg().ofType(String.class);\n\n\t\t// detail mode\n\t\tparser.acceptsAll(Arrays.asList(\"m\", \"mode\"),\n\t\t\t\t\"number of thread display mode: \\n\"\n\t\t\t\t\t\t+ \" cpu(default): display thread cpu usage and sort by its delta cpu time\\n\"\n\t\t\t\t\t\t+ \" syscpu: display thread cpu usage and sort by delta syscpu time\\n\"\n\t\t\t\t\t\t+ \" totalcpu: display thread cpu usage and sort by total cpu time\\n\"\n\t\t\t\t\t\t+ \" totalsyscpu: display thread cpu usage and sort by total syscpu time\\n\"\n\t\t\t\t\t\t+ \" memory: display thread memory allocated and sort by delta\\n\"\n\t\t\t\t\t\t+ \" totalmemory: display thread memory allocated and sort by total\")\n\t\t\t\t.withRequiredArg().ofType(String.class);\n\n\t\tparser.acceptsAll(Arrays.asList(\"o\", \"output\"),\n\t\t\t\t\"output format: \\n\" + \" console(default): console with warning and flush ansi code\\n\"\n\t\t\t\t\t\t+ \" clean: console without warning and flush ansi code\\n\"\n\t\t\t\t\t\t+ \" text: plain text like /proc/status for 3rd tools\\n\")\n\t\t\t\t.withRequiredArg().ofType(String.class);\n\n\t\tparser.acceptsAll(Arrays.asList(\"c\", \"content\"),\n\t\t\t\t\"output content: \\n\"\n\t\t\t\t\t\t+ \" all(default): jvm info and theads info\\n jvm: only jvm info\\n thread: only thread info\\n\")\n\t\t\t\t.withRequiredArg().ofType(String.class);\n\n\t\treturn parser;\n\t}\n\n\tpublic static Integer parseInterval(OptionSet optionSet) {\n\t\tInteger interval = OptionAdvanceParser.DEFAULT_INTERVAL;\n\t\tif (optionSet.hasArgument(\"interval\")) {\n\t\t\tinterval = (Integer) (optionSet.valueOf(\"interval\"));\n\t\t\tif (interval < 1) {\n\t\t\t\tthrow new IllegalArgumentException(\"Interval cannot be set below 1.0\");\n\t\t\t}\n\t\t}\n\t\treturn interval;\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/util/SelectPid.java",
    "content": "package com.vip.vjtools.vjtop.util;\n\nimport java.io.BufferedReader;\nimport java.io.Console;\nimport java.io.InputStreamReader;\n\n/**\n * Created by traburiss on 2019/12/11.\n * describe:\n */\npublic class SelectPid {\n\n\tpublic static Integer getPidFromJpsList(){\n\n\t\tInteger pid = null;\n\t\tProcess process = null;\n\t\tBufferedReader reader = null;\n\t\ttry {\n\n\t\t\tConsole console = System.console();\n\t\t\tprocess = Runtime.getRuntime().exec(\"jps -l\");\n\t\t\treader = new BufferedReader(new InputStreamReader(process.getInputStream()));\n\t\t\tString defPidStr = \"\";\n\t\t\tString line;\n\t\t\tSystem.out.println(\"please input a pid from list:\");\n\t\t\tSystem.out.println(\"PID\\tNAME\");\n\t\t\twhile ((line = reader.readLine()) != null) {\n\t\t\t\t//不显示jps和自己\n\t\t\t\tif (!line.contains(\"sun.tools.jps.Jps\") && !line.contains(\"com.vip.vjtools.vjtop.VJTop\")){\n\t\t\t\t\tString[] pm = line.split(\" \",2);\n\t\t\t\t\tif (defPidStr.isEmpty()){\n\t\t\t\t\t\tdefPidStr = pm[0];\n\t\t\t\t\t}\n\t\t\t\t\tSystem.out.println(pm[0] + \"\\t\" + pm[1]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tString pidString = console.readLine(\"\\nplease input pid(default is \" + defPidStr + \"):\");\n\t\t\tif (pidString == null || pidString.isEmpty()){\n\t\t\t\tpidString = defPidStr;\n\t\t\t}\n\t\t\tpid = Integer.valueOf(pidString);\n\t\t}catch (Exception ignored){\n\t\t}finally {\n\n\t\t\ttry {\n\t\t\t\tif (reader != null) {\n\t\t\t\t\treader.close();\n\t\t\t\t}\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tif (process != null) {\n\t\t\t\t\tprocess.destroy();\n\t\t\t\t}\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t\treturn pid;\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/main/java/com/vip/vjtools/vjtop/util/Utils.java",
    "content": "package com.vip.vjtools.vjtop.util;\n\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class Utils {\n\n\tpublic static long NANOS_TO_MILLS = 1000 * 1000;\n\n\t/**\n\t * Sorts a Map by its values, using natural ordering.\n\t */\n\tpublic static long[] sortAndFilterThreadIdsByValue(LongObjectMap map, int threadLimit) {\n\t\tint max = Math.min(threadLimit, map.size());\n\t\tList<Map.Entry> list = new LinkedList(map.entrySet());\n\t\tCollections.sort(list, new Comparator() {\n\t\t\t@Override\n\t\t\tpublic int compare(Object o1, Object o2) {\n\t\t\t\treturn ((Comparable) ((Map.Entry) (o2)).getValue()).compareTo(((Map.Entry) (o1)).getValue());\n\t\t\t}\n\t\t});\n\n\t\tlong[] topTidArray = new long[max];\n\t\tint i = 0;\n\t\tfor (Map.Entry entry : list) {\n\t\t\ttopTidArray[i] = (Long) entry.getKey();\n\t\t\tif (++i >= max) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn topTidArray;\n\t}\n\n\t/**\n\t * calculates a \"load\", given on two deltas\n\t */\n\tpublic static double calcLoad(long deltaCpuTime, long deltaUptime) {\n\t\tif (deltaCpuTime <= 0 || deltaUptime == 0) {\n\t\t\treturn 0.0;\n\t\t}\n\t\treturn deltaCpuTime * 100d / deltaUptime;\n\t}\n\n\t/**\n\t * calculates a \"load\", given on two deltas\n\t */\n\tpublic static double calcLoad(Long deltaCpuTime, long deltaUptime, long factor) {\n\t\tif (deltaCpuTime == null || deltaCpuTime <= 0 || deltaUptime == 0) {\n\t\t\treturn 0.0;\n\t\t}\n\t\treturn deltaCpuTime * 100d / factor / deltaUptime;\n\t}\n\n\tpublic static double calcMemoryUtilization(Long threadBytes, long totalBytes) {\n\t\tif (threadBytes == null || totalBytes == 0) {\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn (threadBytes * 100d) / totalBytes;// 这里因为最后单位是百分比%，所以bytes除以totalBytes以后要乘以100，才可以再加上单位%\n\t}\n\n\n\tpublic static void sleep(long mills) {\n\t\ttry {\n\t\t\tThread.sleep(mills);\n\t\t} catch (InterruptedException e) {\n\t\t}\n\t}\n\n\tpublic static int getJavaMajorVersion(String javaSpecificationVersion) {\n\t\tif (javaSpecificationVersion.startsWith(\"1.8\")) {\n\t\t\treturn 8;\n\t\t} else if (javaSpecificationVersion.startsWith(\"1.7\")) {\n\t\t\treturn 7;\n\t\t} else if (javaSpecificationVersion.startsWith(\"1.6\")) {\n\t\t\treturn 6;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\treturn Integer.parseInt(javaSpecificationVersion);\n\t\t\t} catch (NumberFormatException e) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vjtop/src/test/java/com/vip/vjtools/vjtop/util/FormatsTest.java",
    "content": "package com.vip.vjtools.vjtop.util;\n\nimport com.vip.vjtools.vjtop.WarningRule.LongWarning;\nimport org.junit.Assert;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\nimport java.util.ArrayList;\n\npublic class FormatsTest {\n\n    @Rule public ExpectedException thrown = ExpectedException.none();\n\n    @Test\n    public void joinInput0NotNullOutputNotNull() {\n\n        // Arrange\n        final ArrayList<String> list = new ArrayList<String>();\n        final String delim = \"AAAAAAAA\";\n\n        // Act\n        final String retval = Formats.join(list, delim);\n\n        // Assert result\n        Assert.assertEquals(\"\", retval);\n    }\n\n    @Test\n    public void joinInput1NullOutputNotNull() {\n\n        // Arrange\n        final ArrayList<String> list = new ArrayList<String>();\n        list.add(\"\");\n        final String delim = null;\n\n        // Act\n        final String retval = Formats.join(list, delim);\n\n        // Assert result\n        Assert.assertEquals(\"\", retval);\n    }\n\n    @Test\n    public void leftStrInputNotNullNegativeOutputStringIndexOutOfBoundsException() {\n\n        // Arrange\n        final String str = \"!\";\n        final int length = -536_870_911;\n\n        // Act\n        thrown.expect(StringIndexOutOfBoundsException.class);\n        Formats.leftStr(str, length);\n\n        // Method is not expected to return due to exception thrown\n    }\n\n    @Test\n    public void leftStrInputNotNullZeroOutputNotNull() {\n\n        // Arrange\n        final String str = \"!!!!!!!!\";\n        final int length = 0;\n\n        // Act\n        final String retval = Formats.leftStr(str, length);\n\n        // Assert result\n        Assert.assertEquals(\"\", retval);\n    }\n\n    @Test\n    public void rightStrInputNotNullNegativeOutputStringIndexOutOfBoundsException() {\n\n        // Arrange\n        final String str = \"!!!!!!!!\";\n        final int length = -1_048_568;\n\n        // Act\n        thrown.expect(StringIndexOutOfBoundsException.class);\n        Formats.rightStr(str, length);\n\n        // Method is not expected to return due to exception thrown\n    }\n\n    @Test\n    public void rightStrInputNotNullPositiveOutputNotNull() {\n\n        // Arrange\n        final String str = \"!!!!!!!!\";\n        final int length = 2_147_221_512;\n\n        // Act\n        final String retval = Formats.rightStr(str, length);\n\n        // Assert result\n        Assert.assertEquals(\"!!!!!!!!\", retval);\n    }\n\n    @Test\n    public void shortNameInputNotNullPositivePositiveOutputNotNull() {\n\n        // Arrange\n        final String str = \"!\";\n        final int length = 1;\n        final int rightLength = 6;\n\n        // Act\n        final String retval = Formats.shortName(str, length, rightLength);\n\n        // Assert result\n        Assert.assertEquals(\"!\", retval);\n    }\n\n    @Test\n    public void shortNameInputNotNullPositiveZeroOutputNotNull() {\n\n        // Arrange\n        final String str = \"!!!!!!!!!!\";\n        final int length = 4;\n        final int rightLength = 0;\n\n        // Act\n        final String retval = Formats.shortName(str, length, rightLength);\n\n        // Assert result\n        Assert.assertEquals(\"!...\", retval);\n    }\n\n    @Test\n    public void shortNameInputNotNullZeroNegativeOutputStringIndexOutOfBoundsException() {\n\n        // Arrange\n        final String str = \"!!!!!!!!!!\";\n        final int length = 0;\n        final int rightLength = -19;\n\n        // Act\n        thrown.expect(StringIndexOutOfBoundsException.class);\n        Formats.shortName(str, length, rightLength);\n\n        // Method is not expected to return due to exception thrown\n    }\n\n    @Test\n    public void toFixLengthSizeUnitInputNullOutputNotNull() {\n\n        // Arrange\n        final Long size = null;\n\n        // Act\n        final String retval = Formats.toFixLengthSizeUnit(size);\n\n        // Assert result\n        Assert.assertEquals(\"NaN\", retval);\n    }\n\n    @Test\n    public void toFixLengthSizeUnitInputPositiveOutputNotNull() {\n\n        // Arrange\n        final Long size = 4_611_686_018_427_387_906L;\n\n        // Act\n        final String retval = Formats.toFixLengthSizeUnit(size);\n\n        // Assert result\n        Assert.assertEquals(\"4194304t\", retval);\n    }\n\n    @Test\n    public void toMBInputNegativeOutputNotNull() {\n\n        // Arrange\n        final long bytes = -8L;\n\n        // Act\n        final String retval = Formats.toMB(bytes);\n\n        // Assert result\n        Assert.assertEquals(\"NaN\", retval);\n    }\n\n    @Test\n    public void toMBInputPositiveOutputNotNull() {\n\n        // Arrange\n        final long bytes = 8L;\n\n        // Act\n        final String retval = Formats.toMB(bytes);\n\n        // Assert result\n        Assert.assertEquals(\"0m\", retval);\n    }\n\n    @Test\n    public void toSizeUnitInputNegativeOutputNotNull() {\n\n        // Arrange\n        final Long size = -1023L;\n\n        // Act\n        final String retval = Formats.toSizeUnit(size);\n\n        // Assert result\n        Assert.assertEquals(\"-1023\", retval);\n    }\n\n    @Test\n    public void toSizeUnitInputNullOutputNotNull() {\n\n        // Arrange\n        final Long size = null;\n\n        // Act\n        final String retval = Formats.toSizeUnit(size);\n\n        // Assert result\n        Assert.assertEquals(\"NaN\", retval);\n    }\n\n    @Test\n    public void toSizeUnitInputPositiveOutputNotNull() {\n\n        // Arrange\n        final Long size = 45_312L;\n\n        // Act\n        final String retval = Formats.toSizeUnit(size);\n\n        // Assert result\n        Assert.assertEquals(\"44k\", retval);\n    }\n\n    @Test\n    public void toSizeUnitWithColorInputNullNullOutputNullPointerException() {\n\n        // Arrange\n        final Long size = null;\n        final LongWarning warning = null;\n\n        // Act\n        thrown.expect(NullPointerException.class);\n        Formats.toSizeUnitWithColor(size, warning);\n\n        // Method is not expected to return due to exception thrown\n    }\n\n}\n"
  },
  {
    "path": "vjtop/src/test/java/com/vip/vjtools/vjtop/util/UtilsTest.java",
    "content": "package com.vip.vjtools.vjtop.util;\n\nimport com.vip.vjtools.vjtop.util.Utils;\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class UtilsTest {\n\n    @Test\n    public void calcLoadInputNegativeZeroZeroOutputZero() {\n\n        // Arrange\n        final Long deltaCpuTime = -4_194_304L;\n        final long deltaUptime = 0L;\n        final long factor = 0L;\n\n        // Act\n        final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime, factor);\n\n        // Assert result\n        Assert.assertEquals(0.0, retval, 0.0);\n    }\n\n    @Test\n    public void calcLoadInputPositivePositiveOutputPositive() {\n\n        // Arrange\n        final long deltaCpuTime = 558_348_370L;\n        final long deltaUptime = 1L;\n\n        // Act\n        final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime);\n\n        // Assert result\n        Assert.assertEquals(0x1.a0008001p+35 /* 5.58348e+10 */, retval, 0.0);\n    }\n\n    @Test\n    public void calcLoadInputPositivePositiveZeroOutputPositiveInfinity() {\n\n        // Arrange\n        final Long deltaCpuTime = 4_194_304L;\n        final long deltaUptime = 4_611_686_018_427_387_904L;\n        final long factor = 0L;\n\n        // Act\n        final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime, factor);\n\n        // Assert result\n        Assert.assertEquals(Double.POSITIVE_INFINITY, retval, 0.0);\n    }\n\n    @Test\n    public void calcLoadInputPositiveZeroOutputZero() {\n\n        // Arrange\n        final long deltaCpuTime = 558_348_370L;\n        final long deltaUptime = 0L;\n\n        // Act\n        final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime);\n\n        // Assert result\n        Assert.assertEquals(0.0, retval, 0.0);\n    }\n\n    @Test\n    public void calcLoadInputPositiveZeroZeroOutputZero() {\n\n        // Arrange\n        final Long deltaCpuTime = 4_194_304L;\n        final long deltaUptime = 0L;\n        final long factor = 0L;\n\n        // Act\n        final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime, factor);\n\n        // Assert result\n        Assert.assertEquals(0.0, retval, 0.0);\n    }\n\n    @Test\n    public void calcLoadInputZeroZeroOutputZero() {\n\n        // Arrange\n        final long deltaCpuTime = 0L;\n        final long deltaUptime = 0L;\n\n        // Act\n        final double retval = Utils.calcLoad(deltaCpuTime, deltaUptime);\n\n        // Assert result\n        Assert.assertEquals(0.0, retval, 0.0);\n    }\n\n    @Test\n    public void calcMemoryUtilizationInputNegativePositiveOutputNegative() {\n\n        // Arrange\n        final Long threadBytes = -507_680_768_073_012_547L;\n        final long totalBytes = 1_147_301_170_758_571_992L;\n\n        // Act\n        final double retval = Utils.calcMemoryUtilization(threadBytes, totalBytes);\n\n        // Assert result\n        Assert.assertEquals(-0x1.6200000024f82p+5 /* -44.25 */, retval, 0.0);\n    }\n\n    @Test\n    public void calcMemoryUtilizationInputPositiveZeroOutputZero() {\n\n        // Arrange\n        final Long threadBytes = 1L;\n        final long totalBytes = 0L;\n\n        // Act\n        final double retval = Utils.calcMemoryUtilization(threadBytes, totalBytes);\n\n        // Assert result\n        Assert.assertEquals(0.0, retval, 0.0);\n    }\n\n}\n"
  }
]