[
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\ncharset = utf-8\nend_of_line = lf\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.{json,yml,yaml,xml}]\nindent_size = 2\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/workflows/build-with-maven.yml",
    "content": "# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time\n# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven\n\nname: build-with-maven\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v3\n    - name: Set up JDK 8\n      uses: actions/setup-java@v3\n      with:\n        java-version: '8'\n        distribution: 'temurin'\n        cache: maven\n    - name: Build with Maven\n      run: ./mvnw clean package -DskipTests -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true -B -V -U --file pom.xml\n"
  },
  {
    "path": ".gitignore",
    "content": "# Eclipse\n/**/.classpath\n/**/.project\n/**/.settings/\n/**/.myeclipse/\n\n# Intellij\n/**/.idea/\n/**/*.iml\n/**/*.iws\n/**/*.ipr\n\n# NetBeans\nnbproject/private/\nnbbuild/\nnbdist/\n/**/.nb-gradle/\n\n# Mac\n/**/.DS_Store\n\n# Maven\n/**/target/\n\n# Gradle\n/**/.gradle/\n\n# SBT: Simple Build Tool. Is a build tool for Scala, Java, and more.\ndist/*\ntarget/\nlib_managed/\nsrc_managed/\nproject/boot/\nproject/plugins/project/\n.history\n.cache\n.lib/\n\n# STS: Spring Tool Suit\n/**/.apt_generated\n/**/.factorypath\n/**/.springBeans\n\n# Webapp\n/**/src/main/webapp/WEB-INF/classes/\n\n# Others\n/**/.svn/\n/**/.externalToolBuilders/\n/**/.recommenders/\n/**/.metadata/\n/**/node_modules/\nout/\nbuild/\nbin/\nlogs/\ndist/\n\n# rebel.xml\n/**/rebel.xml\n/**/.cache-main\n/**/.cache-tests\n/**/dependency-reduced-pom.xml\n"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\ndistributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\n"
  },
  {
    "path": "LICENSE",
    "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 (c) 2017-2023 Ponfee\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": "[![Blog](https://img.shields.io/badge/blog-@Ponfee-informational.svg?logo=Pelican)](http://www.ponfee.cn)\n[![License](https://img.shields.io/badge/license-Apache--2.0-green.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)\n[![JDK](https://img.shields.io/badge/jdk-8+-green.svg)](https://www.oracle.com/java/technologies/downloads/#java8)\n[![Build status](https://github.com/ponfee/commons-core/workflows/build-with-maven/badge.svg)](https://github.com/ponfee/commons-core/actions)\n[![Maven Central](https://img.shields.io/badge/maven--central-1.4-orange.svg?style=plastic&logo=apachemaven)](https://central.sonatype.com/artifact/cn.ponfee/commons-core/1.4)\n\n# Commons Core\n\nA commons java tool lib\n\n## ⬇️ [Download From Maven Central](https://central.sonatype.com/artifact/cn.ponfee/commons-core/1.4)\n\n```xml\n<dependency>\n  <groupId>cn.ponfee</groupId>\n  <artifactId>commons-core</artifactId>\n  <version>1.4</version>\n</dependency>\n```\n\n## 🔄 Build From Source\n\n```bash\n./mvnw clean package -DskipTests -Dcheckstyle.skip=true -U\n```\n\n## 🛠️ Functions\n| **function** |                                           **description**                                                |\n| ------------ | -------------------------------------------------------------------------------------------------------- |\n| base         | 基础类：Tuple数据类型、原始与包装类型等                                                                        |\n| collect      | 集合工具类                                                                                                 |\n| concurrent   | 并发相关的工具类：异步批处理、延时消费、线程池创建与监控等                                                         |\n| constrain    | 方法参数、实体字段等数据校验                                                                                  |\n| data         | 多数据源组件，动态增加数据源                                                                                  |\n| date         | 时间工具类(支持各种时间格式的解析，时间周期处理)                                                                 |\n| exception    | 异常工具类                                                                                                 |\n| export       | 数据导出为Excel(支持复杂表头及切分多个文件)、HTML(支持复杂表头)、CSV(支持切分多个文件)、Console(类似SQL命令行查询结果)  |\n| extract      | 数据文件导入：支持XLS/XLSX/CSV格式的文件，支持大文件                                                            |\n| http         | HTTP工具类(轻量级，不依赖第三方库)                                                                            |\n| io           | IO操作工具类(如文件UTF编码BOM头处理、文件编码探测、文件编码转换及内容替换、数字格式化为KB/MB/GB/TB/PB、Gzip等)         |\n| jce          | 加解密工具(对称加解密、非对称加解密、签名/验签、数字信封、ECC算法、哈希算法、国密算法、根证创建与CA证书签发、密码处理等)    |\n| model        | 数据模型相关公用类(带类型的Map操作、定义返回结果的结构体、分页实体等)                                               |\n| reflect      | 反射工具类(泛型解析、实体与Map互转、实体字段拷贝、实体字段获取、方法调用、Unsafe工具等)                               |\n| schema       | 表格数据结构定义，任意JSON格式数据转二维表等                                                                    |\n| serial       | 序列化工具类(JDK、JSON、FST、Hessian、Kryo、Protostuff)                                                      |\n| spring       | Spring相关工具类                                                                                           |\n| tree         | 强大的树型数据结构组件，构建复杂表头的基础(多路树构造及解析、类似`tree -N`命令的多路树打印、二叉树打印等)                |\n| util         | 常用工具类(Zip、时间轮、Snowflake id生成算法、Money/币种、一致性Hash算法、Base58编码、高效的字节处理等)              |\n"
  },
  {
    "path": "mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# 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,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Apache Maven Wrapper startup batch script, version 3.1.1\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /usr/local/etc/mavenrc ] ; then\n    . /usr/local/etc/mavenrc\n  fi\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        JAVA_HOME=\"`/usr/libexec/java_home`\"; export JAVA_HOME\n      else\n        JAVA_HOME=\"/Library/Java/Home\"; export JAVA_HOME\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`\\\\unset -f command; \\\\command -v java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  printf '%s' \"$(cd \"$basedir\"; pwd)\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=$(find_maven_basedir \"$(dirname $0)\")\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\nMAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}; export MAVEN_PROJECTBASEDIR\nif [ \"$MVNW_VERBOSE\" = true ]; then\n  echo $MAVEN_PROJECTBASEDIR\nfi\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nif [ -r \"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\" ]; then\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Found .mvn/wrapper/maven-wrapper.jar\"\n    fi\nelse\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ...\"\n    fi\n    if [ -n \"$MVNW_REPOURL\" ]; then\n      wrapperUrl=\"$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\"\n    else\n      wrapperUrl=\"https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\"\n    fi\n    while IFS=\"=\" read key value; do\n      case \"$key\" in (wrapperUrl) wrapperUrl=\"$value\"; break ;;\n      esac\n    done < \"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties\"\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Downloading from: $wrapperUrl\"\n    fi\n    wrapperJarPath=\"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\"\n    if $cygwin; then\n      wrapperJarPath=`cygpath --path --windows \"$wrapperJarPath\"`\n    fi\n\n    if command -v wget > /dev/null; then\n        QUIET=\"--quiet\"\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found wget ... using wget\"\n          QUIET=\"\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            wget $QUIET \"$wrapperUrl\" -O \"$wrapperJarPath\"\n        else\n            wget $QUIET --http-user=\"$MVNW_USERNAME\" --http-password=\"$MVNW_PASSWORD\" \"$wrapperUrl\" -O \"$wrapperJarPath\"\n        fi\n        [ $? -eq 0 ] || rm -f \"$wrapperJarPath\"\n    elif command -v curl > /dev/null; then\n        QUIET=\"--silent\"\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found curl ... using curl\"\n          QUIET=\"\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            curl $QUIET -o \"$wrapperJarPath\" \"$wrapperUrl\" -f -L\n        else\n            curl $QUIET --user \"$MVNW_USERNAME:$MVNW_PASSWORD\" -o \"$wrapperJarPath\" \"$wrapperUrl\" -f -L\n        fi\n        [ $? -eq 0 ] || rm -f \"$wrapperJarPath\"\n    else\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Falling back to using Java to download\"\n        fi\n        javaSource=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        javaClass=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\"\n        # For Cygwin, switch paths to Windows format before running javac\n        if $cygwin; then\n          javaSource=`cygpath --path --windows \"$javaSource\"`\n          javaClass=`cygpath --path --windows \"$javaClass\"`\n        fi\n        if [ -e \"$javaSource\" ]; then\n            if [ ! -e \"$javaClass\" ]; then\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Compiling MavenWrapperDownloader.java ...\"\n                fi\n                # Compiling the Java class\n                (\"$JAVA_HOME/bin/javac\" \"$javaSource\")\n            fi\n            if [ -e \"$javaClass\" ]; then\n                # Running the downloader\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Running MavenWrapperDownloader.java ...\"\n                fi\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$MAVEN_PROJECTBASEDIR\")\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\n# Provide a \"standardized\" way to retrieve the CLI args that will\n# work with both Windows and non-Windows executions.\nMAVEN_CMD_LINE_ARGS=\"$MAVEN_CONFIG $@\"\nexport MAVEN_CMD_LINE_ARGS\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  $MAVEN_DEBUG_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software Foundation (ASF) under one\n@REM or more contributor license agreements.  See the NOTICE file\n@REM distributed with this work for additional information\n@REM regarding copyright ownership.  The ASF licenses this file\n@REM to you under the Apache License, Version 2.0 (the\n@REM \"License\"); you may not use this file except in compliance\n@REM with the License.  You may obtain a copy of the License at\n@REM\n@REM    http://www.apache.org/licenses/LICENSE-2.0\n@REM\n@REM Unless required by applicable law or agreed to in writing,\n@REM software distributed under the License is distributed on an\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n@REM KIND, either express or implied.  See the License for the\n@REM specific language governing permissions and limitations\n@REM under the License.\n@REM ----------------------------------------------------------------------------\n\n@REM ----------------------------------------------------------------------------\n@REM Apache Maven Wrapper startup batch script, version 3.1.1\n@REM\n@REM Required ENV vars:\n@REM JAVA_HOME - location of a JDK home dir\n@REM\n@REM Optional ENV vars\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\n@REM     e.g. to debug Maven itself, use\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n@REM ----------------------------------------------------------------------------\n\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\n@echo off\n@REM set title of command window\ntitle %0\n@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\n\n@REM set %HOME% to equivalent of $HOME\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\n\n@REM Execute a user defined script before this one\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\nif exist \"%USERPROFILE%\\mavenrc_pre.bat\" call \"%USERPROFILE%\\mavenrc_pre.bat\" %*\nif exist \"%USERPROFILE%\\mavenrc_pre.cmd\" call \"%USERPROFILE%\\mavenrc_pre.cmd\" %*\n:skipRcPre\n\n@setlocal\n\nset ERROR_CODE=0\n\n@REM To isolate internal variables from possible post scripts, we use another setlocal\n@setlocal\n\n@REM ==== START VALIDATION ====\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\n\necho.\necho Error: JAVA_HOME not found in your environment. >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n:OkJHome\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\n\necho.\necho Error: JAVA_HOME is set to an invalid directory. >&2\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n@REM ==== END VALIDATION ====\n\n:init\n\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\n@REM Fallback to current working directory if not found.\n\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\n\nset EXEC_DIR=%CD%\nset WDIR=%EXEC_DIR%\n:findBaseDir\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\ncd ..\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\nset WDIR=%CD%\ngoto findBaseDir\n\n:baseDirFound\nset MAVEN_PROJECTBASEDIR=%WDIR%\ncd \"%EXEC_DIR%\"\ngoto endDetectBaseDir\n\n:baseDirNotFound\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\ncd \"%EXEC_DIR%\"\n\n:endDetectBaseDir\n\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\n\n@setlocal EnableExtensions EnableDelayedExpansion\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\n\n:endReadAdditionalConfig\n\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nset WRAPPER_URL=\"https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\"\n\nFOR /F \"usebackq tokens=1,2 delims==\" %%A IN (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties\") DO (\n    IF \"%%A\"==\"wrapperUrl\" SET WRAPPER_URL=%%B\n)\n\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\nif exist %WRAPPER_JAR% (\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Found %WRAPPER_JAR%\n    )\n) else (\n    if not \"%MVNW_REPOURL%\" == \"\" (\n        SET WRAPPER_URL=\"%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\"\n    )\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Couldn't find %WRAPPER_JAR%, downloading it ...\n        echo Downloading from: %WRAPPER_URL%\n    )\n\n    powershell -Command \"&{\"^\n\t\t\"$webclient = new-object System.Net.WebClient;\"^\n\t\t\"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {\"^\n\t\t\"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');\"^\n\t\t\"}\"^\n\t\t\"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')\"^\n\t\t\"}\"\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Finished downloading %WRAPPER_JAR%\n    )\n)\n@REM End of extension\n\n@REM Provide a \"standardized\" way to retrieve the CLI args that will\n@REM work with both Windows and non-Windows executions.\nset MAVEN_CMD_LINE_ARGS=%*\n\n%MAVEN_JAVA_EXE% ^\n  %JVM_CONFIG_MAVEN_PROPS% ^\n  %MAVEN_OPTS% ^\n  %MAVEN_DEBUG_OPTS% ^\n  -classpath %WRAPPER_JAR% ^\n  \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" ^\n  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\nif ERRORLEVEL 1 goto error\ngoto end\n\n:error\nset ERROR_CODE=1\n\n:end\n@endlocal & set ERROR_CODE=%ERROR_CODE%\n\nif not \"%MAVEN_SKIP_RC%\"==\"\" goto skipRcPost\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\nif exist \"%USERPROFILE%\\mavenrc_post.bat\" call \"%USERPROFILE%\\mavenrc_post.bat\"\nif exist \"%USERPROFILE%\\mavenrc_post.cmd\" call \"%USERPROFILE%\\mavenrc_post.cmd\"\n:skipRcPost\n\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\nif \"%MAVEN_BATCH_PAUSE%\"==\"on\" pause\n\nif \"%MAVEN_TERMINATE_CMD%\"==\"on\" exit %ERROR_CODE%\n\ncmd /C exit /B %ERROR_CODE%\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>cn.ponfee</groupId>\n  <artifactId>commons-core</artifactId>\n  <version>1.5-SNAPSHOT</version>\n\n  <!-- Maven:\n    > deploy: mvn -Drevision=_ versions:set -DnewVersion=1.4-SNAPSHOT && mvn clean deploy -Prelease -DskipTests -Dcheckstyle.skip=true -U\n\n    > maven wrapper：https://maven.apache.org/wrapper/index.html\n    > 解决maven-wrapper的版本问题：生成时需要删除掉“$MAVEN_HOME/conf/settings.xml”文件中配置的mirror\n    > 1）Install(会生成.mvn文件夹、mvnw和mvnw.cmd脚本)：mvn wrapper:wrapper -Dmaven=3.6.3\n    > 2）Usage：\n    >   2.1）Linux：./mvnw clean install -Dmaven.test.skip=true\n    >   2.2）Windows：mvnw.cmd clean install -Dmaven.test.skip=true\n    > 依赖处理：1、最短路径原则；2、最先声明原则；\n    > 查看有效的pom：mvn help:effective-pom\n\n    > javac: 不加“-g”则默认为“-g:source,lines”\n    >   lines：字节码文件中对应源码的行号(Line number debugging information)\n    >   vars：字节码文件中对应源码的局部变量的符号表(Local variable debugging information)\n    >   source：将该类文件对应的源文件名称写进字节码中(Source file debugging information，针对非public修饰类场景)\n    >   -g                     生成所有调试信息\n    >   -g:none                不生成任何调试信息\n    >   -g:{lines,vars,source} 只生成某些调试信息\n    >   -source: version\n    >   -target: version\n\n    > Alibaba P3C手册：https://github.com/alibaba/p3c\n    > sonar参考文章：https://www.jianshu.com/p/68a3ed571314\n  -->\n\n  <!-- Logging:\n    slf4j-ext.jar         —>                                  扩展功能\n    slf4j-nop.jar         —>    (slf4j    —>  null)           slf4j的空接口输出绑定，丢弃所有日志输出\n    slf4j-simple.jar      —>    (slf4j    —>  slf4j-simple)   slf4j的自带的简单日志输出实现\n    slf4j-android.jar     —>    (android  —>  slf4j)          将android环境下的日志，桥接到slf4j\n    log4j-1.2-api.jar     —>    (log4j    —>  log4j2)         将log4j的日志转接到log4j2日志框架\n\n    slf4j的具体实现：slf4j-simple、logback\n\n    slf4j adapter to impl(LogImpl)：http://www.slf4j.org/manual.html\n      log4j  : slf4j-api -> org.slf4j:slf4j-log4j12, log4j:log4j\n      log4j2 : slf4j-api -> org.apache.logging.log4j:(log4j-slf4j-impl, log4j-api, log4j-core)\n      logback: slf4j-api -> ch.qos.logback:(logback-classic, logback-core)\n      jcl    : slf4j-api -> org.slf4j:slf4j-jcl, commons-logging:commons-logging\n      jul    : slf4j-api -> org.slf4j:slf4j-jdk14, java.util.logging\n\n    log api bridge to slf4j：http://www.slf4j.org/legacy.html\n      log4j            : log4j-over-slf4j(log4j api)  -> slf4j-api -> LogImpl\n      log4j2           : log4j-to-slf4j  (log4j2 api) -> slf4j-api -> LogImpl\n      logback          : logback就是slf4j的原生实现（即logback的api就是slf4j-api）\n      commons-logging  : jcl-over-slf4j  (jcl api)    -> slf4j-api -> LogImpl\n      java.util.logging: jul-to-slf4j    (jul api)    -> slf4j-api -> LogImpl（SLF4JBridgeHandler.removeHandlersForRootLogger();SLF4JBridgeHandler.install();）\n      osgi             : osgi-over-slf4j (osgi api)   —> slf4j-api -> LogImpl（将osgi环境下的日志，桥接到slf4j）\n   -->\n\n  <name>Commons core</name>\n  <description>A commons tool java lib</description>\n  <url>https://github.com/ponfee/commons-core</url>\n  <licenses>\n    <license>\n      <name>The Apache Software License, Version 2.0</name>\n      <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>\n    </license>\n  </licenses>\n  <developers>\n    <developer>\n      <name>Ponfee</name>\n      <email>ponfee.cn@gmail.com</email>\n      <organization>ponfee.cn</organization>\n      <organizationUrl>http://www.ponfee.cn/</organizationUrl>\n    </developer>\n  </developers>\n  <scm>\n    <connection>scm:git:https://github.com/ponfee/commons-core.git</connection>\n    <developerConnection>scm:git:https://github.com/ponfee/commons-core.git</developerConnection>\n    <url>https://github.com/ponfee/commons-core</url>\n    <tag>HEAD</tag>\n  </scm>\n  <!-- 根据version中是否带有“-SNAPSHOT”来判断是分发到snapshots库还是releases库 -->\n  <distributionManagement>\n    <!-- 添加到maven的安装目录的settings.xml中\n    <servers>\n      <server>\n        <id>ossrh</id>\n        <username>username</username>\n        <password>password</password>\n      </server>\n    </servers> -->\n    <snapshotRepository>\n      <id>ossrh</id>\n      <url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>\n    </snapshotRepository>\n    <repository>\n      <id>ossrh</id>\n      <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>\n    </repository>\n  </distributionManagement>\n\n  <properties>\n    <file.encoding>UTF-8</file.encoding>\n    <project.build.sourceEncoding>${file.encoding}</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>${file.encoding}</project.reporting.outputEncoding>\n    <java.version>1.8</java.version>\n    <maven.compiler.source>${java.version}</maven.compiler.source>\n    <maven.compiler.target>${java.version}</maven.compiler.target>\n    <maven.compiler.compilerVersion>${java.version}</maven.compiler.compilerVersion>\n\n    <spring-framework.version>5.3.24</spring-framework.version>\n    <slf4j.version>1.7.36</slf4j.version>\n    <log4j2.version>2.19.0</log4j2.version>\n    <bouncycastle.version>1.72</bouncycastle.version>\n    <jackson.version>2.14.1</jackson.version>\n  </properties>\n\n  <profiles>\n    <!-- 1、查看处于激活状态的profile：mvn help:active-profiles\n         2、查看计算机上的这些OS属性：mvn enforcer:display-info\n         3、查看系统属性：mvn help:system | grep \"os\\.\"\n      -->\n    <profile>\n      <id>unix</id>\n      <activation>\n        <os><family>unix</family></os>\n      </activation>\n      <properties>\n        <system.separator>:</system.separator>\n      </properties>\n    </profile>\n    <profile>\n      <id>windows</id>\n      <activation>\n        <os><family>windows</family></os>\n      </activation>\n      <properties>\n        <system.separator>;</system.separator>\n      </properties>\n    </profile>\n    <profile>\n      <!-- mvn clean package -Pdevelop -->\n      <id>develop</id>\n      <!--<activation>前面加了OS的profile后，此处的默认设置无效\n        <activeByDefault>true</activeByDefault>\n      </activation>-->\n      <properties>\n      </properties>\n    </profile>\n    <profile>\n      <!-- mvn clean deploy -Prelease -->\n      <id>release</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-javadoc-plugin</artifactId>\n            <version>3.4.1</version>\n            <executions>\n              <execution>\n                <id>attach-javadocs</id>\n                <goals>\n                  <goal>jar</goal>\n                </goals>\n              </execution>\n            </executions>\n            <configuration>\n              <doclint>none</doclint>\n            </configuration>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-gpg-plugin</artifactId>\n            <version>3.0.1</version>\n            <executions>\n              <execution>\n                <id>sign-artifacts</id>\n                <phase>verify</phase>\n                <goals>\n                  <goal>sign</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n\n  <dependencyManagement>\n    <dependencies>\n      <dependency>\n        <groupId>org.apache.tika</groupId>\n        <artifactId>tika-bom</artifactId>\n        <version>2.6.0</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n    </dependencies>\n  </dependencyManagement>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.projectlombok</groupId>\n      <artifactId>lombok</artifactId>\n      <version>1.18.24</version>\n      <optional>true</optional>\n      <scope>provided</scope>\n    </dependency>\n\n    <!-- ==============================logger============================== -->\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-api</artifactId>\n      <version>${slf4j.version}</version>\n    </dependency>\n\n    <!-- adapter to slf4j-api -->\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>jcl-over-slf4j</artifactId>\n      <version>${slf4j.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>jul-to-slf4j</artifactId>\n      <version>${slf4j.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>log4j-over-slf4j</artifactId>\n      <version>${slf4j.version}</version>\n    </dependency>\n    <!-- log4j2：当使用log4j2为日志实现时，需注释此依赖项 -->\n    <!-- <dependency>\n      <groupId>org.apache.logging.log4j</groupId>\n      <artifactId>log4j-to-slf4j</artifactId>\n      <version>${log4j2.version}</version>\n    </dependency> -->\n\n    <!-- 具体的日志实现 -->\n    <!-- logback为日志实现：logback.xml -->\n    <!-- <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <version>1.2.3</version>\n      <scope>runtime</scope>\n    </dependency> -->\n    <!-- log4j为日志实现：log4j.properties -->\n    <!-- <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-log4j12</artifactId>\n      <version>${slf4j.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>log4j</groupId>\n      <artifactId>log4j</artifactId>\n      <version>1.2.17</version>\n    </dependency> -->\n    <!-- log4j2为日志实现：log4j2.xml -->\n    <dependency>\n      <groupId>org.apache.logging.log4j</groupId>\n      <artifactId>log4j-slf4j-impl</artifactId>\n      <version>${log4j2.version}</version>\n      <scope>runtime</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.logging.log4j</groupId>\n      <artifactId>log4j-web</artifactId>\n      <version>${log4j2.version}</version>\n      <scope>runtime</scope>\n    </dependency>\n    <!-- <context-param> web.xml自定义配置文件的位置\n      <param-name>log4jConfiguration</param-name>\n      <param-value>/WEB-INF/classes/log4j2.xml</param-value>\n    </context-param> -->\n    <!-- commons-logging桥接到Log4j2 -->\n    <!-- <dependency>\n      <groupId>org.apache.logging.log4j</groupId>\n      <artifactId>log4j-jcl</artifactId>\n      <version>${log4j2.version}</version>\n    </dependency> -->\n    <!-- java.util.logging桥接到Log4j2 -->\n    <!-- <dependency>\n      <groupId>org.apache.logging.log4j</groupId>\n      <artifactId>log4j-jul</artifactId>\n      <version>${log4j2.version}</version>\n    </dependency> -->\n    <!-- ====================logger==================== -->\n\n\n    <!-- commons -->\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n      <version>3.12.0</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-text</artifactId>\n      <version>1.10.0</version>\n    </dependency>\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n      <version>2.11.0</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-collections4</artifactId>\n      <version>4.4</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-math3</artifactId>\n      <version>3.6.1</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-pool2</artifactId>\n      <version>2.11.1</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-csv</artifactId>\n      <version>1.9.0</version>\n    </dependency>\n    <dependency>\n      <groupId>org.dom4j</groupId>\n      <artifactId>dom4j</artifactId>\n      <version>2.1.3</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n      <version>31.1-jre</version>\n    </dependency>\n    <dependency>\n      <groupId>joda-time</groupId>\n      <artifactId>joda-time</artifactId>\n      <version>2.10.13</version>\n    </dependency>\n    <dependency>\n      <groupId>net.lingala.zip4j</groupId>\n      <artifactId>zip4j</artifactId>\n      <version>2.11.2</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.tika</groupId>\n      <artifactId>tika-parsers-standard-package</artifactId>\n      <exclusions>\n        <exclusion>\n          <groupId>commons-logging</groupId>\n          <artifactId>commons-logging</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.tika</groupId>\n      <artifactId>tika-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.itextpdf</groupId>\n      <artifactId>itextpdf</artifactId>\n      <version>5.5.13.3</version>\n    </dependency>\n    <dependency>\n      <groupId>javax.money</groupId>\n      <artifactId>money-api</artifactId>\n      <version>1.1</version>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>org.javamoney</groupId>\n      <artifactId>moneta</artifactId>\n      <version>1.4.2</version>\n      <type>pom</type>\n      <optional>true</optional>\n    </dependency>\n\n    <!-- jakarta(当前版本是“javax”的包名空间，再往上升级版本就是“jakarta”包名空间) -->\n    <dependency>\n      <groupId>jakarta.servlet</groupId>\n      <artifactId>jakarta.servlet-api</artifactId>\n      <version>4.0.4</version>\n      <optional>true</optional>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.annotation</groupId>\n      <artifactId>jakarta.annotation-api</artifactId>\n      <version>1.3.5</version>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.validation</groupId>\n      <artifactId>jakarta.validation-api</artifactId>\n      <version>2.0.2</version>\n    </dependency>\n    <dependency>\n      <groupId>org.hibernate.validator</groupId>\n      <artifactId>hibernate-validator</artifactId>\n      <version>6.2.5.Final</version>\n    </dependency>\n\n    <!-- ====================spring==================== -->\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-core</artifactId>\n      <version>${spring-framework.version}</version>\n      <exclusions>\n        <exclusion>\n          <groupId>commons-logging</groupId>\n          <artifactId>commons-logging</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-context-support</artifactId>\n      <version>${spring-framework.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-web</artifactId>\n      <version>${spring-framework.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-aspects</artifactId>\n      <version>${spring-framework.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-jdbc</artifactId>\n      <version>${spring-framework.version}</version>\n    </dependency>\n\n    <!-- ====================excel poi==================== -->\n    <dependency>\n      <groupId>org.apache.poi</groupId>\n      <artifactId>poi-ooxml</artifactId>\n      <version>5.2.3</version>\n      <exclusions>\n        <exclusion>\n          <groupId>org.apache.logging.log4j</groupId>\n          <artifactId>log4j-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>com.monitorjbl</groupId>\n      <artifactId>xlsx-streamer</artifactId>\n      <version>2.2.0</version>\n      <exclusions>\n        <exclusion>\n          <groupId>org.apache.poi</groupId>\n          <artifactId>*</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>*</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.apache.logging.log4j</groupId>\n          <artifactId>*</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n\n    <!-- ====================序列化==================== -->\n    <dependency>\n      <groupId>com.esotericsoftware</groupId>\n      <artifactId>kryo</artifactId>\n      <version>5.3.0</version>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.core</groupId>\n      <artifactId>jackson-databind</artifactId>\n      <version>${jackson.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.datatype</groupId>\n      <artifactId>jackson-datatype-jsr310</artifactId>\n      <version>${jackson.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.caucho</groupId>\n      <artifactId>hessian</artifactId>\n      <version>4.0.66</version>\n    </dependency>\n    <dependency>\n      <groupId>de.ruedigermoeller</groupId>\n      <artifactId>fst</artifactId>\n      <version>2.57</version>\n      <exclusions>\n        <exclusion>\n          <groupId>com.fasterxml.jackson.core</groupId>\n          <artifactId>jackson-core</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.objenesis</groupId>\n          <artifactId>objenesis</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>io.protostuff</groupId>\n      <artifactId>protostuff-runtime</artifactId>\n      <version>1.8.0</version>\n    </dependency>\n    <dependency>\n      <groupId>io.protostuff</groupId>\n      <artifactId>protostuff-core</artifactId>\n      <version>1.8.0</version>\n    </dependency>\n    <dependency>\n      <groupId>com.alibaba</groupId>\n      <artifactId>fastjson</artifactId>\n      <version>1.2.83</version>\n    </dependency>\n\n    <!-- ====================mybatis==================== -->\n    <dependency>\n      <groupId>com.alibaba</groupId>\n      <artifactId>druid</artifactId>\n      <version>1.2.15</version>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>org.mybatis</groupId>\n      <artifactId>mybatis</artifactId>\n      <version>3.5.11</version>\n    </dependency>\n    <dependency>\n      <groupId>org.mybatis</groupId>\n      <artifactId>mybatis-spring</artifactId>\n      <version>2.1.0</version>\n    </dependency>\n    <dependency>\n      <groupId>com.github.pagehelper</groupId>\n      <artifactId>pagehelper</artifactId>\n      <version>5.3.2</version>\n    </dependency>\n\n    <!-- ====================加解密==================== -->\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcmail-jdk18on</artifactId>\n      <version>${bouncycastle.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcpg-jdk18on</artifactId>\n      <version>${bouncycastle.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bctls-jdk18on</artifactId>\n      <version>${bouncycastle.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcprov-ext-jdk18on</artifactId>\n      <version>${bouncycastle.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>commons-codec</groupId>\n      <artifactId>commons-codec</artifactId>\n      <version>1.15</version>\n    </dependency>\n\n    <!-- ====================test==================== -->\n    <dependency>\n      <groupId>junit</groupId>\n      <artifactId>junit</artifactId>\n      <version>4.13.2</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-test</artifactId>\n      <version>${spring-framework.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.zeroturnaround</groupId>\n      <artifactId>zt-zip</artifactId>\n      <version>1.14</version>\n      <scope>test</scope>\n      <type>jar</type>\n      <exclusions>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>*</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.jsoup</groupId>\n      <artifactId>jsoup</artifactId>\n      <version>1.13.1</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.lmax</groupId>\n      <artifactId>disruptor</artifactId>\n      <version>3.4.2</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.httpcomponents</groupId>\n      <artifactId>httpclient</artifactId>\n      <version>4.5.14</version>\n      <scope>test</scope>\n      <exclusions>\n        <exclusion>\n          <groupId>commons-logging</groupId>\n          <artifactId>commons-logging</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.testng</groupId>\n      <artifactId>testng</artifactId>\n      <version>6.14.3</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.google.protobuf</groupId>\n      <artifactId>protobuf-java</artifactId>\n      <version>3.11.4</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.alibaba</groupId>\n      <artifactId>transmittable-thread-local</artifactId>\n      <version>2.11.4</version>\n      <scope>test</scope>\n    </dependency>\n    <!-- 密码哈希处理 -->\n    <dependency>\n      <groupId>de.mkammerer</groupId>\n      <artifactId>argon2-jvm</artifactId>\n      <version>2.6</version>\n      <scope>test</scope>\n    </dependency>\n    <!-- Java Object Layout -->\n    <dependency>\n      <groupId>org.openjdk.jol</groupId>\n      <artifactId>jol-core</artifactId>\n      <version>0.16</version>\n      <scope>test</scope>\n    </dependency>\n    <!-- 二维码生成 -->\n    <!-- <dependency>\n      <groupId>com.github.kenglxn.QRGen</groupId>\n      <artifactId>javase</artifactId>\n      <version>2.6.0</version>\n      <scope>test</scope>\n    </dependency> -->\n\n    <dependency>\n      <groupId>com.beust</groupId>\n      <artifactId>jcommander</artifactId>\n      <version>1.82</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>info.picocli</groupId>\n      <artifactId>picocli</artifactId>\n      <version>4.7.0</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <!--\n    1、“https://repo1.maven.org/maven2”与“https://repo.maven.apache.org/maven2”是同一个仓库(central，即mavenCentral)\n    2、central是在“$MAVEN_HOME/lib/maven-model-builder.jar!/org/apache/maven/model/pom-4.0.0.xml”中配置的\n    3、aliyun central+jcenter的聚合镜像仓(jcenter 2022-02-01开始停用不再支持下载)：https://maven.aliyun.com/repository/public\n    4、aliyun maven文档：https://developer.aliyun.com/mvn/guide\n    5、mvn repository地址：https://mvnrepository.com/\n    6、中央仓库搜索地址：https://central.sonatype.com/\n  -->\n  <!--\n  <repositories>\n    <repository>\n      <id>central</id>\n      <url>https://repo.maven.apache.org/maven2</url>\n    </repository>\n  </repositories>\n  -->\n\n  <build>\n    <finalName>${project.artifactId}-${project.version}</finalName>\n    <resources>\n      <resource>\n        <directory>src/main/resources</directory>\n        <!-- filtering用于替换资源文件(*.xml、*.properties)中的占位符(${...}) -->\n        <!-- https://blog.csdn.net/luckyzhoustar/article/details/50411962 -->\n        <filtering>false</filtering>\n      </resource>\n      <resource>\n        <directory>src/main/java</directory>\n        <filtering>false</filtering>\n        <excludes>\n          <exclude>**/*.java</exclude>\n        </excludes>\n      </resource>\n    </resources>\n    <testResources>\n      <testResource>\n        <directory>src/test/resources</directory>\n        <filtering>false</filtering>\n      </testResource>\n    </testResources>\n\n    <plugins>\n      <plugin>\n        <groupId>org.codehaus.mojo</groupId>\n        <artifactId>versions-maven-plugin</artifactId>\n        <version>2.13.0</version>\n        <configuration>\n          <generateBackupPoms>false</generateBackupPoms>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-resources-plugin</artifactId>\n        <version>3.2.0</version>\n        <configuration>\n          <encoding>${file.encoding}</encoding>\n        </configuration>\n      </plugin>\n      <plugin>\n        <!-- http://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html -->\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.10.1</version>\n        <configuration>\n          <source>${maven.compiler.source}</source>\n          <target>${maven.compiler.target}</target>\n          <compilerVersion>${maven.compiler.compilerVersion}</compilerVersion>\n          <encoding>${file.encoding}</encoding>\n          <showDeprecation>true</showDeprecation>\n          <showWarnings>true</showWarnings>\n          <!-- <fork>true</fork> 乱码 -->\n          <!--<compilerArgument>-Xlint:unchecked,deprecation</compilerArgument>-->\n          <!-- <compilerArgument>-verbose -parameters -bootclasspath ${java.home}\\lib\\rt.jar</compilerArgument> -->\n          <compilerArgs>\n            <arg>-bootclasspath</arg>\n            <!-- windows用“;”号分隔；linux用“:”号分隔；${JAVA_HOME}/lib/tools.jar； -->\n            <arg>${java.home}/lib/rt.jar${system.separator}${java.home}/lib/jce.jar${system.separator}${java.home}/lib/jsse.jar</arg>\n            <arg>-parameters</arg>\n            <arg>-Xlint:unchecked,deprecation</arg>\n          </compilerArgs>\n          <excludes>\n            <exclude>node_modules/**</exclude>\n          </excludes>\n          <!--<debug>false</debug>\n          <debuglevel>none</debuglevel>\n          <skipMain>true</skipMain>-->\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-source-plugin</artifactId>\n        <version>3.2.1</version>\n        <executions>\n          <execution>\n            <id>attach-sources</id>\n            <phase>verify</phase>\n            <goals>\n              <goal>jar-no-fork</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n\n      <!--\n        http://wvengen.github.io/proguard-maven-plugin\n        https://github.com/wvengen/proguard-maven-plugin\n        https://blog.csdn.net/xiao190128/article/details/81777912\n       -->\n      <!-- <plugin>\n        <groupId>com.github.wvengen</groupId>\n        <artifactId>proguard-maven-plugin</artifactId>\n        <version>2.6.0</version>\n        <executions>\n          <execution>\n            <phase>package</phase>\n            <goals>\n              <goal>proguard</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <obfuscate>true</obfuscate>\n          <addMavenDescriptor>false</addMavenDescriptor>\n          <inFilter>!application.properties</inFilter>\n          <injarNotExistsSkip>true</injarNotExistsSkip>\n\n          <injar>classes</injar>\n          <attach>false</attach>\n          <outjar>${project.build.finalName}-proguard.jar</outjar>\n\n          <!—— <injar>${project.build.finalName}.jar</injar>\n          <attach>true</attach>\n          <attachArtifactClassifier>proguard</attachArtifactClassifier>\n          <attachArtifactType>jar</attachArtifactType> ——>\n\n          <outputDirectory>${project.build.directory}</outputDirectory>\n          <libs>\n            <lib>${java.home}/lib/rt.jar</lib>\n            <lib>${java.home}/lib/jce.jar</lib>\n            <lib>${java.home}/lib/jsse.jar</lib>\n          </libs>\n          <options>\n            <option>-target ${java.version}</option>\n            <option>-ignorewarnings</option>\n            <option>-dontshrink</option>\n            <option>-dontoptimize</option>\n            <option>-dontskipnonpubliclibraryclasses</option>\n            <option>-dontskipnonpubliclibraryclassmembers</option>\n            <option>-keeppackagenames</option>\n            <option><![CDATA[-keep class * {*;}]]></option>\n          </options>\n        </configuration>\n        <dependencies>\n          <dependency>\n            <groupId>net.sf.proguard</groupId>\n            <artifactId>proguard-base</artifactId>\n            <version>6.2.2</version>\n          </dependency>\n        </dependencies>\n      </plugin> -->\n\n\n      <!-- http://maven.apache.org/plugins/maven-assembly-plugin/single-mojo.html -->\n      <!-- <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-assembly-plugin</artifactId>\n        <version>3.1.1</version>\n        <configuration>\n          <appendAssemblyId>false</appendAssemblyId>\n          <descriptorRefs>\n            <descriptorRef>jar-with-dependencies</descriptorRef>\n          </descriptorRefs>\n          <archive>\n            <manifest>\n              <mainClass>cn.ponfee.Main</mainClass>\n            </manifest>\n          </archive>\n        </configuration>\n        <executions>\n          <execution>\n            <id>make-assembly</id>\n            <phase>package</phase>\n            <goals>\n              <goal>single</goal>\n            </goals>\n            <configuration>\n              <finalName>${project.artifactId}</finalName>\n              <skipAssembly>false</skipAssembly>\n              <descriptors>\n                <descriptor>src/main/assembly/assembly.xml</descriptor>\n              </descriptors>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin> -->\n\n      <!--\n        https://blog.csdn.net/u011499747/article/details/83045928\n        http://maven.apache.org/plugins/maven-shade-plugin/shade-mojo.html\n          maven-jar-plugin:\n          maven-assembly-plugin: mvn assembly:assembly\n          maven-shade-plugin: mvn clean package\n       -->\n      <!-- <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-shade-plugin</artifactId>\n        <version>3.2.1</version>\n        <executions>\n          <execution>\n            <phase>package</phase>\n            <goals>\n              <goal>shade</goal>\n            </goals>\n            <configuration>\n              <transformers>\n                <transformer implementation=\"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\">\n                  <mainClass>cn.ponfee.Main</mainClass>\n                </transformer>\n              </transformers>\n              <createSourcesJar>true</createSourcesJar>\n              <artifactSet>\n                <includes>\n                  <include>${project.groupId}:*:*</include>\n                </includes>\n                <excludes>\n                  <!—— log jar file provide by flink ——>\n                  <exclude>org.slf4j:*</exclude>\n                  <exclude>log4j:*</exclude>\n                </excludes>\n              </artifactSet>\n              <filters>\n                <filter>\n                  <artifact>*:*</artifact>\n                  <excludes>\n                    <exclude>META-INF/*.SF</exclude>\n                    <exclude>META-INF/*.DSA</exclude>\n                    <exclude>META-INF/*.RSA</exclude>\n                    <!—— log conf file provide by flink ——>\n                    <exclude>log4j2.xml</exclude>\n                  </excludes>\n                </filter>\n              </filters>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin> -->\n\n      <!--  package jar -->\n      <!-- <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <version>3.1.2</version>\n        <configuration>\n          <archive>\n            <manifest>\n              <addClasspath>true</addClasspath>\n              <classpathPrefix>lib/</classpathPrefix>\n              <mainClass>cn.ponfee.Main</mainClass>\n            </manifest>\n          </archive>\n        </configuration>\n        <executions>\n          <execution>\n            <id>make-a-jar</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>jar</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-dependency-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>copy</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>copy-dependencies</goal>\n            </goals>\n            <configuration>\n              <outputDirectory>\n                ${project.build.directory}/lib\n              </outputDirectory>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin> -->\n\n      <!-- <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-war-plugin</artifactId>\n        <version>3.2.3</version>\n        <configuration>\n          <!—— warSourceExcludes是在编译完成后从warSourceDirectory目录复制文件时忽略，\n               packagingExcludes是在从target/${artifactId}-${version}目录打包时忽略 ——>\n          <!—— <warSourceDirectory>src/main/webapp | WebRoot | WebContent</warSourceDirectory>\n          <warSourceExcludes>page/**,js/**,css/**,imgs/**</warSourceExcludes> ——>\n          <warSourceIncludes>WEB-INF/lib/**,WEB-INF/web.xml,WEB-INF/mvc-config.xml,WEB-INF/jetty-web.xml</warSourceIncludes>\n\n          <!—— packagingExcludes在warSourceExcludes后面执行：排除src/main/resources或从远程仓库pull下来的\n          <packagingExcludes>WEB-INF/classes/logback.xml,WEB-INF/lib/commons-logging-*.jar,%regex[WEB-INF/lib/log4j-(?!over-slf4j).*.jar]</packagingExcludes>\n          <packagingIncludes></packagingIncludes> ——>\n          <failOnMissingWebXml>true</failOnMissingWebXml>\n        </configuration>\n      </plugin> -->\n    </plugins>\n\n  </build>\n</project>\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/Initializable.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base;\n\n/**\n * Initialize resources\n * \n * @author Ponfee\n */\n@FunctionalInterface\npublic interface Initializable {\n\n    NoArgMethodInvoker INITIATOR = new NoArgMethodInvoker(\"open\", \"init\", \"initialize\");\n\n    void init();\n\n    static void init(Object caller) {\n        if (caller == null) {\n            return;\n        }\n\n        if (caller instanceof Initializable) {\n            ((Initializable) caller).init();\n        } else {\n            INITIATOR.invoke(caller);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/NoArgMethodInvoker.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base;\n\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.annotation.Nonnull;\nimport java.util.Arrays;\n\n/**\n * Specifies multiple non-arg method names, find the first and invoke it\n * \n * @author Ponfee\n */\npublic final class NoArgMethodInvoker {\n\n    private final String[] methodNames;\n\n    /**\n     * @param methodNames the no-arg method list\n     */\n    public NoArgMethodInvoker(@Nonnull String... methodNames) {\n        if (methodNames == null || methodNames.length == 0) {\n            throw new IllegalArgumentException(\"Must be specified least once no-arg method name.\");\n        }\n        this.methodNames = methodNames;\n    }\n\n    public void invoke(Object caller) {\n        if (caller == null) {\n            return;\n        }\n        if (caller instanceof Class<?>) {\n            throw new IllegalArgumentException(\"Invalid caller object \" + caller);\n        }\n\n        Arrays.stream(methodNames)\n              .filter(StringUtils::isNotBlank)\n              .map(name -> ClassUtils.getMethod(caller, name))\n              .findAny()\n              .ifPresent(method -> ClassUtils.invoke(caller, method));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/Predicates.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base;\n\nimport java.util.function.Predicate;\n\n/**\n * Representing a boolean status\n *\n * <pre>\n *  异或(XOR , ⊕) = A ^ B\n *  同或(XNOR, ⊙) = 异或 ^ 1 = (A ^ B) ^ 1\n *  (0, 1)间的切换可以使用异或：1 ^ n，其中n ∈ (0, 1)\n * </pre>\n *\n * @author Ponfee\n */\npublic enum Predicates {\n\n    Y(1, \"是\"), //\n\n    N(0, \"否\"), //\n\n    ;\n\n    private final int value;\n    private final char code;\n    private final String desc;\n\n    Predicates(int value, String desc) {\n        this.value = value;\n        this.code = name().charAt(0); // 'Y' or 'N'\n        this.desc = desc;\n    }\n\n    public int value() {\n        return value;\n    }\n\n    public char code() {\n        return code;\n    }\n\n    public boolean state() {\n        return this == Y;\n    }\n\n    public String desc() {\n        return this.desc;\n    }\n\n    // ------------------------------------------------ equals methods\n    public boolean equals(Integer value) {\n        return equals(value == null ? N.value : value);\n    }\n\n    public boolean equals(int value) {\n        if (value != Y.value && value != N.value) {\n            throw new IllegalArgumentException(\"Invalid int value '\" + value + \"'\");\n        }\n        return this.value == value;\n    }\n\n    public boolean equals(String code) {\n        char c;\n        if (code == null) {\n            c = N.code;\n        } else if (code.length() == 1) {\n            c = code.charAt(0);\n        } else {\n            throw new IllegalArgumentException(\"Invalid string code '\" + code + \"'\");\n        }\n        return equals(c);\n    }\n\n    public boolean equals(Character code) {\n        return equals(code == null ? N.code : code);\n    }\n\n    public boolean equals(char code) {\n        code = Character.toUpperCase(code);\n        if (code != Y.code && code != N.code) {\n            throw new IllegalArgumentException(\"Invalid char code '\" + code + \"'\");\n        }\n        return this.code == code;\n    }\n\n    public boolean equals(Boolean state) {\n        return equals(state == null ? N.state() : state);\n    }\n\n    public boolean equals(boolean state) {\n        return state() == state;\n    }\n\n    public boolean equals(Predicates other) {\n        return this == (other == null ? N : other);\n    }\n\n    // ------------------------------------------------ check whether the value is yes\n    public static boolean yes(Integer value) {\n        return Y.equals(value);\n    }\n\n    public static boolean yes(int value) {\n        return Y.equals(value);\n    }\n\n    public static boolean yes(String code) {\n        return Y.equals(code);\n    }\n\n    public static boolean yes(Character code) {\n        return Y.equals(code);\n    }\n\n    public static boolean yes(char code) {\n        return Y.equals(code);\n    }\n\n    public static boolean yes(Boolean state) {\n        return Y.equals(state);\n    }\n\n    public static boolean yes(boolean state) {\n        return Y.equals(state);\n    }\n\n    public static boolean yes(Predicates other) {\n        return Y.equals(other);\n    }\n\n    // ------------------------------------------------ check whether the value is no\n    public static boolean no(Integer value) {\n        return N.equals(value);\n    }\n\n    public static boolean no(int value) {\n        return N.equals(value);\n    }\n\n    public static boolean no(String code) {\n        return N.equals(code);\n    }\n\n    public static boolean no(Character code) {\n        return N.equals(code);\n    }\n\n    public static boolean no(char code) {\n        return N.equals(code);\n    }\n\n    public static boolean no(Boolean state) {\n        return N.equals(state);\n    }\n\n    public static boolean no(boolean state) {\n        return N.equals(state);\n    }\n\n    public static boolean no(Predicates other) {\n        return N.equals(other);\n    }\n\n    // ------------------------------------------------ of methods\n    public static Predicates of(Integer value) {\n        return Y.equals(value) ? Y : N;\n    }\n\n    public static Predicates of(int value) {\n        return Y.equals(value) ? Y : N;\n    }\n\n    public static Predicates of(String code) {\n        return Y.equals(code) ? Y : N;\n    }\n\n    public static Predicates of(Character code) {\n        return Y.equals(code) ? Y : N;\n    }\n\n    public static Predicates of(char code) {\n        return Y.equals(code) ? Y : N;\n    }\n\n    public static Predicates of(Boolean state) {\n        return Y.equals(state) ? Y : N;\n    }\n\n    public static Predicates of(boolean state) {\n        return Y.equals(state) ? Y : N;\n    }\n\n    public static <T> Predicate<T> not(Predicate<T> target) {\n        return target.negate();\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/PrimitiveTypes.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base;\n\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.util.Enums;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * <pre>\n * 基本数据类型及其包装类型工具类(不包含 {@link Void})，以及这些数据类型间的转换规则\n *\n * +----------+-------+------+-----+-------+------+------+---------+\n * |  double  | float | long | int | short | char | byte | boolean |\n * +----------+-------+------+-----+-------+------+------+---------+\n * </pre>\n *\n * @author Ponfee\n */\npublic enum PrimitiveTypes {\n\n    DOUBLE (Double.class   , (byte) 0B1_0_0_0_0_0_0_0, (byte) 0B1_0_0_0_0_0_0_0   ),\n    FLOAT  (Float.class    , (byte) 0B0_1_0_0_0_0_0_0, (byte) 0B1_1_0_0_0_0_0_0   ),\n    LONG   (Long.class     , (byte) 0B0_0_1_0_0_0_0_0, (byte) 0B1_1_1_0_0_0_0_0   ),\n    INT    (Integer.class  , (byte) 0B0_0_0_1_0_0_0_0, (byte) 0B1_1_1_1_0_0_0_0   ),\n    SHORT  (Short.class    , (byte) 0B0_0_0_0_1_0_0_0, (byte) 0B1_1_1_1_1_0_0_0   ), // short与char不能互相转换\n    CHAR   (Character.class, (byte) 0B0_0_0_0_0_1_0_0, (byte) 0B1_1_1_1_0_1_0_0   ), // short与char不能互相转换\n    BYTE   (Byte.class     , (byte) 0B0_0_0_0_0_0_1_0, (byte) 0B1_1_1_1_1_0_1_0   ), // byte不能转为char\n    BOOLEAN(Boolean.class  , (byte) 0B0_0_0_0_0_0_0_1, (byte) 0B0_0_0_0_0_0_0_1, 1), // boolean只能转boolean\n\n    ;\n\n    private final Class<?> wrapper;\n    private final byte value;    // 类型值\n    private final byte castable; // 支持转换到的目标类型\n    private final Class<?> primitive;\n    private final int size;\n\n    private static final Map<Class<?>, PrimitiveTypes> PRIMITIVE_MAPPING = Enums.toMap(PrimitiveTypes.class, PrimitiveTypes::primitive);\n    private static final Map<Class<?>, PrimitiveTypes> WRAPPER_MAPPING = Enums.toMap(PrimitiveTypes.class, PrimitiveTypes::wrapper);\n\n    PrimitiveTypes(Class<?> wrapper, byte value, byte castable) {\n        this(wrapper, value, castable, (int) Fields.get(wrapper, \"SIZE\"));\n    }\n\n    PrimitiveTypes(Class<?> wrapper, byte value, byte castable, int size) {\n        this.wrapper = wrapper;\n        this.value = value;\n        this.castable = castable;\n        this.primitive = (Class<?>) Fields.get(wrapper, \"TYPE\");\n        this.size = size;\n        Hide.PRIMITIVE_OR_WRAPPER_MAPPING.put(primitive, this);\n        Hide.PRIMITIVE_OR_WRAPPER_MAPPING.put(wrapper, this);\n    }\n\n    public Class<?> primitive() {\n        return primitive;\n    }\n\n    public Class<?> wrapper() {\n        return wrapper;\n    }\n\n    public int size() {\n        return size;\n    }\n\n    /**\n     * 用于判断传入方法真实的参数类型(this)是否能转换到方法定义的参数类型(target)\n     *\n     * @param target 目标参数类型\n     * @return {@code true}是，{@code false}否\n     */\n    public boolean isCastable(PrimitiveTypes target) {\n        return (this.castable & target.value) == target.value;\n    }\n\n    public static PrimitiveTypes ofPrimitive(Class<?> primitive) {\n        return PRIMITIVE_MAPPING.get(primitive);\n    }\n\n    public static PrimitiveTypes ofWrapper(Class<?> wrapper) {\n        return WRAPPER_MAPPING.get(wrapper);\n    }\n\n    public static PrimitiveTypes ofPrimitiveOrWrapper(Class<?> primitive) {\n        return Hide.PRIMITIVE_OR_WRAPPER_MAPPING.get(primitive);\n    }\n\n    public static Set<Class<?>> allPrimitiveTypes() {\n        return PRIMITIVE_MAPPING.keySet();\n    }\n\n    public static Set<Class<?>> allWrapperTypes() {\n        return WRAPPER_MAPPING.keySet();\n    }\n\n    public static boolean isWrapperType(Class<?> primitive) {\n        return ofWrapper(primitive) != null;\n    }\n\n    public static <T> Class<T> wrap(Class<T> type) {\n        PrimitiveTypes pt = ofPrimitiveOrWrapper(type);\n        return pt == null ? type : (Class<T>) pt.wrapper;\n    }\n\n    public static <T> Class<T> unwrap(Class<T> type) {\n        PrimitiveTypes pt = ofPrimitiveOrWrapper(type);\n        return pt == null ? type : (Class<T>) pt.primitive;\n    }\n\n    private static class Hide {\n        private static final Map<Class<?>, PrimitiveTypes> PRIMITIVE_OR_WRAPPER_MAPPING = new HashMap<>();\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/Releasable.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base;\n\nimport cn.ponfee.commons.exception.ServerException;\n\n/**\n * Release resources\n * \n * @author Ponfee\n */\n@FunctionalInterface\npublic interface Releasable {\n\n    NoArgMethodInvoker RELEASER = new NoArgMethodInvoker(\"close\", \"destroy\", \"release\");\n\n    /**\n     * 释放资源\n     */\n    void release();\n\n    static void release(Object caller) {\n        if (caller == null) {\n            return;\n        }\n\n        try {\n            if (caller instanceof AutoCloseable) {\n                ((AutoCloseable) caller).close();\n            } else if (caller instanceof Releasable) {\n                Releasable releasable = (Releasable) caller;\n                if (!releasable.isReleased()) {\n                    ((Releasable) caller).release();\n                }\n            } else {\n                RELEASER.invoke(caller);\n            }\n        } catch (RuntimeException e) {\n            throw e;\n        } catch (Exception e) {\n            throw new ServerException(e);\n        }\n    }\n\n    /**\n     * 是否已经释放，true为已经释放，false未释放\n     *\n     * @return {@code true}已经释放\n     */\n    default boolean isReleased() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/Symbol.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base;\n\n/**\n * Symbol definitions.\n *\n * @author Ponfee\n */\npublic final class Symbol {\n\n    public interface Str {\n        /**\n         * Zero symbol\n         */\n        String ZERO = \"\\u0000\";\n\n        /**\n         * Colon symbol\n         */\n        String COLON = \":\";\n\n        /**\n         * Comma symbol\n         */\n        String COMMA = \",\";\n\n        /**\n         * Dot symbol\n         */\n        String DOT = \".\";\n\n        /**\n         * Hyphen symbol\n         */\n        String HYPHEN = \"-\";\n\n        /**\n         * Slash symbol\n         */\n        String SLASH = \"/\";\n\n        /**\n         * Space symbol\n         */\n        String SPACE = \" \";\n\n        /**\n         * Tab symbol\n         */\n        String TAB = \"\t\";\n\n        /**\n         * Backslash symbol\n         */\n        String BACKSLASH = \"\\\\\";\n\n        /**\n         * CR symbol\n         */\n        String CR = \"\\r\";\n\n        /**\n         * LF symbol\n         */\n        String LF = \"\\n\";\n\n        /**\n         * Underscore symbol\n         */\n        String UNDERSCORE = \"_\";\n\n        /**\n         * Asterisk symbol\n         */\n        String ASTERISK = \"*\";\n\n        /**\n         * Semicolon symbol\n         */\n        String SEMICOLON = \";\";\n\n        /**\n         * Ampersand symbol\n         */\n        String AMPERSAND = \"&\";\n\n        /**\n         * Open symbol\n         */\n        String OPEN = \"(\";\n\n        /**\n         * Close symbol\n         */\n        String CLOSE = \")\";\n    }\n\n    public interface Char {\n        /**\n         * Zero char symbol, equals '\\0'\n         */\n        char ZERO = '\\u0000';\n\n        /**\n         * Colon symbol\n         */\n        char COLON = ':';\n\n        /**\n         * Comma symbol\n         */\n        char COMMA = ',';\n\n        /**\n         * Dot symbol\n         */\n        char DOT = '.';\n\n        /**\n         * Hyphen symbol\n         */\n        char HYPHEN = '-';\n\n        /**\n         * Slash symbol\n         */\n        char SLASH = '/';\n\n        /**\n         * Space symbol\n         */\n        char SPACE = ' ';\n\n        /**\n         * Tab symbol\n         */\n        char TAB = '\t';\n\n        /**\n         * Backslash symbol\n         */\n        char BACKSLASH = '\\\\';\n\n        /**\n         * CR symbol\n         */\n        char CR = '\\r';\n\n        /**\n         * LF symbol\n         */\n        char LF = '\\n';\n\n        /**\n         * Underscore symbol\n         */\n        char UNDERSCORE = '_';\n\n        /**\n         * Asterisk symbol\n         */\n        char ASTERISK = '*';\n\n        /**\n         * Semicolon symbol\n         */\n        char SEMICOLON = ';';\n\n        /**\n         * Ampersand symbol\n         */\n        char AMPERSAND = '&';\n\n        /**\n         * Open symbol\n         */\n        char OPEN = '(';\n\n        /**\n         * Close symbol\n         */\n        char CLOSE = ')';\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/TimestampProvider.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base;\n\n/**\n * 时间戳服务提供\n *\n * @author Ponfee\n */\n@FunctionalInterface\npublic interface TimestampProvider {\n\n    TimestampProvider EARLIEST = () -> Long.MIN_VALUE;\n    TimestampProvider CURRENT  = System::currentTimeMillis;\n    TimestampProvider LATEST   = () -> Long.MAX_VALUE;\n\n    long get();\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\nimport cn.ponfee.commons.collect.Comparators;\nimport cn.ponfee.commons.collect.DelegatedIntSpliterator;\nimport cn.ponfee.commons.collect.ImmutableArrayList;\nimport cn.ponfee.commons.util.ObjectUtils;\n\nimport java.io.Serializable;\nimport java.util.*;\nimport java.util.function.Function;\n\n/**\n * Abstract Tuple type.\n *\n * @author Ponfee\n */\npublic abstract class Tuple implements Comparable<Object>, Iterable<Object>, Serializable {\n    private static final long serialVersionUID = -3292038317953347997L;\n\n    /**\n     * Get the object at the given index.\n     *\n     * @param index The index of the object to retrieve. Starts at 0.\n     * @return The object or {@literal null} if out of bounds.\n     */\n    public abstract <T> T get(int index);\n\n    /**\n     * Set the value at the given index.\n     *\n     * @param value The object value.\n     * @param index The index of the object to retrieve. Starts at 0.\n     */\n    public abstract <T> void set(T value, int index);\n\n    /**\n     * Returns a copy of this instance.\n     *\n     * @return a copy of this instance.\n     */\n    public abstract <T extends Tuple> T copy();\n\n    /**\n     * Returns int value of this tuple elements count.\n     *\n     * @return int value of this tuple elements count.\n     */\n    public abstract int length();\n\n    /**\n     * Turn this {@code Tuple} into a plain {@code Object[]}.\n     * The array isn't tied to this Tuple but is a <strong>copy</strong>.\n     *\n     * @return A copy of the tuple as a new {@link Object Object[]}.\n     */\n    public final Object[] toArray() {\n        int len = length();\n        Object[] array = new Object[len];\n        for (int i = 0; i < len; i++) {\n            array[i] = get(i);\n        }\n        return array;\n    }\n\n    /**\n     * Returns a string representation of the object.\n     *\n     * @return a string representation of the Tuple.\n     */\n    @Override\n    public final String toString() {\n        return join(\", \", String::valueOf, \"(\", \")\");\n    }\n\n    /**\n     * Returns a hash code value for the object.\n     *\n     * @return a hash code value for this object.\n     */\n    @Override\n    public final int hashCode() {\n        int len = length();\n        if (len < 1) {\n            return 0;\n        }\n\n        int hash = Objects.hashCode(get(0));\n        for (int i = 1; i < len; i++) {\n            hash = 31 * hash + Objects.hashCode(get(i));\n        }\n        return hash;\n    }\n\n    /**\n     * Indicates whether some other object is \"equal to\" this one.\n     *\n     * @param obj the reference object with which to compare.\n     * @return {@code true} if this object equals the other.\n     */\n    @Override\n    public final boolean equals(Object obj) {\n        if (this == obj) {\n            return true;\n        }\n\n        if (obj == null || obj.getClass() != this.getClass()) {\n            return false;\n        }\n\n        Tuple other = (Tuple) obj;\n        for (int i = 0, len = length(); i < len; i++) {\n            if (!Objects.equals(this.get(i), other.get(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Returns tuple elements are equals array elements.\n     *\n     * @param elements the elements\n     * @return {@code true} if elements equals.\n     */\n    public final boolean equals(Object... elements) {\n        int len;\n        if (elements == null || elements.length != (len = length())) {\n            return false;\n        }\n        for (int i = 0; i < len; i++) {\n            if (!Objects.equals(get(i), elements[i])) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public final int compareTo(Object o) {\n        if (this == o) {\n            return Comparators.EQ;\n        }\n        if (!(o instanceof Tuple)) {\n            return ObjectUtils.compare(this, o);\n        }\n\n        Tuple other = (Tuple) o;\n        for (int c, i = 0, n = this.length(); i < n; i++) {\n            c = ObjectUtils.compare(this.get(i), other.get(i));\n            if (c != Comparators.EQ) {\n                return c;\n            }\n        }\n\n        return Comparators.EQ;\n    }\n\n    /**\n     * Turn this {@code Tuple} into a {@link List List&lt;Object&gt;}.\n     * The list isn't tied to this Tuple but is a <strong>copy</strong> with limited\n     * mutability ({@code add} and {@code remove} are not supported, but {@code set} is).\n     *\n     * @return A copy of the tuple as a new {@link List List&lt;Object&gt;}.\n     */\n    public List<Object> toList() {\n        return ImmutableArrayList.of(toArray());\n    }\n\n    /**\n     * Return an <strong>immutable</strong> {@link Iterator Iterator&lt;Object&gt;} around\n     * the content of this {@code Tuple}.\n     *\n     * @return An unmodifiable {@link Iterator} over the elements in this Tuple.\n     * @implNote As an {@link Iterator} is always tied to its {@link Iterable} source by\n     * definition, the iterator cannot be mutable without the iterable also being mutable.\n     */\n    @Override\n    public Iterator<Object> iterator() {\n        // Also use: toList().iterator();\n        return new TupleIterator<>();\n    }\n\n    @Override\n    public Spliterator<Object> spliterator() {\n        return new DelegatedIntSpliterator<>(0, length(), this::get);\n    }\n\n    /**\n     * Returns string of joined the tuple elements.\n     *\n     * @param delimiter   the delimiter\n     * @param valueMapper the valueMapper for each element to string function\n     * @param prefix      the prefix\n     * @param suffix      the suffix\n     * @return string of joined the tuple elements\n     */\n    public final String join(CharSequence delimiter,\n                             Function<Object, String> valueMapper,\n                             CharSequence prefix,\n                             CharSequence suffix) {\n        StringBuilder builder = new StringBuilder(prefix);\n        for (int i = 0, n = length() - 1; i <= n; i++) {\n            builder.append(valueMapper.apply(get(i)));\n            if (i < n) {\n                builder.append(delimiter);\n            }\n        }\n        return builder.append(suffix).toString();\n    }\n\n    /**\n     * Tuple Iterator\n     *\n     * @param <T> element type\n     */\n    private class TupleIterator<T> implements Iterator<T> {\n        private int position = 0;\n        private final int size = length();\n\n        @Override\n        public boolean hasNext() {\n            return position < size;\n        }\n\n        @Override\n        public T next() {\n            if (!hasNext()) {\n                throw new NoSuchElementException();\n            }\n            return get(position++);\n        }\n\n        @Override\n        public void remove() {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple0.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\nimport java.util.*;\n\n/**\n * Tuple0 consisting of empty element.\n *\n * @author Ponfee\n */\npublic final class Tuple0 extends Tuple {\n    private static final long serialVersionUID = -3627925720098458172L;\n    private static final Tuple0 INSTANCE = new Tuple0();\n\n    public Tuple0() {\n    }\n\n    public static Tuple0 of() {\n        return INSTANCE;\n    }\n\n    @Override\n    public <T> T get(int index) {\n        throw new IndexOutOfBoundsException(\"Index: \" + index);\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        throw new IndexOutOfBoundsException(\"Index: \" + index);\n    }\n\n    @Override\n    public int length() {\n        return 0;\n    }\n\n    @Override\n    public Tuple0 copy() {\n        return INSTANCE;\n    }\n\n    @Override\n    public List<Object> toList() {\n        return Collections.emptyList();\n    }\n\n    @Override\n    public Iterator<Object> iterator() {\n        return Collections.emptyIterator();\n    }\n\n    @Override\n    public Spliterator<Object> spliterator() {\n        return Spliterators.emptySpliterator();\n    }\n\n    private Object readResolve() {\n        return INSTANCE;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple1.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\n/**\n * Tuple1 consisting of one element.\n *\n * @author Ponfee\n */\npublic final class Tuple1<A> extends Tuple {\n    private static final long serialVersionUID = -3627925720098458172L;\n\n    public A a;\n\n    public Tuple1(A a) {\n        this.a = a;\n    }\n\n    public static <A> Tuple1<A> of(A a) {\n        return new Tuple1<>(a);\n    }\n\n    @Override\n    public <T> T get(int index) {\n        if (index == 0) {\n            return (T) a;\n        } else {\n            throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        if (index == 0) {\n            a = (A) value;\n        } else {\n            throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public int length() {\n        return 1;\n    }\n\n    @Override\n    public Tuple1<A> copy() {\n        return new Tuple1<>(a);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple2.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\n/**\n * Tuple2 consisting of two elements.\n *\n * @author Ponfee\n */\npublic final class Tuple2<A, B> extends Tuple {\n    private static final long serialVersionUID = -3627925720098458172L;\n\n    public A a;\n    public B b;\n\n    public Tuple2(A a, B b) {\n        this.a = a;\n        this.b = b;\n    }\n\n    public static <A, B> Tuple2<A, B> of(A a, B b) {\n        return new Tuple2<>(a, b);\n    }\n\n    @Override\n    public <T> T get(int index) {\n        switch (index) {\n            case  0: return (T) a;\n            case  1: return (T) b;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        switch (index) {\n            case  0: a = (A) value; break;\n            case  1: b = (B) value; break;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public int length() {\n        return 2;\n    }\n\n    @Override\n    public Tuple2<A, B> copy() {\n        return new Tuple2<>(a, b);\n    }\n\n    /**\n     * Returns a Tuple2 Object of this instance swapped values.\n     *\n     * @return a Tuple2 Object of this instance swapped values\n     */\n    public Tuple2<B, A> swap() {\n        return new Tuple2<>(b, a);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple3.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\n/**\n * Tuple3 consisting of three elements.\n *\n * @author Ponfee\n */\npublic final class Tuple3<A, B, C> extends Tuple {\n    private static final long serialVersionUID = -8101132015890693468L;\n\n    public A a;\n    public B b;\n    public C c;\n\n    public Tuple3(A a, B b, C c) {\n        this.a = a;\n        this.b = b;\n        this.c = c;\n    }\n\n    public static <A, B, C> Tuple3<A, B, C> of(A a, B b, C c) {\n        return new Tuple3<>(a, b, c);\n    }\n\n    @Override\n    public <T> T get(int index) {\n        switch (index) {\n            case  0: return (T) a;\n            case  1: return (T) b;\n            case  2: return (T) c;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        switch (index) {\n            case  0: a = (A) value; break;\n            case  1: b = (B) value; break;\n            case  2: c = (C) value; break;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public int length() {\n        return 3;\n    }\n\n    @Override\n    public Tuple3<A, B, C> copy() {\n        return new Tuple3<>(a, b, c);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple4.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\n/**\n * Tuple4 consisting of four elements.\n *\n * @author Ponfee\n */\npublic final class Tuple4<A, B, C, D> extends Tuple {\n    private static final long serialVersionUID = -4282006520880127762L;\n\n    public A a;\n    public B b;\n    public C c;\n    public D d;\n\n    public Tuple4(A a, B b, C c, D d) {\n        this.a = a;\n        this.b = b;\n        this.c = c;\n        this.d = d;\n    }\n\n    public static <A, B, C, D> Tuple4<A, B, C, D> of(A a, B b, C c, D d) {\n        return new Tuple4<>(a, b, c, d);\n    }\n\n    @Override\n    public <T> T get(int index) {\n        switch (index) {\n            case  0: return (T) a;\n            case  1: return (T) b;\n            case  2: return (T) c;\n            case  3: return (T) d;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        switch (index) {\n            case  0: a = (A) value; break;\n            case  1: b = (B) value; break;\n            case  2: c = (C) value; break;\n            case  3: d = (D) value; break;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public int length() {\n        return 4;\n    }\n\n    @Override\n    public Tuple4<A, B, C, D> copy() {\n        return new Tuple4<>(a, b, c, d);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple5.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\n/**\n * Tuple5 consisting of five elements.\n *\n * @author Ponfee\n */\npublic final class Tuple5<A, B, C, D, E> extends Tuple {\n    private static final long serialVersionUID = -528096819207260665L;\n\n    public A a;\n    public B b;\n    public C c;\n    public D d;\n    public E e;\n\n    public Tuple5(A a, B b, C c, D d, E e) {\n        this.a = a;\n        this.b = b;\n        this.c = c;\n        this.d = d;\n        this.e = e;\n    }\n\n    public static <A, B, C, D, E> Tuple5<A, B, C, D, E> of(A a, B b, C c, D d, E e) {\n        return new Tuple5<>(a, b, c, d, e);\n    }\n\n    @Override\n    public <T> T get(int index) {\n        switch (index) {\n            case  0: return (T) a;\n            case  1: return (T) b;\n            case  2: return (T) c;\n            case  3: return (T) d;\n            case  4: return (T) e;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        switch (index) {\n            case  0: a = (A) value; break;\n            case  1: b = (B) value; break;\n            case  2: c = (C) value; break;\n            case  3: d = (D) value; break;\n            case  4: e = (E) value; break;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public int length() {\n        return 5;\n    }\n\n    @Override\n    public Tuple5<A, B, C, D, E> copy() {\n        return new Tuple5<>(a, b, c, d, e);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple6.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\n/**\n * Tuple6 consisting of six elements.\n *\n * @author Ponfee\n */\npublic final class Tuple6<A, B, C, D, E, F> extends Tuple {\n    private static final long serialVersionUID = 8697978867751048118L;\n\n    public A a;\n    public B b;\n    public C c;\n    public D d;\n    public E e;\n    public F f;\n\n    public Tuple6(A a, B b, C c, D d, E e, F f) {\n        this.a = a;\n        this.b = b;\n        this.c = c;\n        this.d = d;\n        this.e = e;\n        this.f = f;\n    }\n\n    public static <A, B, C, D, E, F> Tuple6<A, B, C, D, E, F> of(A a, B b, C c, D d, E e, F f) {\n        return new Tuple6<>(a, b, c, d, e, f);\n    }\n\n    @Override\n    public <T> T get(int index) {\n        switch (index) {\n            case  0: return (T) a;\n            case  1: return (T) b;\n            case  2: return (T) c;\n            case  3: return (T) d;\n            case  4: return (T) e;\n            case  5: return (T) f;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        switch (index) {\n            case  0: a = (A) value; break;\n            case  1: b = (B) value; break;\n            case  2: c = (C) value; break;\n            case  3: d = (D) value; break;\n            case  4: e = (E) value; break;\n            case  5: f = (F) value; break;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public int length() {\n        return 6;\n    }\n\n    @Override\n    public Tuple6<A, B, C, D, E, F> copy() {\n        return new Tuple6<>(a, b, c, d, e, f);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple7.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\n/**\n * Tuple7 consisting of seven elements.\n *\n * @author Ponfee\n */\npublic final class Tuple7<A, B, C, D, E, F, G> extends Tuple {\n    private static final long serialVersionUID = 4235194450172178770L;\n\n    public A a;\n    public B b;\n    public C c;\n    public D d;\n    public E e;\n    public F f;\n    public G g;\n\n    public Tuple7(A a, B b, C c, D d, E e, F f, G g) {\n        this.a = a;\n        this.b = b;\n        this.c = c;\n        this.d = d;\n        this.e = e;\n        this.f = f;\n        this.g = g;\n    }\n\n    public static <A, B, C, D, E, F, G> Tuple7<A, B, C, D, E, F, G> of(A a, B b, C c, D d, E e, F f, G g) {\n        return new Tuple7<>(a, b, c, d, e, f, g);\n    }\n\n    @Override\n    public <T> T get(int index) {\n        switch (index) {\n            case  0: return (T) a;\n            case  1: return (T) b;\n            case  2: return (T) c;\n            case  3: return (T) d;\n            case  4: return (T) e;\n            case  5: return (T) f;\n            case  6: return (T) g;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        switch (index) {\n            case  0: a = (A) value; break;\n            case  1: b = (B) value; break;\n            case  2: c = (C) value; break;\n            case  3: d = (D) value; break;\n            case  4: e = (E) value; break;\n            case  5: f = (F) value; break;\n            case  6: g = (G) value; break;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public int length() {\n        return 7;\n    }\n\n    @Override\n    public Tuple7<A, B, C, D, E, F, G> copy() {\n        return new Tuple7<>(a, b, c, d, e, f, g);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple8.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\n/**\n * Tuple8 consisting of eight elements.\n *\n * @author Ponfee\n */\npublic final class Tuple8<A, B, C, D, E, F, G, H> extends Tuple {\n    private static final long serialVersionUID = 3607273779775623549L;\n\n    public A a;\n    public B b;\n    public C c;\n    public D d;\n    public E e;\n    public F f;\n    public G g;\n    public H h;\n\n    public Tuple8(A a, B b, C c, D d, E e, F f, G g, H h) {\n        this.a = a;\n        this.b = b;\n        this.c = c;\n        this.d = d;\n        this.e = e;\n        this.f = f;\n        this.g = g;\n        this.h = h;\n    }\n\n    public static <A, B, C, D, E, F, G, H> Tuple8<A, B, C, D, E, F, G, H> of(A a, B b, C c, D d, E e, F f, G g, H h) {\n        return new Tuple8<>(a, b, c, d, e, f, g, h);\n    }\n\n    @Override\n    public <T> T get(int index) {\n        switch (index) {\n            case  0: return (T) a;\n            case  1: return (T) b;\n            case  2: return (T) c;\n            case  3: return (T) d;\n            case  4: return (T) e;\n            case  5: return (T) f;\n            case  6: return (T) g;\n            case  7: return (T) h;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        switch (index) {\n            case  0: a = (A) value; break;\n            case  1: b = (B) value; break;\n            case  2: c = (C) value; break;\n            case  3: d = (D) value; break;\n            case  4: e = (E) value; break;\n            case  5: f = (F) value; break;\n            case  6: g = (G) value; break;\n            case  7: h = (H) value; break;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public int length() {\n        return 8;\n    }\n\n    @Override\n    public Tuple8<A, B, C, D, E, F, G, H> copy() {\n        return new Tuple8<>(a, b, c, d, e, f, g, h);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/base/tuple/Tuple9.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.base.tuple;\n\n/**\n * Tuple9 consisting of nine elements.\n *\n * @author Ponfee\n */\npublic final class Tuple9<A, B, C, D, E, F, G, H, I> extends Tuple {\n    private static final long serialVersionUID = 3462929449266307061L;\n\n    public A a;\n    public B b;\n    public C c;\n    public D d;\n    public E e;\n    public F f;\n    public G g;\n    public H h;\n    public I i;\n\n    public Tuple9(A a, B b, C c, D d, E e, F f, G g, H h, I i) {\n        this.a = a;\n        this.b = b;\n        this.c = c;\n        this.d = d;\n        this.e = e;\n        this.f = f;\n        this.g = g;\n        this.h = h;\n        this.i = i;\n    }\n\n    public static <A, B, C, D, E, F, G, H, I> Tuple9<A, B, C, D, E, F, G, H, I> of(A a, B b, C c, D d, E e, F f, G g, H h, I i) {\n        return new Tuple9<>(a, b, c, d, e, f, g, h, i);\n    }\n\n    @Override\n    public <T> T get(int index) {\n        switch (index) {\n            case  0: return (T) a;\n            case  1: return (T) b;\n            case  2: return (T) c;\n            case  3: return (T) d;\n            case  4: return (T) e;\n            case  5: return (T) f;\n            case  6: return (T) g;\n            case  7: return (T) h;\n            case  8: return (T) i;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public <T> void set(T value, int index) {\n        switch (index) {\n            case  0: a = (A) value; break;\n            case  1: b = (B) value; break;\n            case  2: c = (C) value; break;\n            case  3: d = (D) value; break;\n            case  4: e = (E) value; break;\n            case  5: f = (F) value; break;\n            case  6: g = (G) value; break;\n            case  7: h = (H) value; break;\n            case  8: i = (I) value; break;\n            default: throw new IndexOutOfBoundsException(\"Index: \" + index);\n        }\n    }\n\n    @Override\n    public int length() {\n        return 9;\n    }\n\n    @Override\n    public Tuple9<A, B, C, D, E, F, G, H, I> copy() {\n        return new Tuple9<>(a, b, c, d, e, f, g, h, i);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/ArrayHashKey.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport org.apache.commons.lang3.builder.CompareToBuilder;\n\nimport java.util.Arrays;\n\n/**\n * The class use in Object array as hash map key\n * <p>Use for HashMap key\n *\n * @author Ponfee\n */\npublic final class ArrayHashKey implements java.io.Serializable, Comparable<ArrayHashKey> {\n\n    private static final long serialVersionUID = -8749483734287105153L;\n\n    private final Object[] key;\n\n    public ArrayHashKey(Object... key) {\n        this.key = key;\n    }\n\n    public static ArrayHashKey of(Object... key) {\n        return new ArrayHashKey(key);\n    }\n\n    @Override\n    public boolean equals(Object other) {\n        if (other == this) {\n            return true;\n        }\n        return other instanceof ArrayHashKey && Arrays.equals(key, ((ArrayHashKey) other).key);\n    }\n\n    @Override\n    public int hashCode() {\n        return Arrays.hashCode(key);\n    }\n\n    @Override\n    public int compareTo(ArrayHashKey o) {\n        return new CompareToBuilder().append(key, o.key).toComparison();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/ByteArrayComparator.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\n/**\n * The utility for compare two byte array\n * \n * For compare\n * \n * @author Ponfee\n */\npublic final class ByteArrayComparator {\n\n    public static int compareTo(final byte[] left, final byte[] right) {\n        return compareTo(left, 0, left.length, right, 0, right.length);\n    }\n\n    public static int compareTo(byte[] buffer1, int offset1, int length1,\n                                byte[] buffer2, int offset2, int length2) {\n        // Short circuit equal case\n        if (buffer1 == buffer2 &&\n            offset1 == offset2 &&\n            length1 == length2) {\n            return 0;\n        }\n        // Bring WritableComparator code local\n        int end1 = offset1 + length1;\n        int end2 = offset2 + length2;\n        for (int i = offset1, j = offset2; i < end1 && j < end2; i++, j++) {\n            int a = (buffer1[i] & 0xff);\n            int b = (buffer2[j] & 0xff);\n            if (a != b) {\n                return a - b;\n            }\n        }\n        return length1 - length2;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/ByteArrayTrait.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\n/**\n * Represents the class has an byte[] array args constructor and a toByteArray method\n * \n * For serialize\n * \n * @author Ponfee\n */\npublic abstract class ByteArrayTrait {\n\n    public ByteArrayTrait(byte[] array) {}\n\n    /**\n     * Returns byte array\n     *\n     * @return byte array\n     */\n    public abstract byte[] toByteArray();\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/ByteArrayWrapper.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.util.Arrays;\n\n/**\n * The class use in byte array as hash map key\n * \n * For HashMap key\n * \n * @author Ponfee\n * @see org.springframework.data.redis.connection.util.ByteArrayWrapper\n */\npublic final class ByteArrayWrapper implements java.io.Serializable, Comparable<ByteArrayWrapper> {\n    private static final long serialVersionUID = -8749483734287105153L;\n\n    private final byte[] array;\n    private final int hashCode;\n\n    public ByteArrayWrapper(Byte... array) {\n        this(ArrayUtils.toPrimitive(array));\n    }\n\n    public ByteArrayWrapper(byte... array) {\n        this.array = array;\n        this.hashCode = Arrays.hashCode(array);\n    }\n\n    public static ByteArrayWrapper of(byte... array) {\n        return new ByteArrayWrapper(array);\n    }\n\n    @Override\n    public boolean equals(Object other) {\n        if (other == this) {\n            return true;\n        }\n        if (other instanceof ByteArrayWrapper) {\n            return Arrays.equals(array, ((ByteArrayWrapper) other).array);\n        }\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        return hashCode;\n    }\n\n    @Override\n    public int compareTo(ByteArrayWrapper o) {\n        return ByteArrayComparator.compareTo(array, o.array);\n        //return Bytes.toBigInteger(array).compareTo(Bytes.toBigInteger(o.array));\n    }\n\n    /**\n     * Returns the byte array\n     * \n     * @return a byte array\n     */\n    public byte[] getArray() {\n        return array;\n    }\n\n    @Override\n    public String toString() {\n        return \"ByteArrayWrapper[\" + (array == null ? \"null\" : array.length) + \"]\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/Collects.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport cn.ponfee.commons.base.Predicates;\nimport cn.ponfee.commons.math.Numbers;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.springframework.util.Assert;\n\nimport java.lang.reflect.Array;\nimport java.util.*;\nimport java.util.Map.Entry;\nimport java.util.function.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\n\n/**\n * 集合工具类\n *\n * @author Ponfee\n */\npublic final class Collects {\n\n    /**\n     * 转数组\n     * @param args\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T[] toArray(T... args) {\n        return args;\n    }\n\n    /**\n     * object to list\n     * @param obj of elements\n     * @return list with the same elements\n     */\n    public static List<Object> toList(Object obj) {\n        if (obj == null) {\n            return null;\n        }\n        if (obj.getClass().isArray()) {\n            int length = Array.getLength(obj);\n            List<Object> result = new ArrayList<>(length);\n            for (int i = 0; i < length; i++) {\n                result.add(Array.get(obj, i));\n            }\n            return result;\n        }\n        if (obj instanceof Collection) {\n            return new ArrayList<>((Collection<?>) obj);\n        }\n        return Collections.singletonList(obj);\n\n    }\n\n    public static <E> LinkedList<E> newLinkedList(E element) {\n        LinkedList<E> list = new LinkedList<>();\n        list.add(element);\n        return list;\n    }\n\n    /**\n     * Gets the first element for values\n     *\n     * @param values the values\n     * @param <T>    the values element type\n     * @return first element of values\n     */\n    public static <T> T getFirst(Collection<T> values) {\n        if (values == null || values.isEmpty()) {\n            return null;\n        }\n        if (values instanceof Deque) {\n            return ((Deque<T>) values).getFirst();\n        }\n        if (values instanceof List) {\n            return ((List<T>) values).get(0);\n        }\n        return values.iterator().next();\n    }\n\n    /**\n     * Gets the last element for values\n     *\n     * @param values the values\n     * @param <T>    the values element type\n     * @return last element of values\n     */\n    public static <T> T getLast(Collection<T> values) {\n        if (values == null || values.isEmpty()) {\n            return null;\n        }\n        if (values instanceof Deque) {\n            return ((Deque<T>) values).getLast();\n        }\n        if (values instanceof List) {\n            return ((List<T>) values).get(values.size() - 1);\n        }\n        return values.stream().reduce((a, b) -> b).orElse(null);\n    }\n\n    public static <T> T get(T[] array, int index) {\n        if (array == null || index < 0 || index >= array.length) {\n            return null;\n        }\n        return index < array.length ? array[index] : null;\n    }\n\n    public static <T> T get(List<T> list, int index) {\n        if (list == null || index < 0 || index >= list.size()) {\n            return null;\n        }\n        return list.get(index);\n    }\n\n    // -----------------------------the collection of intersect, union and different operations\n    /**\n     * two Collection intersect\n     * intersect([1,2,3], [2,3,4]) = [2,3]\n     *\n     * @param coll1 the collection 1\n     * @param coll2 the collection 2\n     * @return a list of the two collection intersect result\n     */\n    public static <T> List<T> intersect(Collection<T> coll1, Collection<T> coll2) {\n        return coll1.stream().filter(coll2::contains).collect(Collectors.toList());\n    }\n\n    /**\n     * two array intersect\n     *\n     * @param array1\n     * @param array2\n     * @return\n     */\n    @SuppressWarnings({ \"unchecked\" })\n    public static <T> T[] intersect(T[] array1, T[] array2) {\n        List<T> list = Stream.of(array1)\n                             .filter(t -> ArrayUtils.contains(array2, t))\n                             .collect(Collectors.toList());\n\n        Class<?> type = array1.getClass().getComponentType();\n        return list.toArray((T[]) Array.newInstance(type, list.size()));\n    }\n\n    /**\n     * two Collection union result\n     *\n     * @param coll1\n     * @param coll2\n     * @return\n     */\n    public static <T> List<T> union(Collection<T> coll1, Collection<T> coll2) {\n        int max = coll1.size(), min = coll2.size();\n        if (max < min) {\n            int tmp = max;\n            max = min;\n            min = tmp;\n        }\n        List<T> res = new ArrayList<>(max + (min >> 1));\n        res.addAll(coll1);\n        coll2.stream().filter(Predicates.not(coll1::contains)).forEach(res::add);\n        return res;\n    }\n\n    /**\n     * list差集\n     * different([1,2,3], [2,3,4]) = [1,4]\n     *\n     * @param list1\n     * @param list2\n     * @return\n     */\n    public static <T> List<T> different(List<T> list1, List<T> list2) {\n        List<T> res = new ArrayList<>();\n        list1.stream().filter(Predicates.not(list2::contains)).forEach(res::add);\n        list2.stream().filter(Predicates.not(list1::contains)).forEach(res::add);\n        return res;\n    }\n\n    /**\n     * The two set different elements\n     *\n     * @param set1\n     * @param set2\n     * @return\n     */\n    public static <T> Set<T> different(Set<T> set1, Set<T> set2) {\n        Set<T> res = new HashSet<>();\n        set1.stream().filter(Predicates.not(set2::contains)).forEach(res::add);\n        set2.stream().filter(Predicates.not(set1::contains)).forEach(res::add);\n        return res;\n    }\n\n    /**\n     * map差集\n     *\n     * @param map1\n     * @param map2\n     * @return\n     */\n    public static <K, V> Map<K, V> different(Map<K, V> map1, Map<K, V> map2) {\n        Map<K, V> res = new HashMap<>(Math.max(map1.size(), map2.size()));\n        map1.entrySet()\n            .stream()\n            .filter(e -> !map2.containsKey(e.getKey()))\n            .forEach(e -> res.put(e.getKey(), e.getValue()));\n\n        map2.entrySet()\n            .stream()\n            .filter(e -> !map1.containsKey(e.getKey()))\n            .forEach(e -> res.put(e.getKey(), e.getValue()));\n        return res;\n    }\n\n    public static <T> List<T> duplicate(Collection<T> list) {\n        return duplicate(list, Function.identity());\n    }\n\n    /**\n     * Returns the duplicates elements for list\n     *\n     * @param list the list\n     * @return a set of duplicates elements for list\n     */\n    public static <T, R> List<R> duplicate(Collection<T> list, Function<T, R> mapper) {\n        if (CollectionUtils.isEmpty(list)) {\n            return Collections.emptyList();\n        }\n\n        return list.stream()\n                   .map(mapper)\n                   .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))\n                   .entrySet()\n                   .stream()\n                   .filter(e -> e.getValue() > 1)\n                   .map(Entry::getKey)\n                   .collect(Collectors.toList());\n    }\n\n    /**\n     * Returns a new array for merged the generic array generator\n     *\n     * @see org.apache.commons.lang3.ArrayUtils#addAll(T[] array1, T... array2)\n     * @param generator the generic array generator\n     * @param arrays the multiple generic object array\n     * @return a new array of merged\n     */\n    @SuppressWarnings({ \"unchecked\" })\n    public static <T> T[] concat(IntFunction<T[]> generator, T[]... arrays) {\n        // component type maybe not correct\n        //Class<?> type = arrays[0].getClass().getComponentType();\n        //return list.toArray((T[]) Array.newInstance(type, list.size()));\n\n        // [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;\n        //return list.toArray((T[]) new Object[list.size()]);\n\n        if (ArrayUtils.isEmpty(arrays)) {\n            return null;\n        }\n\n        return Arrays.stream(arrays)\n                     .filter(Objects::nonNull)\n                     .flatMap(Arrays::stream)\n                     .toArray(generator);\n    }\n\n    /**\n     * Puts the element to list specified index\n     *\n     * @param list a list\n     * @param index spec index\n     * @param obj the element\n     */\n    public static <T> void set(List<T> list, int index, T obj) {\n        for (int i = list.size(); i <= index; i++) {\n            list.add(null);\n        }\n        list.set(index, obj);\n    }\n\n    /**\n     * Expand the list size\n     *\n     * @param list     the list\n     * @param size     the target size\n     * @param supplier element provider\n     * @param <T>      the element type\n     */\n    public static <T> void expand(List<T> list, int size, Supplier<T> supplier) {\n        for (int i = list.size(); i <= size; i++) {\n            list.add(supplier.get());\n        }\n    }\n\n    /**\n     * Returns consecutive sub array of an array,\n     * each of the same size (the final list may be smaller).\n     *\n     * <pre>\n     *  Collects.partition(new int[]{1,1,2,5,3}, 1)    ->  [1, 1, 2, 5, 3]\n     *  Collects.partition(new int[]{1,1,2,5,3}, 3)    ->  [1, 1]; [2, 5]; [3]\n     *  Collects.partition(new int[]{1,1,2,5,3}, 5)    ->  [1]; [1]; [2]; [5]; [3]\n     *  Collects.partition(new int[]{1,1,2,5,3}, 6)    ->  [1]; [1]; [2]; [5]; [3]\n     *  Collects.partition(new int[]{1,1,2,5,3}, 100)  ->  [1]; [1]; [2]; [5]; [3]\n     * </pre>\n     *\n     * @param array the array\n     * @param size  the size\n     * @return a list of consecutive sub sets\n     */\n    public static List<int[]> partition(int[] array, int size) {\n        Assert.isTrue(size > 0, \"Size must be greater than 0.\");\n        if (array == null || array.length == 0) {\n            return null;\n        }\n        size = Math.min(size, array.length);\n        if (size == 1) {\n            return Collections.singletonList(array);\n        }\n\n        List<int[]> result = new ArrayList<>(size);\n        int pos = 0;\n        for (int number : Numbers.slice(array.length, size)) {\n            if (number == 0) {\n                break;\n            }\n            result.add(Arrays.copyOfRange(array, pos, pos = pos + number));\n        }\n        return result;\n    }\n\n    /**\n     * Compute cartesian product\n     *\n     * [1, 2, 3] x [4, 5, 6] = [[4, 5, 6], [8, 10, 12], [12, 15, 18]]\n     *\n     * @param x the list of type A\n     * @param y the list of type B\n     * @param fun convert A and B to T\n     * @return a list of type T\n     */\n    public static <A, B, T> List<List<T>> cartesian(List<A> x, List<B> y, BiFunction<A, B, T> fun) {\n        List<List<T>> product = new ArrayList<>(x.size());\n        for (A a : x) {\n            List<T> row = new ArrayList<>(y.size());\n            for (B b : y) {\n                row.add(fun.apply(a, b));\n            }\n            product.add(row);\n        }\n        return product;\n    }\n\n    /**\n     * Rotate list array data\n     *\n     * [[a,b,c,d],[1,2,3,4]] -> [[a,1],[b,2],[c,3],[d,4]]\n     *\n     * @param list the list\n     * @return a list array result\n     */\n    public static List<Object[]> rotate(List<Object[]> list) {\n        if (list == null || list.isEmpty()) {\n            return null;\n        }\n\n        int length = list.get(0).length, size = list.size();\n        List<Object[]> result = new ArrayList<>(length);\n        for (int i = 0; i < length; i++) {\n            Object[] array = new Object[size];\n            for (int j = 0; j < size; j++) {\n                array[j] = list.get(j)[i];\n            }\n            result.add(array);\n        }\n        return result;\n    }\n\n    public static int[] sortAndGetIndexSwapMapping(int[] array) {\n        int[] indexSwapMapping = IntStream.range(0, array.length).toArray();\n        for (int n = array.length - 1, i = 0; i < n; i++) {\n            int minimumIndex = i;\n            for (int j = i + 1; j <= n; j++) {\n                if (array[minimumIndex] > array[j]) {\n                    minimumIndex = j;\n                }\n            }\n            if (minimumIndex != i) {\n                ArrayUtils.swap(array, i, minimumIndex);\n                ArrayUtils.swap(indexSwapMapping, i, minimumIndex);\n            }\n        }\n        return indexSwapMapping;\n    }\n\n    /**\n     * Checks that the specified array reference is not null and not empty,\n     * throws a customized {@link IllegalStateException} if it is.\n     *\n     * @param array the array\n     * @param <T> the type of the array element\n     * @return {@code array} if not null and not empty\n     */\n    public static <T> T[] requireNonEmpty(T[] array) {\n        if (ArrayUtils.isEmpty(array)) {\n            throw new IllegalStateException(\"The array cannot be empty.\");\n        }\n        return array;\n    }\n\n    /**\n     * Checks that the specified list reference is not null and not empty,\n     * throws a customized {@link IllegalStateException} if it is.\n     *\n     * @param list the list\n     * @param <T> the type of the list element\n     * @return {@code list} if not null and not empty\n     */\n    public static <T> List<T> requireNonEmpty(List<T> list) {\n        if (CollectionUtils.isEmpty(list)) {\n            throw new IllegalStateException(\"The list cannot be empty.\");\n        }\n        return list;\n    }\n\n    public static <E, R> List<R> convert(Collection<E> collection, Function<E, R> mapper) {\n        return convert(collection, null, mapper);\n    }\n\n    public static <E, R> List<R> convert(Collection<E> collection, Predicate<? super E> predicate, Function<E, R> mapper) {\n        if (collection == null) {\n            return null;\n        }\n        if (collection.isEmpty()) {\n            return Collections.emptyList();\n        }\n\n        List<R> result = new ArrayList<>(collection.size());\n        Stream<E> stream = collection.stream();\n        if (predicate != null) {\n            stream = stream.filter(predicate);\n        }\n        stream.map(mapper).forEach(result::add);\n\n        return result;\n    }\n\n    @SafeVarargs\n    public static <T> List<T> concat(List<T> list, T... array) {\n        if (list == null) {\n            return array == null ? Collections.emptyList() : Arrays.asList(array);\n        }\n        if (array == null || array.length == 0) {\n            return list;\n        }\n        List<T> result = new ArrayList<>(list.size() + array.length);\n        result.addAll(list);\n        Collections.addAll(result, array);\n        return result;\n    }\n\n    public static <T> T[] newArray(Class<? extends T[]> arrayType, int length) {\n        return arrayType.equals(Object[].class)\n            ? (T[]) new Object[length]\n            : (T[]) Array.newInstance(arrayType.getComponentType(), length);\n    }\n\n    public static <E> Stream<E> stream(Collection<E> collection) {\n        return CollectionUtils.isEmpty(collection) ? Stream.empty() : collection.stream();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/Comparators.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport java.util.Comparator;\n\n/**\n * For collection order\n *\n * @author Ponfee\n */\npublic final class Comparators {\n\n    public static final int EQ =  0;\n    public static final int GT =  1;\n    public static final int LT = -1;\n\n    public static <T extends Comparable<? super T>> Comparator<T> asc() {\n        return Comparator.naturalOrder();\n    }\n\n    public static <T extends Comparable<? super T>> Comparator<T> desc() {\n        return Comparator.reverseOrder();\n    }\n\n    public static <T extends Comparable<? super T>> Comparator<T> order(boolean asc) {\n        return asc ? asc() : desc();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/DelegatedIntSpliterator.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport java.util.Comparator;\nimport java.util.Spliterator;\nimport java.util.function.Consumer;\nimport java.util.function.IntConsumer;\nimport java.util.function.IntFunction;\nimport java.util.stream.IntStream;\n\n/**\n * Delegated int spliterator\n *\n * @param <T> elelement type\n * @author Ponfee\n */\npublic class DelegatedIntSpliterator<T> implements Spliterator<T> {\n\n    private static final int CHARACTERISTICS = Spliterator.ORDERED\n                                             | Spliterator.SIZED\n                                             | Spliterator.SUBSIZED\n                                             | Spliterator.IMMUTABLE;\n\n    private final Spliterator.OfInt delegate;\n    private final IntFunction<? extends T> mapper;\n\n    public DelegatedIntSpliterator(int startInclusive, int endExclusive, IntFunction<? extends T> mapper) {\n        this.delegate = IntStream.range(startInclusive, endExclusive).spliterator();\n        this.mapper = mapper;\n    }\n\n    public DelegatedIntSpliterator(Spliterator.OfInt delegate, IntFunction<? extends T> mapper) {\n        this.delegate = delegate;\n        this.mapper = mapper;\n    }\n\n    @Override\n    public boolean tryAdvance(Consumer<? super T> action) {\n        return delegate.tryAdvance((IntConsumer) i -> action.accept(mapper.apply(i)));\n    }\n\n    @Override\n    public void forEachRemaining(Consumer<? super T> action) {\n        delegate.forEachRemaining((IntConsumer) i -> action.accept(mapper.apply(i)));\n    }\n\n    @Override\n    public Spliterator<T> trySplit() {\n        Spliterator.OfInt split = delegate.trySplit();\n        return (split == null) ? null : new DelegatedIntSpliterator<>(split, mapper);\n    }\n\n    @Override\n    public long estimateSize() {\n        return delegate.estimateSize();\n    }\n\n    @Override\n    public int characteristics() {\n        return CHARACTERISTICS;\n    }\n\n    @Override\n    public Comparator<? super T> getComparator() {\n        // inner elements unsupported sortable\n        throw new IllegalStateException();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/DoubleListViewer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport org.springframework.util.Assert;\n\nimport java.util.*;\n\n/**\n * Double(tow-dimensional) list viewer\n *\n * @param <E> the element type\n * @author Ponfee\n */\npublic class DoubleListViewer<E> implements List<E>, RandomAccess {\n\n    private final Collection<List<E>> list;\n    private final int size;\n\n    public DoubleListViewer(Collection<List<E>> list) {\n        Assert.notNull(list, \"Origin list cannot be null.\");\n        this.list = list;\n        this.size = list.stream().mapToInt(e -> e == null ? 0 : e.size()).sum();\n    }\n\n    @Override\n    public int size() {\n        return size;\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    @Override\n    public boolean contains(Object o) {\n        return indexOf(o) >= 0;\n    }\n\n    @Override\n    public boolean containsAll(Collection<?> c) {\n        for (Object e : c) {\n            if (!contains(e)) {\n                return false;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public Object[] toArray() {\n        return list.stream().filter(Objects::nonNull).flatMap(List::stream).toArray();\n    }\n\n    @Override\n    public <T> T[] toArray(T[] a) {\n        return list.stream()\n            .filter(Objects::nonNull)\n            .flatMap(List::stream)\n            .toArray(length -> (T[]) Collects.newArray(a.getClass(), length));\n    }\n\n    @Override\n    public E get(int index) {\n        Assert.isTrue(index >= 0, \"Index must greater than zero.\");\n        if (index >= size()) {\n            throw new IndexOutOfBoundsException(\"Index out of bounds: \" + index);\n        }\n        int count = 0;\n        for (List<E> sub : list) {\n            if (sub == null || sub.isEmpty()) {\n                continue;\n            }\n            if (index >= count + sub.size()) {\n                count += sub.size();\n                continue;\n            }\n            return sub.get(index - count);\n        }\n\n        // cannot happen\n        throw new IllegalStateException();\n    }\n\n    @Override\n    public int indexOf(Object o) {\n        int index = 0;\n        if (o == null) {\n            for (List<E> sub : list) {\n                if (sub == null || sub.isEmpty()) {\n                    continue;\n                }\n                for (E e : sub) {\n                    if (e == null) {\n                        return index;\n                    }\n                    index++;\n                }\n            }\n        } else {\n            for (List<E> sub : list) {\n                if (sub == null || sub.isEmpty()) {\n                    continue;\n                }\n                for (E e : sub) {\n                    if (o.equals(e)) {\n                        return index;\n                    }\n                    index++;\n                }\n            }\n        }\n        return -1;\n    }\n\n    @Override\n    public int lastIndexOf(Object o) {\n        int index = size() - 1;\n        if (o == null) {\n            for (List<E> sub : list) {\n                if (sub == null || sub.isEmpty()) {\n                    continue;\n                }\n                for (int i = sub.size() - 1; i >= 0; i--) {\n                    if (sub.get(i) == null) {\n                        return index;\n                    }\n                    index--;\n                }\n            }\n        } else {\n            for (List<E> sub : list) {\n                if (sub == null || sub.isEmpty()) {\n                    continue;\n                }\n                for (int i = sub.size() - 1; i >= 0; i--) {\n                    if (o.equals(sub.get(i))) {\n                        return index;\n                    }\n                    index--;\n                }\n            }\n        }\n        return -1;\n    }\n\n    @Override\n    public Iterator<E> iterator() {\n        return new UnmodifiableIterator(0, size());\n    }\n\n    @Override\n    public ListIterator<E> listIterator() {\n        return new UnmodifiableListIterator(0, size());\n    }\n\n    @Override\n    public ListIterator<E> listIterator(int index) {\n        return new UnmodifiableListIterator(index, size());\n    }\n\n    @Override\n    public List<E> subList(int fromIndex, int toIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public String toString() {\n        Iterator<E> it = iterator();\n        if (!it.hasNext()) {\n            return \"[]\";\n        }\n\n        StringBuilder sb = new StringBuilder();\n        sb.append('[');\n        for (; ; ) {\n            E e = it.next();\n            sb.append(e == this ? \"(this Collection)\" : e);\n            if (!it.hasNext()) {\n                return sb.append(']').toString();\n            }\n            sb.append(',').append(' ');\n        }\n    }\n\n    @Override\n    public int hashCode() {\n        int hashCode = 1;\n        for (E e : this) {\n            hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());\n        }\n        return hashCode;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == this) {\n            return true;\n        }\n        if (!(o instanceof List)) {\n            return false;\n        }\n\n        ListIterator<E> a = listIterator();\n        ListIterator<?> b = ((List<?>) o).listIterator();\n        while (a.hasNext() && b.hasNext()) {\n            if (!(Objects.equals(a.next(), b.next()))) {\n                return false;\n            }\n        }\n        return !(a.hasNext() || b.hasNext());\n    }\n\n    // ----------------------------------------------------Unsupported operations\n\n    @Override\n    public E set(int index, E element) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void add(int index, E element) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public E remove(int index) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean addAll(Collection<? extends E> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean addAll(int index, Collection<? extends E> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean removeAll(Collection<?> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean retainAll(Collection<?> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void clear() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean add(E e) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean remove(Object o) {\n        throw new UnsupportedOperationException();\n    }\n\n    // ----------------------------------------------------Static class\n\n    private class UnmodifiableListIterator extends UnmodifiableIterator implements ListIterator<E> {\n        UnmodifiableListIterator(int position, int end) {\n            super(position, end);\n        }\n\n        @Override\n        public boolean hasPrevious() {\n            return !isEmpty() && position > 0;\n        }\n\n        @Override\n        public E previous() {\n            if (!hasPrevious()) {\n                throw new NoSuchElementException();\n            }\n            return get(--position);\n        }\n\n        @Override\n        public int nextIndex() {\n            return position;\n        }\n\n        @Override\n        public int previousIndex() {\n            return position - 1;\n        }\n\n        @Override\n        public void set(E e) {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override\n        public void add(E e) {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    private class UnmodifiableIterator implements Iterator<E> {\n        protected int position;\n        protected final int end;\n\n        UnmodifiableIterator(int position, int end) {\n            this.position = position;\n            this.end = end;\n        }\n\n        @Override\n        public boolean hasNext() {\n            return !isEmpty() && position < end;\n        }\n\n        @Override\n        public E next() {\n            if (!hasNext()) {\n                throw new NoSuchElementException();\n            }\n            return get(position++);\n        }\n\n        @Override\n        public void remove() {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/FilterableIterator.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport java.util.Iterator;\nimport java.util.Objects;\nimport java.util.function.Predicate;\n\n/**\n * 遍历集合，选取符合条件的元素，用于增强for循环场景\n * <pre>{@code\n *  for (String s : FilterableIterator.of(\"\", null, \"a\")) {\n *    System.out.println(s);\n *  }\n * }</pre>\n *\n * @param <T> Parameterized Type\n * @author Ponfee\n */\npublic class FilterableIterator<T> implements Iterable<T>, Iterator<T> {\n\n    private final Predicate<T> predicate;\n    private final Iterator<T> iterator;\n    private T current;\n\n    private FilterableIterator(Iterator<T> iterator) {\n        this(Objects::nonNull, iterator);\n    }\n\n    private FilterableIterator(Predicate<T> predicate, Iterator<T> iterator) {\n        this.predicate = predicate;\n        this.iterator = iterator;\n    }\n\n    public static <T> FilterableIterator<T> of(Iterator<T> iterator) {\n        return new FilterableIterator<>(iterator);\n    }\n\n    public static <T> FilterableIterator<T> of(Predicate<T> predicate, Iterator<T> iterator) {\n        return new FilterableIterator<>(predicate, iterator);\n    }\n\n    public static <T> FilterableIterator<T> of(Iterable<T> iterable) {\n        return new FilterableIterator<>(iterable.iterator());\n    }\n\n    public static <T> FilterableIterator<T> of(Predicate<T> predicate, Iterable<T> iterable) {\n        return new FilterableIterator<>(predicate, iterable.iterator());\n    }\n\n    @SafeVarargs\n    public static <T> FilterableIterator<T> of(T... array) {\n        return new FilterableIterator<>(new ArrayIterator<>(array));\n    }\n\n    @SafeVarargs\n    public static <T> FilterableIterator<T> of(Predicate<T> predicate, T... array) {\n        return new FilterableIterator<>(predicate, new ArrayIterator<>(array));\n    }\n\n    @Override\n    public Iterator<T> iterator() {\n        return this;\n    }\n\n    @Override\n    public boolean hasNext() {\n        while (iterator.hasNext()) {\n            if (predicate.test(current = iterator.next())) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public T next() {\n        return current;\n    }\n\n    private static class ArrayIterator<T> implements Iterator<T> {\n        private final T[] array;\n        private int cursor = 0;\n\n        @SafeVarargs\n        private ArrayIterator(T... array) {\n            this.array = array;\n        }\n\n        @Override\n        public boolean hasNext() {\n            return array != null && cursor != array.length;\n        }\n\n        @Override\n        public T next() {\n            return array[cursor++];\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/ImmutableArrayList.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport cn.ponfee.commons.model.ToJsonString;\nimport com.google.common.base.Preconditions;\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.lang.reflect.Array;\nimport java.util.*;\nimport java.util.function.Predicate;\nimport java.util.function.UnaryOperator;\n\n/**\n * Representing immutable List\n *\n * @param <E> the element type\n * @author Ponfee\n */\npublic class ImmutableArrayList<E> extends ToJsonString\n    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {\n\n    private static final long serialVersionUID = 7013120001220709229L;\n\n    public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];\n\n    private final E[] elements;\n\n    public ImmutableArrayList() {\n        this.elements = (E[]) EMPTY_OBJECT_ARRAY;\n    }\n\n    public ImmutableArrayList(Object[] elements) {\n        // 为了节省时间及空间，此处认为外部环境不会修改数组，故不做拷贝操作\n        //this.elements = (T[]) Arrays.copyOf(elements, elements.length);\n        this.elements = (E[]) Objects.requireNonNull(elements);\n    }\n\n    /**\n     * Returns an empty list\n     *\n     * @return empty list\n     */\n    public static <T> ImmutableArrayList<T> of() {\n        return new ImmutableArrayList<>();\n    }\n\n    @SafeVarargs\n    public static <T> ImmutableArrayList<T> of(T... array) {\n        return new ImmutableArrayList<>(array);\n    }\n\n    public static <T> ImmutableArrayList<T> of(T[] array, T last) {\n        return of(ArrayUtils.addAll(Objects.requireNonNull(array), last));\n    }\n\n    public static <T> ImmutableArrayList<T> of(List<T> list) {\n        return of((T[]) (list.isEmpty() ? EMPTY_OBJECT_ARRAY : list.toArray()));\n    }\n\n    public static <T> ImmutableArrayList<T> of(List<T> list, T last) {\n        return of((T[]) list.toArray(), last);\n    }\n\n    // ------------------------------------------------------------------------- methods\n    protected int offset() {\n        return 0;\n    }\n\n    @Override\n    public int size() {\n        return elements.length;\n    }\n\n    @Override\n    public final boolean isEmpty() {\n        return size() == 0;\n    }\n\n    @Override\n    public final Object[] toArray() {\n        if (isEmpty()) {\n            return elements.length == 0 ? elements : EMPTY_OBJECT_ARRAY;\n        }\n        return Arrays.copyOfRange(elements, offset(), offset() + size());\n    }\n\n    @Override\n    public final <T> T[] toArray(T[] a) {\n        if (isEmpty()) {\n            if (a.length > 0) {\n                a[0] = null;\n            }\n            return a;\n        } else if (a.length < size()) {\n            return (T[]) Arrays.copyOfRange(elements, offset(), offset() + size(), a.getClass());\n        } else {\n            System.arraycopy(elements, offset(), a, 0, size());\n            if (a.length > size()) {\n                a[size()] = null;\n            }\n            return a;\n        }\n    }\n\n    @Override\n    public final E get(int index) {\n        if (index >= size()) {\n            throw new IndexOutOfBoundsException(\"Index: \" + index + \", size: \" + size());\n        }\n        return elements[offset() + index];\n    }\n\n    @Override\n    public final int indexOf(Object o) {\n        if (o == null) {\n            for (int i = 0, n = size(); i < n; i++) {\n                if (elements[offset() + i] == null) {\n                    return i;\n                }\n            }\n        } else {\n            for (int i = 0, n = size(); i < n; i++) {\n                if (o.equals(elements[offset() + i])) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    }\n\n    @Override\n    public final int lastIndexOf(Object o) {\n        if (o == null) {\n            for (int i = size() - 1; i >= 0; i--) {\n                if (elements[offset() + i] == null) {\n                    return i;\n                }\n            }\n        } else {\n            for (int i = size() - 1; i >= 0; i--) {\n                if (o.equals(elements[offset() + i])) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    }\n\n    @Override\n    public final boolean contains(Object o) {\n        return indexOf(o) >= 0;\n    }\n\n    @Override\n    public final boolean containsAll(Collection<?> c) {\n        for (Object e : c) {\n            if (!contains(e)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public final Iterator<E> iterator() {\n        return new UnmodifiableIterator(offset(), offset() + size());\n    }\n\n    @Override\n    public final ListIterator<E> listIterator() {\n        return new UnmodifiableListIterator(offset(), offset() + size());\n    }\n\n    @Override\n    public final ListIterator<E> listIterator(int index) {\n        return new UnmodifiableListIterator(offset() + index, offset() + size());\n    }\n\n    @Override\n    public final ImmutableArrayList<E> subList(int fromIndex, int toIndex) {\n        Preconditions.checkPositionIndexes(fromIndex, toIndex, size());\n        int length = toIndex - fromIndex;\n        if (length == size()) {\n            return this;\n        } else if (length == 0) {\n            return of((E[]) EMPTY_OBJECT_ARRAY);\n        } else {\n            return new SubList(offset() + fromIndex, offset() + toIndex);\n        }\n    }\n\n    @Override\n    public final Spliterator<E> spliterator() {\n        return new DelegatedIntSpliterator<>(offset(), offset() + size(), i -> elements[i]);\n    }\n\n    @Override\n    public final int hashCode() {\n        int hashCode = 1;\n        for (E e : this) {\n            hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());\n        }\n        return hashCode;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == this) {\n            return true;\n        }\n        if (!(o instanceof List)) {\n            return false;\n        }\n\n        ListIterator<E> a = listIterator();\n        ListIterator<?> b = ((List<?>) o).listIterator();\n        while (a.hasNext() && b.hasNext()) {\n            if (!Objects.equals(a.next(), b.next())) {\n                return false;\n            }\n        }\n        return !(a.hasNext() || b.hasNext());\n    }\n\n    @Override\n    public Object clone() {\n        return this;\n    }\n\n    public final E[] join(E last) {\n        if (isEmpty()) {\n            // t.getClass().getComponentType()\n            return last == null ? (E[]) new Object[]{null} : Collects.toArray(last);\n        }\n\n        Class<? extends E[]> arrayType = (Class<? extends E[]>) elements.getClass();\n        E[] array = arrayType.equals(Object[].class)\n                  ? (E[]) new Object[size() + 1]\n                  : (E[]) Array.newInstance(arrayType.getComponentType(), size() + 1);\n        System.arraycopy(elements, offset(), array, 0, size());\n        array[size()] = last;\n        return array;\n    }\n\n    public final ImmutableArrayList<E> concat(E last) {\n        return of(join(last));\n    }\n\n    // ---------------------------------------------------- unsupported operation\n    @Override\n    public final boolean add(E e) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final boolean remove(Object o) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final boolean addAll(Collection<? extends E> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final boolean addAll(int index, Collection<? extends E> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final boolean removeAll(Collection<?> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final boolean removeIf(Predicate<? super E> filter) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final boolean retainAll(Collection<?> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final void replaceAll(UnaryOperator<E> operator) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final void sort(Comparator<? super E> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final void clear() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final E set(int index, E element) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final void add(int index, E element) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public final E remove(int index) {\n        throw new UnsupportedOperationException();\n    }\n\n    private class UnmodifiableIterator implements Iterator<E> {\n        protected int position;\n        protected final int end;\n\n        UnmodifiableIterator(int position, int end) {\n            this.position = position;\n            this.end = end;\n        }\n\n        @Override\n        public boolean hasNext() {\n            return !isEmpty() && position < end;\n        }\n\n        @Override\n        public E next() {\n            if (!hasNext()) {\n                throw new NoSuchElementException();\n            }\n            return elements[position++];\n        }\n\n        @Override\n        public void remove() {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    private class UnmodifiableListIterator extends UnmodifiableIterator implements ListIterator<E> {\n\n        UnmodifiableListIterator(int position, int end) {\n            super(position, end);\n        }\n\n        @Override\n        public boolean hasPrevious() {\n            return !isEmpty() && position > 0;\n        }\n\n        @Override\n        public E previous() {\n            if (!hasPrevious()) {\n                throw new NoSuchElementException();\n            }\n            return elements[--position];\n        }\n\n        @Override\n        public int nextIndex() {\n            return position;\n        }\n\n        @Override\n        public int previousIndex() {\n            return position - 1;\n        }\n\n        @Override\n        public void set(E e) {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override\n        public void add(E e) {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    private class SubList extends ImmutableArrayList<E> {\n        private static final long serialVersionUID = 8017446305586649188L;\n\n        final int offset;\n        final int size;\n\n        SubList(int from, int to) {\n            super(elements);\n            this.offset = from;\n            this.size = to - from;\n        }\n\n        @Override\n        protected int offset() {\n            return offset;\n        }\n\n        @Override\n        public int size() {\n            return size;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/ImmutableHashList.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport org.apache.commons.collections4.CollectionUtils;\n\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Immutable hash map & list structure\n *\n * @param <K> the key type\n * @param <V> the val type\n * @author Ponfee\n */\npublic class ImmutableHashList<K extends Comparable<K>, V> {\n    private static final ImmutableHashList EMPTY = new ImmutableHashList<>();\n\n    private final Function<V, K> mapper;\n    private final Set<K> keys;\n    private final List<V> values;\n\n    private ImmutableHashList() {\n        this.mapper = v -> null;\n        this.keys = Collections.emptySet();\n        this.values = Collections.emptyList();\n    }\n\n    private ImmutableHashList(List<V> values, Function<V, K> mapper) {\n        // sort\n        values.sort(Comparator.comparing(mapper));\n\n        this.mapper = mapper;\n        this.keys = values.stream().map(mapper).collect(Collectors.toSet());\n        this.values = Collections.unmodifiableList(values);\n    }\n\n    public final List<V> values() {\n        return values;\n    }\n\n    public final boolean contains(V value) {\n        return keys.contains(mapper.apply(value));\n    }\n\n    public final boolean isEmpty() {\n        return values.isEmpty();\n    }\n\n    public static <K extends Comparable<K>, V> ImmutableHashList<K, V> of(List<V> values, Function<V, K> mapper) {\n        return CollectionUtils.isEmpty(values) ? EMPTY : new ImmutableHashList<>(values, mapper);\n    }\n\n    public static <K extends Comparable<K>, V> ImmutableHashList<K, V> empty() {\n        return EMPTY;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/LRUCache.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport java.util.LinkedHashMap;\nimport java.util.concurrent.locks.ReadWriteLock;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\n\n/**\n * LRU cache based LinkedHashMap\n * \n * @author Ponfee\n * \n * @param <K> the key type\n * @param <V> the val type\n */\npublic class LRUCache<K, V> extends LinkedHashMap<K, V> {\n\n    private static final long serialVersionUID = 3943991140850259837L;\n\n    private final ReadWriteLock lock = new ReentrantReadWriteLock();\n\n    private volatile int maxSize;\n\n    public LRUCache() {\n        this(1024); // default maximum size 1024\n    }\n\n    public LRUCache(int maxCapacity) {\n        super(16, 0.75f, true); // default initial capacity 16\n        this.maxSize = maxCapacity;\n    }\n\n    @Override\n    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {\n        return this.size() > maxSize;\n    }\n\n    @Override\n    public boolean containsKey(Object key) {\n        lock.readLock().lock();\n        try {\n            return super.containsKey(key);\n        } finally {\n            lock.readLock().unlock();\n        }\n    }\n\n    @Override\n    public V get(Object key) {\n        lock.readLock().lock();\n        try {\n            return super.get(key);\n        } finally {\n            lock.readLock().unlock();\n        }\n    }\n\n    @Override\n    public V put(K key, V value) {\n        lock.writeLock().lock();\n        try {\n            return super.put(key, value);\n        } finally {\n            lock.writeLock().unlock();\n        }\n    }\n\n    @Override\n    public V remove(Object key) {\n        lock.writeLock().lock();\n        try {\n            return super.remove(key);\n        } finally {\n            lock.writeLock().unlock();\n        }\n    }\n\n    @Override\n    public int size() {\n        lock.readLock().lock();\n        try {\n            return super.size();\n        } finally {\n            lock.readLock().unlock();\n        }\n    }\n\n    @Override\n    public void clear() {\n        lock.writeLock().lock();\n        try {\n            super.clear();\n        } finally {\n            lock.writeLock().unlock();\n        }\n    }\n\n    public int getMaxSize() {\n        return maxSize;\n    }\n\n    public void setMaxSize(int maxSize) {\n        this.maxSize = maxSize;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/Maps.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport cn.ponfee.commons.model.Page;\nimport cn.ponfee.commons.model.Result;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Map Utilities\n * \n * @author Ponfee\n */\npublic final class Maps {\n\n    /**\n     * Returns a map contains key specified key \n     * \n     * @param map the map\n     * @param key the string of key\n     * @return {@code true} means constains\n     */\n    public static boolean hasKey(Map<?, ?> map, String key) {\n        return map != null && map.containsKey(key);\n    }\n\n    // ----------------------------------------------------------------map to array\n\n    /**\n     * map转数组\n     *\n     * @param map    the map\n     * @param fields the string fields\n     * @return array of fields mapped values\n     */\n    public static Object[] toArray(Map<String, Object> map, String... fields) {\n        return Stream.of(fields).map(map::get).toArray();\n    }\n\n    /**\n     * List<Map<String, Object>>转List<Object[]>\n     * @param data\n     * @param fields\n     * @return\n     */\n    public static List<Object[]> toArray(List<Map<String, Object>> data, String... fields) {\n        if (data == null) {\n            return null;\n        }\n        return data.stream()\n                   .map(map -> toArray(map, fields))\n                   .collect(Collectors.toList());\n    }\n\n    /**\n     * LinkedHashMap<String, Object>转Object[]\n     * \n     * @param data\n     * @return\n     */\n    public static Object[] toArray(LinkedHashMap<String, Object> data) {\n        if (data == null) {\n            return null;\n        }\n        return data.values().stream().toArray();\n    }\n\n    /**\n     * List<LinkedHashMap<String, Object>> -> List<Object[]>\n     * @param data\n     * @return\n     */\n    public static List<Object[]> toArray(List<LinkedHashMap<String, Object>> data) {\n        if (data == null) {\n            return null;\n        }\n\n        return data.stream()\n                   .map(Maps::toArray)\n                   .collect(Collectors.toList());\n    }\n\n    /**\n     * Result<Page<LinkedHashMap<String, Object>>>转Result<Page<Object[]>>\n     * @param source\n     * @return\n     */\n    public static Result<Page<Object[]>> toArray(Result<Page<LinkedHashMap<String, Object>>> source) {\n        return source.from(source.getData().map(Maps::toArray));\n    }\n\n    /**\n     * Result<Page<Map<String, Object>>>转Result<Page<Object[]>>\n     * @param source\n     * @param fields\n     * @return\n     */\n    public static Result<Page<Object[]>> toArray(Result<Page<Map<String, Object>>> source, String... fields) {\n        return source.from(source.getData().map(map -> toArray(map, fields)));\n    }\n\n    // ----------------------------------------------------------------to List, Map and Array\n    /**\n     * Converts array to map\n     * \n     * @param kv the key value array\n     * @return a map\n     */\n    public static Map<String, Object> toMap(Object... kv) {\n        if (kv == null) {\n            return null;\n        }\n\n        int length = kv.length;\n        // length % 2\n        if ((length & 0x01) != 0) {\n            throw new IllegalArgumentException(\"args must be pair.\");\n        }\n\n        Map<String, Object> map = new LinkedHashMap<>(length);\n        for (int i = 0; i < length; i += 2) {\n            map.put((String) kv[i], kv[i + 1]);\n        }\n        return map;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/StreamForker.java",
    "content": "package cn.ponfee.commons.collect;\n\nimport cn.ponfee.commons.exception.ServerException;\n\nimport java.util.*;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\nimport java.util.stream.StreamSupport;\n\n/**\n * The class use in fork {@link Stream},\n * from book \"Java 8 In Action\"<p>\n * \n * Usage:\n * <pre> {@code\n *   Stream<Integer> stream = Stream.of(1, 2, 3, 4, 4, 5, 5);\n *   StreamForker.Results results = new StreamForker<>(stream)\n *     .fork(1, s -> s.max(Integer::compareTo)) // 直接聚合\n *     .fork(2, s -> s.distinct().reduce(0, Integer::sum))\n *     .getResults();\n * } </pre>\n * \n * @param <T>\n * @author Java 8 In Action\n */\npublic class StreamForker<T> {\n\n    private final Stream<T> stream;\n    private final Map<Object, Function<Stream<T>, ?>> forks = new HashMap<>();\n\n    public StreamForker(Stream<T> stream) {\n        this.stream = stream;\n    }\n\n    public StreamForker<T> fork(Object key, Function<Stream<T>, ?> f) {\n        forks.put(key, f);\n        return this;\n    }\n\n    public Results getResults() {\n        ForkingStreamConsumer<T> consumer = build();\n        try {\n            stream.sequential().forEach(consumer);\n        } finally {\n            consumer.finish();\n        }\n        return consumer;\n    }\n\n    private ForkingStreamConsumer<T> build() {\n        List<BlockingQueue<T>> queues = new ArrayList<>();\n\n        Map<Object, Future<?>> actions = forks.entrySet().stream().reduce(\n            new HashMap<>(),\n            (map, e) -> {\n                map.put(e.getKey(), getOperationResult(queues, e.getValue()));\n                return map;\n            },\n            (m1, m2) -> {\n                m1.putAll(m2);\n                return m1;\n            }\n        );\n\n        return new ForkingStreamConsumer<>(queues, actions);\n    }\n\n    private Future<?> getOperationResult(List<BlockingQueue<T>> queues, Function<Stream<T>, ?> f) {\n        LinkedBlockingQueue<T> queue = new LinkedBlockingQueue<>();\n        queues.add(queue);\n        Stream<T> source = StreamSupport.stream(new BlockingQueueSpliterator<>(queue), false);\n        return CompletableFuture.supplyAsync(() -> f.apply(source));\n    }\n\n    public interface Results {\n        <R> R get(Object key);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static class ForkingStreamConsumer<T> implements Consumer<T>, Results {\n        private static final Object END_OF_STREAM = new Object();\n\n        private final List<BlockingQueue<T>> queues;\n        private final Map<Object, Future<?>> actions;\n\n        ForkingStreamConsumer(List<BlockingQueue<T>> queues, Map<Object, Future<?>> actions) {\n            this.queues = queues;\n            this.actions = actions;\n        }\n\n        @Override\n        public void accept(T t) {\n            queues.forEach(q -> q.add(t));\n        }\n\n        @Override\n        public <R> R get(Object key) {\n            try {\n                return ((Future<R>) actions.get(key)).get();\n            } catch (Exception e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n        void finish() {\n            accept((T) END_OF_STREAM);\n        }\n    }\n\n    private static class BlockingQueueSpliterator<T> implements Spliterator<T> {\n        private final BlockingQueue<T> q;\n\n        BlockingQueueSpliterator(BlockingQueue<T> q) {\n            this.q = q;\n        }\n\n        @Override\n        public boolean tryAdvance(Consumer<? super T> action) {\n            T t;\n            while (true) { // if occur exception, then keep take\n                try {\n                    t = q.take();\n                    break;\n                } catch (InterruptedException e) {\n                    Thread.currentThread().interrupt();\n                    throw new ServerException(e);\n                }\n            }\n\n            if (t != ForkingStreamConsumer.END_OF_STREAM) {\n                action.accept(t);\n                return true;\n            }\n\n            return false;\n        }\n\n        @Override\n        public Spliterator<T> trySplit() {\n            return null;\n        }\n\n        @Override\n        public long estimateSize() {\n            return 0;\n        }\n\n        @Override\n        public int characteristics() {\n            return 0;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/collect/ValueSortedMap.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.collect;\n\nimport java.util.Comparator;\nimport java.util.Map;\nimport java.util.TreeMap;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\n\n/**\n * <pre>\n * Extends TreeMap sort by value: {@code\n *   ValueSortedMap valueSortedMap = ValueSortedMap.nullsFirst(a map);\n * }\n *\n * Also use like this: {@code\n *    Map<String, Integer> originMap = ImmutableMap.of(\"b\", 2, \"a\", 1);\n *    TreeMap<String, Integer> sortedByValueMap = new TreeMap<>(Comparator.comparing(originMap::get));\n *    sortedByValueMap.putAll(originMap);\n * }\n * </pre>\n *\n * @author Ponfee\n */\npublic class ValueSortedMap<K, V> extends TreeMap<K, V> {\n\n    private static final long serialVersionUID = -6242175050718596776L;\n\n    private ValueSortedMap(Map<? extends K, ? extends V> map, \n                           Comparator<? super V> comparator) {\n        super(new MapValueComparator<>(map, comparator));\n        map.forEach(super::put);\n    }\n\n    public static <K, V extends Comparable<? super V>> ValueSortedMap<K, V> nullsFirst(\n        Map<? extends K, ? extends V> map) {\n        return nullsFirst(map, Comparators.asc());\n    }\n\n    public static <K, V> ValueSortedMap<K, V> nullsFirst(\n        Map<? extends K, ? extends V> map, Comparator<? super V> comparator) {\n        return new ValueSortedMap<>(map, Comparator.nullsFirst(comparator));\n    }\n\n    public static <K, V extends Comparable<? super V>> ValueSortedMap<K, V> nullsLast(\n        Map<? extends K, ? extends V> map) {\n        return nullsLast(map, Comparators.asc());\n    }\n\n    public static <K, V> ValueSortedMap<K, V> nullsLast(\n        Map<? extends K,? extends V> map, Comparator<? super V> comparator) {\n        return new ValueSortedMap<>(map, Comparator.nullsLast(comparator));\n    }\n\n    // ------------------------------------------------------------Comparator\n    private static class MapValueComparator<K, V> implements Comparator<K> {\n        private final Map<? extends K, ? extends V> data;\n        private final Comparator<? super V> comparator;\n\n        private MapValueComparator(Map<? extends K, ? extends V> data,\n                                   Comparator<? super V> comparator) {\n            this.data = data;\n            this.comparator = comparator;\n        }\n\n        @Override\n        public int compare(K k1, K k2) {\n            int n = comparator.compare(data.get(k1), data.get(k2));\n            return n != 0 ? n : k1.toString().compareTo(k2.toString());\n        }\n    }\n\n    // ------------------------------------------------------------Deprecated Methods\n    @Deprecated @Override\n    public final V put(K k, V v) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final V putIfAbsent(K key, V value) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final boolean replace(K key, V oldValue, V newValue) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final V replace(K key, V value) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final V computeIfPresent(\n        K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final V merge(\n        K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final void putAll(Map<? extends K, ? extends V> map) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final V remove(Object o) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final boolean remove(Object key, Object value) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Deprecated @Override\n    public final void clear() {\n      throw new UnsupportedOperationException();\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/AsyncBatchProcessor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.util.Assert;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * <pre>\n *  异步数据批处理\n *\n *  execute：直接抛出异常，在线程外部无法捕获异常，想要捕获该异常，可以实现UncaughtExceptionHandler接口\n *  submit ：不会抛出异常，需要调用返回值Future对象的get方法\n *\n *  <a href=\"https://github.com/JCTools/JCTools\">JCTools</a>\n *  <a href=\"https://github.com/LMAX-Exchange/disruptor\">disruptor</a>\n * </pre>\n *\n * @param <T>\n * @author Ponfee\n * @see Thread#setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler)\n */\npublic final class AsyncBatchProcessor<T> {\n\n    private final static Logger LOG = LoggerFactory.getLogger(AsyncBatchProcessor.class);\n\n    private final AsyncBatchThread<T> async;\n\n    public AsyncBatchProcessor(BatchProcessor<T> processor) {\n        this(processor, 100, 200, 2);\n    }\n\n    /**\n     * @param processor        批处理器\n     * @param periodTimeMillis 处理周期(毫秒)\n     * @param batchSize        批量大小\n     * @param maximumPoolSize  最大线程数\n     */\n    public AsyncBatchProcessor(BatchProcessor<T> processor,\n                               int periodTimeMillis,\n                               int batchSize,\n                               int maximumPoolSize) {\n        this.async = new AsyncBatchThread<>(\n            processor, periodTimeMillis, batchSize, maximumPoolSize\n        );\n    }\n\n    /**\n     * Puts an element to queue\n     *\n     * @param element the element\n     */\n    public boolean put(T element) {\n        return !async.stopped.get() && async.queue.offer(element);\n    }\n\n    /**\n     * Batch put elements to queue.\n     *\n     * @param elements the elements\n     */\n    public boolean put(T[] elements) {\n        if (async.stopped.get() || elements == null || elements.length == 0) {\n            return false;\n        }\n\n        for (T element : elements) {\n            if (!async.queue.offer(element)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Batch put elements to queue.\n     *\n     * @param elements the list of elements\n     */\n    public boolean put(List<T> elements) {\n        if (async.stopped.get() || elements == null || elements.isEmpty()) {\n            return false;\n        }\n\n        for (T element : elements) {\n            if (!async.queue.offer(element)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Do stop\n     *\n     * @return {@code true} if stop success\n     */\n    public boolean stop() {\n        return async.stopped.compareAndSet(false, true);\n    }\n\n    public void stopAndAwait() throws InterruptedException {\n        stop();\n        while (!Threads.isStopped(async)) {\n            Thread.sleep(async.periodTimeMillis);\n        }\n    }\n\n    /**\n     * Async batch consume into this alone thread\n     */\n    private static class AsyncBatchThread<T> extends Thread {\n        private static final int MINIMUM_PERIOD_TIME_MILLIS = 9;\n\n        // 单消费者用LinkedBlockingQueue，多消费者用ConcurrentLinkedQueue\n        private final LinkedBlockingQueue<T> queue = new LinkedBlockingQueue<>();\n        private final AtomicBoolean        stopped = new AtomicBoolean(false);\n\n        private final BatchProcessor<T> processor; // 处理器\n        private final int periodTimeMillis;        // 消费周期(毫秒)\n        private final int sleepTimeMillis;         // 休眠时间\n        private final int batchSize;               // 批量大小\n        private final int asyncExecuteThreshold;   // 启用异步执行器的阈值\n        private final int maximumPoolSize;         // 最大线程数\n\n        private long nextRefreshTimeMillis = 0L;   // 下一次刷新时间\n\n        /**\n         * @param processor        批处理器\n         * @param periodTimeMillis 处理周期(毫秒)\n         * @param batchSize        批量大小\n         * @param maximumPoolSize  最大线程数\n         */\n        private AsyncBatchThread(BatchProcessor<T> processor,\n                                 int periodTimeMillis,\n                                 int batchSize,\n                                 int maximumPoolSize) {\n            Assert.isTrue(\n                periodTimeMillis >= MINIMUM_PERIOD_TIME_MILLIS,\n                () -> \"Period time millis must greater than \" + MINIMUM_PERIOD_TIME_MILLIS + \", but actual \" + periodTimeMillis\n            );\n            Assert.isTrue(batchSize > 0, \"Batch size cannot negative number.\");\n            Assert.isTrue(maximumPoolSize > 0, \"Maximum pool size cannot negative number.\");\n\n            this.processor = processor;\n            this.periodTimeMillis = periodTimeMillis;\n            this.sleepTimeMillis = (periodTimeMillis >>> 1);\n            this.batchSize = batchSize;\n            this.asyncExecuteThreshold = batchSize + (batchSize >>> 1);\n            this.maximumPoolSize = maximumPoolSize;\n\n            super.setName(\"async-batch-processor-thread-\" + Integer.toHexString(hashCode()));\n            super.setDaemon(false);\n            super.start();\n        }\n\n        /**\n         * thread inner run, don't to direct call this method\n         * it is a thread and the alone thread\n         */\n        @Override\n        public void run() {\n            // async thread pool executor\n            ThreadPoolExecutor asyncExecutor = null;\n            ArrayList<T> list = new ArrayList<>(batchSize);\n\n            for (int left = batchSize; ; ) {\n                if (isEnd()) {\n                    if (asyncExecutor != null) {\n                        // wait a moment if async execute\n                        try {\n                            Thread.sleep(periodTimeMillis);\n                        } catch (InterruptedException e) {\n                            LOG.error(\"Thread#sleep occur error.\", e);\n                            Thread.currentThread().interrupt();\n                        }\n                    }\n\n                    // double check\n                    if (isEnd()) {\n                        if (asyncExecutor != null) {\n                            // destroy the async executor\n                            ThreadPoolExecutors.shutdown(asyncExecutor, 3);\n                        }\n\n                        // exit for loop if end\n                        break;\n                    }\n                }\n\n                // 尽量不要使用queue.size()，时间复杂度O(n)\n                if (!queue.isEmpty() && left > 0) {\n                    left -= queue.drainTo(list, left);\n                }\n\n                long currentTimeMillis = System.currentTimeMillis();\n                if (left == 0 || (!list.isEmpty() && (stopped.get() || currentTimeMillis >= nextRefreshTimeMillis))) {\n                    if (asyncExecutor == null && left == 0 && queue.size() > asyncExecuteThreshold) {\n                        asyncExecutor = ThreadPoolExecutors.builder()\n                            .corePoolSize(1)\n                            .maximumPoolSize(maximumPoolSize)\n                            .workQueue(new LinkedBlockingQueue<>(2))\n                            .keepAliveTimeSeconds(300)\n                            .threadFactory(NamedThreadFactory.builder().prefix(\"async-batch-processor-worker\").build())\n                            .rejectedHandler(ThreadPoolExecutors.CALLER_RUNS)\n                            .build();\n                        LOG.info(\"Asnyc batch processor created thread pool executor: {}\", new ThreadPoolMonitor(asyncExecutor));\n                    }\n\n                    if (asyncExecutor != null) {\n                        // async thread pool execute\n                        // task抛异常后：\n                        //   execute输出错误信息，线程结束，后续任务会创建新线程执行，会抛出异常\n                        //   submit不输出错误信息，线程继续分配执行其它任务，不会抛出异常，除非你调用Future.get()\n                        final List<T> data = list;\n                        asyncExecutor.submit(() -> processor.process(data, isEnd()));\n                        list = new ArrayList<>(left = batchSize);\n                    } else {\n                        // current thread execute\n                        processor.process(list, isEnd());\n                        // reuse the list object\n                        list.clear();\n                        left = batchSize;\n                    }\n\n                    nextRefreshTimeMillis = currentTimeMillis + periodTimeMillis;\n                } else if (!stopped.get()) {\n                    try {\n                        // to sleep for prevent endless loop\n                        Thread.sleep(sleepTimeMillis);\n                    } catch (InterruptedException e) {\n                        LOG.error(\"Thread#sleep occur error.\", e);\n                        stopped.compareAndSet(false, true);\n                        Thread.currentThread().interrupt();\n                        break;\n                    }\n                }\n            }\n        }\n\n        private boolean isEnd() {\n            return stopped.get() && queue.isEmpty();\n        }\n    }\n\n    @FunctionalInterface\n    public interface BatchProcessor<T> {\n        void process(List<T> t, boolean stopped);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/AsyncDelayedExecutor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.DelayQueue;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\n\n/**\n * Async delayed executor\n *\n * @param <E> the element type\n * @author Ponfee\n */\npublic final class AsyncDelayedExecutor<E> extends Thread {\n\n    private final static Logger LOG = LoggerFactory.getLogger(AsyncDelayedExecutor.class);\n\n    private final Consumer<E> processor;            // 数据处理器\n    private final ThreadPoolExecutor asyncExecutor; // 异步执行器\n\n    private final DelayQueue<DelayedData<E>> queue = new DelayQueue<>();\n    private final AtomicBoolean stopped = new AtomicBoolean(false);\n\n    public AsyncDelayedExecutor(Consumer<E> processor) {\n        this(1, processor);\n    }\n\n    /**\n     * @param maximumPoolSize the maximumPoolSize\n     * @param processor       the data processor\n     */\n    public AsyncDelayedExecutor(int maximumPoolSize,\n                                Consumer<E> processor) {\n        this.processor = processor;\n\n        ThreadPoolExecutor executor = null;\n        if (maximumPoolSize > 1) {\n            executor = ThreadPoolExecutors.builder()\n                .corePoolSize(1)\n                .maximumPoolSize(maximumPoolSize)\n                .workQueue(new SynchronousQueue<>())\n                .keepAliveTimeSeconds(300)\n                .threadFactory(NamedThreadFactory.builder().prefix(\"async_delayed_worker\").build())\n                .rejectedHandler(ThreadPoolExecutors.CALLER_RUNS)\n                .build();\n        }\n        this.asyncExecutor = executor;\n\n        super.setName(\"async_delayed_executor-\" + Integer.toHexString(hashCode()));\n        super.setDaemon(false);\n        super.start();\n    }\n\n    /**\n     * Puts an element to queue\n     *\n     * @param delayedData the delayed data\n     */\n    public boolean put(DelayedData<E> delayedData) {\n        if (stopped.get()) {\n            return false;\n        }\n        return queue.offer(delayedData);\n    }\n\n    public void doStop() {\n        if (stopped.compareAndSet(false, true)) {\n            Threads.stopThread(this, 0, 0, 1000);\n        }\n    }\n\n    @Override\n    public void run() {\n        while (!stopped.get()) {\n            DelayedData<E> delayed;\n            try {\n                delayed = queue.poll(3000, TimeUnit.MILLISECONDS);\n            } catch (InterruptedException e) {\n                LOG.error(\"Delayed queue pool occur interrupted.\", e);\n                stopped.compareAndSet(false, true);\n                Thread.currentThread().interrupt();\n                break;\n            }\n\n            if (delayed != null) {\n                E data = delayed.getData();\n                if (asyncExecutor != null) {\n                    asyncExecutor.submit(() -> processor.accept(data));\n                } else {\n                    processor.accept(data);\n                }\n            }\n        }\n\n        if (asyncExecutor != null) {\n            // destroy the async executor\n            ThreadPoolExecutors.shutdown(asyncExecutor, 3);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/DelayedData.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport com.google.common.primitives.Ints;\n\nimport java.util.Objects;\nimport java.util.concurrent.Delayed;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Delayed data\n *\n * @author Ponfee\n */\npublic class DelayedData<E> implements Delayed {\n\n    private final long fireTime;\n    private final E data;\n\n    private DelayedData(E data, long delayInMilliseconds) {\n        this.data = Objects.requireNonNull(data);\n        this.fireTime = System.currentTimeMillis() + delayInMilliseconds;\n    }\n\n    public static <E> DelayedData<E> of(E data, long delayInMilliseconds) {\n        return new DelayedData<>(data, delayInMilliseconds);\n    }\n\n    @Override\n    public long getDelay(TimeUnit unit) {\n        long diff = fireTime - System.currentTimeMillis();\n        return unit.convert(diff, TimeUnit.MILLISECONDS);\n    }\n\n    @Override\n    public int compareTo(Delayed o) {\n        return Ints.saturatedCast(this.fireTime - ((DelayedData<E>) o).fireTime);\n    }\n\n    public E getData() {\n        return data;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/MultithreadExecutors.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Multi Thread executor\n *\n * <p> {@code Thread#stop()} will occur \"java.lang.ThreadDeath: null\" if try...catch wrapped in Throwable\n *\n * @author Ponfee\n */\npublic class MultithreadExecutors {\n\n\n    public static <T> void run(Collection<T> coll, Consumer<T> action, Executor executor) {\n        run(coll, action, executor, 2);\n    }\n\n    /**\n     * Run async, action the T collection\n     *\n     * @param coll     the T collection\n     * @param action   the T action\n     * @param executor thread executor service\n     */\n    public static <T> void run(Collection<T> coll, Consumer<T> action, Executor executor, int dataSizeThreshold) {\n        if (coll == null || coll.isEmpty()) {\n            return;\n        }\n        if (dataSizeThreshold <= 0 || coll.size() < dataSizeThreshold) {\n            coll.forEach(action);\n            return;\n        }\n        coll.stream()\n            .map(e -> CompletableFuture.runAsync(() -> action.accept(e), executor))\n            .collect(Collectors.toList())\n            .forEach(CompletableFuture::join);\n    }\n\n    public static <T, U> List<U> call(Collection<T> coll, Function<T, U> mapper, Executor executor) {\n        return call(coll, mapper, executor, 2);\n    }\n\n    /**\n     * Call async, mapped T to U\n     *\n     * @param coll     the T collection\n     * @param mapper   the mapper of T to U\n     * @param executor thread executor service\n     * @return the U collection\n     */\n    public static <T, U> List<U> call(Collection<T> coll, Function<T, U> mapper, Executor executor, int dataSizeThreshold) {\n        if (coll == null) {\n            return null;\n        }\n        if (coll.isEmpty()) {\n            return Collections.emptyList();\n        }\n        if (dataSizeThreshold <= 0 || coll.size() < dataSizeThreshold) {\n            return coll.stream().map(mapper).collect(Collectors.toList());\n        }\n        return coll.stream()\n            .map(e -> CompletableFuture.supplyAsync(() -> mapper.apply(e), executor))\n            .collect(Collectors.toList())\n            .stream()\n            .map(CompletableFuture::join)\n            .collect(Collectors.toList());\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/NamedThreadFactory.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Implementation of thread factory.\n *\n * @author Ponfee\n */\npublic class NamedThreadFactory implements ThreadFactory {\n\n    private static final AtomicInteger POOL_SEQ = new AtomicInteger(1);\n    private final AtomicInteger threadNo = new AtomicInteger(1);\n\n    private final String prefix;\n    private final Boolean daemon;\n    private final Integer priority;\n    private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler;\n    private final ThreadGroup group;\n\n    public NamedThreadFactory(String prefix,\n                              Boolean daemon,\n                              Integer priority,\n                              Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {\n        if (StringUtils.isBlank(prefix)) {\n            prefix = \"pool-\" + POOL_SEQ.getAndIncrement();\n        }\n        SecurityManager sm = System.getSecurityManager();\n        this.prefix = prefix + \"-thread-\";\n        this.daemon = daemon;\n        this.priority = priority;\n        this.uncaughtExceptionHandler = uncaughtExceptionHandler;\n        this.group = sm != null ? sm.getThreadGroup() : Thread.currentThread().getThreadGroup();\n    }\n\n    @Override\n    public Thread newThread(Runnable runnable) {\n        Thread thread = new Thread(group, runnable, prefix + threadNo.getAndIncrement(), 0);\n        thread.setDaemon(daemon != null ? daemon : Thread.currentThread().isDaemon());\n        if (priority != null) {\n            thread.setPriority(priority);\n        }\n        if (uncaughtExceptionHandler != null) {\n            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);\n        }\n        return thread;\n    }\n\n    public ThreadGroup getThreadGroup() {\n        return group;\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public static class Builder {\n        private String prefix;\n        private Boolean daemon;\n        private Integer priority;\n        private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;\n\n        private Builder() { }\n\n        public Builder prefix(String prefix) {\n            this.prefix = prefix;\n            return this;\n        }\n\n        public Builder daemon(boolean daemon) {\n            this.daemon = daemon;\n            return this;\n        }\n\n        public Builder priority(Integer priority) {\n            this.priority = priority;\n            return this;\n        }\n\n        public Builder uncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {\n            this.uncaughtExceptionHandler = uncaughtExceptionHandler;\n            return this;\n        }\n\n        public NamedThreadFactory build() {\n            return new NamedThreadFactory(prefix, daemon, priority, uncaughtExceptionHandler);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/SingleThreadShutdownHook.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport cn.ponfee.commons.exception.Throwables.ThrowingRunnable;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * Single thread runtime shutdown hook.\n *\n * @author Ponfee\n */\npublic class SingleThreadShutdownHook {\n\n    private static final Object LOCK = new Object();\n\n    private static HookThread thread = null;\n\n    public static void addHook(Runnable hook) {\n        synchronized (LOCK) {\n            if (thread == null) {\n                thread = new HookThread();\n                Runtime.getRuntime().addShutdownHook(thread);\n            }\n            thread.hooks.add(hook);\n        }\n    }\n\n    private static class HookThread extends Thread {\n        private final List<Runnable> hooks = new LinkedList<>();\n\n        @Override\n        public void run() {\n            synchronized (LOCK) {\n                hooks.forEach(e -> ThrowingRunnable.doCaught(e::run));\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/ThreadPoolExecutors.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.util.Assert;\n\nimport java.util.concurrent.*;\nimport java.util.concurrent.ThreadPoolExecutor.AbortPolicy;\nimport java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;\nimport java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy;\nimport java.util.concurrent.ThreadPoolExecutor.DiscardPolicy;\n\n/**\n * Thread pool executor utility\n * <a href=\"https://blog.csdn.net/Holmofy/article/details/73237153\">Multiple Thread</a>\n *\n * @author Ponfee\n */\npublic final class ThreadPoolExecutors {\n\n    private final static Logger LOG = LoggerFactory.getLogger(ThreadPoolExecutors.class);\n\n    public static final int MAX_CAP = 0x7FFF; // max #workers - 1\n\n    // ----------------------------------------------------------build-in rejected policy\n    /**\n     * abort and throw RejectedExecutionException\n     */\n    public static final RejectedExecutionHandler ABORT = new AbortPolicy();\n\n    /**\n     * discard the task\n     */\n    public static final RejectedExecutionHandler DISCARD = new DiscardPolicy();\n\n    /**\n     * if not shutdown then run\n     */\n    public static final RejectedExecutionHandler CALLER_RUNS = new CallerRunsPolicy();\n\n    /**\n     * if not shutdown then discard oldest and execute the new\n     */\n    public static final RejectedExecutionHandler DISCARD_OLDEST = new DiscardOldestPolicy();\n\n    /**\n     * if not shutdown then put queue until enqueue\n     */\n    public static final RejectedExecutionHandler CALLER_BLOCKS = (task, executor) -> {\n        if (!executor.isShutdown()) {\n            try {\n                executor.getQueue().put(task);\n            } catch (InterruptedException e) {\n                Thread.currentThread().interrupt();\n                throw new RuntimeException(\"Put a task to queue occur error: BLOCK_PRODUCER\", e);\n            }\n        }\n    };\n\n    /**\n     * anyway always run\n     */\n    public static final RejectedExecutionHandler ALWAYS_CALLER_RUNS = (task, executor) -> task.run();\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public enum PrestartCoreThreadType { NONE, ONE, ALL }\n\n    public static class Builder {\n        private int corePoolSize;\n        private int maximumPoolSize;\n        private BlockingQueue<Runnable> workQueue;\n        private long keepAliveTimeSeconds;\n        private RejectedExecutionHandler rejectedHandler;\n        private ThreadFactory threadFactory;\n        private boolean allowCoreThreadTimeOut = true;\n        private PrestartCoreThreadType prestartCoreThreadType = PrestartCoreThreadType.NONE;\n\n        private Builder() {\n        }\n\n        public Builder corePoolSize(int corePoolSize) {\n            this.corePoolSize = corePoolSize;\n            return this;\n        }\n\n        public Builder maximumPoolSize(int maximumPoolSize) {\n            this.maximumPoolSize = maximumPoolSize;\n            return this;\n        }\n\n        public Builder workQueue(BlockingQueue<Runnable> workQueue) {\n            this.workQueue = workQueue;\n            return this;\n        }\n\n        public Builder keepAliveTimeSeconds(long keepAliveTimeSeconds) {\n            this.keepAliveTimeSeconds = keepAliveTimeSeconds;\n            return this;\n        }\n\n        public Builder rejectedHandler(RejectedExecutionHandler rejectedHandler) {\n            this.rejectedHandler = rejectedHandler;\n            return this;\n        }\n\n        public Builder threadFactory(ThreadFactory threadFactory) {\n            this.threadFactory = threadFactory;\n            return this;\n        }\n\n        public Builder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {\n            this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;\n            return this;\n        }\n\n        public Builder prestartCoreThreadType(PrestartCoreThreadType prestartCoreThreadType) {\n            this.prestartCoreThreadType = prestartCoreThreadType;\n            return this;\n        }\n\n        public ThreadPoolExecutor build() {\n            Assert.isTrue(maximumPoolSize > 0, () -> String.format(\"Maximum pool size %d must greater than 0.\", maximumPoolSize));\n            Assert.isTrue(maximumPoolSize <= MAX_CAP, () -> String.format(\"Maximum pool size %d cannot greater than %d.\", maximumPoolSize, MAX_CAP));\n            Assert.isTrue(corePoolSize > 0, () -> String.format(\"Core pool size %d must greater than 0.\", corePoolSize));\n            Assert.isTrue(corePoolSize <= maximumPoolSize, () -> String.format(\"Core pool size %d cannot greater than maximum pool size %d.\", corePoolSize, maximumPoolSize));\n            Assert.notNull(workQueue, \"Worker queue cannot be null.\");\n\n            // create ThreadPoolExecutor instance\n            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(\n                corePoolSize,\n                maximumPoolSize,\n                keepAliveTimeSeconds,\n                TimeUnit.SECONDS,\n                workQueue,\n                threadFactory != null ? threadFactory : Executors.defaultThreadFactory(),\n                rejectedHandler != null ? rejectedHandler : CALLER_RUNS\n            );\n\n            threadPoolExecutor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);\n            if (prestartCoreThreadType == PrestartCoreThreadType.ONE) {\n                threadPoolExecutor.prestartCoreThread();\n            } else if (prestartCoreThreadType == PrestartCoreThreadType.ALL) {\n                threadPoolExecutor.prestartAllCoreThreads();\n            }\n            return threadPoolExecutor;\n        }\n    }\n\n    // ----------------------------------------------------------\n\n    /**\n     * Shutdown the ExecutorService safe\n     *\n     * @param executorService the executorService\n     * @return is safe shutdown\n     */\n    public static boolean shutdown(ExecutorService executorService) {\n        executorService.shutdown();\n        try {\n            while (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {\n                // do nothing\n            }\n            return true;\n        } catch (Exception e) {\n            LOG.error(\"Shutdown ExecutorService occur error.\", e);\n            executorService.shutdownNow();\n            Threads.interruptIfNecessary(e);\n            return false;\n        }\n    }\n\n    /**\n     * Shutdown the executorService max wait time\n     *\n     * @param executorService the executorService\n     * @param awaitSeconds    await time seconds\n     * @return {@code true} if safe terminate\n     */\n    public static boolean shutdown(ExecutorService executorService, int awaitSeconds) {\n        executorService.shutdown();\n        boolean isSafeTerminated = false, hasCallShutdownNow = false;\n        try {\n            isSafeTerminated = executorService.awaitTermination(awaitSeconds, TimeUnit.SECONDS);\n            if (!isSafeTerminated) {\n                hasCallShutdownNow = true;\n                executorService.shutdownNow();\n            }\n        } catch (Exception e) {\n            LOG.error(\"Shutdown ExecutorService occur error.\", e);\n            if (!hasCallShutdownNow) {\n                executorService.shutdownNow();\n            }\n            Threads.interruptIfNecessary(e);\n        }\n        return isSafeTerminated;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/ThreadPoolMonitor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport cn.ponfee.commons.model.ToJsonString;\nimport lombok.Getter;\n\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 线程池监控信息\n * \n * @author Ponfee\n */\n@Getter\npublic class ThreadPoolMonitor extends ToJsonString implements java.io.Serializable {\n    private static final long serialVersionUID = 3890678647435855868L;\n\n    private final long keepAliveTime;\n\n    private final int corePoolSize;\n    private final int maximumPoolSize;\n    private final int largestPoolSize;\n    private final int poolSize;\n\n    private final long taskCount;\n    private final long completedTaskCount;\n    private final int queueSize;\n    private final int activeCount;\n\n    private final boolean shutdown;\n    private final boolean terminated;\n\n    public ThreadPoolMonitor(ThreadPoolExecutor pool) {\n        this.keepAliveTime = pool.getKeepAliveTime(TimeUnit.MILLISECONDS);\n\n        this.corePoolSize = pool.getCorePoolSize();\n        this.maximumPoolSize = pool.getMaximumPoolSize();\n        this.largestPoolSize = pool.getLargestPoolSize();\n        this.poolSize = pool.getPoolSize();\n\n        this.taskCount = pool.getTaskCount();\n        this.completedTaskCount = pool.getCompletedTaskCount();\n        this.queueSize = pool.getQueue().size();\n        this.activeCount = pool.getActiveCount();\n\n        this.shutdown = pool.isShutdown();\n        this.terminated = pool.isTerminated();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/Threads.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport cn.ponfee.commons.util.ObjectUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Thread Utilities\n * \n * @author Ponfee\n */\npublic final class Threads {\n    private final static Logger LOG = LoggerFactory.getLogger(ThreadPoolExecutors.class);\n\n    /**\n     * Returns the thread is whether stopped\n     *\n     * @param thread the thread\n     * @return {@code true} if the thread is stopped\n     */\n    public static boolean isStopped(Thread thread) {\n        return thread.getState() == Thread.State.TERMINATED;\n    }\n\n    /**\n     * Stop the thread, and return boolean result of has called java.lang.Thread#stop()\n     *\n     * @param thread      the thread\n     * @param sleepCount  the sleepCount\n     * @param sleepMillis the sleepMillis\n     * @param joinMillis  the joinMillis\n     * @return {@code true} if called java.lang.Thread#stop()\n     */\n    public static boolean stopThread(Thread thread, int sleepCount, long sleepMillis, long joinMillis) {\n        if (isStopped(thread)) {\n            return false;\n        }\n\n        if (Thread.currentThread() == thread) {\n            LOG.warn(\"Call stop on self thread: {}\\n{}\", thread.getName(), ObjectUtils.getStackTrace());\n            thread.interrupt();\n            return stopThread(thread);\n        }\n\n        LOG.info(\"Thread stopping: {}\", thread.getName());\n        while (sleepCount-- > 0 && sleepMillis > 0 && !isStopped(thread)) {\n            try {\n                // Wait some time\n                Thread.sleep(sleepMillis);\n            } catch (InterruptedException e) {\n                LOG.error(\"Waiting thread terminal interrupted: \" + thread.getName(), e);\n                thread.interrupt();\n                Thread.currentThread().interrupt();\n            }\n        }\n\n        if (!isStopped(thread)) {\n            // interrupt and wait joined\n            thread.interrupt();\n            if (joinMillis > 0) {\n                try {\n                    thread.join(joinMillis);\n                } catch (InterruptedException e) {\n                    LOG.error(\"Join thread terminal interrupted: \" + thread.getName(), e);\n                    thread.interrupt();\n                    Thread.currentThread().interrupt();\n                }\n            }\n        }\n\n        return stopThread(thread);\n    }\n\n    public static void interruptIfNecessary(Throwable t) {\n        if (t instanceof InterruptedException) {\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    /**\n     * Stop the thread, and return boolean result of has called java.lang.Thread#stop()\n     *\n     * @param thread the thread\n     * @return {@code true} if called java.lang.Thread#stop()\n     */\n    private static boolean stopThread(Thread thread) {\n        if (isStopped(thread)) {\n            return false;\n        }\n\n        synchronized (thread) {\n            if (isStopped(thread)) {\n                return false;\n            }\n            try {\n                thread.stop();\n                // cannot catch Throwable, because it will occur \"java.lang.ThreadDeath: null\"\n            } catch (Exception e) {\n                LOG.error(\"Invoke thread stop occur error: \" + thread.getName(), e);\n            }\n            LOG.warn(\"Invoked java.lang.Thread#stop() method: {}\", thread.getName());\n        }\n\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/concurrent/TracedRunnable.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.concurrent;\n\nimport org.slf4j.MDC;\n\nimport java.util.Map;\n\n/**\n * Traced runnable\n * \n * @author Ponfee\n */\npublic final class TracedRunnable implements Runnable {\n\n    private final Runnable runnable;\n\n    private TracedRunnable(Runnable runnable) {\n        this.runnable = runnable;\n    }\n\n    public static TracedRunnable of(Runnable runnable) {\n        return new TracedRunnable(runnable);\n    }\n\n    @Override\n    public void run() {\n        Map<String, String> ctx = MDC.getCopyOfContextMap();\n        if (ctx != null) {\n            MDC.setContextMap(ctx);\n        }\n        try {\n            runnable.run();\n        } finally {\n            MDC.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/constrain/ConstrainParam.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.constrain;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * <pre>\n * 方法参数验证，用于方法参数内，e.g.\n * public void method(@ConstrainParam SomeBean param);\n * </pre>\n * \n * @author Ponfee\n */\n@Target({ ElementType.PARAMETER })\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface ConstrainParam {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/constrain/Constraint.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.constrain;\n\nimport java.lang.annotation.*;\n\n/**\n * <pre>\n *  `@Constraints({\n *      `@Constraint(field = \"name\", notBlank = true, maxLen = 64),\n *      `@Constraint(field = \"type\", series = { 1, 2 })\n *  })\n *\n *  or\n *\n *  `@Constraint(notBlank = true, maxLen = 64)\n *  private String name;\n * </pre>\n *\n * 参数约束\n * \n * @author Ponfee\n */\n@Target({ElementType.FIELD, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Repeatable(Constraints.class) // 当只有一个Constraint时要包上Constraints\n@Documented\npublic @interface Constraint {\n\n    /**\n     * 参数索引位置（第一个参数为0，第二个参数为1，...，第N个参数为N-1）\n     */\n    int index() default 0;\n\n    /**\n     * 基本数据类型参数不用设值，对象类型参数为字段名\n     */\n    String field() default \"\"; // can not use in class filed\n\n    /**\n     * 校验失败时的提示信息（不设置时自动拼装为如：orderNo{null}：not allow blank;）\n     */\n    String msg() default \"\";\n\n    /**\n     * 是否不能为null ， 为true表示不能为空 ， false表示能够为空（对所有类型有效，默认不能为空）\n     */\n    boolean notNull() default true;\n\n    /**\n     * 是否不能为空（只针对CharSequence,Collection,Map,Dictionary,Array）\n     */\n    boolean notEmpty() default false;\n\n    /**\n     * 是否不为空白串（只针对CharSequence）\n     */\n    boolean notBlank() default false;\n\n    /**\n     * 最大长度（只针对CharSequence）\n     */\n    int maxLen() default -1;\n\n    /**\n     * 最小长度（只针对CharSequence）\n     */\n    int minLen() default -1;\n\n    /**\n     * 正则验证（只针对CharSequence）\n     */\n    String regExp() default \"\";\n\n    /**\n     * 最大值（只针对整数）\n     * the max value cannot supported Long.MAX_VALUE\n     */\n    long max() default Long.MAX_VALUE;\n\n    /**\n     * 最小值（只针对整数）\n     * the max value cannot supported Long.MIN_VALUE\n     */\n    long min() default Long.MIN_VALUE;\n\n    /**\n     * 日期格式（只针对CharSequence）\n     */\n    String datePattern() default \"\";\n\n    /**\n     * 时态（只针对CharSequence[datePattern]，Date）\n     */\n    Tense tense() default Tense.ANY;\n\n    /**\n     * 数列\n     */\n    long[] series() default {};\n\n    /**\n     * 最大值（只针对浮点数）\n     * the decimalMax value cannot supported Double.POSITIVE_INFINITY\n     */\n    double decimalMax() default Double.POSITIVE_INFINITY;\n\n    /**\n     * 最小值（只针对浮点数）\n     * the decimalMin value cannot supported Double.NEGATIVE_INFINITY\n     */\n    double decimalMin() default Double.NEGATIVE_INFINITY;\n\n    /**\n     * 时态（过去或将来）\n     */\n    enum Tense {\n        PAST(\"过去\"), FUTURE(\"将来\"), ANY(\"任意\");\n\n        private final String desc;\n\n        Tense(String desc) {\n            this.desc = desc;\n        }\n\n        public String desc() {\n            return this.desc;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/constrain/Constraints.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.constrain;\n\nimport java.lang.annotation.*;\n\n/**\n * <pre>\n *  `@Constraints({\n *      `@Constraint(field = \"name\", notBlank = true, maxLen = 64),\n *      `@Constraint(field = \"type\", series = { 1, 2 }),\n *  })\n * </pre>\n * \n * 方法参数校验器\n * \n * @author Ponfee\n */\n@Target({ ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface Constraints {\n    Constraint[] value() default {};\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/constrain/FailFastValidatorFactoryBean.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.constrain;\n\nimport org.hibernate.validator.HibernateValidator;\nimport org.springframework.beans.factory.FactoryBean;\n\nimport javax.validation.Validation;\nimport javax.validation.Validator;\n\n/**\n * For fail fast validator factory bean\n * \n * @author Ponfee\n */\npublic class FailFastValidatorFactoryBean implements FactoryBean<Validator> {\n\n    private final Validator validator;\n    private final Class<? extends Validator> validatorType;\n\n    public FailFastValidatorFactoryBean() {\n        this.validator = Validation\n            .byProvider(HibernateValidator.class)\n            .configure()\n            .failFast(true)\n            //.addProperty(\"hibernate.validator.fail_fast\", \"true\")\n            .buildValidatorFactory()\n            .getValidator();\n\n        this.validatorType = this.validator.getClass();\n    }\n\n    @Override\n    public Validator getObject() {\n        return this.validator;\n    }\n\n    @Override\n    public Class<?> getObjectType() {\n        return this.validatorType;\n    }\n\n    @Override\n    public boolean isSingleton() {\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/constrain/FieldValidator.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.constrain;\n\nimport cn.ponfee.commons.base.PrimitiveTypes;\nimport cn.ponfee.commons.base.tuple.Tuple2;\nimport cn.ponfee.commons.date.Dates;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.RegexUtils;\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.time.DateFormatUtils;\nimport org.apache.commons.lang3.time.DateUtils;\nimport org.apache.commons.lang3.tuple.ImmutablePair;\nimport org.apache.commons.lang3.tuple.Pair;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.GenericDeclaration;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.text.ParseException;\nimport java.util.*;\n\nimport static cn.ponfee.commons.model.ResultCode.BAD_REQUEST;\nimport static org.apache.commons.lang3.StringUtils.isBlank;\nimport static org.apache.commons.lang3.StringUtils.isNotBlank;\n\n/**\n * <pre>\n *   校验bean实体中含@Constraint注解的属性\n *   e.g.：FieldValidator.newInstance().constrain(bean);\n * </pre>\n *\n * GAV: net.sf.oval:oval:1.90\n *\n * 字段校验\n *\n * @author Ponfee\n */\npublic class FieldValidator {\n\n    private static final Logger LOG = LoggerFactory.getLogger(FieldValidator.class);\n\n    static final int MAX_MSG_SIZE = 500;\n    private static final String CFG_ERR = \"约束配置错误[\";\n    private static final String EMPTY = \"\";\n\n    // Method equals & same hashCode\n    static final Cache<Method, String[]> METHOD_ARGSNAME = CacheBuilder.newBuilder().build();\n\n    private static final Cache<Tuple2<?, ?>, Pair<Boolean, String>> META_CFG_CACHE = CacheBuilder.newBuilder().build();\n\n    protected FieldValidator() {}\n\n    public static FieldValidator newInstance() {\n        return new FieldValidator();\n    }\n\n    /**\n     * 约束验证\n     * @param bean\n     */\n    public final void constrain(Object bean) {\n        Class<?> clazz = bean.getClass();\n        if (clazz.isInterface()) {\n            throw new UnsupportedOperationException(\"unsupported interface constrain.\");\n        }\n\n        StringBuilder builder = new StringBuilder();\n        while (!clazz.equals(Object.class)) {\n            for (Field field : clazz.getDeclaredFields()) {\n                Constraint cst = field.getAnnotation(Constraint.class);\n                if (cst == null || Modifier.isStatic(field.getModifiers())) {\n                    continue;\n                }\n\n                builder.append(\n                    constrain(clazz, field.getName(), Fields.get(bean, field), cst, GenericUtils.getFieldActualType(clazz, field))\n                );\n            }\n            clazz = clazz.getSuperclass();\n        }\n\n        if (builder.length() > 0) {\n            throw new IllegalArgumentException(builder.toString());\n        }\n    }\n\n    protected Object processError(StringBuilder errorMsgBuilder, Method method, Object[] args) {\n        // 校验失败，不调用方法，进入失败处理\n        if (errorMsgBuilder.length() > MAX_MSG_SIZE) {\n            errorMsgBuilder.setLength(MAX_MSG_SIZE - 3);\n            errorMsgBuilder.append(\"...\");\n        }\n        String errMsg = errorMsgBuilder.toString();\n        if (LOG.isInfoEnabled()) {\n            LOG.info(\n                \"[args check not pass]-[{}]-{}-[{}]\",\n                method.toGenericString(), ObjectUtils.toString(args), errMsg\n            );\n        }\n\n        return handleFailure(method.getReturnType(), errMsg);\n    }\n\n    protected Object handleFailure(Class<?> returnType, String errMsg) {\n        if (returnType == Result.class) {\n            return Result.failure(BAD_REQUEST.getCode(), errMsg);\n        } else {\n            throw new IllegalArgumentException(errMsg);\n        }\n    }\n\n    protected final String constrain(GenericDeclaration classOrMethod, String field,\n                                     Object value, Constraint cst, Class<?> type) {\n        Tuple2<?, ?> key = Tuple2.of(classOrMethod, field);\n        Pair<Boolean, String> result = META_CFG_CACHE.getIfPresent(key);\n        if (result == null) {\n            synchronized (META_CFG_CACHE) {\n                if ((result = META_CFG_CACHE.getIfPresent(key)) == null) {\n                    try {\n                        verifyMeta(field, cst, type);\n                        result = ImmutablePair.of(true, null);\n                        META_CFG_CACHE.put(key, result);\n                    } catch (Exception e) {\n                        result = ImmutablePair.of(false, e.getMessage());\n                        META_CFG_CACHE.put(key, result);\n                        throw e;\n                    }\n                }\n            }\n        }\n\n        // 配置验证\n        if (!result.getLeft()) {\n            throw new UnsupportedOperationException(result.getRight());\n        }\n\n        // 参数验证\n        return verifyValue(field, value, cst);\n    }\n\n    /**\n     * Constrain without cache,\n     * only use in method parameter is Map or Dictionary type\n     *\n     * @param name\n     * @param value\n     * @param cst\n     * @param type\n     * @return the constrain result, if empty string means success\n     */\n    protected final String constrain(String name, Object value,\n                                     Constraint cst, Class<?> type) {\n        // 配置验证\n        verifyMeta(name, cst, type);\n\n        // 参数验证\n        return verifyValue(name, value, cst);\n    }\n\n    /**\n     * 配置元数据验证\n     * @param name\n     * @param c\n     * @param type\n     */\n    private void verifyMeta(String name, Constraint c, Class<?> type) {\n        if (type == null) {\n            return;\n        }\n\n        // 基本类型转包装类型（如果是）\n        type = PrimitiveTypes.ofPrimitive(type).wrapper();\n\n        // 字串类型验证\n        if (   (isNotBlank(c.regExp()) || isNotBlank(c.datePattern()) || c.notBlank() || c.maxLen() > -1 || c.minLen() > -1)\n            && !CharSequence.class.isAssignableFrom(type)\n        ) {\n            throw new UnsupportedOperationException(CFG_ERR + name + \"]：非字符类型不支持字符规则验证\");\n        }\n\n        // 整数类型验证\n        if (   !(c.max() == Long.MAX_VALUE && c.min() == Long.MIN_VALUE) // meet not all the conditions\n            && !(Long.class.isAssignableFrom(type) || Integer.class.isAssignableFrom(type))\n        ) {\n            throw new UnsupportedOperationException(CFG_ERR + name + \"]：非整数类型不支持整数数值验证\");\n        }\n\n        // 数列类型验证\n        if (   (c.series() != null && c.series().length > 0)\n            && !(Long.class.isAssignableFrom(type) || Integer.class.isAssignableFrom(type))\n        ) {\n            throw new UnsupportedOperationException(CFG_ERR + name + \"]：非整数类型不支持数列验证\");\n        }\n\n        // 小数类型验证\n        if (   !(c.decimalMax() == Double.POSITIVE_INFINITY && c.decimalMin() == Double.NEGATIVE_INFINITY)\n            && !(Double.class.isAssignableFrom(type) || Float.class.isAssignableFrom(type))\n        ) {\n            throw new UnsupportedOperationException(CFG_ERR + name + \"]：非浮点数类型不支持浮点数值验证\");\n        }\n\n        // 时间类型验证\n        if (   (c.tense() != Constraint.Tense.ANY && isBlank(c.datePattern()))\n            && !Date.class.isAssignableFrom(type)\n        ) {\n            throw new UnsupportedOperationException(CFG_ERR + name + \"]：非日期类型不支持时态验证\");\n        }\n\n        // 字串、集合类型验证\n        if (c.notEmpty() && !isEmptiable(type)) {\n            throw new UnsupportedOperationException(CFG_ERR + name + \"非集合/字符类型不支持非空验证\");\n        }\n    }\n\n    /**\n     * Returns the type instance value can with empty verifyValue\n     *\n     * @param type  the class type\n     * @return {@code true} means should with empty verifyValue\n     */\n    private boolean isEmptiable(Class<?> type) {\n        return CharSequence.class.isAssignableFrom(type)\n            || Collection.class.isAssignableFrom(type)\n            || type.isArray()\n            || Map.class.isAssignableFrom(type)\n            || Dictionary.class.isAssignableFrom(type);\n    }\n\n    private String verifyValue(String str, Object value, Constraint cst) {\n        String error = verify(str, value, cst);\n        if (isNotBlank(error)) { // verify result is not pass\n            return isNotBlank(cst.msg()) ? cst.msg() + \";\" : error;\n        } else {\n            return EMPTY;\n        }\n    }\n\n    /**\n     * 参数验证\n     * @param n\n     * @param v\n     * @param c\n     * @return\n     */\n    private String verify(String n, Object v, Constraint c) {\n        // 可以为null且值为null，则跳过验证\n        if (!c.notNull() && v == null) {\n            return EMPTY;\n        }\n\n        // 是否不能为blank\n        if (c.notBlank() && isBlank((CharSequence) v)) {\n            return n + \"{\" + v + \"}：不能为空串;\";\n        }\n\n        // 是否不能为empty\n        if (c.notEmpty() && ObjectUtils.isEmpty(v)) {\n            return n + \"{\" + v + \"}：不能为empty;\";\n        }\n\n        // 是否不能为null\n        if (c.notNull() && v == null) {\n            return n + \"{null}：不能为null;\";\n        }\n\n        // 正则校验\n        if (!(!c.notNull() && v == null) && isNotBlank(c.regExp())\n            && (v == null || !RegexUtils.matches(v.toString(), c.regExp()))) {\n            return n + \"{\" + v + \"}：格式不匹配\" + c.regExp() + \";\";\n        }\n\n        // 最大字符长度\n        if (c.maxLen() > -1 && v != null && ((CharSequence) v).length() > c.maxLen()) {\n            return n + \"{\" + v + \"}：不能大于\" + c.maxLen() + \"个字符;\";\n        }\n\n        // 最小字符长度\n        if (c.minLen() > -1 && (v == null || ((CharSequence) v).length() < c.minLen())) {\n            return n + \"{\" + v + \"}：不能小于\" + c.minLen() + \"个字符;\";\n        }\n\n        // 整数值限制\n        if (c.max() != Long.MAX_VALUE && v != null && Long.parseLong(v.toString()) > c.max()) {\n            return n + \"{\" + v + \"}：不能大于\" + c.max() + \";\";\n        }\n        if (c.min() != Long.MIN_VALUE && (v == null || Long.parseLong(v.toString()) < c.min())) {\n            return n + \"{\" + v + \"}：不能小于\" + c.min() + \";\";\n        }\n\n        // 数列验证\n        long[] seqs = c.series();\n        if ((seqs != null && seqs.length > 0)\n            && (v == null || !ArrayUtils.contains(seqs, Long.parseLong(v.toString())))) {\n            return n + \"{\" + v + \"}：不属于数列\" + Arrays.toString(seqs) + \";\";\n        }\n\n        // 浮点数限制\n        if (c.decimalMax() != Double.POSITIVE_INFINITY && v != null\n            && Double.parseDouble(v.toString()) > c.decimalMax()) {\n            return n + \"{\" + v + \"}：不能大于\" + c.decimalMax() + \";\";\n        }\n        if (c.decimalMin() != Double.NEGATIVE_INFINITY\n            && (v == null || Double.parseDouble(v.toString()) < c.decimalMin())) {\n            return n + \"{\" + v + \"}：不能小于\" + c.decimalMin() + \";\";\n        }\n\n        // 时间格式\n        Date date = null;\n        if (isNotBlank(c.datePattern()) && !(!c.notNull() && (v == null || ObjectUtils.isEmpty(v)))) {\n            try {\n                date = DateUtils.parseDateStrictly((String) v, c.datePattern());\n                //date = FastDateFormat.getInstance(c.datePattern()).parse((String) v);\n            } catch (ParseException e) {\n                return n + \"{\" + v + \"}：日期格式不匹配\" + c.datePattern() + \";\";\n            }\n        }\n\n        // 时态校验\n        if (c.tense() != Constraint.Tense.ANY) {\n            if (date == null) {\n                date = (Date) v;\n            }\n\n            String pattern = c.datePattern();\n            if (isBlank(pattern)) {\n                pattern = Dates.DATETIME_PATTERN;\n            }\n            if (date == null) {\n                return n + \"{null}：日期不能为空;\";\n            } else if (c.tense() == Constraint.Tense.FUTURE && date.before(new Date())) {\n                return n + \"{\" + DateFormatUtils.format(date, pattern) + \"}：不为将来时间;\";\n            } else if (c.tense() == Constraint.Tense.PAST && date.after(new Date())) {\n                return n + \"{\" + DateFormatUtils.format(date, pattern) + \"}：不为过去时间;\";\n            }\n        }\n\n        return EMPTY;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/constrain/Jsr303Validator.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.constrain;\n\nimport cn.ponfee.commons.model.Result;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.ObjectError;\n\nimport java.util.stream.Collectors;\n\nimport static cn.ponfee.commons.model.ResultCode.BAD_REQUEST;\n\n/**\n * 基于JSR303的Web端参数校验统一处理\n * \n * Controller的方法中有BindingResult参数，则spring框架会进入Controller的方法内\n * public Result<Void> testValidate(@Valid Article article, BindingResult result) {}\n * \n * @author Ponfee\n */\n//@ControllerAdvice\n//@Aspect\n//@Order(Ordered.HIGHEST_PRECEDENCE)\npublic abstract class Jsr303Validator {\n\n    //@Around(\"execution(public * cn.ponfee.xxx.controller..*Controller..*(..)) && args(..,bindingResult)\")\n    public Object verify(ProceedingJoinPoint pjp, BindingResult bindingResult) throws Throwable {\n        if (bindingResult.hasErrors()) {\n            return handleFailure(\n                ((MethodSignature) pjp.getSignature()).getMethod().getReturnType(), bindingResult\n            );\n        }\n        return pjp.proceed();\n    }\n\n    protected Object handleFailure(Class<?> returnType, BindingResult bindingResult) {\n        String errorMsg = bindingResult.getAllErrors()\n                                       .stream()\n                                       .map(ObjectError::getDefaultMessage)\n                                       .collect(Collectors.joining(\",\", \"[\", \"]\"));\n        if (returnType == Result.class) {\n            return Result.failure(BAD_REQUEST.getCode(), BAD_REQUEST.getMsg() + \": \" + errorMsg);\n        } else {\n            throw new IllegalArgumentException(errorMsg);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/constrain/MethodValidator.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.constrain;\n\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.*;\n\n/**\n * <pre>\n * 方法参数校验：拦截方法中包含@Constraints注解的方法\n * e.g.：\n *    1.开启spring切面特性：<aop:aspectj-autoproxy />\n *    2.编写子类：\n *        ＠Component ＠Aspect\n *        public class TestMethodValidator extends MethodValidator {\n *\n *            ＠Around(\n *                value = \"execution(public * cn.ponfee.xxx.service.impl..*Impl..*(..)) && ＠annotation(cst)\", \n *                argNames = \"pjp,cst\"\n *            )\n *            ＠Override\n *            public Object constrain(ProceedingJoinPoint pjp, Constraints cst) throws Throwable {\n *                return super.constrain(pjp, cst);\n *            }\n *\n *            ＠Override\n *            protected Object handleFailure(Class<?> returnType, String errMsg) {\n *                if (BaseResult.class.isAssignableFrom(returnType)) {\n *                    return BaseResult.failure(errMsg);\n *                }\n *                return super.handleFailure(returnType, errMsg);\n *            }\n *\n *        }\n * </pre>\n * \n * 参数校验\n * \n * @author Ponfee\n */\npublic abstract class MethodValidator extends FieldValidator {\n\n    private static final Logger LOG = LoggerFactory.getLogger(MethodValidator.class);\n\n    /**\n     * @param pjp\n     * @param validator\n     * @return\n     * @throws Throwable\n     */\n    @SuppressWarnings(\"unchecked\")\n    public Object constrain(ProceedingJoinPoint pjp, Constraints validator) throws Throwable {\n        Object[] args = pjp.getArgs();\n        if (args == null || args.length == 0) {\n            return pjp.proceed();\n        }\n\n        Method method = ((MethodSignature) pjp.getSignature()).getMethod();\n        String[] argsName = METHOD_ARGSNAME.getIfPresent(method);\n        if (argsName == null) {\n            // 要用到asm字节码操作，消耗性能，所以缓存\n            argsName = ClassUtils.getMethodParamNames(method);\n            METHOD_ARGSNAME.put(method, argsName);\n        }\n\n        // 校验开始\n        StringBuilder builder = new StringBuilder();\n        Class<?>[] paramTypes = method.getParameterTypes();\n        Constraint cst;\n        String fieldName;\n        Object fieldVal;\n        Class<?> fieldType;\n        Constraint[] csts = validator.value();\n        try {\n            boolean[] argsNullable = argsNullable(args, csts);\n            for (int len = csts.length, i = 0; i < len; i++) {\n                cst = csts[i];\n                fieldVal = args[cst.index()]; // 参数对象校验\n                fieldType = paramTypes[cst.index()];\n                if (argsNullable[cst.index()] && fieldVal == null) {\n                    continue; // 参数可为空，则跳过校验\n                } else if (StringUtils.isEmpty(cst.field())) {\n                    // 验证参数对象\n                    fieldName = argsName[cst.index()];\n                    builder.append(constrain(method, fieldName, fieldVal, cst, fieldType));\n                } else if (fieldVal == null) {\n                    // 不可为空，则抛出异常\n                    String msg;\n                    if (args.length == 1) {\n                        msg = \"参数不能为空;\";\n                    } else {\n                        msg = \"参数{\" + argsName[cst.index()] + \"}不能为空;\";\n                    }\n                    throw new IllegalArgumentException(msg);\n                } else if (fieldVal instanceof Map) {\n                    /*Method get = fieldVal.getClass().getMethod(\"get\", Object.class);\n                    get.setAccessible(true); // ImmutableMap must be set accessible true\n                    fieldVal = get.invoke(fieldVal, cst.field());*/\n                    fieldVal = ((Map<String, Object>) fieldVal).get(cst.field());\n                    fieldType = fieldVal == null ? null : fieldVal.getClass();\n                    fieldName = argsName[cst.index()] + \"[\" + cst.field() + \"]\";\n                    builder.append(constrain(fieldName, fieldVal, cst, fieldType)); // cannot cache\n                } else if (fieldVal instanceof Dictionary) {\n                    fieldVal = ((Dictionary<String, Object>) fieldVal).get(cst.field());\n                    fieldType = fieldVal == null ? null : fieldVal.getClass();\n                    fieldName = argsName[cst.index()] + \"[\" + cst.field() + \"]\";\n                    builder.append(constrain(fieldName, fieldVal, cst, fieldType)); // cannot cache\n                } else {\n                    // 验证java bean\n                    String[] ognl = cst.field().split(\"\\\\.\");\n                    Field field;\n                    for (String s : ognl) {\n                        field = ClassUtils.getField(fieldType, s);\n                        fieldType = GenericUtils.getFieldActualType(fieldType, field);\n                        if (fieldVal != null) {\n                            fieldVal = Fields.get(fieldVal, field);\n                        }\n                    }\n                    fieldName = argsName[cst.index()] + \".\" + cst.field();\n                    builder.append(constrain(method, fieldName, fieldVal, cst, fieldType));\n                }\n\n                if (builder.length() > MAX_MSG_SIZE) {\n                    break;\n                }\n            }\n        } catch (UnsupportedOperationException | IllegalArgumentException e) {\n            builder.append(e.getMessage());\n        } catch (Exception e) {\n            LOG.error(\"参数约束校验异常\", e);\n            builder.append(\"参数约束校验异常：\").append(e.getMessage());\n        }\n\n        return builder.length() == 0 ? pjp.proceed() : processError(builder, method, args);\n    }\n\n    // -------------------------------------------------------------------------private methods\n    private boolean[] argsNullable(Object[] args, Constraint[] csts) {\n        Set<String> set = new HashSet<>(csts.length);\n        boolean[] isArgsNullable = new boolean[args.length];\n        Arrays.fill(isArgsNullable, false);\n        for (Constraint cst : csts) {\n            String key = \"index=\" + cst.index() + \", field=\\\"\" + cst.field() + \"\\\"\";\n            if (!set.add(key)) {\n                throw new RuntimeException(\"配置错误，重复校验[\" + key + \"]\");\n            }\n\n            if (cst.index() > args.length - 1) {\n                throw new RuntimeException(\"配置错误，下标超出[index=\" + cst.index() + \"]\");\n            }\n\n            if (StringUtils.isEmpty(cst.field()) && !cst.notNull()) {\n                isArgsNullable[cst.index()] = true; // 该参数可为空\n            }\n        }\n        return isArgsNullable;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/constrain/ParamValidator.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.constrain;\n\nimport cn.ponfee.commons.exception.Throwables;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Method;\n\n/**\n * <pre>\n * 方法参数校验：拦截参数中包含@ConstrainParam注解的方法\n * ＠Component\n * ＠Aspect\n * public class TestParamValidator extends ParamValidator {\n *    ＠Around(value = \"execution(public * cn.ponfee.xxx.service.impl.*Impl.*(\n *         ＠cn.ponfee.commons.constrain.ConstrainParam (*)\n *    ))\")\n *    public ＠Override Object constrain(ProceedingJoinPoint joinPoint) throws Throwable {\n *      return super.constrain(joinPoint);\n *    }\n * }\n * </pre>\n * \n * @author Ponfee\n */\npublic abstract class ParamValidator extends FieldValidator {\n\n    private static final Logger LOG = LoggerFactory.getLogger(ParamValidator.class);\n\n    /**\n     * @param joinPoint\n     * @return\n     * @throws Throwable\n     */\n    public Object constrain(ProceedingJoinPoint joinPoint) throws Throwable {\n        Object[] args = joinPoint.getArgs();\n        if (args == null || args.length == 0) {\n            return joinPoint.proceed();\n        }\n\n        // 参数校验\n        StringBuilder builder = new StringBuilder();\n        String[] argsName;\n        Method method = null;\n        try {\n            // 缓存方法参数名\n            MethodSignature mSign = (MethodSignature) joinPoint.getSignature();\n            method = joinPoint.getTarget().getClass()\n                              .getMethod(mSign.getName(), mSign.getParameterTypes());\n            argsName = METHOD_ARGSNAME.getIfPresent(method);\n            if (argsName == null) {\n                argsName = ClassUtils.getMethodParamNames(method);\n                METHOD_ARGSNAME.put(method, argsName);\n            }\n\n            // 方法参数注解校验\n            Annotation[][] anns = method.getParameterAnnotations();\n\n            outer: // this is the label for the outer loop\n            for (int i = 0; i < args.length; i++) {\n                for (Annotation ann : anns[i]) {\n                    if (ann instanceof ConstrainParam) {\n                        try {\n                            constrain(args[i]);\n                        } catch (IllegalArgumentException e) {\n                            builder.append(\"[\").append(argsName[i]).append(\"]\").append(e.getMessage());\n                        }\n                    }\n                    if (builder.length() > MAX_MSG_SIZE) {\n                        break outer;\n                    }\n                }\n            }\n        } catch (UnsupportedOperationException e) {\n            builder.append(e.getMessage());\n        } catch (NoSuchMethodException e) {\n            LOG.error(\"reflect exception\", e);\n            builder.append(Throwables.getRootCauseStackTrace(e));\n        }\n\n        return builder.length() == 0 ? joinPoint.proceed() : processError(builder, method, args);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/dag/DAGEdge.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.dag;\n\nimport cn.ponfee.commons.model.ToJsonString;\nimport com.google.common.graph.EndpointPair;\n\nimport java.io.Serializable;\nimport java.util.Objects;\n\n/**\n * DAG Edge\n *\n * @author Ponfee\n */\npublic final class DAGEdge extends ToJsonString implements Serializable {\n    private static final long serialVersionUID = 2292231888365728538L;\n\n    private final DAGNode source;\n    private final DAGNode target;\n\n    private DAGEdge(DAGNode source, DAGNode target) {\n        this.source = Objects.requireNonNull(source, \"DAG source node cannot be null.\");\n        this.target = Objects.requireNonNull(target, \"DAG target node cannot be null.\");\n    }\n\n    public static DAGEdge of(DAGNode source, DAGNode target) {\n        return new DAGEdge(source, target);\n    }\n\n    public static DAGEdge of(String source, String target) {\n        Objects.requireNonNull(source, \"DAG source text cannot be blank.\");\n        Objects.requireNonNull(target, \"DAG target text cannot be blank.\");\n        return new DAGEdge(DAGNode.fromString(source), DAGNode.fromString(target));\n    }\n\n    public static DAGEdge of(EndpointPair<DAGNode> pair) {\n        Objects.requireNonNull(pair, \"DAG node pair cannot be blank.\");\n        return new DAGEdge(pair.source(), pair.target());\n    }\n\n    public DAGNode getSource() {\n        return source;\n    }\n\n    public DAGNode getTarget() {\n        return target;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(source, target);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this) {\n            return true;\n        }\n        if (!(obj instanceof DAGEdge)) {\n            return false;\n        }\n\n        DAGEdge other = (DAGEdge) obj;\n        return this.source.equals(other.source)\n            && this.target.equals(other.target);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/dag/DAGExpressionParser.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.dag;\n\nimport cn.ponfee.commons.base.Predicates;\nimport cn.ponfee.commons.base.Symbol.Char;\nimport cn.ponfee.commons.base.Symbol.Str;\nimport cn.ponfee.commons.base.tuple.Tuple2;\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.tree.BaseNode;\nimport cn.ponfee.commons.tree.PlainNode;\nimport cn.ponfee.commons.tree.TreeNode;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.graph.Graph;\nimport com.google.common.graph.GraphBuilder;\nimport com.google.common.graph.Graphs;\nimport com.google.common.graph.ImmutableGraph;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.util.Assert;\n\nimport java.io.Serializable;\nimport java.util.*;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Parse DAG expression to graph\n *\n * <pre>\n * 解析：new DAGParser(\"A->((B->C->D),(A->F))->(G,H,X)->J; A->Y\").parse();\n * 结果：\n *  \"A->((B->C->D),(A->F))->(G,H,X)->J\"\n *    <0:0:Start -> 1:1:A>\n *    <1:1:A -> 1:1:B>\n *    <1:1:A -> 1:2:A>\n *    <1:1:B -> 1:1:C>\n *    <1:1:C -> 1:1:D>\n *    <1:2:A -> 1:1:F>\n *    <1:1:D -> 1:1:G>\n *    <1:1:D -> 1:1:H>\n *    <1:1:D -> 1:1:X>\n *    <1:1:F -> 1:1:G>\n *    <1:1:F -> 1:1:H>\n *    <1:1:F -> 1:1:X>\n *    <1:1:G -> 1:1:J>\n *    <1:1:H -> 1:1:J>\n *    <1:1:X -> 1:1:J>\n *    <1:1:J -> 0:0:End>\n *\n *  \"A->Y\"\n *    <0:0:Start -> 2:3:A>\n *    <2:3:A -> 2:1:Y>\n *    <2:1:Y -> 0:0:End>\n *\n * ---------------------------------------------------\n *\n * 无法用expression来表达的场景：[A->C, A->D, B->D, B->E]\n * ┌─────────────────────────────────┐\n * │               ┌─────>C──┐       │\n * │        ┌──>A──┤         │       │\n * │        │      └──┐      │       │\n * │ Start──┤         ├──>D──┼──>End │\n * │        │      ┌──┘      │       │\n * │        └──>B──┤         │       │\n * │               └─────>E──┘       │\n * └─────────────────────────────────┘\n * 但可通过json graph来表达：\n *   [\n *     {\"source\": \"1:1:A\", \"target\": \"1:1:C\"},\n *     {\"source\": \"1:1:A\", \"target\": \"1:1:D\"},\n *     {\"source\": \"1:1:B\", \"target\": \"1:1:D\"},\n *     {\"source\": \"1:1:B\", \"target\": \"1:1:E\"}\n *   ]\n * </pre>\n *\n * @author Ponfee\n */\npublic class DAGExpressionParser {\n\n    /**\n     * <pre>\n     * 1、(?i) 开启大小写忽略模式，但是只适用于ASCII字符\n     * 2、(?u) 开启utf-8编码模式\n     * 3、(?s) 单行模式，“.”匹配任意字符(包括空白字符)\n     * 4、(?m) 开启多行匹配模式，“.”不匹配空白字符\n     * 5、(?d) 单行模式，“.”不匹配空白字符\n     *\n     * Match json array: [{...}]\n     * 有两种方式：\n     *   1、(?s)^\\s*\\[\\s*\\{.+}\\s*]\\s*$\n     *   2、(?m)^\\s*\\[\\s*\\{(\\s*\\S+\\s*)+}\\s*]\\s*$\n     * </pre>\n     */\n    private static final Pattern JSON_GRAPH_PATTERN = Pattern.compile(\"(?s)^\\\\s*\\\\[\\\\s*\\\\{.+}\\\\s*]\\\\s*$\");\n\n    private static final String SEP_STAGE = \"->\";\n    private static final String SEP_UNION = Str.COMMA;\n    private static final List<String> SEP_SYMBOLS = ImmutableList.of(SEP_STAGE, SEP_UNION);\n    private static final List<String> ALL_SYMBOLS = ImmutableList.of(SEP_STAGE, SEP_UNION, Str.CLOSE, Str.OPEN);\n    private static final char[] SINGLE_SYMBOLS = {Char.OPEN, Char.CLOSE, Char.COMMA};\n\n    private final String expression;\n\n    /**\n     * Identity cache of expression wrapped '()'\n     */\n    private final Map<String, String> wrappedCache = new IdentityHashMap<>();\n\n    /**\n     * Identity cache of partition key\n     */\n    private final Map<PartitionIdentityKey, String> partitionCache = new HashMap<>();\n\n    /**\n     * Map<name, List<Tuple2<name, ordinal>>>\n     */\n    private final Map<String, List<Tuple2<String, Integer>>> incrementer = new HashMap<>();\n\n    public DAGExpressionParser(String text) {\n        Assert.hasText(text, \"Expression cannot be blank.\");\n        this.expression = text.trim();\n    }\n\n    public Graph<DAGNode> parse() {\n        ImmutableGraph.Builder<DAGNode> graphBuilder = GraphBuilder.directed().allowsSelfLoops(false).immutable();\n\n        List<GraphEdge> edges;\n        if (JSON_GRAPH_PATTERN.matcher(expression).matches() && (edges = GraphEdge.fromJson(expression)) != null) {\n            parseJsonGraph(graphBuilder, edges);\n        } else {\n            parsePlainExpr(graphBuilder);\n        }\n\n        ImmutableGraph<DAGNode> graph = graphBuilder.build();\n        Assert.state(graph.nodes().size() > 2, () -> \"Expression not any name: \" + expression);\n        Assert.state(graph.successors(DAGNode.START).stream().noneMatch(DAGNode::isEnd), () -> \"Expression name cannot direct end: \" + expression);\n        Assert.state(graph.predecessors(DAGNode.END).stream().noneMatch(DAGNode::isStart), () -> \"Expression name cannot direct start: \" + expression);\n        Assert.state(!Graphs.hasCycle(graph), () -> \"Expression name section has cycle: \" + expression);\n        return graph;\n    }\n\n    /**\n     * [{\"source\":\"1:1:A\",\"target\":\"1:1:C\"},{\"source\":\"1:1:A\",\"target\":\"1:1:D\"},{\"source\":\"1:1:B\",\"target\":\"1:1:D\"},{\"source\":\"1:1:B\",\"target\":\"1:1:E\"}]\n     *\n     * @param graphBuilder the graph builder\n     * @param edges        the edges\n     */\n    private void parseJsonGraph(ImmutableGraph.Builder<DAGNode> graphBuilder, List<GraphEdge> edges) {\n        Assert.notEmpty(edges, \"Graph edges cannot be empty.\");\n        Set<DAGNode> allNode = new HashSet<>();\n        Set<DAGNode> nonHead = new HashSet<>();\n        Set<DAGNode> nonTail = new HashSet<>();\n        for (GraphEdge graphEdge : edges) {\n            DAGEdge dagEdge = graphEdge.toDAGEdge();\n            DAGNode source = dagEdge.getSource();\n            DAGNode target = dagEdge.getTarget();\n            Assert.isTrue(!source.isStartOrEnd(), () -> \"Graph edge cannot be start or end: \" + source);\n            Assert.isTrue(!target.isStartOrEnd(), () -> \"Graph edge cannot be start or end: \" + target);\n\n            graphBuilder.putEdge(source, target);\n            allNode.add(source);\n            allNode.add(target);\n            nonHead.add(target);\n            nonTail.add(source);\n        }\n        allNode.stream().filter(Predicates.not(nonHead::contains)).forEach(e -> graphBuilder.putEdge(DAGNode.START, e));\n        allNode.stream().filter(Predicates.not(nonTail::contains)).forEach(e -> graphBuilder.putEdge(e, DAGNode.END));\n    }\n\n    /**\n     * A->((B->C->D),(A->F))->(G,H,X)->J; A->Y\n     *\n     * @param graphBuilder the graph builder\n     */\n    private void parsePlainExpr(ImmutableGraph.Builder<DAGNode> graphBuilder) {\n        Assert.isTrue(checkParenthesis(expression), () -> \"Invalid expression parenthesis: \" + expression);\n        List<String> sections = Stream.of(expression.split(\";\")).filter(StringUtils::isNotBlank).map(String::trim).collect(Collectors.toList());\n        Assert.notEmpty(sections, () -> \"Invalid split with ';' expression: \" + expression);\n\n        for (int i = 0, n = sections.size(); i < n; i++) {\n            String section = sections.get(i);\n            Assert.isTrue(checkParenthesis(section), () -> \"Invalid expression parenthesis: \" + section);\n            String expr = completeParenthesis(section);\n            buildGraph(i + 1, Collections.singletonList(expr), graphBuilder, DAGNode.START, DAGNode.END);\n        }\n    }\n\n    private void buildGraph(int section, List<String> expressions,\n                            ImmutableGraph.Builder<DAGNode> graphBuilder, DAGNode prev, DAGNode next) {\n        // 划分第一个stage\n        Tuple2<List<String>, List<String>> tuple = divideFirstStage(expressions);\n        if (tuple == null) {\n            return;\n        }\n\n        List<String> first = tuple.a, remains = tuple.b;\n        for (int i = 0, n = first.size() - 1; i <= n; i++) {\n            List<String> list = resolve(first.get(i));\n            Assert.notEmpty(list, () -> \"Invalid expression: \" + String.join(\"\", expressions));\n            if (list.size() == 1) {\n                String name = list.get(0);\n                DAGNode node = DAGNode.of(section, increment(name), name);\n                graphBuilder.putEdge(prev, node);\n                if (remains == null) {\n                    graphBuilder.putEdge(node, next);\n                } else {\n                    buildGraph(section, remains, graphBuilder, node, next);\n                }\n            } else {\n                buildGraph(section, concat(list, remains), graphBuilder, prev, next);\n            }\n        }\n    }\n\n    private List<String> resolve(String text) {\n        String expr = text.trim();\n        if (ALL_SYMBOLS.stream().noneMatch(expr::contains)) {\n            // unnecessary resolve\n            return Collections.singletonList(expr);\n        }\n\n        if (!expr.startsWith(Str.OPEN) || !expr.endsWith(Str.CLOSE)) {\n            return resolve(wrappedCache.computeIfAbsent(expr, DAGExpressionParser::wrap));\n        }\n\n        List<Tuple2<Integer, Integer>> groups = group(expr);\n\n        // 取被\"()\"包裹的最外层表达式\n        List<Tuple2<Integer, Integer>> outermost = groups.stream().filter(e -> e.b == 1).collect(Collectors.toList());\n        if (outermost.size() == 2) {\n            // 首尾括号，如：(A,B -> C,D)\n            Assert.isTrue(outermost.get(0).a == 0 && outermost.get(1).a == expr.length() - 1, () -> \"Invalid expression: \" + text);\n        } else if (outermost.size() > 2) {\n            // 多组括号情况，需要在外层再包层括号，如：\n            //   1）“(A,B) -> (C,D)”    =>    “((A,B) -> (C,D))”\n            //   2）“(B->C->D),(A->F)”  =>    “((B->C->D),(A->F))”\n            return resolve(wrappedCache.computeIfAbsent(expr, DAGExpressionParser::wrap));\n        } else {\n            throw new IllegalArgumentException(\"Invalid expression: \" + expr);\n        }\n\n        TreeNode<TreeNodeId, Object> root = buildTree(groups);\n        List<Integer> list = new ArrayList<>(root.getChildrenCount() * 2 + 2);\n        list.add(root.getNid().open);\n        root.forEachChild(child -> {\n            list.add(child.getNid().open);\n            list.add(child.getNid().close);\n        });\n        list.add(root.getNid().close);\n        return partition(expr, list);\n    }\n\n    private List<String> partition(String expr, List<Integer> groups) {\n        List<String> result = new ArrayList<>(groups.size());\n        for (int i = 0, n = groups.size() - 1; i < n; i++) {\n            PartitionIdentityKey key = new PartitionIdentityKey(expr, groups.get(i) + 1, groups.get(i + 1));\n            // if twice open “((”，then str is empty content\n            String str = partitionCache.computeIfAbsent(key, PartitionIdentityKey::partition);\n            if (StringUtils.isNotBlank(str)) {\n                result.add(str);\n            }\n        }\n        return result;\n    }\n\n    private int increment(String name) {\n        List<Tuple2<String, Integer>> list = incrementer.computeIfAbsent(name, k -> new LinkedList<>());\n        Tuple2<String, Integer> tuple = list.stream().filter(e -> name == e.a).findAny().orElse(null);\n        if (tuple == null) {\n            // increment name ordinal\n            list.add(tuple = Tuple2.of(name, list.size() + 1));\n        }\n        return tuple.b;\n    }\n\n    // ------------------------------------------------------------------------------------static methods\n\n    private static Tuple2<List<String>, List<String>> divideFirstStage(List<String> list) {\n        if (CollectionUtils.isEmpty(list)) {\n            return null;\n        }\n\n        Assert.isTrue(!SEP_SYMBOLS.contains(Collects.getFirst(list)), () -> \"Invalid expression: \" +  String.join(\"\", list));\n        Assert.isTrue(!SEP_SYMBOLS.contains(Collects.getLast(list)), () -> \"Invalid expression: \" + String.join(\"\", list));\n\n        if (list.size() == 1) {\n            return Tuple2.of(list, null);\n        }\n\n        List<String> head = new ArrayList<>();\n        for (int i = 0, n = list.size() - 1; i <= n; ) {\n            head.add(list.get(i++));\n            if (i > n) {\n                return Tuple2.of(head, null);\n            }\n            switch (list.get(i++)) {\n                case SEP_STAGE:\n                    return Tuple2.of(head, list.subList(i, list.size()));\n                case SEP_UNION:\n                    // skip “,”\n                    break;\n                default:\n                    throw new IllegalArgumentException(\"Invalid expression: \" + String.join(\"\", list));\n            }\n        }\n        return Tuple2.of(head, null);\n    }\n\n    static TreeNode<TreeNodeId, Object> buildTree(List<Tuple2<Integer, Integer>> groups) {\n        List<BaseNode<TreeNodeId, Object>> nodes = new ArrayList<>(groups.size() + 1);\n        buildTree(groups, TreeNodeId.ROOT_ID, 1, 0, nodes);\n\n        // create a dummy root node\n        TreeNode<TreeNodeId, Object> dummyRoot = TreeNode.builder(TreeNodeId.ROOT_ID).build();\n\n        // mount nodes\n        dummyRoot.mount(nodes);\n\n        // gets the actual root\n        Assert.state(dummyRoot.getChildrenCount() == 1, \"Build tree root node must be has a single child.\");\n        return dummyRoot.getChildren().get(0);\n    }\n\n    private static void buildTree(List<Tuple2<Integer, Integer>> groups,\n                                  TreeNodeId pid, int level, int start,\n                                  List<BaseNode<TreeNodeId, Object>> nodes) {\n        int open = -1;\n        for (int i = start, n = groups.size(); i < n; i++) {\n            if (groups.get(i).b < level) {\n                return;\n            }\n            if (groups.get(i).b == level) {\n                if (open == -1) {\n                    open = i;\n                } else {\n                    // find \"()\" position\n                    TreeNodeId nid = TreeNodeId.of(groups.get(open).a, groups.get(i).a);\n                    nodes.add(new PlainNode<>(nid, pid, null));\n                    buildTree(groups, nid, level + 1, open + 1, nodes);\n                    open = -1;\n                }\n            }\n        }\n    }\n\n    private static List<String> concat(List<String> left, List<String> right) {\n        if (CollectionUtils.isEmpty(right)) {\n            return left;\n        }\n\n        List<String> result = new ArrayList<>(left.size() + 1 + right.size());\n        result.addAll(left);\n        result.add(SEP_STAGE);\n        result.addAll(right);\n        return result;\n    }\n\n    /**\n     * Checks the text is wrapped '()' is valid.\n     *\n     * @param text the text string\n     * @return {@code true} if valid\n     */\n    static boolean checkParenthesis(String text) {\n        int openCount = 0;\n        for (int i = 0, n = text.length(); i < n; i++) {\n            char c = text.charAt(i);\n            if (c == Char.OPEN) {\n                openCount++;\n            } else if (c == Char.CLOSE) {\n                openCount--;\n            }\n            if (openCount < 0) {\n                // For example \"())(\"\n                return false;\n            }\n        }\n        return openCount == 0;\n    }\n\n    /**\n     * Complete the text wrapped with '()'\n     *\n     * @param text the text string\n     * @return wrapped text string\n     */\n    static String completeParenthesis(String text) {\n        List<String> list = new ArrayList<>();\n        int mark = 0, position = 0;\n        for (int len = text.length() - 1; position <= len; ) {\n            char ch = text.charAt(position++);\n            Assert.isTrue(ch != '>', () -> \"Invalid '\" + ch + \"': \" + text);\n            if (ArrayUtils.contains(SINGLE_SYMBOLS, ch)) {\n                list.add(text.substring(mark, position - 1).trim());\n                list.add(Character.toString(ch));\n                mark = position;\n            } else if (ch == '-') {\n                // position not equals len, because expression cannot end with '>'\n                Assert.isTrue(position <= len && text.charAt(position) == '>', () -> \"Invalid '->' :\" + text);\n                list.add(text.substring(mark, position - 1).trim());\n                list.add(SEP_STAGE);\n                mark = ++position;\n            }\n        }\n        if (position > mark) {\n            list.add(text.substring(mark, position).trim());\n        }\n\n        StringBuilder builder = new StringBuilder();\n        for (int i = 0, n = list.size() - 1; i <= n; i++) {\n            String item = list.get(i);\n            if (StringUtils.isBlank(item)) {\n                // skip empty string\n            } else if (ALL_SYMBOLS.contains(item)) {\n                builder.append(item);\n            } else if (Str.OPEN.equals(Collects.get(list, i - 1)) && Str.CLOSE.equals(Collects.get(list, i + 1))) {\n                builder.append(item);\n            } else {\n                builder.append(Str.OPEN).append(item).append(Str.CLOSE);\n            }\n        }\n        return builder.toString();\n    }\n\n    /**\n     * Group expression by \"()\"\n     *\n     * @param expr the expression\n     * @return groups of \"()\"\n     */\n    static List<Tuple2<Integer, Integer>> group(String expr) {\n        Assert.isTrue(checkParenthesis(expr), () -> \"Invalid expression parenthesis: \" + expr);\n        int depth = 0;\n        // Tuple2<position, level>\n        List<Tuple2<Integer, Integer>> list = new ArrayList<>();\n        for (int i = 0, n = expr.length(); i < n; i++) {\n            if (expr.charAt(i) == Char.OPEN) {\n                ++depth;\n                if (depth <= 2) {\n                    // 只取两层\n                    list.add(Tuple2.of(i, depth));\n                }\n            } else if (expr.charAt(i) == Char.CLOSE) {\n                if (depth <= 2) {\n                    list.add(Tuple2.of(i, depth));\n                }\n                --depth;\n            }\n        }\n        Assert.isTrue((list.size() & 0x01) == 0, () -> \"Expression not pair with '()': \" + expr);\n        return list;\n    }\n\n    private static String wrap(String text) {\n        return Str.OPEN + text + Str.CLOSE;\n    }\n\n    @Getter\n    @Setter\n    private static final class GraphEdge implements Serializable {\n        private static final long serialVersionUID = 7881441757444058390L;\n        private static final TypeReference<List<GraphEdge>> LIST_TYPE = new TypeReference<List<GraphEdge>>() {};\n\n        private String source;\n        private String target;\n\n        private DAGEdge toDAGEdge() {\n            return DAGEdge.of(source, target);\n        }\n\n        private static List<GraphEdge> fromJson(String text) {\n            try {\n                return Jsons.fromJson(text, LIST_TYPE);\n            } catch (Exception e) {\n                return null;\n            }\n        }\n    }\n\n    static final class TreeNodeId implements Serializable, Comparable<TreeNodeId> {\n        private static final long serialVersionUID = -468548698179536500L;\n        private static final TreeNodeId ROOT_ID = new TreeNodeId(-1, -1);\n\n        /**\n         * position of \"(\"\n         */\n        private final int open;\n\n        /**\n         * position of \")\"\n         */\n        private final int close;\n\n        private TreeNodeId(int open, int close) {\n            this.open = open;\n            this.close = close;\n        }\n\n        private static TreeNodeId of(int open, int close) {\n            Assert.isTrue(open > -1, \"Tree node id open must be greater than -1: \" + open);\n            Assert.isTrue(close > 0, \"Tree node id close must be greater than 0: \" + close);\n            return new TreeNodeId(open, close);\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            if (!(obj instanceof TreeNodeId)) {\n                return false;\n            }\n            TreeNodeId other = (TreeNodeId) obj;\n            return this.open == other.open\n                && this.close == other.close;\n        }\n\n        @Override\n        public int hashCode() {\n            return open + close;\n        }\n\n        @Override\n        public int compareTo(TreeNodeId other) {\n            int n = this.open - other.open;\n            return n != 0 ? n : (this.close - other.close);\n        }\n\n        @Override\n        public String toString() {\n            return \"(\" + open + \",\" + close + \")\";\n        }\n    }\n\n    private static final class PartitionIdentityKey {\n        private final String expr;\n        private final int open;\n        private final int close;\n\n        private PartitionIdentityKey(String expr, int open, int close) {\n            Assert.hasText(expr, () -> \"Partition expression cannot be blank: \" + expr);\n            Assert.isTrue(open > -1, () -> \"Partition key open must be greater than -1: \" + open);\n            Assert.isTrue(close > 0, () -> \"Partition key close must be greater than 0: \" + close);\n            this.expr = expr;\n            this.open = open;\n            this.close = close;\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            if (!(obj instanceof PartitionIdentityKey)) {\n                return false;\n            }\n            PartitionIdentityKey other = (PartitionIdentityKey) obj;\n            // 比较对象地址\n            return this.expr == other.expr\n                && this.open == other.open\n                && this.close == other.close;\n        }\n\n        @Override\n        public int hashCode() {\n            return System.identityHashCode(expr) + open + close;\n        }\n\n        private String partition() {\n            return expr.substring(open, close).trim();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/dag/DAGNode.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.dag;\n\nimport org.springframework.util.Assert;\n\nimport java.beans.Transient;\nimport java.io.Serializable;\nimport java.util.Objects;\n\nimport static cn.ponfee.commons.base.Symbol.Str.COLON;\n\n/**\n * DAG Node\n *\n * @author Ponfee\n */\npublic final class DAGNode implements Serializable {\n    private static final long serialVersionUID = 7413110685194391605L;\n\n    public static final DAGNode START = new DAGNode(0, 0, \"Start\");\n    public static final DAGNode END = new DAGNode(0, 0, \"End\");\n\n    /**\n     * <pre>\n     *  任务链的编号，用来区分不同的任务链\n     *  如[A -> B; C -> D]，表达式用“;”分隔成两个不同的任务链\n     *  section=1： A -> B\n     *  section=2： C -> D\n     * </pre>\n     */\n    private final int section;\n\n    /**\n     * 名称相同时通过顺序来区分，如[A -> B -> A]，两个A是不同的\n     * <p>实际结果为 [1:1:A -> 1:1:B -> 1:2:A]\n     */\n    private final int ordinal;\n\n    /**\n     * 名称\n     */\n    private final String name;\n\n    private DAGNode(int section, int ordinal, String name) {\n        this.section = section;\n        this.ordinal = ordinal;\n        this.name = name;\n    }\n\n    public static DAGNode of(int section, int ordinal, String name) {\n        Assert.isTrue(section > 0, () -> \"Graph node section must be greater than 0: \" + section);\n        Assert.isTrue(ordinal > 0, () -> \"Graph node ordinal must be greater than 0: \" + ordinal);\n        Assert.hasText(name, () -> \"Graph node name cannot be blank: \" + name);\n        return new DAGNode(section, ordinal, name);\n    }\n\n    public int getSection() {\n        return section;\n    }\n\n    public int getOrdinal() {\n        return ordinal;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    @Transient\n    public boolean isStart() {\n        return this.equals(START);\n    }\n\n    @Transient\n    public boolean isEnd() {\n        return this.equals(END);\n    }\n\n    @Transient\n    public boolean isStartOrEnd() {\n        return isStart() || isEnd();\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(section, ordinal, name);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this) {\n            return true;\n        }\n        if (!(obj instanceof DAGNode)) {\n            return false;\n        }\n        DAGNode other = (DAGNode) obj;\n        return this.section == other.section\n            && this.ordinal == other.ordinal\n            && this.name.equals(other.name);\n    }\n\n    public boolean equals(int section, int ordinal, String name) {\n        return this.section == section\n            && this.ordinal == ordinal\n            && this.name.equals(name);\n    }\n\n    @Override\n    public String toString() {\n        return section + COLON + ordinal + COLON + name;\n    }\n\n    public static DAGNode fromString(String str) {\n        int pos = -1;\n        int section = Integer.parseInt(str.substring(pos += 1, pos = str.indexOf(COLON, pos)));\n        int ordinal = Integer.parseInt(str.substring(pos += 1, pos = str.indexOf(COLON, pos)));\n        String name = str.substring(pos + 1);\n        if (START.equals(section, ordinal, name)) {\n            return START;\n        }\n        if (END.equals(section, ordinal, name)) {\n            return END;\n        }\n        return DAGNode.of(section, ordinal, name);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/DataSourceFactory.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data;\n\nimport cn.ponfee.commons.reflect.BeanMaps;\n\nimport javax.sql.DataSource;\nimport java.util.Map;\nimport java.util.Properties;\n\n/**\n * DataSource factory interface\n *\n * @author Ponfee\n */\n@SuppressWarnings({\"unchecked\", \"rawtypes\"})\npublic interface DataSourceFactory<T extends DataSource> {\n\n    default void configure(T dataSource, Properties commonConfig) {\n        BeanMaps.PROPS.copyFromMap((Map) commonConfig, dataSource);\n    }\n\n    default T create(String dataSourceClassName, Properties basicConfig, Properties commonConfig) {\n        T dataSource;\n        try {\n            dataSource = (T) Class.forName(dataSourceClassName).newInstance();\n        } catch (ReflectiveOperationException e) {\n            throw new RuntimeException(e);\n        }\n        BeanMaps.PROPS.copyFromMap((Map) basicConfig, dataSource);\n\n        configure(dataSource, commonConfig);\n\n        return dataSource;\n    }\n\n    DataSourceFactory COMMON_FACTORY = new DataSourceFactory() {};\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/DataSourceNaming.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data;\n\nimport java.lang.annotation.*;\n\n/**\n * 多数据源注解，指定要切换的数据源名称，支持Spring SPEL，上下文为方法参数（数组）\n * \n * @author Ponfee\n */\n@Documented\n@Inherited\n@Target({ ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface DataSourceNaming {\n\n    /**\n     * Specifiy string of the dataSource name\n     * Spring EL expression\n     */\n    String value() default \"\";\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/DruidDataSourceFactory.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data;\n\nimport com.alibaba.druid.pool.DruidDataSource;\nimport com.alibaba.druid.wall.WallConfig;\nimport com.alibaba.druid.wall.WallFilter;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.sql.SQLException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.stream.Collectors;\n\n/**\n * Druid DataSource factory\n *\n * @author Ponfee\n */\npublic class DruidDataSourceFactory implements DataSourceFactory<DruidDataSource> {\n\n    @Override\n    public void configure(DruidDataSource ds, Properties props) {\n        configureFilters(ds, props.getProperty(\"filters\"));\n    }\n\n    private void configureFilters(DruidDataSource ds, String filters) {\n        if (StringUtils.isBlank(filters)) {\n            return;\n        }\n\n        filters = filters.trim();\n        boolean force = filters.startsWith(\"!\");\n        if (force) {\n            filters = filters.substring(1);\n        }\n\n        List<String> list = Arrays.stream(filters.split(\",\"))\n                                  .map(String::trim)\n                                  .collect(Collectors.toList());\n        boolean hasWall = list.remove(\"wall\");\n        if (hasWall) {\n            WallConfig wallConfig = new WallConfig();\n            wallConfig.setCommentAllow(true);\n            wallConfig.setMultiStatementAllow(true);\n            WallFilter wallFilter = new WallFilter();\n            wallFilter.setConfig(wallConfig);\n            ds.setProxyFilters(Collections.singletonList(wallFilter));\n        }\n\n        filters = (force ? \"!\" : \"\") + String.join(\",\", list);\n        try {\n            ds.setFilters(filters);\n        } catch (SQLException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/MultipleDataSourceAdvisor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data;\n\nimport cn.ponfee.commons.data.lookup.MultipleDataSourceContext;\nimport cn.ponfee.commons.exception.Throwables.ThrowingCallable;\nimport org.aopalliance.intercept.MethodInterceptor;\nimport org.aopalliance.intercept.MethodInvocation;\nimport org.apache.commons.lang3.StringUtils;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.expression.ExpressionParser;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.StandardEvaluationContext;\n\nimport java.lang.reflect.Method;\nimport java.util.concurrent.Callable;\n\n/**\n * 多数据源切换，用于Spring XML配置文件形式的切面拦截多数据源切换处理\n *\n * @author Ponfee\n */\npublic class MultipleDataSourceAdvisor implements MethodInterceptor {\n\n    private static final ExpressionParser PARSER = new SpelExpressionParser();\n\n    /**\n     * 基于Spring XML &lt;aop:aspect /&gt;的配置方式\n     *\n     * <pre>\n     * {@code\n     *   <!-- aop:advisor必须在aop:aspect前面 -->\n     *   <bean id=\"dsChangeAdvice\" class=\"cn.ponfee.commons.data.MultipleDataSourceAdvisor\" />\n     *   <aop:config proxy-target-class=\"true\">\n     *     <aop:pointcut id=\"dbTxMgrPointcut\" expression=\"execution(public * cn.ponfee..*.service.impl..*..*(..))\" />\n     *     <aop:advisor advice-ref=\"txManageAdvice\" pointcut-ref=\"dbTxMgrPointcut\" order=\"2147483647\" />\n     *     <aop:aspect ref=\"dsChangeAdvice\" order=\"2147483646\">\n     *       <aop:around method=\"doAround\" pointcut-ref=\"dbTxMgrPointcut\" />\n     *     </aop:aspect>\n     *   </aop:config>\n     * }\n     *\n     *  1、transaction-xml：<aop:config proxy-target-class=\"true\">\n     *    MultipleDataSourceAdvisor.doAround：数据源切换正常，事务正常√\n     *\n     *  2、transaction-xml：<aop:config proxy-target-class=\"false\">\n     *    MultipleDataSourceAdvisor.doAround：数据源切换无效，事务正常×\n     *\n     *  3、<tx:annotation-driven proxy-target-class=\"true\">，<aop:config proxy-target-class=\"true\">\n     *    MultipleDataSourceAdvisor.doAround：数据源切换正常，事务正常√\n     *\n     *  4、<tx:annotation-driven proxy-target-class=\"true\">，<aop:config proxy-target-class=\"false\">\n     *    MultipleDataSourceAdvisor.doAround：数据源切换正常，事务正常√\n     *\n     *  5、<tx:annotation-driven proxy-target-class=\"false\">，<aop:config proxy-target-class=\"true\">\n     *    MultipleDataSourceAdvisor.doAround：数据源切换正常，事务正常√\n     *\n     *  6、<tx:annotation-driven proxy-target-class=\"false\">，<aop:config proxy-target-class=\"false\">\n     *    MultipleDataSourceAdvisor.doAround：数据源切换无效，事务正常×\n     * </pre>\n     *\n     * @param pjp the ProceedingJoinPoint\n     * @return target method return result\n     * @throws Throwable if occur error\n     */\n    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {\n        /*MethodSignature ms = (MethodSignature) pjp.getSignature();\n        // DatabaseQueryServiceImpl.query4page(PageRequestParams)\n        System.out.println(ms.getMethod());\n\n        // DatabaseQueryServiceImpl$$EnhancerBySpringCGLIB$$ca6c0b12.query4page(PageRequestParams)\n        System.out.println(pjp.getTarget().getClass().getMethod(ms.getName(), ms.getParameterTypes()));*/\n\n        return around(\n            ((MethodSignature) pjp.getSignature()).getMethod(),\n            pjp.getArgs(),\n            ThrowingCallable.toChecked(pjp::proceed)\n        );\n    }\n\n    /**\n     * 基于Spring XML &lt;aop:advisor /&gt;的配置方式\n     * <pre>\n     * {@code\n     *   <bean id=\"dsChangeAdvice\" class=\"cn.ponfee.commons.data.MultipleDataSourceAdvisor\" />\n     *   <aop:config proxy-target-class=\"true\">\n     *     <aop:pointcut id=\"dbTxMgrPointcut\" expression=\"execution(public * cn.ponfee..*.service.impl..*..*(..))\" />\n     *     <aop:advisor advice-ref=\"dsChangeAdvice\" pointcut-ref=\"dbTxMgrPointcut\" order=\"2147483646\" />\n     *     <aop:advisor advice-ref=\"txManageAdvice\" pointcut-ref=\"dbTxMgrPointcut\" order=\"2147483647\" />\n     *   </aop:config>\n     * }\n\n     *  1、transaction-xml：<aop:config proxy-target-class=\"true\">\n     *    MultipleDataSourceAdvisor.invoke  ：数据源切换正常，事务无效×\n     *\n     *  2、transaction-xml：<aop:config proxy-target-class=\"false\">\n     *    MultipleDataSourceAdvisor.invoke  ：数据源切换无效，事务无效×\n     *\n     *  3、<tx:annotation-driven proxy-target-class=\"true\">，<aop:config proxy-target-class=\"true\">\n     *    MultipleDataSourceAdvisor.invoke  ：数据源切换正常，事务无效×\n     *\n     *  4、<tx:annotation-driven proxy-target-class=\"true\">，<aop:config proxy-target-class=\"false\">\n     *    MultipleDataSourceAdvisor.invoke  ：数据源切换正常，事务无效×\n     *\n     *  5、<tx:annotation-driven proxy-target-class=\"false\">，<aop:config proxy-target-class=\"true\">\n     *    MultipleDataSourceAdvisor.invoke  ：数据源切换正常，事务无效×\n     *\n     *  6、<tx:annotation-driven proxy-target-class=\"false\">，<aop:config proxy-target-class=\"false\">\n     *    MultipleDataSourceAdvisor.invoke  ：数据源切换无效，事务无效×\n     * </pre>\n     *\n     * @param invocation the MethodInvocation\n     * @return target method return result\n     * @throws Throwable if occur error\n     *\n     * @deprecated 此方式事务失效（去掉此数据源切面，则事务正常）\n     */\n    @Override @Deprecated\n    public Object invoke(MethodInvocation invocation) throws Throwable {\n        Method method = invocation.getMethod();\n        Object[] args = invocation.getArguments();\n        return around(method, args, () -> method.invoke(invocation.getThis(), args));\n    }\n\n    private static Object around(Method method, Object[] args, Callable<Object> call) throws Throwable {\n        return around(method, args, method.getAnnotation(DataSourceNaming.class), call);\n    }\n\n    static Object around(Method method, Object[] args, DataSourceNaming dsn,\n                         Callable<Object> call) throws Throwable {\n        String name = null;\n        if (dsn != null && StringUtils.isNotBlank(dsn.value())) {\n            name = PARSER.parseExpression(dsn.value())\n                         .getValue(new StandardEvaluationContext(args), String.class);\n        }\n\n        boolean changed = false;\n        try {\n            if (StringUtils.isNotBlank(name)) {\n                MultipleDataSourceContext.set(name);\n                changed = true;\n            }\n            return call.call();\n        } finally {\n            if (changed) {\n                MultipleDataSourceContext.clear();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/MultipleDataSourceAspect.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data;\n\nimport cn.ponfee.commons.exception.Throwables.ThrowingCallable;\nimport cn.ponfee.commons.math.Maths;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.core.Ordered;\n\n/**\n * <pre>\n *  1、开启spring切面特性：<aop:aspectj-autoproxy />\n *\n *  2、编写子类：\n *  {@code\n *\n *    `@Component `@Aspect\n *    public class MultipleDataSourceChanger extends MultipleDataSourceAspect {\n *      `@Around(\n *        value = \"execution(public * cn.ponfee..*.service.impl..*Impl..*(..)) && `@annotation(dsn)\",\n *        argNames = \"pjp,dsn\"\n *      )\n *      `@Override\n *      public Object doAround(ProceedingJoinPoint pjp, DataSourceNaming dsn) throws Throwable {\n *        return super.doAround(pjp, dsn);\n *      }\n *    }\n *\n *  }\n *\n *  1、transaction-xml：<aop:config proxy-target-class=\"true\">\n *    MultipleDataSourceAspect.doAround ：数据源切换无效，事务正常×\n *\n *  2、transaction-xml：<aop:config proxy-target-class=\"false\">\n *    MultipleDataSourceAspect.doAround ：数据源切换正常，事务正常√\n *\n *  3、<tx:annotation-driven proxy-target-class=\"true\">\n *    MultipleDataSourceAspect.doAround ：数据源切换正常，事务正常√\n *\n *  4、<tx:annotation-driven proxy-target-class=\"false\">\n *    MultipleDataSourceAspect.doAround ：数据源切换正常，事务正常√\n * </pre>\n *\n * @author Ponfee\n */\npublic abstract class MultipleDataSourceAspect implements Ordered {\n\n    private static final int ORDER = Maths.minus(Ordered.LOWEST_PRECEDENCE, 1);\n\n    public Object doAround(ProceedingJoinPoint pjp, DataSourceNaming dsn) throws Throwable {\n        return MultipleDataSourceAdvisor.around(\n            ((MethodSignature) pjp.getSignature()).getMethod(),\n            pjp.getArgs(), dsn,\n            ThrowingCallable.toChecked(pjp::proceed)\n        );\n    }\n\n    @Override\n    public int getOrder() {\n        return ORDER;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/NamedDataSource.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data;\n\nimport cn.ponfee.commons.util.Asserts;\nimport cn.ponfee.commons.util.PropertiesUtils;\nimport cn.ponfee.commons.util.Strings;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.sql.DataSource;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Named Data Source\n * \n * @author Ponfee\n */\npublic class NamedDataSource {\n\n    private static final Pattern PATTERN_DBURL_KEY = Pattern.compile(\"^(\\\\w+)\\\\.url$\");\n\n    private final String name;\n    private final DataSource dataSource;\n\n    public NamedDataSource(String name, DataSource dataSource) {\n        this.name = name;\n        this.dataSource = dataSource;\n    }\n\n    public static NamedDataSource of(String name, DataSource dataSource) {\n        return new NamedDataSource(name, dataSource);\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public DataSource getDataSource() {\n        return dataSource;\n    }\n\n    /**\n     *\n     * @param prefix\n     * @param props\n     * @return\n     */\n    public static NamedDataSource[] build(String prefix, Properties props) {\n        prefix = StringUtils.isBlank(prefix) ? \"\" : prefix.trim() + \".\";\n        props = PropertiesUtils.filterProperties(props, prefix);\n\n        Set<String> names = new LinkedHashSet<>();\n        props.forEach((k, v) -> {\n            Matcher matcher = PATTERN_DBURL_KEY.matcher(k.toString());\n            if (matcher.matches()) {\n                String name = matcher.group(1);\n                if (!names.add(name)) {\n                    throw new IllegalStateException(\"Duplicated datasource name '\" + name + \"'.\");\n                }\n            }\n        });\n        Asserts.isTrue(!names.isEmpty(), \"Not configured datasource 'name' option.\");\n\n        Pattern pattern = Pattern.compile(\"^(?!(\" + Strings.join(names, \"|\") + \")\\\\.).*\");\n        Properties commonConfig = new Properties(); // commons properties\n        props.entrySet()\n             .stream()\n             .filter(e -> pattern.matcher(e.toString()).matches())\n             .forEach(e -> commonConfig.put(e.getKey(), e.getValue()));\n        String defaultDsName = (String) commonConfig.remove(\"default\"); // default datasource name\n        String defaultType = (String) commonConfig.remove(\"type\"); // default datasource type\n\n        List<NamedDataSource> dataSources = new LinkedList<>();\n        for (String name : names) {\n            // specify \"{name}.type\" or default \"type\" for datasource type\n            String dsType = Strings.ifEmpty((String) props.remove(name + \".type\"), defaultType);\n            Properties basicConfig = PropertiesUtils.filterProperties(props, name + \".\");\n            DataSource dataSource = DataSourceFactory.COMMON_FACTORY.create(dsType, basicConfig, commonConfig);\n\n            NamedDataSource namedDs = NamedDataSource.of(name, dataSource);\n            if (name.equals(defaultDsName)) {\n                dataSources.add(0, namedDs); // default ds at index 0\n            } else {\n                dataSources.add(namedDs);\n            }\n        }\n\n        return dataSources.toArray(new NamedDataSource[0]);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/lookup/DataSourceLookup.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data.lookup;\n\nimport javax.sql.DataSource;\n\n/**\n * Looking up DataSources by name.\n * \n * @author Ponfee\n */\npublic interface DataSourceLookup {\n\n    DataSource lookupDataSource(String name);\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/lookup/MultipleCachedDataSource.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data.lookup;\n\nimport cn.ponfee.commons.base.Initializable;\nimport cn.ponfee.commons.base.Releasable;\nimport cn.ponfee.commons.data.NamedDataSource;\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.springframework.jdbc.datasource.AbstractDataSource;\n\nimport javax.annotation.Nonnull;\nimport javax.sql.DataSource;\nimport java.io.Closeable;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.time.Duration;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\n/**\n * 可缓存的多数据源类型：可动态增加数据源、可动态移除数据源、数据源自动超时失效\n * \n * @author Ponfee\n * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource\n */\npublic class MultipleCachedDataSource extends AbstractDataSource\n    implements DataSourceLookup, Initializable, Closeable {\n\n    private final Map<String, DataSource> naturalDataSources; // original/native\n    private final DataSource defaultDataSource;\n\n    private final Cache<String, DataSource> adoptedDataSources; // foreign/stranger\n\n    public MultipleCachedDataSource(int expireSeconds, NamedDataSource dataSource) {\n        this(expireSeconds, dataSource.getName(), dataSource.getDataSource());\n    }\n\n    public MultipleCachedDataSource(int expireSeconds, NamedDataSource... dataSources) {\n        this(\n            expireSeconds,\n            dataSources[0].getName(), \n            dataSources[0].getDataSource(), \n            ArrayUtils.subarray(dataSources, 1, dataSources.length)\n        );\n    }\n\n    public MultipleCachedDataSource(int expireSeconds, String defaultName,\n                                    DataSource defaultDataSource,\n                                    NamedDataSource... othersDataSource) {\n        // set the default data source\n        this.defaultDataSource = defaultDataSource;\n\n        this.naturalDataSources = ImmutableMap.copyOf(\n            MultipleDataSourceContext.process(defaultName, defaultDataSource, othersDataSource)\n        );\n\n        this.adoptedDataSources = CacheBuilder.newBuilder()\n            .expireAfterAccess(Duration.ofSeconds(expireSeconds))\n            .maximumSize(8192)\n            .removalListener(notification -> {\n                try {\n                    Releasable.release(notification.getValue());\n                } catch (Exception ignored) {\n                    ignored.printStackTrace();\n                }\n            })\n            .build();\n    }\n\n    // -----------------------------------------------------------------add/remove\n    public synchronized boolean addIfAbsent(String dataSourceName, \n                                            Supplier<DataSource> supplier) {\n        if (existsDatasourceName(dataSourceName)) {\n            return false;\n        }\n\n        this.add(dataSourceName, supplier.get());\n        return true;\n    }\n\n    public synchronized boolean addIfAbsent(String dataSourceName, DataSource datasource) {\n        if (existsDatasourceName(dataSourceName)) {\n            return false;\n        }\n\n        this.add(dataSourceName, datasource);\n        return true;\n    }\n\n    public synchronized void add(NamedDataSource ds) {\n        this.add(ds.getName(), ds.getDataSource());\n    }\n\n    public synchronized void add(@Nonnull String dataSourceName, \n                                 @Nonnull DataSource datasource) {\n        if (existsDatasourceName(dataSourceName)) { // check the datasource name not exists\n            throw new IllegalArgumentException(\"Duplicated datasource name: \" + dataSourceName);\n        }\n\n        this.adoptedDataSources.put(dataSourceName, datasource);\n        MultipleDataSourceContext.add(dataSourceName);\n    }\n\n    public synchronized void remove(String dataSourceName) {\n        if (this.naturalDataSources.containsKey(dataSourceName)) {\n            throw new UnsupportedOperationException(\"Local datasource cannot remove: \" + dataSourceName);\n        }\n\n        this.adoptedDataSources.invalidate(dataSourceName);\n        MultipleDataSourceContext.remove(dataSourceName);\n    }\n\n    // -----------------------------------------------------------------override methods\n    @Override\n    public Connection getConnection() throws SQLException {\n        return determineTargetDataSource().getConnection();\n    }\n\n    @Override\n    public Connection getConnection(String username, String password) throws SQLException {\n        return determineTargetDataSource().getConnection(username, password);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <T> T unwrap(Class<T> iface) throws SQLException {\n        if (iface.isInstance(this)) {\n            return (T) this;\n        }\n        return determineTargetDataSource().unwrap(iface);\n    }\n\n    @Override\n    public boolean isWrapperFor(Class<?> iface) throws SQLException {\n        return iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface);\n    }\n\n    @Override\n    public DataSource lookupDataSource(String name) {\n        DataSource dataSource = this.naturalDataSources.get(name);\n        return dataSource != null \n             ? dataSource \n             : this.adoptedDataSources.getIfPresent(name);\n    }\n\n    @Override\n    public void init() {\n        naturalDataSources.forEach((name, ds) -> Initializable.init(ds));\n    }\n\n    @Override\n    public void close() {\n        naturalDataSources.forEach((name, ds) -> {\n            try {\n                Releasable.release(ds);\n            } catch (Exception ignored) {\n                ignored.printStackTrace();\n            }\n        });\n        adoptedDataSources.asMap().forEach((name, ds) -> {\n            try {\n                Releasable.release(ds);\n            } catch (Exception ignored) {\n                ignored.printStackTrace();\n            }\n        });\n    }\n\n    // -----------------------------------------------------------------private methods\n    /**\n     * Retrieve the current target DataSource. Determines the\n     */\n    private DataSource determineTargetDataSource() {\n        String lookupKey = MultipleDataSourceContext.get();\n        DataSource dataSource = (lookupKey == null) \n                              ? this.defaultDataSource \n                              : lookupDataSource(lookupKey);\n        if (dataSource == null) {\n            throw new IllegalStateException(\"Cannot found DataSource by name [\" + lookupKey + \"]\");\n        }\n        return dataSource;\n    }\n\n    private boolean existsDatasourceName(String name) {\n        return this.naturalDataSources.containsKey(name)\n            || this.adoptedDataSources.getIfPresent(name) != null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/lookup/MultipleDataSourceContext.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data.lookup;\n\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.data.NamedDataSource;\nimport org.apache.commons.collections4.CollectionUtils;\n\nimport javax.annotation.Nonnull;\nimport javax.sql.DataSource;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * Multiple DataSource Context\n * \n * @author Ponfee\n */\npublic final class MultipleDataSourceContext {\n\n    private static final List<String> KEYS = new LinkedList<>(); // new CopyOnWriteArrayList<>()\n    private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();\n\n    public static void set(String datasourceName) {\n        CONTEXT.set(datasourceName);\n    }\n\n    public static String get() {\n        return CONTEXT.get();\n    }\n\n    public static void clear() {\n        CONTEXT.remove();\n    }\n\n    /**\n     * Provides gets the list of data source name to external\n     * \n     * @return a list of data source name string\n     */\n    public static List<String> listDataSourceNames() {\n        return KEYS.isEmpty()\n             ? Collections.emptyList() \n             : Collections.unmodifiableList(KEYS);\n    }\n\n    // -----------------------------------------------------datasource keys\n    synchronized static void add(@Nonnull String key) {\n        if (KEYS.contains(key)) {\n            throw new IllegalArgumentException(\"Duplicate key: \" + key);\n        }\n        KEYS.add(key);\n    }\n\n    synchronized static void addAll(List<String> keys) {\n        if (CollectionUtils.isNotEmpty(keys)) {\n            keys.forEach(MultipleDataSourceContext::add);\n        }\n    }\n\n    synchronized static void remove(String key) {\n        KEYS.remove(key);\n    }\n\n    static Map<String, DataSource> process(String defaultName, DataSource defaultDataSource,\n                                           NamedDataSource... othersDataSource) {\n        if (othersDataSource == null) {\n            othersDataSource = new NamedDataSource[0];\n        }\n        List<String> names = Arrays.stream(othersDataSource)\n                                   .map(NamedDataSource::getName)\n                                   .collect(Collectors.toList());\n        names.add(0, defaultName); // default data source at the first\n\n        // checks whether duplicate datasource name\n        List<String> duplicates = Collects.duplicate(names);\n        if (CollectionUtils.isNotEmpty(duplicates)) {\n            throw new IllegalArgumentException(\"Duplicated data source name: \" + duplicates);\n        }\n\n        addAll(names); // add data source keys \n\n        Map<String, DataSource> dataSources = new LinkedHashMap<>(othersDataSource.length + 1, 1);\n        dataSources.put(defaultName, defaultDataSource);\n        Arrays.stream(othersDataSource)\n              .forEach(ns -> dataSources.put(ns.getName(), ns.getDataSource()));\n        return dataSources;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/lookup/MultipleFixedDataSource.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data.lookup;\n\nimport cn.ponfee.commons.base.Initializable;\nimport cn.ponfee.commons.base.Releasable;\nimport cn.ponfee.commons.data.NamedDataSource;\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;\n\nimport javax.sql.DataSource;\nimport java.io.Closeable;\nimport java.util.Map;\n\n/**\n * 固定的多数据源类型：一旦创建则不可再变\n *\n * Multiple DataSource: <p>\n *   {@linkplain #setTargetDataSources(Map)}：设置数据源集 <p>\n *   {@linkplain #setDefaultTargetDataSource(Object)}：设置默认的数据源 <p>\n *   {@linkplain #determineCurrentLookupKey()}：获取当前数据源， 当返回为空或无对应数据源时会使用defaultTargetDataSource <p>\n * \n * @author Ponfee\n * @see MultipleScalableDataSource\n * @see MultipleCachedDataSource\n */\npublic class MultipleFixedDataSource extends AbstractRoutingDataSource\n    implements DataSourceLookup, Initializable, Closeable {\n\n    private final Map<String, DataSource> dataSources;\n\n    public MultipleFixedDataSource(NamedDataSource dataSource) {\n        this(dataSource.getName(), dataSource.getDataSource());\n    }\n\n    public MultipleFixedDataSource(NamedDataSource... dataSources) {\n        this(\n            dataSources[0].getName(), \n            dataSources[0].getDataSource(), \n            ArrayUtils.subarray(dataSources, 1, dataSources.length)\n        );\n    }\n\n    @SuppressWarnings({ \"unchecked\", \"rawtypes\" })\n    public MultipleFixedDataSource(String defaultName, DataSource defaultDataSource, \n                                   NamedDataSource... othersDataSource) {\n        Map<String, DataSource> dataSources = MultipleDataSourceContext.process(\n            defaultName, defaultDataSource, othersDataSource\n        );\n\n        // if determineCurrentLookupKey not get, then use this default\n        super.setDefaultTargetDataSource(defaultDataSource);\n\n        // set all the data sources\n        super.setTargetDataSources((Map) dataSources);\n\n        this.dataSources = ImmutableMap.copyOf(dataSources);\n    }\n\n    @Override\n    protected Object determineCurrentLookupKey() {\n        return MultipleDataSourceContext.get();\n    }\n\n    @Override\n    public DataSource lookupDataSource(String name) {\n        return this.dataSources.get(name);\n    }\n\n    @Override\n    public void init() {\n        dataSources.forEach((name, ds) -> Initializable.init(ds));\n    }\n\n    @Override\n    public void close() {\n        dataSources.forEach((name, ds) -> {\n            try {\n                Releasable.release(ds);\n            } catch (Exception ignored) {\n                ignored.printStackTrace();\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/data/lookup/MultipleScalableDataSource.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.data.lookup;\n\nimport cn.ponfee.commons.base.Initializable;\nimport cn.ponfee.commons.base.Releasable;\nimport cn.ponfee.commons.data.NamedDataSource;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.springframework.jdbc.datasource.AbstractDataSource;\n\nimport javax.annotation.Nonnull;\nimport javax.sql.DataSource;\nimport java.io.Closeable;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Objects;\n\n/**\n * 可扩展的多数据源类型：可动态增加/移除数据源\n * \n * @author Ponfee\n * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource\n */\npublic class MultipleScalableDataSource extends AbstractDataSource\n    implements DataSourceLookup, Initializable, Closeable {\n\n    private final Map<String, DataSource> dataSources = new HashMap<>();\n    private final DataSource defaultDataSource;\n\n    public MultipleScalableDataSource(NamedDataSource dataSource) {\n        this(dataSource.getName(), dataSource.getDataSource());\n    }\n\n    public MultipleScalableDataSource(NamedDataSource... dataSources) {\n        this(\n            dataSources[0].getName(), \n            dataSources[0].getDataSource(), \n            ArrayUtils.subarray(dataSources, 1, dataSources.length)\n        );\n    }\n\n    public MultipleScalableDataSource(String defaultName, DataSource defaultDataSource,\n                                      NamedDataSource... othersDataSource) {\n        Map<String, DataSource> dataSources = MultipleDataSourceContext.process(\n            defaultName, defaultDataSource, othersDataSource\n        );\n\n        // set the default data source\n        this.defaultDataSource = defaultDataSource;\n\n        // set all the data sources\n        this.dataSources.putAll(dataSources);\n    }\n\n    public synchronized void add(NamedDataSource ds) {\n        this.add(ds.getName(), ds.getDataSource());\n    }\n\n    public synchronized void add(@Nonnull String dataSourceName, @Nonnull DataSource datasource) {\n        if (dataSources.containsKey(dataSourceName)) {\n            throw new IllegalArgumentException(\"Duplicated name: \" + dataSourceName);\n        }\n        dataSources.put(dataSourceName, datasource);\n        MultipleDataSourceContext.add(dataSourceName);\n    }\n\n    public synchronized void remove(String dataSourceName) {\n        dataSources.remove(dataSourceName);\n        MultipleDataSourceContext.remove(dataSourceName);\n    }\n\n    public synchronized void remove(@Nonnull DataSource dataSource) {\n        Objects.requireNonNull(dataSource);\n\n        for (Iterator<Entry<String, DataSource>> iter = dataSources.entrySet().iterator(); iter.hasNext();) {\n            Entry<String, DataSource> entry = iter.next();\n            if (dataSource.equals(entry.getValue())) {\n                iter.remove();\n                MultipleDataSourceContext.remove(entry.getKey());\n            }\n        }\n    }\n\n    @Override\n    public Connection getConnection() throws SQLException {\n        return determineTargetDataSource().getConnection();\n    }\n\n    @Override\n    public Connection getConnection(String username, String password) throws SQLException {\n        return determineTargetDataSource().getConnection(username, password);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <T> T unwrap(Class<T> iface) throws SQLException {\n        if (iface.isInstance(this)) {\n            return (T) this;\n        }\n        return determineTargetDataSource().unwrap(iface);\n    }\n\n    @Override\n    public boolean isWrapperFor(Class<?> iface) throws SQLException {\n        return iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface);\n    }\n\n    @Override\n    public DataSource lookupDataSource(String name) {\n        return this.dataSources.get(name);\n    }\n\n    @Override\n    public void init() {\n        dataSources.forEach((name, ds) -> Initializable.init(ds));\n    }\n\n    @Override\n    public void close() {\n        dataSources.forEach((name, ds) -> {\n            try {\n                Releasable.release(ds);\n            } catch (Exception ignored) {\n                ignored.printStackTrace();\n            }\n        });\n    }\n\n    /**\n     * Retrieve the current target DataSource. Determines the\n     */\n    private DataSource determineTargetDataSource() {\n        String lookupKey = MultipleDataSourceContext.get();\n        DataSource dataSource = (lookupKey == null) \n                              ? this.defaultDataSource \n                              : this.dataSources.get(lookupKey);\n        if (dataSource == null) {\n            throw new IllegalStateException(\"Cannot found DataSource by name [\" + lookupKey + \"]\");\n        }\n        return dataSource;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/date/CustomLocalDateTimeDeserializer.java",
    "content": "package cn.ponfee.commons.date;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.core.JsonToken;\nimport com.fasterxml.jackson.core.JsonTokenId;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.datatype.jsr310.deser.JSR310DateTimeDeserializerBase;\n\nimport java.io.IOException;\nimport java.time.DateTimeException;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\n\n/**\n * Deserializer for Java 8 temporal {@link LocalDateTime}s.\n *\n * @author Nick Williams\n * @author Ponfee\n * @see com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer\n * @since 2.2.0\n */\npublic class CustomLocalDateTimeDeserializer extends JSR310DateTimeDeserializerBase<LocalDateTime> {\n    private static final long serialVersionUID = 1L;\n\n    public static final CustomLocalDateTimeDeserializer INSTANCE = new CustomLocalDateTimeDeserializer();\n\n    private final LocalDateTimeFormat wrappedFormatter;\n\n    protected CustomLocalDateTimeDeserializer() {\n        this(Dates.DATETIME_PATTERN);\n    }\n\n    public CustomLocalDateTimeDeserializer(String pattern) {\n        this(DateTimeFormatter.ofPattern(pattern));\n    }\n\n    public CustomLocalDateTimeDeserializer(DateTimeFormatter formatter) {\n        super(LocalDateTime.class, formatter);\n        this.wrappedFormatter = new LocalDateTimeFormat(formatter);\n    }\n\n    @Override\n    protected JSR310DateTimeDeserializerBase<LocalDateTime> withShape(JsonFormat.Shape shape) {\n        return this;\n    }\n\n    @Override\n    protected CustomLocalDateTimeDeserializer withDateFormat(DateTimeFormatter formatter) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    protected CustomLocalDateTimeDeserializer withLeniency(Boolean leniency) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException {\n        if (parser.hasTokenId(JsonTokenId.ID_STRING)) {\n            return _fromString(parser, context, parser.getText());\n        }\n        // 30-Sep-2020, tatu: New! \"Scalar from Object\" (mostly for XML)\n        if (parser.isExpectedStartObjectToken()) {\n            return _fromString(parser, context, context.extractScalarFromObject(parser, this, handledType()));\n        }\n        if (parser.isExpectedStartArrayToken()) {\n            JsonToken t = parser.nextToken();\n            if (t == JsonToken.END_ARRAY) {\n                return null;\n            }\n            if (   (t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT)\n                && context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)\n            ) {\n                final LocalDateTime parsed = deserialize(parser, context);\n                if (parser.nextToken() != JsonToken.END_ARRAY) {\n                    handleMissingEndArrayForSingle(parser, context);\n                }\n                return parsed;\n            }\n            if (t == JsonToken.VALUE_NUMBER_INT) {\n                LocalDateTime result;\n\n                int year = parser.getIntValue();\n                int month = parser.nextIntValue(-1);\n                int day = parser.nextIntValue(-1);\n                int hour = parser.nextIntValue(-1);\n                int minute = parser.nextIntValue(-1);\n\n                t = parser.nextToken();\n                if (t == JsonToken.END_ARRAY) {\n                    result = LocalDateTime.of(year, month, day, hour, minute);\n                } else {\n                    int second = parser.getIntValue();\n                    t = parser.nextToken();\n                    if (t == JsonToken.END_ARRAY) {\n                        result = LocalDateTime.of(year, month, day, hour, minute, second);\n                    } else {\n                        int partialSecond = parser.getIntValue();\n                        if (partialSecond < 1_000 && !context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) {\n                            // value is milliseconds, convert it to nanoseconds\n                            partialSecond *= 1_000_000;\n                        }\n                        if (parser.nextToken() != JsonToken.END_ARRAY) {\n                            throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY,\n                                \"Expected array to end\");\n                        }\n                        result = LocalDateTime.of(year, month, day, hour, minute, second, partialSecond);\n                    }\n                }\n                return result;\n            }\n            context.reportInputMismatch(handledType(), \"Unexpected token (%s) within Array, expected VALUE_NUMBER_INT\", t);\n        }\n        if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {\n            return (LocalDateTime) parser.getEmbeddedObject();\n        }\n        if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {\n            _throwNoNumericTimestampNeedTimeZone(parser, context);\n        }\n        return _handleUnexpectedToken(context, parser, \"Expected array or string.\");\n    }\n\n    protected LocalDateTime _fromString(JsonParser p, DeserializationContext ctxt, String string0) throws IOException {\n        String string = string0.trim();\n        if (string.length() == 0) {\n            return _fromEmptyString(p, ctxt, string);\n        }\n        try {\n            return wrappedFormatter.parse(string);\n        } catch (DateTimeException e) {\n            return _handleDateTimeException(ctxt, e, string);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/date/DatePeriods.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.date;\n\nimport org.springframework.util.Assert;\n\nimport java.time.LocalDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Date;\n\n/**\n * <pre>\n * 1990-04-15 00:00:00这天调整了夏令时，即在4月15号0点的时候将表调快了一小时，导致这一天少了一小时。\n *\n * 1986年4月，中国中央有关部门发出“在全国范围内实行夏时制的通知”，具体作法是：每年从四月中旬第一个星\n * 期日的凌晨2时整（北京时间），将时钟拨快一小时，即将表针由2时拨至3时，夏令时开始；到九月中旬第一个\n * 星期日的凌晨2时整（北京夏令时），再将时钟拨回一小时，即将表针由2时拨至1时，夏令时结束。从1986年到\n * 1991年的六个年度，除1986年因是实行夏时制的第一年，从5月4日开始到9月14日结束外，其它年份均按规定的\n * 时段施行。在夏令时开始和结束前几天，新闻媒体均刊登有关部门的通告。1992年起，夏令时暂停实行。\n *\n * 时间周期，计算周期性的时间段\n * </pre>\n *\n * @author Ponfee\n */\npublic enum DatePeriods {\n\n    /**\n     * 每毫秒的\n     */\n    PER_MILLIS(ChronoUnit.MILLIS, 1),\n\n    /**\n     * 每秒钟的\n     */\n    PER_SECOND(ChronoUnit.SECONDS, 1),\n\n    /**\n     * 每分钟的\n     */\n    MINUTELY(ChronoUnit.MINUTES, 1),\n\n    /**\n     * 每小时的\n     */\n    HOURLY(ChronoUnit.HOURS, 1),\n\n    /**\n     * 每天\n     */\n    DAILY(ChronoUnit.DAYS, 1),\n\n    /**\n     * 每周\n     */\n    WEEKLY(ChronoUnit.WEEKS, 1),\n\n    /**\n     * 每月\n     */\n    MONTHLY(ChronoUnit.MONTHS, 1),\n\n    /**\n     * 每季度\n     */\n    QUARTERLY(ChronoUnit.MONTHS, 3),\n\n    /**\n     * 每半年\n     */\n    SEMIANNUAL(ChronoUnit.MONTHS, 6),\n\n    /**\n     * 每年度\n     */\n    ANNUAL(ChronoUnit.YEARS, 1),\n\n    /**\n     * 每十年的\n     */\n    DECADES(ChronoUnit.DECADES, 1),\n\n    /**\n     * 每百年的（世纪）\n     */\n    CENTURIES(ChronoUnit.CENTURIES, 1),\n\n    ;\n\n    private final ChronoUnit unit;\n    private final int multiple;\n\n    DatePeriods(ChronoUnit unit, int multiple) {\n        this.unit = unit;\n        this.multiple = multiple;\n    }\n\n    /**\n     * Compute the next segment based original and reference target\n     *\n     * @param original the period original\n     * @param target   the target of next reference\n     * @param step     the period step\n     * @param next     the next of target segment\n     * @return {@code Segment(begin, end)}\n     */\n    public final Segment next(LocalDateTime original, LocalDateTime target, int step, int next) {\n        Assert.isTrue(step > 0, \"Step must be positive number.\");\n        Assert.isTrue(!original.isAfter(target), \"Original date cannot be after target date.\");\n\n        step *= multiple;\n        long start = (unit.between(original, target) / step + next) * step;\n        LocalDateTime begin = original.plus(start, unit);\n        return new Segment(begin, begin.plus(step, unit));\n    }\n\n    public final Segment next(LocalDateTime target, int step, int next) {\n        return next(target, target, step, next);\n    }\n\n    public final Segment next(LocalDateTime target, int next) {\n        return next(target, target, 1, next);\n    }\n\n    public final Segment next(Date original, Date target, int step, int next) {\n        return next(Dates.toLocalDateTime(original), Dates.toLocalDateTime(target), step, next);\n    }\n\n    public final Segment next(Date target, int step, int next) {\n        LocalDateTime original = Dates.toLocalDateTime(target);\n        return next(original, original, step, next);\n    }\n\n    public final Segment next(Date target, int next) {\n        LocalDateTime original = Dates.toLocalDateTime(target);\n        return next(original, original, 1, next);\n    }\n\n    public static final class Segment {\n        private final Date begin;\n        private final Date end;\n\n        private Segment(LocalDateTime begin, LocalDateTime end) {\n            this.begin = Dates.toDate(begin);\n            this.end = Dates.toDate(end.minus(1, ChronoUnit.MILLIS));\n        }\n\n        public Date begin() {\n            return begin;\n        }\n\n        public Date end() {\n            return end;\n        }\n\n        @Override\n        public String toString() {\n            return JavaUtilDateFormat.PATTERN_51.format(begin) + \" ~ \" + JavaUtilDateFormat.PATTERN_51.format(end);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/date/Dates.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.date;\n\nimport cn.ponfee.commons.base.Symbol.Char;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\n\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.time.*;\nimport java.time.temporal.Temporal;\nimport java.time.temporal.TemporalAdjusters;\nimport java.time.temporal.WeekFields;\nimport java.util.Date;\nimport java.util.concurrent.ThreadLocalRandom;\n\n/**\n * Date utility\n * <p><a href=\"https://segmentfault.com/a/1190000039047353\">Java处理GMT/UTC日期时间</a>\n *\n * <pre>\n * 时区：\n *   LocalDateTime：无时区\n *   Date(UTC0)：表示自格林威治时间(GMT)1970年1月1日0点经过指定的毫秒数后的时间点\n *   Instant(UTC0)：同Date\n *   ZonedDateTime：自带时区\n *\n * ZoneId子类：ZoneRegion、ZoneOffset\n *   ZoneId.of(\"Etc/GMT-8\")                   -->    Etc/GMT-8\n *   ZoneId.of(\"GMT+8\")                       -->    GMT+08:00\n *   ZoneId.of(\"UTC+8\")                       -->    UTC+08:00\n *   ZoneId.of(\"Asia/Shanghai\")               -->    Asia/Shanghai\n *   ZoneId.systemDefault()                   -->    Asia/Shanghai\n *\n * TimeZone子类（不支持UTC）：ZoneInfo\n *   TimeZone.getTimeZone(\"Etc/GMT-8\")        -->    Etc/GMT-8\n *   TimeZone.getTimeZone(\"GMT+8\")            -->    GMT+08:00\n *   TimeZone.getTimeZone(\"Asia/Shanghai\")    -->    Asia/Shanghai\n *   TimeZone.getTimeZone(ZoneId.of(\"GMT+8\")) -->    GMT+08:00\n *   TimeZone.getDefault()                    -->    Asia/Shanghai\n * </pre>\n *\n * @author Ponfee\n */\npublic class Dates {\n\n    /**\n     * Date pattern\n     */\n    public static final String DATE_PATTERN = \"yyyy-MM-dd\";\n\n    /**\n     * Datetime pattern\n     */\n    public static final String DATETIME_PATTERN = \"yyyy-MM-dd HH:mm:ss\";\n\n    /**\n     * Full datetime pattern\n     */\n    public static final String DATEFULL_PATTERN = \"yyyy-MM-dd HH:mm:ss.SSS\";\n\n    /**\n     * Zero time millis: -62170185600000L\n     */\n    public static final String ZERO_DATETIME = \"0000-00-00 00:00:00\";\n\n    /**\n     * 简单的日期格式校验(yyyy-MM-dd HH:mm:ss)\n     *\n     * @param dateStr 输入日期\n     * @return 有效返回true, 反之false\n     */\n    public static boolean isValidDate(String dateStr) {\n        if (StringUtils.isEmpty(dateStr)) {\n            return false;\n        }\n\n        try {\n            JavaUtilDateFormat.DEFAULT.parse(dateStr);\n            return true;\n        } catch (Exception ignored) {\n            return false;\n        }\n    }\n\n    /**\n     * 简单的日期格式校验\n     *\n     * @param dateStr 输入日期，如(yyyy-MM-dd)\n     * @param pattern 日期格式\n     * @return 有效返回true, 反之false\n     */\n    public static boolean isValidDate(String dateStr, String pattern) {\n        if (StringUtils.isEmpty(dateStr)) {\n            return false;\n        }\n\n        try {\n            new SimpleDateFormat(pattern).parse(dateStr);\n            return true;\n        } catch (Exception ignored) {\n            return false;\n        }\n    }\n\n    /**\n     * Check the date is whether zero date\n     *\n     * @param date the date\n     * @return is zero if {@code true}\n     */\n    public static boolean isZeroDate(Date date) {\n        return date != null && date.getTime() == -62170185600000L;\n    }\n\n    /**\n     * 获取当前日期对象\n     *\n     * @return 当前日期对象\n     */\n    public static Date now() {\n        return new Date();\n    }\n\n    /**\n     * 获取当前日期字符串\n     *\n     * @param pattern 日期格式\n     * @return 当前日期字符串\n     */\n    public static String now(String pattern) {\n        return format(now(), pattern);\n    }\n\n    /**\n     * 转换日期字符串为日期对象(默认格式: yyyy-MM-dd HH:mm:ss)\n     *\n     * @param dateStr 日期字符串\n     * @return 日期对象\n     */\n    public static Date toDate(String dateStr) {\n        try {\n            return JavaUtilDateFormat.DEFAULT.parse(dateStr);\n        } catch (ParseException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    /**\n     * 转换日期即字符串为Date对象\n     *\n     * @param dateStr 日期字符串\n     * @param pattern 日期格式\n     * @return 日期对象\n     */\n    public static Date toDate(String dateStr, String pattern) {\n        try {\n            return new SimpleDateFormat(pattern).parse(dateStr);\n        } catch (ParseException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    /**\n     * java（毫秒）时间戳\n     *\n     * @param timeMillis 毫秒时间戳\n     * @return 日期\n     */\n    public static Date ofTimeMillis(long timeMillis) {\n        return new Date(timeMillis);\n    }\n\n    public static Date ofTimeMillis(Long timeMillis) {\n        return timeMillis == null ? null : new Date(timeMillis);\n    }\n\n    public static long currentUnixTimestamp() {\n        return System.currentTimeMillis() / 1000;\n    }\n\n    /**\n     * unix时间戳\n     *\n     * @param unixTimestamp unix时间戳\n     * @return 日期\n     */\n    public static Date ofUnixTimestamp(long unixTimestamp) {\n        return new Date(unixTimestamp * 1000);\n    }\n\n    public static Date ofUnixTimestamp(Long unixTimestamp) {\n        return unixTimestamp == null ? null : new Date(unixTimestamp * 1000);\n    }\n\n    /**\n     * 格式化日期对象\n     *\n     * @param date    日期对象\n     * @param pattern 日期格式\n     * @return 当前日期字符串\n     */\n    public static String format(Date date, String pattern) {\n        if (date == null) {\n            return null;\n        }\n        return new SimpleDateFormat(pattern).format(date);\n    }\n\n    /**\n     * 格式化日期对象，格式为yyyy-MM-dd HH:mm:ss\n     *\n     * @param date 日期对象\n     * @return 日期字符串\n     */\n    public static String format(Date date) {\n        return format(date, DATETIME_PATTERN);\n    }\n\n    /**\n     * 格式化日期对象\n     *\n     * @param timeMillis 毫秒\n     * @param pattern    格式\n     * @return 日期字符串\n     */\n    public static String format(long timeMillis, String pattern) {\n        return format(new Date(timeMillis), pattern);\n    }\n\n    // ----------------------------------------------------------------plus\n\n    /**\n     * 增加毫秒数\n     *\n     * @param date   时间\n     * @param millis 毫秒数\n     * @return 时间\n     */\n    public static Date plusMillis(Date date, long millis) {\n        return new Date(date.getTime() + millis);\n    }\n\n    /**\n     * 增加秒数\n     *\n     * @param date    时间\n     * @param seconds 秒数\n     * @return 时间\n     */\n    public static Date plusSeconds(Date date, long seconds) {\n        return plusMillis(date, seconds * 1000);\n    }\n\n    /**\n     * 增加分钟\n     *\n     * @param date    时间\n     * @param minutes 分钟数\n     * @return 时间\n     */\n    public static Date plusMinutes(Date date, long minutes) {\n        return plusMillis(date, minutes * 60 * 1000);\n    }\n\n    /**\n     * 增加小时\n     *\n     * @param date  时间\n     * @param hours 小时数\n     * @return 时间\n     */\n    public static Date plusHours(Date date, long hours) {\n        return plusMillis(date, hours * 60 * 60 * 1000);\n    }\n\n    /**\n     * 增加天数\n     *\n     * @param date 时间\n     * @param days 天数\n     * @return 时间\n     */\n    public static Date plusDays(Date date, long days) {\n        return plusMillis(date, days * 24 * 60 * 60 * 1000);\n    }\n\n    /**\n     * 增加周\n     *\n     * @param date  时间\n     * @param weeks 周数\n     * @return 时间\n     */\n    public static Date plusWeeks(Date date, long weeks) {\n        return plusMillis(date, weeks * 7 * 24 * 60 * 60 * 1000);\n    }\n\n    /**\n     * 增加月份\n     *\n     * @param date   时间\n     * @param months 月数\n     * @return 时间\n     */\n    public static Date plusMonths(Date date, long months) {\n        return toDate(toLocalDateTime(date).plusMonths(months));\n    }\n\n    /**\n     * 增加年\n     *\n     * @param date  时间\n     * @param years 年数\n     * @return 时间\n     */\n    public static Date plusYears(Date date, long years) {\n        return toDate(toLocalDateTime(date).plusYears(years));\n    }\n\n    // ----------------------------------------------------------------minus\n\n    /**\n     * 减少毫秒数\n     *\n     * @param date   时间\n     * @param millis 毫秒数\n     * @return 时间\n     */\n    public static Date minusMillis(Date date, long millis) {\n        return plusMillis(date, -millis);\n    }\n\n    /**\n     * 减少秒数\n     *\n     * @param date    时间\n     * @param seconds 秒数\n     * @return 时间\n     */\n    public static Date minusSeconds(Date date, long seconds) {\n        return plusSeconds(date, -seconds);\n    }\n\n    /**\n     * 减少分钟\n     *\n     * @param date    时间\n     * @param minutes 分钟数\n     * @return 时间\n     */\n    public static Date minusMinutes(Date date, long minutes) {\n        return plusMinutes(date, -minutes);\n    }\n\n    /**\n     * 减少小时\n     *\n     * @param date  时间\n     * @param hours 小时数\n     * @return 时间\n     */\n    public static Date minusHours(Date date, long hours) {\n        return plusHours(date, -hours);\n    }\n\n    /**\n     * 减少天数\n     *\n     * @param date 时间\n     * @param days 天数\n     * @return 时间\n     */\n    public static Date minusDays(Date date, long days) {\n        return plusDays(date, -days);\n    }\n\n    /**\n     * 减少周\n     *\n     * @param date  时间\n     * @param weeks 周数\n     * @return 时间\n     */\n    public static Date minusWeeks(Date date, long weeks) {\n        return plusWeeks(date, -weeks);\n    }\n\n    /**\n     * 减少月份\n     *\n     * @param date   时间\n     * @param months 月数\n     * @return 时间\n     */\n    public static Date minusMonths(Date date, long months) {\n        return toDate(toLocalDateTime(date).minusMonths(months));\n    }\n\n    /**\n     * 减少年\n     *\n     * @param date  时间\n     * @param years 年数\n     * @return 时间\n     */\n    public static Date minusYears(Date date, long years) {\n        return toDate(toLocalDateTime(date).minusYears(years));\n    }\n\n    // ----------------------------------------------------------------start/end\n\n    /**\n     * 获取指定日期所在天的开始时间：yyyy-MM-dd 00:00:00\n     *\n     * @param date 时间\n     * @return 时间\n     */\n    public static Date startOfDay(Date date) {\n        return toDate(startOfDay0(date));\n    }\n\n    /**\n     * 获取指定日期所在天的结束时间：yyyy-MM-dd 23:59:59\n     *\n     * @param date 时间\n     * @return 时间\n     */\n    public static Date endOfDay(Date date) {\n        return toDate(endOfDay0(date));\n    }\n\n    /**\n     * 获取指定日期所在周的开始时间：yyyy-MM-周一 00:00:00\n     *\n     * @param date 日期\n     * @return 当前周第一天\n     */\n    public static Date startOfWeek(Date date) {\n        return toDate(startOfDay0(date).with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)));\n    }\n\n    /**\n     * 获取指定日期所在周的结束时间：yyyy-MM-周日 23:59:59\n     *\n     * @param date 日期\n     * @return 当前周最后一天\n     */\n    public static Date endOfWeek(Date date) {\n        return toDate(endOfDay0(date).with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)));\n    }\n\n    /**\n     * 获取指定日期所在月的开始时间：yyyy-MM-01 00:00:00\n     *\n     * @param date 日期\n     * @return 当前月的第一天\n     */\n    public static Date startOfMonth(Date date) {\n        return toDate(startOfDay0(date).with(TemporalAdjusters.firstDayOfMonth()));\n    }\n\n    /**\n     * 获取指定日期所在月的结束时间：yyyy-MM-月未 23:59:59\n     *\n     * @param date 日期\n     * @return 当前月的最后一天\n     */\n    public static Date endOfMonth(Date date) {\n        return toDate(endOfDay0(date).with(TemporalAdjusters.lastDayOfMonth()));\n    }\n\n    /**\n     * 获取指定日期所在月的开始时间：yyyy-01-01 00:00:00\n     *\n     * @param date 日期\n     * @return 当前年的第一天\n     */\n    public static Date startOfYear(Date date) {\n        return toDate(startOfDay0(date).with(TemporalAdjusters.firstDayOfYear()));\n    }\n\n    /**\n     * 获取指定日期所在月的结束时间：yyyy-12-31 23:59:59\n     *\n     * @param date 日期\n     * @return 当前年的最后一天\n     */\n    public static Date endOfYear(Date date) {\n        return toDate(endOfDay0(date).with(TemporalAdjusters.lastDayOfYear()));\n    }\n\n    // ----------------------------------------------------------------day of\n\n    /**\n     * 获取指定时间所在周的周n，1<=day<=7\n     *\n     * @param date      相对日期\n     * @param dayOfWeek 1-星期一；2-星期二；...\n     * @return 本周周几的日期对象\n     */\n    public static Date withDayOfWeek(Date date, int dayOfWeek) {\n        LocalDateTime dateTime = toLocalDateTime(date).with(WeekFields.of(DayOfWeek.MONDAY, 1).dayOfWeek(), dayOfWeek);\n        //LocalDateTime dateTime = toLocalDateTime(date).with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).with(TemporalAdjusters.nextOrSame(DayOfWeek.of(dayOfWeek)));\n        return toDate(dateTime);\n    }\n\n    /**\n     * 获取指定时间所在月的n号，1<=day<=31\n     *\n     * @param date       the date\n     * @param dayOfMonth the day of month\n     * @return date\n     */\n    public static Date withDayOfMonth(Date date, int dayOfMonth) {\n        return toDate(toLocalDateTime(date).withDayOfMonth(dayOfMonth));\n    }\n\n    /**\n     * 获取指定时间所在年的n天，1<=day<=366\n     *\n     * @param date      the date\n     * @param dayOfYear the day of year\n     * @return date\n     */\n    public static Date withDayOfYear(Date date, int dayOfYear) {\n        return toDate(toLocalDateTime(date).withDayOfYear(dayOfYear));\n    }\n\n    // ----------------------------------------------------------------day of\n\n    public static int dayOfYear(Date date) {\n        return toLocalDateTime(date).getDayOfYear();\n    }\n\n    public static int dayOfMonth(Date date) {\n        return toLocalDateTime(date).getDayOfMonth();\n    }\n\n    public static int dayOfWeek(Date date) {\n        return toLocalDateTime(date).getDayOfWeek().getValue();\n    }\n\n    public static int hourOfDay(Date date) {\n        return toLocalDateTime(date).getHour();\n    }\n\n    // ----------------------------------------------------------------others\n\n    /**\n     * 计算两个日期的时间差（单位：秒）\n     *\n     * @param start 开始时间\n     * @param end   结束时间\n     * @return 时间间隔\n     */\n    public static long clockDiff(Date start, Date end) {\n        return (end.getTime() - start.getTime()) / 1000;\n    }\n\n    /**\n     * Returns a days between the two date(end-start)\n     *\n     * @param start the start date\n     * @param end   the end date\n     * @return a number of between start to end days\n     * @see java.time.temporal.ChronoUnit#between(Temporal, Temporal)\n     */\n    public static int daysBetween(Date start, Date end) {\n        return (int) (toLocalDate(end).toEpochDay() - toLocalDate(start).toEpochDay());\n    }\n\n    /**\n     * 日期随机\n     *\n     * @param begin 开发日期\n     * @param end   结束日期\n     * @return\n     */\n    public static Date random(Date begin, Date end) {\n        long beginMills = begin.getTime(), endMills = end.getTime();\n        if (beginMills >= endMills) {\n            throw new IllegalArgumentException(\"Date [\" + format(begin) + \"] must before [\" + format(end) + \"]\");\n        }\n        return random(beginMills, endMills);\n    }\n\n    public static Date random(long beginTimeMills, long endTimeMills) {\n        if (beginTimeMills >= endTimeMills) {\n            throw new IllegalArgumentException(\"Date [\" + beginTimeMills + \"] must before [\" + endTimeMills + \"]\");\n        }\n\n        return new Date(beginTimeMills + ThreadLocalRandom.current().nextLong(endTimeMills - beginTimeMills));\n    }\n\n    /**\n     * Returns the smaller of two {@code Date} values.\n     *\n     * @param a the first Date\n     * @param b the second Date\n     * @return the smallest of {@code a} and {@code b}\n     */\n    public static Date min(Date a, Date b) {\n        return a == null ? b : (b == null || a.before(b)) ? a : b;\n    }\n\n    /**\n     * Returns the greater of two {@code Date} values.\n     *\n     * @param a the first Date\n     * @param b the second Date\n     * @return the greatest of {@code a} and {@code b}\n     */\n    public static Date max(Date a, Date b) {\n        return a == null ? b : (b == null || a.after(b)) ? a : b;\n    }\n\n    // ----------------------------------------------------------------java 8 date\n\n    public static LocalDateTime toLocalDateTime(Date date) {\n        //return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());\n        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();\n    }\n\n    public static Date toDate(LocalDateTime localDateTime) {\n        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());\n    }\n\n    public static LocalDate toLocalDate(Date date) {\n        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();\n    }\n\n    public static Date toDate(LocalDate localDate) {\n        return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());\n    }\n\n    public static LocalDateTime startOfDay(LocalDateTime dateTime) {\n        return LocalDateTime.of(dateTime.toLocalDate(), LocalTime.MIN);\n        //return dateTime.withHour(0).withMinute(0).withSecond(0).withNano(0);\n    }\n\n    public static LocalDateTime endOfDay(LocalDateTime dateTime) {\n        // 当毫秒数大于499时，如果Mysql的datetime字段没有毫秒位数，数据会自动加1秒，所以此处毫秒为000\n        return LocalDateTime.of(dateTime.toLocalDate(), LocalTime.of(23, 59, 59, /*999_999_999*/0));\n    }\n\n    /**\n     * 时区转换\n     *\n     * @param date       the date\n     * @param sourceZone the source zone id\n     * @param targetZone the target zone id\n     * @return date of target zone id\n     */\n    public static Date zoneConvert(Date date, ZoneId sourceZone, ZoneId targetZone) {\n        if (date == null || sourceZone.equals(targetZone)) {\n            return date;\n        }\n        return Date.from(\n            date.toInstant().atZone(targetZone).withZoneSameLocal(sourceZone).toInstant()\n        );\n    }\n\n    /**\n     * 时区转换\n     *\n     * @param localDateTime the localDateTime\n     * @param sourceZone    the source zone id\n     * @param targetZone    the target zone id\n     * @return localDateTime of target zone id\n     */\n    public static LocalDateTime zoneConvert(LocalDateTime localDateTime, ZoneId sourceZone, ZoneId targetZone) {\n        if (localDateTime == null || sourceZone.equals(targetZone)) {\n            return localDateTime;\n        }\n        return ZonedDateTime.of(localDateTime, sourceZone).withZoneSameInstant(targetZone).toLocalDateTime();\n    }\n\n    public static String toCronExpression(Date date) {\n        return toCronExpression(toLocalDateTime(date));\n    }\n\n    /**\n     * Converts date time to cron expression\n     *\n     * @param dateTime the local date time\n     * @return cron expression of the spec date\n     */\n    public static String toCronExpression(LocalDateTime dateTime) {\n        return new StringBuilder(22)\n            .append(dateTime.getSecond()    ).append(Char.SPACE) // second\n            .append(dateTime.getMinute()    ).append(Char.SPACE) // minute\n            .append(dateTime.getHour()      ).append(Char.SPACE) // hour\n            .append(dateTime.getDayOfMonth()).append(Char.SPACE) // day\n            .append(dateTime.getMonthValue()).append(Char.SPACE) // month\n            .append('?'                     ).append(Char.SPACE) // week\n            .append(dateTime.getYear()      )                    // year\n            .toString();\n    }\n\n    // -------------------------------------------------------------private methods\n\n    private static LocalDateTime startOfDay0(Date date) {\n        return startOfDay(toLocalDateTime(date));\n    }\n\n    private static LocalDateTime endOfDay0(Date date) {\n        return endOfDay(toLocalDateTime(date));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/date/JavaUtilDateFormat.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.date;\n\nimport cn.ponfee.commons.base.Symbol.Char;\nimport com.google.common.base.Strings;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.time.FastDateFormat;\n\nimport javax.annotation.concurrent.ThreadSafe;\nimport java.text.*;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.*;\nimport java.util.regex.Pattern;\n\n/**\n * Convert to {@code java.util.Date}, none zone offset.\n * <p>unix timestamp只支持对10位(秒)和13位(毫秒)做解析\n *\n * @author Ponfee\n * @ThreadSafe\n */\n@ThreadSafe\npublic class JavaUtilDateFormat extends DateFormat {\n\n    private static final long serialVersionUID = 6837172676882367405L;\n\n    /**\n     * For {@code java.util.Date#toString}\n     */\n    private static final DateTimeFormatter DATE_TO_STRING_FORMAT = DateTimeFormatter.ofPattern(\"EEE MMM dd HH:mm:ss zzz yyyy\", Locale.ROOT);\n\n    /**\n     * For {@link Date#toString()} \"EEE MMM dd HH:mm:ss zzz yyyy\" format\n     */\n    static final Pattern DATE_TO_STRING_PATTERN = Pattern.compile(\"^(Sun|Mon|Tue|Wed|Thu|Fri|Sat) [A-Z][a-z]{2} \\\\d{2} \\\\d{2}:\\\\d{2}:\\\\d{2} CST \\\\d{4}$\");\n\n    /**\n     * 日期时间戳：秒/毫秒\n     */\n    static final Pattern DATE_TIMESTAMP_PATTERN = Pattern.compile(\"^0|[1-9]\\\\d*$\");\n\n    static final FastDateFormat PATTERN_11 = FastDateFormat.getInstance(\"yyyyMM\");\n    static final FastDateFormat PATTERN_12 = FastDateFormat.getInstance(\"yyyy-MM\");\n    static final FastDateFormat PATTERN_13 = FastDateFormat.getInstance(\"yyyy/MM\");\n\n    static final FastDateFormat PATTERN_21 = FastDateFormat.getInstance(\"yyyyMMdd\");\n    static final FastDateFormat PATTERN_22 = FastDateFormat.getInstance(Dates.DATE_PATTERN);\n    static final FastDateFormat PATTERN_23 = FastDateFormat.getInstance(\"yyyy/MM/dd\");\n\n    static final FastDateFormat PATTERN_31 = FastDateFormat.getInstance(\"yyyyMMddHHmmss\");\n    static final FastDateFormat PATTERN_32 = FastDateFormat.getInstance(\"yyyyMMddHHmmssSSS\");\n\n    static final FastDateFormat PATTERN_41 = FastDateFormat.getInstance(Dates.DATETIME_PATTERN);\n    static final FastDateFormat PATTERN_42 = FastDateFormat.getInstance(\"yyyy/MM/dd HH:mm:ss\");\n    static final FastDateFormat PATTERN_43 = FastDateFormat.getInstance(\"yyyy-MM-dd'T'HH:mm:ss\");\n    static final FastDateFormat PATTERN_44 = FastDateFormat.getInstance(\"yyyy/MM/dd'T'HH:mm:ss\");\n\n    static final FastDateFormat PATTERN_51 = FastDateFormat.getInstance(Dates.DATEFULL_PATTERN);\n    static final FastDateFormat PATTERN_52 = FastDateFormat.getInstance(\"yyyy/MM/dd HH:mm:ss.SSS\");\n    static final FastDateFormat PATTERN_53 = FastDateFormat.getInstance(\"yyyy-MM-dd'T'HH:mm:ss.SSS\");\n    static final FastDateFormat PATTERN_54 = FastDateFormat.getInstance(\"yyyy/MM/dd'T'HH:mm:ss.SSS\");\n\n    static final FastDateFormat PATTERN_61 = FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss.SSS'Z'\");\n    static final FastDateFormat PATTERN_62 = FastDateFormat.getInstance(\"yyyy/MM/dd HH:mm:ss.SSS'Z'\");\n    static final FastDateFormat PATTERN_63 = FastDateFormat.getInstance(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\");\n    static final FastDateFormat PATTERN_64 = FastDateFormat.getInstance(\"yyyy/MM/dd'T'HH:mm:ss.SSS'Z'\");\n\n    static final FastDateFormat PATTERN_71 = FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss.SSSX\");\n    static final FastDateFormat PATTERN_72 = FastDateFormat.getInstance(\"yyyy/MM/dd HH:mm:ss.SSSX\");\n    static final FastDateFormat PATTERN_73 = FastDateFormat.getInstance(\"yyyy-MM-dd'T'HH:mm:ss.SSSX\");\n    static final FastDateFormat PATTERN_74 = FastDateFormat.getInstance(\"yyyy/MM/dd'T'HH:mm:ss.SSSX\");\n\n    /**\n     * The default date format with yyyy-MM-dd HH:mm:ss\n     */\n    public static final JavaUtilDateFormat DEFAULT = new JavaUtilDateFormat(Dates.DATETIME_PATTERN);\n\n    /**\n     * 兜底解析器\n     */\n    private final FastDateFormat backstopFormat;\n\n    public JavaUtilDateFormat(String pattern) {\n        this(pattern, Locale.getDefault());\n    }\n\n    public JavaUtilDateFormat(String pattern, Locale locale) {\n        this(FastDateFormat.getInstance(pattern, locale));\n    }\n\n    public JavaUtilDateFormat(FastDateFormat format) {\n        this.backstopFormat = format;\n\n        super.setCalendar(Calendar.getInstance(format.getTimeZone(), format.getLocale()));\n\n        NumberFormat numberFormat = NumberFormat.getIntegerInstance(format.getLocale());\n        numberFormat.setGroupingUsed(false);\n        super.setNumberFormat(numberFormat);\n    }\n\n    @Override\n    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {\n        return backstopFormat.format(date, toAppendTo, fieldPosition);\n    }\n\n    @Override\n    public Date parse(String source, ParsePosition pos) {\n        Objects.requireNonNull(pos);\n        if (pos.getIndex() < 0) {\n            throw new IllegalArgumentException(\"Invalid parse position: \" + pos.getIndex());\n        }\n        if (StringUtils.isEmpty(source) || source.length() <= pos.getIndex()) {\n            return null;\n        }\n\n        String date = source.substring(pos.getIndex());\n        try {\n            return parse(date);\n        } catch (IllegalArgumentException e) {\n            throw new IllegalArgumentException(\"Invalid date format: \" + source + \", \" + pos.getIndex() + \", \" + date);\n        } catch (Exception e) {\n            throw new IllegalArgumentException(\"Invalid date format: \" + source + \", \" + pos.getIndex() + \", \" + date, e);\n        }\n    }\n\n    @Override\n    public Date parse(String source) throws ParseException {\n        if (StringUtils.isEmpty(source)) {\n            return null;\n        }\n\n        int length = source.length();\n        if (length >= 20 && source.endsWith(\"Z\")) {\n            if (length < 24) {\n                source = padding(source) + \"Z\";\n            }\n            if (isTSeparator(source)) {\n                return (isCrossbar(source) ? PATTERN_63 : PATTERN_64).parse(source);\n            } else {\n                return (isCrossbar(source) ? PATTERN_61 : PATTERN_62).parse(source);\n            }\n        }\n\n        switch (length) {\n            case  6: return PATTERN_11.parse(source);\n            case  7: return (isCrossbar(source) ? PATTERN_12 : PATTERN_13).parse(source);\n            case  8: return PATTERN_21.parse(source);\n            case 10:\n                char separator = source.charAt(4);\n                if (separator == Char.HYPHEN) {\n                    return PATTERN_22.parse(source);\n                } else if (separator == Char.SLASH) {\n                    return PATTERN_23.parse(source);\n                } else if (DATE_TIMESTAMP_PATTERN.matcher(source).matches()) {\n                    // long string(length 10) of unix timestamp(e.g. 1640966400)\n                    return new Date(Long.parseLong(source) * 1000);\n                }\n                break;\n            case 13:\n                // long string(length 13) of mills unix timestamp(e.g. 1640966400000)\n                if (DATE_TIMESTAMP_PATTERN.matcher(source).matches()) {\n                    return new Date(Long.parseLong(source));\n                }\n                break;\n            case 14: return PATTERN_31.parse(source);\n            case 19:\n                if (isTSeparator(source)) {\n                    return (isCrossbar(source) ? PATTERN_43 : PATTERN_44).parse(source);\n                } else {\n                    return (isCrossbar(source) ? PATTERN_41 : PATTERN_42).parse(source);\n                }\n            case 17: return PATTERN_32.parse(source);\n            case 23:\n                if (isTSeparator(source)) {\n                    return (isCrossbar(source) ? PATTERN_53 : PATTERN_54).parse(source);\n                } else {\n                    return (isCrossbar(source) ? PATTERN_51 : PATTERN_52).parse(source);\n                }\n            case 26:\n            case 29:\n                if (isTSeparator(source)) {\n                    // 2021-12-31T17:01:01.000+08、2021-12-31T17:01:01.000+08:00\n                    return (isCrossbar(source) ? PATTERN_73 : PATTERN_74).parse(source);\n                } else {\n                    // 2021-12-31 17:01:01.000+08、2021-12-31 17:01:01.000+08:00\n                    return (isCrossbar(source) ? PATTERN_71 : PATTERN_72).parse(source);\n                }\n            case 28:\n                if (isCST(source)) {\n                    // 以下使用方式会相差14小时：\n                    //   1）FastDateFormat.getInstance(\"EEE MMM dd HH:mm:ss zzz yyyy\", Locale.ENGLISH).parse(source);\n                    //   2）new Date(source);\n                    //   3）Date.from(ZonedDateTime.parse(source, DATE_TO_STRING_FORMAT).toInstant());\n                    return Dates.toDate(LocalDateTime.parse(source, DATE_TO_STRING_FORMAT));\n                }\n                break;\n            default: break;\n        }\n\n        return backstopFormat.parse(source);\n    }\n\n    public LocalDateTime parseToLocalDateTime(String source) throws ParseException {\n        Date date = parse(source);\n        return date == null ? null : Dates.toLocalDateTime(date);\n    }\n\n    public LocalDateTime parseToLocalDateTime(String source, ParsePosition pos) {\n        Date date = parse(source, pos);\n        return date == null ? null : Dates.toLocalDateTime(date);\n    }\n\n    @Override\n    public Object parseObject(String source, ParsePosition pos) {\n        return parse(source, pos);\n    }\n\n    @Override\n    public Object parseObject(String source) throws ParseException {\n        return parse(source);\n    }\n\n    @Override\n    public int hashCode() {\n        return backstopFormat.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this) {\n            return true;\n        }\n\n        if (!(obj instanceof JavaUtilDateFormat)) {\n            return false;\n        }\n\n        JavaUtilDateFormat other = (JavaUtilDateFormat) obj;\n        return this.backstopFormat.equals(other.backstopFormat);\n    }\n\n    @Override\n    public AttributedCharacterIterator formatToCharacterIterator(Object obj) {\n        return backstopFormat.formatToCharacterIterator(obj);\n    }\n\n    @Override\n    public Object clone() {\n        return this;\n    }\n\n    // ------------------------------------------------------------------------deprecated methods\n\n    @Override @Deprecated\n    public void setCalendar(Calendar newCalendar) {\n        if (!Objects.equals(newCalendar, super.getCalendar())) {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    @Override @Deprecated\n    public void setNumberFormat(NumberFormat newNumberFormat) {\n        if (!Objects.equals(newNumberFormat, super.getNumberFormat())) {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    @Override @Deprecated\n    public void setTimeZone(TimeZone zone) {\n        if (!Objects.equals(zone, super.getTimeZone())) {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    @Override @Deprecated\n    public void setLenient(boolean lenient) {\n        if (lenient != super.isLenient()) {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    // ------------------------------------------------------------------------package methods\n    static boolean isCrossbar(String str) {\n        return str.charAt(4) == '-';\n    }\n\n    // 'T' literal is the date and time separator\n    static boolean isTSeparator(String str) {\n        return str.charAt(10) == 'T';\n    }\n\n    static boolean isCST(String str) {\n        return DATE_TO_STRING_PATTERN.matcher(str).matches();\n    }\n\n    static String padding(String source) {\n        // example: 2022/07/18T15:11:11Z, 2022/07/18T15:11:11.Z, 2022/07/18T15:11:11.1Z, 2022/07/18T15:11:11.13Z\n        String[] array = source.split(\"[.Z]\");\n        return array[0] + \".\" + (array.length == 1 ? \"000\" : Strings.padEnd(array[1], 3, '0'));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/date/LocalDateTimeFormat.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.date;\n\nimport cn.ponfee.commons.base.Symbol.Char;\n\nimport javax.annotation.concurrent.ThreadSafe;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.time.format.DateTimeFormatter;\nimport java.util.Date;\n\nimport static cn.ponfee.commons.date.JavaUtilDateFormat.*;\n\n/**\n * Convert to {@code java.time.LocalDateTime}, none zone offset.\n * <p>unix timestamp只支持对10位(秒)和13位(毫秒)做解析\n * <p>时区：LocalDateTime[无]、Date[0时区]、Instant[0时区]、ZonedDateTime[自带]\n *\n * @author Ponfee\n * @ThreadSafe\n * @see JavaUtilDateFormat#parseToLocalDateTime(String)\n */\n@ThreadSafe\npublic class LocalDateTimeFormat {\n\n    static final DateTimeFormatter PATTERN_01 = DateTimeFormatter.ofPattern(\"yyyyMMddHHmmss\");\n\n    public static final DateTimeFormatter PATTERN_11 = DateTimeFormatter.ofPattern(Dates.DATETIME_PATTERN);\n    static final DateTimeFormatter PATTERN_12 = DateTimeFormatter.ofPattern(\"yyyy/MM/dd HH:mm:ss\");\n    static final DateTimeFormatter PATTERN_13 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss\");\n    static final DateTimeFormatter PATTERN_14 = DateTimeFormatter.ofPattern(\"yyyy/MM/dd'T'HH:mm:ss\");\n\n    static final DateTimeFormatter PATTERN_21 = DateTimeFormatter.ofPattern(Dates.DATEFULL_PATTERN);\n    static final DateTimeFormatter PATTERN_22 = DateTimeFormatter.ofPattern(\"yyyy/MM/dd HH:mm:ss.SSS\");\n    static final DateTimeFormatter PATTERN_23 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss.SSS\");\n    static final DateTimeFormatter PATTERN_24 = DateTimeFormatter.ofPattern(\"yyyy/MM/dd'T'HH:mm:ss.SSS\");\n\n    /**\n     * The default date format with yyyy-MM-dd HH:mm:ss\n     */\n    public static final LocalDateTimeFormat DEFAULT = new LocalDateTimeFormat(Dates.DATETIME_PATTERN);\n\n    /**\n     * 兜底解析器\n     */\n    private final DateTimeFormatter backstopFormat;\n\n    public LocalDateTimeFormat(String pattern) {\n        this(DateTimeFormatter.ofPattern(pattern));\n    }\n\n    public LocalDateTimeFormat(DateTimeFormatter dateTimeFormatter) {\n        this.backstopFormat = dateTimeFormatter;\n    }\n\n    // --------------------------------------------------------------------------public methods\n\n    public LocalDateTime parse(String source) {\n        if (source == null || source.length() == 0) {\n            return null;\n        }\n\n        int length = source.length();\n        if (length >= 20 && isTSeparator(source) && source.endsWith(\"Z\")) {\n            if (isCrossbar(source)) {\n                // example: 2022-07-18T15:11:11Z, 2022-07-18T15:11:11.Z, 2022-07-18T15:11:11.1Z, 2022-07-18T15:11:11.13Z, 2022-07-18T15:11:11.133Z\n                // 解析会报错：DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss.SSSZ\")\n                return LocalDateTime.ofInstant(Instant.parse(source), ZoneOffset.UTC);\n            } else {\n                // example: 2022/07/18T15:11:11Z, 2022/07/18T15:11:11.Z, 2022/07/18T15:11:11.1Z, 2022/07/18T15:11:11.13Z, 2022/07/18T15:11:11.133Z\n                source = length < 24 ? padding(source) : source.substring(0, source.length() - 1);\n                return LocalDateTime.parse(source, PATTERN_24);\n            }\n        }\n\n        switch (length) {\n            case 8:\n                // yyyyMMdd\n                return LocalDateTime.parse(source + \"000000\", PATTERN_01);\n            case 10:\n                char c = source.charAt(4);\n                if (c == Char.HYPHEN) {\n                    // yyyy-MM-dd\n                    return LocalDateTime.parse(source + \" 00:00:00\", PATTERN_11);\n                } else if (c == Char.SLASH) {\n                    // yyyy/MM/dd\n                    return LocalDateTime.parse(source + \" 00:00:00\", PATTERN_12);\n                } else if (JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher(source).matches()) {\n                    // long string(length 10) of second unix timestamp(e.g. 1640966400)\n                    return Dates.toLocalDateTime(new Date(Long.parseLong(source) * 1000));\n                }\n                break;\n            case 13:\n                if (JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher(source).matches()) {\n                    // long string(length 13) of millisecond unix timestamp(e.g. 1640966400000)\n                    return Dates.toLocalDateTime(new Date(Long.parseLong(source)));\n                }\n                break;\n            case 14:\n                return LocalDateTime.parse(source, PATTERN_01);\n            case 19:\n                if (isTSeparator(source)) {\n                    return LocalDateTime.parse(source, isCrossbar(source) ? PATTERN_13 : PATTERN_14);\n                } else {\n                    return LocalDateTime.parse(source, isCrossbar(source) ? PATTERN_11 : PATTERN_12);\n                }\n            case 23:\n                if (isTSeparator(source)) {\n                    return LocalDateTime.parse(source, isCrossbar(source) ? PATTERN_23 : PATTERN_24);\n                } else {\n                    return LocalDateTime.parse(source, isCrossbar(source) ? PATTERN_21 : PATTERN_22);\n                }\n            default:\n                break;\n        }\n\n        return LocalDateTime.parse(source, backstopFormat);\n    }\n\n    public String format(LocalDateTime dateTime) {\n        if (dateTime == null) {\n            return null;\n        }\n        return backstopFormat.format(dateTime);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/exception/BaseCheckedException.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.exception;\n\nimport cn.ponfee.commons.model.CodeMsg;\n\n/**\n * Base checked exception definition\n *\n * @author Ponfee\n */\npublic abstract class BaseCheckedException extends Exception {\n    private static final long serialVersionUID = -1199930172272040396L;\n\n    /**\n     * Error code\n     */\n    private final int code;\n\n    public BaseCheckedException(int code) {\n        this(code, null, null);\n    }\n\n    public BaseCheckedException(CodeMsg codeMsg) {\n        this(codeMsg.getCode(), codeMsg.getMsg(), null);\n    }\n\n    /**\n     * @param code    error code\n     * @param message error message\n     */\n    public BaseCheckedException(int code, String message) {\n        this(code, message, null);\n    }\n\n    public BaseCheckedException(CodeMsg codeMsg, Throwable cause) {\n        this(codeMsg.getCode(), codeMsg.getMsg(), cause);\n    }\n\n    /**\n     * @param code    error code\n     * @param message error message\n     * @param cause   root cause\n     */\n    public BaseCheckedException(int code, String message, Throwable cause) {\n        super(message, cause);\n        this.code = code;\n    }\n\n    /**\n     * @param code               error code\n     * @param message            error message\n     * @param cause              root cause\n     * @param enableSuppression  the enableSuppression\n     * @param writableStackTrace then writableStackTrace\n     */\n    public BaseCheckedException(int code,\n                                String message,\n                                Throwable cause,\n                                boolean enableSuppression,\n                                boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n        this.code = code;\n    }\n\n    /**\n     * Returns the error code\n     *\n     * @return int value of error code\n     */\n    public int getCode() {\n        return code;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/exception/BaseUncheckedException.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.exception;\n\nimport cn.ponfee.commons.model.CodeMsg;\n\n/**\n * Base unchecked exception definition\n *\n * @author Ponfee\n */\npublic abstract class BaseUncheckedException extends RuntimeException {\n    private static final long serialVersionUID = -54158942051387210L;\n\n    /**\n     * Error code\n     */\n    private final int code;\n\n    public BaseUncheckedException(int code) {\n        this(code, null, null);\n    }\n\n    public BaseUncheckedException(CodeMsg codeMsg) {\n        this(codeMsg.getCode(), codeMsg.getMsg(), null);\n    }\n\n    /**\n     * @param code    error code\n     * @param message error message\n     */\n    public BaseUncheckedException(int code, String message) {\n        this(code, message, null);\n    }\n\n    public BaseUncheckedException(CodeMsg codeMsg, Throwable cause) {\n        this(codeMsg.getCode(), codeMsg.getMsg(), cause);\n    }\n\n    /**\n     * @param code    error code\n     * @param message error message\n     * @param cause   root cause\n     */\n    public BaseUncheckedException(int code, String message, Throwable cause) {\n        super(message, cause);\n        this.code = code;\n    }\n\n    /**\n     * @param code               error code\n     * @param message            error message\n     * @param cause              root cause\n     * @param enableSuppression  the enableSuppression\n     * @param writableStackTrace then writableStackTrace\n     */\n    public BaseUncheckedException(int code,\n                                  String message,\n                                  Throwable cause,\n                                  boolean enableSuppression,\n                                  boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n        this.code = code;\n    }\n\n    /**\n     * Returns the error code\n     *\n     * @return int value of error code\n     */\n    public int getCode() {\n        return code;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/exception/ServerException.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.exception;\n\nimport cn.ponfee.commons.model.ResultCode;\n\n/**\n * Server exception definition\n *\n * @author Ponfee\n */\npublic class ServerException extends BaseUncheckedException {\n    private static final long serialVersionUID = -247253152815744553L;\n\n    private static final int CODE = ResultCode.SERVER_ERROR.getCode();\n\n    public ServerException() {\n        super(CODE, null, null);\n    }\n\n    public ServerException(String message) {\n        super(CODE, message, null);\n    }\n\n    public ServerException(Throwable cause) {\n        super(CODE, null, cause);\n    }\n\n    public ServerException(String message, Throwable cause) {\n        super(CODE, message, cause);\n    }\n\n    public ServerException(String message,\n                           Throwable cause,\n                           boolean enableSuppression,\n                           boolean writableStackTrace) {\n        super(CODE, message, cause, enableSuppression, writableStackTrace);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/exception/Throwables.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.exception;\n\nimport cn.ponfee.commons.concurrent.Threads;\nimport org.apache.commons.lang3.ClassUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\n/**\n * Throwable utilities.\n *\n * @author Ponfee\n */\npublic final class Throwables {\n\n    private static final Logger LOG = LoggerFactory.getLogger(Throwables.class);\n\n    private static final Supplier<String> EMPTY_MESSAGE = () -> \"\";\n\n    /**\n     * Gets the root cause throwable stack trace\n     *\n     * @param throwable the throwable\n     * @return a string of throwable stack trace information\n     */\n    public static String getRootCauseStackTrace(Throwable throwable) {\n        if (throwable == null) {\n            return null;\n        }\n\n        while (throwable.getCause() != null) {\n            throwable = throwable.getCause();\n        }\n        return ExceptionUtils.getStackTrace(throwable);\n    }\n\n    public static String getRootCauseMessage(Throwable throwable) {\n        if (throwable == null) {\n            return null;\n        }\n\n        List<Throwable> list = ExceptionUtils.getThrowableList(throwable);\n        for (int i = list.size() - 1; i >= 0; i--) {\n            String message = list.get(i).getMessage();\n            if (StringUtils.isNotBlank(message)) {\n                return \"error[\" + message + \"]\";\n            }\n        }\n\n        return \"error[\" + ClassUtils.getName(throwable.getClass()) + \"]\";\n    }\n\n    // -------------------------------------------------------------------------------interface definitions\n\n    @FunctionalInterface\n    public interface ThrowingRunnable<T extends Throwable> {\n        void run() throws T;\n\n        default <R> ThrowingSupplier<R, T> toSupplier(R result) {\n            return () -> {\n                run();\n                return result;\n            };\n        }\n\n        default <R> ThrowingCallable<R, T> toCallable(R result) {\n            return () -> {\n                run();\n                return result;\n            };\n        }\n\n        static void doChecked(ThrowingRunnable<?> runnable) {\n            try {\n                runnable.run();\n            } catch (Throwable t) {\n                ExceptionUtils.rethrow(t);\n            }\n        }\n\n        static void doCaught(ThrowingRunnable<?> runnable) {\n            doCaught(runnable, EMPTY_MESSAGE);\n        }\n\n        static void doCaught(ThrowingRunnable<?> runnable, Supplier<String> message) {\n            try {\n                runnable.run();\n            } catch (Throwable t) {\n                LOG.error(message.get(), t);\n                Threads.interruptIfNecessary(t);\n            }\n        }\n\n        static Runnable toChecked(ThrowingRunnable<?> runnable) {\n            return () -> {\n                try {\n                    runnable.run();\n                } catch (Throwable t) {\n                    ExceptionUtils.rethrow(t);\n                }\n            };\n        }\n\n        static Runnable toCaught(ThrowingRunnable<?> runnable) {\n            return toCaught(runnable, EMPTY_MESSAGE);\n        }\n\n        static Runnable toCaught(ThrowingRunnable<?> runnable, Supplier<String> message) {\n            return () -> {\n                try {\n                    runnable.run();\n                } catch (Throwable t) {\n                    LOG.error(message.get(), t);\n                    Threads.interruptIfNecessary(t);\n                }\n            };\n        }\n    }\n\n    /**\n     * Lambda function checked exception\n     *\n     * @param <R> the type of results supplied by this supplier\n     * @param <T> the type of the call get method possible occur exception\n     */\n    @FunctionalInterface\n    public interface ThrowingSupplier<R, T extends Throwable> {\n        R get() throws T;\n\n        default ThrowingRunnable<Throwable> toRunnable() {\n            return this::get;\n        }\n\n        static <R> R doChecked(ThrowingSupplier<R, ?> supplier) {\n            try {\n                return supplier.get();\n            } catch (Throwable t) {\n                return ExceptionUtils.rethrow(t);\n            }\n        }\n\n        static <R> R doCaught(ThrowingSupplier<R, ?> supplier) {\n            return doCaught(supplier, null, EMPTY_MESSAGE);\n        }\n\n        static <R> R doCaught(ThrowingSupplier<R, ?> supplier, R defaultValue, Supplier<String> message) {\n            try {\n                return supplier.get();\n            } catch (Throwable t) {\n                LOG.error(message.get(), t);\n                Threads.interruptIfNecessary(t);\n                return defaultValue;\n            }\n        }\n\n        static <R> Supplier<R> toChecked(ThrowingSupplier<R, ?> supplier) {\n            return () -> {\n                try {\n                    return supplier.get();\n                } catch (Throwable t) {\n                    return ExceptionUtils.rethrow(t);\n                }\n            };\n        }\n\n        static <R> Supplier<R> toCaught(ThrowingSupplier<R, ?> supplier) {\n            return toCaught(supplier, null, EMPTY_MESSAGE);\n        }\n\n        static <R> Supplier<R> toCaught(ThrowingSupplier<R, ?> supplier, R defaultValue, Supplier<String> message) {\n            return () -> {\n                try {\n                    return supplier.get();\n                } catch (Throwable t) {\n                    LOG.error(message.get(), t);\n                    Threads.interruptIfNecessary(t);\n                    return defaultValue;\n                }\n            };\n        }\n    }\n\n    /**\n     * Lambda function checked exception\n     *\n     * @param <R> the result type of method {@code call}\n     * @param <T> the type of the call \"call\" method possible occur exception\n     */\n    @FunctionalInterface\n    public interface ThrowingCallable<R, T extends Throwable> {\n        R call() throws T;\n\n        default ThrowingRunnable<T> toRunnable() {\n            return this::call;\n        }\n\n        static <R> R doChecked(ThrowingCallable<R, ?> callable) {\n            try {\n                return callable.call();\n            } catch (Throwable t) {\n                return ExceptionUtils.rethrow(t);\n            }\n        }\n\n        static <R> R doCaught(ThrowingCallable<R, ?> callable) {\n            return doCaught(callable, null, EMPTY_MESSAGE);\n        }\n\n        static <R> R doCaught(ThrowingCallable<R, ?> callable, R defaultValue, Supplier<String> message) {\n            try {\n                return callable.call();\n            } catch (Throwable t) {\n                LOG.error(message.get(), t);\n                Threads.interruptIfNecessary(t);\n                return defaultValue;\n            }\n        }\n\n        static <R> Callable<R> toChecked(ThrowingCallable<R, ?> callable) {\n            return () -> {\n                try {\n                    return callable.call();\n                } catch (Throwable t) {\n                    return ExceptionUtils.rethrow(t);\n                }\n            };\n        }\n\n        static <R> Callable<R> toCaught(ThrowingCallable<R, ?> supplier) {\n            return toCaught(supplier, null, EMPTY_MESSAGE);\n        }\n\n        static <R> Callable<R> toCaught(ThrowingCallable<R, ?> supplier, R defaultValue, Supplier<String> message) {\n            return () -> {\n                try {\n                    return supplier.call();\n                } catch (Throwable t) {\n                    LOG.error(message.get(), t);\n                    Threads.interruptIfNecessary(t);\n                    return defaultValue;\n                }\n            };\n        }\n    }\n\n    /**\n     * Lambda function checked exception\n     *\n     * @param <E> the type of the input to the operation\n     * @param <T> the type of the call accept method possible occur exception\n     */\n    @FunctionalInterface\n    public interface ThrowingConsumer<E, T extends Throwable> {\n        void accept(E e) throws T;\n\n        default <R> ThrowingFunction<E, R, T> toFunction(R result) {\n            return x -> {\n                accept(x);\n                return result;\n            };\n        }\n\n        static <E> void doChecked(ThrowingConsumer<E, ?> consumer, E arg) {\n            try {\n                consumer.accept(arg);\n            } catch (Throwable t) {\n                ExceptionUtils.rethrow(t);\n            }\n        }\n\n        static <E> void doCaught(ThrowingConsumer<E, ?> consumer, E arg) {\n            doCaught(consumer, arg, EMPTY_MESSAGE);\n        }\n\n        static <E> void doCaught(ThrowingConsumer<E, ?> consumer, E arg, Supplier<String> message) {\n            try {\n                consumer.accept(arg);\n            } catch (Throwable t) {\n                LOG.error(message.get(), t);\n                Threads.interruptIfNecessary(t);\n            }\n        }\n\n        static <E> Consumer<E> toChecked(ThrowingConsumer<E, ?> consumer) {\n            return e -> {\n                try {\n                    consumer.accept(e);\n                } catch (Throwable t) {\n                    ExceptionUtils.rethrow(t);\n                }\n            };\n        }\n\n        static <E> Consumer<E> toCaught(ThrowingConsumer<E, ?> consumer) {\n            return toCaught(consumer, EMPTY_MESSAGE);\n        }\n\n        static <E> Consumer<E> toCaught(ThrowingConsumer<E, ?> consumer, Supplier<String> message) {\n            return arg -> {\n                try {\n                    consumer.accept(arg);\n                } catch (Throwable t) {\n                    LOG.error(message.get(), t);\n                    Threads.interruptIfNecessary(t);\n                }\n            };\n        }\n    }\n\n    /**\n     * Lambda function checked exception\n     *\n     * @param <E> the type of the input to the function\n     * @param <R> the type of the result of the function\n     * @param <T> the type of the call apply method possible occur exception\n     */\n    @FunctionalInterface\n    public interface ThrowingFunction<E, R, T extends Throwable> {\n        R apply(E e) throws T;\n\n        default ThrowingConsumer<E, T> toConsumer() {\n            return this::apply;\n        }\n\n        static <E, R> R doChecked(ThrowingFunction<E, R, ?> function, E arg) {\n            try {\n                return function.apply(arg);\n            } catch (Throwable t) {\n                return ExceptionUtils.rethrow(t);\n            }\n        }\n\n        static <E, R> R doCaught(ThrowingFunction<E, R, ?> function, E arg) {\n            return doCaught(function, arg, null, EMPTY_MESSAGE);\n        }\n\n        static <E, R> R doCaught(ThrowingFunction<E, R, ?> function, E arg, R defaultValue, Supplier<String> message) {\n            try {\n                return function.apply(arg);\n            } catch (Throwable t) {\n                LOG.error(message.get(), t);\n                Threads.interruptIfNecessary(t);\n                return defaultValue;\n            }\n        }\n\n        static <E, R> Function<E, R> toChecked(ThrowingFunction<E, R, ?> function) {\n            return e -> {\n                try {\n                    return function.apply(e);\n                } catch (Throwable t) {\n                    return ExceptionUtils.rethrow(t);\n                }\n            };\n        }\n\n        static <E, R> Function<E, R> toCaught(ThrowingFunction<E, R, ?> function) {\n            return toCaught(function, null, EMPTY_MESSAGE);\n        }\n\n        static <E, R> Function<E, R> toCaught(ThrowingFunction<E, R, ?> function, R defaultValue, Supplier<String> message) {\n            return arg -> {\n                try {\n                    return function.apply(arg);\n                } catch (Throwable t) {\n                    LOG.error(message.get(), t);\n                    Threads.interruptIfNecessary(t);\n                    return defaultValue;\n                }\n            };\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/exception/UnauthorizedException.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.exception;\n\nimport cn.ponfee.commons.model.ResultCode;\n\n/**\n * Unauthorized exception definition\n *\n * @author Ponfee\n */\npublic class UnauthorizedException extends BaseUncheckedException {\n    private static final long serialVersionUID = -5678901285130119481L;\n\n    private static final int CODE = ResultCode.UNAUTHORIZED.getCode();\n\n    public UnauthorizedException() {\n        super(CODE, null, null);\n    }\n\n    public UnauthorizedException(String message) {\n        super(CODE, message, null);\n    }\n\n    public UnauthorizedException(Throwable cause) {\n        super(CODE, null, cause);\n    }\n\n    public UnauthorizedException(String message, Throwable cause) {\n        super(CODE, message, cause);\n    }\n\n    public UnauthorizedException(String message,\n                                 Throwable cause,\n                                 boolean enableSuppression,\n                                 boolean writableStackTrace) {\n        super(CODE, message, cause, enableSuppression, writableStackTrace);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/exception/UnimplementedException.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.exception;\n\nimport cn.ponfee.commons.model.ResultCode;\n\n/**\n * Unimplemented exception definition\n *\n * @author Ponfee\n */\npublic class UnimplementedException extends BaseUncheckedException {\n    private static final long serialVersionUID = -5983398403463732650L;\n\n    private static final int CODE = ResultCode.SERVER_UNSUPPORTED.getCode();\n\n    public UnimplementedException() {\n        super(CODE, null, null);\n    }\n\n    public UnimplementedException(String message) {\n        super(CODE, message, null);\n    }\n\n    public UnimplementedException(Throwable cause) {\n        super(CODE, null, cause);\n    }\n\n    public UnimplementedException(String message, Throwable cause) {\n        super(CODE, message, cause);\n    }\n\n    public UnimplementedException(String message,\n                                  Throwable cause,\n                                  boolean enableSuppression,\n                                  boolean writableStackTrace) {\n        super(CODE, message, cause, enableSuppression, writableStackTrace);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/AbstractCsvExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.tree.FlatNode;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Exports csv\n * \n * @author Ponfee\n */\npublic abstract class AbstractCsvExporter<T> extends AbstractDataExporter<T> {\n\n    protected final Appendable csv;\n    private final char csvSeparator;\n    private final AtomicBoolean hasBuild = new AtomicBoolean(false);\n\n    public AbstractCsvExporter(Appendable csv) {\n        this(csv, ',');\n    }\n\n    public AbstractCsvExporter(Appendable csv, char csvSeparator) {\n        this.csv = csv;\n        this.csvSeparator = csvSeparator;\n    }\n\n    @Override\n    public final <E> void build(Table<E> table) {\n        if (hasBuild.getAndSet(true)) {\n            throw new UnsupportedOperationException(\"Only support single table.\");\n        }\n\n        List<FlatNode<Integer, Thead>> thead = table.getThead();\n        if (CollectionUtils.isEmpty(thead)) {\n            throw new IllegalArgumentException(\"Thead cannot be null.\");\n        }\n\n        // build table thead\n        buildComplexThead(thead);\n\n        // tbody---------------\n        rollingTbody(table, (data, i) -> {\n            try {\n                for (int m = data.length - 1, j = 0; j <= m; j++) {\n                    // escapeCsv(toString(data[j]), csvSeparator);\n                    csv.append(toString(data[j]));\n                    if (j < m) {\n                        csv.append(csvSeparator);\n                    }\n                }\n                csv.append(Files.SYSTEM_LINE_SEPARATOR); // 换行\n                //if ((i & 0xFF) == 0) {\n                //    this.flush();\n                //}\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n        });\n\n        try {\n            if (table.isEmptyTbody()) {\n                csv.append(NO_RESULT_TIP);\n            } else {\n                super.nonEmpty();\n            }\n\n            // tfoot---------\n            if (ArrayUtils.isNotEmpty(table.getTfoot())) {\n                FlatNode<Integer, Thead> root = thead.get(0);\n                if (table.getTfoot().length > root.getTreeLeafCount()) {\n                    throw new IllegalStateException(\"Tfoot length cannot more than total leaf count.\");\n                }\n\n                int n = root.getTreeLeafCount(), m = table.getTfoot().length, mergeNum = n - m;\n                for (int i = 0; i < mergeNum; i++) {\n                    if (i == mergeNum - 1) {\n                        csv.append(\"合计\");\n                    }\n                    csv.append(csvSeparator);\n                }\n                for (int i = mergeNum; i < n; i++) {\n                    // escapeCsv(toString((table.getTfoot()[i - mergeNum])), csvSeparator);\n                    csv.append(toString(table.getTfoot()[i - mergeNum]));\n                    if (i != n - 1) {\n                        csv.append(csvSeparator);\n                    }\n                }\n\n                csv.append(Files.SYSTEM_LINE_SEPARATOR);\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    //protected void flush() {}\n\n    private void buildComplexThead(List<FlatNode<Integer, Thead>> thead) {\n        List<Thead> leafs = super.getLeafThead(thead);\n        try {\n            for (int i = 0, n = leafs.size(); i < n; i++) {\n                csv.append(leafs.get(i).getName());\n                if (i != n - 1) {\n                    csv.append(csvSeparator);\n                }\n            }\n            csv.append(Files.SYSTEM_LINE_SEPARATOR);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    // 创建简单表头\n    /*private void buildSimpleThead(String[] theadName) {\n        for (String th : theadName) {\n            csv.append(th).append(csvSeparator);\n        }\n        csv.setLength(csv.length() - 1);\n        csv.append(Files.LINE_SEPARATOR);\n    }*/\n\n    public static String escapeCsv(String text) {\n        return escapeCsv(text, ',');\n    }\n\n    public static String escapeCsv(String text, char separator) {\n        if (StringUtils.isEmpty(text)) {\n            return text;\n        }\n\n        if (text.contains(\"\\\"\")) {\n            text = text.replace(\"\\\"\", \"\\\"\\\"\");\n        }\n        if (StringUtils.contains(text, separator)) {\n            //String.format(\"\\\"%s\\\"\", text)\n            text = new StringBuilder(text.length() + 2).append('\"').append(text).append('\"').toString();\n        }\n        return text;\n    }\n\n    private static String toString(Object value) {\n        return value == null ? \"\" : value.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/AbstractDataExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.tree.FlatNode;\n\nimport java.lang.reflect.Array;\nimport java.util.*;\nimport java.util.function.BiConsumer;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Exports abstract class\n * \n * @author Ponfee\n */\npublic abstract class AbstractDataExporter<T> implements DataExporter<T> {\n\n    public static final int AWAIT_TIME_MILLIS = 47;\n\n    private boolean empty = true;\n    private String name; // report name: non thread safe\n\n    @Override\n    public boolean isEmpty() {\n        return empty;\n    }\n\n    @Override\n    public void close() {\n        // nothing to do\n    }\n\n    public final void nonEmpty() {\n        this.empty = false;\n    }\n\n    public final AbstractDataExporter<T> setName(String name) {\n        this.name = name;\n        return this;\n    }\n\n    public final String getName() {\n        return name;\n    }\n\n    protected final <E> void rollingTbody(Table<E> table, BiConsumer<Object[], Integer> action) {\n        try {\n            E data; Function<E, Object[]> converter;\n            if ((converter = table.getConverter()) != null) {\n                for (int i = 0; table.isNotEnd();) {\n                    if ((data = table.getRow(AWAIT_TIME_MILLIS)) != null) {\n                        action.accept(converter.apply(data), i++);\n                    }\n                }\n            } else {\n                String[] fields = table.getThead()\n                                       .stream()\n                                       .filter(FlatNode::isLeaf)\n                                       .map(f -> f.getAttach().getField())\n                                       .toArray(String[]::new);\n                Object[] array;\n                for (int i = 0; table.isNotEnd();) {\n                    if ((data = table.getRow(AWAIT_TIME_MILLIS)) != null) {\n                        if (data instanceof Object[]) {\n                            array = (Object[]) data;\n                        } else if (data.getClass().isArray()) {\n                            array = covariantArray(data);\n                        } else if (data instanceof Collection<?>) {\n                            array = collection2array((Collection<?>) data);\n                        } else if (data instanceof Iterable<?>) {\n                            array = iterable2array((Iterable<?>) data);\n                        } else if (data instanceof Iterator<?>) {\n                            array = iterator2array((Iterator<?>) data);\n                        } else if (data instanceof Map<?, ?>) {\n                            array = map2array((Map<?, ?>) data);\n                        } else if (data instanceof Dictionary<?, ?>) {\n                            array = dictionary2array((Dictionary<?, ?>) data);\n                        } else {\n                            array = bean2array(data, fields);\n                        }\n                        action.accept(array, i++);\n                    }\n                }\n            }\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new RuntimeException(e);\n        }\n    }\n\n    protected final List<Thead> getLeafThead(List<FlatNode<Integer, Thead>> thead) {\n        return thead.stream()\n                    .filter(FlatNode::isLeaf)\n                    .map(FlatNode::getAttach)\n                    .collect(Collectors.toList());\n    }\n\n    private static Object[] collection2array(Collection<?> coll) {\n        Object[] array = new Object[coll.size()];\n        int i = 0;\n        for (Object obj : coll) {\n            array[i++] = obj;\n        }\n        return array;\n    }\n\n    private static Object[] iterable2array(Iterable<?> iterable) {\n        List<Object> list = new LinkedList<>();\n        iterable.forEach(list::add);\n        return list.toArray();\n    }\n\n    private static Object[] iterator2array(Iterator<?> iter) {\n        List<Object> list = new LinkedList<>();\n        while (iter.hasNext()) {\n            list.add(iter.next());\n        }\n        return list.toArray();\n    }\n\n    private static Object[] covariantArray(Object array0) {\n        int size = Array.getLength(array0);\n        Object[] array = new Object[size];\n        for (int i = 0; i < size; i++) {\n            array[i] = Array.get(array0, i);\n        }\n        return array;\n    }\n\n    private static Object[] map2array(Map<?, ?> map) {\n        List<Object> list = new LinkedList<>();\n        map.forEach((k, v) -> list.add(v));\n        return list.toArray();\n    }\n\n    private static Object[] dictionary2array(Dictionary<?, ?> dic) {\n        List<Object> list = new LinkedList<>();\n        Enumeration<?> enu = dic.elements();\n        while (enu.hasMoreElements()) {\n            list.add(enu.nextElement());\n        }\n        return list.toArray();\n    }\n\n    private static Object[] bean2array(Object bean, String[] fields) {\n        int size = fields.length;\n        Object[] array = new Object[size];\n        for (int i = 0; i < size; i++) {\n            // must be setting field\n            array[i] = Fields.get(bean, fields[i]);\n        }\n        return array;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/AbstractSplitExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.util.Holder;\nimport com.google.common.base.Preconditions;\n\nimport java.io.IOException;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Function;\n\n/**\n * Export multiple file\n *\n * @author Ponfee\n */\npublic abstract class AbstractSplitExporter extends AbstractDataExporter<Void> {\n\n    private final int batchSize;\n    private final String savingFilePathPrefix;\n    private final String fileSuffix;\n    private final Executor executor;\n\n    public AbstractSplitExporter(int batchSize, String savingFilePathPrefix, \n                                 String fileSuffix, Executor executor) {\n        Preconditions.checkArgument(batchSize > 0);\n        this.batchSize = batchSize;\n        this.savingFilePathPrefix = savingFilePathPrefix;\n        this.fileSuffix = fileSuffix;\n        this.executor = executor;\n    }\n\n    @Override\n    public final <E> void build(Table<E> table) {\n        List<CompletableFuture<Void>> futures = new LinkedList<>();\n        AtomicInteger count = new AtomicInteger(0);\n        AtomicInteger split = new AtomicInteger(0);\n        Holder<Table<Object[]>> subTable = Holder.of(table.copyOfWithoutTbody(Function.identity()));\n        rollingTbody(table, (data, i) -> {\n            subTable.get().addRow(data);\n            if (count.incrementAndGet() == batchSize) {\n                // sets a new table and return the last\n                Table<Object[]> last = subTable.set(table.copyOfWithoutTbody(Function.identity()));\n                String path = buildFilePath(split.incrementAndGet());\n                futures.add(CompletableFuture.runAsync(splitExporter(last, path), executor));\n                count.set(0); // reset count and sub table\n            }\n        });\n        if (!subTable.get().isEmptyTbody()) {\n            String path = buildFilePath(split.incrementAndGet());\n            futures.add(CompletableFuture.runAsync(splitExporter(subTable.get(), path), executor));\n        }\n\n        if (!futures.isEmpty()) {\n            super.nonEmpty();\n            futures.forEach(CompletableFuture::join);\n        }\n    }\n\n    protected abstract AbstractAsyncSplitExporter splitExporter(Table<Object[]> subTable,\n                                                                String savingFilePath);\n\n    @Override\n    public final Void export() {\n        throw new UnsupportedOperationException();\n    }\n\n    private String buildFilePath(int fileNo) {\n        return savingFilePathPrefix + String.format(\"%04d\", fileNo) + fileSuffix;\n    }\n\n    public static abstract class AbstractAsyncSplitExporter implements Runnable {\n        private final Table<Object[]> subTable;\n        protected final String savingFilePath;\n\n        public AbstractAsyncSplitExporter(Table<Object[]> subTable, String savingFilePath) {\n            this.subTable = subTable;\n            this.savingFilePath = savingFilePath;\n        }\n\n        @Override\n        public final void run()  {\n            subTable.toEnd();\n            try (AbstractDataExporter<?> exporter = createExporter()) {\n                exporter.build(subTable);\n                complete(exporter);\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n        protected abstract AbstractDataExporter<?> createExporter() throws IOException;\n\n        protected void complete(AbstractDataExporter<?> exporter) {}\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/CellStyleOptions.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\n/**\n * 单元格样式选项\n * \n * @author Ponfee\n */\npublic enum CellStyleOptions {\n\n    HIGHLIGHT, CELL_PROCESS\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/ConsoleExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.tree.FlatNode;\nimport com.google.common.base.Strings;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.LongAdder;\nimport java.util.stream.Collectors;\n\n/**\n * Console Exporter\n *\n * @author Ponfee\n */\npublic class ConsoleExporter extends AbstractDataExporter<String> {\n\n    public static final String HORIZON = \"\\n\\n-------------------------------------------------------\\n\\n\";\n    public static final String ELLIPSIS_STR = \"...\";\n    public static final int ELLIPSIS_LEN = ELLIPSIS_STR.length();\n\n    protected final Appendable out;\n    protected final int maxColumnWidth;\n    protected final boolean hasLineSeparator;\n\n    public ConsoleExporter(Appendable out) {\n        this(out, 36, false);\n    }\n\n    public ConsoleExporter(Appendable out, int maxColumnWidth, boolean rowSeparator) {\n        this.out = out;\n        this.maxColumnWidth = Math.max(maxColumnWidth, ELLIPSIS_LEN + 1);\n        this.hasLineSeparator = rowSeparator;\n    }\n\n    /**\n     * 构建html\n     */\n    @Override\n    public <E> void build(Table<E> table) {\n        List<FlatNode<Integer, Thead>> flats = table.getThead();\n        if (flats == null || flats.isEmpty()) {\n            throw new IllegalArgumentException(\"thead can't be null\");\n        }\n\n        try {\n            // horizon\n            horizon();\n\n            // table start\n            List<Column> columns = union(\n                new Column(new Thead((\"#\"))),\n                getLeafThead(table.getThead()).stream().map(Column::new).collect(Collectors.toList())\n            );\n\n            LongAdder rowCount = new LongAdder();\n            rollingTbody(table, (data, i) -> {\n                // row number\n                Column first = columns.get(0);\n                String rowNumber = Integer.toString(i + 1);\n                first.values.add(rowNumber);\n                first.width = Numbers.bounds(rowNumber.length(), first.width, maxColumnWidth);\n\n                // each row\n                for (int m = data.length, colIdx = 0; colIdx < m; colIdx++) {\n                    Column column = columns.get(colIdx + 1);\n                    String value = Objects.toString(data[colIdx], \"\");\n                    column.values.add(value);\n                    column.width = Numbers.bounds(value.length(), column.width, maxColumnWidth);\n                }\n                rowCount.increment();\n            });\n\n            // print caption\n            int rowWidth = columns.stream().mapToInt(e -> e.width + 3).sum() + 1;\n            String caption = Objects.toString(table.getCaption(), \"\");\n            append(\"+-\").append('-', rowWidth - 4).append(\"-+\").newLine();\n            append(\"| \").center(caption, rowWidth - 4).append(\" |\").newLine();\n\n            String separator = \"+-\" + columns.stream().map(e -> Strings.repeat(\"-\", e.width)).collect(Collectors.joining(\"-+-\")) + \"-+\";\n\n            // print thead\n            append(separator).newLine();\n            for (Column col : columns) {\n                append(\"| \").center(col.getName(), col.width).append(' ');\n            }\n            append('|').newLine();\n            append(separator).newLine();\n\n            // print rows\n            for (int n = rowCount.intValue(), rowIdx = 0; rowIdx < n; rowIdx++) {\n                if (hasLineSeparator && rowIdx > 0) {\n                    append(separator).newLine();\n                }\n                for (Column col : columns) {\n                    append(\"| \").append(col.values.get(rowIdx), col.width).append(' ');\n                }\n                append('|').newLine();\n            }\n            append(separator).newLine();\n\n            nonEmpty();\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public String export() {\n        return out.toString();\n    }\n\n    // ------------------------------------------------------------private methods\n    private ConsoleExporter horizon() throws IOException {\n        if (!isEmpty()) {\n            out.append(HORIZON);\n        }\n        return this;\n    }\n\n    private ConsoleExporter append(char c) throws IOException {\n        out.append(c);\n        return this;\n    }\n\n    private ConsoleExporter append(char c, int count) throws IOException {\n        for (int i = 0; i < count; i++) {\n            out.append(c);\n        }\n        return this;\n    }\n\n    private ConsoleExporter append(CharSequence text) throws IOException {\n        out.append(text);\n        return this;\n    }\n\n    private ConsoleExporter append(String text, int width) throws IOException {\n        int padding = width - text.length();\n        if (padding >= 0) {\n            append(text).append(' ', padding);\n        } else {\n            out.append(text, 0, width - ELLIPSIS_LEN);\n            out.append(ELLIPSIS_STR);\n        }\n        return this;\n    }\n\n    private ConsoleExporter center(String text, int width) throws IOException {\n        int padding = width - text.length();\n        if (padding >= 0) {\n            append(' ', padding / 2).append(text).append(' ', (padding + 1) / 2);\n        } else {\n            out.append(text, 0, width - ELLIPSIS_LEN);\n            out.append(ELLIPSIS_STR);\n        }\n        return this;\n    }\n\n    private void newLine() throws IOException {\n        out.append('\\n');\n    }\n\n    private static <T> List<T> union(T first, Collection<T> coll) {\n        List<T> list = new ArrayList<>(coll.size() + 1);\n        list.add(first);\n        list.addAll(coll);\n        return list;\n    }\n\n    private class Column extends Thead {\n        private static final long serialVersionUID = -5764311953058980984L;\n        private final List<String> values = new ArrayList<>();\n        private int width;\n\n        public Column(Thead th) {\n            super(th.getName(), th.getTmeta(), th.getField());\n            this.width = Math.min(th.getName().length(), maxColumnWidth);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/CsvFileExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.io.ByteOrderMarks;\nimport cn.ponfee.commons.io.WrappedBufferedWriter;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * Exports csv file\n * \n * @author Ponfee\n */\npublic class CsvFileExporter extends CsvWriteExporter {\n\n    public CsvFileExporter(String filePath, boolean withBom) throws IOException {\n        this(new File(filePath), StandardCharsets.UTF_8, withBom);\n    }\n\n    public CsvFileExporter(File file, Charset charset, boolean withBom) throws IOException {\n        super(createWriter(file, charset, withBom));\n    }\n\n    public CsvFileExporter(File file, Charset charset, boolean withBom, char csvSeparator) throws IOException {\n        super(createWriter(file, charset, withBom), csvSeparator);\n    }\n\n    private static Writer createWriter(File file, Charset charset, boolean withBom) throws IOException {\n        WrappedBufferedWriter writer = new WrappedBufferedWriter(file, charset);\n        byte[] bom;\n        if (withBom && (bom = ByteOrderMarks.get(charset)) != null) {\n            writer.write(bom);\n        }\n        return writer;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/CsvStringExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.io.ByteOrderMarks;\nimport cn.ponfee.commons.io.WrappedBufferedWriter;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.Charset;\n\n/**\n * Exports csv string\n * \n * @author Ponfee\n */\npublic class CsvStringExporter extends AbstractCsvExporter<String> {\n\n    public CsvStringExporter() {\n        this(0x2000);\n    }\n\n    public CsvStringExporter(int capacity) {\n        super(new StringBuilder(capacity));\n    }\n\n    public CsvStringExporter(int capacity, char csvSeparator) {\n        super(new StringBuilder(capacity), csvSeparator);\n    }\n\n    @Override\n    public String export() {\n        return csv.toString();\n    }\n\n    public void write(String filePath, Charset charset, boolean withBom) {\n        File file = new File(filePath);\n        try (WrappedBufferedWriter writer = new WrappedBufferedWriter(file, charset)) {\n            byte[] bom;\n            if (withBom && (bom = ByteOrderMarks.get(charset)) != null) {\n                writer.write(bom);\n            }\n            writer.append((StringBuilder) super.csv);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/CsvWriteExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport java.io.Writer;\n\n/**\n * Exports csv wirte\n * \n * @author Ponfee\n */\npublic class CsvWriteExporter extends AbstractCsvExporter<Void> {\n\n    public CsvWriteExporter(Writer writer) {\n        super(writer);\n    }\n\n    public CsvWriteExporter(Writer writer, char csvSeparator) {\n        super(writer, csvSeparator);\n    }\n\n    @Override\n    public Void export() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void close() {\n        try {\n            ((Writer) super.csv).close();\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/DataExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport java.io.Closeable;\n\n/**\n * {@link Closeable#close()} 要求幂等\n * {@link AutoCloseable#close()} 不要求幂等\n * \n * <p>数据导出\n * \n * @author Ponfee\n */\npublic interface DataExporter<T> extends Closeable {\n\n    /** 提示无结果 */\n    String NO_RESULT_TIP = \"No results found\";\n\n    /**\n     * 构建表格\n     */\n    <E> void build(Table<E> table);\n\n    /**\n     * 获取表格\n     */\n    T export();\n\n    /**\n     * 判断是否为空\n     */\n    boolean isEmpty();\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/ExcelExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.date.Dates;\nimport cn.ponfee.commons.export.Tmeta.Type;\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.tree.FlatNode;\nimport cn.ponfee.commons.util.Colors;\nimport cn.ponfee.commons.util.ImageUtils;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.collections4.MapUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.time.DateUtils;\nimport org.apache.poi.ss.usermodel.*;\nimport org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;\nimport org.apache.poi.ss.util.CellRangeAddress;\nimport org.apache.poi.xssf.streaming.*;\nimport org.apache.poi.xssf.usermodel.*;\n\nimport java.awt.Color;\nimport java.io.*;\nimport java.text.ParseException;\nimport java.util.*;\nimport java.util.Map.Entry;\nimport java.util.function.Consumer;\n\n/**\n * excel导出\n *\n * SXSSFWorkbook（size>65536），XSSFWorkbook（.xlsx），HSSFWorkbook（.xls）\n *\n * @see <a href=\"https://blog.csdn.net/zl_momomo/article/details/80703533\">poi</a>\n *\n * |------------|----------------------------------------------------------|--------------|\n * |    model   |                         description                      | Read & Write |\n * |------------|----------------------------------------------------------|--------------|\n * | SXSSF      | holding fix rows in memory, write disk when row overflow |      R       |\n * |------------|----------------------------------------------------------|--------------|\n * | eventmodel | based event model, parse with SAX, thrift cup and memory |      R       |\n * |------------|----------------------------------------------------------|--------------|\n * | usermodel  | tradition model, need more cpu and memory                |     R & W    |\n * |------------|----------------------------------------------------------|--------------|\n *\n * @author Ponfee\n */\npublic class ExcelExporter extends AbstractDataExporter<byte[]> {\n\n    // 内存最大存放100行数据 超过100自动刷新到硬盘中\n    // SXSSFSheet.flushRows(100); retain 100 last rows and flush all others\n    public static final int DEFAULT_WINDOW_SIZE = 100;\n\n    /** row and cell config，默认的列宽和行高大小 */\n    private static final int DEFAULT_WIDTH = 3200;\n    private static final short DEFAULT_HEIGHT = 350;\n\n    /** nested image config，office 2013版本 */\n    private static final double RATE_WIDTH = 70.5; // 图片真实宽度与excel列宽的比例\n    private static final double RATE_HEIGHT = 18; // 图片真实高度与excel行高的比例\n\n    /** 作为分隔符（类似html的<hr />）的合并列数目 */\n    private static final int MARGIN_ROW_CELL_SIZE = 26;\n\n    private final IndexedColorMap defaultColorMap = new DefaultIndexedColorMap();\n\n    private SXSSFWorkbook workbook; // excel\n    private final XSSFCellStyle titleStyle; // 标题样式\n    private final XSSFCellStyle headStyle; // 表头样式\n    private final XSSFCellStyle dataStyle; // 数据样式\n    private final XSSFCellStyle tfootMergeStyle; // 合计行样式\n    private final XSSFCellStyle noneStyle; // 无样式\n    private final XSSFCellStyle tipStyle; // 无样式\n    private final XSSFDataFormat dataFormat; // 数据格式\n\n    private final Map<String, SXSSFSheet> sheets = new HashMap<>();\n    private final Map<String, Integer>    images = new HashMap<>();\n    private final Map<String, Freeze>    freezes = new HashMap<>();\n\n    public ExcelExporter() {\n        workbook = new SXSSFWorkbook(DEFAULT_WINDOW_SIZE);\n        //workbook.setCompressTempFiles(true); // temp files will be gzipped\n        dataFormat = (XSSFDataFormat) workbook.createDataFormat();\n\n        XSSFFont titleFont = (XSSFFont) workbook.createFont();\n        titleFont.setBold(true);\n        titleFont.setFontName(\"黑体\");\n        //titleFont.setFontHeightInPoints((short) 12); // set font 1 to 12 point type\n\n        XSSFFont headFont = (XSSFFont) workbook.createFont();\n        headFont.setBold(true);\n        headFont.setFontName(\"宋体\");\n\n        XSSFFont redFont = (XSSFFont) workbook.createFont();\n        redFont.setColor(new XSSFColor(new Color(255, 0, 0), defaultColorMap));\n        redFont.setFontName(\"宋体\");\n\n        XSSFCellStyle baseStyle = (XSSFCellStyle) workbook.createCellStyle();\n        baseStyle.setBorderLeft(BorderStyle.THIN);\n        baseStyle.setBorderTop(BorderStyle.THIN);\n        baseStyle.setBorderRight(BorderStyle.THIN);\n        baseStyle.setBorderBottom(BorderStyle.THIN);\n        baseStyle.setVerticalAlignment(VerticalAlignment.CENTER);\n        baseStyle.setWrapText(true);\n        baseStyle.setDataFormat(dataFormat.getFormat(\"@\"));\n\n        titleStyle = (XSSFCellStyle) workbook.createCellStyle();\n        titleStyle.cloneStyleFrom(baseStyle);\n        titleStyle.setAlignment(HorizontalAlignment.CENTER);\n        titleStyle.setFont(titleFont);\n        titleStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 填充样式\n        titleStyle.setFillForegroundColor(new XSSFColor(new Color(255, 255, 224), defaultColorMap)); // 填充颜色\n\n        headStyle = (XSSFCellStyle) workbook.createCellStyle();\n        headStyle.cloneStyleFrom(baseStyle);\n        headStyle.setAlignment(HorizontalAlignment.CENTER);\n        headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);\n        headStyle.setFillForegroundColor(new XSSFColor(new Color(192, 192, 192), defaultColorMap));\n        headStyle.setFont(headFont);\n        headStyle.setWrapText(false);\n\n        dataStyle = (XSSFCellStyle) workbook.createCellStyle();\n        dataStyle.cloneStyleFrom(baseStyle);\n        dataStyle.setWrapText(false);\n\n        tfootMergeStyle = (XSSFCellStyle) workbook.createCellStyle();\n        tfootMergeStyle.cloneStyleFrom(dataStyle);\n        tfootMergeStyle.setFont(headFont);\n        tfootMergeStyle.setWrapText(false);\n        tfootMergeStyle.setAlignment(HorizontalAlignment.RIGHT);\n        tfootMergeStyle.setBorderLeft(BorderStyle.THIN);\n        tfootMergeStyle.setBorderTop(BorderStyle.THIN);\n        tfootMergeStyle.setBorderRight(BorderStyle.THIN);\n        tfootMergeStyle.setBorderBottom(BorderStyle.THIN);\n\n        tipStyle = (XSSFCellStyle) workbook.createCellStyle();\n        tipStyle.cloneStyleFrom(baseStyle);\n        tipStyle.setFont(redFont);\n\n        noneStyle = (XSSFCellStyle) workbook.createCellStyle();\n        noneStyle.setVerticalAlignment(VerticalAlignment.CENTER);\n    }\n\n    /**\n     * 构建excel\n     */\n    @Override\n    public <E> void build(Table<E> table) {\n        // 1、校验表头是否为空\n        List<FlatNode<Integer, Thead>> flats = table.getThead();\n        if (CollectionUtils.isEmpty(flats)) {\n            throw new IllegalArgumentException(\"thead can't be null\");\n        }\n\n        // 2、获取工作簿\n        String name = this.getName();\n        SXSSFSheet sheet = getSheet(name);\n\n        // 3、判断工作簿是否已创建过行数据，还没有创建行时sheet.getLastRowNum()返回-1\n        CursorRowNumber cursorRow = new CursorRowNumber(Math.max(sheet.getLastRowNum(), 0));\n        if (cursorRow.get() > 0) {\n            // 创建两行空白行\n            cursorRow.increment();\n            int i = cursorRow.getAndIncrement(), j = cursorRow.getAndIncrement();\n            SXSSFRow row1 = sheet.createRow(i);\n            row1.setHeight(DEFAULT_HEIGHT);\n            SXSSFRow row2 = sheet.createRow(j);\n            row2.setHeight(DEFAULT_HEIGHT);\n            for (int k = 0; k < MARGIN_ROW_CELL_SIZE; k++) {\n                createCell(row1, k, noneStyle, null);\n                createCell(row2, k, noneStyle, null);\n            }\n            sheet.addMergedRegion(new CellRangeAddress(i, j, 0, MARGIN_ROW_CELL_SIZE - 1));\n        }\n\n        // 4、构建复合表头\n        buildComplexThead(table, sheet, cursorRow);\n\n        // 5、冻结窗口配置\n        Freeze freeze;\n        if ((freeze = freezes.get(name)) != null) {\n            freeze.disable();\n        } else {\n            freezes.put(name, new Freeze(1, cursorRow.get())); // 叶子节点只占一列，故colSplit=1\n        }\n\n        // 6、处理tbody数据\n        Map<CellStyleOptions, Object> options = table.getOptions();\n        List<Thead> leafs = getLeafThead(flats);\n        List<XSSFCellStyle> styles = createStyles(leafs);\n        rollingTbody(table, (data, i) -> {\n            SXSSFRow row = sheet.createRow(cursorRow.getAndIncrement());\n            //row.setHeight(DEFAULT_HEIGHT);\n            for (int m = data.length, j = 0; j < m; j++) {\n                createCell(row, j, styles.get(j), getTmeta(leafs, j), data[j], i, j, options);\n            }\n        });\n\n        // 7、判断是否有数据\n        SXSSFRow row;\n        int totalLeafCount = flats.get(0).getTreeLeafCount();\n        if (table.isEmptyTbody()) {\n            createBlankRow(NO_RESULT_TIP, sheet, tipStyle, cursorRow, totalLeafCount);\n        } else {\n            super.nonEmpty();\n        }\n\n        // 8、处理tfoot数据\n        Object[] tfoots = table.getTfoot();\n        if (ArrayUtils.isNotEmpty(tfoots)) {\n            int rowNum = cursorRow.getAndIncrement();\n            row = sheet.createRow(rowNum);\n            row.setHeight(DEFAULT_HEIGHT);\n\n            if (table.getTfoot().length > totalLeafCount) {\n                throw new IllegalStateException(\"tfoot data length cannot more than total leaf count.\");\n            }\n\n            // 合计单元格\n            int mergeNum = totalLeafCount - table.getTfoot().length;\n            for (int i = 0; i < mergeNum; i++) {\n                createCell(row, i, tfootMergeStyle, (i == 0) ? \"合计\" : null);\n            }\n            if (mergeNum > 1) {\n                sheet.addMergedRegion(new CellRangeAddress(rowNum, rowNum, 0, mergeNum - 1));\n            }\n\n            // 合计数据\n            for (int i = 0; i < tfoots.length; i++) {\n                createCell(row, i + mergeNum, styles.get(mergeNum + i),\n                           getTmeta(leafs, mergeNum + i), tfoots[i]);\n            }\n        }\n\n        // 9、文字注释\n        if (StringUtils.isNotBlank(table.getComment())) {\n            createBlankRow(table.getComment(), sheet, tipStyle, cursorRow, totalLeafCount);\n        }\n    }\n\n    public void insertImage(byte[] imageBytes) {\n        int[] size = ImageUtils.getImageSize(new ByteArrayInputStream(imageBytes));\n        insertImage(imageBytes, size[0], size[1]);\n    }\n\n    /**\n     * excel中嵌入图片\n     * excel.insertImage(byte[] image, width, cheight);\n     */\n    public void insertImage(byte[] imageBytes, int width, int height) {\n        if (imageBytes == null || imageBytes.length == 0) {\n            return;\n        }\n\n        super.nonEmpty();\n\n        SXSSFSheet sheet = getSheet(getName());\n        int startRow = Optional.ofNullable(images.get(getName())).orElse(1), startCol = 1;\n        int endCol = startCol + (int) Math.round(((double) width) / RATE_WIDTH);\n        int endRow = startRow + (int) Math.round(((double) height) / RATE_HEIGHT);\n        images.put(getName(), endRow + 2);\n\n        SXSSFDrawing drawing = sheet.createDrawingPatriarch();\n        XSSFClientAnchor anchor = new XSSFClientAnchor(\n            0, 0, width, height, startCol, startRow, (short) endCol, endRow\n        );\n\n        anchor.setAnchorType(AnchorType.DONT_MOVE_AND_RESIZE);\n        drawing.createPicture(anchor, workbook.addPicture(imageBytes, SXSSFWorkbook.PICTURE_TYPE_PNG));\n\n        /*int pictureIdx = workbook.addPicture(imageBytes, Workbook.PICTURE_TYPE_PNG);\n        CreationHelper helper = workbook.getCreationHelper();\n        SXSSFDrawing drawing = sheet.createDrawingPatriarch();\n        ClientAnchor anchor = helper.createClientAnchor();\n        anchor.setCol1(0);\n        anchor.setRow1(Math.max(sheet.getLastRowNum(), 0));\n        Picture pict = drawing.createPicture(anchor, pictureIdx);\n        pict.resize(1);*/\n    }\n\n    /**\n     * 输出到输出流\n     */\n    public void write(OutputStream out) {\n        try (BufferedOutputStream bos = new BufferedOutputStream(out);\n             SXSSFWorkbook wb = workbook\n        ) {\n            createFreezePane();\n            wb.write(bos);\n            wb.dispose();\n            workbook = null;\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public void write(String filepath) {\n        write(new File(filepath));\n    }\n\n    public void write(File file) {\n        try (OutputStream out = new FileOutputStream(file)) {\n            write(out);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 导出\n     */\n    @Override\n    public byte[] export() {\n        ByteArrayOutputStream out = new ByteArrayOutputStream(8192);\n        write(out);\n        return out.toByteArray();\n    }\n\n    /**\n     * 关闭\n     */\n    @Override\n    public void close() {\n        if (workbook == null) {\n            return;\n        }\n\n        try (SXSSFWorkbook wb = workbook) {\n            // nothing to do\n            wb.dispose(); // dispose of temporary files backing this workbook on disk\n            workbook = null;\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        } finally {\n            sheets.clear();\n            images.clear();\n            freezes.clear();\n        }\n    }\n\n    //------------------------------------------------------------protected methods\n    protected SXSSFSheet getSheet(String name) {\n        SXSSFSheet sheet = sheets.get(name);\n        if (sheet == null) {\n            sheet = workbook.createSheet(name);\n            sheet.setDisplayGridlines(false); // 不显示网格线\n            /*sheet.setDefaultColumnWidth(DEFAULT_WIDTH);\n            sheet.setDefaultRowHeight(DEFAULT_HEIGHT);*/\n            sheets.put(name, sheet);\n        }\n        return sheet;\n    }\n\n    protected void removeSheet(String name) {\n        workbook.removeSheetAt(workbook.getSheetIndex(sheets.get(name)));\n        sheets.remove(name);\n    }\n\n    private void createFreezePane() {\n        for (Entry<String, SXSSFSheet> entry : sheets.entrySet()) {\n            Freeze freeze = freezes.get(entry.getKey());\n            if (freeze != null && freeze.freeze) {\n                entry.getValue().createFreezePane(freeze.colSplit, freeze.rowSplit);\n            }\n        }\n    }\n\n    // 创建简单表头\n    /*private void buildSimpleThead(String title, XSSFSheet sheet, CursorRow cursorRow, String[] theadName) {\n        createCell(title, sheet, titleStyle, cursorRow, theadName.length);\n        XSSFRow row = sheet.createRow(cursorRow.getAndIncrement());\n        row.setHeight(DEFAULT_HEIGHT);\n        for (int i = 0; i < theadName.length; i++) {\n            sheet.setColumnWidth(i, DEFAULT_WIDTH);\n            createCell(row, i, headStyle, theadName[i]);\n        }\n    }*/\n\n    // 复合表头\n    private <E> void buildComplexThead(Table<E> table, SXSSFSheet sheet, CursorRowNumber cursorRow) {\n        List<FlatNode<Integer, Thead>> flats = table.getThead();\n        FlatNode<Integer, Thead> root = flats.get(0);\n        int totalLeafCount = root.getTreeLeafCount();\n        List<FlatNode<Integer, Thead>> thead = flats.subList(1, flats.size());\n\n        // create caption\n        if (StringUtils.isNotBlank(table.getCaption())) {\n            createBlankRow(table.getCaption(), sheet, titleStyle, cursorRow, totalLeafCount);\n        }\n\n        //sheet.trackAllColumnsForAutoSizing(); // 设置自动列宽\n\n        // 约定非叶子节点不能跨行\n        Set<Integer> rows = new HashSet<>();\n        int beginCol, endRow, endCol, lastLevel = 1;\n        int cellLevel, treeDepth = root.getTreeDepth() - 1;\n        for (int n = thead.size(), i = 0; i < n; i++) {\n            FlatNode<Integer, Thead> flat = thead.get(i);\n            cellLevel = flat.getLevel() - 1;\n            if (cellLevel > lastLevel) {\n                lastLevel = cellLevel;\n                cursorRow.increment();\n            }\n\n            beginCol = flat.getLeftLeafCount();\n            endCol = beginCol + flat.getTreeLeafCount() - 1;\n            if (flat.isLeaf()) {\n                endRow = cursorRow.get() + treeDepth - cellLevel;\n                sheet.setColumnWidth(beginCol, DEFAULT_WIDTH);\n                //sheet.autoSizeColumn(beginCol); // 设置自动列宽，要在autoSizeColumn前使用trackAllColumnsForAutoSizing()\n            } else {\n                endRow = cursorRow.get(); // 约定非子节点不能跨行\n            }\n\n            SXSSFRow colRow;\n            if (rows.add(cursorRow.get())) {\n                colRow = sheet.createRow(cursorRow.get()); // 还未创建该行\n                colRow.setHeight(DEFAULT_HEIGHT);\n            } else {\n                colRow = sheet.getRow(cursorRow.get());\n            }\n            createCell(colRow, beginCol, headStyle, flat.getAttach().getName());\n\n            // -----------------------------设置被合并单元格的样式------------------------------ //\n            for (int b = beginCol + 1; b <= endCol; b++) { // 列\n                createCell(colRow, b, headStyle, null);\n            }\n            for (int a = cursorRow.get() + 1; a <= endRow; a++) { // 行\n                if (rows.add(a)) { // 行未创建\n                    colRow = sheet.createRow(a);\n                    colRow.setHeight(DEFAULT_HEIGHT);\n                } else { // 行已创建\n                    colRow = sheet.getRow(a);\n                }\n\n                for (int b = beginCol; b <= endCol; b++) {\n                    createCell(colRow, b, headStyle, null);\n                }\n            }\n            if (cursorRow.get() != endRow || beginCol != endCol) {\n                sheet.addMergedRegion(new CellRangeAddress(cursorRow.get(), endRow, beginCol, endCol));\n            }\n            // -----------------------------设置被合并单元格的样式------------------------------ //\n        }\n        cursorRow.increment();\n    }\n\n    private Tmeta getTmeta(List<Thead> thead, int index) {\n        return thead.get(index).getTmeta();\n    }\n\n    /**\n     * 创建空行\n     * @param text\n     * @param sheet\n     * @param style\n     * @param cursorRow\n     * @param columnLen\n     */\n    private void createBlankRow(String text, SXSSFSheet sheet, XSSFCellStyle style,\n                                CursorRowNumber cursorRow, int columnLen) {\n        SXSSFRow row = sheet.createRow(cursorRow.get());\n        row.setHeight(DEFAULT_HEIGHT);\n        createCell(row, 0, style, text);\n        for (int i = 1; i < columnLen; i++) {\n            createCell(row, i, style, null);\n        }\n        sheet.addMergedRegion(new CellRangeAddress(cursorRow.get(), cursorRow.get(), 0, columnLen - 1));\n        cursorRow.increment();\n    }\n\n    private void createCell(SXSSFRow row, int colIndex, XSSFCellStyle style, Tmeta tmeta, Object value) {\n        createCell(row, colIndex, style, tmeta, value, -1, -1, null);\n    }\n\n    private void createCell(SXSSFRow row, int colIndex, XSSFCellStyle style, Object value) {\n        createCell(row, colIndex, style, null, value, -1, -1, null);\n    }\n\n    /**\n     * 创建单元格\n     * @param row\n     * @param colIndex\n     * @param style\n     * @param tmeta\n     * @param value\n     * @param tbodyRowIdx\n     * @param tbodyColIdx\n     * @param options\n     */\n    private void createCell(SXSSFRow row, int colIndex, XSSFCellStyle style, Tmeta tmeta, Object value,\n                            int tbodyRowIdx, int tbodyColIdx, Map<CellStyleOptions, Object> options) {\n\n        SXSSFCell cell = row.createCell(colIndex);\n        cell.setCellStyle(style);\n\n        // 设置单元格格式\n        if (tmeta == null) {\n            setCellString(cell, value);\n        } else if (tmeta.getType() == Type.NUMERIC) {\n            if (ObjectUtils.isEmpty(value)) {\n                cell.setCellType(CellType.NUMERIC);\n                cell.setCellValue(new XSSFRichTextString());\n            } else if (value instanceof String && ((String) value).endsWith(\"%\")) {\n                String val = ((String) value).substring(0, ((String) value).length() - 1);\n                cell.setCellValue(Numbers.toDouble(val.replace(\",\", \"\")) / 100);\n            } else {\n                cell.setCellValue(Numbers.toDouble(value.toString().replace(\",\", \"\")));\n            }\n        } else if (tmeta.getType() == Type.DATETIME) {\n            if (value == null) {\n                cell.setCellType(CellType.BLANK);\n            } else if (value instanceof Date) {\n                cell.setCellValue((Date) value);\n            } else if (value instanceof Calendar) {\n                cell.setCellValue((Calendar) value);\n            } else {\n                String str = value.toString();\n                String format = Optional.ofNullable(tmeta.getFormat()).orElse(Dates.DATETIME_PATTERN);\n                try {\n                    cell.setCellValue(DateUtils.parseDateStrictly(str, format));\n                    //cell.setCellValue(FastDateFormat.getInstance(format).parse(str));\n                } catch (ParseException e) {\n                    throw new IllegalArgumentException(\"invalid date str: \" + str + \", format: \" + format, e);\n                }\n            }\n        } else {\n            setCellString(cell, value);\n        }\n\n        // 样式自定义处理\n        processOptions(cell, tbodyRowIdx, tbodyColIdx, options);\n    }\n\n    /**\n     * 处理其它配置项\n     * @param cell\n     * @param tbodyRowIdx\n     * @param tbodyColIdx\n     * @param options\n     */\n    @SuppressWarnings(\"unchecked\")\n    private void processOptions(SXSSFCell cell, int tbodyRowIdx, int tbodyColIdx,\n                                Map<CellStyleOptions, Object> options) {\n        if (MapUtils.isEmpty(options)) {\n            return;\n        }\n\n        // 单元格高亮显示\n        Map<String, Object> highlight = (Map<String, Object>) options.get(CellStyleOptions.HIGHLIGHT);\n        if (MapUtils.isNotEmpty(highlight)) {\n            for (List<Integer> c : (List<List<Integer>>) highlight.get(\"cells\")) {\n                if (c.get(0) == tbodyRowIdx && c.get(1) == tbodyColIdx) {\n                    XSSFFont font = (XSSFFont) workbook.createFont();\n                    font.setColor(new XSSFColor(\n                        Colors.fromHex((String) highlight.get(\"color\")), defaultColorMap\n                    ));\n                    XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle();\n                    style.cloneStyleFrom(cell.getCellStyle());\n                    style.setFont(font);\n                    cell.setCellStyle(style);\n                }\n            }\n        }\n\n        // 处理\n        Consumer<Object[]> processor = (Consumer<Object[]>) options.get(CellStyleOptions.CELL_PROCESS);\n        if (processor != null) {\n            processor.accept(new Object[] { workbook, cell, tbodyRowIdx, tbodyColIdx });\n        }\n    }\n\n    /**\n     * create cell style, only called once\n     * @param thead\n     * @return\n     */\n    private List<XSSFCellStyle> createStyles(List<Thead> thead) {\n        List<XSSFCellStyle> styles = new ArrayList<>(thead.size());\n        for (Thead flat : thead) {\n            XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle();\n            styles.add(style);\n            style.cloneStyleFrom(dataStyle);\n\n            Tmeta tmeta = flat.getTmeta();\n            if (tmeta != null) {\n                switch (tmeta.getAlign()) { // 对齐方式\n                    case LEFT:\n                        style.setAlignment(HorizontalAlignment.LEFT);\n                        break;\n                    case CENTER:\n                        style.setAlignment(HorizontalAlignment.CENTER);\n                        break;\n                    case RIGHT:\n                        style.setAlignment(HorizontalAlignment.RIGHT);\n                        break;\n                    default:\n                        break;\n                }\n\n                // 设置单元格格式\n                if (StringUtils.isNotBlank(tmeta.getFormat())) {\n                    //dataFormat.getFormat(\"0.00%\")： 0.00%->0xa; #,###.00%->0xa5; #,##0->xxx;\n                    style.setDataFormat(dataFormat.getFormat(tmeta.getFormat()));\n                }\n\n                // 设置颜色\n                if (tmeta.getColor() != null) {\n                    XSSFFont font = (XSSFFont) workbook.createFont();\n                    font.setColor(new XSSFColor(tmeta.getColor(), defaultColorMap));\n                    style.setFont(font);\n                }\n            } // end of tmeta\n\n        }\n\n        return styles;\n    }\n\n    private static void setCellString(SXSSFCell cell, Object value) {\n        if (value != null) {\n            cell.setCellValue(value.toString());\n        } else {\n            cell.setCellType(CellType.BLANK);\n        }\n    }\n\n    /**\n     * 游标行\n     */\n    @SuppressWarnings(\"unused\")\n    private static final class CursorRowNumber {\n        int current;\n\n        CursorRowNumber() {\n            this(0);\n        }\n\n        CursorRowNumber(int initValue) {\n            this.current = initValue;\n        }\n\n        int getAndIncrement() {\n            return this.current++;\n        }\n\n        int incrementAndGet() {\n            return ++this.current;\n        }\n\n        int getAndDecrement() {\n            return this.current--;\n        }\n\n        int decrementAndGet() {\n            return --this.current;\n        }\n\n        void add(int i) {\n            this.current += i;\n        }\n\n        int addAndGet(int i) {\n            this.current += i;\n            return this.current;\n        }\n\n        int getAndAdd(int i) {\n            int temp = this.current;\n            this.current += i;\n            return temp;\n        }\n\n        void set(int i) {\n            this.current = i;\n        }\n\n        int getAndSet(int i) {\n            int temp = this.current;\n            this.current = i;\n            return temp;\n        }\n\n        int get() {\n            return this.current;\n        }\n\n        void increment() {\n            this.current++;\n        }\n    }\n\n    /**\n     * 窗口冻结\n     */\n    @SuppressWarnings(\"unused\")\n    private static final class Freeze {\n        boolean freeze = true;\n        final int colSplit;\n        final int rowSplit;\n\n        Freeze(int colSplit, int rowSplit) {\n            this.colSplit = colSplit;\n            this.rowSplit = rowSplit;\n        }\n\n        void enable() {\n            freeze = true;\n        }\n\n        void disable() {\n            freeze = false;\n        }\n    }\n\n    /*public void deleteTempFiles() {\n        for (int i = 0, n = workbook.getNumberOfSheets(); i <= n; i++) {\n            SXSSFSheet sheet = workbook.getSheetAt(i); // only support in SXSSFSheet\n            // delete only if the sheet is written by stream\n            SheetDataWriter sdw = (SheetDataWriter) Fields.get(sheet, \"_writer\");\n            FileUtils.deleteQuietly((File) Fields.get(sdw, \"_fd\"));\n        }\n    }*/\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/HtmlExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.export.Tmeta.Type;\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.tree.FlatNode;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.text.MessageFormat;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\n\n/**\n * html导出\n * \n * @author Ponfee\n */\npublic class HtmlExporter extends AbstractDataExporter<String> {\n\n    //private static final Pattern PATTERN_NEGATIVE = Pattern.compile(\"^(-(([0-9]+\\\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\\\.[0-9]+)|([0-9]*[1-9][0-9]*)))(%)?$\");\n\n    public static final String HORIZON = \"<hr style=\\\"border:3 double #b0c4de;with:95%;margin:20px 0;\\\" />\";\n    private static final String TEMPLATE = new StringBuilder(4096) \n       .append(\"<!DOCTYPE html>                                                                   \\n\")\n       .append(\"<html>                                                                            \\n\")\n       .append(\"  <head lang=\\\"en\\\">                                                              \\n\")\n       .append(\"    <meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\" />     \\n\")\n       .append(\"    <title>{0}</title>                                                            \\n\")\n       .append(\"    '<style>                                                                      \\n\")\n       .append(\"      * {font-family: Microsoft YaHei;}                                           \\n\")\n       .append(\"      .grid {overflow-x: auto;background-color: #fff;color: #555;}                \\n\")\n       .append(\"      .grid table {                                                               \\n\")\n       .append(\"        width:100%;font-size:12px;border-collapse:collapse;border-style:hidden;   \\n\")\n       .append(\"      }                                                                           \\n\")\n       .append(\"      .grid table, div.grid table caption, div.grid table tr {                    \\n\")\n       .append(\"        border: 1px solid #6d6d6d;                                                \\n\")\n       .append(\"      }                                                                           \\n\")\n       .append(\"      .grid table tr td, div.grid table tr th {border: 1px solid #6d6d6d;}        \\n\")\n       .append(\"      .grid table caption {                                                       \\n\")\n       .append(\"        font-size:14px; padding:5px;                                              \\n\")\n       .append(\"        background:#e6e6fa; font-weight:bolder; border-bottom:none;               \\n\")\n       .append(\"      }                                                                           \\n\")\n       .append(\"      .grid table thead th {padding: 5px;background: #ccc;}                       \\n\")\n       .append(\"      .grid table td {text-align: center;padding: 3px;}                           \\n\")\n       .append(\"      .grid table td.text-left, .grid table th.text-left {text-align:left;}       \\n\")\n       .append(\"      .grid table td.text-right, .grid table th.text-right {text-align:right;}    \\n\")\n       .append(\"      .grid table td.text-center, .grid table th.text-center {text-align:center;} \\n\")\n       .append(\"      .grid table tfoot th {padding: 5px;}                                        \\n\")\n       .append(\"      .grid table tr:nth-child(odd) td{background:#fff;}                          \\n\")\n       .append(\"      .grid table tr:nth-child(even) td{background: #e8e8e8}                      \\n\")\n       .append(\"      .grid p.remark {font-size: 14px;}                                           \\n\")\n       .append(\"      .grid .nowrap {                                                             \\n\")\n       .append(\"        white-space:nowrap; word-break:keep-all;                                  \\n\")\n       .append(\"        overflow:hidden; text-overflow:ellipsis; max-width:200px;                 \\n\")\n       .append(\"      }                                                                           \\n\")\n       .append(\"    </style>'                                                                     \\n\")\n       .append(\"  </head>                                                                         \\n\")\n       .append(\"  <body>{1}</body>                                                                \\n\")\n       .append(\"</html>                                                                           \\n\")\n       .toString().replaceAll(\"\\\\s+\\n\", \"\\n\");\n\n    private final StringBuilder html; // StringBuilder扩容：(value.length << 1) + 2\n                                     // 容量如果不够，直接扩充到需要的容量大小\n\n    public HtmlExporter() {\n        this.html = new StringBuilder(0x2000); // 初始容量8192\n    }\n\n    public HtmlExporter(String initHtml) {\n        this.html = new StringBuilder(initHtml);\n    }\n\n    /**\n     * 构建html\n     */\n    @Override\n    public <E> void build(Table<E> table) {\n        List<FlatNode<Integer, Thead>> flats = table.getThead();\n        if (flats == null || flats.isEmpty()) {\n            throw new IllegalArgumentException(\"thead can't be null\");\n        }\n\n        // horizon-------\n        horizon();\n\n        // table start-------\n        html.append(\"<div class=\\\"grid\\\"><table cellpadding=\\\"0\\\" cellspacing=\\\"0\\\">\");\n        if (StringUtils.isNotBlank(table.getCaption())) {\n            html.append(\"<caption>\")\n                .append(table.getCaption())\n                .append(\"</caption>\");\n        }\n\n        // thead-------\n        buildComplexThead(flats);\n\n        // tbody-------\n        List<Thead> leafs = getLeafThead(flats);\n        html.append(\"<tbody>\");\n        rollingTbody(table, (data, i) -> {\n            html.append(\"<tr>\");\n            for (int m = data.length, j = 0; j < m; j++) {\n                html.append(\"<td\");\n                processMeta(data[j], getTmeta(leafs, j), i, j, table.getOptions()); // 样式\n                html.append(\">\").append(formatData(data[j], getTmeta(leafs, j))).append(\"</td>\");\n            }\n            html.append(\"</tr>\");\n        });\n\n        int totalLeafCount = flats.get(0).getTreeLeafCount();\n        if (table.isEmptyTbody()) {\n            html.append(\"<tr><td colspan=\\\"\")\n                .append(totalLeafCount)\n                .append(\"\\\" style=\\\"color:red;padding:3px;font-size:14px;\\\">\")\n                .append(NO_RESULT_TIP)\n                .append(\"</td></tr>\");\n        } else {\n            super.nonEmpty();\n        }\n        html.append(\"</tbody>\");\n\n        // tfoot-------\n        boolean hasTfoot = false;\n        if (ArrayUtils.isNotEmpty(table.getTfoot())) {\n            hasTfoot = true;\n            html.append(\"<tfoot><tr>\");\n\n            if (table.getTfoot().length > totalLeafCount) {\n                throw new IllegalStateException(\"tfoot data length cannot more than total leaf count.\");\n            }\n\n            int mergeNum = totalLeafCount - table.getTfoot().length;\n            if (mergeNum > 0) {\n                html.append(\"<th colspan=\\\"\")\n                    .append(mergeNum)\n                    .append(\"\\\" style=\\\"text-align:right;\\\">合计</th>\");\n            }\n\n            for (int i = 0; i < table.getTfoot().length; i++) {\n                html.append(\"<th\");\n                processMeta(table.getTfoot()[i], getTmeta(leafs, mergeNum + i));\n                html.append(\">\")\n                    .append(formatData(table.getTfoot()[i], getTmeta(leafs, mergeNum + i)))\n                    .append(\"</th>\");\n            }\n            html.append(\"</tr></tfoot>\");\n        }\n\n        // comment------\n        if (StringUtils.isNotBlank(table.getComment())) {\n            String[] comments = table.getComment().split(\";\");\n            StringBuilder builder = new StringBuilder(\"<tr><td colspan=\\\"\")\n                .append(totalLeafCount)\n                .append(\"\\\" style=\\\"color:red; padding:3px;font-size:14px;\\\">\")\n                .append(\"<div style=\\\"text-align:left;font-weight:bold;\\\">备注：</div>\");\n            for (String comment : comments) {\n                builder.append(\"<div style=\\\"text-align:left;text-indent:2em;\\\">\")\n                       .append(comment)\n                       .append(\"</div>\");\n            }\n            builder.append(\"</td></tr>\");\n\n            if (hasTfoot) {\n                html.insert(html.length() - \"</tfoot>\".length(), builder);\n            } else {\n                html.append(\"<tfoot>\").append(builder).append(\"</tfoot>\");\n            }\n        }\n\n        // table end-----\n        html.append(\"</table></div>\");\n    }\n\n    @Override\n    public String export() {\n        return MessageFormat.format(TEMPLATE, super.getName(), html.toString());\n    }\n\n    public String body() {\n        return html.toString();\n    }\n\n    public HtmlExporter horizon() {\n        if (html.length() > 0) {\n            html.append(HORIZON);\n        }\n        return this;\n    }\n\n    //htmlExporter.horizon().append(\"<div align=\\\"center\\\"><img src=\\\"cid:\")\n    //                      .append(img.getId()).apend(\"\\\" /></div>\");\n    public HtmlExporter append(String string) {\n        if (StringUtils.isNotBlank(string)) {\n            super.nonEmpty();\n            html.append(string);\n        }\n        return this;\n    }\n\n    // <img src=\"data:image/jpg;base64,/9j/4QMZR...\" />\n    public HtmlExporter insertImage(String imageB64) {\n        super.nonEmpty();\n        html.append(\"<img src=\\\"\").append(imageB64).append(\"\\\" />\");\n        return this;\n    }\n\n    // 创建简单表头\n    /*private void buildSimpleThead(String[] theadName) {\n        html.append(\"<thead><tr>\");\n        for (String th : theadName) {\n            html.append(\"<th>\").append(th).append(\"</th>\");\n        }\n        html.append(\"</tr></thead>\");\n    }*/\n\n    // 复合表头\n    private void buildComplexThead(List<FlatNode<Integer, Thead>> flats) {\n        html.append(\"<thead><tr>\");\n        int lastLevel = 1, treeDepth = flats.get(0).getTreeDepth() - 1, cellLevel;\n        for (FlatNode<Integer, Thead> flat : flats.subList(1, flats.size())) {\n            cellLevel = flat.getLevel() - 1;\n            if (lastLevel < cellLevel) {\n                html.append(\"</tr><tr>\");\n                lastLevel = cellLevel;\n            }\n            html.append(\"<th\");\n            if (flat.isLeaf()) { // 叶子节点，跨行\n                if (treeDepth - cellLevel > 0) {\n                    html.append(\" rowspan=\\\"\").append(treeDepth - cellLevel + 1).append(\"\\\"\");\n                }\n            } else { // 非叶子节点，跨列\n                if (flat.getTreeLeafCount() > 1) {\n                    html.append(\" colspan=\\\"\").append(flat.getTreeLeafCount()).append(\"\\\"\");\n                }\n            }\n            html.append(\">\").append(flat.getAttach().getName()).append(\"</th>\");\n        }\n        html.append(\"</tr></thead>\");\n    }\n\n    private Tmeta getTmeta(List<Thead> thead, int index) {\n        return thead.get(index).getTmeta();\n    }\n\n    private void processMeta(Object value, Tmeta tmeta) {\n        processMeta(value, tmeta, -1, -1, null);\n    }\n\n    /**\n     * 样式处理\n     * @param value\n     * @param tmeta\n     * @param tbodyRowIdx\n     * @param tbodyColIdx\n     * @param options\n     */\n    private final StringBuilder style = new StringBuilder();\n    private final StringBuilder clazz = new StringBuilder();\n    private void processMeta(Object value, Tmeta tmeta, int tbodyRowIdx, \n                             int tbodyColIdx, Map<CellStyleOptions, Object> options) {\n        style.setLength(0);\n        clazz.setLength(0);\n\n        /*if (PATTERN_NEGATIVE.matcher(Objects.toString(value, \"\")).matches()) {\n            style.append(\"color:#006400;font-weight:bold;\"); // 负数显示绿色\n        }*/\n\n        if (tmeta != null) {\n            switch (tmeta.getAlign()) {\n                case LEFT:\n                    clazz.append(\"text-left \");\n                    break;\n                case CENTER:\n                    clazz.append(\"text-center \");\n                    break;\n                case RIGHT:\n                    clazz.append(\"text-right \");\n                    break;\n                default:\n                    break;\n            }\n\n            if (tmeta.getColor() != null) {\n                style.append(\"color:\").append(tmeta.getColorHex()).append(\";\");\n            }\n\n            if (tmeta.isNowrap()) {\n                clazz.append(\"nowrap \");\n            }\n        }\n\n        processOptions(style, tbodyRowIdx, tbodyColIdx, options);\n\n        if (style.length() > 0) {\n            html.append(\" style=\\\"\").append(style).append(\"\\\"\");\n        }\n        if (clazz.length() > 0) {\n            clazz.setLength(clazz.length() - 1);\n            html.append(\" class=\\\"\").append(clazz).append(\"\\\"\");\n        }\n    }\n\n    /**\n     * 格式化\n     * @param data\n     * @param tmeta\n     * @return\n     */\n    private static String formatData(Object data, Tmeta tmeta) {\n        if (data == null) {\n            return \"\";\n        } else if (tmeta == null) {\n            return data.toString();\n        } else if (tmeta.getType() == Type.NUMERIC) {\n            return Numbers.format(data);\n        } else {\n            return data.toString();\n        }\n    }\n\n    /**\n     * 样式自定义处理\n     * @param style\n     * @param dataRowIdx\n     * @param dataColIdx\n     * @param options\n     */\n    @SuppressWarnings(\"unchecked\")\n    private static void processOptions(StringBuilder style, int dataRowIdx, int dataColIdx, \n                                       Map<CellStyleOptions, Object> options) {\n        if (options == null || options.isEmpty()) {\n            return;\n        }\n\n        Map<String, Object> highlight = (Map<String, Object>) options.get(CellStyleOptions.HIGHLIGHT);\n        if (highlight != null && !highlight.isEmpty()) {\n            String color = \"color:\" + highlight.get(\"color\") + \";font-weight:bold;\";\n            List<List<Integer>> cells = (List<List<Integer>>) highlight.get(\"cells\");\n            for (List<Integer> cell : cells) {\n                if (cell.get(0).equals(dataRowIdx) && cell.get(1).equals(dataColIdx)) {\n                    style.append(color);\n                }\n            }\n        }\n\n        Function<Object, String> processor = (Function<Object, String>) options.get(CellStyleOptions.CELL_PROCESS);\n        if (processor != null) {\n            style.append(processor.apply(new Object[] { dataRowIdx, dataColIdx }));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/SplitCsvFileExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport java.io.IOException;\nimport java.util.concurrent.Executor;\n\n/**\n * Export multiple csv file\n *\n * @author Ponfee\n */\npublic class SplitCsvFileExporter extends AbstractSplitExporter {\n\n    private final boolean withBom;\n\n    public SplitCsvFileExporter(int batchSize, String savingFilePathPrefix,\n                                boolean withBom, Executor executor) {\n        super(batchSize, savingFilePathPrefix, \".csv\", executor);\n        this.withBom = withBom;\n    }\n\n    @Override\n    protected AbstractAsyncSplitExporter splitExporter(Table<Object[]> subTable, String savingFilePath) {\n        return new AsnycCsvFileExporter(subTable, savingFilePath, withBom);\n    }\n\n    private static class AsnycCsvFileExporter extends AbstractAsyncSplitExporter {\n        final boolean withBom;\n\n        AsnycCsvFileExporter(Table<Object[]> subTable, String savingFilePath,\n                             boolean withBom) {\n            super(subTable, savingFilePath);\n            this.withBom = withBom;\n        }\n\n        @Override\n        protected AbstractDataExporter<?> createExporter() throws IOException {\n            return new CsvFileExporter(savingFilePath, withBom);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/SplitExcelExporter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport java.util.concurrent.Executor;\n\n/**\n * Export multiple excel file\n *\n * @author Ponfee\n */\npublic class SplitExcelExporter extends AbstractSplitExporter {\n\n    public SplitExcelExporter(int batchSize, String savingFilePathPrefix, Executor executor) {\n        super(batchSize, savingFilePathPrefix, \".xlsx\", executor);\n    }\n\n    @Override\n    protected AbstractAsyncSplitExporter splitExporter(Table<Object[]> subTable, String savingFilePath) {\n        return new AsnycExcelExporter(subTable, savingFilePath, super.getName());\n    }\n\n    private static class AsnycExcelExporter extends AbstractAsyncSplitExporter {\n        final String sheetName;\n\n        AsnycExcelExporter(Table<Object[]> subTable, String savingFilePath,\n                           String sheetName) {\n            super(subTable, savingFilePath);\n            this.sheetName = sheetName;\n        }\n\n        @Override\n        protected AbstractDataExporter<?> createExporter() {\n            AbstractDataExporter<?> excel = new ExcelExporter();\n            excel.setName(sheetName);\n            return excel;\n        }\n\n        @Override\n        protected void complete(AbstractDataExporter<?> exporter) {\n            ((ExcelExporter) exporter).write(savingFilePath);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/Table.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.tree.FlatNode;\nimport cn.ponfee.commons.tree.PlainNode;\nimport cn.ponfee.commons.tree.TreeNode;\nimport org.apache.commons.collections4.CollectionUtils;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\n\n/**\n * 表格\n *\n * @author Ponfee\n */\npublic class Table<E> implements Serializable {\n    private static final long serialVersionUID = 1600567917100486004L;\n\n    private static final int ROOT_PID = 0;\n\n    private final List<FlatNode<Integer, Thead>> thead; // 表头\n    private final Function<E, Object[]> converter; // 数据转换\n    private String caption; // 标题\n    private Object[] tfoot; // 表尾\n    private String comment; // 注释说明\n    private Map<CellStyleOptions, Object> options; // 其它特殊配置项，如：{HIGHLIGHT:{\\\"cells\\\":[[2,15],[2,16]],\\\"color\\\":\\\"#f00\\\"}}\n\n    private final LinkedBlockingQueue<E> tbody = new LinkedBlockingQueue<>(); // 表体：不指定容量，默认为Integer.MAX_VALUE，也就是无界队列\n    private volatile boolean empty = true;\n    private volatile boolean end = false;\n\n    public Table(List<FlatNode<Integer, Thead>> thead,\n                 Function<E, Object[]> converter,\n                 String caption, Object[] tfoot, String comment,\n                 Map<CellStyleOptions, Object> options) {\n        this.thead = thead;\n        this.converter = converter;\n        this.caption = caption;\n        this.tfoot = tfoot;\n        this.comment = comment;\n        this.options = options;\n    }\n\n    public Table(List<PlainNode<Integer, Thead>> list) {\n        this(list, null);\n    }\n\n    public Table(List<PlainNode<Integer, Thead>> list,\n                 Function<E, Object[]> converter) {\n        this.thead = TreeNode.<Integer, Thead> builder(ROOT_PID).build().mount(list).flatCFS();\n        this.converter = converter;\n    }\n\n    public Table(String[] names) {\n        this(names, null);\n    }\n\n    public Table(String[] names, Function<E, Object[]> converter) {\n        List<PlainNode<Integer, Thead>> list = new ArrayList<>(names.length);\n        for (int i = 0; i < names.length; i++) {\n            list.add(new PlainNode<>(i + 1, ROOT_PID, new Thead(names[i])));\n        }\n        this.thead = TreeNode.<Integer, Thead> builder(ROOT_PID).build().mount(list).flatCFS();\n        this.converter = converter;\n    }\n\n    public Table<E> copyOfWithoutTbody() {\n        return new Table<>(thead, converter, caption, tfoot, comment, options);\n    }\n\n    public <H> Table<H> copyOfWithoutTbody(Function<H, Object[]> converter) {\n        return new Table<>(thead, converter, caption, tfoot, comment, options);\n    }\n\n    public List<FlatNode<Integer, Thead>> getThead() {\n        return thead;\n    }\n\n    public Function<E, Object[]> getConverter() {\n        return converter;\n    }\n\n    public String getCaption() {\n        return caption;\n    }\n\n    public void setCaption(String caption) {\n        this.caption = caption;\n    }\n\n    public Object[] getTfoot() {\n        return tfoot;\n    }\n\n    public void setTfoot(Object[] tfoot) {\n        this.tfoot = tfoot;\n    }\n\n    public String getComment() {\n        return comment;\n    }\n\n    public void setComment(String comment) {\n        this.comment = comment;\n    }\n\n    public Map<CellStyleOptions, Object> getOptions() {\n        return options;\n    }\n\n    public void setOptions(Map<CellStyleOptions, Object> options) {\n        this.options = options;\n    }\n\n    // -----------------------------------------------add row data\n    public void addRowsAndEnd(List<E> rows) {\n        addRows(rows);\n        toEnd();\n    }\n\n    public void addRows(List<E> rows) {\n        if (CollectionUtils.isEmpty(rows)) {\n            return;\n        }\n        try {\n            for (E row : rows) {\n                tbody.put(row);\n            }\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new RuntimeException(\"Put an element to queue failed.\", e);\n        }\n        empty = false;\n    }\n\n    public void addRowAndEnd(E row) {\n        addRow(row);\n        toEnd();\n    }\n\n    public void addRow(E row) {\n        if (row == null) {\n            return;\n        }\n        try {\n            tbody.put(row);\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new RuntimeException(\"Put an element to queue failed.\", e);\n        }\n        empty = false;\n    }\n\n    // -----------------------------------------------to end operation\n    public synchronized Table<E> toEnd() {\n        this.end = true;\n        return this;\n    }\n\n    // -----------------------------------------------data exporter use\n    boolean isEnd() {\n        return end && tbody.isEmpty();\n    }\n\n    boolean isNotEnd() {\n        return !isEnd();\n    }\n\n    boolean isEmptyTbody() {\n        return empty && isEnd();\n    }\n\n    E getRow(long timeoutMillis) throws InterruptedException {\n        return tbody.poll(timeoutMillis, TimeUnit.MILLISECONDS);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/Thead.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport java.io.Serializable;\n\n/**\n * 表头\n * \n * @author Ponfee\n */\npublic class Thead implements Serializable {\n\n    private static final long serialVersionUID = 1898674740598755648L;\n\n    private final String name;  // 列名\n    private final Tmeta tmeta;  // 列配置信息\n    private final String field; // 字段（对应类的字段）\n\n    public Thead(String name) {\n        this(name, null, null);\n    }\n\n    public Thead(String name, Tmeta tmeta, String field) {\n        this.name = name;\n        this.tmeta = tmeta;\n        this.field = field;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public Tmeta getTmeta() {\n        return tmeta;\n    }\n\n    public String getField() {\n        return field;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/export/Tmeta.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.export;\n\nimport cn.ponfee.commons.util.Colors;\n\nimport java.awt.Color;\nimport java.io.Serializable;\n\n/**\n * 每列的元数据配置\n * \n * @author Ponfee\n */\npublic class Tmeta implements Serializable {\n\n    private static final long serialVersionUID = -7653917777812920043L;\n\n    private Type type = Type.CHAR;\n    private String format;\n    private Align align = Align.LEFT;\n    private boolean nowrap = false; // only use in html [nowrap=\"nowrap\"]\n    private Color color = null;\n    private String colorHex = null;\n\n    public Tmeta() {}\n\n    public Tmeta(Type type, String format, Align align, \n                 boolean nowrap, String colorHex) {\n        this.type = type;\n        this.format = format;\n        this.align = align;\n        this.nowrap = nowrap;\n        setColor(colorHex);\n    }\n\n    public Type getType() {\n        return type;\n    }\n\n    public void setType(Type type) {\n        this.type = type;\n    }\n\n    public Align getAlign() {\n        return align;\n    }\n\n    public void setAlign(Align align) {\n        this.align = align;\n    }\n\n    public boolean isNowrap() {\n        return nowrap;\n    }\n\n    public void setNowrap(boolean nowrap) {\n        this.nowrap = nowrap;\n    }\n\n    public void setColor(Color color) {\n        this.color = color;\n        this.colorHex = Colors.toHex(color);\n    }\n\n    public void setColor(String color) {\n        this.colorHex = color;\n        this.color = Colors.fromHex(color);\n    }\n\n    public Color getColor() {\n        return color;\n    }\n\n    public String getColorHex() {\n        return colorHex;\n    }\n\n    public String getFormat() {\n        return format;\n    }\n\n    public void setFormat(String format) {\n        this.format = format;\n    }\n\n    public enum Type {\n        CHAR, NUMERIC, DATETIME\n    }\n\n    public enum Align {\n        LEFT, CENTER, RIGHT\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/CsvExtractor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract;\n\nimport cn.ponfee.commons.io.ByteOrderMarks;\nimport cn.ponfee.commons.io.CharsetDetector;\nimport cn.ponfee.commons.io.PrereadInputStream;\nimport org.apache.commons.csv.CSVFormat;\nimport org.apache.commons.csv.CSVRecord;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.ObjectUtils;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.nio.charset.Charset;\nimport java.util.function.BiConsumer;\n\n/**\n * Csv file data extractor\n * \n * @author Ponfee\n */\npublic class CsvExtractor extends DataExtractor {\n\n    private final CSVFormat csvFormat;\n    private final boolean withHeader;\n    private final int startRow; // start with 0\n    private final Charset charset;\n\n    protected CsvExtractor(ExtractableDataSource dataSource, String[] headers, \n                           CSVFormat csvFormat, int startRow, Charset charset) {\n        super(dataSource, headers);\n        this.withHeader = ArrayUtils.isNotEmpty(headers);\n        this.startRow = startRow;\n        this.charset = charset;\n        CSVFormat.Builder builder = CSVFormat.Builder.create(ObjectUtils.defaultIfNull(csvFormat, CSVFormat.DEFAULT));\n        if (this.withHeader) {\n            builder.setHeader(headers);\n        }\n        this.csvFormat = builder.build();\n    }\n\n    @Override\n    public void extract(BiConsumer<Integer, String[]> processor) throws IOException {\n        PrereadInputStream bris = new PrereadInputStream(\n            super.dataSource.asInputStream(), CharsetDetector.DEFAULT_DETECT_LENGTH\n        );\n\n        // 检测文件编码\n        Charset encoding = this.charset != null \n                         ? this.charset \n                         : CharsetDetector.detect(bris.heads());\n\n        // 检查是否有BOM\n        ByteOrderMarks bom = ByteOrderMarks.of(encoding, bris.heads());\n        if (bom != null) {\n            bris.skip(bom.length());\n        }\n\n        // Use BOMInputStream maybe occur error(dead loop): UTF-16LE, UTF-16BE,\n        try (Reader reader = new InputStreamReader(/*new BOMInputStream(bris)*/bris, encoding)) {\n            int columnSize = this.withHeader ? this.headers.length : 0;\n            Iterable<CSVRecord> records = this.csvFormat.parse(reader);\n            int i = 0, j, n, start = this.startRow;\n            String[] data;\n            for (CSVRecord record : records) {\n                if (super.end) {\n                    break;\n                }\n                if (start > 0) {\n                    start--;\n                    continue;\n                }\n                if (!this.withHeader && i == 0) {\n                    columnSize = record.size(); // 不指定表头，则取第一行数据为表头\n                }\n\n                n = record.size();\n                data = new String[columnSize];\n                for (j = 0; j < n && j < columnSize; j++) {\n                    data[j] = record.get(j);\n                }\n                for (; j < columnSize; j++) {\n                    data[j] = null;\n                }\n                if (isNotEmpty(data)) {\n                    processor.accept(i++, data);\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/DataExtractor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract;\n\nimport cn.ponfee.commons.util.Holder;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.function.BiConsumer;\nimport java.util.function.BiFunction;\nimport java.util.function.Consumer;\n\n/**\n * The file data extractor\n * \n * @author Ponfee\n */\npublic abstract class DataExtractor {\n\n    protected final ExtractableDataSource dataSource;\n    protected final String[] headers;\n    protected volatile boolean end = false;\n\n    protected DataExtractor(ExtractableDataSource dataSource, String[] headers) {\n        this.dataSource = dataSource;\n        this.headers = headers;\n    }\n\n    public abstract void extract(BiConsumer<Integer, String[]> processor) throws IOException;\n\n    public final List<String[]> extract() throws IOException {\n        List<String[]> list = new LinkedList<>();\n        this.extract((rowNumber, data) -> list.add(data));\n        return list;\n    }\n\n    /**\n     * Extracts specified count of top rows\n     * \n     * @param count the top rows count\n     * @return a list\n     * @throws IOException if occur io error\n     */\n    public final List<String[]> extract(int count) throws IOException {\n        List<String[]> result = new ArrayList<>(count);\n        this.extract((rowNum, row) -> {\n            if (rowNum >= count) {\n                this.end = true;\n                return;\n            }\n            result.add(row);\n        });\n        return result;\n    }\n\n    public final void extract(int batchSize, Consumer<List<String[]>> action) throws IOException {\n        Holder<List<String[]>> holder = Holder.of(new ArrayList<>(batchSize));\n        this.extract((rowNumber, data) -> {\n            List<String[]> list = holder.get();\n            list.add(data);\n            if (list.size() == batchSize) {\n                action.accept(list);\n                holder.set(new ArrayList<>(batchSize));\n            }\n        });\n        if (CollectionUtils.isNotEmpty(holder.get())) {\n            action.accept(holder.get());\n        }\n    }\n\n    /**\n     * 验证\n     * \n     * @param validator\n     * @return\n     * @throws IOException\n     */\n    public final ValidateResult verify(BiFunction<Integer, String[], String> validator) \n        throws IOException {\n        ValidateResult result = new ValidateResult();\n        this.extract((rowNumber, data) -> {\n            String error = validator.apply(rowNumber, data);\n            if (StringUtils.isBlank(error)) {\n                result.addData(data);\n            } else {\n                result.addError(\n                    new StringBuilder(error.length() + 12)\n                        .append(\"第[\").append(rowNumber + 1) // rowNumber start 0\n                        .append(\"]行错误：\").append(error).toString()\n                );\n            }\n        });\n        return result;\n    }\n\n    // ---------------------------------------------------------------------------protected methods\n    protected boolean isNotEmpty(String[] data) {\n        if (data == null || data.length == 0) {\n            return false;\n        }\n\n        for (String str : data) {\n            if (StringUtils.isNotBlank(str)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/DataExtractorBuilder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract;\n\nimport cn.ponfee.commons.extract.ExcelExtractor.ExcelType;\nimport cn.ponfee.commons.extract.streaming.StreamingExcelExtractor;\nimport cn.ponfee.commons.http.ContentType;\nimport com.google.common.collect.ImmutableList;\nimport org.apache.commons.csv.CSVFormat;\nimport org.apache.commons.io.FilenameUtils;\nimport org.apache.commons.lang3.EnumUtils;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.util.List;\n\n/**\n * The data extractor builder, facade operator\n * \n * @author Ponfee\n */\npublic class DataExtractorBuilder {\n\n    private static final List<String> EXCEL_EXTENSION = ImmutableList.of(\"xlsx\", \"xls\");\n    private static final List<String> CSV_EXTENSION = ImmutableList.of(\"csv\", \"log\", \"txt\");\n\n    private final Object dataSource; // only support such type as File, InputStream\n    private final String fileName;\n    private final String contentType;\n    private String[] headers;\n    private int startRow = 0; // start with 0\n\n    // ------------------------------------------excel config\n    private int sheetIndex = 0; // excel work book sheet index: start with 0\n    private boolean streaming = true; // excel whether streaming read, default true\n\n    // ------------------------------------------csv config\n    private CSVFormat csvFormat; // csv format\n    private Charset charset; // csv file encoding\n\n    private DataExtractorBuilder(Object dataSource, String fileName, \n                                 String contentType) {\n        this.dataSource = dataSource;\n        this.fileName = fileName;\n        this.contentType = contentType;\n    }\n\n    public static DataExtractorBuilder newBuilder(InputStream dataSource, \n                                                  String fileName, String contentType) {\n        return new DataExtractorBuilder(dataSource, fileName, contentType);\n    }\n\n    public static DataExtractorBuilder newBuilder(String path) {\n        return newBuilder(new File(path));\n    }\n\n    public static DataExtractorBuilder newBuilder(File dataSource) {\n        String fileName = dataSource.getName();\n        return new DataExtractorBuilder(\n            dataSource, fileName, FilenameUtils.getExtension(fileName)\n        );\n    }\n\n    public DataExtractorBuilder headers(String[] headers) {\n        this.headers = headers;\n        return this;\n    }\n\n    public DataExtractorBuilder startRow(int startRow) {\n        this.startRow = startRow;\n        return this;\n    }\n\n    public DataExtractorBuilder sheetIndex(int sheetIndex) {\n        this.sheetIndex = sheetIndex;\n        return this;\n    }\n\n    public DataExtractorBuilder streaming(boolean streaming) {\n        this.streaming = streaming;\n        return this;\n    }\n\n    public DataExtractorBuilder csvFormat(CSVFormat csvFormat) {\n        this.csvFormat = csvFormat;\n        return this;\n    }\n\n    public DataExtractorBuilder charset(Charset charset) {\n        this.charset = charset;\n        return this;\n    }\n\n    public DataExtractor build() {\n        String extension = FilenameUtils.getExtension(fileName).toLowerCase();\n        if (   ContentType.TEXT_PLAIN.value().equalsIgnoreCase(contentType)\n            || CSV_EXTENSION.contains(extension)\n        ) {\n            // csv, txt文本格式数据\n            ExtractableDataSource ds = new ExtractableDataSource(dataSource);\n            return new CsvExtractor(ds, headers, csvFormat, startRow, charset);\n        } else if (EXCEL_EXTENSION.contains(extension)) {\n            // Content-Type\n            // xlsx: application/vnd.openxmlformats-officedocument.wordprocessingml.document\n            //       application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\n            //\n            // xls: application/vnd.ms-excel\n            //      application/x-xls\n            ExtractableDataSource ds = new ExtractableDataSource(dataSource);\n            ExcelType type = EnumUtils.getEnumIgnoreCase(ExcelType.class, extension);\n            return streaming \n                   ? new StreamingExcelExtractor(ds, headers, startRow, type, sheetIndex)\n                   : new ExcelExtractor(ds, headers, startRow, type, sheetIndex);\n        } else {\n            throw new RuntimeException(\"File content type not supported: \" + fileName);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/ExcelExtractor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract;\n\nimport cn.ponfee.commons.date.Dates;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.poi.ss.usermodel.*;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Iterator;\nimport java.util.function.BiConsumer;\n\nimport static org.apache.poi.ss.usermodel.Row.MissingCellPolicy.RETURN_NULL_AND_BLANK;\n\n/**\n * Excel file data extractor\n * \n * <a href=\"https://www.cnblogs.com/cksvsaaa/p/7280261.html\">xlsx big file import</a>\n * \n * @author Ponfee\n */\npublic class ExcelExtractor extends DataExtractor {\n\n    protected final ExcelType type;\n    protected final int sheetIndex; // start with 0\n    private final int startRow;     // start with 0\n\n    protected ExcelExtractor(ExtractableDataSource dataSource, String[] headers, \n                             int startRow, ExcelType type, int sheetIndex) {\n        super(dataSource, headers);\n        this.startRow = startRow;\n        this.type = type;\n        this.sheetIndex = sheetIndex;\n    }\n\n    @Override\n    public final void extract(BiConsumer<Integer, String[]> processor) throws IOException {\n        try (ExtractableDataSource ds = dataSource; Workbook workbook = createWorkbook(ds)) {\n            extract(workbook, processor);\n        }\n    }\n\n    protected Workbook createWorkbook(ExtractableDataSource dataSource) throws IOException {\n        Object ds = dataSource.getDataSource();\n        if (ds instanceof File) {\n            return WorkbookFactory.create((File) ds);\n        } else {\n            return WorkbookFactory.create((InputStream) ds);\n        }\n    }\n\n    private void extract(Workbook workbook, BiConsumer<Integer, String[]> processor) {\n        boolean specHeaders; int columnSize;\n        if (ArrayUtils.isNotEmpty(headers)) {\n            specHeaders = true;\n            columnSize = this.headers.length;\n        } else {\n            specHeaders = false;\n            columnSize = 0;\n        }\n\n        Row row; String[] data;\n        // sheet.getPhysicalNumberOfRows()\n        Iterator<Row> iter = workbook.getSheetAt(sheetIndex).iterator();\n        for (int i = 0, k = 0, m, j; iter.hasNext(); i++) {\n            if (super.end) {\n                break;\n            }\n            row = iter.next(); // row = sheet.getRow(i);\n            if (row == null || i < startRow) {\n                continue;\n            }\n\n            if (!specHeaders && i == startRow) {\n                columnSize = row.getLastCellNum(); // 不指定表头则以开始行为表头\n            }\n\n            data = new String[columnSize];\n            for (m = row.getLastCellNum(), j = 0; j <= m && j < columnSize; j++) {\n                // Missing cells are returned as null, Blank cells are returned as normal\n                data[j] = getStringCellValue(row.getCell(j, RETURN_NULL_AND_BLANK));\n            }\n            for (; j < columnSize; j++) {\n                data[j] = null; // padding\n            }\n            if (isNotEmpty(data)) {\n                processor.accept(k++, data);\n            }\n        }\n    }\n\n    /**\n     * 获取单元格的值\n     * \n     * @param cell\n     * @return\n     */\n    private static String getStringCellValue(Cell cell) {\n        if (cell == null) {\n            return null;\n        }\n\n        switch (cell.getCellType()) {\n            case NUMERIC:\n                return getNumericAsString(cell);\n\n            case FORMULA:\n                try {\n                    return getNumericAsString(cell);\n                } catch (Exception e) {\n                    return cell.getRichStringCellValue().getString();\n                }\n\n            case STRING:\n                return cell.getStringCellValue();\n\n            case BOOLEAN:\n                return Boolean.toString(cell.getBooleanCellValue());\n\n            case ERROR: // 错误\n                return \"Error: \" + cell.getErrorCellValue();\n\n            default:\n                return cell.getRichStringCellValue().getString();\n        }\n    }\n\n    private static String getNumericAsString(Cell cell) {\n        return (DateUtil.isCellDateFormatted(cell) || DateUtil.isCellInternalDateFormatted(cell))\n             ? Dates.format(cell.getDateCellValue()) : String.valueOf(cell.getNumericCellValue());\n    }\n\n    public enum ExcelType {\n        XLS, XLSX\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/ExtractableDataSource.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract;\n\nimport javax.annotation.Nonnull;\nimport java.io.*;\n\n/**\n * The extractable DataSource is a Inputstream or File\n * \n * @author Ponfee\n */\npublic class ExtractableDataSource implements Closeable {\n\n    private final Object dataSource;\n\n    public ExtractableDataSource(@Nonnull Object dataSource) {\n        if (!(dataSource instanceof InputStream || dataSource instanceof File)) {\n            throw new IllegalArgumentException(\n                \"Invalid datasource '\" + dataSource.getClass().getName() + \"', only support File or InputStream.\"\n            );\n        }\n        this.dataSource = dataSource;\n    }\n\n    @Override\n    public void close() throws IOException {\n        if (dataSource instanceof InputStream) {\n            ((InputStream) dataSource).close();\n        }\n    }\n\n    public Object getDataSource() {\n        return dataSource;\n    }\n\n    public InputStream asInputStream() throws IOException {\n        return dataSource instanceof File\n             ? new FileInputStream((File) dataSource)\n             : (InputStream) dataSource;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/ValidateResult.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract;\n\nimport cn.ponfee.commons.io.Files;\nimport com.google.common.collect.Lists;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.List;\n\n/**\n * Validate result\n * \n * @author Ponfee\n */\npublic class ValidateResult {\n\n    private final List<String[]> data = Lists.newLinkedList();\n    private final List<String> errors = Lists.newLinkedList();\n\n    public boolean hasErrors() {\n        return !errors.isEmpty();\n    }\n\n    public boolean isEmpty() {\n        return data.isEmpty();\n    }\n\n    public String getErrorsAsString() {\n        return StringUtils.join(errors, Files.UNIX_LINE_SEPARATOR);\n    }\n\n    public List<String[]> getData() {\n        return data;\n    }\n\n    public List<String> getErrors() {\n        return errors;\n    }\n\n    public void addData(String[] obj) {\n        this.data.add(obj);\n    }\n\n    public void addError(String error) {\n        this.errors.add(error);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/streaming/StreamingExcelExtractor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract.streaming;\n\nimport cn.ponfee.commons.extract.ExcelExtractor;\nimport cn.ponfee.commons.extract.ExtractableDataSource;\nimport cn.ponfee.commons.extract.streaming.xls.HSSFStreamingReader;\nimport com.monitorjbl.xlsx.StreamingReader;\nimport org.apache.poi.ss.usermodel.Workbook;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.util.concurrent.Executor;\n\n/**\n * Excel file data extractor based streaming\n * \n * 在打开一本工作簿时，不管是一个.xls HSSFWorkbook，还是一个.xlsx XSSFWorkbook，\n * 工作簿都可以从文件或InputStream中加载。使用File对象可以降低内存消耗，\n * 而InputStream则需要更多的内存，因为它必须缓冲整个文件。\n * \n *  // Use a file\n *  Workbook wb = WorkbookFactory.create(new File(\"MyExcel.xls\"));\n *  \n *  // Use an InputStream, needs more memory\n *  Workbook wb = WorkbookFactory.create(new FileInputStream(\"MyExcel.xlsx\"));\n *  \n *  \n *  =======================================================================\n *  // HSSFWorkbook, File\n *  NPOIFSFileSystem fs = new NPOIFSFileSystem(new File(\"file.xls\"));\n *  HSSFWorkbook wb = new HSSFWorkbook(fs.getRoot(), true);\n *  ....\n *  fs.close();\n *  \n *  // HSSFWorkbook, InputStream, needs more memory\n *  NPOIFSFileSystem fs = new NPOIFSFileSystem(myInputStream);\n *  HSSFWorkbook wb = new HSSFWorkbook(fs.getRoot(), true);\n *  \n *  \n *  =======================================================================\n *  // XSSFWorkbook, File\n *  OPCPackage pkg = OPCPackage.open(new File(\"file.xlsx\"));\n *  XSSFWorkbook wb = new XSSFWorkbook(pkg);\n *  ....\n *  pkg.close();\n *  \n *  // XSSFWorkbook, InputStream, needs more memory\n *  OPCPackage pkg = OPCPackage.open(myInputStream);\n *  XSSFWorkbook wb = new XSSFWorkbook(pkg);\n *  ....\n *  pkg.close();\n * \n * \n * https://blog.csdn.net/zl_momomo/article/details/80703533\n * http://poi.apache.org/components/spreadsheet/how-to.html\n * https://github.com/monitorjbl/excel-streaming-reader\n * \n * \n * https://github.com/alibaba/easyexcel\n * https://www.jianshu.com/p/cb9cd9965a63\n * \n * @author Ponfee\n */\npublic class StreamingExcelExtractor extends ExcelExtractor {\n\n    private final Executor executor;\n\n    public StreamingExcelExtractor(ExtractableDataSource dataSource, String[] headers, \n                                   int startRow, ExcelType type) {\n        this(dataSource, headers, startRow, type, 0, null);\n    }\n\n    public StreamingExcelExtractor(ExtractableDataSource dataSource, String[] headers,\n                                   int startRow, ExcelType type, int sheetIndex) {\n        this(dataSource, headers, startRow, type, sheetIndex, null);\n    }\n\n    public StreamingExcelExtractor(ExtractableDataSource dataSource, String[] headers, \n                                   int startRow, ExcelType type, int sheetIndex, Executor executor) {\n        super(dataSource, headers, startRow, type, sheetIndex);\n        this.executor = executor;\n    }\n\n    @Override\n    protected Workbook createWorkbook(ExtractableDataSource dataSource) {\n        Object ds = dataSource.getDataSource();\n        switch (type) {\n            case XLS:\n                HSSFStreamingReader reader = HSSFStreamingReader.create(200, sheetIndex);\n                return ds instanceof File \n                     ? reader.open((File) ds, executor) \n                     : reader.open((InputStream) ds, executor);\n            case XLSX:\n                // only support xlsx\n                StreamingReader.Builder builder = StreamingReader.builder()\n                    .rowCacheSize(50) // 缓存到内存中的行数，默认是10\n                    .bufferSize(4096); // 读取资源时，缓存到内存的字节大小，默认是1024\n                return ds instanceof File\n                     ? builder.open((File) ds) \n                     : builder.open((InputStream) ds);\n            default:\n                throw new RuntimeException(\"Unknown excel type: \" + type);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingCell.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract.streaming.xls;\n\nimport org.apache.poi.ss.formula.FormulaParseException;\nimport org.apache.poi.ss.usermodel.*;\nimport org.apache.poi.ss.util.CellAddress;\nimport org.apache.poi.ss.util.CellRangeAddress;\n\nimport java.time.LocalDateTime;\nimport java.util.Calendar;\nimport java.util.Date;\n\n/**\n * The version for 2003 or early XSL excel file streaming reader excel cell\n *\n * @author Ponfee\n */\npublic class HSSFStreamingCell implements Cell {\n\n    private final String value;\n\n    public HSSFStreamingCell(String value) {\n        this.value = value;\n    }\n\n    @Override\n    public String getStringCellValue() {\n        return this.value;\n    }\n\n    @Override\n    public CellType getCellType() {\n        return CellType.STRING;\n    }\n\n    // ----------------------------------------------unsupported operation\n    @Override @Deprecated\n    public int getColumnIndex() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int getRowIndex() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Sheet getSheet() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Row getRow() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellType(CellType cellType) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public CellType getCachedFormulaResultType() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellValue(double value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellValue(Date value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellValue(Calendar value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellValue(RichTextString value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellValue(String value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellFormula(String formula) throws FormulaParseException {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public String getCellFormula() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public double getNumericCellValue() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Date getDateCellValue() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public RichTextString getRichStringCellValue() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellValue(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellErrorValue(byte value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public boolean getBooleanCellValue() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public byte getErrorCellValue() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellStyle(CellStyle style) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public CellStyle getCellStyle() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setAsActiveCell() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public CellAddress getAddress() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellComment(Comment comment) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Comment getCellComment() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void removeCellComment() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Hyperlink getHyperlink() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setHyperlink(Hyperlink link) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void removeHyperlink() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public CellRangeAddress getArrayFormulaRange() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public boolean isPartOfArrayFormulaGroup() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void removeFormula() throws IllegalStateException {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setBlank() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellValue(LocalDateTime value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public LocalDateTime getLocalDateTimeCellValue() {\n        throw new UnsupportedOperationException();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingReader.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract.streaming.xls;\n\nimport com.google.common.base.Preconditions;\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.InputStream;\nimport java.util.concurrent.Executor;\n\n/**\n * The version for 2003 or early XSL excel file \n * streaming reader\n * \n * excel reader\n * \n * @author Ponfee\n */\npublic class HSSFStreamingReader {\n\n    private int rowCacheSize = 0; // if less 0 then 0(SynchronousQueue)\n    private int[] sheetIndexs;\n    private String[] sheetNames;\n\n    private HSSFStreamingReader() {}\n\n    /**\n     * Reads all sheet\n     * \n     * @return HSSFStreamingReader instance\n     */\n    public static HSSFStreamingReader create() {\n        return new HSSFStreamingReader();\n    }\n\n    /**\n     * Reads spec sheet that is in sheet index at int array\n     * \n     * @param rowCacheSize  the size of read row count in memory\n     * @param sheetIndexs sheet index at int array\n     * @return HSSFStreamingReader instance\n     */\n    public static HSSFStreamingReader create(int rowCacheSize, int... sheetIndexs) {\n        Preconditions.checkArgument(rowCacheSize > 0);\n        Preconditions.checkArgument(ArrayUtils.isNotEmpty(sheetIndexs));\n        HSSFStreamingReader reader = new HSSFStreamingReader();\n        reader.rowCacheSize = rowCacheSize;\n        reader.sheetIndexs = sheetIndexs;\n        return reader;\n    }\n\n    /**\n     * Reads spec sheet that is in sheet name at string array\n     * \n     * @param rowCacheSize  the size of read row count in memory\n     * @return HSSFStreamingReader instance\n     */\n    public static HSSFStreamingReader create(int rowCacheSize, String... sheetNames) {\n        Preconditions.checkArgument(rowCacheSize > 0);\n        Preconditions.checkArgument(ArrayUtils.isNotEmpty(sheetNames));\n        HSSFStreamingReader reader = new HSSFStreamingReader();\n        reader.rowCacheSize = rowCacheSize;\n        reader.sheetNames = sheetNames;\n        return reader;\n    }\n\n    public HSSFStreamingWorkbook open(InputStream input, Executor executor) {\n        return new HSSFStreamingWorkbook(\n            input, rowCacheSize, sheetIndexs, sheetNames, executor\n        );\n    }\n\n    public HSSFStreamingWorkbook open(File file, Executor executor) {\n        try {\n            return open(new FileInputStream(file), executor);\n        } catch (FileNotFoundException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public HSSFStreamingWorkbook open(String filePath, Executor executor) {\n        return open(new File(filePath), executor);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingRow.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract.streaming.xls;\n\nimport cn.ponfee.commons.collect.Collects;\nimport org.apache.poi.ss.usermodel.*;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n/**\n * The version for 2003 or early XSL excel file \n * streaming reader\n * \n * excel row\n * \n * @author Ponfee\n */\npublic class HSSFStreamingRow implements Row {\n\n    private final List<Cell> cells = new ArrayList<>();\n    private final int rowNum; // excel row number\n    private final int rowOrder; // excel row order\n\n    public HSSFStreamingRow(int rowNum, int rowOrder) {\n        this.rowNum = rowNum;\n        this.rowOrder = rowOrder;\n    }\n\n    @Override\n    public Iterator<Cell> iterator() {\n        return this.cells.iterator();\n    }\n\n    @Override\n    public void removeCell(Cell cell) {\n        this.cells.remove(cell);\n    }\n\n    @Override\n    public int getRowNum() {\n        return this.rowNum;\n    }\n\n    public int getRowOrder() {\n        return this.rowOrder;\n    }\n\n    @Override\n    public Cell getCell(int cellnum) {\n        return this.cells.get(cellnum);\n    }\n\n    @Override\n    public Cell getCell(int cellnum, MissingCellPolicy policy) {\n        return getCell(cellnum);\n    }\n\n    @Override\n    public short getLastCellNum() {\n        return (short) (cells.size() - 1);\n    }\n\n    @Override\n    public int getPhysicalNumberOfCells() {\n        return this.cells.size();\n    }\n\n    @Override\n    public Iterator<Cell> cellIterator() {\n        return iterator();\n    }\n\n    public void putCell(int index, Cell cell) {\n        Collects.set(this.cells, index, cell);\n    }\n\n    public boolean isEmpty() {\n        return this.cells.isEmpty();\n    }\n\n    // ----------------------------------------------unsupported operation\n    @Override\n    public Sheet getSheet() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Cell createCell(int column) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Cell createCell(int column, CellType type) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRowNum(int rowNum) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public short getFirstCellNum() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setHeight(short height) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setZeroHeight(boolean zHeight) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getZeroHeight() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setHeightInPoints(float height) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public short getHeight() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public float getHeightInPoints() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isFormatted() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public CellStyle getRowStyle() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRowStyle(CellStyle style) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int getOutlineLevel() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void shiftCellsRight(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void shiftCellsLeft(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) {\n        throw new UnsupportedOperationException();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingSheet.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract.streaming.xls;\n\nimport org.apache.poi.ss.usermodel.*;\nimport org.apache.poi.ss.util.CellAddress;\nimport org.apache.poi.ss.util.CellRangeAddress;\nimport org.apache.poi.ss.util.PaneInformation;\n\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.TimeUnit;\n\nimport static cn.ponfee.commons.extract.streaming.xls.HSSFStreamingWorkbook.AWAIT_MILLIS;\n/**\n * The version for 2003 or early XSL excel file \n * streaming reader\n * \n * excel sheet\n * \n * @author Ponfee\n */\npublic class HSSFStreamingSheet implements Sheet {\n\n    private final int index;\n    private final String name;\n    private final boolean discard;\n    private final BlockingQueue<Row> rows;\n    private final RowsIterator iterator;\n    private volatile boolean end = false;\n\n    public HSSFStreamingSheet(int index, String name, \n                              boolean discard, int rowCacheSize) {\n        this.index = index;\n        this.name = name;\n        this.discard = discard;\n        if (discard) {\n            this.iterator = null;\n            this.rows = null;\n            toEnd();\n        } else {\n            this.iterator = new RowsIterator(this);\n            this.rows = rowCacheSize > 0\n                        ? new LinkedBlockingQueue<>(rowCacheSize)\n                        : new SynchronousQueue<>(); // new LinkedBlockingQueue<>();\n        }\n    }\n\n    @Override\n    public Iterator<Row> iterator() {\n        return this.iterator;\n    }\n\n    @Override\n    public Iterator<Row> rowIterator() {\n        return this.iterator;\n    }\n\n    @Override\n    public String getSheetName() {\n        return this.name;\n    }\n\n    public int getSheetIndex() {\n        return this.index;\n    }\n\n    public boolean isDiscard() {\n        return discard;\n    }\n\n    public int getCacheRowCount() {\n        return rows == null ? 0 : rows.size();\n    }\n\n    void toEnd() {\n        end = true;\n    }\n\n    private boolean isEnd() {\n        return end && rows.isEmpty();\n    }\n\n    void putRow(Row row) throws InterruptedException {\n        this.rows.put(row);\n    }\n\n    private static class RowsIterator implements Iterator<Row> {\n        final HSSFStreamingSheet sheet;\n        Row currentRow;\n\n        RowsIterator(HSSFStreamingSheet sheet) {\n            this.sheet = sheet;\n        }\n\n        @Override\n        public boolean hasNext() {\n            try {\n                while (!sheet.isEnd()) {\n                    if (   currentRow == null\n                        && (currentRow = sheet.rows.poll(AWAIT_MILLIS, TimeUnit.MILLISECONDS)) != null\n                    ) {\n                        return true;\n                    }\n                }\n                return false;\n            } catch (InterruptedException e) {\n                Thread.currentThread().interrupt();\n                throw new RuntimeException(e);\n            }\n        }\n\n        @Override\n        public Row next() {\n            Row res = currentRow;\n            currentRow = null;\n            return res;\n        }\n    }\n\n    // ----------------------------------------------unsupported operation\n    @Override\n    public Workbook getWorkbook() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Row createRow(int rownum) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void removeRow(Row row) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Row getRow(int rownum) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int getPhysicalNumberOfRows() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int getFirstRowNum() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int getLastRowNum() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setColumnHidden(int columnIndex, boolean hidden) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isColumnHidden(int columnIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRightToLeft(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isRightToLeft() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setColumnWidth(int columnIndex, int width) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int getColumnWidth(int columnIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public float getColumnWidthInPixels(int columnIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setDefaultColumnWidth(int width) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int getDefaultColumnWidth() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public short getDefaultRowHeight() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public float getDefaultRowHeightInPoints() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setDefaultRowHeight(short height) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setDefaultRowHeightInPoints(float height) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public CellStyle getColumnStyle(int column) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int addMergedRegion(CellRangeAddress region) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int addMergedRegionUnsafe(CellRangeAddress region) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void validateMergedRegions() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setVerticallyCenter(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setHorizontallyCenter(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getHorizontallyCenter() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getVerticallyCenter() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void removeMergedRegion(int index) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void removeMergedRegions(Collection<Integer> indices) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int getNumMergedRegions() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public CellRangeAddress getMergedRegion(int index) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public List<CellRangeAddress> getMergedRegions() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setForceFormulaRecalculation(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getForceFormulaRecalculation() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setAutobreaks(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setDisplayGuts(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setDisplayZeros(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isDisplayZeros() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setFitToPage(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRowSumsBelow(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRowSumsRight(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getAutobreaks() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getDisplayGuts() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getFitToPage() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getRowSumsBelow() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getRowSumsRight() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isPrintGridlines() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setPrintGridlines(boolean show) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isPrintRowAndColumnHeadings() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setPrintRowAndColumnHeadings(boolean show) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public PrintSetup getPrintSetup() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Header getHeader() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Footer getFooter() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setSelected(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public double getMargin(short margin) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public double getMargin(PageMargin margin) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setMargin(short margin, double size) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setMargin(PageMargin margin, double size) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getProtect() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void protectSheet(String password) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean getScenarioProtect() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setZoom(int scale) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public short getTopRow() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public short getLeftCol() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void showInPane(int toprow, int leftcol) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void shiftRows(int startRow, int endRow, int n) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void shiftRows(int startRow, int endRow, int n,\n        boolean copyRowHeight, boolean resetOriginalRowHeight) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void shiftColumns(int startColumn, int endColumn, int n) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void createFreezePane(int colSplit, int rowSplit,\n        int leftmostColumn, int topRow) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void createFreezePane(int colSplit, int rowSplit) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void createSplitPane(int xSplitPos, int ySplitPos,\n        int leftmostColumn, int topRow, int activePane) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, PaneType activePane) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public PaneInformation getPaneInformation() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setDisplayGridlines(boolean show) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isDisplayGridlines() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setDisplayFormulas(boolean show) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isDisplayFormulas() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setDisplayRowColHeadings(boolean show) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isDisplayRowColHeadings() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRowBreak(int row) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isRowBroken(int row) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void removeRowBreak(int row) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int[] getRowBreaks() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int[] getColumnBreaks() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setColumnBreak(int column) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isColumnBroken(int column) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void removeColumnBreak(int column) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setColumnGroupCollapsed(int columnNumber, boolean collapsed) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void groupColumn(int fromColumn, int toColumn) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void ungroupColumn(int fromColumn, int toColumn) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void groupRow(int fromRow, int toRow) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void ungroupRow(int fromRow, int toRow) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRowGroupCollapsed(int row, boolean collapse) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setDefaultColumnStyle(int column, CellStyle style) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void autoSizeColumn(int column) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void autoSizeColumn(int column, boolean useMergedCells) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Comment getCellComment(CellAddress ref) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Map<CellAddress, ? extends Comment> getCellComments() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Drawing<?> getDrawingPatriarch() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Drawing<?> createDrawingPatriarch() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean isSelected() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public CellRange<? extends Cell> setArrayFormula(String formula,\n        CellRangeAddress range) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public CellRange<? extends Cell> removeArrayFormula(Cell cell) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public DataValidationHelper getDataValidationHelper() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public List<? extends DataValidation> getDataValidations() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void addValidationData(DataValidation dataValidation) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public AutoFilter setAutoFilter(CellRangeAddress range) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public SheetConditionalFormatting getSheetConditionalFormatting() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public CellRangeAddress getRepeatingRows() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public CellRangeAddress getRepeatingColumns() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRepeatingRows(CellRangeAddress rowRangeRef) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRepeatingColumns(CellRangeAddress columnRangeRef) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int getColumnOutlineLevel(int columnIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Hyperlink getHyperlink(int row, int column) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Hyperlink getHyperlink(CellAddress addr) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public List<? extends Hyperlink> getHyperlinkList() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public CellAddress getActiveCell() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setActiveCell(CellAddress address) {\n        throw new UnsupportedOperationException();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/extract/streaming/xls/HSSFStreamingWorkbook.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.extract.streaming.xls;\n\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.poi.hssf.eventusermodel.*;\nimport org.apache.poi.hssf.record.*;\nimport org.apache.poi.poifs.filesystem.DocumentInputStream;\nimport org.apache.poi.poifs.filesystem.POIFSFileSystem;\nimport org.apache.poi.ss.SpreadsheetVersion;\nimport org.apache.poi.ss.formula.EvaluationWorkbook;\nimport org.apache.poi.ss.formula.udf.UDFFinder;\nimport org.apache.poi.ss.usermodel.*;\nimport org.apache.poi.ss.usermodel.Row.MissingCellPolicy;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.concurrent.Executor;\n\n/**\n * The version for 2003 or early XSL excel file streaming reader excel workbook\n *\n * @author Ponfee\n */\npublic class HSSFStreamingWorkbook implements Workbook, Closeable {\n\n    public static final int AWAIT_MILLIS = 47;\n\n    private volatile boolean allSheetReadied = false;\n    private final List<Sheet> sheets = new ArrayList<>();\n\n    public HSSFStreamingWorkbook(InputStream input, int rowCacheSize,\n                                 int[] sheetIndexs, String[] sheetNames,\n                                 Executor executor) {\n        executor.execute(new AsyncHSSFReader(\n            rowCacheSize, sheetIndexs, sheetNames, input\n        ));\n    }\n\n    @Override\n    public Iterator<Sheet> iterator() {\n        awaitReadAllSheet();\n        return sheets.iterator();\n    }\n\n    @Override\n    public Iterator<Sheet> sheetIterator() {\n        awaitReadAllSheet();\n        return iterator();\n    }\n\n    @Override\n    public String getSheetName(int sheet) {\n        awaitReadAllSheet();\n        return sheets.get(sheet).getSheetName();\n    }\n\n    @Override\n    public int getSheetIndex(String name) {\n        awaitReadAllSheet();\n        for (int i = 0; i < sheets.size(); i++) {\n            if (sheets.get(i).getSheetName().equals(name)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    @Override\n    public int getSheetIndex(Sheet sheet) {\n        awaitReadAllSheet();\n        for (int i = 0; i < sheets.size(); i++) {\n            if (sheets.get(i) == sheet) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    @Override\n    public int getNumberOfSheets() {\n        awaitReadAllSheet();\n        return sheets.size();\n    }\n\n    @Override\n    public Sheet getSheetAt(int index) {\n        awaitReadAllSheet();\n        return sheets.size() > index ? sheets.get(index) : null;\n    }\n\n    @Override\n    public Sheet getSheet(String name) {\n        awaitReadAllSheet();\n        for (Sheet sheet : sheets) {\n            if (sheet.getSheetName().equals(name)) {\n                return sheet;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public void close() {\n        // do nothing\n    }\n\n    private void awaitReadAllSheet() {\n        try {\n            while (!allSheetReadied) {\n                Thread.sleep(AWAIT_MILLIS);\n            }\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Runs in alone thread\n     */\n    private class AsyncHSSFReader implements HSSFListener, Runnable {\n        private final InputStream input;\n        private final int rowCacheSize;\n        private final int[] sheetIndexs;\n        private final String[] sheetNames;\n\n        private int currentSheetIndex = -1; // start with 0\n        private HSSFStreamingSheet currentSheet;\n\n        private int currentRowNumber = -1; // start with 0\n        private int currentRowOrder = -1; // start with 0\n        private HSSFStreamingRow currentRow;\n\n\n        private SSTRecord sstrec;\n        private FormatTrackingHSSFListener formatListener;\n\n        private AsyncHSSFReader(int rowCacheSize, int[] sheetIndexs,\n                                String[] sheetNames, InputStream input) {\n            this.rowCacheSize = rowCacheSize;\n            this.sheetIndexs = sheetIndexs;\n            this.sheetNames = sheetNames;\n            this.input = input;\n        }\n\n        @Override\n        public void run() {\n            try (InputStream steam = input;\n                 POIFSFileSystem poi = new POIFSFileSystem(steam);\n                 DocumentInputStream doc = poi.createDocumentInputStream(\"Workbook\")\n            ) {\n                HSSFRequest request = new HSSFRequest();\n                formatListener = new FormatTrackingHSSFListener(\n                    new MissingRecordAwareHSSFListener(this)\n                );\n                request.addListenerForAllRecords(formatListener);\n                new HSSFEventFactory().processEvents(request, doc);\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            } finally {\n                this.endRead(); // read end of xls file\n            }\n        }\n\n        /**\n         * This method listens for incoming records and handles them as required.\n         *\n         * @param record the record that was found while reading.\n         */\n        @Override\n        public void processRecord(Record record) {\n            if (record instanceof BOFRecord) { // beginning of a sheet or the workbook\n                if (((BOFRecord) record).getType() == BOFRecord.TYPE_WORKSHEET) { // beginning a sheet\n                    allSheetReadied = true;\n                    if (currentSheet != null) {\n                        putRow(currentRow);\n                        currentSheet.toEnd();\n                    }\n                    currentRow = null;\n                    currentRowNumber = -1;\n                    currentRowOrder = -1; // reset current row\n                    currentSheet = (HSSFStreamingSheet) sheets.get(++currentSheetIndex);\n                } else {\n                    // BOFRecord.TYPE_WORKBOOK: beginning the workbook\n                    // others uncapture ...\n                }\n            } else if (record instanceof BoundSheetRecord) { // the workbook all of sheet\n                BoundSheetRecord bsr = (BoundSheetRecord) record;\n                int sstIdx = sheets.size();\n                sheets.add(new HSSFStreamingSheet(\n                        sstIdx, bsr.getSheetname(), isDiscard(sstIdx, bsr.getSheetname()), rowCacheSize\n                ));\n            } else if (record instanceof SSTRecord) { // store a array of unique strings used in Excel.\n                sstrec = (SSTRecord) record;\n            } else if (record instanceof CellRecord) { // excel cell\n                CellRecord cell = (CellRecord) record;\n                if (currentRowNumber != cell.getRow()) { // new row\n                    putRow(currentRow);\n                    currentRowNumber = cell.getRow();\n                    currentRow = new HSSFStreamingRow(currentRowNumber, ++currentRowOrder);\n                }\n                currentRow.putCell(cell.getColumn(), new HSSFStreamingCell(getString(cell)));\n            } else {\n                // RowRecord: batch row loading\n                // MissingCellDummyRecord: missing cell\n                // LastCellOfRowDummyRecord: last cell\n                // others ...\n            }\n        }\n\n        private void endRead() {\n            allSheetReadied = true;\n            if (currentSheet != null) {\n                putRow(currentRow); // last row\n            }\n            sheets.forEach(s -> ((HSSFStreamingSheet) s).toEnd());\n        }\n\n        private void putRow(HSSFStreamingRow row) {\n            if (   this.currentSheet.isDiscard()\n                || row == null || row.isEmpty()\n            ) {\n                return;\n            }\n            try {\n                this.currentSheet.putRow(row);\n            } catch (InterruptedException e) {\n                Thread.currentThread().interrupt();\n                throw new RuntimeException(e);\n            }\n        }\n\n        private boolean isDiscard(int sstIdx, String sstName) {\n            if (   ArrayUtils.isEmpty(sheetIndexs)\n                && ArrayUtils.isEmpty(sheetNames))\n            {\n                return false;\n            }\n            return !ArrayUtils.contains(sheetIndexs, sstIdx)\n                && !ArrayUtils.contains(sheetNames, sstName);\n        }\n\n        private String getString(CellRecord record) {\n            switch (record.getSid()) {\n                case BoolErrRecord.sid:\n                    return Boolean.toString(((BoolErrRecord) record).getBooleanValue());\n                case FormulaRecord.sid:\n                    FormulaRecord frec = (FormulaRecord) record;\n                    if (Double.isNaN(frec.getValue())) {\n                        return null; // Formula result is a string, This is stored in the next record\n                    } else {\n                        return formatListener.formatNumberDateCell(frec);\n                    }\n                    // return '\"' + HSSFFormulaParser.toFormulaString(stubWorkbook, frec.getParsedExpression()) + '\"';\n                case LabelSSTRecord.sid:\n                    return sstrec == null ? null : sstrec.getString(((LabelSSTRecord) record).getSSTIndex()).getString();\n                case NumberRecord.sid:\n                    NumberRecord number = (NumberRecord) record;\n                    if (StringUtils.containsAny(formatListener.getFormatString(number), '/', ':')) {\n                        return formatListener.formatNumberDateCell(number);\n                    } else {\n                        return String.valueOf(number.getValue());\n                    }\n                default:\n                    return null;\n            }\n        }\n    }\n\n    // ------------------------------------------------------unsupported operation\n    @Override @Deprecated\n    public boolean isSheetHidden(int sheetIx) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public boolean isSheetVeryHidden(int sheetIx) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int getActiveSheetIndex() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setActiveSheet(int sheetIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int getFirstVisibleTab() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setFirstVisibleTab(int sheetIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setSheetOrder(String sheetname, int pos) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setSelectedTab(int index) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setSheetName(int sheet, String name) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Sheet createSheet() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Sheet createSheet(String sheetname) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Sheet cloneSheet(int sheetNum) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void removeSheetAt(int index) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Font createFont() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Font findFont(boolean bold, short color, short fontHeight, String name, boolean italic, boolean strikeout, short typeOffset, byte underline) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int getNumberOfFonts() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int getNumberOfFontsAsInt() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Font getFontAt(int idx) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public CellStyle createCellStyle() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int getNumCellStyles() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public CellStyle getCellStyleAt(int idx) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void write(OutputStream stream) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int getNumberOfNames() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Name getName(String name) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public List<? extends Name> getNames(String name) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public List<? extends Name> getAllNames() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public Name createName() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void removeName(Name name) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int linkExternalWorkbook(String name, Workbook workbook) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setPrintArea(int sheetIndex, String reference) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setPrintArea(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public String getPrintArea(int sheetIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void removePrintArea(int sheetIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public MissingCellPolicy getMissingCellPolicy() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setMissingCellPolicy(MissingCellPolicy missingCellPolicy) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public DataFormat createDataFormat() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int addPicture(byte[] pictureData, int format) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public List<? extends PictureData> getAllPictures() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public CreationHelper getCreationHelper() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public boolean isHidden() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setHidden(boolean hiddenFlag) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setSheetHidden(int sheetIx, boolean hidden) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public SheetVisibility getSheetVisibility(int sheetIx) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setSheetVisibility(int sheetIx, SheetVisibility visibility) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void addToolPack(UDFFinder toopack) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setForceFormulaRecalculation(boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public boolean getForceFormulaRecalculation() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public SpreadsheetVersion getSpreadsheetVersion() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public int addOlePackage(byte[] oleData, String label, String fileName, String command) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public EvaluationWorkbook createEvaluationWorkbook() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public CellReferenceType getCellReferenceType() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public void setCellReferenceType(CellReferenceType cellReferenceType) {\n        throw new UnsupportedOperationException();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/http/ContentType.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.http;\n\n/**\n * Representing http 'Content-Type' header\n *\n * @author Ponfee\n */\npublic enum ContentType {\n\n    APPLICATION_FORM_URLENCODED(\"application/x-www-form-urlencoded\"), //\n    APPLICATION_JSON(\"application/json\"),                             //\n    APPLICATION_OCTET_STREAM(\"application/octet-stream\"),             //\n    APPLICATION_XML(\"application/xml\"),                               //\n    APPLICATION_ATOM_XML(\"application/atom+xml\"),                     //\n    APPLICATION_SVG_XML(\"application/svg+xml\"),                       //\n    APPLICATION_XHTML_XML(\"application/xhtml+xml\"),                   //\n\n    MULTIPART_FORM_DATA(\"multipart/form-data\"),                       //\n\n    TEXT_XML(\"text/xml\"),                                             //\n    TEXT_HTML(\"text/html\"),                                           //\n    TEXT_PLAIN(\"text/plain\"),                                         //\n\n    IMAGE_JPEG(\"image/jpeg\"),                                         //\n    IMAGE_PNG(\"image/png\"),                                           //\n    IMAGE_BMP(\"image/bmp\"),                                           //\n    IMAGE_ICO(\"image/ico\"),                                           //\n    IMAGE_GIF(\"image/gif\"),                                           //\n\n    WILDCARD(\"*/*\"),                                                  //\n    ;\n\n    final String value;\n\n    ContentType(String value) {\n        this.value = value;\n    }\n\n    public String value() {\n        return value;\n    }\n\n    public static ContentType ofValue(String value) {\n        for (ContentType type : ContentType.values()) {\n            if (type.value.equalsIgnoreCase(value)) {\n                return type;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/http/Http.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.http;\n\nimport cn.ponfee.commons.io.Closeables;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.json.Jsons;\nimport com.fasterxml.jackson.databind.JavaType;\nimport com.google.common.base.Preconditions;\nimport org.apache.commons.collections4.MapUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.EnumUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.annotation.Nonnull;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport java.io.*;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * <pre>\n *  Accept属于请求头， Content-Type属于实体头\n *     请求方的http报头结构：通用报头|请求报头|实体报头 \n *     响应方的http报头结构：通用报头|响应报头|实体报头\n *\n *  请求报头有：Accept、Accept-Charset、Accept-Encoding、Accept-Language、Referer、Authorization、From、Host、If-Match、User-Agent、If-Modified-Since等\n *  Accept：告诉WEB服务器自己接受什么介质类型，*∕*表示任何类型，type∕*表示该类型下的所有子类型，type∕sub-type，如Accept(text/html)\n *\n *  响应报头有：Age、Server、Accept-Ranges、Vary等\n *\n *  实体报头有：Allow、Location、Content-Base、Content-Encoding、Content-Length、Content-Range、Content-MD5、Content-Type、Expires、Last-Modified等\n *  Content-Type：\n *     请求实体报头：浏览器告诉Web服务器自己发送的数据格式，如Content-Type(application/json, multipart/form-data, application/x-www-form-urlencoded)\n *     响应实体报头：Web服务器告诉浏览器自己响应的消息格式，例如Content-Type(application/xml, application/json)\n * </pre>\n * http://www.atool.org/httptest.php<p>\n * \n * Restful：https://www.cnblogs.com/pixy/p/4838268.html<p>\n * \n * http工具类<p>\n *   Spring RestTemplate<p>\n *   org.apache.httpcomponents:fluent-hc<p>\n *   org.apache.httpcomponents.client5:httpclient5<p>\n *   com.squareup.okhttp3:okhttp<p>\n * \n * @author Ponfee\n */\npublic final class Http {\n\n    private final String     url;    // url\n    private final HttpMethod method; // 请求方法\n\n    private final Map<String, String> headers = new HashMap<>(0);   // http请求头\n    private final Map<String, Object> params  = new HashMap<>(0);   // http请求参数(query string)\n    private final List<MimePart>      parts   = new ArrayList<>(0); // http文件上传\n\n    private String data;                       // request body（json or form params, such as name1=value1&name2=value2&...&namen=valuen）\n    private int connectTimeout = 2000;         // 连接超时时间\n    private int readTimeout = 5000;            // 读取返回数据超时时间（socket timeout）\n    private Boolean encode = Boolean.TRUE;     // 是否编码\n    private String contentType;                // 请求内容类型：header(\"Content-Type\", \"application/json; charset=UTF-8\")\n    private String contentCharset;             // 请求内容编码\n    private String accept;                     // 接收类型：header(\"Accept\", \"application/json\")\n    private SSLSocketFactory sslSocketFactory; // 走SSL/TSL通道\n\n    // ----------------------------------------------------------response\n    private Map<String, List<String>> respHeaders;\n    private HttpStatus status;\n\n    private Http(@Nonnull String url, @Nonnull HttpMethod method) {\n        this.url = url;\n        this.method = method;\n    }\n\n    // ------------------------------------------------------method\n    public static Http get(String url) {\n        return new Http(url, HttpMethod.GET);\n    }\n\n    public static Http post(String url) {\n        return new Http(url, HttpMethod.POST);\n    }\n\n    public static Http put(String url) {\n        return new Http(url, HttpMethod.PUT);\n    }\n\n    public static Http head(String url) {\n        return new Http(url, HttpMethod.HEAD);\n    }\n\n    public static Http delete(String url) {\n        return new Http(url, HttpMethod.DELETE);\n    }\n\n    public static Http trace(String url) {\n        return new Http(url, HttpMethod.TRACE);\n    }\n\n    public static Http options(String url) {\n        return new Http(url, HttpMethod.OPTIONS);\n    }\n\n    public static Http of(String url, String method) {\n        return of(url, EnumUtils.getEnumIgnoreCase(HttpMethod.class, method, HttpMethod.GET));\n    }\n\n    public static Http of(String url, HttpMethod method) {\n        return new Http(url, method);\n    }\n\n    // ------------------------------------------------------header\n    /**\n     * 设置请求头\n     * @param name\n     * @param value\n     * @return\n     */\n    public Http addHeader(String name, String value) {\n        this.headers.put(name, value);\n        return this;\n    }\n\n    /**\n     * 设置请求头\n     * @param headers\n     * @return\n     */\n    public Http addHeader(Map<String, String> headers) {\n        if (MapUtils.isNotEmpty(headers)) {\n            this.headers.putAll(headers);\n        }\n        return this;\n    }\n\n    // --------------------------------------------------------query params\n    /**\n     * \n     * 最终是拼接成queryString的形式追加到url（即作为get的http请求参数）\n     * get方式会有编码等问题，推荐使用data方式传参数：{@link #data(Map)}\n     * @param params\n     * @return\n     */\n    public Http addParam(Map<String, ?> params) {\n        this.params.putAll(params);\n        return this;\n    }\n\n    public <T> Http addParam(String name, T value) {\n        this.params.put(name, value);\n        return this;\n    }\n\n    // --------------------------------------------------------body params: form(text, file), text(eg. json), file\n    /**\n     * 发送到服务器的查询字符串或json串：name1=value1&name2=value2\n     * \n     * @param data the http body payload data\n     * @return a reference to this object\n     */\n    public Http data(Map<String, ?> data) {\n        return data(data, Files.UTF_8);\n    }\n\n    /**\n     * 发送到服务器的查询字符串或json串：name1=value1&name2=value2\n     * \n     * application/x-www-form-urlencoded\n     * \n     * @param data the http body payload data\n     * @param charset the charset\n     * @return a reference to this object\n     */\n    public Http data(Map<String, ?> data, String charset) {\n        return data(HttpParams.buildParams(data, charset));\n    }\n\n    /**\n     * HttpURLConnection.getOutputStream().write(data)\n     * \n     * @param data the http body data\n     * @return a reference to this object\n     */\n    public Http data(String data) {\n        Preconditions.checkState(this.data == null, \"data are already set.\");\n        Preconditions.checkArgument(data != null && data.length() != 0, \"data cannot be empty.\");\n        this.data = data;\n        return this;\n    }\n\n    // ------------------------------------------------------part\n    public Http addPart(String formName, String fileName, Object mimePart) {\n        return this.addPart(formName, fileName, null, mimePart);\n    }\n\n    /**\n     * 文件上传\n     * @param formName    表单名称\n     * @param fileName    附件名称\n     * @param contentType 附件类型，value of the Content-Type part header\n     * @param mimePart    上传数据\n     * @return\n     */\n    public Http addPart(String formName, String fileName, \n                        String contentType, Object mimePart) {\n        this.parts.add(new MimePart(formName, fileName, contentType, mimePart));\n        return this;\n    }\n\n    // ------------------------------------------------------encode\n    /**\n     * 编码url\n     * @param encode\n     * @return\n     */\n    public Http encode(Boolean encode) {\n        this.encode = encode;\n        return this;\n    }\n\n    // ------------------------------------------------------request contentType\n    /**\n     * <pre>\n     *  请求实体报头，发送信息至服务器时内容编码类型：\n     *    multipart/form-data，application/x-www-form-urlencoded，\n     *    application/json\n     *  默认：application/x-www-form-urlencoded\n     *  调用方式：contentType(\"application/json\", \"UTF-8\")\n     * </pre>\n     * @param contentType\n     * @param contentCharset\n     * @return\n     */\n    public Http contentType(ContentType contentType, String contentCharset) {\n        this.contentType = contentType.value();\n        this.contentCharset = contentCharset;\n        return this;\n    }\n\n    public Http contentType(ContentType contentType) {\n        return this.contentType(contentType, Files.UTF_8);\n    }\n\n    // ------------------------------------------------------response accept\n    /**\n     * 内容类型发送请求头，告诉服务器什么样的响应会接受返回\n     * header(\"Accept\", contentType)\n     * @param contentType  application/json\n     * @return\n     */\n    public Http accept(ContentType contentType) {\n        this.accept = contentType.value();\n        return this;\n    }\n\n    // --------------------------------------------------------------timeout\n    /**\n     * set connect timeout\n     * @param seconds (s)\n     * @return this\n     */\n    public Http connTimeoutSeconds(int seconds) {\n        this.connectTimeout = seconds * 1000;\n        return this;\n    }\n\n    /**\n     * set read timeout\n     * @param seconds (s)\n     * @return this\n     */\n    public Http readTimeoutSeconds(int seconds) {\n        this.readTimeout = seconds * 1000;\n        return this;\n    }\n\n    // ------------------------------------------------------trust spec cert\n    /**\n     * trust spec certificate\n     * @param factory\n     * @return\n     */\n    public Http setSSLSocketFactory(SSLSocketFactory factory) {\n        this.sslSocketFactory = factory;\n        return this;\n    }\n\n    public Http setSSLSocketFactory(SSLContext sslContext) {\n        return setSSLSocketFactory(sslContext.getSocketFactory());\n    }\n\n    // --------------------------------------------------------------request\n    public <T> T request(JavaType type) {\n        return Jsons.fromJson(request(), type);\n    }\n\n    public <T> T request(Class<T> type) {\n        return Jsons.fromJson(request(), type);\n    }\n\n    /**\n     * 发送请求获取响应数据\n     * @return\n     */\n    public String request() {\n        HttpRequest request = request0();\n        try {\n            return request.body();\n        } finally {\n            disconnect(request);\n        }\n    }\n\n    public void download(String filepath) {\n        try (OutputStream out = new FileOutputStream(filepath)) {\n            download(out);\n        } catch (IOException e) {\n            throw new HttpException(\"download error: \" + filepath, e);\n        }\n    }\n\n    public byte[] download() {\n        ByteArrayOutputStream output = new ByteArrayOutputStream();\n        download(output);\n        return output.toByteArray();\n    }\n\n    /**\n     * http下载\n     * @param output    output to stream of response data\n     */\n    //private static final Pattern FILENAME_PATTERN = Pattern.compile(\"(?i)^.*;.*filename=(.*)$\");\n    public void download(OutputStream output) {\n        BufferedOutputStream bos = null;\n        HttpRequest request = request0();\n        try {\n            if (HttpStatus.Series.valueOf(status) == HttpStatus.Series.SUCCESSFUL) {\n                /*\n                // 获取文件名\n                String disposition = UrlCoder.decodeURIComponent(request.header(\"content-Disposition\"));\n                Matcher matcher = FILENAME_PATTERN.matcher(disposition);\n                if (matcher.matches()) {\n                    String filename = matcher.group(1);\n                }\n                */\n                bos = new BufferedOutputStream(output);\n                request.receive(bos);\n            } else {\n                throw new HttpException(\"request failed, status: \" + request.code());\n            }\n        } finally {\n            disconnect(request);\n            Closeables.console(bos);\n        }\n    }\n\n    // ------------------------------------------------------response headers\n    public Map<String, List<String>> getRespHeaders() {\n        return respHeaders;\n    }\n\n    public Map<String, String> getReqHeaders() {\n        return headers;\n    }\n\n    public String[] getRespHeaders(String name) {\n        if (respHeaders == null) {\n            return null;\n        }\n\n        List<String> values = respHeaders.get(name);\n        if (values == null) {\n            return null;\n        }\n        return values.toArray(new String[0]);\n    }\n\n    public String getRespHeader(String name) {\n        if (respHeaders == null) {\n            return null;\n        }\n\n        List<String> values = respHeaders.get(name);\n        return (values == null || values.isEmpty())\n               ? null : values.get(0);\n    }\n\n    public HttpStatus getStatus() {\n        return status;\n    }\n\n    // ------------------------------------------------------private methods\n    private HttpRequest request0() {\n        HttpRequest request;\n        switch (method) {\n            case GET:     request = HttpRequest.get    (url, params, encode); break;\n            case POST:    request = HttpRequest.post   (url, params, encode); break;\n            case PUT:     request = HttpRequest.put    (url, params, encode); break;\n            case HEAD:    request = HttpRequest.head   (url, params, encode); break;\n            case DELETE:  request = HttpRequest.delete (url, params, encode); break;\n            case TRACE:   request = HttpRequest.trace  (url                ); break;\n            case OPTIONS: request = HttpRequest.options(url                ); break;\n            default: throw new UnsupportedOperationException(\"unsupported http method \" + method.name());\n        }\n\n        request.connectTimeout(connectTimeout)\n            .readTimeout(readTimeout)\n            .decompress(true)\n            .acceptGzipEncoding()\n            .headers(headers);\n\n        if (!StringUtils.isEmpty(contentType)) {\n            request.contentType(contentType, contentCharset);\n        }\n\n        if (!StringUtils.isEmpty(accept)) {\n            request.accept(accept);\n        }\n\n        request.trustAllHosts();\n        if (this.sslSocketFactory != null) {\n            request.setSSLSocketFactory(this.sslSocketFactory);\n        } else {\n            request.trustAllCerts();\n        }\n\n        if (!StringUtils.isEmpty(data)) {\n            request.send(data);\n        }\n\n        for (MimePart part : parts) {\n            request.part(part.formName, part.fileName, part.contentType, part.stream);\n        }\n\n        status = request.status();\n        return request;\n    }\n\n    private void disconnect(HttpRequest request) {\n        if (request != null) {\n            this.respHeaders = request.headers(); // get the response headers\n            try {\n                request.disconnect();\n            } catch (Exception ignored) {\n                ignored.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Http method\n     */\n    public enum HttpMethod {\n        GET, POST, PUT, DELETE, HEAD, TRACE, OPTIONS\n    }\n\n    /**\n     * File upload\n     */\n    private static final class MimePart {\n        final String formName;    // 表单域字段名\n        final String fileName;    // 文件名\n        final String contentType; // 附件类型\n        final InputStream stream; // 文件流\n\n        MimePart(String formName, String fileName, String contentType, Object mime) {\n            if (mime instanceof byte[]) {\n                this.stream = new ByteArrayInputStream((byte[]) mime);\n            } else if (mime instanceof Byte[]) {\n                this.stream = new ByteArrayInputStream(ArrayUtils.toPrimitive((Byte[]) mime));\n            } else if (mime instanceof String || mime instanceof File) {\n                File file = (mime instanceof File) ? (File) mime : new File((String) mime);\n                try {\n                    this.stream = new FileInputStream(file);\n                } catch (FileNotFoundException e) {\n                    throw new IllegalArgumentException(e);\n                }\n            } else if (mime instanceof InputStream) {\n                this.stream = (InputStream) mime;\n            } else {\n                throw new IllegalArgumentException(\n                    \"mime must be one of them: file, file path, byte array, input stream.\"\n                );\n            }\n\n            this.formName    = formName;\n            this.fileName    = fileName;\n            this.contentType = contentType;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/http/HttpException.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.http;\n\n/**\n * Http exception\n * \n * @author Ponfee\n */\npublic class HttpException extends RuntimeException {\n\n    private static final long serialVersionUID = 7195686343121118928L;\n\n    public HttpException() {\n        super();\n    }\n\n    public HttpException(String message) {\n        super(message);\n    }\n\n    public HttpException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public HttpException(Throwable cause) {\n        super(cause);\n    }\n\n    protected HttpException(String message, Throwable cause, \n                            boolean enableSuppression, \n                            boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/http/HttpParams.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.http;\n\nimport cn.ponfee.commons.collect.Maps;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.URLCodes;\nimport cn.ponfee.commons.util.UuidUtils;\nimport org.apache.commons.collections4.MapUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.lang.reflect.Array;\nimport java.util.*;\n\n/**\n * http参数工具类\n *\n * @author Ponfee\n */\npublic class HttpParams {\n\n    // --------------------------------------------------获取url中的参数\n    public static Map<String, String[]> parseUrlParams(String url) {\n        return parseUrlParams(url, Files.UTF_8);\n    }\n\n    public static Map<String, String[]> parseUrlParams(String url, String charset) {\n        int idx = url.indexOf('?');\n        return idx == -1 ? Collections.emptyMap() : parseParams(url.substring(idx + 1), charset);\n    }\n\n    // --------------------------------------------------解析query string中的请求参数\n    public static Map<String, String[]> parseParams(String queryString) {\n        return parseParams(queryString, Files.UTF_8);\n    }\n\n    /**\n     * 解析参数\n     * @param queryString\n     * @param encoding\n     * @return\n     */\n    public static Map<String, String[]> parseParams(String queryString, String encoding) {\n        if (queryString == null || queryString.length() == 0) {\n            return Collections.emptyMap();\n        }\n\n        if (encoding == null) {\n            encoding = Files.UTF_8;\n        }\n\n        Map<String, String[]> params = new LinkedHashMap<>();\n        String[] kv;\n        for (String param : queryString.split(\"&\")) {\n            kv = param.split(\"=\", 2);\n            putParam(params, kv[0], kv.length == 1 ? \"\" : URLCodes.decodeURIComponent(kv[1], encoding));\n        }\n        return params;\n    }\n\n    // --------------------------------------------------构建参数\n    /**\n     * 键值对构建参数(默认UTF-8)\n     * @param params\n     * @return\n     */\n    public static String buildParams(Map<String, ?> params) {\n        return HttpParams.buildParams(params, Files.UTF_8);\n    }\n\n    /**\n     * 键值对构建参数\n     * @param params\n     * @param encoding\n     * @return\n     */\n    public static String buildParams(Map<String, ?> params, String encoding) {\n        StringBuilder builder = new StringBuilder();\n        String[] values;\n        Object value;\n        for (Map.Entry<String, ?> entry : params.entrySet()) {\n            value = entry.getValue();\n            if (value != null && value.getClass().isArray()) {\n                values = new String[Array.getLength(value)];\n                for (int length = values.length, i = 0; i < length; i++) {\n                    values[i] = Objects.toString(Array.get(value, i), \"\");\n                }\n            } else {\n                values = new String[] { Objects.toString(entry.getValue(), \"\") };\n            }\n\n            for (String val : values) {\n                builder.append(entry.getKey())\n                       .append(\"=\")\n                       .append(URLCodes.encodeURIComponent(val, encoding))\n                       .append(\"&\");\n            }\n        }\n\n        if (builder.length() > 0) {\n            builder.setLength(builder.length() - 1);\n        }\n        return builder.toString();\n    }\n\n    // --------------------------------------------------构建url地址\n    /**\n     * 构建url地址\n     * @param url\n     * @param encoding\n     * @param params\n     * @return\n     */\n    public static String buildUrlPath(String url, String encoding, Map<String, ?> params) {\n        if (params == null || params.isEmpty()) {\n            return url;\n        }\n\n        return url \n            + (url.indexOf('?') == -1 ? '?' : '&') \n            + buildParams(params, encoding);\n    }\n\n    public static String buildUrlPath(String url, String encoding, Object... params) {\n        return buildUrlPath(url, encoding, Maps.toMap(params));\n    }\n\n    // --------------------------------------------------构建签名数据\n    /**\n     * 构建待签名数据\n     * @param params 请求参数\n     * @return\n     */\n    public static String buildSigning(Map<String, ?> params) {\n        return buildSigning(params, \"\", null);\n    }\n\n    public static String buildSigning(Map<String, ?> params, String[] excludes) {\n        return buildSigning(params, \"\", excludes);\n    }\n\n    public static String buildSigning(Map<String, ?> params, String wrapChar, String[] excludes) {\n        List<String> filter = (excludes == null || excludes.length == 0)\n                              ? Collections.emptyList() \n                              : Arrays.asList(excludes);\n\n        // 过滤参数\n        Map<String, String> signingMap = new TreeMap<>();\n        for (Map.Entry<String, ?> entry : params.entrySet()) {\n            if (!filter.contains(entry.getKey())\n                && StringUtils.isNotEmpty(Objects.toString(entry.getValue(), null))) {\n                signingMap.put(entry.getKey(), entry.getValue().toString());\n            }\n        }\n\n        // 拼接待签名串，blank string to prevent if signingMap is empty\n        StringBuilder signing = new StringBuilder();\n        for (Map.Entry<String, String> entry : signingMap.entrySet()) {\n            signing.append(entry.getKey())\n                   .append('=')\n                   .append(wrapChar).append(entry.getValue()).append(wrapChar)\n                   .append('&');\n        }\n        if (signing.length() > 0) {\n            // 删除未位的'&'\n            signing.setLength(signing.length() - 1);\n        }\n        return signing.toString();\n    }\n\n    // --------------------------------------------------构建Form表单\n    /**\n     * 构建form表单\n     * @param url\n     * @param params\n     * @return\n     */\n    public static String buildForm(String url, Map<String, ?> params) {\n        StringBuilder form = new StringBuilder(256);\n        String formName = UuidUtils.uuid32();\n        form.append(\"<form action=\\\"\")\n            .append(url)\n            .append(\"\\\" name=\\\"\")\n            .append(formName)\n            .append(\"\\\" method=\\\"post\\\">\");\n\n        Object value;\n        for (Map.Entry<String, ?> param : params.entrySet()) {\n            value = param.getValue();\n            if (value != null && value.getClass().isArray()) {\n                for (int length = Array.getLength(value), i = 0; i < length; i++) {\n                    buildInputElement(form, param.getKey(), Array.get(value, i));\n                }\n            } else {\n                buildInputElement(form, param.getKey(), value);\n            }\n        }\n\n        return form.append(\"</form><script>document.forms['\")\n                   .append(formName)\n                   .append(\"'].submit();</script>\")\n                   .toString();\n    }\n\n    /**\n     * Builds the webservice soap xml\n     * \n     * http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx\n     * SOAP 1.1\n     * \n     * @param method the method, like as \"getCountryCityByIp\"\n     * @param namespace the namespace, like as \"http://WebXml.com.cn/\"\n     * @param params the params\n     * @return a soap xml string\n     */\n    public static String buildSoap(String method, String namespace, Map<String, ?> params) {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"<soapenv:Envelope xmlns:soapenv=\\\"http://schemas.xmlsoap.org/soap/envelope/\\\" xmlns:ns=\\\"\").append(namespace).append(\"\\\">\");\n        sb.append(\"<soapenv:Body>\");\n        sb.append(\"<ns:\").append(method).append(\">\");\n        if (MapUtils.isNotEmpty(params)) {\n            params.forEach(\n                (k, v) -> sb.append(\"<\").append(k).append(\">\")\n                            .append(Objects.toString(v, \"\"))\n                            .append(\"</\").append(k).append(\">\")\n            );\n        }\n        sb.append(\"</ns:\").append(method).append(\">\");\n        sb.append(\"</soapenv:Body>\");\n        sb.append(\"</soapenv:Envelope>\");\n        return sb.toString();\n    }\n\n    // --------------------------------------------------private methods\n    private static void putParam(Map<String, String[]> params, String name, String value) {\n        String[] oldValues = params.get(name);\n        if (oldValues == null) {\n            params.put(name, new String[] { value });\n        } else {\n            params.put(name, ArrayUtils.add(oldValues, value));\n        }\n    }\n\n    private static void buildInputElement(StringBuilder form, String name, Object value) {\n        form.append(\"<input type=\\\"hidden\\\" name=\\\"\")\n            .append(name)\n            .append(\"\\\" value=\\\"\")\n            .append(Objects.toString(value, \"\"))\n            .append(\"\\\" />\");\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/http/HttpRequest.java",
    "content": "/*\n * Copyright (c) 2014 Kevin Sawicki <kevinsawicki@gmail.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n *\n * <dependency>\n *   <groupId>com.github.kevinsawicki</groupId>\n *   <artifactId>http-request</artifactId>\n *   <version>6.0</version>\n * </dependency>\n */\npackage cn.ponfee.commons.http;\n\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.UuidUtils;\n\nimport javax.net.ssl.*;\nimport java.io.*;\nimport java.net.*;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.CharsetEncoder;\nimport java.nio.charset.StandardCharsets;\nimport java.security.AccessController;\nimport java.security.GeneralSecurityException;\nimport java.security.PrivilegedAction;\nimport java.security.SecureRandom;\nimport java.security.cert.X509Certificate;\nimport java.util.*;\nimport java.util.Map.Entry;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.zip.GZIPInputStream;\n\nimport static java.net.HttpURLConnection.*;\nimport static java.net.Proxy.Type.HTTP;\n\n/**\n * A fluid interface for making HTTP requests using an underlying\n * {@link HttpURLConnection} (or sub-class).\n * <p>\n * Each instance supports making a single request and cannot be reused for\n * further requests.\n *\n * @author Kevin Sawicki, Ponfee\n *\n * Reference from internet and with optimization\n * https://github.com/kevinsawicki/http-request\n */\npublic class HttpRequest {\n\n    /**\n     * 'gzip' encoding header value\n     */\n    public static final String ENCODING_GZIP = \"gzip\";\n\n    /**\n     * 'Accept' header name\n     */\n    public static final String HEADER_ACCEPT = \"Accept\";\n\n    /**\n     * 'Accept-Charset' header name\n     */\n    public static final String HEADER_ACCEPT_CHARSET = \"Accept-Charset\";\n\n    /**\n     * 'Accept-Encoding' header name\n     */\n    public static final String HEADER_ACCEPT_ENCODING = \"Accept-Encoding\";\n\n    /**\n     * 'Authorization' header name\n     */\n    public static final String HEADER_AUTHORIZATION = \"Authorization\";\n\n    /**\n     * 'Cache-Control' header name\n     */\n    public static final String HEADER_CACHE_CONTROL = \"Cache-Control\";\n\n    /**\n     * 'Content-Encoding' header name\n     */\n    public static final String HEADER_CONTENT_ENCODING = \"Content-Encoding\";\n\n    /**\n     * 'Content-Length' header name\n     */\n    public static final String HEADER_CONTENT_LENGTH = \"Content-Length\";\n\n    /**\n     * 'Content-Type' header name\n     */\n    public static final String HEADER_CONTENT_TYPE = \"Content-Type\";\n\n    /**\n     * 'Date' header name\n     */\n    public static final String HEADER_DATE = \"Date\";\n\n    /**\n     * 'ETag' header name\n     */\n    public static final String HEADER_ETAG = \"ETag\";\n\n    /**\n     * 'Expires' header name\n     */\n    public static final String HEADER_EXPIRES = \"Expires\";\n\n    /**\n     * 'If-None-Match' header name\n     */\n    public static final String HEADER_IF_NONE_MATCH = \"If-None-Match\";\n\n    /**\n     * 'Last-Modified' header name\n     */\n    public static final String HEADER_LAST_MODIFIED = \"Last-Modified\";\n\n    /**\n     * 'Location' header name\n     */\n    public static final String HEADER_LOCATION = \"Location\";\n\n    /**\n     * 'Proxy-Authorization' header name\n     */\n    public static final String HEADER_PROXY_AUTHORIZATION = \"Proxy-Authorization\";\n\n    /**\n     * 'Referer' header name\n     */\n    public static final String HEADER_REFERER = \"Referer\";\n\n    /**\n     * 'Server' header name\n     */\n    public static final String HEADER_SERVER = \"Server\";\n\n    /**\n     * 'User-Agent' header name\n     */\n    public static final String HEADER_USER_AGENT = \"User-Agent\";\n\n    /**\n     * 'DELETE' request method\n     */\n    public static final String METHOD_DELETE = \"DELETE\";\n\n    /**\n     * 'GET' request method\n     */\n    public static final String METHOD_GET = \"GET\";\n\n    /**\n     * 'HEAD' request method\n     */\n    public static final String METHOD_HEAD = \"HEAD\";\n\n    /**\n     * 'OPTIONS' options method\n     */\n    public static final String METHOD_OPTIONS = \"OPTIONS\";\n\n    /**\n     * 'POST' request method\n     */\n    public static final String METHOD_POST = \"POST\";\n\n    /**\n     * 'PUT' request method\n     */\n    public static final String METHOD_PUT = \"PUT\";\n\n    /**\n     * 'TRACE' request method\n     */\n    public static final String METHOD_TRACE = \"TRACE\";\n\n    /**\n     * 'charset' header value parameter\n     */\n    public static final String PARAM_CHARSET = \"charset\";\n\n    private static final String BOUNDARY = \"00content0boundary00\";\n\n    private static final String CONTENT_TYPE_MULTIPART = \"multipart/form-data; boundary=\" + BOUNDARY;\n\n    private static final String CRLF = \"\\r\\n\";\n\n    private static final String[] EMPTY_STRINGS = {};\n\n    private static final HostnameVerifier TRUSTED_VERIFIER = (hostname, session) -> /*hostname.equalsIgnoreCase(session.getPeerHost())*/true;\n\n    private static final SSLSocketFactory TRUSTED_FACTORY;\n    static {\n        try {\n            SSLContext context = SSLContext.getInstance(\"TLSv1.3\");\n\n            context.init(null, new TrustManager[] {\n                new X509TrustManager() {\n                    @Override\n                    public X509Certificate[] getAcceptedIssuers() {\n                        return new X509Certificate[0];\n                    }\n\n                    @Override\n                    public void checkClientTrusted(X509Certificate[] chain, String authType) {\n                        // Intentionally left blank\n                    }\n\n                    @Override\n                    public void checkServerTrusted(X509Certificate[] chain, String authType) {\n                        // Intentionally left blank\n                    }\n                }\n            }, new SecureRandom(new SecureRandom(UuidUtils.uuid()).generateSeed(20)));\n\n            TRUSTED_FACTORY = context.getSocketFactory();\n\n        } catch (GeneralSecurityException e) {\n            throw new HttpException(\n                new IOException(\"Security exception configuring SSL context\", e)\n            );\n        }\n    }\n\n    private static String getValidCharset(String charset) {\n        if (charset != null && charset.length() > 0) {\n            return charset;\n        } else {\n            return Files.UTF_8;\n        }\n    }\n\n    private static StringBuilder addPathSeparator(String baseUrl, StringBuilder result) {\n        // Add trailing slash if the base URL doesn't have any path segments.\n        //\n        // The following test is checking for the last slash not being part of\n        // the protocol to host separator: '://'.\n        if (baseUrl.indexOf(':') + 2 == baseUrl.lastIndexOf('/')) {\n            result.append('/');\n        }\n        return result;\n    }\n\n    private static StringBuilder addParamPrefix(String baseUrl, StringBuilder result) {\n        // Add '?' if missing and add '&' if params already exist in base url\n        int queryStart = baseUrl.indexOf('?');\n        int lastChar = result.length() - 1;\n        if (queryStart == -1) {\n            result.append('?');\n        } else if (queryStart < lastChar && baseUrl.charAt(lastChar) != '&') {\n            result.append('&');\n        }\n        return result;\n    }\n\n    private static StringBuilder addParam(Object key, Object value, StringBuilder result) {\n        if (value != null && value.getClass().isArray()) {\n            value = Collects.toList(value);\n        }\n\n        if (value instanceof Iterable<?>) {\n            Iterator<?> iterator = ((Iterable<?>) value).iterator();\n            while (iterator.hasNext()) {\n                result.append(key);\n                result.append(\"[]=\");\n                Object element = iterator.next();\n                if (element != null) {\n                    result.append(element);\n                }\n                if (iterator.hasNext()) {\n                    result.append(\"&\");\n                }\n            }\n        } else {\n            result.append(key);\n            result.append(\"=\");\n            if (value != null) {\n                result.append(value);\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * Creates {@link HttpURLConnection HTTP connections} for\n     * {@link URL urls}.\n     */\n    public interface ConnectionFactory {\n        /**\n         * Open an {@link HttpURLConnection} for the specified {@link URL}.\n         * @throws IOException\n         */\n        HttpURLConnection create(URL url) throws IOException;\n\n        /**\n         * Open an {@link HttpURLConnection} for the specified {@link URL}\n         * and {@link Proxy}.\n         * @throws IOException\n         */\n        HttpURLConnection create(URL url, Proxy proxy) throws IOException;\n\n        /**\n         * A {@link ConnectionFactory} which uses the built-in\n         * {@link URL#openConnection()}\n         */\n        ConnectionFactory DEFAULT = new ConnectionFactory() {\n            @Override\n            public HttpURLConnection create(URL url) throws IOException {\n                return (HttpURLConnection) url.openConnection();\n            }\n\n            @Override\n            public HttpURLConnection create(URL url, Proxy proxy) throws IOException {\n                return (HttpURLConnection) url.openConnection(proxy);\n            }\n        };\n    }\n\n    private static ConnectionFactory connectionFactory = ConnectionFactory.DEFAULT;\n\n    /**\n     * Specify the {@link ConnectionFactory} used to create new requests.\n     */\n    public static void setConnectionFactory(ConnectionFactory cf) {\n        connectionFactory = cf != null ? cf : ConnectionFactory.DEFAULT;\n    }\n\n    /**\n     * Callback interface for reporting upload progress for a request.\n     */\n    public interface UploadProgress {\n        /**\n         * Callback invoked as data is uploaded by the request.\n         *\n         * @param uploaded The number of bytes already uploaded\n         * @param total The total number of bytes that will be uploaded or -1 if\n         *              the length is unknown.\n         */\n        void onUpload(long uploaded, long total);\n\n        UploadProgress DEFAULT = (uploaded, total) -> {\n            // do-non\n        };\n    }\n\n    /**\n     * Operation that handles executing a callback once complete and handling\n     * nested exceptions\n     *\n     * @param <V>\n     */\n    private abstract static class Operation<V> implements Callable<V> {\n\n        /**\n         * Run operation\n         *\n         * @return result\n         * @throws HttpException\n         * @throws IOException\n         */\n        protected abstract V run() throws HttpException, IOException;\n\n        /**\n         * Operation complete callback\n         *\n         * @throws IOException\n         */\n        protected abstract void done() throws IOException;\n\n        @Override\n        public final V call() throws HttpException {\n            try {\n                return run();\n            } catch (HttpException e) {\n                throw e;\n            } catch (Exception e) {\n                throw new HttpException(e);\n            } finally {\n                try {\n                    done();\n                } catch (Exception ignored) {\n                    ignored.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Class that ensures a {@link Closeable} gets closed with proper exception\n     * handling.\n     *\n     * @param <V>\n     */\n    private abstract static class CloseOperation<V> extends Operation<V> {\n\n        private final Closeable closeable;\n\n        private final boolean ignoreCloseExceptions;\n\n        /**\n         * Create closer for operation\n         *\n         * @param closeable\n         * @param ignoreCloseExceptions\n         */\n        protected CloseOperation(Closeable closeable, boolean ignoreCloseExceptions) {\n            this.closeable = closeable;\n            this.ignoreCloseExceptions = ignoreCloseExceptions;\n        }\n\n        @Override\n        protected void done() throws IOException {\n            if (closeable instanceof Flushable) {\n                ((Flushable) closeable).flush();\n            }\n\n            if (ignoreCloseExceptions) {\n                try {\n                    closeable.close();\n                } catch (IOException ignored) {\n                    ignored.printStackTrace(); // ignored\n                }\n            } else {\n                closeable.close();\n            }\n        }\n    }\n\n    /**\n     * Class that and ensures a {@link Flushable} gets flushed with proper\n     * exception handling.\n     *\n     * @param <V>\n     */\n    private abstract static class FlushOperation<V> extends Operation<V> {\n\n        private final Flushable flushable;\n\n        /**\n         * Create flush operation\n         *\n         * @param flushable\n         */\n        protected FlushOperation(Flushable flushable) {\n            this.flushable = flushable;\n        }\n\n        @Override\n        protected void done() throws IOException {\n            flushable.flush();\n        }\n    }\n\n    /**\n     * Request output stream\n     */\n    public static class RequestOutputStream extends BufferedOutputStream {\n\n        private final CharsetEncoder encoder;\n\n        /**\n         * Create request output stream\n         *\n         * @param stream\n         * @param charset\n         * @param bufferSize\n         */\n        public RequestOutputStream(OutputStream stream, String charset, int bufferSize) {\n            super(stream, bufferSize);\n\n            encoder = Charset.forName(getValidCharset(charset)).newEncoder();\n        }\n\n        /**\n         * Write string to stream\n         *\n         * @param value\n         * @return this stream\n         * @throws IOException\n         */\n        public RequestOutputStream write(String value) throws IOException {\n            ByteBuffer bytes = encoder.encode(CharBuffer.wrap(value));\n            super.write(bytes.array(), 0, bytes.limit());\n            return this;\n        }\n    }\n\n    /**\n     * Encode the given URL as an ASCII {@link String}\n     * <p>\n     * This method ensures the path and query segments of the URL are properly\n     * encoded such as ' ' characters being encoded to '%20' or any UTF-8\n     * characters that are non-ASCII. No encoding of URLs is done by default by\n     * the {@link HttpRequest} constructors and so if URL encoding is needed this\n     * method should be called before calling the {@link HttpRequest} constructor.\n     *\n     * @param url\n     * @return encoded URL\n     * @throws HttpException\n     */\n    public static String encode(CharSequence url)\n        throws HttpException {\n        URL u;\n        try {\n            u = new URL(url.toString());\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n\n        String host = u.getHost();\n        int port = u.getPort();\n        if (port != -1) {\n            host = host + ':' + port;\n        }\n\n        try {\n            String s = new URI(u.getProtocol(), host, u.getPath(), u.getQuery(), null)\n                            .toASCIIString();\n            int paramsStart = s.indexOf('?');\n            if (paramsStart > 0 && paramsStart + 1 < s.length()) {\n                s = s.substring(0, paramsStart + 1) + s.substring(paramsStart + 1).replace(\"+\", \"%2B\");\n            }\n            return s;\n        } catch (URISyntaxException e) {\n            throw new HttpException(new IOException(\"Parsing URI failed\", e));\n        }\n    }\n\n    /**\n     * Append given map as query parameters to the base URL\n     * <p>\n     * Each map entry's key will be a parameter name and the value's\n     * {@link Object#toString()} will be the parameter value.\n     *\n     * @param url\n     * @param params\n     * @return URL with appended query params\n     */\n    public static String append(CharSequence url, Map<?, ?> params) {\n        String baseUrl = url.toString();\n        if (params == null || params.isEmpty()) {\n            return baseUrl;\n        }\n\n        StringBuilder result = new StringBuilder(baseUrl);\n\n        addPathSeparator(baseUrl, result);\n        addParamPrefix(baseUrl, result);\n\n        Entry<?, ?> entry;\n        Iterator<?> iterator = params.entrySet().iterator();\n        entry = (Entry<?, ?>) iterator.next();\n        addParam(entry.getKey().toString(), entry.getValue(), result);\n\n        while (iterator.hasNext()) {\n            result.append('&');\n            entry = (Entry<?, ?>) iterator.next();\n            addParam(entry.getKey().toString(), entry.getValue(), result);\n        }\n\n        return result.toString();\n    }\n\n    /**\n     * Append given name/value pairs as query parameters to the base URL\n     * <p>\n     * The params argument is interpreted as a sequence of name/value pairs so the\n     * given number of params must be divisible by 2.\n     *\n     * @param url\n     * @param params name/value pairs\n     * @return URL with appended query params\n     */\n    public static String append(CharSequence url, Object... params) {\n        String baseUrl = url.toString();\n        if (params == null || params.length == 0) {\n            return baseUrl;\n        }\n\n        if ((params.length & 0x01) == 1) {\n            throw new IllegalArgumentException(\"Must specify an even number of parameter names/values\");\n        }\n\n        StringBuilder result = new StringBuilder(baseUrl);\n\n        addPathSeparator(baseUrl, result);\n        addParamPrefix(baseUrl, result);\n\n        addParam(params[0], params[1], result);\n\n        for (int i = 2; i < params.length; i += 2) {\n            result.append('&');\n            addParam(params[i], params[i + 1], result);\n        }\n\n        return result.toString();\n    }\n\n    /**\n     * Start a 'GET' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest get(CharSequence url) throws HttpException {\n        return new HttpRequest(url, METHOD_GET);\n    }\n\n    /**\n     * Start a 'GET' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest get(URL url) throws HttpException {\n        return new HttpRequest(url, METHOD_GET);\n    }\n\n    /**\n     * Start a 'GET' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param params The query parameters to include as part of the baseUrl\n     * @param encode true to encode the full URL\n     *\n     * @see #append(CharSequence, Map)\n     * @see #encode(CharSequence)\n     *\n     * @return request\n     */\n    public static HttpRequest get(CharSequence baseUrl, Map<?, ?> params, boolean encode) {\n        String url = append(baseUrl, params);\n        return get(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start a 'GET' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param encode true to encode the full URL\n     * @param params the name/value query parameter pairs to\n     *               include as part of the baseUrl\n     *\n     * @see #append(CharSequence, Object...)\n     * @see #encode(CharSequence)\n     *\n     * @return request\n     */\n    public static HttpRequest get(CharSequence baseUrl, boolean encode, Object... params) {\n        String url = append(baseUrl, params);\n        return get(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start a 'POST' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest post(CharSequence url) throws HttpException {\n        return new HttpRequest(url, METHOD_POST);\n    }\n\n    /**\n     * Start a 'POST' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest post(URL url) throws HttpException {\n        return new HttpRequest(url, METHOD_POST);\n    }\n\n    /**\n     * Start a 'POST' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param params the query parameters to include as part of the baseUrl\n     * @param encode true to encode the full URL\n     *\n     * @see #append(CharSequence, Map)\n     * @see #encode(CharSequence)\n     *\n     * @return request\n     */\n    public static HttpRequest post(CharSequence baseUrl, Map<?, ?> params, boolean encode) {\n        String url = append(baseUrl, params);\n        return post(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start a 'POST' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param encode  true to encode the full URL\n     * @param params  the name/value query parameter pairs to\n     *                include as part of the baseUrl\n     *\n     * @see #append(CharSequence, Object...)\n     * @see #encode(CharSequence)\n     *\n     * @return request\n     */\n    public static HttpRequest post(CharSequence baseUrl, boolean encode, Object... params) {\n        String url = append(baseUrl, params);\n        return post(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start a 'PUT' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest put(CharSequence url) throws HttpException {\n        return new HttpRequest(url, METHOD_PUT);\n    }\n\n    /**\n     * Start a 'PUT' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest put(URL url) throws HttpException {\n        return new HttpRequest(url, METHOD_PUT);\n    }\n\n    /**\n     * Start a 'PUT' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param params the query parameters to include as part of the baseUrl\n     * @param encode true to encode the full URL\n     *\n     * @see #append(CharSequence, Map)\n     * @see #encode(CharSequence)\n     *\n     * @return request\n     */\n    public static HttpRequest put(CharSequence baseUrl, Map<?, ?> params, boolean encode) {\n        String url = append(baseUrl, params);\n        return put(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start a 'PUT' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param encode true to encode the full URL\n     * @param params the name/value query parameter pairs to\n     *               include as part of the baseUrl\n     *\n     * @see #append(CharSequence, Object...)\n     * @see #encode(CharSequence)\n     *\n     * @return request\n     */\n    public static HttpRequest put(CharSequence baseUrl, boolean encode, Object... params) {\n        String url = append(baseUrl, params);\n        return put(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start a 'DELETE' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest delete(CharSequence url) throws HttpException {\n        return new HttpRequest(url, METHOD_DELETE);\n    }\n\n    /**\n     * Start a 'DELETE' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest delete(URL url) throws HttpException {\n        return new HttpRequest(url, METHOD_DELETE);\n    }\n\n    /**\n     * Start a 'DELETE' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param params The query parameters to include as part of the baseUrl\n     * @param encode true to encode the full URL\n     *\n     * @see #append(CharSequence, Map)\n     * @see #encode(CharSequence)\n     *\n     * @return request\n     */\n    public static HttpRequest delete(CharSequence baseUrl, Map<?, ?> params, boolean encode) {\n        String url = append(baseUrl, params);\n        return delete(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start a 'DELETE' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param encode  true to encode the full URL\n     * @param params  the name/value query parameter pairs to\n     *                include as part of the baseUrl\n     *\n     * @see #append(CharSequence, Object...)\n     * @see #encode(CharSequence)\n     *\n     * @return request\n     */\n    public static HttpRequest delete(CharSequence baseUrl, boolean encode, Object... params) {\n        String url = append(baseUrl, params);\n        return delete(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start a 'HEAD' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest head(CharSequence url) throws HttpException {\n        return new HttpRequest(url, METHOD_HEAD);\n    }\n\n    /**\n     * Start a 'HEAD' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest head(URL url) throws HttpException {\n        return new HttpRequest(url, METHOD_HEAD);\n    }\n\n    /**\n     * Start a 'HEAD' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param params The query parameters to include as part of the baseUrl\n     * @param encode true to encode the full URL\n     * @see #append(CharSequence, Map)\n     * @see #encode(CharSequence)\n     * @return request\n     */\n    public static HttpRequest head(CharSequence baseUrl, Map<?, ?> params, boolean encode) {\n        String url = append(baseUrl, params);\n        return head(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start a 'GET' request to the given URL along with the query params\n     *\n     * @param baseUrl\n     * @param encode true to encode the full URL\n     * @param params the name/value query parameter pairs to include\n     *               as part of the baseUrl\n     * @see #append(CharSequence, Object...)\n     * @see #encode(CharSequence)\n     * @return request\n     */\n    public static HttpRequest head(CharSequence baseUrl,\n                                   boolean encode, Object... params) {\n        String url = append(baseUrl, params);\n        return head(encode ? encode(url) : url);\n    }\n\n    /**\n     * Start an 'OPTIONS' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest options(CharSequence url)\n        throws HttpException {\n        return new HttpRequest(url, METHOD_OPTIONS);\n    }\n\n    /**\n     * Start an 'OPTIONS' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest options(URL url) throws HttpException {\n        return new HttpRequest(url, METHOD_OPTIONS);\n    }\n\n    /**\n     * Start a 'TRACE' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest trace(CharSequence url)\n        throws HttpException {\n        return new HttpRequest(url, METHOD_TRACE);\n    }\n\n    /**\n     * Start a 'TRACE' request to the given URL\n     *\n     * @param url\n     * @return request\n     * @throws HttpException\n     */\n    public static HttpRequest trace(URL url) throws HttpException {\n        return new HttpRequest(url, METHOD_TRACE);\n    }\n\n    /**\n     * Set the 'http.keepAlive' property to the given value.\n     * <p>\n     * This setting will apply to all requests.\n     *\n     * @param keepAlive\n     */\n    public static void keepAlive(boolean keepAlive) {\n        setProperty(\"http.keepAlive\", Boolean.toString(keepAlive));\n    }\n\n    /**\n     * Set the 'http.maxConnections' property to the given value.\n     * <p>\n     * This setting will apply to all requests.\n     *\n     * @param maxConnections\n     */\n    public static void maxConnections(int maxConnections) {\n        setProperty(\"http.maxConnections\", Integer.toString(maxConnections));\n    }\n\n    /**\n     * Set the 'http.proxyHost' and 'https.proxyHost' properties to the given host\n     * value.\n     * <p>\n     * This setting will apply to all requests.\n     *\n     * @param host\n     */\n    public static void proxyHost(String host) {\n        setProperty(\"http.proxyHost\", host);\n        setProperty(\"https.proxyHost\", host);\n    }\n\n    /**\n     * Set the 'http.proxyPort' and 'https.proxyPort' properties to the given port\n     * number.\n     * <p>\n     * This setting will apply to all requests.\n     *\n     * @param port\n     */\n    public static void proxyPort(int port) {\n        String portValue = Integer.toString(port);\n        setProperty(\"http.proxyPort\", portValue);\n        setProperty(\"https.proxyPort\", portValue);\n    }\n\n    /**\n     * Set the 'http.nonProxyHosts' property to the given host values.\n     * <p>\n     * Hosts will be separated by a '|' character.\n     * <p>\n     * This setting will apply to all requests.\n     *\n     * @param hosts\n     */\n    public static void nonProxyHosts(String... hosts) {\n        if (hosts != null && hosts.length > 0) {\n            StringBuilder separated = new StringBuilder();\n            int last = hosts.length - 1;\n            for (int i = 0; i < last; i++) {\n                separated.append(hosts[i]).append('|');\n            }\n            separated.append(hosts[last]);\n            setProperty(\"http.nonProxyHosts\", separated.toString());\n        } else {\n            setProperty(\"http.nonProxyHosts\", null);\n        }\n    }\n\n    /**\n     * Set property to given value.\n     * <p>\n     * Specifying a null value will cause the property to be cleared\n     *\n     * @param name\n     * @param value\n     * @return previous value\n     */\n    private static String setProperty(String name, String value) {\n        PrivilegedAction<String> action;\n        if (value != null) {\n            action = () -> System.setProperty(name, value);\n        } else {\n            action = () -> System.clearProperty(name);\n        }\n        return AccessController.doPrivileged(action);\n    }\n\n    private HttpURLConnection connection = null;\n\n    private final URL url;\n\n    private final String requestMethod;\n\n    private RequestOutputStream output;\n\n    private boolean multipart;\n\n    private boolean form;\n\n    private boolean ignoreCloseExceptions = true;\n\n    private boolean decompress = false;\n\n    private int bufferSize = 8192;\n\n    private long totalSize = -1;\n\n    private long totalWritten = 0;\n\n    private String httpProxyHost;\n\n    private int httpProxyPort;\n\n    private UploadProgress progress = UploadProgress.DEFAULT;\n\n    /**\n     * Create HTTP connection wrapper\n     *\n     * @param url Remote resource URL.\n     * @param method HTTP request method (e.g., \"GET\", \"POST\").\n     * @throws HttpException\n     */\n    public HttpRequest(CharSequence url, String method) throws HttpException {\n        try {\n            this.url = new URL(url.toString());\n        } catch (MalformedURLException e) {\n            throw new HttpException(e);\n        }\n        this.requestMethod = method;\n    }\n\n    /**\n     * Create HTTP connection wrapper\n     *\n     * @param url Remote resource URL.\n     * @param method HTTP request method (e.g., \"GET\", \"POST\").\n     * @throws HttpException\n     */\n    public HttpRequest(URL url, String method)\n        throws HttpException {\n        this.url = url;\n        this.requestMethod = method;\n    }\n\n    private Proxy createProxy() {\n        return new Proxy(HTTP, new InetSocketAddress(httpProxyHost, httpProxyPort));\n    }\n\n    private HttpURLConnection createConnection() {\n        try {\n            HttpURLConnection conn;\n            if (httpProxyHost != null) {\n                conn = connectionFactory.create(url, createProxy());\n            } else {\n                conn = connectionFactory.create(url);\n            }\n            conn.setRequestMethod(requestMethod);\n            return conn;\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return method() + ' ' + url();\n    }\n\n    /**\n     * Get underlying connection\n     *\n     * @return connection\n     */\n    public HttpURLConnection getConnection() {\n        if (connection == null) {\n            connection = createConnection();\n        }\n        return connection;\n    }\n\n    /**\n     * Set whether or not to ignore exceptions that occur from calling\n     * {@link Closeable#close()}\n     * <p>\n     * The default value of this setting is <code>true</code>\n     *\n     * @param ignore\n     * @return this request\n     */\n    public HttpRequest ignoreCloseExceptions(boolean ignore) {\n        ignoreCloseExceptions = ignore;\n        return this;\n    }\n\n    /**\n     * Get whether or not exceptions thrown by {@link Closeable#close()} are\n     * ignored\n     *\n     * @return true if ignoring, false if throwing\n     */\n    public boolean ignoreCloseExceptions() {\n        return ignoreCloseExceptions;\n    }\n\n    /**\n     * Get the status code of the response\n     *\n     * @return the response code\n     * @throws HttpException\n     */\n    public int code() throws HttpException {\n        try {\n            closeOutput();\n            return getConnection().getResponseCode();\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n    }\n\n    /**\n     * Set the value of the given {@link AtomicInteger} to the status code of the\n     * response\n     *\n     * @param output\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest code(AtomicInteger output) throws HttpException {\n        output.set(code());\n        return this;\n    }\n\n    /**\n     * Is the response code a 200 OK?\n     *\n     * @return true if 200, false otherwise\n     * @throws HttpException\n     */\n    public boolean ok() throws HttpException {\n        return HTTP_OK == code();\n    }\n\n    /**\n     * Is the response code a 201 Created?\n     *\n     * @return true if 201, false otherwise\n     * @throws HttpException\n     */\n    public boolean created() throws HttpException {\n        return HTTP_CREATED == code();\n    }\n\n    /**\n     * Is the response code a 204 No Content?\n     *\n     * @return true if 204, false otherwise\n     * @throws HttpException\n     */\n    public boolean noContent() throws HttpException {\n        return HTTP_NO_CONTENT == code();\n    }\n\n    /**\n     * Is the response code a 500 Internal Server Error?\n     *\n     * @return true if 500, false otherwise\n     * @throws HttpException\n     */\n    public boolean serverError() throws HttpException {\n        return HTTP_INTERNAL_ERROR == code();\n    }\n\n    /**\n     * Is the response code a 400 Bad Request?\n     *\n     * @return true if 400, false otherwise\n     * @throws HttpException\n     */\n    public boolean badRequest() throws HttpException {\n        return HTTP_BAD_REQUEST == code();\n    }\n\n    /**\n     * Is the response code a 404 Not Found?\n     *\n     * @return true if 404, false otherwise\n     * @throws HttpException\n     */\n    public boolean notFound() throws HttpException {\n        return HTTP_NOT_FOUND == code();\n    }\n\n    /**\n     * Is the response code a 304 Not Modified?\n     *\n     * @return true if 304, false otherwise\n     * @throws HttpException\n     */\n    public boolean notModified() throws HttpException {\n        return HTTP_NOT_MODIFIED == code();\n    }\n\n    /**\n     * Gets the response status enum\n     *\n     * @return a enum for response http status\n     */\n    public HttpStatus status() {\n        return HttpStatus.valueOf(code());\n    }\n\n    /**\n     * Get status message of the response\n     * @return message   OK\n     * @throws HttpException\n     */\n    public String message() throws HttpException {\n        try {\n            closeOutput();\n            return getConnection().getResponseMessage();\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n    }\n\n    /**\n     * Disconnect the connection\n     *\n     * @return this request\n     */\n    public HttpRequest disconnect() {\n        getConnection().disconnect();\n        return this;\n    }\n\n    /**\n     * Set chunked streaming mode to the given size\n     *\n     * @param size\n     * @return this request\n     */\n    public HttpRequest chunk(int size) {\n        getConnection().setChunkedStreamingMode(size);\n        return this;\n    }\n\n    /**\n     * Set the size used when buffering and copying between streams\n     * <p>\n     * This size is also used for send and receive buffers created for both char\n     * and byte arrays\n     * <p>\n     * The default buffer size is 8,192 bytes\n     *\n     * @param size\n     * @return this request\n     */\n    public HttpRequest bufferSize(int size) {\n        if (size < 1) {\n            throw new IllegalArgumentException(\"size must be greater than zero\");\n        }\n        bufferSize = size;\n        return this;\n    }\n\n    /**\n     * Get the configured buffer size\n     * <p>\n     * The default buffer size is 8,192 bytes\n     *\n     * @return buffer size\n     */\n    public int bufferSize() {\n        return bufferSize;\n    }\n\n    /**\n     * Set whether or not the response body should be automatically decompress\n     * when read from.\n     * <p>\n     * This will only affect requests that have the 'Content-Encoding' response\n     * header set to 'gzip'.\n     * <p>\n     * This causes all receive methods to use a {@link GZIPInputStream} when\n     * applicable so that higher level streams and readers can read the data\n     * decompress.\n     * <p>\n     * Setting this option does not cause any request headers to be set\n     * automatically so {@link #acceptGzipEncoding()} should be used in\n     * conjunction with this setting to tell the server to gzip the response.\n     *\n     * @param decompress\n     * @return this request\n     */\n    public HttpRequest decompress(boolean decompress) {\n        this.decompress = decompress;\n        return this;\n    }\n\n    /**\n     * Create byte array output stream\n     *\n     * @return stream\n     */\n    protected ByteArrayOutputStream byteStream() {\n        int size = contentLength();\n        if (size > 0) {\n            return new ByteArrayOutputStream(size);\n        } else {\n            return new ByteArrayOutputStream();\n        }\n    }\n\n    /**\n     * Get response as {@link String} in given character set\n     * <p>\n     * This will fall back to using the UTF-8 character set if the given charset\n     * is null\n     *\n     * @param charset\n     * @return string\n     * @throws HttpException\n     */\n    public String body(String charset) throws HttpException {\n        ByteArrayOutputStream output = byteStream();\n        try {\n            copy(buffer(), output);\n            return output.toString(getValidCharset(charset));\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n    }\n\n    /**\n     * Get response as {@link String} using character set returned from\n     * {@link #charset()}\n     *\n     * @return string\n     * @throws HttpException\n     */\n    public String body() throws HttpException {\n        return body(charset());\n    }\n\n    /**\n     * Get the response body as a {@link String} and set it as the value of the\n     * given reference.\n     *\n     * @param output\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest body(AtomicReference<String> output)\n        throws HttpException {\n        output.set(body());\n        return this;\n    }\n\n    /**\n     * Get the response body as a {@link String} and set it as the value of the\n     * given reference.\n     *\n     * @param output\n     * @param charset\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest body(AtomicReference<String> output, String charset)\n        throws HttpException {\n        output.set(body(charset));\n        return this;\n    }\n\n    /**\n     * Is the response body empty?\n     *\n     * @return true if the Content-Length response header is 0, false otherwise\n     * @throws HttpException\n     */\n    public boolean isBodyEmpty() throws HttpException {\n        return contentLength() == 0;\n    }\n\n    /**\n     * Get response as byte array\n     *\n     * @return byte array\n     */\n    public byte[] bytes() {\n        ByteArrayOutputStream output = byteStream();\n        copy(buffer(), output);\n        return output.toByteArray();\n    }\n\n    /**\n     * Get response in a buffered stream\n     *\n     * @see #bufferSize(int)\n     * @return stream\n     * @throws HttpException\n     */\n    public BufferedInputStream buffer() throws HttpException {\n        return new BufferedInputStream(stream(), bufferSize);\n    }\n\n    /**\n     * Get stream to response body\n     *\n     * @return stream\n     * @throws HttpException\n     */\n    public InputStream stream() throws HttpException {\n        InputStream stream;\n        if (code() < HTTP_BAD_REQUEST) {\n            try {\n                stream = getConnection().getInputStream();\n            } catch (IOException e) {\n                throw new HttpException(e);\n            }\n        } else {\n            stream = getConnection().getErrorStream();\n            if (stream == null) {\n                try {\n                    stream = getConnection().getInputStream();\n                } catch (IOException e) {\n                    if (contentLength() > 0) {\n                        throw new HttpException(e);\n                    } else {\n                        stream = new ByteArrayInputStream(new byte[0]);\n                    }\n                }\n            }\n        }\n\n        if (!decompress || !ENCODING_GZIP.equals(contentEncoding())) {\n            return stream;\n        } else {\n            try {\n                return new GZIPInputStream(stream);\n            } catch (IOException e) {\n                throw new HttpException(e);\n            }\n        }\n    }\n\n    /**\n     * Get reader to response body using given character set.\n     * <p>\n     * This will fall back to using the UTF-8 character set if the given charset\n     * is null\n     *\n     * @param charset\n     * @return reader\n     * @throws HttpException\n     */\n    public InputStreamReader reader(String charset)\n        throws HttpException {\n        try {\n            return new InputStreamReader(stream(), getValidCharset(charset));\n        } catch (UnsupportedEncodingException e) {\n            throw new HttpException(e);\n        }\n    }\n\n    /**\n     * Get reader to response body using the character set returned from\n     * {@link #charset()}\n     *\n     * @return reader\n     * @throws HttpException\n     */\n    public InputStreamReader reader() throws HttpException {\n        return reader(charset());\n    }\n\n    /**\n     * Get buffered reader to response body using the given character set r and\n     * the configured buffer size\n     *\n     *\n     * @see #bufferSize(int)\n     * @param charset\n     * @return reader\n     * @throws HttpException\n     */\n    public BufferedReader bufferedReader(String charset)\n        throws HttpException {\n        return new BufferedReader(reader(charset), bufferSize);\n    }\n\n    /**\n     * Get buffered reader to response body using the character set returned from\n     * {@link #charset()} and the configured buffer size\n     *\n     * @see #bufferSize(int)\n     * @return reader\n     * @throws HttpException\n     */\n    public BufferedReader bufferedReader() throws HttpException {\n        return bufferedReader(charset());\n    }\n\n    /**\n     * Stream response body to file\n     *\n     * @param file\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest receive(File file) throws HttpException {\n        OutputStream output;\n        try {\n            output = new BufferedOutputStream(new FileOutputStream(file), bufferSize);\n        } catch (FileNotFoundException e) {\n            throw new HttpException(e);\n        }\n\n        return new CloseOperation<HttpRequest>(output, ignoreCloseExceptions) {\n            @Override\n            protected HttpRequest run() throws HttpException {\n                return receive(output);\n            }\n        }.call();\n    }\n\n    /**\n     * Stream response to given output stream\n     *\n     * @param output\n     * @return this request\n     */\n    public HttpRequest receive(OutputStream output) {\n        return copy(buffer(), output);\n    }\n\n    /**\n     * Stream response to given print stream\n     *\n     * @param output\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest receive(PrintStream output) throws HttpException {\n        return receive((OutputStream) output);\n    }\n\n    /**\n     * Receive response into the given appendable\n     *\n     * @param appendable\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest receive(Appendable appendable) throws HttpException {\n        BufferedReader reader = bufferedReader();\n\n        return new CloseOperation<HttpRequest>(reader, ignoreCloseExceptions) {\n            @Override\n            public HttpRequest run() throws IOException {\n                CharBuffer buffer = CharBuffer.allocate(bufferSize);\n                int read;\n                while ((read = reader.read(buffer)) != -1) {\n                    buffer.rewind();\n                    appendable.append(buffer, 0, read);\n                    buffer.rewind();\n                }\n                return HttpRequest.this;\n            }\n        }.call();\n    }\n\n    /**\n     * Receive response into the given writer\n     *\n     * @param writer\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest receive(Writer writer) throws HttpException {\n        BufferedReader reader = bufferedReader();\n\n        return new CloseOperation<HttpRequest>(reader, ignoreCloseExceptions) {\n            @Override\n            public HttpRequest run() {\n                return copy(reader, writer);\n            }\n        }.call();\n    }\n\n    /**\n     * Set read timeout on connection to given value\n     *\n     * @param timeout\n     * @return this request\n     */\n    public HttpRequest readTimeout(int timeout) {\n        getConnection().setReadTimeout(timeout);\n        return this;\n    }\n\n    /**\n     * Set connect timeout on connection to given value\n     *\n     * @param timeout\n     * @return this request\n     */\n    public HttpRequest connectTimeout(int timeout) {\n        getConnection().setConnectTimeout(timeout);\n        return this;\n    }\n\n    /**\n     * Set header name to given value\n     *\n     * @param name\n     * @param value\n     * @return this request\n     */\n    public HttpRequest header(String name, String value) {\n        getConnection().setRequestProperty(name, value);\n        return this;\n    }\n\n    /**\n     * Set header name to given value\n     *\n     * @param name\n     * @param value\n     * @return this request\n     */\n    public HttpRequest header(String name, Number value) {\n        return header(name, value != null ? value.toString() : null);\n    }\n\n    /**\n     * Set all headers found in given map where the keys are the header names and\n     * the values are the header values\n     *\n     * @param headers\n     * @return this request\n     */\n    public HttpRequest headers(Map<String, String> headers) {\n        if (!headers.isEmpty()) {\n            for (Entry<String, String> header : headers.entrySet()) {\n                header(header);\n            }\n        }\n        return this;\n    }\n\n    /**\n     * Set header to have given entry's key as the name and value as the value\n     *\n     * @param header\n     * @return this request\n     */\n    public HttpRequest header(Entry<String, String> header) {\n        return header(header.getKey(), header.getValue());\n    }\n\n    /**\n     * Get a response header\n     *\n     * @param name\n     * @return response header\n     * @throws HttpException\n     */\n    public String header(String name) throws HttpException {\n        closeOutputQuietly();\n        return getConnection().getHeaderField(name);\n    }\n\n    /**\n     * Get all the response headers\n     *\n     * @return map of response header names to their value(s)\n     * @throws HttpException\n     */\n    public Map<String, List<String>> headers() throws HttpException {\n        closeOutputQuietly();\n        return getConnection().getHeaderFields();\n    }\n\n    /**\n     * Get a date header from the response falling back to returning -1 if the\n     * header is missing or parsing fails\n     *\n     * @param name\n     * @return date, -1 on failures\n     * @throws HttpException\n     */\n    public long dateHeader(String name) throws HttpException {\n        return dateHeader(name, -1L);\n    }\n\n    /**\n     * Get a date header from the response falling back to returning the given\n     * default value if the header is missing or parsing fails\n     *\n     * @param name\n     * @param defaultValue\n     * @return date, default value on failures\n     * @throws HttpException\n     */\n    public long dateHeader(String name, long defaultValue) throws HttpException {\n        closeOutputQuietly();\n        return getConnection().getHeaderFieldDate(name, defaultValue);\n    }\n\n    /**\n     * Get an integer header from the response falling back to returning -1 if the\n     * header is missing or parsing fails\n     *\n     * @param name\n     * @return header value as an integer, -1 when missing or parsing fails\n     * @throws HttpException\n     */\n    public int intHeader(String name) throws HttpException {\n        return intHeader(name, -1);\n    }\n\n    /**\n     * Get an integer header value from the response falling back to the given\n     * default value if the header is missing or if parsing fails\n     *\n     * @param name\n     * @param defaultValue\n     * @return header value as an integer, default value when missing or parsing\n     *         fails\n     * @throws HttpException\n     */\n    public int intHeader(String name, int defaultValue)\n        throws HttpException {\n        closeOutputQuietly();\n        return getConnection().getHeaderFieldInt(name, defaultValue);\n    }\n\n    /**\n     * Get all values of the given header from the response\n     *\n     * @param name\n     * @return non-null but possibly empty array of {@link String} header values\n     */\n    public String[] headers(String name) {\n        Map<String, List<String>> headers = headers();\n        if (headers == null || headers.isEmpty()) {\n            return EMPTY_STRINGS;\n        }\n\n        List<String> values = headers.get(name);\n        if (values != null && !values.isEmpty()) {\n            return values.toArray(new String[0]);\n        } else {\n            return EMPTY_STRINGS;\n        }\n    }\n\n    /**\n     * Get parameter with given name from header value in response\n     *\n     * @param headerName\n     * @param paramName\n     * @return parameter value or null if missing\n     */\n    public String parameter(String headerName, String paramName) {\n        return getParam(header(headerName), paramName);\n    }\n\n    /**\n     * Get all parameters from header value in response\n     * <p>\n     * This will be all key=value pairs after the first ';' that are separated by\n     * a ';'\n     *\n     * @param headerName\n     * @return non-null but possibly empty map of parameter headers\n     */\n    public Map<String, String> parameters(String headerName) {\n        return getParams(header(headerName));\n    }\n\n    /**\n     * Get parameter values from header value\n     *\n     * @param header\n     * @return parameter value or null if none\n     */\n    protected Map<String, String> getParams(String header) {\n        if (header == null || header.length() == 0) {\n            return Collections.emptyMap();\n        }\n\n        int headerLength = header.length();\n        int start = header.indexOf(';') + 1;\n        if (start == 0 || start == headerLength) {\n            return Collections.emptyMap();\n        }\n\n        int end = header.indexOf(';', start);\n        if (end == -1) {\n            end = headerLength;\n        }\n\n        Map<String, String> params = new LinkedHashMap<>();\n        while (start < end) {\n            int nameEnd = header.indexOf('=', start);\n            if (nameEnd != -1 && nameEnd < end) {\n                String name = header.substring(start, nameEnd).trim();\n                if (name.length() > 0) {\n                    String value = header.substring(nameEnd + 1, end).trim();\n                    int length = value.length();\n                    if (length != 0) {\n                        if (length > 2 && '\"' == value.charAt(0)\n                            && '\"' == value.charAt(length - 1)) {\n                            params.put(name, value.substring(1, length - 1));\n                        } else {\n                            params.put(name, value);\n                        }\n                    }\n                }\n            }\n\n            start = end + 1;\n            end = header.indexOf(';', start);\n            if (end == -1) {\n                end = headerLength;\n            }\n        }\n\n        return params;\n    }\n\n    /**\n     * Get parameter value from header value\n     *\n     * @param value\n     * @param paramName\n     * @return parameter value or null if none\n     */\n    protected String getParam(String value, String paramName) {\n        if (value == null || value.length() == 0) {\n            return null;\n        }\n\n        int length = value.length();\n        int start = value.indexOf(';') + 1;\n        if (start == 0 || start == length) {\n            return null;\n        }\n\n        int end = value.indexOf(';', start);\n        if (end == -1) {\n            end = length;\n        }\n\n        while (start < end) {\n            int nameEnd = value.indexOf('=', start);\n            if (nameEnd != -1 && nameEnd < end\n                && paramName.equals(value.substring(start, nameEnd).trim())) {\n                String paramValue = value.substring(nameEnd + 1, end).trim();\n                int valueLength = paramValue.length();\n                if (valueLength != 0) {\n                    if (valueLength > 2 && '\"' == paramValue.charAt(0)\n                        && '\"' == paramValue.charAt(valueLength - 1)) {\n                        return paramValue.substring(1, valueLength - 1);\n                    } else {\n                        return paramValue;\n                    }\n                }\n            }\n\n            start = end + 1;\n            end = value.indexOf(';', start);\n            if (end == -1) {\n                end = length;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Get 'charset' parameter from 'Content-Type' response header\n     *\n     * @return charset or null if none\n     */\n    public String charset() {\n        return parameter(HEADER_CONTENT_TYPE, PARAM_CHARSET);\n    }\n\n    /**\n     * Set the 'User-Agent' header to given value\n     *\n     * @param userAgent\n     * @return this request\n     */\n    public HttpRequest userAgent(String userAgent) {\n        return header(HEADER_USER_AGENT, userAgent);\n    }\n\n    /**\n     * Set the 'Referer' header to given value\n     *\n     * @param referer\n     * @return this request\n     */\n    public HttpRequest referer(String referer) {\n        return header(HEADER_REFERER, referer);\n    }\n\n    /**\n     * Set value of {@link HttpURLConnection#setUseCaches(boolean)}\n     *\n     * @param useCaches\n     * @return this request\n     */\n    public HttpRequest useCaches(boolean useCaches) {\n        getConnection().setUseCaches(useCaches);\n        return this;\n    }\n\n    /**\n     * Set the 'Accept-Encoding' header to given value\n     *\n     * @param acceptEncoding\n     * @return this request\n     */\n    public HttpRequest acceptEncoding(String acceptEncoding) {\n        return header(HEADER_ACCEPT_ENCODING, acceptEncoding);\n    }\n\n    /**\n     * Set the 'Accept-Encoding' header to 'gzip'\n     *\n     * @see #decompress(boolean)\n     * @return this request\n     */\n    public HttpRequest acceptGzipEncoding() {\n        return acceptEncoding(ENCODING_GZIP);\n    }\n\n    /**\n     * Set the 'Accept-Charset' header to given value\n     *\n     * @param acceptCharset\n     * @return this request\n     */\n    public HttpRequest acceptCharset(String acceptCharset) {\n        return header(HEADER_ACCEPT_CHARSET, acceptCharset);\n    }\n\n    /**\n     * Get the 'Content-Encoding' header from the response\n     *\n     * @return this request\n     */\n    public String contentEncoding() {\n        return header(HEADER_CONTENT_ENCODING);\n    }\n\n    /**\n     * Get the 'Server' header from the response\n     *\n     * @return server\n     */\n    public String server() {\n        return header(HEADER_SERVER);\n    }\n\n    /**\n     * Get the 'Date' header from the response\n     *\n     * @return date value, -1 on failures\n     */\n    public long date() {\n        return dateHeader(HEADER_DATE);\n    }\n\n    /**\n     * Get the 'Cache-Control' header from the response\n     *\n     * @return cache control\n     */\n    public String cacheControl() {\n        return header(HEADER_CACHE_CONTROL);\n    }\n\n    /**\n     * Get the 'ETag' header from the response\n     *\n     * @return entity tag\n     */\n    public String eTag() {\n        return header(HEADER_ETAG);\n    }\n\n    /**\n     * Get the 'Expires' header from the response\n     *\n     * @return expires value, -1 on failures\n     */\n    public long expires() {\n        return dateHeader(HEADER_EXPIRES);\n    }\n\n    /**\n     * Get the 'Last-Modified' header from the response\n     *\n     * @return last modified value, -1 on failures\n     */\n    public long lastModified() {\n        return dateHeader(HEADER_LAST_MODIFIED);\n    }\n\n    /**\n     * Get the 'Location' header from the response\n     *\n     * @return location\n     */\n    public String location() {\n        return header(HEADER_LOCATION);\n    }\n\n    /**\n     * Set the 'Authorization' header to given value\n     *\n     * @param authorization\n     * @return this request\n     */\n    public HttpRequest authorization(String authorization) {\n        return header(HEADER_AUTHORIZATION, authorization);\n    }\n\n    /**\n     * Set the 'Proxy-Authorization' header to given value\n     *\n     * @param proxyAuthorization\n     * @return this request\n     */\n    public HttpRequest proxyAuthorization(String proxyAuthorization) {\n        return header(HEADER_PROXY_AUTHORIZATION, proxyAuthorization);\n    }\n\n    /**\n     * Set the 'Authorization' header to given values in Basic authentication\n     * format\n     *\n     * @param name\n     * @param password\n     * @return this request\n     */\n    public HttpRequest basic(String name, String password) {\n        byte[] data = (name + ':' + password).getBytes(StandardCharsets.US_ASCII);\n        return authorization(\"Basic \" + Base64.getEncoder().encodeToString(data));\n    }\n\n    /**\n     * Set the 'Proxy-Authorization' header to given values in Basic authentication\n     * format\n     *\n     * @param name\n     * @param password\n     * @return this request\n     */\n    public HttpRequest proxyBasic(String name, String password) {\n        byte[] data = (name + ':' + password).getBytes(StandardCharsets.US_ASCII);\n        return proxyAuthorization(\"Basic \" + Base64.getEncoder().encodeToString(data));\n    }\n\n    /**\n     * Set the 'If-Modified-Since' request header to the given value\n     *\n     * @param ifModifiedSince\n     * @return this request\n     */\n    public HttpRequest ifModifiedSince(long ifModifiedSince) {\n        getConnection().setIfModifiedSince(ifModifiedSince);\n        return this;\n    }\n\n    /**\n     * Set the 'If-None-Match' request header to the given value\n     *\n     * @param ifNoneMatch\n     * @return this request\n     */\n    public HttpRequest ifNoneMatch(String ifNoneMatch) {\n        return header(HEADER_IF_NONE_MATCH, ifNoneMatch);\n    }\n\n    /**\n     * Set the 'Content-Type' request header to the given value\n     *\n     * @param contentType\n     * @return this request\n     */\n    public HttpRequest contentType(String contentType) {\n        return contentType(contentType, null);\n    }\n\n    /**\n     * Set the 'Content-Type' request header to the given value and charset\n     *\n     * @param contentType\n     * @param charset\n     * @return this request\n     */\n    public HttpRequest contentType(String contentType, String charset) {\n        if (charset != null && charset.length() > 0) {\n            String separator = \"; \" + PARAM_CHARSET + '=';\n            return header(HEADER_CONTENT_TYPE, contentType + separator + charset);\n        } else {\n            return header(HEADER_CONTENT_TYPE, contentType);\n        }\n    }\n\n    /**\n     * Get the 'Content-Type' header from the response\n     *\n     * @return response header value\n     */\n    public String contentType() {\n        return header(HEADER_CONTENT_TYPE);\n    }\n\n    /**\n     * Get the 'Content-Length' header from the response\n     *\n     * @return response header value\n     */\n    public int contentLength() {\n        return intHeader(HEADER_CONTENT_LENGTH);\n    }\n\n    /**\n     * Set the 'Content-Length' request header to the given value\n     *\n     * @param contentLength\n     * @return this request\n     */\n    public HttpRequest contentLength(String contentLength) {\n        return contentLength(Integer.parseInt(contentLength));\n    }\n\n    /**\n     * Set the 'Content-Length' request header to the given value\n     *\n     * @param contentLength\n     * @return this request\n     */\n    public HttpRequest contentLength(int contentLength) {\n        getConnection().setFixedLengthStreamingMode(contentLength);\n        return this;\n    }\n\n    /**\n     * Set the 'Accept' header to given value\n     *\n     * @param accept\n     * @return this request\n     */\n    public HttpRequest accept(String accept) {\n        return header(HEADER_ACCEPT, accept);\n    }\n\n    /**\n     * Set the 'Accept' header to 'application/json'\n     *\n     * @return this request\n     */\n    public HttpRequest acceptJson() {\n        return accept(ContentType.APPLICATION_JSON.value());\n    }\n\n    /**\n     * Copy from input stream to output stream\n     *\n     * @param input\n     * @param output\n     * @return this request\n     * @throws IOException\n     */\n    protected HttpRequest copy(InputStream input, OutputStream output) {\n        return new CloseOperation<HttpRequest>(input, ignoreCloseExceptions) {\n            @Override\n            public HttpRequest run() throws IOException {\n                byte[] buffer = new byte[bufferSize];\n                int read;\n                while ((read = input.read(buffer)) != -1) {\n                    output.write(buffer, 0, read);\n                    totalWritten += read;\n                    progress.onUpload(totalWritten, totalSize);\n                }\n                return HttpRequest.this;\n            }\n        }.call();\n    }\n\n    /**\n     * Copy from reader to writer\n     *\n     * @param input\n     * @param output\n     * @return this request\n     * @throws IOException\n     */\n    protected HttpRequest copy(Reader input, Writer output) {\n        return new CloseOperation<HttpRequest>(input, ignoreCloseExceptions) {\n            @Override\n            public HttpRequest run() throws IOException {\n                char[] buffer = new char[bufferSize];\n                int read;\n                while ((read = input.read(buffer)) != -1) {\n                    output.write(buffer, 0, read);\n                    totalWritten += read;\n                    progress.onUpload(totalWritten, -1);\n                }\n                return HttpRequest.this;\n            }\n        }.call();\n    }\n\n    /**\n     * Set the UploadProgress callback for this request\n     *\n     * @param callback\n     * @return this request\n     */\n    public HttpRequest progress(UploadProgress callback) {\n        if (callback == null) {\n            progress = UploadProgress.DEFAULT;\n        } else {\n            progress = callback;\n        }\n        return this;\n    }\n\n    private HttpRequest incrementTotalSize(long size) {\n        if (totalSize == -1) {\n            totalSize = 0;\n        }\n        totalSize += size;\n        return this;\n    }\n\n    /**\n     * Close output stream\n     *\n     * @return this request\n     * @throws HttpException\n     * @throws IOException\n     */\n    protected HttpRequest closeOutput() throws IOException {\n        progress(null);\n\n        if (output == null) {\n            return this;\n        }\n\n        if (multipart) {\n            output.write(CRLF + \"--\" + BOUNDARY + \"--\" + CRLF);\n        }\n        if (ignoreCloseExceptions) {\n            try {\n                output.close();\n            } catch (IOException ignored) {\n                ignored.printStackTrace(); // ignored\n            }\n        } else {\n            output.close();\n        }\n        output = null;\n        return this;\n    }\n\n    /**\n     * Call {@link #closeOutput()} and re-throw a caught {@link IOException}s as\n     * an {@link HttpException}\n     *\n     * @return this request\n     * @throws HttpException\n     */\n    protected HttpRequest closeOutputQuietly() throws HttpException {\n        try {\n            return closeOutput();\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n    }\n\n    /**\n     * Open output stream\n     *\n     * @return this request\n     * @throws IOException\n     */\n    protected HttpRequest openOutput() throws IOException {\n        if (output != null) {\n            return this;\n        }\n        getConnection().setDoOutput(true);\n        String charset = getParam(getConnection().getRequestProperty(HEADER_CONTENT_TYPE), PARAM_CHARSET);\n        output = new RequestOutputStream(getConnection().getOutputStream(), charset, bufferSize);\n        return this;\n    }\n\n    /**\n     * Start part of a multipart\n     *\n     * @return this request\n     * @throws IOException\n     */\n    protected HttpRequest startPart() throws IOException {\n        if (!multipart) {\n            multipart = true;\n            contentType(CONTENT_TYPE_MULTIPART).openOutput();\n            output.write(\"--\" + BOUNDARY + CRLF);\n        } else {\n            output.write(CRLF + \"--\" + BOUNDARY + CRLF);\n        }\n        return this;\n    }\n\n    /**\n     * Write part header\n     *\n     * @param name\n     * @param filename\n     * @return this request\n     * @throws IOException\n     */\n    protected HttpRequest writePartHeader(String name, String filename) {\n        return writePartHeader(name, filename, null);\n    }\n\n    /**\n     * Write part header\n     *\n     * @param name\n     * @param filename\n     * @param contentType\n     * @return this request\n     * @throws IOException\n     */\n    protected HttpRequest writePartHeader(String name, String filename, String contentType) {\n        StringBuilder partBuffer = new StringBuilder();\n        partBuffer.append(\"form-data; name=\\\"\").append(name);\n        if (filename != null) {\n            partBuffer.append(\"\\\"; filename=\\\"\").append(filename);\n        }\n        partBuffer.append('\"');\n        partHeader(\"Content-Disposition\", partBuffer.toString());\n        if (contentType != null) {\n            partHeader(HEADER_CONTENT_TYPE, contentType);\n        }\n        return send(CRLF);\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param part\n     * @return this request\n     */\n    public HttpRequest part(String name, String part) {\n        return part(name, null, part);\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param filename\n     * @param part\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest part(String name, String filename, String part)\n        throws HttpException {\n        return part(name, filename, null, part);\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param filename\n     * @param contentType value of the Content-Type part header\n     * @param part\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest part(String name, String filename, String contentType, String part)\n        throws HttpException {\n        try {\n            startPart();\n            writePartHeader(name, filename, contentType);\n            output.write(part);\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n        return this;\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param part\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest part(String name, Number part)\n        throws HttpException {\n        return part(name, null, part);\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param filename\n     * @param part\n     * @return this request\n     */\n    public HttpRequest part(String name, String filename, Number part) {\n        return part(name, filename, Objects.toString(part, null));\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param part\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest part(String name, File part)\n        throws HttpException {\n        return part(name, null, part);\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param filename\n     * @param part\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest part(String name, String filename, File part)\n        throws HttpException {\n        return part(name, filename, null, part);\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param filename\n     * @param contentType value of the Content-Type part header\n     * @param part\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest part(String name, String filename, String contentType, File part)\n        throws HttpException {\n        InputStream stream;\n        try {\n            stream = new BufferedInputStream(new FileInputStream(part));\n            incrementTotalSize(part.length());\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n        return part(name, filename, contentType, stream);\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param part\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest part(String name, InputStream part)\n        throws HttpException {\n        return part(name, null, null, part);\n    }\n\n    /**\n     * Write part of a multipart request to the request body\n     *\n     * @param name\n     * @param filename\n     * @param contentType value of the Content-Type part header\n     * @param part\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest part(String name, String filename, String contentType, InputStream part)\n        throws HttpException {\n        try {\n            startPart();\n            writePartHeader(name, filename, contentType);\n            copy(part, output);\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n        return this;\n    }\n\n    /**\n     * Write a multipart header to the response body\n     *\n     * @param name\n     * @param value\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest partHeader(String name, String value)\n        throws HttpException {\n        return send(name).send(\": \").send(value).send(CRLF);\n    }\n\n    /**\n     * Write contents of file to request body\n     *\n     * @param input\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest send(File input) throws HttpException {\n        InputStream stream;\n        try {\n            stream = new BufferedInputStream(new FileInputStream(input));\n            incrementTotalSize(input.length());\n        } catch (FileNotFoundException e) {\n            throw new HttpException(e);\n        }\n        return send(stream);\n    }\n\n    /**\n     * Write byte array to request body\n     *\n     * @param input\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest send(byte[] input) throws HttpException {\n        if (input == null) {\n            return this;\n        }\n        incrementTotalSize(input.length);\n        return send(new ByteArrayInputStream(input));\n    }\n\n    /**\n     * Write stream to request body\n     * <p>\n     * The given stream will be closed once sending completes\n     *\n     * @param input\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest send(InputStream input) throws HttpException {\n        try {\n            openOutput();\n            copy(input, output);\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n        return this;\n    }\n\n    /**\n     * Write reader to request body\n     * <p>\n     * The given reader will be closed once sending completes\n     *\n     * @param input\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest send(Reader input) throws HttpException {\n        try {\n            openOutput();\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n\n        Writer writer = new OutputStreamWriter(output, output.encoder.charset());\n\n        return new FlushOperation<HttpRequest>(writer) {\n            @Override\n            protected HttpRequest run() {\n                return copy(input, writer);\n            }\n        }.call();\n    }\n\n    /**\n     * Write char sequence to request body\n     * <p>\n     * The charset configured via {@link #contentType(String)} will be used and\n     * UTF-8 will be used if it is unset.\n     *\n     * @param value\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest send(CharSequence value) throws HttpException {\n        try {\n            openOutput();\n            output.write(value.toString());\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n        return this;\n    }\n\n    /**\n     * Create writer to request output stream\n     *\n     * @return writer\n     * @throws HttpException\n     */\n    public OutputStreamWriter writer() throws HttpException {\n        try {\n            openOutput();\n            return new OutputStreamWriter(output, output.encoder.charset());\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n    }\n\n    /**\n     * Write the values in the map as form data to the request body\n     * <p>\n     * The pairs specified will be URL-encoded in UTF-8 and sent with the\n     * 'application/x-www-form-urlencoded' content-type\n     *\n     * @param values\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest form(Map<?, ?> values) throws HttpException {\n        return form(values, Files.UTF_8);\n    }\n\n    /**\n     * Write the key and value in the entry as form data to the request body\n     * <p>\n     * The pair specified will be URL-encoded in UTF-8 and sent with the\n     * 'application/x-www-form-urlencoded' content-type\n     *\n     * @param entry\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest form(Entry<?, ?> entry) throws HttpException {\n        return form(entry, Files.UTF_8);\n    }\n\n    /**\n     * Write the key and value in the entry as form data to the request body\n     * <p>\n     * The pair specified will be URL-encoded and sent with the\n     * 'application/x-www-form-urlencoded' content-type\n     *\n     * @param entry\n     * @param charset\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest form(Entry<?, ?> entry, String charset)\n        throws HttpException {\n        return form(entry.getKey(), entry.getValue(), charset);\n    }\n\n    /**\n     * Write the name/value pair as form data to the request body\n     * <p>\n     * The pair specified will be URL-encoded in UTF-8 and sent with the\n     * 'application/x-www-form-urlencoded' content-type\n     *\n     * @param name\n     * @param value\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest form(Object name, Object value) throws HttpException {\n        return form(name, value, Files.UTF_8);\n    }\n\n    /**\n     * Write the name/value pair as form data to the request body\n     * <p>\n     * The values specified will be URL-encoded and sent with the\n     * 'application/x-www-form-urlencoded' content-type\n     *\n     * @param name\n     * @param value\n     * @param charset\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest form(Object name, Object value, String charset)\n        throws HttpException {\n        boolean first = !form;\n        if (first) {\n            contentType(ContentType.APPLICATION_FORM_URLENCODED.value(), charset);\n            form = true;\n        }\n        charset = getValidCharset(charset);\n        try {\n            openOutput();\n            if (!first) {\n                output.write('&');\n            }\n            output.write(URLEncoder.encode(name.toString(), charset));\n            output.write('=');\n            if (value != null) {\n                output.write(URLEncoder.encode(value.toString(), charset));\n            }\n        } catch (IOException e) {\n            throw new HttpException(e);\n        }\n        return this;\n    }\n\n    /**\n     * Write the values in the map as encoded form data to the request body\n     *\n     * @param values\n     * @param charset\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest form(Map<?, ?> values, String charset)\n        throws HttpException {\n        if (!values.isEmpty()) {\n            for (Entry<?, ?> entry : values.entrySet()) {\n                form(entry, charset);\n            }\n        }\n        return this;\n    }\n\n    /**\n     * Configure HTTPS connection to trust all certificates\n     * <p>\n     * This method does nothing if the current request is not a HTTPS request\n     *\n     * @return this request\n     * @throws HttpException\n     */\n    public HttpRequest trustAllCerts() throws HttpException {\n        HttpURLConnection connection = getConnection();\n        if (connection instanceof HttpsURLConnection) {\n            ((HttpsURLConnection) connection).setSSLSocketFactory(TRUSTED_FACTORY);\n        }\n        return this;\n    }\n\n    /**\n     * 设置SSLSocketFactory\n     * @param factory SSLSocketFactory\n     * @return this\n     */\n    public HttpRequest setSSLSocketFactory(SSLSocketFactory factory) {\n        HttpURLConnection connection = getConnection();\n        if (connection instanceof HttpsURLConnection) {\n            ((HttpsURLConnection) connection).setSSLSocketFactory(factory);\n        }\n        return this;\n    }\n\n    /**\n     * Configure HTTPS connection to trust all hosts using a custom\n     * {@link HostnameVerifier} that always returns <code>true</code> for each\n     * host verified\n     * <p>\n     * This method does nothing if the current request is not a HTTPS request\n     *\n     * @return this request\n     */\n    public HttpRequest trustAllHosts() {\n        HttpURLConnection connection = getConnection();\n        if (connection instanceof HttpsURLConnection) {\n            ((HttpsURLConnection) connection).setHostnameVerifier(TRUSTED_VERIFIER);\n        }\n        return this;\n    }\n\n    /**\n     * Get the {@link URL} of this request's connection\n     *\n     * @return request URL\n     */\n    public URL url() {\n        return getConnection().getURL();\n    }\n\n    /**\n     * Get the HTTP method of this request\n     *\n     * @return method\n     */\n    public String method() {\n        return getConnection().getRequestMethod();\n    }\n\n    /**\n     * Configure an HTTP proxy on this connection. Use {{@link #proxyBasic(String, String)} if\n     * this proxy requires basic authentication.\n     *\n     * @param proxyHost\n     * @param proxyPort\n     * @return this request\n     */\n    public HttpRequest useProxy(String proxyHost, int proxyPort) {\n        if (connection != null) {\n            throw new IllegalStateException(\"The connection has already been created. This \"\n                         + \"method must be called before reading or writing to the request.\");\n        }\n\n        this.httpProxyHost = proxyHost;\n        this.httpProxyPort = proxyPort;\n        return this;\n    }\n\n    /**\n     * Set whether or not the underlying connection should follow redirects in\n     * the response.\n     *\n     * @param followRedirects - true fo follow redirects, false to not.\n     * @return this request\n     */\n    public HttpRequest followRedirects(boolean followRedirects) {\n        getConnection().setInstanceFollowRedirects(followRedirects);\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/http/HttpStatus.java",
    "content": "package cn.ponfee.commons.http;\n\nimport com.google.common.collect.ImmutableMap;\n\nimport java.util.Map;\n\n/**\n* Enumeration of HTTP status codes.\n*\n* <p>The HTTP status code series can be retrieved via {@link #series()}.\n*\n* @author Arjen Poutsma\n* @author Sebastien Deleuze\n* @author Brian Clozel\n* @since 3.0\n* @see HttpStatus.Series\n* @see <a href=\"http://www.iana.org/assignments/http-status-codes\">HTTP Status Code Registry</a>\n* @see <a href=\"http://en.wikipedia.org/wiki/List_of_HTTP_status_codes\">List of HTTP status codes - Wikipedia</a>\n*/\npublic enum HttpStatus {\n\n    // ---------------------------------------1xx Informational\n    /**\n    * {@code 100 Continue}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.2.1\">HTTP/1.1: Semantics and Content, section 6.2.1</a>\n    */\n    CONTINUE(100, \"Continue\"),\n\n    /**\n    * {@code 101 Switching Protocols}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.2.2\">HTTP/1.1: Semantics and Content, section 6.2.2</a>\n    */\n    SWITCHING_PROTOCOLS(101, \"Switching Protocols\"),\n\n    /**\n    * {@code 102 Processing}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc2518#section-10.1\">WebDAV</a>\n    */\n    PROCESSING(102, \"Processing\"),\n\n    /**\n    * {@code 103 Checkpoint}.\n    * @see <a href=\"http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal\">A proposal for supporting\n    * resumable POST/PUT HTTP requests in HTTP/1.0</a>\n    */\n    CHECKPOINT(103, \"Checkpoint\"),\n\n    // --------------------------------------- 2xx Success\n    /**\n    * {@code 200 OK}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.3.1\">HTTP/1.1: Semantics and Content, section 6.3.1</a>\n    */\n    OK(200, \"OK\"),\n\n    /**\n    * {@code 201 Created}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.3.2\">HTTP/1.1: Semantics and Content, section 6.3.2</a>\n    */\n    CREATED(201, \"Created\"),\n\n    /**\n    * {@code 202 Accepted}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.3.3\">HTTP/1.1: Semantics and Content, section 6.3.3</a>\n    */\n    ACCEPTED(202, \"Accepted\"),\n\n    /**\n    * {@code 203 Non-Authoritative Information}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.3.4\">HTTP/1.1: Semantics and Content, section 6.3.4</a>\n    */\n    NON_AUTHORITATIVE_INFORMATION(203, \"Non-Authoritative Information\"),\n\n    /**\n    * {@code 204 No Content}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.3.5\">HTTP/1.1: Semantics and Content, section 6.3.5</a>\n    */\n    NO_CONTENT(204, \"No Content\"),\n\n    /**\n    * {@code 205 Reset Content}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.3.6\">HTTP/1.1: Semantics and Content, section 6.3.6</a>\n    */\n    RESET_CONTENT(205, \"Reset Content\"),\n\n    /**\n    * {@code 206 Partial Content}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7233#section-4.1\">HTTP/1.1: Range Requests, section 4.1</a>\n    */\n    PARTIAL_CONTENT(206, \"Partial Content\"),\n\n    /**\n    * {@code 207 Multi-Status}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc4918#section-13\">WebDAV</a>\n    */\n    MULTI_STATUS(207, \"Multi-Status\"),\n\n    /**\n    * {@code 208 Already Reported}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc5842#section-7.1\">WebDAV Binding Extensions</a>\n    */\n    ALREADY_REPORTED(208, \"Already Reported\"),\n\n    /**\n    * {@code 226 IM Used}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc3229#section-10.4.1\">Delta encoding in HTTP</a>\n    */\n    IM_USED(226, \"IM Used\"),\n\n    // --------------------------------------- 3xx Redirection\n    /**\n    * {@code 300 Multiple Choices}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.4.1\">HTTP/1.1: Semantics and Content, section 6.4.1</a>\n    */\n    MULTIPLE_CHOICES(300, \"Multiple Choices\"),\n\n    /**\n    * {@code 301 Moved Permanently}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.4.2\">HTTP/1.1: Semantics and Content, section 6.4.2</a>\n    */\n    MOVED_PERMANENTLY(301, \"Moved Permanently\"),\n\n    /**\n    * {@code 302 Found}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.4.3\">HTTP/1.1: Semantics and Content, section 6.4.3</a>\n    */\n    FOUND(302, \"Found\"),\n\n    /**\n    * {@code 303 See Other}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.4.4\">HTTP/1.1: Semantics and Content, section 6.4.4</a>\n    */\n    SEE_OTHER(303, \"See Other\"),\n\n    /**\n    * {@code 304 Not Modified}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7232#section-4.1\">HTTP/1.1: Conditional Requests, section 4.1</a>\n    */\n    NOT_MODIFIED(304, \"Not Modified\"),\n\n    /**\n    * {@code 307 Temporary Redirect}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.4.7\">HTTP/1.1: Semantics and Content, section 6.4.7</a>\n    */\n    TEMPORARY_REDIRECT(307, \"Temporary Redirect\"),\n\n    /**\n    * {@code 308 Permanent Redirect}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7238\">RFC 7238</a>\n    */\n    PERMANENT_REDIRECT(308, \"Permanent Redirect\"),\n\n    // --------------------------------------- 4xx Client Error\n    /**\n    * {@code 400 Bad Request}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.1\">HTTP/1.1: Semantics and Content, section 6.5.1</a>\n    */\n    BAD_REQUEST(400, \"Bad Request\"),\n\n    /**\n    * {@code 401 Unauthorized}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7235#section-3.1\">HTTP/1.1: Authentication, section 3.1</a>\n    */\n    UNAUTHORIZED(401, \"Unauthorized\"),\n\n    /**\n    * {@code 402 Payment Required}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.2\">HTTP/1.1: Semantics and Content, section 6.5.2</a>\n    */\n    PAYMENT_REQUIRED(402, \"Payment Required\"),\n\n    /**\n    * {@code 403 Forbidden}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.3\">HTTP/1.1: Semantics and Content, section 6.5.3</a>\n    */\n    FORBIDDEN(403, \"Forbidden\"),\n\n    /**\n    * {@code 404 Not Found}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.4\">HTTP/1.1: Semantics and Content, section 6.5.4</a>\n    */\n    NOT_FOUND(404, \"Not Found\"),\n\n    /**\n    * {@code 405 Method Not Allowed}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.5\">HTTP/1.1: Semantics and Content, section 6.5.5</a>\n    */\n    METHOD_NOT_ALLOWED(405, \"Method Not Allowed\"),\n\n    /**\n    * {@code 406 Not Acceptable}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.6\">HTTP/1.1: Semantics and Content, section 6.5.6</a>\n    */\n    NOT_ACCEPTABLE(406, \"Not Acceptable\"),\n\n    /**\n    * {@code 407 Proxy Authentication Required}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7235#section-3.2\">HTTP/1.1: Authentication, section 3.2</a>\n    */\n    PROXY_AUTHENTICATION_REQUIRED(407, \"Proxy Authentication Required\"),\n\n    /**\n    * {@code 408 Request Timeout}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.7\">HTTP/1.1: Semantics and Content, section 6.5.7</a>\n    */\n    REQUEST_TIMEOUT(408, \"Request Timeout\"),\n\n    /**\n    * {@code 409 Conflict}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.8\">HTTP/1.1: Semantics and Content, section 6.5.8</a>\n    */\n    CONFLICT(409, \"Conflict\"),\n\n    /**\n    * {@code 410 Gone}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.9\">HTTP/1.1: Semantics and Content, section 6.5.9</a>\n    */\n    GONE(410, \"Gone\"),\n\n    /**\n    * {@code 411 Length Required}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.10\">HTTP/1.1: Semantics and Content, section 6.5.10</a>\n    */\n    LENGTH_REQUIRED(411, \"Length Required\"),\n\n    /**\n    * {@code 412 Precondition failed}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7232#section-4.2\">HTTP/1.1: Conditional Requests, section 4.2</a>\n    */\n    PRECONDITION_FAILED(412, \"Precondition Failed\"),\n\n    /**\n    * {@code 413 Payload Too Large}.\n    * @since 4.1\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.11\">HTTP/1.1: Semantics and Content, section 6.5.11</a>\n    */\n    PAYLOAD_TOO_LARGE(413, \"Payload Too Large\"),\n\n    /**\n    * {@code 414 URI Too Long}.\n    * @since 4.1\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.12\">HTTP/1.1: Semantics and Content, section 6.5.12</a>\n    */\n    URI_TOO_LONG(414, \"URI Too Long\"),\n\n    /**\n    * {@code 415 Unsupported Media Type}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.13\">HTTP/1.1: Semantics and Content, section 6.5.13</a>\n    */\n    UNSUPPORTED_MEDIA_TYPE(415, \"Unsupported Media Type\"),\n\n    /**\n    * {@code 416 Requested Range Not Satisfiable}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7233#section-4.4\">HTTP/1.1: Range Requests, section 4.4</a>\n    */\n    REQUESTED_RANGE_NOT_SATISFIABLE(416, \"Requested range not satisfiable\"),\n\n    /**\n    * {@code 417 Expectation Failed}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.5.14\">HTTP/1.1: Semantics and Content, section 6.5.14</a>\n    */\n    EXPECTATION_FAILED(417, \"Expectation Failed\"),\n\n    /**\n    * {@code 418 I'm a teapot}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc2324#section-2.3.2\">HTCPCP/1.0</a>\n    */\n    I_AM_A_TEAPOT(418, \"I'm a teapot\"),\n\n    /**\n    * {@code 422 Unprocessable Entity}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc4918#section-11.2\">WebDAV</a>\n    */\n    UNPROCESSABLE_ENTITY(422, \"Unprocessable Entity\"),\n\n    /**\n    * {@code 423 Locked}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc4918#section-11.3\">WebDAV</a>\n    */\n    LOCKED(423, \"Locked\"),\n\n    /**\n    * {@code 424 Failed Dependency}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc4918#section-11.4\">WebDAV</a>\n    */\n    FAILED_DEPENDENCY(424, \"Failed Dependency\"),\n\n    /**\n    * {@code 426 Upgrade Required}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc2817#section-6\">Upgrading to TLS Within HTTP/1.1</a>\n    */\n    UPGRADE_REQUIRED(426, \"Upgrade Required\"),\n\n    /**\n    * {@code 428 Precondition Required}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc6585#section-3\">Additional HTTP Status Codes</a>\n    */\n    PRECONDITION_REQUIRED(428, \"Precondition Required\"),\n\n    /**\n    * {@code 429 Too Many Requests}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc6585#section-4\">Additional HTTP Status Codes</a>\n    */\n    TOO_MANY_REQUESTS(429, \"Too Many Requests\"),\n\n    /**\n    * {@code 431 Request Header Fields Too Large}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc6585#section-5\">Additional HTTP Status Codes</a>\n    */\n    REQUEST_HEADER_FIELDS_TOO_LARGE(431, \"Request Header Fields Too Large\"),\n\n    /**\n    * {@code 451 Unavailable For Legal Reasons}.\n    * @see <a href=\"https://tools.ietf.org/html/draft-ietf-httpbis-legally-restricted-status-04\">\n    * An HTTP Status Code to Report Legal Obstacles</a>\n    * @since 4.3\n    */\n    UNAVAILABLE_FOR_LEGAL_REASONS(451, \"Unavailable For Legal Reasons\"),\n\n    // ---------------------------------------5xx Server Error\n\n    /**\n    * {@code 500 Internal Server Error}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.6.1\">HTTP/1.1: Semantics and Content, section 6.6.1</a>\n    */\n    INTERNAL_SERVER_ERROR(500, \"Internal Server Error\"),\n\n    /**\n    * {@code 501 Not Implemented}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.6.2\">HTTP/1.1: Semantics and Content, section 6.6.2</a>\n    */\n    NOT_IMPLEMENTED(501, \"Not Implemented\"),\n\n    /**\n    * {@code 502 Bad Gateway}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.6.3\">HTTP/1.1: Semantics and Content, section 6.6.3</a>\n    */\n    BAD_GATEWAY(502, \"Bad Gateway\"),\n\n    /**\n    * {@code 503 Service Unavailable}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.6.4\">HTTP/1.1: Semantics and Content, section 6.6.4</a>\n    */\n    SERVICE_UNAVAILABLE(503, \"Service Unavailable\"),\n\n    /**\n    * {@code 504 Gateway Timeout}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.6.5\">HTTP/1.1: Semantics and Content, section 6.6.5</a>\n    */\n    GATEWAY_TIMEOUT(504, \"Gateway Timeout\"),\n\n    /**\n    * {@code 505 HTTP Version Not Supported}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc7231#section-6.6.6\">HTTP/1.1: Semantics and Content, section 6.6.6</a>\n    */\n    HTTP_VERSION_NOT_SUPPORTED(505, \"HTTP Version not supported\"),\n\n    /**\n    * {@code 506 Variant Also Negotiates}\n    * @see <a href=\"http://tools.ietf.org/html/rfc2295#section-8.1\">Transparent Content Negotiation</a>\n    */\n    VARIANT_ALSO_NEGOTIATES(506, \"Variant Also Negotiates\"),\n\n    /**\n    * {@code 507 Insufficient Storage}\n    * @see <a href=\"http://tools.ietf.org/html/rfc4918#section-11.5\">WebDAV</a>\n    */\n    INSUFFICIENT_STORAGE(507, \"Insufficient Storage\"),\n\n    /**\n    * {@code 508 Loop Detected}\n    * @see <a href=\"http://tools.ietf.org/html/rfc5842#section-7.2\">WebDAV Binding Extensions</a>\n    */\n    LOOP_DETECTED(508, \"Loop Detected\"),\n\n    /**\n    * {@code 509 Bandwidth Limit Exceeded}\n    */\n    BANDWIDTH_LIMIT_EXCEEDED(509, \"Bandwidth Limit Exceeded\"),\n\n    /**\n    * {@code 510 Not Extended}\n    * @see <a href=\"http://tools.ietf.org/html/rfc2774#section-7\">HTTP Extension Framework</a>\n    */\n    NOT_EXTENDED(510, \"Not Extended\"),\n\n    /**\n    * {@code 511 Network Authentication Required}.\n    * @see <a href=\"http://tools.ietf.org/html/rfc6585#section-6\">Additional HTTP Status Codes</a>\n    */\n    NETWORK_AUTHENTICATION_REQUIRED(511, \"Network Authentication Required\");\n\n    private final int    code;\n    private final String desc;\n\n    private static final Map<Integer, HttpStatus> MAPPING;\n    static {\n        ImmutableMap.Builder<Integer, HttpStatus> builder = new ImmutableMap.Builder<>();\n        for (HttpStatus status : HttpStatus.values()) {\n            builder.put(status.code, status);\n        }\n        MAPPING = builder.build();\n    }\n\n    HttpStatus(int code, String desc) {\n        this.code = code;\n        this.desc = desc;\n    }\n\n    /**\n    * Return the integer value of this status code.\n    */\n    public int code() {\n        return this.code;\n    }\n\n    /**\n    * Return the reason phrase of this status code.\n    */\n    public String desc() {\n        return this.desc;\n    }\n\n    /**\n    * Return the HTTP status series of this status code.\n    * @see HttpStatus.Series\n    */\n    public Series series() {\n        return Series.valueOf(this);\n    }\n\n    /**\n    * Whether this status code is in the HTTP series\n    * {@link org.springframework.http.HttpStatus.Series#INFORMATIONAL}.\n    * This is a shortcut for checking the value of {@link #series()}.\n    * @see #series()\n    */\n    public boolean is1xxInformational() {\n        return (series() == Series.INFORMATIONAL);\n    }\n\n    /**\n    * Whether this status code is in the HTTP series\n    * {@link org.springframework.http.HttpStatus.Series#SUCCESSFUL}.\n    * This is a shortcut for checking the value of {@link #series()}.\n    * @see #series()\n    */\n    public boolean is2xxSuccessful() {\n        return (series() == Series.SUCCESSFUL);\n    }\n\n    /**\n    * Whether this status code is in the HTTP series\n    * {@link org.springframework.http.HttpStatus.Series#REDIRECTION}.\n    * This is a shortcut for checking the value of {@link #series()}.\n    * @see #series()\n    */\n    public boolean is3xxRedirection() {\n        return (series() == Series.REDIRECTION);\n    }\n\n    /**\n    * Whether this status code is in the HTTP series\n    * {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}.\n    * This is a shortcut for checking the value of {@link #series()}.\n    * @see #series()\n    */\n    public boolean is4xxClientError() {\n        return (series() == Series.CLIENT_ERROR);\n    }\n\n    /**\n    * Whether this status code is in the HTTP series\n    * {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}.\n    * This is a shortcut for checking the value of {@link #series()}.\n    * @see #series()\n    */\n    public boolean is5xxServerError() {\n        return (series() == Series.SERVER_ERROR);\n    }\n\n    /**\n    * Return a string representation of this status code.\n    */\n    @Override\n    public String toString() {\n        return Integer.toString(this.code);\n    }\n\n    /**\n    * Return the enum constant of this type with the specified numeric value.\n    * @param statusCode the numeric value of the enum to be returned\n    * @return the enum constant with the specified numeric value\n    * @throws IllegalArgumentException if this enum has no constant for the specified numeric value\n    */\n    public static HttpStatus valueOf(int statusCode) {\n        /*for (HttpStatus status : values()) {\n            if (status.code == statusCode) {\n                return status;\n            }\n        }*/\n        HttpStatus status = MAPPING.get(statusCode);\n        if (status != null) {\n            return status;\n        } else {\n            throw new IllegalArgumentException(\"No matching constant for [\" + statusCode + \"]\");\n        }\n    }\n\n    /**\n    * Enumeration of HTTP status series.\n    * <p>Retrievable via {@link HttpStatus#series()}.\n    */\n    public enum Series {\n        INFORMATIONAL(1), //\n        SUCCESSFUL   (2), //\n        REDIRECTION  (3), // \n        CLIENT_ERROR (4), //\n        SERVER_ERROR (5); //\n\n        private final int value;\n\n        Series(int value) {\n            this.value = value;\n        }\n\n        /**\n        * Return the integer value of this status series. Ranges from 1 to 5.\n        */\n        public int value() {\n            return this.value;\n        }\n\n        /**\n        * Return the enum constant of this type with the corresponding series.\n        * @param status a standard HTTP status enum value\n        * @return the enum constant of this type with the corresponding series\n        * @throws IllegalArgumentException if this enum has no corresponding constant\n        */\n        public static Series valueOf(HttpStatus status) {\n            return valueOf(status.code);\n        }\n\n        /**\n        * Return the enum constant of this type with the corresponding series.\n        * @param statusCode the HTTP status code (potentially non-standard)\n        * @return the enum constant of this type with the corresponding series\n        * @throws IllegalArgumentException if this enum has no corresponding constant\n        */\n        public static Series valueOf(int statusCode) {\n            int seriesCode = statusCode / 100;\n            for (Series series : values()) {\n                if (series.value == seriesCode) {\n                    return series;\n                }\n            }\n            throw new IllegalArgumentException(\"No matching constant for [\" + statusCode + \"]\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/ByteOrderMarks.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport cn.ponfee.commons.util.Enums;\nimport org.apache.commons.io.output.ByteArrayOutputStream;\nimport org.bouncycastle.util.Arrays;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.RandomAccessFile;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Map;\n\nimport static cn.ponfee.commons.io.CharsetDetector.DEFAULT_DETECT_LENGTH;\nimport static cn.ponfee.commons.io.CharsetDetector.detect;\n\n/**\n * <pre>\n * BOM（byte order mark）是为UTF-16和UTF-32准备的，用于标记字节序（byte order）。\n * 微软在UTF-8中使用BOM是为了可以把UTF-8和ASCII等编码区分开，不含BOM的UTF-8才是标准形式\n * http://www.unicode.org/faq/utf_bom.html\n * \n * BOM(byte-order mark) Encoding: \n *   EF BB BF       UTF-8\n *   FF FE          UTF-16 (little-endian)\n *   FE FF          UTF-16 (big-endian)\n *   FF FE 00 00    UTF-32 (little-endian)\n *   00 00 FE FF    UTF-32 (big-endian)\n * \n * link sun.nio.cs.StandardCharsets\n * </pre>\n * \n * @author Ponfee\n */\npublic enum ByteOrderMarks {\n\n    UTF_8   (StandardCharsets.UTF_8     , (byte) 0xEF, (byte) 0xBB, (byte) 0xBF             ), //\n\n    UTF_16LE(StandardCharsets.UTF_16LE  , (byte) 0xFF, (byte) 0xFE                          ), //\n\n    UTF_16BE(StandardCharsets.UTF_16BE  , (byte) 0xFE, (byte) 0xFF                          ), //\n\n    UTF_32LE(Charset.forName(\"UTF-32LE\"), (byte) 0xFF, (byte) 0xFE, (byte) 0x00, (byte) 0x00), //\n\n    UTF_32BE(Charset.forName(\"UTF-32BE\"), (byte) 0x00, (byte) 0x00, (byte) 0xFE, (byte) 0xFF), //\n\n    ;\n\n    private static final Map<Charset, ByteOrderMarks> MAPPING = Enums.toMap(ByteOrderMarks.class, ByteOrderMarks::charset);\n\n    private final Charset charset;\n    private final byte[]  bytes;\n\n    ByteOrderMarks(Charset charset, byte... bytes) {\n        this.charset = charset;\n        this.bytes = bytes;\n    }\n\n    // ------------------------------------------------------------------------of bom without charset\n    public static ByteOrderMarks of(String path) throws IOException {\n        return of(new File(path));\n    }\n\n    public static ByteOrderMarks of(File file) throws IOException {\n        return of(Files.readByteArray(file, DEFAULT_DETECT_LENGTH));\n    }\n\n    public static ByteOrderMarks of(InputStream input) throws IOException {\n        return of(Files.readByteArray(input, DEFAULT_DETECT_LENGTH));\n    }\n\n    public static ByteOrderMarks of(byte[] bytes) {\n        return of(detect(bytes, DEFAULT_DETECT_LENGTH), bytes);\n    }\n\n    // ------------------------------------------------------------------------of bom specified charset\n    public static ByteOrderMarks of(Charset charset, String path) throws IOException {\n        return of(charset, new File(path));\n    }\n\n    public static ByteOrderMarks of(Charset charset, File file) throws IOException {\n        ByteOrderMarks bom = MAPPING.get(charset);\n        if (bom == null) {\n            return null; // no bom charset\n        }\n        return bom.match(Files.readByteArray(file, bom.length())) ? bom : null;\n    }\n\n    public static ByteOrderMarks of(Charset charset, InputStream input) throws IOException {\n        ByteOrderMarks bom = MAPPING.get(charset);\n        if (bom == null) {\n            return null; // no bom charset\n        }\n        return bom.match(Files.readByteArray(input, bom.length())) ? bom : null;\n    }\n\n    public static ByteOrderMarks of(Charset charset, byte[] bytes) {\n        ByteOrderMarks bom = MAPPING.get(charset);\n        if (bom == null) {\n            return null; // no bom charset\n        }\n\n        return bom.match(bytes) ? bom : null;\n    }\n\n    // ------------------------------------------------------------------------has bom without charset\n    public static boolean has(String path) throws IOException {\n        return has(new File(path));\n    }\n\n    public static boolean has(File file) throws IOException {\n        return has(Files.readByteArray(file, DEFAULT_DETECT_LENGTH));\n    }\n\n    public static boolean has(InputStream input) throws IOException {\n        return has(Files.readByteArray(input, DEFAULT_DETECT_LENGTH));\n    }\n\n    public static boolean has(byte[] bytes) {\n        return has(detect(bytes, DEFAULT_DETECT_LENGTH), bytes);\n    }\n\n    // ------------------------------------------------------------------------has bom specified charset\n    public static boolean has(Charset charset, String path) throws IOException {\n        return has(charset, new File(path));\n    }\n\n    public static boolean has(Charset charset, File file) throws IOException {\n        return of(charset, file) != null;\n    }\n\n    public static boolean has(Charset charset, InputStream input) throws IOException {\n        return of(charset, input) != null;\n    }\n\n    public static boolean has(Charset charset, byte[] bytes) {\n        return of(charset, bytes) != null;\n    }\n\n    // ------------------------------------------------------------------------add bom\n    public static ByteOrderMarks add(String path) throws IOException {\n        return add(null, new File(path));\n    }\n\n    public static ByteOrderMarks add(File file) throws IOException {\n        return add(null, file);\n    }\n\n    public static ByteOrderMarks add(Charset charset, String path) throws IOException {\n        return add(charset, new File(path));\n    }\n\n    public static ByteOrderMarks add(Charset charset, File file) throws IOException {\n        try (RandomAccessFile raf = new RandomAccessFile(file, \"rw\")) {\n            byte[] headBytes;\n            ByteOrderMarks bom;\n            int count;\n            if (charset == null) {\n                headBytes = new byte[(int) Math.min(file.length(), DEFAULT_DETECT_LENGTH)];\n                count = raf.read(headBytes);\n                charset = detect(headBytes, count);\n                if ((bom = MAPPING.get(charset)) == null) {\n                    return null; // not bom charset\n                }\n            } else {\n                if ((bom = MAPPING.get(charset)) == null) {\n                    return null; // not bom charset\n                }\n                headBytes = new byte[bom.length()];\n                count = raf.read(headBytes);\n            }\n            if (bom.match(headBytes, count)) {\n                return bom; // already has bom\n            }\n\n            ByteArrayOutputStream tailBytes = new ByteArrayOutputStream();\n            byte[] buffer = new byte[Files.BUFF_SIZE];\n            for (int n; (n = raf.read(buffer)) != Files.EOF;) {\n                tailBytes.write(buffer, 0, n);\n            }\n            raf.seek(0); // 将指针移动到文件首部\n            raf.write(bom.bytes);\n            raf.write(headBytes, 0, count);\n            raf.write(tailBytes.toByteArray());\n            tailBytes.close();\n            return bom;\n        }\n    }\n\n    // ------------------------------------------------------------------------remove bom\n    public static ByteOrderMarks remove(String path) throws IOException {\n        return remove(null, new File(path));\n    }\n\n    public static ByteOrderMarks remove(File file) throws IOException {\n        return remove(null, file);\n    }\n\n    public static ByteOrderMarks remove(Charset charset, String path) throws IOException {\n        return remove(charset, new File(path));\n    }\n\n    public static ByteOrderMarks remove(Charset charset, File file) throws IOException {\n        try (RandomAccessFile raf = new RandomAccessFile(file, \"rw\")) {\n            long length = raf.length();\n            byte[] headBytes;\n            ByteOrderMarks bom;\n            int count;\n            if (charset == null) {\n                headBytes = new byte[(int) Math.min(file.length(), DEFAULT_DETECT_LENGTH)];\n                count = raf.read(headBytes);\n                charset = detect(headBytes, count);\n                if ((bom = MAPPING.get(charset)) == null) {\n                    return null; // not bom charset\n                }\n            } else {\n                if ((bom = MAPPING.get(charset)) == null) {\n                    return null; // not bom charset\n                }\n                headBytes = new byte[bom.length()];\n                count = raf.read(headBytes);\n            }\n            if (!bom.match(headBytes, count)) {\n                return bom; // not has bom\n            }\n\n            ByteArrayOutputStream tailBytes = new ByteArrayOutputStream();\n            byte[] buffer = new byte[Files.BUFF_SIZE];\n            for (int n; (n = raf.read(buffer)) != Files.EOF;) {\n                tailBytes.write(buffer, 0, n);\n            }\n            raf.seek(0); // 将指针移动到文件首部\n            raf.write(headBytes, bom.length(), count - bom.length());\n            raf.write(tailBytes.toByteArray());\n            raf.setLength(length - bom.length());\n            tailBytes.close();\n            return bom;\n        }\n    }\n\n    public static byte[] get(Charset charset) {\n        ByteOrderMarks bom = MAPPING.get(charset);\n        return bom == null ? null : bom.bytes();\n    }\n\n    // ------------------------------------------------------------------------public methods\n    public Charset charset() {\n        return this.charset;\n    }\n\n    public byte[] bytes() {\n        return Arrays.copyOf(this.bytes, this.bytes.length);\n    }\n\n    public int length() {\n        return this.bytes.length;\n    }\n\n    // ------------------------------------------------------------------------private methods\n    private boolean match(byte[] array) {\n        return match(array, array.length);\n    }\n\n    private boolean match(byte[] array, int count) {\n        int n = this.length();\n        if (count < n) {\n            return false;\n        }\n\n        for (int i = 0; i < n; i++) {\n            if (array[i] != this.bytes[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/CharsetDetector.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport cn.ponfee.commons.math.Numbers;\n\nimport java.io.*;\nimport java.net.URL;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * 字符编码检测\n *\n * @author Ponfee\n */\npublic class CharsetDetector {\n\n    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;\n\n    public static final int DEFAULT_DETECT_LENGTH = 3600;\n\n    public static Charset detect(String path) {\n        return detect(path, DEFAULT_DETECT_LENGTH);\n    }\n\n    public static Charset detect(String path, int length) {\n        return detect(new File(path), length);\n    }\n\n    public static Charset detect(File file) {\n        return detect(file, DEFAULT_DETECT_LENGTH);\n    }\n\n    public static Charset detect(File file, int length) {\n        try (InputStream input = new FileInputStream(file)) {\n            return detect(input, (int) Math.min(file.length(), length));\n        } catch (IOException e) {\n            throw new RuntimeException(\"Detect file '\" + file.getPath() + \"' occur error.\", e);\n        }\n    }\n\n    public static Charset detect(URL url) {\n        return detect(url, DEFAULT_DETECT_LENGTH);\n    }\n\n    public static Charset detect(URL url, int length) {\n        // 对于网络输入流的InputStream.available()表示对方发过来可用的数据，并不是整个流的大小\n        try (InputStream input = url.openStream()) {\n            return detect(input, length);\n        } catch (IOException e) {\n            throw new RuntimeException(\"Detect url '\" + url.getPath() + \"' occur error.\", e);\n        }\n    }\n\n    public static Charset detect(byte[] bytes) {\n        return detect(bytes, 0, bytes.length);\n    }\n\n    public static Charset detect(byte[] bytes, int length) {\n        return detect(bytes, 0, length);\n    }\n\n    public static Charset detect(byte[] bytes, int offset, int length) {\n        offset = Numbers.bounds(offset, 0, bytes.length);\n        length = Numbers.bounds(length, 0, bytes.length - offset);\n        try {\n            return detect(new ByteArrayInputStream(bytes, offset, length), length);\n        } catch (IOException e) {\n            throw new RuntimeException(\"Detect byte array charset occur error.\", e);\n        }\n    }\n\n    public static Charset detect(InputStream input) throws IOException {\n        return detect(input, DEFAULT_DETECT_LENGTH);\n    }\n\n    public static Charset detect(InputStream input, int length) throws IOException {\n        //return cn.ponfee.commons.io.charset.CodepageDetector.detect(input, length);\n        //return cn.ponfee.commons.io.charset.JchardetDetector.detect(input, length);\n        //return cn.ponfee.commons.io.charset.BytesDetector.detect(input, length);\n        return cn.ponfee.commons.io.charset.TikaDetector.detect(input, length);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/Closeables.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.annotation.Nullable;\n\n/**\n * \n * Close the AutoCloseable utility\n * \n * @author Ponfee\n */\npublic final class Closeables {\n\n    private static final Logger LOG = LoggerFactory.getLogger(Closeables.class);\n\n    /**\n     * Close and ignore\n     * \n     * @param closeable the Closeable\n     */\n    public static void ignore(@Nullable AutoCloseable closeable) {\n        if (closeable != null) {\n            try {\n                closeable.close();\n            } catch (Exception ignored) {\n                // ignored\n            }\n        }\n    }\n\n    /**\n     * Close with console if occur exception\n     * \n     * @param closeable the Closeable\n     */\n    public static void console(@Nullable AutoCloseable closeable) {\n        if (closeable != null) {\n            try {\n                closeable.close();\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    public static void log(@Nullable AutoCloseable closeable) {\n        log(closeable, \"\");\n    }\n\n    /**\n     * Close the AutoCloseable, if occur exception then log error message\n     * \n     * @param closeable the Closeable\n     * @param errMsg    the error message\n     */\n    public static void log(@Nullable AutoCloseable closeable, String errMsg) {\n        if (closeable != null) {\n            try {\n                closeable.close();\n            } catch (Exception e) {\n                LOG.error(errMsg, e);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/ExtendedGZIPOutputStream.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.zip.Deflater;\nimport java.util.zip.GZIPOutputStream;\n\n/**\n * 扩展自GZIPOutputStream的giz压缩输出流\n * @author Ponfee\n */\npublic class ExtendedGZIPOutputStream extends GZIPOutputStream {\n\n    public ExtendedGZIPOutputStream(OutputStream out) throws IOException {\n        this(out, Deflater.DEFAULT_COMPRESSION);\n    }\n\n    public ExtendedGZIPOutputStream(OutputStream out, int level) throws IOException {\n        super(out);\n        super.def.setLevel(level);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/FileTransformer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * 文件编码转换与文本内容替换\n * \n * @author Ponfee\n */\npublic class FileTransformer {\n\n    private static final int FIX_LENGTH = 85;\n    private static final String[] CHARSETS = {\n        \"GBK\", \"GB2312\", \"UTF-8\", \n        \"UTF-16\", \"UTF-16LE\", \"UTF-16BE\", \n        \"UTF-32\", \"UTF-32LE\", \"UTF-32BE\"\n    };\n\n    private String includeFileExtensions = regexExtensions(\n        \"java\", \"txt\", \"properties\", \"xml\", \"sql\", \"html\", \"htm\", \"jsp\", \n        \"css\", \"js\", \"log\", \"bak\", \"ini\", \"csv\", \"yml\", \"yaml\"\n    );\n\n    private final String sourcePath;\n    private final String targetPath;\n    private final String encoding;\n    private final StringBuilder log = new StringBuilder(4096);\n    private String[] searchList;\n    private String[] replacementList;\n\n    public FileTransformer(String source, String target) {\n        this(source, target, null);\n    }\n\n    public FileTransformer(String source, String target, String encoding) {\n        this.sourcePath = new File(source).getAbsolutePath();\n        this.targetPath = Files.mkdir(target).getAbsolutePath();\n        this.encoding = encoding;\n    }\n\n    /**\n     * 文件后缀名，不加“.”\n     * \n     * @param includeFileExtensions\n     */\n    public void setIncludeFileExtensions(String... includeFileExtensions) {\n        this.includeFileExtensions = regexExtensions(includeFileExtensions);\n    }\n\n    public void setReplaceEach(String[] searchList, String[] replacementList) {\n        this.searchList = searchList;\n        this.replacementList = replacementList;\n    }\n\n    /**\n     * 转换（移）\n     */\n    public void transform() {\n        transform(new File(sourcePath));\n    }\n\n    public String getTransformLog() {\n        return log.toString();\n    }\n\n    private void transform(File file) {\n        if (file == null) {\n            // nothing to do\n        } else if (file.isDirectory()) {\n            File[] subfiles = file.listFiles();\n            if (subfiles != null) {\n                for (File sub : subfiles) {\n                    transform(sub);\n                }\n            }\n        } else {\n            String path = file.getAbsolutePath(), charset;\n            File dest = new File(targetPath + path.substring(sourcePath.length()));\n            boolean isMatch = file.getName().matches(includeFileExtensions);\n\n            if (   isMatch \n                && StringUtils.isNotEmpty(encoding) \n                && ArrayUtils.contains(CHARSETS, charset = CharsetDetector.detect(path).name().toUpperCase())\n                && !encoding.equalsIgnoreCase(charset)\n            ) {\n                log.append(\"转换：[\").append(charset).append(\"]\").append(StringUtils.rightPad(path, FIX_LENGTH)).append(\"  -->  \");\n                transform(file, dest, charset, encoding, searchList, replacementList);\n                log.append(\"[\").append(encoding).append(\"]\").append(dest.getAbsolutePath()).append(\"\\n\");\n            } else if (isMatch && ArrayUtils.isNotEmpty(searchList)) {\n                log.append(\"替换：\").append(StringUtils.rightPad(path, FIX_LENGTH)).append(\"  -->  \");\n                transform(file, dest, searchList, replacementList);\n                log.append(dest.getAbsolutePath()).append(\"\\n\");\n            } else {\n                log.append(\"复制：\").append(StringUtils.rightPad(path, FIX_LENGTH)).append(\"  -->  \");\n                transform(file, dest);\n                log.append(dest.getAbsolutePath()).append(\"\\n\");\n            }\n        }\n    }\n\n    /**\n     * 采用nio方式转移\n     * \n     * @param source\n     * @param target\n     */\n    public static void transform(File source, File target) {\n        try {\n            target.getParentFile().mkdirs();\n            //com.google.common.io.Files.copy(source, source);\n            //sourceChannel.transferTo(0, sourceChannel.size(), targetChannel);\n            java.nio.file.Files.copy(source.toPath(), target.toPath());\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * @param source 源文件路径\n     * @param target 输出文件路径\n     * @param searchList  the Strings to search for, no-op if null\n     * @param replacementList  the Strings to replace them with, no-op if null\n     */\n    public static void transform(File source, File target,\n                                 String[] searchList, String[] replacementList) {\n        Files.touch(target);\n        try (WrappedBufferedReader reader = new WrappedBufferedReader(source);\n             WrappedBufferedWriter writer = new WrappedBufferedWriter(target)\n        ) {\n            writeln(reader, writer, searchList, replacementList);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Transforms the source file to target file charset\n     *\n     * @param source\n     * @param target\n     * @param fromCharset\n     * @param toCharset\n     */\n    public static void transform(File source, File target, \n                                 String fromCharset, String toCharset) {\n        transform(source, target, fromCharset, toCharset, null, null);\n    }\n\n    /**\n     * @param source 源文件路径\n     * @param target 输出文件路径\n     * @param fromCharset 源文件编码\n     * @param toCharset 目标文件编码\n     * @param searchList  the Strings to search for, no-op if null\n     * @param replacementList  the Strings to replace them with, no-op if null\n     */\n    public static void transform(File source, File target, \n                                 String fromCharset, String toCharset,\n                                 String[] searchList, String[] replacementList) {\n        Files.touch(target);\n        try (WrappedBufferedReader reader = new WrappedBufferedReader(source, fromCharset);\n             WrappedBufferedWriter writer = new WrappedBufferedWriter(target, toCharset)\n        ) {\n            writeln(reader, writer, searchList, replacementList);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private static void writeln(WrappedBufferedReader reader,\n                                WrappedBufferedWriter writer,\n                                String[] searchList,\n                                String[] replacementList) throws IOException {\n        String line;\n        if (searchList != null && searchList.length > 0) {\n            while ((line = reader.readLine()) != null) {\n                writer.write(StringUtils.replaceEach(line, searchList, replacementList));\n                writer.write(Files.UNIX_LINE_SEPARATOR);\n            }\n        } else {\n            while ((line = reader.readLine()) != null) {\n                writer.write(line);\n                writer.write(Files.UNIX_LINE_SEPARATOR);\n            }\n        }\n    }\n\n    private static String regexExtensions(String... fileExtensions) {\n        return \"(?i)^(.+\\\\.)(\" + String.join(\"|\", fileExtensions) + \")$\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/Files.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport cn.ponfee.commons.tree.PlainNode;\nimport cn.ponfee.commons.tree.TreeNode;\nimport cn.ponfee.commons.tree.print.MultiwayTreePrinter;\nimport org.apache.commons.io.output.StringBuilderWriter;\n\nimport java.io.*;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.channels.FileChannel.MapMode;\nimport java.nio.charset.Charset;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * 文件工具类\n * @author Ponfee\n */\npublic final class Files {\n\n    public static final int EOF             = -1; // end of file read\n\n    public static final int BUFF_SIZE       = 8192; // file buffer size\n\n    public static final String TOP_PATH     = \"..\";\n\n    public static final String CURRENT_PATH = \".\";\n\n    // ------------------------------------------------------------charset encoding\n    public static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); // default charset\n\n    public static final String DEFAULT_CHARSET_NAME = DEFAULT_CHARSET.name(); // default charset name\n\n    public static final String UTF_8 = \"UTF-8\"; // UTF-8 encoding\n\n    // ------------------------------------------------------------file separator\n    public static final String WINDOWS_FOLDER_SEPARATOR = \"\\\\\";\n\n    public static final String UNIX_FOLDER_SEPARATOR = \"/\";\n\n    public static final String SYSTEM_FOLDER_SEPARATOR = File.separator;\n\n    // ------------------------------------------------------------line separator\n    public static final String UNIX_LINE_SEPARATOR = \"\\n\"; // unix file line separator spec \\n  LF\n\n    public static final String WINDOWS_LINE_SEPARATOR = \"\\r\\n\"; // windows file line separator spec \\r\\n  CRLF\n\n    public static final String MAC_LINE_SEPARATOR = \"\\r\"; // mac file line separator spec \\r  CR\n\n    public static final String SYSTEM_LINE_SEPARATOR; // system file line separator\n    static {\n        /*\n        String separator = java.security.AccessController.doPrivileged(\n            new sun.security.action.GetPropertyAction(\"line.separator\")\n        );\n        if (separator == null || separator.length() == 0) {\n            separator = System.getProperty(\"line.separator\", \"\\n\");\n        }\n        SYSTEM_LINE_SEPARATOR = separator;\n        */\n        StringBuilderWriter buffer = new StringBuilderWriter(4);\n        PrintWriter out = new PrintWriter(buffer);\n        out.println();\n        out.flush();\n        SYSTEM_LINE_SEPARATOR = buffer.toString();\n        out.close();\n    }\n\n    /**\n     * 创建目录\n     * @param filePath\n     * @return\n     */\n    public static File mkdir(String filePath) {\n        File file = new File(filePath);\n        mkdir(file);\n        return file;\n    }\n\n    /**\n     * 创建目录\n     *\n     * @param file\n     * @return\n     */\n    public static void mkdir(File file) {\n        if (file.isFile()) {\n            throw new IllegalStateException(file.getAbsolutePath() + \" is a directory.\");\n        }\n\n        if (file.exists()) {\n            return;\n        }\n\n        if (file.mkdirs()) {\n            file.setLastModified(System.currentTimeMillis());\n        }\n    }\n\n    /**\n     * 创建文件\n     * @param file\n     * @return\n     */\n    public static void touch(File file) {\n        if (file.isDirectory()) {\n            throw new IllegalStateException(file.getAbsolutePath() + \" is a directory.\");\n        }\n\n        if (file.exists()) {\n            return;\n        }\n\n        if (!file.getParentFile().exists()) {\n            file.getParentFile().mkdirs();\n        }\n\n        try {\n            if (file.createNewFile()) {\n                file.setLastModified(System.currentTimeMillis());\n            }\n        } catch (IOException e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    // --------------------------------------------------------------------------file to string\n\n    public static String toString(File file) throws IOException {\n        return toString(file, CharsetDetector.detect(file));\n    }\n\n\n    public static String toString(File file, Charset charset) throws IOException {\n        ByteOrderMarks bom = ByteOrderMarks.of(charset, file);\n\n        try (FileInputStream input = new FileInputStream(file);\n             FileChannel channel = input.getChannel()\n        ) {\n            //FileLock lock = channel.lock();\n            //lock.release();\n            long offset = 0, length = channel.size();\n            if (bom != null) {\n                offset = bom.length();\n                length -= offset;\n            }\n            ByteBuffer buffer = channel.map(MapMode.READ_ONLY, offset, length);\n            return charset.decode(buffer).toString();\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Reads file to byte array\n     *\n     * @param file\n     * @return\n     */\n    public static byte[] toByteArray(File file) {\n        try (FileInputStream in = new FileInputStream(file);\n             FileChannel channel = in.getChannel()\n        ) {\n            ByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, channel.size());\n            byte[] bytes = new byte[buffer.capacity()];\n            buffer.get(bytes, 0, bytes.length);\n            return bytes;\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    // -----------------------------------------------------------------readByteArray\n\n    public static byte[] readByteArray(String filePath, int count) throws IOException {\n        return readByteArray(new File(filePath), count);\n    }\n\n    public static byte[] readByteArray(File file, int count) throws IOException {\n        try (InputStream input = new FileInputStream(file)) {\n            return readByteArray(input, count);\n        }\n    }\n\n    public static byte[] readByteArray(InputStream input, int count) throws IOException {\n        byte[] bytes = new byte[count];\n        int n, index = 0;\n        while (index < count && (n = input.read(bytes, index, count - index)) != EOF) {\n            index += n;\n        }\n\n        return (index == count) ? bytes : Arrays.copyOf(bytes, index);\n    }\n\n    public static TreeNode<Integer, File> listFiles(String filePath) {\n        return listFiles(new File(filePath));\n    }\n\n    /**\n     * 递归列出所有文件\n     *\n     * @param file\n     * @return\n     */\n    public static TreeNode<Integer, File> listFiles(File file) {\n        List<PlainNode<Integer, File>> files = new LinkedList<>();\n        AtomicInteger counter = new AtomicInteger(1);\n        Integer dummyRootPid = 0;\n        Deque<PlainNode<Integer, File>> stack = new LinkedList<>();\n        stack.push(new PlainNode<>(counter.getAndIncrement(), dummyRootPid, file));\n        while (!stack.isEmpty()) {\n            PlainNode<Integer, File> node = stack.pop();\n            files.add(node);\n            if (node.getAttach().isDirectory()) {\n                Arrays.stream(node.getAttach().listFiles())\n                      .sorted(Comparator.comparing(File::getName))\n                      .forEach(f -> stack.push(new PlainNode<>(counter.getAndIncrement(), node.getNid(), f)));\n            }\n        }\n\n        return TreeNode.<Integer, File>builder(dummyRootPid).build().mount(files).getChildren().get(0);\n    }\n\n    public static String tree(String filePath) throws IOException {\n        return tree(new File(filePath));\n    }\n\n    /**\n     * 打印文件树\n     *\n     * @param file\n     * @return\n     * @throws IOException\n     */\n    public static String tree(File file) throws IOException {\n        StringBuilder builder = new StringBuilder();\n        new MultiwayTreePrinter<>(builder, File::getName, f -> f.isDirectory() ? Arrays.asList(f.listFiles()) : null).print(file);\n        return builder.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/GzipProcessor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport org.apache.commons.io.IOUtils;\n\nimport java.io.*;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\n\n/**\n * gzip压缩/解压缩处理\n * @author Ponfee\n */\npublic final class GzipProcessor {\n\n    static final int BYTE_SIZE = 512;\n\n    /**\n     * gzip压缩\n     * @param data\n     * @return\n     */\n    public static byte[] compress(byte[] data) {\n        ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE);\n        compress(data, baos);\n        return baos.toByteArray();\n    }\n\n    /**\n     * gzip压缩\n     * @param data\n     * @param output\n     */\n    public static void compress(byte[] data, OutputStream output) {\n        try (GZIPOutputStream gzout = new ExtendedGZIPOutputStream(output)) {\n            gzout.write(data);\n            gzout.flush();\n            gzout.finish();\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * gzip压缩\n     * @param input\n     * @param output\n     */\n    public static long compress(InputStream input, OutputStream output) {\n        try (GZIPOutputStream gzout = new ExtendedGZIPOutputStream(output)) {\n            long size = IOUtils.copyLarge(input, gzout);\n            gzout.flush();\n            gzout.finish();\n            return size;\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * gzip解压缩\n     * @param data\n     * @return\n     */\n    public static byte[] decompress(byte[] data) {\n        ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE);\n        decompress(new ByteArrayInputStream(data), baos);\n        return baos.toByteArray();\n    }\n\n    public static void decompress(byte[] data, OutputStream output) {\n        decompress(new ByteArrayInputStream(data), output);\n    }\n\n    /**\n     * gzip解压缩\n     * @param input\n     * @param output\n     */\n    public static void decompress(InputStream input, OutputStream output) {\n        try (GZIPInputStream gzin = new GZIPInputStream(input)) {\n            IOUtils.copyLarge(gzin, output);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/HumanReadables.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport java.text.DecimalFormat;\nimport java.text.ParseException;\nimport java.util.Arrays;\nimport java.util.regex.Pattern;\n\n/**\n * The file size human readable utility class, \n * provide mutual conversions from human readable size to byte size<p>\n * \n * The similar function in stackoverflow, linked:\n *  https://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java?r=SearchResults<p>\n * \n * Apache also provide similar function \n *  {@link org.apache.commons.io.FileUtils#byteCountToDisplaySize(long)}<p>\n * \n * spring-core 5.x: org.springframework.util.unit.DataSize\n * \n * @author Ponfee\n */\npublic enum HumanReadables {\n\n    SI    (1000, \"B\", \"KB\",  \"MB\",  \"GB\",  \"TB\",  \"PB\",  \"EB\" /*, \"ZB\",  \"YB\" */), // \n\n    BINARY(1024, \"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\"/*, \"ZiB\", \"YiB\"*/), // \n\n    ;\n\n    private static final String  FORMAT  = \"#,##0.##\";\n    private static final Pattern PATTERN = Pattern.compile(\".*[0-9]+.*\");\n\n    private final int      base;\n    private final String[] units;\n    private final long[]   sizes;\n\n    HumanReadables(int base, String... units) {\n        this.base  = base;\n        this.units = units;\n        this.sizes = new long[this.units.length];\n\n        this.sizes[0] = 1L;\n        for (int i = 1; i < this.sizes.length; i++) {\n            this.sizes[i] = this.sizes[i - 1] * base; // Maths.pow(base, i);\n        }\n    }\n\n    /**\n     * Returns a string of bytes count human-readable size\n     * \n     * @param size the size\n     * @return string of human-readable data size\n     */\n    public strictfp String human(long size) {\n        if (size == 0) {\n            return \"0 \" + this.units[0];\n        }\n\n        String sign = \"\";\n        if (size < 0) {\n            sign = \"-\";\n            size = size == Long.MIN_VALUE ? Long.MAX_VALUE : -size;\n        }\n\n        /*int unit = (int) Maths.log(size, this.base);\n        return sign + new DecimalFormat(FORMAT).format(size / Math.pow(this.base, unit)) + \" \" + this.units[unit];*/\n\n        int unit = find(size);\n        return new StringBuilder(13) // 13 max length like as \"-1,023.45 GiB\"\n            .append(sign)\n            .append(new DecimalFormat(FORMAT).format(size / (double) this.sizes[unit]))\n            .append(\" \")\n            .append(this.units[unit])\n            .toString();\n    }\n\n    public long parse(String size) {\n        return parse(size, false);\n    }\n\n    /**\n     * Parse the readable byte count, allowed suffix units: \"1\", \"1 B\", \"1 MB\", \"1 MiB\", \"1 M\"\n     * \n     * @param size   the size\n     * @param strict the strict, if BINARY then verify whether contains \"i\"\n     * @return a long value bytes count\n     */\n    public strictfp long parse(String size, boolean strict) {\n        if (size == null || size.isEmpty()) {\n            return 0L;\n        }\n        if (!PATTERN.matcher(size).matches()) {\n            throw new IllegalArgumentException(\"Invalid format [\" + size + \"]\");\n        }\n\n        String value = size = size.trim();\n        long factor = this.sizes[0];\n        int sign = 1;\n        switch (value.charAt(0)) {\n            case '+': value = value.substring(1);            break;\n            case '-': value = value.substring(1); sign = -1; break;\n            default : /*          Nothing to do          */  break;\n        }\n\n        int end = 0, lastPos = value.length() - 1;\n        // last character isn't a digit\n        char c = value.charAt(lastPos - end);\n        if (c == 'i') {\n            // last pos cannot end with \"i\"\n            throw new IllegalArgumentException(\"Invalid format [\" + size + \"], cannot end with \\\"i\\\".\");\n        }\n\n        if (c == 'B') {\n            end++;\n            c = value.charAt(lastPos - end);\n\n            boolean flag = isBlank(c);\n            while (isBlank(c) && end < lastPos) {\n                end++;\n                c = value.charAt(lastPos - end);\n            }\n            // if \"B\" head has space char, then the first head non space char must be a digit\n            if (flag && !Character.isDigit(c)) {\n                throw new IllegalArgumentException(\"Invalid format [\" + size + \"]: \\\"\" + c + \"\\\".\");\n            }\n        }\n\n        if (!Character.isDigit(c)) {\n            // if not a digit character, then assume is a unit character\n            if (c == 'i') {\n                if (this == SI) {\n                    // SI cannot contains \"i\"\n                    throw new IllegalArgumentException(\"Invalid SI format [\" + size + \"], cannot contains \\\"i\\\".\");\n                }\n                end++;\n                c = value.charAt(lastPos - end);\n            } else {\n                if (this == BINARY && strict) {\n                    // if strict, then BINARY must contains \"i\"\n                    throw new IllegalArgumentException(\"Invalid BINARY format [\" + size + \"], miss character \\\"i\\\".\");\n                }\n            }\n\n            switch (c) {\n                case 'K': factor = this.sizes[1]; break;\n                case 'M': factor = this.sizes[2]; break;\n                case 'G': factor = this.sizes[3]; break;\n                case 'T': factor = this.sizes[4]; break;\n                case 'P': factor = this.sizes[5]; break;\n                case 'E': factor = this.sizes[6]; break;\n                /*\n                case 'Z': factor = this.bytes[7]; break;\n                case 'Y': factor = this.bytes[8]; break;\n                */\n                default: throw new IllegalArgumentException(\"Invalid format [\" + size + \"]: \\\"\" + c + \"\\\".\");\n            }\n\n            do {\n                end++;\n                c = value.charAt(lastPos - end);\n            } while (isBlank(c) && end < lastPos);\n        }\n\n        value = value.substring(0, value.length() - end);\n        try {\n            return sign * (long) (factor * new DecimalFormat(FORMAT).parse(value).doubleValue());\n        } catch (NumberFormatException | ParseException e) {\n            throw new IllegalArgumentException(\"Failed to parse [\" + size + \"]: \\\"\" + value + \"\\\".\");\n        }\n    }\n\n    public int base() {\n        return this.base;\n    }\n\n    public String[] units() {\n        return Arrays.copyOf(this.units, this.units.length);\n    }\n\n    public long[] sizes() {\n        return Arrays.copyOf(this.sizes, this.sizes.length);\n    }\n\n    // --------------------------------------------------------------private methods\n    private int find(long bytes) {\n        int n = this.sizes.length;\n        for (int i = 1; i < n; i++) {\n            if (bytes < this.sizes[i]) {\n                return i - 1;\n            }\n        }\n        return n - 1;\n    }\n\n    private boolean isBlank(char c) {\n        return c == ' ' || c == '\\t';\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/PrereadInputStream.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * 预先第一次读取InputStream的数据用来判断文件类型，文件编码等用途\n * \n * @author Ponfee\n */\npublic class PrereadInputStream extends InputStream {\n\n    private final InputStream input;\n    private final byte[] heads;\n    private final int limit;\n    private int offset;\n\n    public PrereadInputStream(InputStream input, int maxCount) throws IOException {\n        this.input = input;\n        this.heads = Files.readByteArray(input, maxCount);\n        this.offset = 0;\n        this.limit = this.heads.length;\n    }\n\n    /**\n     * 读取下一个可用的字节数据，如果到达流的末尾而没有字节可用，则返回值-1\n     */\n    @Override\n    public int read() throws IOException {\n        if (this.offset < this.limit) {\n            return this.heads[this.offset];\n        } else {\n            return this.input.read();\n        }\n    }\n\n    @Override\n    public int read(byte[] buf, int off, int len) throws IOException {\n        int remaining = this.limit - this.offset;\n        if (remaining > 0) {\n            int count = len - off;\n            if (remaining >= count) {\n                System.arraycopy(this.heads, this.offset, buf, off, len);\n                this.offset += count;\n                return count;\n            } else {\n                System.arraycopy(this.heads, this.offset, buf, off, remaining);\n                int cnt = this.input.read(buf, off + remaining, len - remaining);\n                this.offset = this.limit;\n                return cnt == -1 ? remaining : remaining + cnt;\n            }\n        } else {\n            return this.input.read(buf, off, len);\n        }\n    }\n\n    @Override\n    public int read(byte[] buf) throws IOException {\n        return read(buf, 0, buf.length);\n    }\n\n    public byte[] heads() {\n        return this.heads;\n    }\n\n    @Override\n    public long skip(long n) throws IOException {\n        int remaining = this.limit - this.offset;\n        if (remaining <= 0) {\n            return this.input.skip(n);\n        } else if (remaining > n) {\n            this.offset += n;\n            return n;\n        } else {\n            this.offset = this.limit;\n            return this.input.skip(n - remaining) + remaining;\n        }\n    }\n\n    @Override\n    public int available() throws IOException {\n        return this.input.available() + this.limit - this.offset;\n    }\n\n    @Override\n    public void close() throws IOException {\n        this.input.close();\n        this.offset = this.limit;\n    }\n\n    @Override @Deprecated\n    public synchronized void mark(int readLimit) {\n        throw new UnsupportedOperationException(\"mark/reset not supported\");\n    }\n\n    @Override\n    public synchronized void reset() throws IOException {\n        throw new IOException(\"mark/reset not supported\");\n    }\n\n    @Override\n    public boolean markSupported() {\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/StringPrintWriter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\n\n/**\n * 字符串输出类\n * @see java.io.StringWriter\n * @author Ponfee\n */\npublic class StringPrintWriter extends PrintWriter {\n\n    public StringPrintWriter() {\n        super(new StringWriter());\n    }\n\n    public StringPrintWriter(int initialSize) {\n        super(new StringWriter(initialSize));\n    }\n\n    public String getString() {\n        flush();\n        return super.out.toString();\n    }\n\n    @Override\n    public String toString() {\n        return getString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/WrappedBufferedReader.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport java.io.*;\nimport java.nio.CharBuffer;\nimport java.nio.charset.Charset;\n\n/**\n * 包装文件/输入流缓冲读取（decorator）\n *\n * @author Ponfee\n */\npublic class WrappedBufferedReader extends Reader {\n\n    private BufferedReader buffer;\n\n    public WrappedBufferedReader(File file) throws FileNotFoundException {\n        this(file, Charset.defaultCharset());\n    }\n\n    public WrappedBufferedReader(File file, String charset) throws FileNotFoundException {\n        this(new FileInputStream(file), Charset.forName(charset));\n    }\n\n    public WrappedBufferedReader(File file, Charset charset) throws FileNotFoundException {\n        this(new FileInputStream(file), charset);\n    }\n\n    public WrappedBufferedReader(InputStream input, Charset charset) {\n        this.buffer = new BufferedReader(\n            new InputStreamReader(input, charset), Files.BUFF_SIZE\n        );\n    }\n\n    @Override\n    public void close() {\n        Closeables.console(buffer);\n        buffer = null;\n    }\n\n    @Override\n    public int read(char[] cbuf, int off, int len) throws IOException {\n        return buffer.read(cbuf, off, len);\n    }\n\n    @Override\n    public int read(CharBuffer target) throws IOException {\n        return buffer.read(target);\n    }\n\n    @Override\n    public int read() throws IOException {\n        return buffer.read();\n    }\n\n    @Override\n    public int read(char[] cbuf) throws IOException {\n        return buffer.read(cbuf);\n    }\n\n    @Override\n    public long skip(long n) throws IOException {\n        return buffer.skip(n);\n    }\n\n    @Override\n    public boolean ready() throws IOException {\n        return buffer.ready();\n    }\n\n    @Override\n    public boolean markSupported() {\n        return buffer.markSupported();\n    }\n\n    @Override\n    public void mark(int readAheadLimit) throws IOException {\n        buffer.mark(readAheadLimit);\n    }\n\n    @Override\n    public void reset() throws IOException {\n        buffer.reset();\n    }\n\n    public String readLine() throws IOException {\n        return buffer.readLine();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/WrappedBufferedWriter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport java.io.*;\nimport java.nio.charset.Charset;\n\n/**\n * 包装文件/输入流缓冲写入（decorator）\n *\n * @author Ponfee\n */\npublic class WrappedBufferedWriter extends Writer {\n\n    private OutputStream output;\n    private BufferedWriter buffer;\n\n    public WrappedBufferedWriter(File file) throws FileNotFoundException {\n        this(file, Charset.defaultCharset());\n    }\n\n    public WrappedBufferedWriter(File file, String charset) throws FileNotFoundException {\n        this(new FileOutputStream(file), Charset.forName(charset));\n    }\n\n    public WrappedBufferedWriter(File file, Charset charset) throws FileNotFoundException {\n        this(new FileOutputStream(file), charset);\n    }\n\n    public WrappedBufferedWriter(OutputStream output, Charset charset) {\n        this.output = output;\n        this.buffer = new BufferedWriter(\n            new OutputStreamWriter(output, charset), Files.BUFF_SIZE\n        );\n    }\n\n    @Override\n    public void write(String str) throws IOException {\n        buffer.write(str);\n    }\n\n    @Override\n    public void write(int c) throws IOException {\n        buffer.write(c);\n    }\n\n    @Override\n    public void write(char[] cbuf) throws IOException {\n        buffer.write(cbuf);\n    }\n\n    @Override\n    public void write(String str, int off, int len) throws IOException {\n        buffer.write(str, off, len);\n    }\n\n    @Override\n    public Writer append(CharSequence csq) throws IOException {\n        return buffer.append(csq);\n    }\n\n    @Override\n    public Writer append(CharSequence csq, int start, int end) throws IOException {\n        return buffer.append(csq, start, end);\n    }\n\n    @Override\n    public Writer append(char c) throws IOException {\n        return buffer.append(c);\n    }\n\n    @Override\n    public void flush() throws IOException {\n        buffer.flush();\n    }\n\n    @Override\n    public void close() {\n        Closeables.console(buffer);\n        buffer = null;\n        output = null;\n    }\n\n    @Override\n    public void write(char[] cbuf, int off, int len) throws IOException {\n        buffer.write(cbuf, off, len);\n    }\n\n    public void newLine() throws IOException {\n        buffer.newLine();\n    }\n\n    public void write(byte[] bytes) throws IOException {\n        output.write(bytes);\n    }\n\n    public void writeln() throws IOException {\n        newLine();\n    }\n\n    public void writeln(String str) throws IOException {\n        synchronized (super.lock) {\n            buffer.write(str);\n            writeln();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/charset/BytesDetector.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io.charset;\n\n\nimport cn.ponfee.commons.io.CharsetDetector;\nimport cn.ponfee.commons.io.Files;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\n\n/**\n * Byte array charset detector\n *\n * @author Ponfee\n */\npublic class BytesDetector {\n\n    private static final Logger LOG = LoggerFactory.getLogger(BytesDetector.class);\n\n    public static Charset detect(InputStream input, int length) throws IOException {\n        String charset = detect(Files.readByteArray(input, length));\n        return charset == null ? CharsetDetector.DEFAULT_CHARSET : Charset.forName(charset);\n    }\n\n    public static String detect(byte[] rawtext) {\n        int[] scores = new int[Encoding.TOTAL_TYPES];\n        // Assign Scores\n        scores[Encoding.GB2312]        = gb2312_probability(rawtext)     ;\n        scores[Encoding.GBK]           = gbk_probability(rawtext)        ;\n        scores[Encoding.GB18030]       = gb18030_probability(rawtext)    ;\n        scores[Encoding.HZ]            = hz_probability(rawtext)         ;\n        scores[Encoding.BIG5]          = big5_probability(rawtext)       ;\n        scores[Encoding.CNS11643]      = euc_tw_probability(rawtext)     ;\n        scores[Encoding.UTF8]          = utf8_probability(rawtext)       ;\n        scores[Encoding.UTF8T]         = 0                               ;\n        scores[Encoding.UTF8S]         = 0                               ;\n        scores[Encoding.UNICODE]       = utf16_probability(rawtext)      ;\n        scores[Encoding.UNICODET]      = 0                               ;\n        scores[Encoding.UNICODES]      = 0                               ;\n        scores[Encoding.ISO2022CN]     = iso_2022_cn_probability(rawtext);\n        scores[Encoding.ISO2022CN_CNS] = 0                               ;\n        scores[Encoding.ISO2022CN_GB]  = 0                               ;\n        scores[Encoding.EUC_KR]        = euc_kr_probability(rawtext)     ;\n        scores[Encoding.CP949]         = cp949_probability(rawtext)      ;\n        scores[Encoding.ISO2022KR]     = iso_2022_kr_probability(rawtext);\n        scores[Encoding.JOHAB]         = 0                               ;\n        scores[Encoding.SJIS]          = sjis_probability(rawtext)       ;\n        scores[Encoding.EUC_JP]        = euc_jp_probability(rawtext)     ;\n        scores[Encoding.ISO2022JP]     = iso_2022_jp_probability(rawtext);\n        scores[Encoding.ASCII]         = ascii_probability(rawtext)      ;\n\n        // Tabulate Scores\n        int maxScore = 0, encodingGuess = -1;\n        for (int i = 0; i < scores.length; i++) {\n            LOG.debug(\"Encoding {} score {}\", Encoding.JAVA_CHARSET[i], scores[i]);\n            if (scores[i] > maxScore) {\n                encodingGuess = i;\n                maxScore = scores[i];\n            }\n        }\n        // Not guessed if nothing scored above 50\n        return maxScore > 50 ? Encoding.JAVA_CHARSET[encodingGuess] : null;\n    }\n\n    /*\n     * Function: gb2312_probability Argument: pointer to byte array Returns : number from 0 to 100 representing\n     * probability text in array uses GB-2312 encoding\n     */\n    private static int gb2312_probability(byte[] rawtext) {\n        int dbchars = 1, gbchars = 1;\n        long gbfreq = 0, totalfreq = 1;\n        // Stage 1: Check to see if characters fit into acceptable ranges\n        for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) {\n            // System.err.println(rawtext[i]);\n            if (rawtext[i] >= 0) {\n                // asciichars++;\n            } else {\n                dbchars++;\n                if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7 && (byte) 0xA1 <= rawtext[i + 1]\n                    && rawtext[i + 1] <= (byte) 0xFE) {\n                    gbchars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256 - 0xA1;\n                    column = rawtext[i + 1] + 256 - 0xA1;\n                    if (GB_FREQ[row][column] != 0) {\n                        gbfreq += GB_FREQ[row][column];\n                    } else if (15 <= row && row < 55) {\n                        // In GB high-freq character range\n                        gbfreq += 200;\n                    }\n                }\n                i++;\n            }\n        }\n        float rangeval = 50 * ((float) gbchars / (float) dbchars);\n        float freqval = 50 * ((float) gbfreq / (float) totalfreq);\n        return (int) (rangeval + freqval);\n    }\n\n    /*\n     * Function: gbk_probability Argument: pointer to byte array Returns : number from 0 to 100 representing\n     * probability text in array uses GBK encoding\n     */\n    private static int gbk_probability(byte[] rawtext) {\n        int dbchars = 1, gbchars = 1;\n        long gbfreq = 0, totalfreq = 1;\n        // Stage 1: Check to see if characters fit into acceptable ranges\n        for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) {\n            // System.err.println(rawtext[i]);\n            if (rawtext[i] >= 0) {\n                // asciichars++;\n            } else {\n                dbchars++;\n                if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7 && // Original GB range\n                    (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) {\n                    gbchars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256 - 0xA1;\n                    column = rawtext[i + 1] + 256 - 0xA1;\n                    // System.out.println(\"original row \" + row + \" column \" +\n                    // column);\n                    if (GB_FREQ[row][column] != 0) {\n                        gbfreq += GB_FREQ[row][column];\n                    } else if (15 <= row && row < 55) {\n                        gbfreq += 200;\n                    }\n                } else if ((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && // Extended GB range\n                    (((byte) 0x80 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE)\n                        || ((byte) 0x40 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7E))) {\n                    gbchars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256 - 0x81;\n                    if (0x40 <= rawtext[i + 1] && rawtext[i + 1] <= 0x7E) {\n                        column = rawtext[i + 1] - 0x40;\n                    } else {\n                        column = rawtext[i + 1] + 256 - 0x40;\n                    }\n                    // System.out.println(\"extended row \" + row + \" column \" +\n                    // column + \" rawtext[i] \" + rawtext[i]);\n                    if (GBK_FREQ[row][column] != 0) {\n                        gbfreq += GBK_FREQ[row][column];\n                    }\n                }\n                i++;\n            }\n        }\n        float rangeval = 50 * ((float) gbchars / (float) dbchars);\n        float freqval = 50 * ((float) gbfreq / (float) totalfreq);\n        // For regular GB files, this would give the same score, so I handicap\n        // it slightly\n        return (int) (rangeval + freqval) - 1;\n    }\n\n    /*\n     * Function: gb18030_probability Argument: pointer to byte array Returns : number from 0 to 100 representing\n     * probability text in array uses GBK encoding\n     */\n    private static int gb18030_probability(byte[] rawtext) {\n        int dbchars = 1, gbchars = 1;\n        long gbfreq = 0, totalfreq = 1;\n        // Stage 1: Check to see if characters fit into acceptable ranges\n        for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) {\n            // System.err.println(rawtext[i]);\n            if (rawtext[i] >= 0) {\n                // asciichars++;\n            } else {\n                dbchars++;\n                if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7 && // Original GB range\n                    i + 1 < rawtext.length && (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) {\n                    gbchars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256 - 0xA1;\n                    column = rawtext[i + 1] + 256 - 0xA1;\n                    // System.out.println(\"original row \" + row + \" column \" +\n                    // column);\n                    if (GB_FREQ[row][column] != 0) {\n                        gbfreq += GB_FREQ[row][column];\n                    } else if (15 <= row && row < 55) {\n                        gbfreq += 200;\n                    }\n                } else if ((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && // Extended GB range\n                    i + 1 < rawtext.length && (((byte) 0x80 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE)\n                    || ((byte) 0x40 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7E))) {\n                    gbchars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256 - 0x81;\n                    if (0x40 <= rawtext[i + 1] && rawtext[i + 1] <= 0x7E) {\n                        column = rawtext[i + 1] - 0x40;\n                    } else {\n                        column = rawtext[i + 1] + 256 - 0x40;\n                    }\n                    // System.out.println(\"extended row \" + row + \" column \" +\n                    // column + \" rawtext[i] \" + rawtext[i]);\n                    if (GBK_FREQ[row][column] != 0) {\n                        gbfreq += GBK_FREQ[row][column];\n                    }\n                } else if ((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && // Extended GB range\n                    i + 3 < rawtext.length && (byte) 0x30 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x39\n                    && (byte) 0x81 <= rawtext[i + 2] && rawtext[i + 2] <= (byte) 0xFE && (byte) 0x30 <= rawtext[i + 3]\n                    && rawtext[i + 3] <= (byte) 0x39) {\n                    gbchars++;\n                    /*\n                     * totalfreq += 500; row = rawtext[i] + 256 - 0x81; if (0x40 <= rawtext[i+1] && rawtext[i+1] <=\n                     * 0x7E) { column = rawtext[i+1] - 0x40; } else { column = rawtext[i+1] + 256 - 0x40; }\n                     * //System.out.println(\"extended row \" + row + \" column \" + column + \" rawtext[i] \" +\n                     * rawtext[i]); if (GBKFreq[row][column] != 0) { gbfreq += GBKFreq[row][column]; }\n                     */\n                }\n                i++;\n            }\n        }\n        float rangeval = 50 * ((float) gbchars / (float) dbchars);\n        float freqval = 50 * ((float) gbfreq / (float) totalfreq);\n        // For regular GB files, this would give the same score, so I handicap\n        // it slightly\n        return (int) (rangeval + freqval) - 1;\n    }\n\n    /*\n     * Function: hz_probability Argument: byte array Returns : number from 0 to 100 representing probability text in\n     * array uses HZ encoding\n     */\n    private static int hz_probability(byte[] rawtext) {\n        long hzfreq = 0, totalfreq = 1;\n        int hzstart = 0;\n        for (int i = 0, row, column, n = rawtext.length - 1; i < rawtext.length; i++) {\n            if (rawtext[i] == '~') {\n                if (rawtext[i + 1] == '{') {\n                    hzstart++;\n                    i += 2;\n                    while (i < n) {\n                        if (rawtext[i] == 0x0A || rawtext[i] == 0x0D) {\n                            break;\n                        } else if (rawtext[i] == '~' && rawtext[i + 1] == '}') {\n                            i++;\n                            break;\n                        } else if ((0x21 <= rawtext[i] && rawtext[i] <= 0x77) && (0x21 <= rawtext[i + 1] && rawtext[i + 1] <= 0x77)) {\n                            row = rawtext[i] - 0x21;\n                            column = rawtext[i + 1] - 0x21;\n                            totalfreq += 500;\n                            if (GB_FREQ[row][column] != 0) {\n                                hzfreq += GB_FREQ[row][column];\n                            } else if (15 <= row && row < 55) {\n                                hzfreq += 200;\n                            }\n                        } else if (between(rawtext[i]) && between(rawtext[i + 1])) {\n                            row = rawtext[i] + 256 - 0xA1;\n                            column = rawtext[i + 1] + 256 - 0xA1;\n                            totalfreq += 500;\n                            if (GB_FREQ[row][column] != 0) {\n                                hzfreq += GB_FREQ[row][column];\n                            } else if (15 <= row && row < 55) {\n                                hzfreq += 200;\n                            }\n                        }\n                        i += 2;\n                    }\n                } else if (rawtext[i + 1] == '}') {\n                    i++;\n                } else if (rawtext[i + 1] == '~') {\n                    i++;\n                }\n            }\n        }\n\n        float rangeval;\n        if (hzstart > 4) {\n            rangeval = 50;\n        } else if (hzstart > 1) {\n            rangeval = 41;\n        } else if (hzstart > 0) { // Only 39 in case the sequence happened to\n            // occur\n            rangeval = 39; // in otherwise non-Hz text\n        } else {\n            rangeval = 0;\n        }\n        float freqval = 50 * ((float) hzfreq / (float) totalfreq);\n        return (int) (rangeval + freqval);\n    }\n\n    private static boolean between(byte b) {\n        return (byte) 0xA1 <= b && (byte) 0xF7 >= b;\n    }\n\n    /**\n     * Function: big5_probability Argument: byte array Returns : number from 0 to 100 representing probability text\n     * in array uses Big5 encoding\n     */\n    private static int big5_probability(byte[] rawtext) {\n        int dbchars = 1, bfchars = 1;\n        long bffreq = 0, totalfreq = 1;\n        // Check to see if characters fit into acceptable ranges\n        for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) {\n            if (rawtext[i] >= 0) {\n                // asciichars++;\n            } else {\n                dbchars++;\n                if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF9\n                    && (((byte) 0x40 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7E)\n                    || ((byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE))) {\n                    bfchars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256 - 0xA1;\n                    if (0x40 <= rawtext[i + 1] && rawtext[i + 1] <= 0x7E) {\n                        column = rawtext[i + 1] - 0x40;\n                    } else {\n                        column = rawtext[i + 1] + 256 - 0x61;\n                    }\n                    if (BIG5_FREQ[row][column] != 0) {\n                        bffreq += BIG5_FREQ[row][column];\n                    } else if (3 <= row && row <= 37) {\n                        bffreq += 200;\n                    }\n                }\n                i++;\n            }\n        }\n        float rangeval = 50 * ((float) bfchars / (float) dbchars);\n        float freqval = 50 * ((float) bffreq / (float) totalfreq);\n        return (int) (rangeval + freqval);\n    }\n\n    /*\n     * Function: euc_tw_probability Argument: byte array Returns : number from 0 to 100 representing probability\n     * text in array uses EUC-TW (CNS 11643) encoding\n     */\n    private static int euc_tw_probability(byte[] rawtext) {\n        int dbchars = 1, cnschars = 1;\n        long cnsfreq = 0, totalfreq = 1;\n        // Check to see if characters fit into acceptable ranges\n        // and have expected frequency of use\n        for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) {\n            if (rawtext[i] >= 0) { // in ASCII range\n                // asciichars++;\n            } else { // high bit set\n                dbchars++;\n                if (i + 3 < rawtext.length && (byte) 0x8E == rawtext[i] && (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xB0\n                    && (byte) 0xA1 <= rawtext[i + 2] && rawtext[i + 2] <= (byte) 0xFE && (byte) 0xA1 <= rawtext[i + 3]\n                    && rawtext[i + 3] <= (byte) 0xFE) { // Planes 1 - 16\n                    cnschars++;\n                    // System.out.println(\"plane 2 or above CNS char\");\n                    // These are all less frequent chars so just ignore freq\n                    i += 3;\n                } else if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && // Plane 1\n                    (byte) 0xA1 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE) {\n                    cnschars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256 - 0xA1;\n                    column = rawtext[i + 1] + 256 - 0xA1;\n                    if (EUC_TW_FREQ[row][column] != 0) {\n                        cnsfreq += EUC_TW_FREQ[row][column];\n                    } else if (35 <= row && row <= 92) {\n                        cnsfreq += 150;\n                    }\n                    i++;\n                }\n            }\n        }\n        float rangeval = 50 * ((float) cnschars / (float) dbchars);\n        float freqval = 50 * ((float) cnsfreq / (float) totalfreq);\n        return (int) (rangeval + freqval);\n    }\n\n    /*\n     * Function: iso_2022_cn_probability Argument: byte array Returns : number from 0 to 100 representing\n     * probability text in array uses ISO 2022-CN encoding WORKS FOR BASIC CASES, BUT STILL NEEDS MORE WORK\n     */\n    private static int iso_2022_cn_probability(byte[] rawtext) {\n        int dbchars = 1, isochars = 1;\n        long isofreq = 0, totalfreq = 1;\n        // Check to see if characters fit into acceptable ranges\n        // and have expected frequency of use\n        for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) {\n            if (rawtext[i] == (byte) 0x1B && i + 3 < rawtext.length) { // Escape\n                // char ESC\n                if (rawtext[i + 1] == (byte) 0x24 && rawtext[i + 2] == 0x29 && rawtext[i + 3] == (byte) 0x41) { // GB\n                    // Escape\n                    // $\n                    // )\n                    // A\n                    i += 4;\n                    while (rawtext[i] != (byte) 0x1B) {\n                        dbchars++;\n                        if ((0x21 <= rawtext[i] && rawtext[i] <= 0x77) && (0x21 <= rawtext[i + 1] && rawtext[i + 1] <= 0x77)) {\n                            isochars++;\n                            row = rawtext[i] - 0x21;\n                            column = rawtext[i + 1] - 0x21;\n                            totalfreq += 500;\n                            if (GB_FREQ[row][column] != 0) {\n                                isofreq += GB_FREQ[row][column];\n                            } else if (15 <= row && row < 55) {\n                                isofreq += 200;\n                            }\n                            i++;\n                        }\n                        i++;\n                    }\n                } else if (i + 3 < rawtext.length && rawtext[i + 1] == (byte) 0x24 && rawtext[i + 2] == (byte) 0x29\n                    && rawtext[i + 3] == (byte) 0x47) {\n                    // CNS Escape $ ) G\n                    i += 4;\n                    while (rawtext[i] != (byte) 0x1B) {\n                        dbchars++;\n                        if ((byte) 0x21 <= rawtext[i] && rawtext[i] <= (byte) 0x7E && (byte) 0x21 <= rawtext[i + 1]\n                            && rawtext[i + 1] <= (byte) 0x7E) {\n                            isochars++;\n                            totalfreq += 500;\n                            row = rawtext[i] - 0x21;\n                            column = rawtext[i + 1] - 0x21;\n                            if (EUC_TW_FREQ[row][column] != 0) {\n                                isofreq += EUC_TW_FREQ[row][column];\n                            } else if (35 <= row && row <= 92) {\n                                isofreq += 150;\n                            }\n                            i++;\n                        }\n                        i++;\n                    }\n                }\n                if (rawtext[i] == (byte) 0x1B && i + 2 < rawtext.length && rawtext[i + 1] == (byte) 0x28 && rawtext[i + 2] == (byte) 0x42) { // ASCII:\n                    // ESC\n                    // ( B\n                    i += 2;\n                }\n            }\n        }\n        float rangeval = 50 * ((float) isochars / (float) dbchars);\n        float freqval = 50 * ((float) isofreq / (float) totalfreq);\n        // System.out.println(\"isochars dbchars isofreq totalfreq \" + isochars +\n        // \" \" + dbchars + \" \" + isofreq + \" \" + totalfreq + \"\n        // \" + rangeval + \" \" + freqval);\n        return (int) (rangeval + freqval);\n        // return 0;\n    }\n\n    /*\n     * Function: utf8_probability Argument: byte array Returns : number from 0 to 100 representing probability text\n     * in array uses UTF-8 encoding of Unicode\n     */\n    private static int utf8_probability(byte[] rawtext) {\n        int goodbytes = 0, asciibytes = 0;\n        // Maybe also use UTF8 Byte Order Mark: EF BB BF\n        // Check to see if characters fit into acceptable ranges\n        for (int i = 0; i < rawtext.length; i++) {\n            if ((rawtext[i] & (byte) 0x7F) == rawtext[i]) { // One byte\n                asciibytes++;\n                // Ignore ASCII, can throw off count\n            } else if (-64 <= rawtext[i] && rawtext[i] <= -33 && // Two bytes\n                i + 1 < rawtext.length && -128 <= rawtext[i + 1] && rawtext[i + 1] <= -65) {\n                goodbytes += 2;\n                i++;\n            } else if (-32 <= rawtext[i] && rawtext[i] <= -17 && // Three bytes\n                i + 2 < rawtext.length && -128 <= rawtext[i + 1] && rawtext[i + 1] <= -65 && -128 <= rawtext[i + 2]\n                && rawtext[i + 2] <= -65) {\n                goodbytes += 3;\n                i += 2;\n            }\n        }\n        if (asciibytes == rawtext.length) {\n            return 0;\n        }\n\n        int score = (int) (100 * ((float) goodbytes / (float) (rawtext.length - asciibytes)));\n        // System.out.println(\"rawtextlen \" + rawtextlen + \" goodbytes \" +\n        // goodbytes + \" asciibytes \" + asciibytes + \" score \" +\n        // score);\n        // If not above 98, reduce to zero to prevent coincidental matches\n        // Allows for some (few) bad formed sequences\n        if (score > 98) {\n            return score;\n        } else if (score > 95 && goodbytes > 30) {\n            return score;\n        } else {\n            return 0;\n        }\n    }\n\n    /*\n     * Function: utf16_probability Argument: byte array Returns : number from 0 to 100 representing probability text\n     * in array uses UTF-16 encoding of Unicode, guess based on BOM // NOT VERY GENERAL, NEEDS MUCH MORE WORK\n     */\n    private static int utf16_probability(byte[] rawtext) {\n        // int score = 0;\n        // int i, rawtextlen = 0;\n        // int goodbytes = 0, asciibytes = 0;\n        if (rawtext.length > 1 && ((byte) 0xFE == rawtext[0] && (byte) 0xFF == rawtext[1]) || // Big-endian\n            ((byte) 0xFF == rawtext[0] && (byte) 0xFE == rawtext[1])) { // Little-endian\n            return 100;\n        }\n        return 0;\n        /*\n         * // Check to see if characters fit into acceptable ranges rawtextlen = rawtext.length; for (i = 0; i <\n         * rawtextlen; i++) { if ((rawtext[i] & (byte)0x7F) == rawtext[i]) { // One byte goodbytes += 1;\n         * asciibytes++; } else if ((rawtext[i] & (byte)0xDF) == rawtext[i]) { // Two bytes if (i+1 < rawtextlen &&\n         * (rawtext[i+1] & (byte)0xBF) == rawtext[i+1]) { goodbytes += 2; i++; } } else if ((rawtext[i] &\n         * (byte)0xEF) == rawtext[i]) { // Three bytes if (i+2 < rawtextlen && (rawtext[i+1] & (byte)0xBF) ==\n         * rawtext[i+1] && (rawtext[i+2] & (byte)0xBF) == rawtext[i+2]) { goodbytes += 3; i+=2; } } }\n         *\n         * score = (int)(100 * ((float)goodbytes/(float)rawtext.length)); // An all ASCII file is also a good UTF8\n         * file, but I'd rather it // get identified as ASCII. Can delete following 3 lines otherwise if (goodbytes\n         * == asciibytes) { score = 0; } // If not above 90, reduce to zero to prevent coincidental matches if\n         * (score > 90) { return score; } else { return 0; }\n         */\n    }\n\n    /*\n     * Function: ascii_probability Argument: byte array Returns : number from 0 to 100 representing probability text\n     * in array uses all ASCII Description: Sees if array has any characters not in ASCII range, if so, score is\n     * reduced\n     */\n    private static int ascii_probability(byte[] rawtext) {\n        int score = 75;\n        for (byte b : rawtext) {\n            if (b < 0) {\n                score = score - 5;\n            } else if (b == (byte) 0x1B) { // ESC (used by ISO 2022)\n                score = score - 5;\n            }\n            if (score <= 0) {\n                return 0;\n            }\n        }\n        return score;\n    }\n\n    /*\n     * Function: euc_kr__probability Argument: pointer to byte array Returns : number from 0 to 100 representing\n     * probability text in array uses EUC-KR encoding\n     */\n    private static int euc_kr_probability(byte[] rawtext) {\n        int dbchars = 1, krchars = 1;\n        long krfreq = 0, totalfreq = 1;\n        // Stage 1: Check to see if characters fit into acceptable ranges\n        for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) {\n            // System.err.println(rawtext[i]);\n            if (rawtext[i] >= 0) {\n                // asciichars++;\n            } else {\n                dbchars++;\n                if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && (byte) 0xA1 <= rawtext[i + 1]\n                    && rawtext[i + 1] <= (byte) 0xFE) {\n                    krchars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256 - 0xA1;\n                    column = rawtext[i + 1] + 256 - 0xA1;\n                    if (KR_FREQ[row][column] != 0) {\n                        krfreq += KR_FREQ[row][column];\n                    } else if (15 <= row && row < 55) {\n                        krfreq += 0;\n                    }\n                }\n                i++;\n            }\n        }\n        float rangeval = 50 * ((float) krchars / (float) dbchars);\n        float freqval = 50 * ((float) krfreq / (float) totalfreq);\n        return (int) (rangeval + freqval);\n    }\n\n    /*\n     * Function: cp949__probability Argument: pointer to byte array Returns : number from 0 to 100 representing\n     * probability text in array uses Cp949 encoding\n     */\n    private static int cp949_probability(byte[] rawtext) {\n        int dbchars = 1, krchars = 1;\n        long krfreq = 0, totalfreq = 1;\n        // Stage 1: Check to see if characters fit into acceptable ranges\n        for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) {\n            // System.err.println(rawtext[i]);\n            if (rawtext[i] >= 0) {\n                // asciichars++;\n            } else {\n                dbchars++;\n                if ((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0xFE\n                    && ((byte) 0x41 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x5A\n                    || (byte) 0x61 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7A\n                    || (byte) 0x81 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFE)) {\n                    krchars++;\n                    totalfreq += 500;\n                    if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && (byte) 0xA1 <= rawtext[i + 1]\n                        && rawtext[i + 1] <= (byte) 0xFE) {\n                        row = rawtext[i] + 256 - 0xA1;\n                        column = rawtext[i + 1] + 256 - 0xA1;\n                        if (KR_FREQ[row][column] != 0) {\n                            krfreq += KR_FREQ[row][column];\n                        }\n                    }\n                }\n                i++;\n            }\n        }\n        float rangeval = 50 * ((float) krchars / (float) dbchars);\n        float freqval = 50 * ((float) krfreq / (float) totalfreq);\n        return (int) (rangeval + freqval);\n    }\n\n    private static int iso_2022_kr_probability(byte[] rawtext) {\n        for (int i = 0; i < rawtext.length; i++) {\n            if (i + 3 < rawtext.length && rawtext[i] == 0x1b && (char) rawtext[i + 1] == '$' && (char) rawtext[i + 2] == ')'\n                && (char) rawtext[i + 3] == 'C') {\n                return 100;\n            }\n        }\n        return 0;\n    }\n\n    /*\n     * Function: euc_jp_probability Argument: pointer to byte array Returns : number from 0 to 100 representing\n     * probability text in array uses EUC-JP encoding\n     */\n    private static int euc_jp_probability(byte[] rawtext) {\n        int dbchars = 1, jpchars = 1;\n        long jpfreq = 0, totalfreq = 1;\n        // Stage 1: Check to see if characters fit into acceptable ranges\n        for (int i = 0, row, column, n = rawtext.length - 1; i < n; i++) {\n            // System.err.println(rawtext[i]);\n            if (rawtext[i] >= 0) {\n                // asciichars++;\n            } else {\n                dbchars++;\n                if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE && (byte) 0xA1 <= rawtext[i + 1]\n                    && rawtext[i + 1] <= (byte) 0xFE) {\n                    jpchars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256 - 0xA1;\n                    column = rawtext[i + 1] + 256 - 0xA1;\n                    if (JP_FREQ[row][column] != 0) {\n                        jpfreq += JP_FREQ[row][column];\n                    } else if (15 <= row && row < 55) {\n                        jpfreq += 0;\n                    }\n                }\n                i++;\n            }\n        }\n        float rangeval = 50 * ((float) jpchars / (float) dbchars);\n        float freqval = 50 * ((float) jpfreq / (float) totalfreq);\n        return (int) (rangeval + freqval);\n    }\n\n    private static int iso_2022_jp_probability(byte[] rawtext) {\n        for (int i = 0; i < rawtext.length; i++) {\n            if (i + 2 < rawtext.length && rawtext[i] == 0x1b && (char) rawtext[i + 1] == '$' && (char) rawtext[i + 2] == 'B') {\n                return 100;\n            }\n        }\n        return 0;\n    }\n\n    /*\n     * Function: sjis_probability Argument: pointer to byte array Returns : number from 0 to 100 representing\n     * probability text in array uses Shift-JIS encoding\n     */\n    private static int sjis_probability(byte[] rawtext) {\n        int dbchars = 1, jpchars = 1;\n        long jpfreq = 0, totalfreq = 1;\n        // Stage 1: Check to see if characters fit into acceptable ranges\n        for (int i = 0, row, column, adjust, n = rawtext.length - 1; i < n; i++) {\n            // System.err.println(rawtext[i]);\n            if (rawtext[i] >= 0) {\n                // asciichars++;\n            } else {\n                dbchars++;\n                if (i + 1 < rawtext.length\n                    && (((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0x9F)\n                    || ((byte) 0xE0 <= rawtext[i] && rawtext[i] <= (byte) 0xEF))\n                    && (((byte) 0x40 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0x7E)\n                    || ((byte) 0x80 <= rawtext[i + 1] && rawtext[i + 1] <= (byte) 0xFC))) {\n                    jpchars++;\n                    totalfreq += 500;\n                    row = rawtext[i] + 256;\n                    column = rawtext[i + 1] + 256;\n                    if (column < 0x9f) {\n                        adjust = 1;\n                        if (column > 0x7f) {\n                            column -= 0x20;\n                        } else {\n                            column -= 0x19;\n                        }\n                    } else {\n                        adjust = 0;\n                        column -= 0x7e;\n                    }\n                    if (row < 0xa0) {\n                        row = ((row - 0x70) << 1) - adjust;\n                    } else {\n                        row = ((row - 0xb0) << 1) - adjust;\n                    }\n                    row -= 0x20;\n                    column = 0x20;\n                    // System.out.println(\"original row \" + row + \" column \" +\n                    // column);\n                    if (row < JP_FREQ.length && column < JP_FREQ[row].length && JP_FREQ[row][column] != 0) {\n                        jpfreq += JP_FREQ[row][column];\n                    }\n                    i++;\n                } else if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xDF) {\n                    // half-width katakana, convert to full-width\n                }\n            }\n        }\n        float rangeval = 50 * ((float) jpchars / (float) dbchars);\n        float freqval = 50 * ((float) jpfreq / (float) totalfreq);\n        // For regular GB files, this would give the same score, so I handicap\n        // it slightly\n        return (int) (rangeval + freqval) - 1;\n    }\n\n    // ------------------------------------------------------------------------------------private static fields\n\n    private static final int[][] GB_FREQ     = new int[ 94][ 94];\n    private static final int[][] GBK_FREQ    = new int[126][191];\n    private static final int[][] BIG5_FREQ   = new int[ 94][158];\n    private static final int[][] BIG5P_FREQ  = new int[126][191];\n    private static final int[][] EUC_TW_FREQ = new int[ 94][ 94];\n    private static final int[][] KR_FREQ     = new int[ 94][ 94];\n    private static final int[][] JP_FREQ     = new int[ 94][ 94];\n    static {\n\n        // ------------------------------------------------------------------------------------GB_FREQ\n\n        GB_FREQ[20][35] = 599; GB_FREQ[49][26] = 598; GB_FREQ[41][38] = 597; GB_FREQ[17][26] = 596; GB_FREQ[32][42] = 595;\n        GB_FREQ[39][42] = 594; GB_FREQ[45][49] = 593; GB_FREQ[51][57] = 592; GB_FREQ[50][47] = 591; GB_FREQ[42][90] = 590;\n        GB_FREQ[52][65] = 589; GB_FREQ[53][47] = 588; GB_FREQ[19][82] = 587; GB_FREQ[31][19] = 586; GB_FREQ[40][46] = 585;\n        GB_FREQ[24][89] = 584; GB_FREQ[23][85] = 583; GB_FREQ[20][28] = 582; GB_FREQ[42][20] = 581; GB_FREQ[34][38] = 580;\n        GB_FREQ[45][9]  = 579; GB_FREQ[54][50] = 578; GB_FREQ[25][44] = 577; GB_FREQ[35][66] = 576; GB_FREQ[20][55] = 575;\n        GB_FREQ[18][85] = 574; GB_FREQ[20][31] = 573; GB_FREQ[49][17] = 572; GB_FREQ[41][16] = 571; GB_FREQ[35][73] = 570;\n        GB_FREQ[20][34] = 569; GB_FREQ[29][44] = 568; GB_FREQ[35][38] = 567; GB_FREQ[49][9]  = 566; GB_FREQ[46][33] = 565;\n        GB_FREQ[49][51] = 564; GB_FREQ[40][89] = 563; GB_FREQ[26][64] = 562; GB_FREQ[54][51] = 561; GB_FREQ[54][36] = 560;\n        GB_FREQ[39][4]  = 559; GB_FREQ[53][13] = 558; GB_FREQ[24][92] = 557; GB_FREQ[27][49] = 556; GB_FREQ[48][6]  = 555;\n        GB_FREQ[21][51] = 554; GB_FREQ[30][40] = 553; GB_FREQ[42][92] = 552; GB_FREQ[31][78] = 551; GB_FREQ[25][82] = 550;\n        GB_FREQ[47][0]  = 549; GB_FREQ[34][19] = 548; GB_FREQ[47][35] = 547; GB_FREQ[21][63] = 546; GB_FREQ[43][75] = 545;\n        GB_FREQ[21][87] = 544; GB_FREQ[35][59] = 543; GB_FREQ[25][34] = 542; GB_FREQ[21][27] = 541; GB_FREQ[39][26] = 540;\n        GB_FREQ[34][26] = 539; GB_FREQ[39][52] = 538; GB_FREQ[50][57] = 537; GB_FREQ[37][79] = 536; GB_FREQ[26][24] = 535;\n        GB_FREQ[22][1]  = 534; GB_FREQ[18][40] = 533; GB_FREQ[41][33] = 532; GB_FREQ[53][26] = 531; GB_FREQ[54][86] = 530;\n        GB_FREQ[20][16] = 529; GB_FREQ[46][74] = 528; GB_FREQ[30][19] = 527; GB_FREQ[45][35] = 526; GB_FREQ[45][61] = 525;\n        GB_FREQ[30][9]  = 524; GB_FREQ[41][53] = 523; GB_FREQ[41][13] = 522; GB_FREQ[50][34] = 521; GB_FREQ[53][86] = 520;\n        GB_FREQ[47][47] = 519; GB_FREQ[22][28] = 518; GB_FREQ[50][53] = 517; GB_FREQ[39][70] = 516; GB_FREQ[38][15] = 515;\n        GB_FREQ[42][88] = 514; GB_FREQ[16][29] = 513; GB_FREQ[27][90] = 512; GB_FREQ[29][12] = 511; GB_FREQ[44][22] = 510;\n        GB_FREQ[34][69] = 509; GB_FREQ[24][10] = 508; GB_FREQ[44][11] = 507; GB_FREQ[39][92] = 506; GB_FREQ[49][48] = 505;\n        GB_FREQ[31][46] = 504; GB_FREQ[19][50] = 503; GB_FREQ[21][14] = 502; GB_FREQ[32][28] = 501; GB_FREQ[18][3]  = 500;\n        GB_FREQ[53][9]  = 499; GB_FREQ[34][80] = 498; GB_FREQ[48][88] = 497; GB_FREQ[46][53] = 496; GB_FREQ[22][53] = 495;\n        GB_FREQ[28][10] = 494; GB_FREQ[44][65] = 493; GB_FREQ[20][10] = 492; GB_FREQ[40][76] = 491; GB_FREQ[47][8]  = 490;\n        GB_FREQ[50][74] = 489; GB_FREQ[23][62] = 488; GB_FREQ[49][65] = 487; GB_FREQ[28][87] = 486; GB_FREQ[15][48] = 485;\n        GB_FREQ[22][7]  = 484; GB_FREQ[19][42] = 483; GB_FREQ[41][20] = 482; GB_FREQ[26][55] = 481; GB_FREQ[21][93] = 480;\n        GB_FREQ[31][76] = 479; GB_FREQ[34][31] = 478; GB_FREQ[20][66] = 477; GB_FREQ[51][33] = 476; GB_FREQ[34][86] = 475;\n        GB_FREQ[37][67] = 474; GB_FREQ[53][53] = 473; GB_FREQ[40][88] = 472; GB_FREQ[39][10] = 471; GB_FREQ[24][3]  = 470;\n        GB_FREQ[27][25] = 469; GB_FREQ[26][15] = 468; GB_FREQ[21][88] = 467; GB_FREQ[52][62] = 466; GB_FREQ[46][81] = 465;\n        GB_FREQ[38][72] = 464; GB_FREQ[17][30] = 463; GB_FREQ[52][92] = 462; GB_FREQ[34][90] = 461; GB_FREQ[21][7]  = 460;\n        GB_FREQ[36][13] = 459; GB_FREQ[45][41] = 458; GB_FREQ[32][5]  = 457; GB_FREQ[26][89] = 456; GB_FREQ[23][87] = 455;\n        GB_FREQ[20][39] = 454; GB_FREQ[27][23] = 453; GB_FREQ[25][59] = 452; GB_FREQ[49][20] = 451; GB_FREQ[54][77] = 450;\n        GB_FREQ[27][67] = 449; GB_FREQ[47][33] = 448; GB_FREQ[41][17] = 447; GB_FREQ[19][81] = 446; GB_FREQ[16][66] = 445;\n        GB_FREQ[45][26] = 444; GB_FREQ[49][81] = 443; GB_FREQ[53][55] = 442; GB_FREQ[16][26] = 441; GB_FREQ[54][62] = 440;\n        GB_FREQ[20][70] = 439; GB_FREQ[42][35] = 438; GB_FREQ[20][57] = 437; GB_FREQ[34][36] = 436; GB_FREQ[46][63] = 435;\n        GB_FREQ[19][45] = 434; GB_FREQ[21][10] = 433; GB_FREQ[52][93] = 432; GB_FREQ[25][2]  = 431; GB_FREQ[30][57] = 430;\n        GB_FREQ[41][24] = 429; GB_FREQ[28][43] = 428; GB_FREQ[45][86] = 427; GB_FREQ[51][56] = 426; GB_FREQ[37][28] = 425;\n        GB_FREQ[52][69] = 424; GB_FREQ[43][92] = 423; GB_FREQ[41][31] = 422; GB_FREQ[37][87] = 421; GB_FREQ[47][36] = 420;\n        GB_FREQ[16][16] = 419; GB_FREQ[40][56] = 418; GB_FREQ[24][55] = 417; GB_FREQ[17][1]  = 416; GB_FREQ[35][57] = 415;\n        GB_FREQ[27][50] = 414; GB_FREQ[26][14] = 413; GB_FREQ[50][40] = 412; GB_FREQ[39][19] = 411; GB_FREQ[19][89] = 410;\n        GB_FREQ[29][91] = 409; GB_FREQ[17][89] = 408; GB_FREQ[39][74] = 407; GB_FREQ[46][39] = 406; GB_FREQ[40][28] = 405;\n        GB_FREQ[45][68] = 404; GB_FREQ[43][10] = 403; GB_FREQ[42][13] = 402; GB_FREQ[44][81] = 401; GB_FREQ[41][47] = 400;\n        GB_FREQ[48][58] = 399; GB_FREQ[43][68] = 398; GB_FREQ[16][79] = 397; GB_FREQ[19][5]  = 396; GB_FREQ[54][59] = 395;\n        GB_FREQ[17][36] = 394; GB_FREQ[18][0]  = 393; GB_FREQ[41][5]  = 392; GB_FREQ[41][72] = 391; GB_FREQ[16][39] = 390;\n        GB_FREQ[54][0]  = 389; GB_FREQ[51][16] = 388; GB_FREQ[29][36] = 387; GB_FREQ[47][5]  = 386; GB_FREQ[47][51] = 385;\n        GB_FREQ[44][7]  = 384; GB_FREQ[35][30] = 383; GB_FREQ[26][9]  = 382; GB_FREQ[16][7]  = 381; GB_FREQ[32][1]  = 380;\n        GB_FREQ[33][76] = 379; GB_FREQ[34][91] = 378; GB_FREQ[52][36] = 377; GB_FREQ[26][77] = 376; GB_FREQ[35][48] = 375;\n        GB_FREQ[40][80] = 374; GB_FREQ[41][92] = 373; GB_FREQ[27][93] = 372; GB_FREQ[15][17] = 371; GB_FREQ[16][76] = 370;\n        GB_FREQ[51][12] = 369; GB_FREQ[18][20] = 368; GB_FREQ[15][54] = 367; GB_FREQ[50][5]  = 366; GB_FREQ[33][22] = 365;\n        GB_FREQ[37][57] = 364; GB_FREQ[28][47] = 363; GB_FREQ[42][31] = 362; GB_FREQ[18][2]  = 361; GB_FREQ[43][64] = 360;\n        GB_FREQ[23][47] = 359; GB_FREQ[28][79] = 358; GB_FREQ[25][45] = 357; GB_FREQ[23][91] = 356; GB_FREQ[22][19] = 355;\n        GB_FREQ[25][46] = 354; GB_FREQ[22][36] = 353; GB_FREQ[54][85] = 352; GB_FREQ[46][20] = 351; GB_FREQ[27][37] = 350;\n        GB_FREQ[26][81] = 349; GB_FREQ[42][29] = 348; GB_FREQ[31][90] = 347; GB_FREQ[41][59] = 346; GB_FREQ[24][65] = 345;\n        GB_FREQ[44][84] = 344; GB_FREQ[24][90] = 343; GB_FREQ[38][54] = 342; GB_FREQ[28][70] = 341; GB_FREQ[27][15] = 340;\n        GB_FREQ[28][80] = 339; GB_FREQ[29][8]  = 338; GB_FREQ[45][80] = 337; GB_FREQ[53][37] = 336; GB_FREQ[28][65] = 335;\n        GB_FREQ[23][86] = 334; GB_FREQ[39][45] = 333; GB_FREQ[53][32] = 332; GB_FREQ[38][68] = 331; GB_FREQ[45][78] = 330;\n        GB_FREQ[43][7]  = 329; GB_FREQ[46][82] = 328; GB_FREQ[27][38] = 327; GB_FREQ[16][62] = 326; GB_FREQ[24][17] = 325;\n        GB_FREQ[22][70] = 324; GB_FREQ[52][28] = 323; GB_FREQ[23][40] = 322; GB_FREQ[28][50] = 321; GB_FREQ[42][91] = 320;\n        GB_FREQ[47][76] = 319; GB_FREQ[15][42] = 318; GB_FREQ[43][55] = 317; GB_FREQ[29][84] = 316; GB_FREQ[44][90] = 315;\n        GB_FREQ[53][16] = 314; GB_FREQ[22][93] = 313; GB_FREQ[34][10] = 312; GB_FREQ[32][53] = 311; GB_FREQ[43][65] = 310;\n        GB_FREQ[28][7]  = 309; GB_FREQ[35][46] = 308; GB_FREQ[21][39] = 307; GB_FREQ[44][18] = 306; GB_FREQ[40][10] = 305;\n        GB_FREQ[54][53] = 304; GB_FREQ[38][74] = 303; GB_FREQ[28][26] = 302; GB_FREQ[15][13] = 301; GB_FREQ[39][34] = 300;\n        GB_FREQ[39][46] = 299; GB_FREQ[42][66] = 298; GB_FREQ[33][58] = 297; GB_FREQ[15][56] = 296; GB_FREQ[18][51] = 295;\n        GB_FREQ[49][68] = 294; GB_FREQ[30][37] = 293; GB_FREQ[51][84] = 292; GB_FREQ[51][9]  = 291; GB_FREQ[40][70] = 290;\n        GB_FREQ[41][84] = 289; GB_FREQ[28][64] = 288; GB_FREQ[32][88] = 287; GB_FREQ[24][5]  = 286; GB_FREQ[53][23] = 285;\n        GB_FREQ[42][27] = 284; GB_FREQ[22][38] = 283; GB_FREQ[32][86] = 282; GB_FREQ[34][30] = 281; GB_FREQ[38][63] = 280;\n        GB_FREQ[24][59] = 279; GB_FREQ[22][81] = 278; GB_FREQ[32][11] = 277; GB_FREQ[51][21] = 276; GB_FREQ[54][41] = 275;\n        GB_FREQ[21][50] = 274; GB_FREQ[23][89] = 273; GB_FREQ[19][87] = 272; GB_FREQ[26][7]  = 271; GB_FREQ[30][75] = 270;\n        GB_FREQ[43][84] = 269; GB_FREQ[51][25] = 268; GB_FREQ[16][67] = 267; GB_FREQ[32][9]  = 266; GB_FREQ[48][51] = 265;\n        GB_FREQ[39][7]  = 264; GB_FREQ[44][88] = 263; GB_FREQ[52][24] = 262; GB_FREQ[23][34] = 261; GB_FREQ[32][75] = 260;\n        GB_FREQ[19][10] = 259; GB_FREQ[28][91] = 258; GB_FREQ[32][83] = 257; GB_FREQ[25][75] = 256; GB_FREQ[53][45] = 255;\n        GB_FREQ[29][85] = 254; GB_FREQ[53][59] = 253; GB_FREQ[16][2]  = 252; GB_FREQ[19][78] = 251; GB_FREQ[15][75] = 250;\n        GB_FREQ[51][42] = 249; GB_FREQ[45][67] = 248; GB_FREQ[15][74] = 247; GB_FREQ[25][81] = 246; GB_FREQ[37][62] = 245;\n        GB_FREQ[16][55] = 244; GB_FREQ[18][38] = 243; GB_FREQ[23][23] = 242; GB_FREQ[38][30] = 241; GB_FREQ[17][28] = 240;\n        GB_FREQ[44][73] = 239; GB_FREQ[23][78] = 238; GB_FREQ[40][77] = 237; GB_FREQ[38][87] = 236; GB_FREQ[27][19] = 235;\n        GB_FREQ[38][82] = 234; GB_FREQ[37][22] = 233; GB_FREQ[41][30] = 232; GB_FREQ[54][9]  = 231; GB_FREQ[32][30] = 230;\n        GB_FREQ[30][52] = 229; GB_FREQ[40][84] = 228; GB_FREQ[53][57] = 227; GB_FREQ[27][27] = 226; GB_FREQ[38][64] = 225;\n        GB_FREQ[18][43] = 224; GB_FREQ[23][69] = 223; GB_FREQ[28][12] = 222; GB_FREQ[50][78] = 221; GB_FREQ[50][1]  = 220;\n        GB_FREQ[26][88] = 219; GB_FREQ[36][40] = 218; GB_FREQ[33][89] = 217; GB_FREQ[41][28] = 216; GB_FREQ[31][77] = 215;\n        GB_FREQ[46][1]  = 214; GB_FREQ[47][19] = 213; GB_FREQ[35][55] = 212; GB_FREQ[41][21] = 211; GB_FREQ[27][10] = 210;\n        GB_FREQ[32][77] = 209; GB_FREQ[26][37] = 208; GB_FREQ[20][33] = 207; GB_FREQ[41][52] = 206; GB_FREQ[32][18] = 205;\n        GB_FREQ[38][13] = 204; GB_FREQ[20][18] = 203; GB_FREQ[20][24] = 202; GB_FREQ[45][19] = 201; GB_FREQ[18][53] = 200;\n        /*\n        GBFreq[39][0]  = 199; GBFreq[40][71] = 198; GBFreq[41][27] = 197; GBFreq[15][69] = 196; GBFreq[42][10] = 195;\n        GBFreq[31][89] = 194; GBFreq[51][28] = 193; GBFreq[41][22] = 192; GBFreq[40][43] = 191; GBFreq[38][6]  = 190;\n        GBFreq[37][11] = 189; GBFreq[39][60] = 188; GBFreq[48][47] = 187; GBFreq[46][80] = 186; GBFreq[52][49] = 185;\n        GBFreq[50][48] = 184; GBFreq[25][1]  = 183; GBFreq[52][29] = 182; GBFreq[24][66] = 181; GBFreq[23][35] = 180;\n        GBFreq[49][72] = 179; GBFreq[47][45] = 178; GBFreq[45][14] = 177; GBFreq[51][70] = 176; GBFreq[22][30] = 175;\n        GBFreq[49][83] = 174; GBFreq[26][79] = 173; GBFreq[27][41] = 172; GBFreq[51][81] = 171; GBFreq[41][54] = 170;\n        GBFreq[20][4]  = 169; GBFreq[29][60] = 168; GBFreq[20][27] = 167; GBFreq[50][15] = 166; GBFreq[41][6]  = 165;\n        GBFreq[35][34] = 164; GBFreq[44][87] = 163; GBFreq[46][66] = 162; GBFreq[42][37] = 161; GBFreq[42][24] = 160;\n        GBFreq[54][7]  = 159; GBFreq[41][14] = 158; GBFreq[39][83] = 157; GBFreq[16][87] = 156; GBFreq[20][59] = 155;\n        GBFreq[42][12] = 154; GBFreq[47][2]  = 153; GBFreq[21][32] = 152; GBFreq[53][29] = 151; GBFreq[22][40] = 150;\n        GBFreq[24][58] = 149; GBFreq[52][88] = 148; GBFreq[29][30] = 147; GBFreq[15][91] = 146; GBFreq[54][72] = 145;\n        GBFreq[51][75] = 144; GBFreq[33][67] = 143; GBFreq[41][50] = 142; GBFreq[27][34] = 141; GBFreq[46][17] = 140;\n        GBFreq[31][74] = 139; GBFreq[42][67] = 138; GBFreq[54][87] = 137; GBFreq[27][14] = 136; GBFreq[16][63] = 135;\n        GBFreq[16][5]  = 134; GBFreq[43][23] = 133; GBFreq[23][13] = 132; GBFreq[31][12] = 131; GBFreq[25][57] = 130;\n        GBFreq[38][49] = 129; GBFreq[42][69] = 128; GBFreq[23][80] = 127; GBFreq[29][0]  = 126; GBFreq[28][2]  = 125;\n        GBFreq[28][17] = 124; GBFreq[17][27] = 123; GBFreq[40][16] = 122; GBFreq[45][1]  = 121; GBFreq[36][33] = 120;\n        GBFreq[35][23] = 119; GBFreq[20][86] = 118; GBFreq[29][53] = 117; GBFreq[23][88] = 116; GBFreq[51][87] = 115;\n        GBFreq[54][27] = 114; GBFreq[44][36] = 113; GBFreq[21][45] = 112; GBFreq[53][52] = 111; GBFreq[31][53] = 110;\n        GBFreq[38][47] = 109; GBFreq[27][21] = 108; GBFreq[30][42] = 107; GBFreq[29][10] = 106; GBFreq[35][35] = 105;\n        GBFreq[24][56] = 104; GBFreq[41][29] = 103; GBFreq[18][68] = 102; GBFreq[29][24] = 101; GBFreq[25][84] = 100;\n        GBFreq[35][47] =  99; GBFreq[29][56] =  98; GBFreq[30][44] =  97; GBFreq[53][3]  =  96; GBFreq[30][63] =  95;\n        GBFreq[52][52] =  94; GBFreq[54][1]  =  93; GBFreq[22][48] =  92; GBFreq[54][66] =  91; GBFreq[21][90] =  90;\n        GBFreq[52][47] =  89; GBFreq[39][25] =  88; GBFreq[39][39] =  87; GBFreq[44][37] =  86; GBFreq[44][76] =  85;\n        GBFreq[46][75] =  84; GBFreq[18][37] =  83; GBFreq[47][42] =  82; GBFreq[19][92] =  81; GBFreq[51][27] =  80;\n        GBFreq[48][83] =  79; GBFreq[23][70] =  78; GBFreq[29][9]  =  77; GBFreq[33][79] =  76; GBFreq[52][90] =  75;\n        GBFreq[53][6]  =  74; GBFreq[24][36] =  73; GBFreq[25][25] =  72; GBFreq[44][26] =  71; GBFreq[25][36] =  70;\n        GBFreq[29][87] =  69; GBFreq[48][0]  =  68; GBFreq[15][40] =  67; GBFreq[17][45] =  66; GBFreq[30][14] =  65;\n        GBFreq[48][38] =  64; GBFreq[23][19] =  63; GBFreq[40][42] =  62; GBFreq[31][63] =  61; GBFreq[16][23] =  60;\n        GBFreq[26][21] =  59; GBFreq[32][76] =  58; GBFreq[23][58] =  57; GBFreq[41][37] =  56; GBFreq[30][43] =  55;\n        GBFreq[47][38] =  54; GBFreq[21][46] =  53; GBFreq[18][33] =  52; GBFreq[52][37] =  51; GBFreq[36][8]  =  50;\n        GBFreq[49][24] =  49; GBFreq[15][66] =  48; GBFreq[35][77] =  47; GBFreq[27][58] =  46; GBFreq[35][51] =  45;\n        GBFreq[24][69] =  44; GBFreq[20][54] =  43; GBFreq[24][41] =  42; GBFreq[41][0]  =  41; GBFreq[33][71] =  40;\n        GBFreq[23][52] =  39; GBFreq[29][67] =  38; GBFreq[46][51] =  37; GBFreq[46][90] =  36; GBFreq[49][33] =  35;\n        GBFreq[33][28] =  34; GBFreq[37][86] =  33; GBFreq[39][22] =  32; GBFreq[37][37] =  31; GBFreq[29][62] =  30;\n        GBFreq[29][50] =  29; GBFreq[36][89] =  28; GBFreq[42][44] =  27; GBFreq[51][82] =  26; GBFreq[28][83] =  25;\n        GBFreq[15][78] =  24; GBFreq[46][62] =  23; GBFreq[19][69] =  22; GBFreq[51][23] =  21; GBFreq[37][69] =  20;\n        GBFreq[25][5]  =  19; GBFreq[51][85] =  18; GBFreq[48][77] =  17; GBFreq[32][46] =  16; GBFreq[53][60] =  15;\n        GBFreq[28][57] =  14; GBFreq[54][82] =  13; GBFreq[54][15] =  12; GBFreq[49][54] =  11; GBFreq[53][87] =  10;\n        GBFreq[27][16] =   9; GBFreq[29][34] =   8; GBFreq[20][44] =   7; GBFreq[42][73] =   6; GBFreq[47][71] =   5;\n        GBFreq[29][37] =   4; GBFreq[25][50] =   3; GBFreq[18][84] =   2; GBFreq[50][45] =   1; GBFreq[48][46] =   0;\n        GBFreq[43][89] =  -1; GBFreq[54][68] =  -2;\n        */\n\n        // ------------------------------------------------------------------------------------BIG5_FREQ\n\n        BIG5_FREQ[9][89]    = 600; BIG5_FREQ[11][15]   = 599; BIG5_FREQ[3][66]    = 598; BIG5_FREQ[6][121]   = 597; BIG5_FREQ[3][0]     = 596;\n        BIG5_FREQ[5][82]    = 595; BIG5_FREQ[3][42]    = 594; BIG5_FREQ[5][34]    = 593; BIG5_FREQ[3][8]     = 592; BIG5_FREQ[3][6]     = 591;\n        BIG5_FREQ[3][67]    = 590; BIG5_FREQ[7][139]   = 589; BIG5_FREQ[23][137]  = 588; BIG5_FREQ[12][46]   = 587; BIG5_FREQ[4][8]     = 586;\n        BIG5_FREQ[4][41]    = 585; BIG5_FREQ[18][47]   = 584; BIG5_FREQ[12][114]  = 583; BIG5_FREQ[6][1]     = 582; BIG5_FREQ[22][60]   = 581;\n        BIG5_FREQ[5][46]    = 580; BIG5_FREQ[11][79]   = 579; BIG5_FREQ[3][23]    = 578; BIG5_FREQ[7][114]   = 577; BIG5_FREQ[29][102]  = 576;\n        BIG5_FREQ[19][14]   = 575; BIG5_FREQ[4][133]   = 574; BIG5_FREQ[3][29]    = 573; BIG5_FREQ[4][109]   = 572; BIG5_FREQ[14][127]  = 571;\n        BIG5_FREQ[5][48]    = 570; BIG5_FREQ[13][104]  = 569; BIG5_FREQ[3][132]   = 568; BIG5_FREQ[26][64]   = 567; BIG5_FREQ[7][19]    = 566;\n        BIG5_FREQ[4][12]    = 565; BIG5_FREQ[11][124]  = 564; BIG5_FREQ[7][89]    = 563; BIG5_FREQ[15][124]  = 562; BIG5_FREQ[4][108]   = 561;\n        BIG5_FREQ[19][66]   = 560; BIG5_FREQ[3][21]    = 559; BIG5_FREQ[24][12]   = 558; BIG5_FREQ[28][111]  = 557; BIG5_FREQ[12][107]  = 556;\n        BIG5_FREQ[3][112]   = 555; BIG5_FREQ[8][113]   = 554; BIG5_FREQ[5][40]    = 553; BIG5_FREQ[26][145]  = 552; BIG5_FREQ[3][48]    = 551;\n        BIG5_FREQ[3][70]    = 550; BIG5_FREQ[22][17]   = 549; BIG5_FREQ[16][47]   = 548; BIG5_FREQ[3][53]    = 547; BIG5_FREQ[4][24]    = 546;\n        BIG5_FREQ[32][120]  = 545; BIG5_FREQ[24][49]   = 544; BIG5_FREQ[24][142]  = 543; BIG5_FREQ[18][66]   = 542; BIG5_FREQ[29][150]  = 541;\n        BIG5_FREQ[5][122]   = 540; BIG5_FREQ[5][114]   = 539; BIG5_FREQ[3][44]    = 538; BIG5_FREQ[10][128]  = 537; BIG5_FREQ[15][20]   = 536;\n        BIG5_FREQ[13][33]   = 535; BIG5_FREQ[14][87]   = 534; BIG5_FREQ[3][126]   = 533; BIG5_FREQ[4][53]    = 532; BIG5_FREQ[4][40]    = 531;\n        BIG5_FREQ[9][93]    = 530; BIG5_FREQ[15][137]  = 529; BIG5_FREQ[10][123]  = 528; BIG5_FREQ[4][56]    = 527; BIG5_FREQ[5][71]    = 526;\n        BIG5_FREQ[10][8]    = 525; BIG5_FREQ[5][16]    = 524; BIG5_FREQ[5][146]   = 523; BIG5_FREQ[18][88]   = 522; BIG5_FREQ[24][4]    = 521;\n        BIG5_FREQ[20][47]   = 520; BIG5_FREQ[5][33]    = 519; BIG5_FREQ[9][43]    = 518; BIG5_FREQ[20][12]   = 517; BIG5_FREQ[20][13]   = 516;\n        BIG5_FREQ[5][156]   = 515; BIG5_FREQ[22][140]  = 514; BIG5_FREQ[8][146]   = 513; BIG5_FREQ[21][123]  = 512; BIG5_FREQ[4][90]    = 511;\n        BIG5_FREQ[5][62]    = 510; BIG5_FREQ[17][59]   = 509; BIG5_FREQ[10][37]   = 508; BIG5_FREQ[18][107]  = 507; BIG5_FREQ[14][53]   = 506;\n        BIG5_FREQ[22][51]   = 505; BIG5_FREQ[8][13]    = 504; BIG5_FREQ[5][29]    = 503; BIG5_FREQ[9][7]     = 502; BIG5_FREQ[22][14]   = 501;\n        BIG5_FREQ[8][55]    = 500; BIG5_FREQ[33][9]    = 499; BIG5_FREQ[16][64]   = 498; BIG5_FREQ[7][131]   = 497; BIG5_FREQ[34][4]    = 496;\n        BIG5_FREQ[7][101]   = 495; BIG5_FREQ[11][139]  = 494; BIG5_FREQ[3][135]   = 493; BIG5_FREQ[7][102]   = 492; BIG5_FREQ[17][13]   = 491;\n        BIG5_FREQ[3][20]    = 490; BIG5_FREQ[27][106]  = 489; BIG5_FREQ[5][88]    = 488; BIG5_FREQ[6][33]    = 487; BIG5_FREQ[5][139]   = 486;\n        BIG5_FREQ[6][0]     = 485; BIG5_FREQ[17][58]   = 484; BIG5_FREQ[5][133]   = 483; BIG5_FREQ[9][107]   = 482; BIG5_FREQ[23][39]   = 481;\n        BIG5_FREQ[5][23]    = 480; BIG5_FREQ[3][79]    = 479; BIG5_FREQ[32][97]   = 478; BIG5_FREQ[3][136]   = 477; BIG5_FREQ[4][94]    = 476;\n        BIG5_FREQ[21][61]   = 475; BIG5_FREQ[23][123]  = 474; BIG5_FREQ[26][16]   = 473; BIG5_FREQ[24][137]  = 472; BIG5_FREQ[22][18]   = 471;\n        BIG5_FREQ[5][1]     = 470; BIG5_FREQ[20][119]  = 469; BIG5_FREQ[3][7]     = 468; BIG5_FREQ[10][79]   = 467; BIG5_FREQ[15][105]  = 466;\n        BIG5_FREQ[3][144]   = 465; BIG5_FREQ[12][80]   = 464; BIG5_FREQ[15][73]   = 463; BIG5_FREQ[3][19]    = 462; BIG5_FREQ[8][109]   = 461;\n        BIG5_FREQ[3][15]    = 460; BIG5_FREQ[31][82]   = 459; BIG5_FREQ[3][43]    = 458; BIG5_FREQ[25][119]  = 457; BIG5_FREQ[16][111]  = 456;\n        BIG5_FREQ[7][77]    = 455; BIG5_FREQ[3][95]    = 454; BIG5_FREQ[24][82]   = 453; BIG5_FREQ[7][52]    = 452; BIG5_FREQ[9][151]   = 451;\n        BIG5_FREQ[3][129]   = 450; BIG5_FREQ[5][87]    = 449; BIG5_FREQ[3][55]    = 448; BIG5_FREQ[8][153]   = 447; BIG5_FREQ[4][83]    = 446;\n        BIG5_FREQ[3][114]   = 445; BIG5_FREQ[23][147]  = 444; BIG5_FREQ[15][31]   = 443; BIG5_FREQ[3][54]    = 442; BIG5_FREQ[11][122]  = 441;\n        BIG5_FREQ[4][4]     = 440; BIG5_FREQ[34][149]  = 439; BIG5_FREQ[3][17]    = 438; BIG5_FREQ[21][64]   = 437; BIG5_FREQ[26][144]  = 436;\n        BIG5_FREQ[4][62]    = 435; BIG5_FREQ[8][15]    = 434; BIG5_FREQ[35][80]   = 433; BIG5_FREQ[7][110]   = 432; BIG5_FREQ[23][114]  = 431;\n        BIG5_FREQ[3][108]   = 430; BIG5_FREQ[3][62]    = 429; BIG5_FREQ[21][41]   = 428; BIG5_FREQ[15][99]   = 427; BIG5_FREQ[5][47]    = 426;\n        BIG5_FREQ[4][96]    = 425; BIG5_FREQ[20][122]  = 424; BIG5_FREQ[5][21]    = 423; BIG5_FREQ[4][157]   = 422; BIG5_FREQ[16][14]   = 421;\n        BIG5_FREQ[3][117]   = 420; BIG5_FREQ[7][129]   = 419; BIG5_FREQ[4][27]    = 418; BIG5_FREQ[5][30]    = 417; BIG5_FREQ[22][16]   = 416;\n        BIG5_FREQ[5][64]    = 415; BIG5_FREQ[17][99]   = 414; BIG5_FREQ[17][57]   = 413; BIG5_FREQ[8][105]   = 412; BIG5_FREQ[5][112]   = 411;\n        BIG5_FREQ[20][59]   = 410; BIG5_FREQ[6][129]   = 409; BIG5_FREQ[18][17]   = 408; BIG5_FREQ[3][92]    = 407; BIG5_FREQ[28][118]  = 406;\n        BIG5_FREQ[3][109]   = 405; BIG5_FREQ[31][51]   = 404; BIG5_FREQ[13][116]  = 403; BIG5_FREQ[6][15]    = 402; BIG5_FREQ[36][136]  = 401;\n        BIG5_FREQ[12][74]   = 400; BIG5_FREQ[20][88]   = 399; BIG5_FREQ[36][68]   = 398; BIG5_FREQ[3][147]   = 397; BIG5_FREQ[15][84]   = 396;\n        BIG5_FREQ[16][32]   = 395; BIG5_FREQ[16][58]   = 394; BIG5_FREQ[7][66]    = 393; BIG5_FREQ[23][107]  = 392; BIG5_FREQ[9][6]     = 391;\n        BIG5_FREQ[12][86]   = 390; BIG5_FREQ[23][112]  = 389; BIG5_FREQ[37][23]   = 388; BIG5_FREQ[3][138]   = 387; BIG5_FREQ[20][68]   = 386;\n        BIG5_FREQ[15][116]  = 385; BIG5_FREQ[18][64]   = 384; BIG5_FREQ[12][139]  = 383; BIG5_FREQ[11][155]  = 382; BIG5_FREQ[4][156]   = 381;\n        BIG5_FREQ[12][84]   = 380; BIG5_FREQ[18][49]   = 379; BIG5_FREQ[25][125]  = 378; BIG5_FREQ[25][147]  = 377; BIG5_FREQ[15][110]  = 376;\n        BIG5_FREQ[19][96]   = 375; BIG5_FREQ[30][152]  = 374; BIG5_FREQ[6][31]    = 373; BIG5_FREQ[27][117]  = 372; BIG5_FREQ[3][10]    = 371;\n        BIG5_FREQ[6][131]   = 370; BIG5_FREQ[13][112]  = 369; BIG5_FREQ[36][156]  = 368; BIG5_FREQ[4][60]    = 367; BIG5_FREQ[15][121]  = 366;\n        BIG5_FREQ[4][112]   = 365; BIG5_FREQ[30][142]  = 364; BIG5_FREQ[23][154]  = 363; BIG5_FREQ[27][101]  = 362; BIG5_FREQ[9][140]   = 361;\n        BIG5_FREQ[3][89]    = 360; BIG5_FREQ[18][148]  = 359; BIG5_FREQ[4][69]    = 358; BIG5_FREQ[16][49]   = 357; BIG5_FREQ[6][117]   = 356;\n        BIG5_FREQ[36][55]   = 355; BIG5_FREQ[5][123]   = 354; BIG5_FREQ[4][126]   = 353; BIG5_FREQ[4][119]   = 352; BIG5_FREQ[9][95]    = 351;\n        BIG5_FREQ[5][24]    = 350; BIG5_FREQ[16][133]  = 349; BIG5_FREQ[10][134]  = 348; BIG5_FREQ[26][59]   = 347; BIG5_FREQ[6][41]    = 346;\n        BIG5_FREQ[6][146]   = 345; BIG5_FREQ[19][24]   = 344; BIG5_FREQ[5][113]   = 343; BIG5_FREQ[10][118]  = 342; BIG5_FREQ[34][151]  = 341;\n        BIG5_FREQ[9][72]    = 340; BIG5_FREQ[31][25]   = 339; BIG5_FREQ[18][126]  = 338; BIG5_FREQ[18][28]   = 337; BIG5_FREQ[4][153]   = 336;\n        BIG5_FREQ[3][84]    = 335; BIG5_FREQ[21][18]   = 334; BIG5_FREQ[25][129]  = 333; BIG5_FREQ[6][107]   = 332; BIG5_FREQ[12][25]   = 331;\n        BIG5_FREQ[17][109]  = 330; BIG5_FREQ[7][76]    = 329; BIG5_FREQ[15][15]   = 328; BIG5_FREQ[4][14]    = 327; BIG5_FREQ[23][88]   = 326;\n        BIG5_FREQ[18][2]    = 325; BIG5_FREQ[6][88]    = 324; BIG5_FREQ[16][84]   = 323; BIG5_FREQ[12][48]   = 322; BIG5_FREQ[7][68]    = 321;\n        BIG5_FREQ[5][50]    = 320; BIG5_FREQ[13][54]   = 319; BIG5_FREQ[7][98]    = 318; BIG5_FREQ[11][6]    = 317; BIG5_FREQ[9][80]    = 316;\n        BIG5_FREQ[16][41]   = 315; BIG5_FREQ[7][43]    = 314; BIG5_FREQ[28][117]  = 313; BIG5_FREQ[3][51]    = 312; BIG5_FREQ[7][3]     = 311;\n        BIG5_FREQ[20][81]   = 310; BIG5_FREQ[4][2]     = 309; BIG5_FREQ[11][16]   = 308; BIG5_FREQ[10][4]    = 307; BIG5_FREQ[10][119]  = 306;\n        BIG5_FREQ[6][142]   = 305; BIG5_FREQ[18][51]   = 304; BIG5_FREQ[8][144]   = 303; BIG5_FREQ[10][65]   = 302; BIG5_FREQ[11][64]   = 301;\n        BIG5_FREQ[11][130]  = 300; BIG5_FREQ[9][92]    = 299; BIG5_FREQ[18][29]   = 298; BIG5_FREQ[18][78]   = 297; BIG5_FREQ[18][151]  = 296;\n        BIG5_FREQ[33][127]  = 295; BIG5_FREQ[35][113]  = 294; BIG5_FREQ[10][155]  = 293; BIG5_FREQ[3][76]    = 292; BIG5_FREQ[36][123]  = 291;\n        BIG5_FREQ[13][143]  = 290; BIG5_FREQ[5][135]   = 289; BIG5_FREQ[23][116]  = 288; BIG5_FREQ[6][101]   = 287; BIG5_FREQ[14][74]   = 286;\n        BIG5_FREQ[7][153]   = 285; BIG5_FREQ[3][101]   = 284; BIG5_FREQ[9][74]    = 283; BIG5_FREQ[3][156]   = 282; BIG5_FREQ[4][147]   = 281;\n        BIG5_FREQ[9][12]    = 280; BIG5_FREQ[18][133]  = 279; BIG5_FREQ[4][0]     = 278; BIG5_FREQ[7][155]   = 277; BIG5_FREQ[9][144]   = 276;\n        BIG5_FREQ[23][49]   = 275; BIG5_FREQ[5][89]    = 274; BIG5_FREQ[10][11]   = 273; BIG5_FREQ[3][110]   = 272; BIG5_FREQ[3][40]    = 271;\n        BIG5_FREQ[29][115]  = 270; BIG5_FREQ[9][100]   = 269; BIG5_FREQ[21][67]   = 268; BIG5_FREQ[23][145]  = 267; BIG5_FREQ[10][47]   = 266;\n        BIG5_FREQ[4][31]    = 265; BIG5_FREQ[4][81]    = 264; BIG5_FREQ[22][62]   = 263; BIG5_FREQ[4][28]    = 262; BIG5_FREQ[27][39]   = 261;\n        BIG5_FREQ[27][54]   = 260; BIG5_FREQ[32][46]   = 259; BIG5_FREQ[4][76]    = 258; BIG5_FREQ[26][15]   = 257; BIG5_FREQ[12][154]  = 256;\n        BIG5_FREQ[9][150]   = 255; BIG5_FREQ[15][17]   = 254; BIG5_FREQ[5][129]   = 253; BIG5_FREQ[10][40]   = 252; BIG5_FREQ[13][37]   = 251;\n        BIG5_FREQ[31][104]  = 250; BIG5_FREQ[3][152]   = 249; BIG5_FREQ[5][22]    = 248; BIG5_FREQ[8][48]    = 247; BIG5_FREQ[4][74]    = 246;\n        BIG5_FREQ[6][17]    = 245; BIG5_FREQ[30][82]   = 244; BIG5_FREQ[4][116]   = 243; BIG5_FREQ[16][42]   = 242; BIG5_FREQ[5][55]    = 241;\n        BIG5_FREQ[4][64]    = 240; BIG5_FREQ[14][19]   = 239; BIG5_FREQ[35][82]   = 238; BIG5_FREQ[30][139]  = 237; BIG5_FREQ[26][152]  = 236;\n        BIG5_FREQ[32][32]   = 235; BIG5_FREQ[21][102]  = 234; BIG5_FREQ[10][131]  = 233; BIG5_FREQ[9][128]   = 232; BIG5_FREQ[3][87]    = 231;\n        BIG5_FREQ[4][51]    = 230; BIG5_FREQ[10][15]   = 229; BIG5_FREQ[4][150]   = 228; BIG5_FREQ[7][4]     = 227; BIG5_FREQ[7][51]    = 226;\n        BIG5_FREQ[7][157]   = 225; BIG5_FREQ[4][146]   = 224; BIG5_FREQ[4][91]    = 223; BIG5_FREQ[7][13]    = 222; BIG5_FREQ[17][116]  = 221;\n        BIG5_FREQ[23][21]   = 220; BIG5_FREQ[5][106]   = 219; BIG5_FREQ[14][100]  = 218; BIG5_FREQ[10][152]  = 217; BIG5_FREQ[14][89]   = 216;\n        BIG5_FREQ[6][138]   = 215; BIG5_FREQ[12][157]  = 214; BIG5_FREQ[10][102]  = 213; BIG5_FREQ[19][94]   = 212; BIG5_FREQ[7][74]    = 211;\n        BIG5_FREQ[18][128]  = 210; BIG5_FREQ[27][111]  = 209; BIG5_FREQ[11][57]   = 208; BIG5_FREQ[3][131]   = 207; BIG5_FREQ[30][23]   = 206;\n        BIG5_FREQ[30][126]  = 205; BIG5_FREQ[4][36]    = 204; BIG5_FREQ[26][124]  = 203; BIG5_FREQ[4][19]    = 202; BIG5_FREQ[9][152]   = 201;\n        BIG5P_FREQ[41][122] = 600; BIG5P_FREQ[35][0]   = 599; BIG5P_FREQ[43][15]  = 598; BIG5P_FREQ[35][99]  = 597; BIG5P_FREQ[35][6]   = 596;\n        BIG5P_FREQ[35][8]   = 595; BIG5P_FREQ[38][154] = 594; BIG5P_FREQ[37][34]  = 593; BIG5P_FREQ[37][115] = 592; BIG5P_FREQ[36][12]  = 591;\n        BIG5P_FREQ[18][77]  = 590; BIG5P_FREQ[35][100] = 589; BIG5P_FREQ[35][42]  = 588; BIG5P_FREQ[120][75] = 587; BIG5P_FREQ[35][23]  = 586;\n        BIG5P_FREQ[13][72]  = 585; BIG5P_FREQ[0][67]   = 584; BIG5P_FREQ[39][172] = 583; BIG5P_FREQ[22][182] = 582; BIG5P_FREQ[15][186] = 581;\n        BIG5P_FREQ[15][165] = 580; BIG5P_FREQ[35][44]  = 579; BIG5P_FREQ[40][13]  = 578; BIG5P_FREQ[38][1]   = 577; BIG5P_FREQ[37][33]  = 576;\n        BIG5P_FREQ[36][24]  = 575; BIG5P_FREQ[56][4]   = 574; BIG5P_FREQ[35][29]  = 573; BIG5P_FREQ[9][96]   = 572; BIG5P_FREQ[37][62]  = 571;\n        BIG5P_FREQ[48][47]  = 570; BIG5P_FREQ[51][14]  = 569; BIG5P_FREQ[39][122] = 568; BIG5P_FREQ[44][46]  = 567; BIG5P_FREQ[35][21]  = 566;\n        BIG5P_FREQ[36][8]   = 565; BIG5P_FREQ[36][141] = 564; BIG5P_FREQ[3][81]   = 563; BIG5P_FREQ[37][155] = 562; BIG5P_FREQ[42][84]  = 561;\n        BIG5P_FREQ[36][40]  = 560; BIG5P_FREQ[35][103] = 559; BIG5P_FREQ[11][84]  = 558; BIG5P_FREQ[45][33]  = 557; BIG5P_FREQ[121][79] = 556;\n        BIG5P_FREQ[2][77]   = 555; BIG5P_FREQ[36][41]  = 554; BIG5P_FREQ[37][47]  = 553; BIG5P_FREQ[39][125] = 552; BIG5P_FREQ[37][26]  = 551;\n        BIG5P_FREQ[35][48]  = 550; BIG5P_FREQ[35][28]  = 549; BIG5P_FREQ[35][159] = 548; BIG5P_FREQ[37][40]  = 547; BIG5P_FREQ[35][145] = 546;\n        BIG5P_FREQ[37][147] = 545; BIG5P_FREQ[46][160] = 544; BIG5P_FREQ[37][46]  = 543; BIG5P_FREQ[50][99]  = 542; BIG5P_FREQ[52][13]  = 541;\n        BIG5P_FREQ[10][82]  = 540; BIG5P_FREQ[35][169] = 539; BIG5P_FREQ[35][31]  = 538; BIG5P_FREQ[47][31]  = 537; BIG5P_FREQ[18][79]  = 536;\n        BIG5P_FREQ[16][113] = 535; BIG5P_FREQ[37][104] = 534; BIG5P_FREQ[39][134] = 533; BIG5P_FREQ[36][53]  = 532; BIG5P_FREQ[38][0]   = 531;\n        BIG5P_FREQ[4][86]   = 530; BIG5P_FREQ[54][17]  = 529; BIG5P_FREQ[43][157] = 528; BIG5P_FREQ[35][165] = 527; BIG5P_FREQ[69][147] = 526;\n        BIG5P_FREQ[117][95] = 525; BIG5P_FREQ[35][162] = 524; BIG5P_FREQ[35][17]  = 523; BIG5P_FREQ[36][142] = 522; BIG5P_FREQ[36][4]   = 521;\n        BIG5P_FREQ[37][166] = 520; BIG5P_FREQ[35][168] = 519; BIG5P_FREQ[35][19]  = 518; BIG5P_FREQ[37][48]  = 517; BIG5P_FREQ[42][37]  = 516;\n        BIG5P_FREQ[40][146] = 515; BIG5P_FREQ[36][123] = 514; BIG5P_FREQ[22][41]  = 513; BIG5P_FREQ[20][119] = 512; BIG5P_FREQ[2][74]   = 511;\n        BIG5P_FREQ[44][113] = 510; BIG5P_FREQ[35][125] = 509; BIG5P_FREQ[37][16]  = 508; BIG5P_FREQ[35][20]  = 507; BIG5P_FREQ[35][55]  = 506;\n        BIG5P_FREQ[37][145] = 505; BIG5P_FREQ[0][88]   = 504; BIG5P_FREQ[3][94]   = 503; BIG5P_FREQ[6][65]   = 502; BIG5P_FREQ[26][15]  = 501;\n        BIG5P_FREQ[41][126] = 500; BIG5P_FREQ[36][129] = 499; BIG5P_FREQ[31][75]  = 498; BIG5P_FREQ[19][61]  = 497; BIG5P_FREQ[35][128] = 496;\n        BIG5P_FREQ[29][79]  = 495; BIG5P_FREQ[36][62]  = 494; BIG5P_FREQ[37][189] = 493; BIG5P_FREQ[39][109] = 492; BIG5P_FREQ[39][135] = 491;\n        BIG5P_FREQ[72][15]  = 490; BIG5P_FREQ[47][106] = 489; BIG5P_FREQ[54][14]  = 488; BIG5P_FREQ[24][52]  = 487; BIG5P_FREQ[38][162] = 486;\n        BIG5P_FREQ[41][43]  = 485; BIG5P_FREQ[37][121] = 484; BIG5P_FREQ[14][66]  = 483; BIG5P_FREQ[37][30]  = 482; BIG5P_FREQ[35][7]   = 481;\n        BIG5P_FREQ[49][58]  = 480; BIG5P_FREQ[43][188] = 479; BIG5P_FREQ[24][66]  = 478; BIG5P_FREQ[35][171] = 477; BIG5P_FREQ[40][186] = 476;\n        BIG5P_FREQ[39][164] = 475; BIG5P_FREQ[78][186] = 474; BIG5P_FREQ[8][72]   = 473; BIG5P_FREQ[36][190] = 472; BIG5P_FREQ[35][53]  = 471;\n        BIG5P_FREQ[35][54]  = 470; BIG5P_FREQ[22][159] = 469; BIG5P_FREQ[35][9]   = 468; BIG5P_FREQ[41][140] = 467; BIG5P_FREQ[37][22]  = 466;\n        BIG5P_FREQ[48][97]  = 465; BIG5P_FREQ[50][97]  = 464; BIG5P_FREQ[36][127] = 463; BIG5P_FREQ[37][23]  = 462; BIG5P_FREQ[40][55]  = 461;\n        BIG5P_FREQ[35][43]  = 460; BIG5P_FREQ[26][22]  = 459; BIG5P_FREQ[35][15]  = 458; BIG5P_FREQ[72][179] = 457; BIG5P_FREQ[20][129] = 456;\n        BIG5P_FREQ[52][101] = 455; BIG5P_FREQ[35][12]  = 454; BIG5P_FREQ[42][156] = 453; BIG5P_FREQ[15][157] = 452; BIG5P_FREQ[50][140] = 451;\n        BIG5P_FREQ[26][28]  = 450; BIG5P_FREQ[54][51]  = 449; BIG5P_FREQ[35][112] = 448; BIG5P_FREQ[36][116] = 447; BIG5P_FREQ[42][11]  = 446;\n        BIG5P_FREQ[37][172] = 445; BIG5P_FREQ[37][29]  = 444; BIG5P_FREQ[44][107] = 443; BIG5P_FREQ[50][17]  = 442; BIG5P_FREQ[39][107] = 441;\n        BIG5P_FREQ[19][109] = 440; BIG5P_FREQ[36][60]  = 439; BIG5P_FREQ[49][132] = 438; BIG5P_FREQ[26][16]  = 437; BIG5P_FREQ[43][155] = 436;\n        BIG5P_FREQ[37][120] = 435; BIG5P_FREQ[15][159] = 434; BIG5P_FREQ[43][6]   = 433; BIG5P_FREQ[45][188] = 432; BIG5P_FREQ[35][38]  = 431;\n        BIG5P_FREQ[39][143] = 430; BIG5P_FREQ[48][144] = 429; BIG5P_FREQ[37][168] = 428; BIG5P_FREQ[37][1]   = 427; BIG5P_FREQ[36][109] = 426;\n        BIG5P_FREQ[46][53]  = 425; BIG5P_FREQ[38][54]  = 424; BIG5P_FREQ[36][0]   = 423; BIG5P_FREQ[72][33]  = 422; BIG5P_FREQ[42][8]   = 421;\n        BIG5P_FREQ[36][31]  = 420; BIG5P_FREQ[35][150] = 419; BIG5P_FREQ[118][93] = 418; BIG5P_FREQ[37][61]  = 417; BIG5P_FREQ[0][85]   = 416;\n        BIG5P_FREQ[36][27]  = 415; BIG5P_FREQ[35][134] = 414; BIG5P_FREQ[36][145] = 413; BIG5P_FREQ[6][96]   = 412; BIG5P_FREQ[36][14]  = 411;\n        BIG5P_FREQ[16][36]  = 410; BIG5P_FREQ[15][175] = 409; BIG5P_FREQ[35][10]  = 408; BIG5P_FREQ[36][189] = 407; BIG5P_FREQ[35][51]  = 406;\n        BIG5P_FREQ[35][109] = 405; BIG5P_FREQ[35][147] = 404; BIG5P_FREQ[35][180] = 403; BIG5P_FREQ[72][5]   = 402; BIG5P_FREQ[36][107] = 401;\n        BIG5P_FREQ[49][116] = 400; BIG5P_FREQ[73][30]  = 399; BIG5P_FREQ[6][90]   = 398; BIG5P_FREQ[2][70]   = 397; BIG5P_FREQ[17][141] = 396;\n        BIG5P_FREQ[35][62]  = 395; BIG5P_FREQ[16][180] = 394; BIG5P_FREQ[4][91]   = 393; BIG5P_FREQ[15][171] = 392; BIG5P_FREQ[35][177] = 391;\n        BIG5P_FREQ[37][173] = 390; BIG5P_FREQ[16][121] = 389; BIG5P_FREQ[35][5]   = 388; BIG5P_FREQ[46][122] = 387; BIG5P_FREQ[40][138] = 386;\n        BIG5P_FREQ[50][49]  = 385; BIG5P_FREQ[36][152] = 384; BIG5P_FREQ[13][43]  = 383; BIG5P_FREQ[9][88]   = 382; BIG5P_FREQ[36][159] = 381;\n        BIG5P_FREQ[27][62]  = 380; BIG5P_FREQ[40][18]  = 379; BIG5P_FREQ[17][129] = 378; BIG5P_FREQ[43][97]  = 377; BIG5P_FREQ[13][131] = 376;\n        BIG5P_FREQ[46][107] = 375; BIG5P_FREQ[60][64]  = 374; BIG5P_FREQ[36][179] = 373; BIG5P_FREQ[37][55]  = 372; BIG5P_FREQ[41][173] = 371;\n        BIG5P_FREQ[44][172] = 370; BIG5P_FREQ[23][187] = 369; BIG5P_FREQ[36][149] = 368; BIG5P_FREQ[17][125] = 367; BIG5P_FREQ[55][180] = 366;\n        BIG5P_FREQ[51][129] = 365; BIG5P_FREQ[36][51]  = 364; BIG5P_FREQ[37][122] = 363; BIG5P_FREQ[48][32]  = 362; BIG5P_FREQ[51][99]  = 361;\n        BIG5P_FREQ[54][16]  = 360; BIG5P_FREQ[41][183] = 359; BIG5P_FREQ[37][179] = 358; BIG5P_FREQ[38][179] = 357; BIG5P_FREQ[35][143] = 356;\n        BIG5P_FREQ[37][24]  = 355; BIG5P_FREQ[40][177] = 354; BIG5P_FREQ[47][117] = 353; BIG5P_FREQ[39][52]  = 352; BIG5P_FREQ[22][99]  = 351;\n        BIG5P_FREQ[40][142] = 350; BIG5P_FREQ[36][49]  = 349; BIG5P_FREQ[38][17]  = 348; BIG5P_FREQ[39][188] = 347; BIG5P_FREQ[36][186] = 346;\n        BIG5P_FREQ[35][189] = 345; BIG5P_FREQ[41][7]   = 344; BIG5P_FREQ[18][91]  = 343; BIG5P_FREQ[43][137] = 342; BIG5P_FREQ[35][142] = 341;\n        BIG5P_FREQ[35][117] = 340; BIG5P_FREQ[39][138] = 339; BIG5P_FREQ[16][59]  = 338; BIG5P_FREQ[39][174] = 337; BIG5P_FREQ[55][145] = 336;\n        BIG5P_FREQ[37][21]  = 335; BIG5P_FREQ[36][180] = 334; BIG5P_FREQ[37][156] = 333; BIG5P_FREQ[49][13]  = 332; BIG5P_FREQ[41][107] = 331;\n        BIG5P_FREQ[36][56]  = 330; BIG5P_FREQ[53][8]   = 329; BIG5P_FREQ[22][114] = 328; BIG5P_FREQ[5][95]   = 327; BIG5P_FREQ[37][0]   = 326;\n        BIG5P_FREQ[26][183] = 325; BIG5P_FREQ[22][66]  = 324; BIG5P_FREQ[35][58]  = 323; BIG5P_FREQ[48][117] = 322; BIG5P_FREQ[36][102] = 321;\n        BIG5P_FREQ[22][122] = 320; BIG5P_FREQ[35][11]  = 319; BIG5P_FREQ[46][19]  = 318; BIG5P_FREQ[22][49]  = 317; BIG5P_FREQ[48][166] = 316;\n        BIG5P_FREQ[41][125] = 315; BIG5P_FREQ[41][1]   = 314; BIG5P_FREQ[35][178] = 313; BIG5P_FREQ[41][12]  = 312; BIG5P_FREQ[26][167] = 311;\n        BIG5P_FREQ[42][152] = 310; BIG5P_FREQ[42][46]  = 309; BIG5P_FREQ[42][151] = 308; BIG5P_FREQ[20][135] = 307; BIG5P_FREQ[37][162] = 306;\n        BIG5P_FREQ[37][50]  = 305; BIG5P_FREQ[22][185] = 304; BIG5P_FREQ[36][166] = 303; BIG5P_FREQ[19][40]  = 302; BIG5P_FREQ[22][107] = 301;\n        BIG5P_FREQ[22][102] = 300; BIG5P_FREQ[57][162] = 299; BIG5P_FREQ[22][124] = 298; BIG5P_FREQ[37][138] = 297; BIG5P_FREQ[37][25]  = 296;\n        BIG5P_FREQ[0][69]   = 295; BIG5P_FREQ[43][172] = 294; BIG5P_FREQ[42][167] = 293; BIG5P_FREQ[35][120] = 292; BIG5P_FREQ[41][128] = 291;\n        BIG5P_FREQ[2][88]   = 290; BIG5P_FREQ[20][123] = 289; BIG5P_FREQ[35][123] = 288; BIG5P_FREQ[36][28]  = 287; BIG5P_FREQ[42][188] = 286;\n        BIG5P_FREQ[42][164] = 285; BIG5P_FREQ[42][4]   = 284; BIG5P_FREQ[43][57]  = 283; BIG5P_FREQ[39][3]   = 282; BIG5P_FREQ[42][3]   = 281;\n        BIG5P_FREQ[57][158] = 280; BIG5P_FREQ[35][146] = 279; BIG5P_FREQ[24][54]  = 278; BIG5P_FREQ[13][110] = 277; BIG5P_FREQ[23][132] = 276;\n        BIG5P_FREQ[26][102] = 275; BIG5P_FREQ[55][178] = 274; BIG5P_FREQ[17][117] = 273; BIG5P_FREQ[41][161] = 272; BIG5P_FREQ[38][150] = 271;\n        BIG5P_FREQ[10][71]  = 270; BIG5P_FREQ[47][60]  = 269; BIG5P_FREQ[16][114] = 268; BIG5P_FREQ[21][47]  = 267; BIG5P_FREQ[39][101] = 266;\n        BIG5P_FREQ[18][45]  = 265; BIG5P_FREQ[40][121] = 264; BIG5P_FREQ[45][41]  = 263; BIG5P_FREQ[22][167] = 262; BIG5P_FREQ[26][149] = 261;\n        BIG5P_FREQ[15][189] = 260; BIG5P_FREQ[41][177] = 259; BIG5P_FREQ[46][36]  = 258; BIG5P_FREQ[20][40]  = 257; BIG5P_FREQ[41][54]  = 256;\n        BIG5P_FREQ[3][87]   = 255; BIG5P_FREQ[40][16]  = 254; BIG5P_FREQ[42][15]  = 253; BIG5P_FREQ[11][83]  = 252; BIG5P_FREQ[0][94]   = 251;\n        BIG5P_FREQ[122][81] = 250; BIG5P_FREQ[41][26]  = 249; BIG5P_FREQ[36][34]  = 248; BIG5P_FREQ[44][148] = 247; BIG5P_FREQ[35][3]   = 246;\n        BIG5P_FREQ[36][114] = 245; BIG5P_FREQ[42][112] = 244; BIG5P_FREQ[35][183] = 243; BIG5P_FREQ[49][73]  = 242; BIG5P_FREQ[39][2]   = 241;\n        BIG5P_FREQ[38][121] = 240; BIG5P_FREQ[44][114] = 239; BIG5P_FREQ[49][32]  = 238; BIG5P_FREQ[1][65]   = 237; BIG5P_FREQ[38][25]  = 236;\n        BIG5P_FREQ[39][4]   = 235; BIG5P_FREQ[42][62]  = 234; BIG5P_FREQ[35][40]  = 233; BIG5P_FREQ[24][2]   = 232; BIG5P_FREQ[53][49]  = 231;\n        BIG5P_FREQ[41][133] = 230; BIG5P_FREQ[43][134] = 229; BIG5P_FREQ[3][83]   = 228; BIG5P_FREQ[38][158] = 227; BIG5P_FREQ[24][17]  = 226;\n        BIG5P_FREQ[52][59]  = 225; BIG5P_FREQ[38][41]  = 224; BIG5P_FREQ[37][127] = 223; BIG5P_FREQ[22][175] = 222; BIG5P_FREQ[44][30]  = 221;\n        BIG5P_FREQ[47][178] = 220; BIG5P_FREQ[43][99]  = 219; BIG5P_FREQ[19][4]   = 218; BIG5P_FREQ[37][97]  = 217; BIG5P_FREQ[38][181] = 216;\n        BIG5P_FREQ[45][103] = 215; BIG5P_FREQ[1][86]   = 214; BIG5P_FREQ[40][15]  = 213; BIG5P_FREQ[22][136] = 212; BIG5P_FREQ[75][165] = 211;\n        BIG5P_FREQ[36][15]  = 210; BIG5P_FREQ[46][80]  = 209; BIG5P_FREQ[59][55]  = 208; BIG5P_FREQ[37][108] = 207; BIG5P_FREQ[21][109] = 206;\n        BIG5P_FREQ[24][165] = 205; BIG5P_FREQ[79][158] = 204; BIG5P_FREQ[44][139] = 203; BIG5P_FREQ[36][124] = 202; BIG5P_FREQ[42][185] = 201;\n        BIG5P_FREQ[39][186] = 200; BIG5P_FREQ[22][128] = 199; BIG5P_FREQ[40][44]  = 198; BIG5P_FREQ[41][105] = 197; BIG5P_FREQ[1][70]   = 196;\n        BIG5P_FREQ[1][68]   = 195; BIG5P_FREQ[53][22]  = 194; BIG5P_FREQ[36][54]  = 193; BIG5P_FREQ[47][147] = 192; BIG5P_FREQ[35][36]  = 191;\n        BIG5P_FREQ[35][185] = 190; BIG5P_FREQ[45][37]  = 189; BIG5P_FREQ[43][163] = 188; BIG5P_FREQ[56][115] = 187; BIG5P_FREQ[38][164] = 186;\n        BIG5P_FREQ[35][141] = 185; BIG5P_FREQ[42][132] = 184; BIG5P_FREQ[46][120] = 183; BIG5P_FREQ[69][142] = 182; BIG5P_FREQ[38][175] = 181;\n        BIG5P_FREQ[22][112] = 180; BIG5P_FREQ[38][142] = 179; BIG5P_FREQ[40][37]  = 178; BIG5P_FREQ[37][109] = 177; BIG5P_FREQ[40][144] = 176;\n        BIG5P_FREQ[44][117] = 175; BIG5P_FREQ[35][181] = 174; BIG5P_FREQ[26][105] = 173; BIG5P_FREQ[16][48]  = 172; BIG5P_FREQ[44][122] = 171;\n        BIG5P_FREQ[12][86]  = 170; BIG5P_FREQ[84][53]  = 169; BIG5P_FREQ[17][44]  = 168; BIG5P_FREQ[59][54]  = 167; BIG5P_FREQ[36][98]  = 166;\n        BIG5P_FREQ[45][115] = 165; BIG5P_FREQ[73][9]   = 164; BIG5P_FREQ[44][123] = 163; BIG5P_FREQ[37][188] = 162; BIG5P_FREQ[51][117] = 161;\n        BIG5P_FREQ[15][156] = 160; BIG5P_FREQ[36][155] = 159; BIG5P_FREQ[44][25]  = 158; BIG5P_FREQ[38][12]  = 157; BIG5P_FREQ[38][140] = 156;\n        BIG5P_FREQ[23][4]   = 155; BIG5P_FREQ[45][149] = 154; BIG5P_FREQ[22][189] = 153; BIG5P_FREQ[38][147] = 152; BIG5P_FREQ[27][5]   = 151;\n        BIG5P_FREQ[22][42]  = 150; BIG5P_FREQ[3][68]   = 149; BIG5P_FREQ[39][51]  = 148; BIG5P_FREQ[36][29]  = 147; BIG5P_FREQ[20][108] = 146;\n        BIG5P_FREQ[50][57]  = 145; BIG5P_FREQ[55][104] = 144; BIG5P_FREQ[22][46]  = 143; BIG5P_FREQ[18][164] = 142; BIG5P_FREQ[50][159] = 141;\n        BIG5P_FREQ[85][131] = 140; BIG5P_FREQ[26][79]  = 139; BIG5P_FREQ[38][100] = 138; BIG5P_FREQ[53][112] = 137; BIG5P_FREQ[20][190] = 136;\n        BIG5P_FREQ[14][69]  = 135; BIG5P_FREQ[23][11]  = 134; BIG5P_FREQ[40][114] = 133; BIG5P_FREQ[40][148] = 132; BIG5P_FREQ[53][130] = 131;\n        BIG5P_FREQ[36][2]   = 130; BIG5P_FREQ[66][82]  = 129; BIG5P_FREQ[45][166] = 128; BIG5P_FREQ[4][88]   = 127; BIG5P_FREQ[16][57]  = 126;\n        BIG5P_FREQ[22][116] = 125; BIG5P_FREQ[36][108] = 124; BIG5P_FREQ[13][48]  = 123; BIG5P_FREQ[54][12]  = 122; BIG5P_FREQ[40][136] = 121;\n        BIG5P_FREQ[36][128] = 120; BIG5P_FREQ[23][6]   = 119; BIG5P_FREQ[38][125] = 118; BIG5P_FREQ[45][154] = 117; BIG5P_FREQ[51][127] = 116;\n        BIG5P_FREQ[44][163] = 115; BIG5P_FREQ[16][173] = 114; BIG5P_FREQ[43][49]  = 113; BIG5P_FREQ[20][112] = 112; BIG5P_FREQ[15][168] = 111;\n        BIG5P_FREQ[35][129] = 110; BIG5P_FREQ[20][45]  = 109; BIG5P_FREQ[38][10]  = 108; BIG5P_FREQ[57][171] = 107; BIG5P_FREQ[44][190] = 106;\n        BIG5P_FREQ[40][56]  = 105; BIG5P_FREQ[36][156] = 104; BIG5P_FREQ[3][88]   = 103; BIG5P_FREQ[50][122] = 102; BIG5P_FREQ[36][7]   = 101;\n        BIG5P_FREQ[39][43]  = 100; BIG5P_FREQ[15][166] =  99; BIG5P_FREQ[42][136] =  98; BIG5P_FREQ[22][131] =  97; BIG5P_FREQ[44][23]  =  96;\n        BIG5P_FREQ[54][147] =  95; BIG5P_FREQ[41][32]  =  94; BIG5P_FREQ[23][121] =  93; BIG5P_FREQ[39][108] =  92; BIG5P_FREQ[2][78]   =  91;\n        BIG5P_FREQ[40][155] =  90; BIG5P_FREQ[55][51]  =  89; BIG5P_FREQ[19][34]  =  88; BIG5P_FREQ[48][128] =  87; BIG5P_FREQ[48][159] =  86;\n        BIG5P_FREQ[20][70]  =  85; BIG5P_FREQ[34][71]  =  84; BIG5P_FREQ[16][31]  =  83; BIG5P_FREQ[42][157] =  82; BIG5P_FREQ[20][44]  =  81;\n        BIG5P_FREQ[11][92]  =  80; BIG5P_FREQ[44][180] =  79; BIG5P_FREQ[84][33]  =  78; BIG5P_FREQ[16][116] =  77; BIG5P_FREQ[61][163] =  76;\n        BIG5P_FREQ[35][164] =  75; BIG5P_FREQ[36][42]  =  74; BIG5P_FREQ[13][40]  =  73; BIG5P_FREQ[43][176] =  72; BIG5P_FREQ[2][66]   =  71;\n        BIG5P_FREQ[20][133] =  70; BIG5P_FREQ[36][65]  =  69; BIG5P_FREQ[38][33]  =  68; BIG5P_FREQ[12][91]  =  67; BIG5P_FREQ[36][26]  =  66;\n        BIG5P_FREQ[15][174] =  65; BIG5P_FREQ[77][32]  =  64; BIG5P_FREQ[16][1]   =  63; BIG5P_FREQ[25][86]  =  62; BIG5P_FREQ[17][13]  =  61;\n        BIG5P_FREQ[5][75]   =  60; BIG5P_FREQ[36][52]  =  59; BIG5P_FREQ[51][164] =  58; BIG5P_FREQ[12][85]  =  57; BIG5P_FREQ[39][168] =  56;\n        BIG5P_FREQ[43][16]  =  55; BIG5P_FREQ[40][69]  =  54; BIG5P_FREQ[26][108] =  53; BIG5P_FREQ[51][56]  =  52; BIG5P_FREQ[16][37]  =  51;\n        BIG5P_FREQ[40][29]  =  50; BIG5P_FREQ[46][171] =  49; BIG5P_FREQ[40][128] =  48; BIG5P_FREQ[72][114] =  47; BIG5P_FREQ[21][103] =  46;\n        BIG5P_FREQ[22][44]  =  45; BIG5P_FREQ[40][115] =  44; BIG5P_FREQ[43][7]   =  43; BIG5P_FREQ[43][153] =  42; BIG5P_FREQ[17][20]  =  41;\n        BIG5P_FREQ[16][49]  =  40; BIG5P_FREQ[36][57]  =  39; BIG5P_FREQ[18][38]  =  38; BIG5P_FREQ[45][184] =  37; BIG5P_FREQ[37][167] =  36;\n        BIG5P_FREQ[26][106] =  35; BIG5P_FREQ[61][121] =  34; BIG5P_FREQ[89][140] =  33; BIG5P_FREQ[46][61]  =  32; BIG5P_FREQ[39][163] =  31;\n        BIG5P_FREQ[40][62]  =  30; BIG5P_FREQ[38][165] =  29; BIG5P_FREQ[47][37]  =  28; BIG5P_FREQ[18][155] =  27; BIG5P_FREQ[20][33]  =  26;\n        BIG5P_FREQ[29][90]  =  25; BIG5P_FREQ[20][103] =  24; BIG5P_FREQ[37][51]  =  23; BIG5P_FREQ[57][0]   =  22; BIG5P_FREQ[40][31]  =  21;\n        BIG5P_FREQ[45][32]  =  20; BIG5P_FREQ[59][23]  =  19; BIG5P_FREQ[18][47]  =  18; BIG5P_FREQ[45][134] =  17; BIG5P_FREQ[37][59]  =  16;\n        BIG5P_FREQ[21][128] =  15; BIG5P_FREQ[36][106] =  14; BIG5P_FREQ[31][39]  =  13; BIG5P_FREQ[40][182] =  12; BIG5P_FREQ[52][155] =  11;\n        BIG5P_FREQ[42][166] =  10; BIG5P_FREQ[35][27]  =   9; BIG5P_FREQ[38][3]   =   8; BIG5P_FREQ[13][44]  =   7; BIG5P_FREQ[58][157] =   6;\n        BIG5P_FREQ[47][51]  =   5; BIG5P_FREQ[41][37]  =   4; BIG5P_FREQ[41][172] =   3; BIG5P_FREQ[51][165] =   2; BIG5P_FREQ[15][161] =   1;\n        BIG5P_FREQ[24][181] =   0;\n        /*\n        Big5Freq[5][0]     = 200; Big5Freq[26][57]   = 199; Big5Freq[13][155]  = 198; Big5Freq[3][38]    = 197; Big5Freq[9][155]   = 196;\n        Big5Freq[28][53]   = 195; Big5Freq[15][71]   = 194; Big5Freq[21][95]   = 193; Big5Freq[15][112]  = 192; Big5Freq[14][138]  = 191;\n        Big5Freq[8][18]    = 190; Big5Freq[20][151]  = 189; Big5Freq[37][27]   = 188; Big5Freq[32][48]   = 187; Big5Freq[23][66]   = 186;\n        Big5Freq[9][2]     = 185; Big5Freq[13][133]  = 184; Big5Freq[7][127]   = 183; Big5Freq[3][11]    = 182; Big5Freq[12][118]  = 181;\n        Big5Freq[13][101]  = 180; Big5Freq[30][153]  = 179; Big5Freq[4][65]    = 178; Big5Freq[5][25]    = 177; Big5Freq[5][140]   = 176;\n        Big5Freq[6][25]    = 175; Big5Freq[4][52]    = 174; Big5Freq[30][156]  = 173; Big5Freq[16][13]   = 172; Big5Freq[21][8]    = 171;\n        Big5Freq[19][74]   = 170; Big5Freq[15][145]  = 169; Big5Freq[9][15]    = 168; Big5Freq[13][82]   = 167; Big5Freq[26][86]   = 166;\n        Big5Freq[18][52]   = 165; Big5Freq[6][109]   = 164; Big5Freq[10][99]   = 163; Big5Freq[18][101]  = 162; Big5Freq[25][49]   = 161;\n        Big5Freq[31][79]   = 160; Big5Freq[28][20]   = 159; Big5Freq[12][115]  = 158; Big5Freq[15][66]   = 157; Big5Freq[11][104]  = 156;\n        Big5Freq[23][106]  = 155; Big5Freq[34][157]  = 154; Big5Freq[32][94]   = 153; Big5Freq[29][88]   = 152; Big5Freq[10][46]   = 151;\n        Big5Freq[13][118]  = 150; Big5Freq[20][37]   = 149; Big5Freq[12][30]   = 148; Big5Freq[21][4]    = 147; Big5Freq[16][33]   = 146;\n        Big5Freq[13][52]   = 145; Big5Freq[4][7]     = 144; Big5Freq[21][49]   = 143; Big5Freq[3][27]    = 142; Big5Freq[16][91]   = 141;\n        Big5Freq[5][155]   = 140; Big5Freq[29][130]  = 139; Big5Freq[3][125]   = 138; Big5Freq[14][26]   = 137; Big5Freq[15][39]   = 136;\n        Big5Freq[24][110]  = 135; Big5Freq[7][141]   = 134; Big5Freq[21][15]   = 133; Big5Freq[32][104]  = 132; Big5Freq[8][31]    = 131;\n        Big5Freq[34][112]  = 130; Big5Freq[10][75]   = 129; Big5Freq[21][23]   = 128; Big5Freq[34][131]  = 127; Big5Freq[12][3]    = 126;\n        Big5Freq[10][62]   = 125; Big5Freq[9][120]   = 124; Big5Freq[32][149]  = 123; Big5Freq[8][44]    = 122; Big5Freq[24][2]    = 121;\n        Big5Freq[6][148]   = 120; Big5Freq[15][103]  = 119; Big5Freq[36][54]   = 118; Big5Freq[36][134]  = 117; Big5Freq[11][7]    = 116;\n        Big5Freq[3][90]    = 115; Big5Freq[36][73]   = 114; Big5Freq[8][102]   = 113; Big5Freq[12][87]   = 112; Big5Freq[25][64]   = 111;\n        Big5Freq[9][1]     = 110; Big5Freq[24][121]  = 109; Big5Freq[5][75]    = 108; Big5Freq[17][83]   = 107; Big5Freq[18][57]   = 106;\n        Big5Freq[8][95]    = 105; Big5Freq[14][36]   = 104; Big5Freq[28][113]  = 103; Big5Freq[12][56]   = 102; Big5Freq[14][61]   = 101;\n        Big5Freq[25][138]  = 100; Big5Freq[4][34]    =  99; Big5Freq[11][152]  =  98; Big5Freq[35][0]    =  97; Big5Freq[4][15]    =  96;\n        Big5Freq[8][82]    =  95; Big5Freq[20][73]   =  94; Big5Freq[25][52]   =  93; Big5Freq[24][6]    =  92; Big5Freq[21][78]   =  91;\n        Big5Freq[17][32]   =  90; Big5Freq[17][91]   =  89; Big5Freq[5][76]    =  88; Big5Freq[15][60]   =  87; Big5Freq[15][150]  =  86;\n        Big5Freq[5][80]    =  85; Big5Freq[15][81]   =  84; Big5Freq[28][108]  =  83; Big5Freq[18][14]   =  82; Big5Freq[19][109]  =  81;\n        Big5Freq[28][133]  =  80; Big5Freq[21][97]   =  79; Big5Freq[5][105]   =  78; Big5Freq[18][114]  =  77; Big5Freq[16][95]   =  76;\n        Big5Freq[5][51]    =  75; Big5Freq[3][148]   =  74; Big5Freq[22][102]  =  73; Big5Freq[4][123]   =  72; Big5Freq[8][88]    =  71;\n        Big5Freq[25][111]  =  70; Big5Freq[8][149]   =  69; Big5Freq[9][48]    =  68; Big5Freq[16][126]  =  67; Big5Freq[33][150]  =  66;\n        Big5Freq[9][54]    =  65; Big5Freq[29][104]  =  64; Big5Freq[3][3]     =  63; Big5Freq[11][49]   =  62; Big5Freq[24][109]  =  61;\n        Big5Freq[28][116]  =  60; Big5Freq[34][113]  =  59; Big5Freq[5][3]     =  58; Big5Freq[21][106]  =  57; Big5Freq[4][98]    =  56;\n        Big5Freq[12][135]  =  55; Big5Freq[16][101]  =  54; Big5Freq[12][147]  =  53; Big5Freq[27][55]   =  52; Big5Freq[3][5]     =  51;\n        Big5Freq[11][101]  =  50; Big5Freq[16][157]  =  49; Big5Freq[22][114]  =  48; Big5Freq[18][46]   =  47; Big5Freq[4][29]    =  46;\n        Big5Freq[8][103]   =  45; Big5Freq[16][151]  =  44; Big5Freq[8][29]    =  43; Big5Freq[15][114]  =  42; Big5Freq[22][70]   =  41;\n        Big5Freq[13][121]  =  40; Big5Freq[7][112]   =  39; Big5Freq[20][83]   =  38; Big5Freq[3][36]    =  37; Big5Freq[10][103]  =  36;\n        Big5Freq[3][96]    =  35; Big5Freq[21][79]   =  34; Big5Freq[25][120]  =  33; Big5Freq[29][121]  =  32; Big5Freq[23][71]   =  31;\n        Big5Freq[21][22]   =  30; Big5Freq[18][89]   =  29; Big5Freq[25][104]  =  28; Big5Freq[10][124]  =  27; Big5Freq[26][4]    =  26;\n        Big5Freq[21][136]  =  25; Big5Freq[6][112]   =  24; Big5Freq[12][103]  =  23; Big5Freq[17][66]   =  22; Big5Freq[13][151]  =  21;\n        Big5Freq[33][152]  =  20; Big5Freq[11][148]  =  19; Big5Freq[13][57]   =  18; Big5Freq[13][41]   =  17; Big5Freq[7][60]    =  16;\n        Big5Freq[21][29]   =  15; Big5Freq[9][157]   =  14; Big5Freq[24][95]   =  13; Big5Freq[15][148]  =  12; Big5Freq[15][122]  =  11;\n        Big5Freq[6][125]   =  10; Big5Freq[11][25]   =   9; Big5Freq[20][55]   =   8; Big5Freq[19][84]   =   7; Big5Freq[21][82]   =   6;\n        Big5Freq[24][3]    =   5; Big5Freq[13][70]   =   4; Big5Freq[6][21]    =   3; Big5Freq[21][86]   =   2; Big5Freq[12][23]   =   1;\n        Big5Freq[3][85]    =   0;\n        */\n\n        // ------------------------------------------------------------------------------------EUC_TW_FREQ\n\n        EUC_TW_FREQ[48][49] = 599; EUC_TW_FREQ[35][65] = 598; EUC_TW_FREQ[41][27] = 597; EUC_TW_FREQ[35][0]  = 596; EUC_TW_FREQ[39][19] = 595;\n        EUC_TW_FREQ[35][42] = 594; EUC_TW_FREQ[38][66] = 593; EUC_TW_FREQ[35][8]  = 592; EUC_TW_FREQ[35][6]  = 591; EUC_TW_FREQ[35][66] = 590;\n        EUC_TW_FREQ[43][14] = 589; EUC_TW_FREQ[69][80] = 588; EUC_TW_FREQ[50][48] = 587; EUC_TW_FREQ[36][71] = 586; EUC_TW_FREQ[37][10] = 585;\n        EUC_TW_FREQ[60][52] = 584; EUC_TW_FREQ[51][21] = 583; EUC_TW_FREQ[40][2]  = 582; EUC_TW_FREQ[67][35] = 581; EUC_TW_FREQ[38][78] = 580;\n        EUC_TW_FREQ[49][18] = 579; EUC_TW_FREQ[35][23] = 578; EUC_TW_FREQ[42][83] = 577; EUC_TW_FREQ[79][47] = 576; EUC_TW_FREQ[61][82] = 575;\n        EUC_TW_FREQ[38][7]  = 574; EUC_TW_FREQ[35][29] = 573; EUC_TW_FREQ[37][77] = 572; EUC_TW_FREQ[54][67] = 571; EUC_TW_FREQ[38][80] = 570;\n        EUC_TW_FREQ[52][74] = 569; EUC_TW_FREQ[36][37] = 568; EUC_TW_FREQ[74][8]  = 567; EUC_TW_FREQ[41][83] = 566; EUC_TW_FREQ[36][75] = 565;\n        EUC_TW_FREQ[49][63] = 564; EUC_TW_FREQ[42][58] = 563; EUC_TW_FREQ[56][33] = 562; EUC_TW_FREQ[37][76] = 561; EUC_TW_FREQ[62][39] = 560;\n        EUC_TW_FREQ[35][21] = 559; EUC_TW_FREQ[70][19] = 558; EUC_TW_FREQ[77][88] = 557; EUC_TW_FREQ[51][14] = 556; EUC_TW_FREQ[36][17] = 555;\n        EUC_TW_FREQ[44][51] = 554; EUC_TW_FREQ[38][72] = 553; EUC_TW_FREQ[74][90] = 552; EUC_TW_FREQ[35][48] = 551; EUC_TW_FREQ[35][69] = 550;\n        EUC_TW_FREQ[66][86] = 549; EUC_TW_FREQ[57][20] = 548; EUC_TW_FREQ[35][53] = 547; EUC_TW_FREQ[36][87] = 546; EUC_TW_FREQ[84][67] = 545;\n        EUC_TW_FREQ[70][56] = 544; EUC_TW_FREQ[71][54] = 543; EUC_TW_FREQ[60][70] = 542; EUC_TW_FREQ[80][1]  = 541; EUC_TW_FREQ[39][59] = 540;\n        EUC_TW_FREQ[39][51] = 539; EUC_TW_FREQ[35][44] = 538; EUC_TW_FREQ[48][4]  = 537; EUC_TW_FREQ[55][24] = 536; EUC_TW_FREQ[52][4]  = 535;\n        EUC_TW_FREQ[54][26] = 534; EUC_TW_FREQ[36][31] = 533; EUC_TW_FREQ[37][22] = 532; EUC_TW_FREQ[37][9]  = 531; EUC_TW_FREQ[46][0]  = 530;\n        EUC_TW_FREQ[56][46] = 529; EUC_TW_FREQ[47][93] = 528; EUC_TW_FREQ[37][25] = 527; EUC_TW_FREQ[39][8]  = 526; EUC_TW_FREQ[46][73] = 525;\n        EUC_TW_FREQ[38][48] = 524; EUC_TW_FREQ[39][83] = 523; EUC_TW_FREQ[60][92] = 522; EUC_TW_FREQ[70][11] = 521; EUC_TW_FREQ[63][84] = 520;\n        EUC_TW_FREQ[38][65] = 519; EUC_TW_FREQ[45][45] = 518; EUC_TW_FREQ[63][49] = 517; EUC_TW_FREQ[63][50] = 516; EUC_TW_FREQ[39][93] = 515;\n        EUC_TW_FREQ[68][20] = 514; EUC_TW_FREQ[44][84] = 513; EUC_TW_FREQ[66][34] = 512; EUC_TW_FREQ[37][58] = 511; EUC_TW_FREQ[39][0]  = 510;\n        EUC_TW_FREQ[59][1]  = 509; EUC_TW_FREQ[47][8]  = 508; EUC_TW_FREQ[61][17] = 507; EUC_TW_FREQ[53][87] = 506; EUC_TW_FREQ[67][26] = 505;\n        EUC_TW_FREQ[43][46] = 504; EUC_TW_FREQ[38][61] = 503; EUC_TW_FREQ[45][9]  = 502; EUC_TW_FREQ[66][83] = 501; EUC_TW_FREQ[43][88] = 500;\n        EUC_TW_FREQ[85][20] = 499; EUC_TW_FREQ[57][36] = 498; EUC_TW_FREQ[43][6]  = 497; EUC_TW_FREQ[86][77] = 496; EUC_TW_FREQ[42][70] = 495;\n        EUC_TW_FREQ[49][78] = 494; EUC_TW_FREQ[36][40] = 493; EUC_TW_FREQ[42][71] = 492; EUC_TW_FREQ[58][49] = 491; EUC_TW_FREQ[35][20] = 490;\n        EUC_TW_FREQ[76][20] = 489; EUC_TW_FREQ[39][25] = 488; EUC_TW_FREQ[40][34] = 487; EUC_TW_FREQ[39][76] = 486; EUC_TW_FREQ[40][1]  = 485;\n        EUC_TW_FREQ[59][0]  = 484; EUC_TW_FREQ[39][70] = 483; EUC_TW_FREQ[46][14] = 482; EUC_TW_FREQ[68][77] = 481; EUC_TW_FREQ[38][55] = 480;\n        EUC_TW_FREQ[35][78] = 479; EUC_TW_FREQ[84][44] = 478; EUC_TW_FREQ[36][41] = 477; EUC_TW_FREQ[37][62] = 476; EUC_TW_FREQ[65][67] = 475;\n        EUC_TW_FREQ[69][66] = 474; EUC_TW_FREQ[73][55] = 473; EUC_TW_FREQ[71][49] = 472; EUC_TW_FREQ[66][87] = 471; EUC_TW_FREQ[38][33] = 470;\n        EUC_TW_FREQ[64][61] = 469; EUC_TW_FREQ[35][7]  = 468; EUC_TW_FREQ[47][49] = 467; EUC_TW_FREQ[56][14] = 466; EUC_TW_FREQ[36][49] = 465;\n        EUC_TW_FREQ[50][81] = 464; EUC_TW_FREQ[55][76] = 463; EUC_TW_FREQ[35][19] = 462; EUC_TW_FREQ[44][47] = 461; EUC_TW_FREQ[35][15] = 460;\n        EUC_TW_FREQ[82][59] = 459; EUC_TW_FREQ[35][43] = 458; EUC_TW_FREQ[73][0]  = 457; EUC_TW_FREQ[57][83] = 456; EUC_TW_FREQ[42][46] = 455;\n        EUC_TW_FREQ[36][0]  = 454; EUC_TW_FREQ[70][88] = 453; EUC_TW_FREQ[42][22] = 452; EUC_TW_FREQ[46][58] = 451; EUC_TW_FREQ[36][34] = 450;\n        EUC_TW_FREQ[39][24] = 449; EUC_TW_FREQ[35][55] = 448; EUC_TW_FREQ[44][91] = 447; EUC_TW_FREQ[37][51] = 446; EUC_TW_FREQ[36][19] = 445;\n        EUC_TW_FREQ[69][90] = 444; EUC_TW_FREQ[55][35] = 443; EUC_TW_FREQ[35][54] = 442; EUC_TW_FREQ[49][61] = 441; EUC_TW_FREQ[36][67] = 440;\n        EUC_TW_FREQ[88][34] = 439; EUC_TW_FREQ[35][17] = 438; EUC_TW_FREQ[65][69] = 437; EUC_TW_FREQ[74][89] = 436; EUC_TW_FREQ[37][31] = 435;\n        EUC_TW_FREQ[43][48] = 434; EUC_TW_FREQ[89][27] = 433; EUC_TW_FREQ[42][79] = 432; EUC_TW_FREQ[69][57] = 431; EUC_TW_FREQ[36][13] = 430;\n        EUC_TW_FREQ[35][62] = 429; EUC_TW_FREQ[65][47] = 428; EUC_TW_FREQ[56][8]  = 427; EUC_TW_FREQ[38][79] = 426; EUC_TW_FREQ[37][64] = 425;\n        EUC_TW_FREQ[64][64] = 424; EUC_TW_FREQ[38][53] = 423; EUC_TW_FREQ[38][31] = 422; EUC_TW_FREQ[56][81] = 421; EUC_TW_FREQ[36][22] = 420;\n        EUC_TW_FREQ[43][4]  = 419; EUC_TW_FREQ[36][90] = 418; EUC_TW_FREQ[38][62] = 417; EUC_TW_FREQ[66][85] = 416; EUC_TW_FREQ[39][1]  = 415;\n        EUC_TW_FREQ[59][40] = 414; EUC_TW_FREQ[58][93] = 413; EUC_TW_FREQ[44][43] = 412; EUC_TW_FREQ[39][49] = 411; EUC_TW_FREQ[64][2]  = 410;\n        EUC_TW_FREQ[41][35] = 409; EUC_TW_FREQ[60][22] = 408; EUC_TW_FREQ[35][91] = 407; EUC_TW_FREQ[78][1]  = 406; EUC_TW_FREQ[36][14] = 405;\n        EUC_TW_FREQ[82][29] = 404; EUC_TW_FREQ[52][86] = 403; EUC_TW_FREQ[40][16] = 402; EUC_TW_FREQ[91][52] = 401; EUC_TW_FREQ[50][75] = 400;\n        EUC_TW_FREQ[64][30] = 399; EUC_TW_FREQ[90][78] = 398; EUC_TW_FREQ[36][52] = 397; EUC_TW_FREQ[55][87] = 396; EUC_TW_FREQ[57][5]  = 395;\n        EUC_TW_FREQ[57][31] = 394; EUC_TW_FREQ[42][35] = 393; EUC_TW_FREQ[69][50] = 392; EUC_TW_FREQ[45][8]  = 391; EUC_TW_FREQ[50][87] = 390;\n        EUC_TW_FREQ[69][55] = 389; EUC_TW_FREQ[92][3]  = 388; EUC_TW_FREQ[36][43] = 387; EUC_TW_FREQ[64][10] = 386; EUC_TW_FREQ[56][25] = 385;\n        EUC_TW_FREQ[60][68] = 384; EUC_TW_FREQ[51][46] = 383; EUC_TW_FREQ[50][0]  = 382; EUC_TW_FREQ[38][30] = 381; EUC_TW_FREQ[50][85] = 380;\n        EUC_TW_FREQ[60][54] = 379; EUC_TW_FREQ[73][6]  = 378; EUC_TW_FREQ[73][28] = 377; EUC_TW_FREQ[56][19] = 376; EUC_TW_FREQ[62][69] = 375;\n        EUC_TW_FREQ[81][66] = 374; EUC_TW_FREQ[40][32] = 373; EUC_TW_FREQ[76][31] = 372; EUC_TW_FREQ[35][10] = 371; EUC_TW_FREQ[41][37] = 370;\n        EUC_TW_FREQ[52][82] = 369; EUC_TW_FREQ[91][72] = 368; EUC_TW_FREQ[37][29] = 367; EUC_TW_FREQ[56][30] = 366; EUC_TW_FREQ[37][80] = 365;\n        EUC_TW_FREQ[81][56] = 364; EUC_TW_FREQ[70][3]  = 363; EUC_TW_FREQ[76][15] = 362; EUC_TW_FREQ[46][47] = 361; EUC_TW_FREQ[35][88] = 360;\n        EUC_TW_FREQ[61][58] = 359; EUC_TW_FREQ[37][37] = 358; EUC_TW_FREQ[57][22] = 357; EUC_TW_FREQ[41][23] = 356; EUC_TW_FREQ[90][66] = 355;\n        EUC_TW_FREQ[39][60] = 354; EUC_TW_FREQ[38][0]  = 353; EUC_TW_FREQ[37][87] = 352; EUC_TW_FREQ[46][2]  = 351; EUC_TW_FREQ[38][56] = 350;\n        EUC_TW_FREQ[58][11] = 349; EUC_TW_FREQ[48][10] = 348; EUC_TW_FREQ[74][4]  = 347; EUC_TW_FREQ[40][42] = 346; EUC_TW_FREQ[41][52] = 345;\n        EUC_TW_FREQ[61][92] = 344; EUC_TW_FREQ[39][50] = 343; EUC_TW_FREQ[47][88] = 342; EUC_TW_FREQ[88][36] = 341; EUC_TW_FREQ[45][73] = 340;\n        EUC_TW_FREQ[82][3]  = 339; EUC_TW_FREQ[61][36] = 338; EUC_TW_FREQ[60][33] = 337; EUC_TW_FREQ[38][27] = 336; EUC_TW_FREQ[35][83] = 335;\n        EUC_TW_FREQ[65][24] = 334; EUC_TW_FREQ[73][10] = 333; EUC_TW_FREQ[41][13] = 332; EUC_TW_FREQ[50][27] = 331; EUC_TW_FREQ[59][50] = 330;\n        EUC_TW_FREQ[42][45] = 329; EUC_TW_FREQ[55][19] = 328; EUC_TW_FREQ[36][77] = 327; EUC_TW_FREQ[69][31] = 326; EUC_TW_FREQ[60][7]  = 325;\n        EUC_TW_FREQ[40][88] = 324; EUC_TW_FREQ[57][56] = 323; EUC_TW_FREQ[50][50] = 322; EUC_TW_FREQ[42][37] = 321; EUC_TW_FREQ[38][82] = 320;\n        EUC_TW_FREQ[52][25] = 319; EUC_TW_FREQ[42][67] = 318; EUC_TW_FREQ[48][40] = 317; EUC_TW_FREQ[45][81] = 316; EUC_TW_FREQ[57][14] = 315;\n        EUC_TW_FREQ[42][13] = 314; EUC_TW_FREQ[78][0]  = 313; EUC_TW_FREQ[35][51] = 312; EUC_TW_FREQ[41][67] = 311; EUC_TW_FREQ[64][23] = 310;\n        EUC_TW_FREQ[36][65] = 309; EUC_TW_FREQ[48][50] = 308; EUC_TW_FREQ[46][69] = 307; EUC_TW_FREQ[47][89] = 306; EUC_TW_FREQ[41][48] = 305;\n        EUC_TW_FREQ[60][56] = 304; EUC_TW_FREQ[44][82] = 303; EUC_TW_FREQ[47][35] = 302; EUC_TW_FREQ[49][3]  = 301; EUC_TW_FREQ[49][69] = 300;\n        EUC_TW_FREQ[45][93] = 299; EUC_TW_FREQ[60][34] = 298; EUC_TW_FREQ[60][82] = 297; EUC_TW_FREQ[61][61] = 296; EUC_TW_FREQ[86][42] = 295;\n        EUC_TW_FREQ[89][60] = 294; EUC_TW_FREQ[48][31] = 293; EUC_TW_FREQ[35][75] = 292; EUC_TW_FREQ[91][39] = 291; EUC_TW_FREQ[53][19] = 290;\n        EUC_TW_FREQ[39][72] = 289; EUC_TW_FREQ[69][59] = 288; EUC_TW_FREQ[41][7]  = 287; EUC_TW_FREQ[54][13] = 286; EUC_TW_FREQ[43][28] = 285;\n        EUC_TW_FREQ[36][6]  = 284; EUC_TW_FREQ[45][75] = 283; EUC_TW_FREQ[36][61] = 282; EUC_TW_FREQ[38][21] = 281; EUC_TW_FREQ[45][14] = 280;\n        EUC_TW_FREQ[61][43] = 279; EUC_TW_FREQ[36][63] = 278; EUC_TW_FREQ[43][30] = 277; EUC_TW_FREQ[46][51] = 276; EUC_TW_FREQ[68][87] = 275;\n        EUC_TW_FREQ[39][26] = 274; EUC_TW_FREQ[46][76] = 273; EUC_TW_FREQ[36][15] = 272; EUC_TW_FREQ[35][40] = 271; EUC_TW_FREQ[79][60] = 270;\n        EUC_TW_FREQ[46][7]  = 269; EUC_TW_FREQ[65][72] = 268; EUC_TW_FREQ[69][88] = 267; EUC_TW_FREQ[47][18] = 266; EUC_TW_FREQ[37][0]  = 265;\n        EUC_TW_FREQ[37][49] = 264; EUC_TW_FREQ[67][37] = 263; EUC_TW_FREQ[36][91] = 262; EUC_TW_FREQ[75][48] = 261; EUC_TW_FREQ[75][63] = 260;\n        EUC_TW_FREQ[83][87] = 259; EUC_TW_FREQ[37][44] = 258; EUC_TW_FREQ[73][54] = 257; EUC_TW_FREQ[51][61] = 256; EUC_TW_FREQ[46][57] = 255;\n        EUC_TW_FREQ[55][21] = 254; EUC_TW_FREQ[39][66] = 253; EUC_TW_FREQ[47][11] = 252; EUC_TW_FREQ[52][8]  = 251; EUC_TW_FREQ[82][81] = 250;\n        EUC_TW_FREQ[36][57] = 249; EUC_TW_FREQ[38][54] = 248; EUC_TW_FREQ[43][81] = 247; EUC_TW_FREQ[37][42] = 246; EUC_TW_FREQ[40][18] = 245;\n        EUC_TW_FREQ[80][90] = 244; EUC_TW_FREQ[37][84] = 243; EUC_TW_FREQ[57][15] = 242; EUC_TW_FREQ[38][87] = 241; EUC_TW_FREQ[37][32] = 240;\n        EUC_TW_FREQ[53][53] = 239; EUC_TW_FREQ[89][29] = 238; EUC_TW_FREQ[81][53] = 237; EUC_TW_FREQ[75][3]  = 236; EUC_TW_FREQ[83][73] = 235;\n        EUC_TW_FREQ[66][13] = 234; EUC_TW_FREQ[48][7]  = 233; EUC_TW_FREQ[46][35] = 232; EUC_TW_FREQ[35][86] = 231; EUC_TW_FREQ[37][20] = 230;\n        EUC_TW_FREQ[46][80] = 229; EUC_TW_FREQ[38][24] = 228; EUC_TW_FREQ[41][68] = 227; EUC_TW_FREQ[42][21] = 226; EUC_TW_FREQ[43][32] = 225;\n        EUC_TW_FREQ[38][20] = 224; EUC_TW_FREQ[37][59] = 223; EUC_TW_FREQ[41][77] = 222; EUC_TW_FREQ[59][57] = 221; EUC_TW_FREQ[68][59] = 220;\n        EUC_TW_FREQ[39][43] = 219; EUC_TW_FREQ[54][39] = 218; EUC_TW_FREQ[48][28] = 217; EUC_TW_FREQ[54][28] = 216; EUC_TW_FREQ[41][44] = 215;\n        EUC_TW_FREQ[51][64] = 214; EUC_TW_FREQ[47][72] = 213; EUC_TW_FREQ[62][67] = 212; EUC_TW_FREQ[42][43] = 211; EUC_TW_FREQ[61][38] = 210;\n        EUC_TW_FREQ[76][25] = 209; EUC_TW_FREQ[48][91] = 208; EUC_TW_FREQ[36][36] = 207; EUC_TW_FREQ[80][32] = 206; EUC_TW_FREQ[81][40] = 205;\n        EUC_TW_FREQ[37][5]  = 204; EUC_TW_FREQ[74][69] = 203; EUC_TW_FREQ[36][82] = 202; EUC_TW_FREQ[46][59] = 201;\n        /*\n        EUC_TWFreq[38][32] = 200; EUC_TWFreq[74][2]  = 199; EUC_TWFreq[53][31] = 198; EUC_TWFreq[35][38] = 197; EUC_TWFreq[46][62] = 196;\n        EUC_TWFreq[77][31] = 195; EUC_TWFreq[55][74] = 194; EUC_TWFreq[66][6]  = 193; EUC_TWFreq[56][21] = 192; EUC_TWFreq[54][78] = 191;\n        EUC_TWFreq[43][51] = 190; EUC_TWFreq[64][93] = 189; EUC_TWFreq[92][7]  = 188; EUC_TWFreq[83][89] = 187; EUC_TWFreq[69][9]  = 186;\n        EUC_TWFreq[45][4]  = 185; EUC_TWFreq[53][9]  = 184; EUC_TWFreq[43][2]  = 183; EUC_TWFreq[35][11] = 182; EUC_TWFreq[51][25] = 181;\n        EUC_TWFreq[52][71] = 180; EUC_TWFreq[81][67] = 179; EUC_TWFreq[37][33] = 178; EUC_TWFreq[38][57] = 177; EUC_TWFreq[39][77] = 176;\n        EUC_TWFreq[40][26] = 175; EUC_TWFreq[37][21] = 174; EUC_TWFreq[81][70] = 173; EUC_TWFreq[56][80] = 172; EUC_TWFreq[65][14] = 171;\n        EUC_TWFreq[62][47] = 170; EUC_TWFreq[56][54] = 169; EUC_TWFreq[45][17] = 168; EUC_TWFreq[52][52] = 167; EUC_TWFreq[74][30] = 166;\n        EUC_TWFreq[60][57] = 165; EUC_TWFreq[41][15] = 164; EUC_TWFreq[47][69] = 163; EUC_TWFreq[61][11] = 162; EUC_TWFreq[72][25] = 161;\n        EUC_TWFreq[82][56] = 160; EUC_TWFreq[76][92] = 159; EUC_TWFreq[51][22] = 158; EUC_TWFreq[55][69] = 157; EUC_TWFreq[49][43] = 156;\n        EUC_TWFreq[69][49] = 155; EUC_TWFreq[88][42] = 154; EUC_TWFreq[84][41] = 153; EUC_TWFreq[79][33] = 152; EUC_TWFreq[47][17] = 151;\n        EUC_TWFreq[52][88] = 150; EUC_TWFreq[63][74] = 149; EUC_TWFreq[50][32] = 148; EUC_TWFreq[65][10] = 147; EUC_TWFreq[57][6]  = 146;\n        EUC_TWFreq[52][23] = 145; EUC_TWFreq[36][70] = 144; EUC_TWFreq[65][55] = 143; EUC_TWFreq[35][27] = 142; EUC_TWFreq[57][63] = 141;\n        EUC_TWFreq[39][92] = 140; EUC_TWFreq[79][75] = 139; EUC_TWFreq[36][30] = 138; EUC_TWFreq[53][60] = 137; EUC_TWFreq[55][43] = 136;\n        EUC_TWFreq[71][22] = 135; EUC_TWFreq[43][16] = 134; EUC_TWFreq[65][21] = 133; EUC_TWFreq[84][51] = 132; EUC_TWFreq[43][64] = 131;\n        EUC_TWFreq[87][91] = 130; EUC_TWFreq[47][45] = 129; EUC_TWFreq[65][29] = 128; EUC_TWFreq[88][16] = 127; EUC_TWFreq[50][5]  = 126;\n        EUC_TWFreq[47][33] = 125; EUC_TWFreq[46][27] = 124; EUC_TWFreq[85][2]  = 123; EUC_TWFreq[43][77] = 122; EUC_TWFreq[70][9]  = 121;\n        EUC_TWFreq[41][54] = 120; EUC_TWFreq[56][12] = 119; EUC_TWFreq[90][65] = 118; EUC_TWFreq[91][50] = 117; EUC_TWFreq[48][41] = 116;\n        EUC_TWFreq[35][89] = 115; EUC_TWFreq[90][83] = 114; EUC_TWFreq[44][40] = 113; EUC_TWFreq[50][88] = 112; EUC_TWFreq[72][39] = 111;\n        EUC_TWFreq[45][3]  = 110; EUC_TWFreq[71][33] = 109; EUC_TWFreq[39][12] = 108; EUC_TWFreq[59][24] = 107; EUC_TWFreq[60][62] = 106;\n        EUC_TWFreq[44][33] = 105; EUC_TWFreq[53][70] = 104; EUC_TWFreq[77][90] = 103; EUC_TWFreq[50][58] = 102; EUC_TWFreq[54][1]  = 101;\n        EUC_TWFreq[73][19] = 100; EUC_TWFreq[37][3]  =  99; EUC_TWFreq[49][91] =  98; EUC_TWFreq[88][43] =  97; EUC_TWFreq[36][78] =  96;\n        EUC_TWFreq[44][20] =  95; EUC_TWFreq[64][15] =  94; EUC_TWFreq[72][28] =  93; EUC_TWFreq[70][13] =  92; EUC_TWFreq[65][83] =  91;\n        EUC_TWFreq[58][68] =  90; EUC_TWFreq[59][32] =  89; EUC_TWFreq[39][13] =  88; EUC_TWFreq[55][64] =  87; EUC_TWFreq[56][59] =  86;\n        EUC_TWFreq[39][17] =  85; EUC_TWFreq[55][84] =  84; EUC_TWFreq[77][85] =  83; EUC_TWFreq[60][19] =  82; EUC_TWFreq[62][82] =  81;\n        EUC_TWFreq[78][16] =  80; EUC_TWFreq[66][8]  =  79; EUC_TWFreq[39][42] =  78; EUC_TWFreq[61][24] =  77; EUC_TWFreq[57][67] =  76;\n        EUC_TWFreq[38][83] =  75; EUC_TWFreq[36][53] =  74; EUC_TWFreq[67][76] =  73; EUC_TWFreq[37][91] =  72; EUC_TWFreq[44][26] =  71;\n        EUC_TWFreq[72][86] =  70; EUC_TWFreq[44][87] =  69; EUC_TWFreq[45][50] =  68; EUC_TWFreq[58][4]  =  67; EUC_TWFreq[86][65] =  66;\n        EUC_TWFreq[45][56] =  65; EUC_TWFreq[79][49] =  64; EUC_TWFreq[35][3]  =  63; EUC_TWFreq[48][83] =  62; EUC_TWFreq[71][21] =  61;\n        EUC_TWFreq[77][93] =  60; EUC_TWFreq[87][92] =  59; EUC_TWFreq[38][35] =  58; EUC_TWFreq[66][17] =  57; EUC_TWFreq[37][66] =  56;\n        EUC_TWFreq[51][42] =  55; EUC_TWFreq[57][73] =  54; EUC_TWFreq[51][54] =  53; EUC_TWFreq[75][64] =  52; EUC_TWFreq[35][5]  =  51;\n        EUC_TWFreq[49][40] =  50; EUC_TWFreq[58][35] =  49; EUC_TWFreq[67][88] =  48; EUC_TWFreq[60][51] =  47; EUC_TWFreq[36][92] =  46;\n        EUC_TWFreq[44][41] =  45; EUC_TWFreq[58][29] =  44; EUC_TWFreq[43][62] =  43; EUC_TWFreq[56][23] =  42; EUC_TWFreq[67][44] =  41;\n        EUC_TWFreq[52][91] =  40; EUC_TWFreq[42][81] =  39; EUC_TWFreq[64][25] =  38; EUC_TWFreq[35][36] =  37; EUC_TWFreq[47][73] =  36;\n        EUC_TWFreq[36][1]  =  35; EUC_TWFreq[65][84] =  34; EUC_TWFreq[73][1]  =  33; EUC_TWFreq[79][66] =  32; EUC_TWFreq[69][14] =  31;\n        EUC_TWFreq[65][28] =  30; EUC_TWFreq[60][93] =  29; EUC_TWFreq[72][79] =  28; EUC_TWFreq[48][0]  =  27; EUC_TWFreq[73][43] =  26;\n        EUC_TWFreq[66][47] =  25; EUC_TWFreq[41][18] =  24; EUC_TWFreq[51][10] =  23; EUC_TWFreq[59][7]  =  22; EUC_TWFreq[53][27] =  21;\n        EUC_TWFreq[86][67] =  20; EUC_TWFreq[49][87] =  19; EUC_TWFreq[52][28] =  18; EUC_TWFreq[52][12] =  17; EUC_TWFreq[42][30] =  16;\n        EUC_TWFreq[65][35] =  15; EUC_TWFreq[46][64] =  14; EUC_TWFreq[71][7]  =  13; EUC_TWFreq[56][57] =  12; EUC_TWFreq[56][31] =  11;\n        EUC_TWFreq[41][31] =  10; EUC_TWFreq[48][59] =   9; EUC_TWFreq[63][92] =   8; EUC_TWFreq[62][57] =   7; EUC_TWFreq[65][87] =   6;\n        EUC_TWFreq[70][10] =   5; EUC_TWFreq[52][40] =   4; EUC_TWFreq[40][22] =   3; EUC_TWFreq[65][91] =   2; EUC_TWFreq[50][25] =   1;\n        EUC_TWFreq[35][84] =   0; EUC_TWFreq[45][90] = 600;\n        */\n\n        // ------------------------------------------------------------------------------------GBK_FREQ\n\n        GBK_FREQ[52][132]  = 600; GBK_FREQ[73][135]  = 599; GBK_FREQ[49][123]  = 598; GBK_FREQ[77][146]  = 597; GBK_FREQ[81][123]  = 596;\n        GBK_FREQ[82][144]  = 595; GBK_FREQ[51][179]  = 594; GBK_FREQ[83][154]  = 593; GBK_FREQ[71][139]  = 592; GBK_FREQ[64][139]  = 591;\n        GBK_FREQ[85][144]  = 590; GBK_FREQ[52][125]  = 589; GBK_FREQ[88][25]   = 588; GBK_FREQ[81][106]  = 587; GBK_FREQ[81][148]  = 586;\n        GBK_FREQ[62][137]  = 585; GBK_FREQ[94][0]    = 584; GBK_FREQ[1][64]    = 583; GBK_FREQ[67][163]  = 582; GBK_FREQ[20][190]  = 581;\n        GBK_FREQ[57][131]  = 580; GBK_FREQ[29][169]  = 579; GBK_FREQ[72][143]  = 578; GBK_FREQ[0][173]   = 577; GBK_FREQ[11][23]   = 576;\n        GBK_FREQ[61][141]  = 575; GBK_FREQ[60][123]  = 574; GBK_FREQ[81][114]  = 573; GBK_FREQ[82][131]  = 572; GBK_FREQ[67][156]  = 571;\n        GBK_FREQ[71][167]  = 570; GBK_FREQ[20][50]   = 569; GBK_FREQ[77][132]  = 568; GBK_FREQ[84][38]   = 567; GBK_FREQ[26][29]   = 566;\n        GBK_FREQ[74][187]  = 565; GBK_FREQ[62][116]  = 564; GBK_FREQ[67][135]  = 563; GBK_FREQ[5][86]    = 562; GBK_FREQ[72][186]  = 561;\n        GBK_FREQ[75][161]  = 560; GBK_FREQ[78][130]  = 559; GBK_FREQ[94][30]   = 558; GBK_FREQ[84][72]   = 557; GBK_FREQ[1][67]    = 556;\n        GBK_FREQ[75][172]  = 555; GBK_FREQ[74][185]  = 554; GBK_FREQ[53][160]  = 553; GBK_FREQ[123][14]  = 552; GBK_FREQ[79][97]   = 551;\n        GBK_FREQ[85][110]  = 550; GBK_FREQ[78][171]  = 549; GBK_FREQ[52][131]  = 548; GBK_FREQ[56][100]  = 547; GBK_FREQ[50][182]  = 546;\n        GBK_FREQ[94][64]   = 545; GBK_FREQ[106][74]  = 544; GBK_FREQ[11][102]  = 543; GBK_FREQ[53][124]  = 542; GBK_FREQ[24][3]    = 541;\n        GBK_FREQ[86][148]  = 540; GBK_FREQ[53][184]  = 539; GBK_FREQ[86][147]  = 538; GBK_FREQ[96][161]  = 537; GBK_FREQ[82][77]   = 536;\n        GBK_FREQ[59][146]  = 535; GBK_FREQ[84][126]  = 534; GBK_FREQ[79][132]  = 533; GBK_FREQ[85][123]  = 532; GBK_FREQ[71][101]  = 531;\n        GBK_FREQ[85][106]  = 530; GBK_FREQ[6][184]   = 529; GBK_FREQ[57][156]  = 528; GBK_FREQ[75][104]  = 527; GBK_FREQ[50][137]  = 526;\n        GBK_FREQ[79][133]  = 525; GBK_FREQ[76][108]  = 524; GBK_FREQ[57][142]  = 523; GBK_FREQ[84][130]  = 522; GBK_FREQ[52][128]  = 521;\n        GBK_FREQ[47][44]   = 520; GBK_FREQ[52][152]  = 519; GBK_FREQ[54][104]  = 518; GBK_FREQ[30][47]   = 517; GBK_FREQ[71][123]  = 516;\n        GBK_FREQ[52][107]  = 515; GBK_FREQ[45][84]   = 514; GBK_FREQ[107][118] = 513; GBK_FREQ[5][161]   = 512; GBK_FREQ[48][126]  = 511;\n        GBK_FREQ[67][170]  = 510; GBK_FREQ[43][6]    = 509; GBK_FREQ[70][112]  = 508; GBK_FREQ[86][174]  = 507; GBK_FREQ[84][166]  = 506;\n        GBK_FREQ[79][130]  = 505; GBK_FREQ[57][141]  = 504; GBK_FREQ[81][178]  = 503; GBK_FREQ[56][187]  = 502; GBK_FREQ[81][162]  = 501;\n        GBK_FREQ[53][104]  = 500; GBK_FREQ[123][35]  = 499; GBK_FREQ[70][169]  = 498; GBK_FREQ[69][164]  = 497; GBK_FREQ[109][61]  = 496;\n        GBK_FREQ[73][130]  = 495; GBK_FREQ[62][134]  = 494; GBK_FREQ[54][125]  = 493; GBK_FREQ[79][105]  = 492; GBK_FREQ[70][165]  = 491;\n        GBK_FREQ[71][189]  = 490; GBK_FREQ[23][147]  = 489; GBK_FREQ[51][139]  = 488; GBK_FREQ[47][137]  = 487; GBK_FREQ[77][123]  = 486;\n        GBK_FREQ[86][183]  = 485; GBK_FREQ[63][173]  = 484; GBK_FREQ[79][144]  = 483; GBK_FREQ[84][159]  = 482; GBK_FREQ[60][91]   = 481;\n        GBK_FREQ[66][187]  = 480; GBK_FREQ[73][114]  = 479; GBK_FREQ[85][56]   = 478; GBK_FREQ[71][149]  = 477; GBK_FREQ[84][189]  = 476;\n        GBK_FREQ[104][31]  = 475; GBK_FREQ[83][82]   = 474; GBK_FREQ[68][35]   = 473; GBK_FREQ[11][77]   = 472; GBK_FREQ[15][155]  = 471;\n        GBK_FREQ[83][153]  = 470; GBK_FREQ[71][1]    = 469; GBK_FREQ[53][190]  = 468; GBK_FREQ[50][135]  = 467; GBK_FREQ[3][147]   = 466;\n        GBK_FREQ[48][136]  = 465; GBK_FREQ[66][166]  = 464; GBK_FREQ[55][159]  = 463; GBK_FREQ[82][150]  = 462; GBK_FREQ[58][178]  = 461;\n        GBK_FREQ[64][102]  = 460; GBK_FREQ[16][106]  = 459; GBK_FREQ[68][110]  = 458; GBK_FREQ[54][14]   = 457; GBK_FREQ[60][140]  = 456;\n        GBK_FREQ[91][71]   = 455; GBK_FREQ[54][150]  = 454; GBK_FREQ[78][177]  = 453; GBK_FREQ[78][117]  = 452; GBK_FREQ[104][12]  = 451;\n        GBK_FREQ[73][150]  = 450; GBK_FREQ[51][142]  = 449; GBK_FREQ[81][145]  = 448; GBK_FREQ[66][183]  = 447; GBK_FREQ[51][178]  = 446;\n        GBK_FREQ[75][107]  = 445; GBK_FREQ[65][119]  = 444; GBK_FREQ[69][176]  = 443; GBK_FREQ[59][122]  = 442; GBK_FREQ[78][160]  = 441;\n        GBK_FREQ[85][183]  = 440; GBK_FREQ[105][16]  = 439; GBK_FREQ[73][110]  = 438; GBK_FREQ[104][39]  = 437; GBK_FREQ[119][16]  = 436;\n        GBK_FREQ[76][162]  = 435; GBK_FREQ[67][152]  = 434; GBK_FREQ[82][24]   = 433; GBK_FREQ[73][121]  = 432; GBK_FREQ[83][83]   = 431;\n        GBK_FREQ[82][145]  = 430; GBK_FREQ[49][133]  = 429; GBK_FREQ[94][13]   = 428; GBK_FREQ[58][139]  = 427; GBK_FREQ[74][189]  = 426;\n        GBK_FREQ[66][177]  = 425; GBK_FREQ[85][184]  = 424; GBK_FREQ[55][183]  = 423; GBK_FREQ[71][107]  = 422; GBK_FREQ[11][98]   = 421;\n        GBK_FREQ[72][153]  = 420; GBK_FREQ[2][137]   = 419; GBK_FREQ[59][147]  = 418; GBK_FREQ[58][152]  = 417; GBK_FREQ[55][144]  = 416;\n        GBK_FREQ[73][125]  = 415; GBK_FREQ[52][154]  = 414; GBK_FREQ[70][178]  = 413; GBK_FREQ[79][148]  = 412; GBK_FREQ[63][143]  = 411;\n        GBK_FREQ[50][140]  = 410; GBK_FREQ[47][145]  = 409; GBK_FREQ[48][123]  = 408; GBK_FREQ[56][107]  = 407; GBK_FREQ[84][83]   = 406;\n        GBK_FREQ[59][112]  = 405; GBK_FREQ[124][72]  = 404; GBK_FREQ[79][99]   = 403; GBK_FREQ[3][37]    = 402; GBK_FREQ[114][55]  = 401;\n        GBK_FREQ[85][152]  = 400; GBK_FREQ[60][47]   = 399; GBK_FREQ[65][96]   = 398; GBK_FREQ[74][110]  = 397; GBK_FREQ[86][182]  = 396;\n        GBK_FREQ[50][99]   = 395; GBK_FREQ[67][186]  = 394; GBK_FREQ[81][74]   = 393; GBK_FREQ[80][37]   = 392; GBK_FREQ[21][60]   = 391;\n        GBK_FREQ[110][12]  = 390; GBK_FREQ[60][162]  = 389; GBK_FREQ[29][115]  = 388; GBK_FREQ[83][130]  = 387; GBK_FREQ[52][136]  = 386;\n        GBK_FREQ[63][114]  = 385; GBK_FREQ[49][127]  = 384; GBK_FREQ[83][109]  = 383; GBK_FREQ[66][128]  = 382; GBK_FREQ[78][136]  = 381;\n        GBK_FREQ[81][180]  = 380; GBK_FREQ[76][104]  = 379; GBK_FREQ[56][156]  = 378; GBK_FREQ[61][23]   = 377; GBK_FREQ[4][30]    = 376;\n        GBK_FREQ[69][154]  = 375; GBK_FREQ[100][37]  = 374; GBK_FREQ[54][177]  = 373; GBK_FREQ[23][119]  = 372; GBK_FREQ[71][171]  = 371;\n        GBK_FREQ[84][146]  = 370; GBK_FREQ[20][184]  = 369; GBK_FREQ[86][76]   = 368; GBK_FREQ[74][132]  = 367; GBK_FREQ[47][97]   = 366;\n        GBK_FREQ[82][137]  = 365; GBK_FREQ[94][56]   = 364; GBK_FREQ[92][30]   = 363; GBK_FREQ[19][117]  = 362; GBK_FREQ[48][173]  = 361;\n        GBK_FREQ[2][136]   = 360; GBK_FREQ[7][182]   = 359; GBK_FREQ[74][188]  = 358; GBK_FREQ[14][132]  = 357; GBK_FREQ[62][172]  = 356;\n        GBK_FREQ[25][39]   = 355; GBK_FREQ[85][129]  = 354; GBK_FREQ[64][98]   = 353; GBK_FREQ[67][127]  = 352; GBK_FREQ[72][167]  = 351;\n        GBK_FREQ[57][143]  = 350; GBK_FREQ[76][187]  = 349; GBK_FREQ[83][181]  = 348; GBK_FREQ[84][10]   = 347; GBK_FREQ[55][166]  = 346;\n        GBK_FREQ[55][188]  = 345; GBK_FREQ[13][151]  = 344; GBK_FREQ[62][124]  = 343; GBK_FREQ[53][136]  = 342; GBK_FREQ[106][57]  = 341;\n        GBK_FREQ[47][166]  = 340; GBK_FREQ[109][30]  = 339; GBK_FREQ[78][114]  = 338; GBK_FREQ[83][19]   = 337; GBK_FREQ[56][162]  = 336;\n        GBK_FREQ[60][177]  = 335; GBK_FREQ[88][9]    = 334; GBK_FREQ[74][163]  = 333; GBK_FREQ[52][156]  = 332; GBK_FREQ[71][180]  = 331;\n        GBK_FREQ[60][57]   = 330; GBK_FREQ[72][173]  = 329; GBK_FREQ[82][91]   = 328; GBK_FREQ[51][186]  = 327; GBK_FREQ[75][86]   = 326;\n        GBK_FREQ[75][78]   = 325; GBK_FREQ[76][170]  = 324; GBK_FREQ[60][147]  = 323; GBK_FREQ[82][75]   = 322; GBK_FREQ[80][148]  = 321;\n        GBK_FREQ[86][150]  = 320; GBK_FREQ[13][95]   = 319; GBK_FREQ[0][11]    = 318; GBK_FREQ[84][190]  = 317; GBK_FREQ[76][166]  = 316;\n        GBK_FREQ[14][72]   = 315; GBK_FREQ[67][144]  = 314; GBK_FREQ[84][44]   = 313; GBK_FREQ[72][125]  = 312; GBK_FREQ[66][127]  = 311;\n        GBK_FREQ[60][25]   = 310; GBK_FREQ[70][146]  = 309; GBK_FREQ[79][135]  = 308; GBK_FREQ[54][135]  = 307; GBK_FREQ[60][104]  = 306;\n        GBK_FREQ[55][132]  = 305; GBK_FREQ[94][2]    = 304; GBK_FREQ[54][133]  = 303; GBK_FREQ[56][190]  = 302; GBK_FREQ[58][174]  = 301;\n        GBK_FREQ[80][144]  = 300; GBK_FREQ[85][113]  = 299;\n        /*\n        GBKFreq[83][15]  = 298; GBKFreq[105][80] = 297; GBKFreq[7][179]  = 296; GBKFreq[93][4]   = 295; GBKFreq[123][40] = 294;\n        GBKFreq[85][120] = 293; GBKFreq[77][165] = 292; GBKFreq[86][67]  = 291; GBKFreq[25][162] = 290; GBKFreq[77][183] = 289;\n        GBKFreq[83][71]  = 288; GBKFreq[78][99]  = 287; GBKFreq[72][177] = 286; GBKFreq[71][97]  = 285; GBKFreq[58][111] = 284;\n        GBKFreq[77][175] = 283; GBKFreq[76][181] = 282; GBKFreq[71][142] = 281; GBKFreq[64][150] = 280; GBKFreq[5][142]  = 279;\n        GBKFreq[73][128] = 278; GBKFreq[73][156] = 277; GBKFreq[60][188] = 276; GBKFreq[64][56]  = 275; GBKFreq[74][128] = 274;\n        GBKFreq[48][163] = 273; GBKFreq[54][116] = 272; GBKFreq[73][127] = 271; GBKFreq[16][176] = 270; GBKFreq[62][149] = 269;\n        GBKFreq[105][96] = 268; GBKFreq[55][186] = 267; GBKFreq[4][51]   = 266; GBKFreq[48][113] = 265; GBKFreq[48][152] = 264;\n        GBKFreq[23][9]   = 263; GBKFreq[56][102] = 262; GBKFreq[11][81]  = 261; GBKFreq[82][112] = 260; GBKFreq[65][85]  = 259;\n        GBKFreq[69][125] = 258; GBKFreq[68][31]  = 257; GBKFreq[5][20]   = 256; GBKFreq[60][176] = 255; GBKFreq[82][81]  = 254;\n        GBKFreq[72][107] = 253; GBKFreq[3][52]   = 252; GBKFreq[71][157] = 251; GBKFreq[24][46]  = 250; GBKFreq[69][108] = 249;\n        GBKFreq[78][178] = 248; GBKFreq[9][69]   = 247; GBKFreq[73][144] = 246; GBKFreq[63][187] = 245; GBKFreq[68][36]  = 244;\n        GBKFreq[47][151] = 243; GBKFreq[14][74]  = 242; GBKFreq[47][114] = 241; GBKFreq[80][171] = 240; GBKFreq[75][152] = 239;\n        GBKFreq[86][40]  = 238; GBKFreq[93][43]  = 237; GBKFreq[2][50]   = 236; GBKFreq[62][66]  = 235; GBKFreq[1][183]  = 234;\n        GBKFreq[74][124] = 233; GBKFreq[58][104] = 232; GBKFreq[83][106] = 231; GBKFreq[60][144] = 230; GBKFreq[48][99]  = 229;\n        GBKFreq[54][157] = 228; GBKFreq[70][179] = 227; GBKFreq[61][127] = 226; GBKFreq[57][135] = 225; GBKFreq[59][190] = 224;\n        GBKFreq[77][116] = 223; GBKFreq[26][17]  = 222; GBKFreq[60][13]  = 221; GBKFreq[71][38]  = 220; GBKFreq[85][177] = 219;\n        GBKFreq[59][73]  = 218; GBKFreq[50][150] = 217; GBKFreq[79][102] = 216; GBKFreq[76][118] = 215; GBKFreq[67][132] = 214;\n        GBKFreq[73][146] = 213; GBKFreq[83][184] = 212; GBKFreq[86][159] = 211; GBKFreq[95][120] = 210; GBKFreq[23][139] = 209;\n        GBKFreq[64][183] = 208; GBKFreq[85][103] = 207; GBKFreq[41][90]  = 206; GBKFreq[87][72]  = 205; GBKFreq[62][104] = 204;\n        GBKFreq[79][168] = 203; GBKFreq[79][150] = 202; GBKFreq[104][20] = 201; GBKFreq[56][114] = 200; GBKFreq[84][26]  = 199;\n        GBKFreq[57][99]  = 198; GBKFreq[62][154] = 197; GBKFreq[47][98]  = 196; GBKFreq[61][64]  = 195; GBKFreq[112][18] = 194;\n        GBKFreq[123][19] = 193; GBKFreq[4][98]   = 192; GBKFreq[47][163] = 191; GBKFreq[66][188] = 190; GBKFreq[81][85]  = 189;\n        GBKFreq[82][30]  = 188; GBKFreq[65][83]  = 187; GBKFreq[67][24]  = 186; GBKFreq[68][179] = 185; GBKFreq[55][177] = 184;\n        GBKFreq[2][122]  = 183; GBKFreq[47][139] = 182; GBKFreq[79][158] = 181; GBKFreq[64][143] = 180; GBKFreq[100][24] = 179;\n        GBKFreq[73][103] = 178; GBKFreq[50][148] = 177; GBKFreq[86][97]  = 176; GBKFreq[59][116] = 175; GBKFreq[64][173] = 174;\n        GBKFreq[99][91]  = 173; GBKFreq[11][99]  = 172; GBKFreq[78][179] = 171; GBKFreq[18][17]  = 170; GBKFreq[58][185] = 169;\n        GBKFreq[47][165] = 168; GBKFreq[67][131] = 167; GBKFreq[94][40]  = 166; GBKFreq[74][153] = 165; GBKFreq[79][142] = 164;\n        GBKFreq[57][98]  = 163; GBKFreq[1][164]  = 162; GBKFreq[55][168] = 161; GBKFreq[13][141] = 160; GBKFreq[51][31]  = 159;\n        GBKFreq[57][178] = 158; GBKFreq[50][189] = 157; GBKFreq[60][167] = 156; GBKFreq[80][34]  = 155; GBKFreq[109][80] = 154;\n        GBKFreq[85][54]  = 153; GBKFreq[69][183] = 152; GBKFreq[67][143] = 151; GBKFreq[47][120] = 150; GBKFreq[45][75]  = 149;\n        GBKFreq[82][98]  = 148; GBKFreq[83][22]  = 147; GBKFreq[13][103] = 146; GBKFreq[49][174] = 145; GBKFreq[57][181] = 144;\n        GBKFreq[64][127] = 143; GBKFreq[61][131] = 142; GBKFreq[52][180] = 141; GBKFreq[74][134] = 140; GBKFreq[84][187] = 139;\n        GBKFreq[81][189] = 138; GBKFreq[47][160] = 137; GBKFreq[66][148] = 136; GBKFreq[7][4]    = 135; GBKFreq[85][134] = 134;\n        GBKFreq[88][13]  = 133; GBKFreq[88][80]  = 132; GBKFreq[69][166] = 131; GBKFreq[86][18]  = 130; GBKFreq[79][141] = 129;\n        GBKFreq[50][108] = 128; GBKFreq[94][69]  = 127; GBKFreq[81][110] = 126; GBKFreq[69][119] = 125; GBKFreq[72][161] = 124;\n        GBKFreq[106][45] = 123; GBKFreq[73][124] = 122; GBKFreq[94][28]  = 121; GBKFreq[63][174] = 120; GBKFreq[3][149]  = 119;\n        GBKFreq[24][160] = 118; GBKFreq[113][94] = 117; GBKFreq[56][138] = 116; GBKFreq[64][185] = 115; GBKFreq[86][56]  = 114;\n        GBKFreq[56][150] = 113; GBKFreq[110][55] = 112; GBKFreq[28][13]  = 111; GBKFreq[54][190] = 110; GBKFreq[8][180]  = 109;\n        GBKFreq[73][149] = 108; GBKFreq[80][155] = 107; GBKFreq[83][172] = 106; GBKFreq[67][174] = 105; GBKFreq[64][180] = 104;\n        GBKFreq[84][46]  = 103; GBKFreq[91][74]  = 102; GBKFreq[69][134] = 101; GBKFreq[61][107] = 100; GBKFreq[47][171] =  99;\n        GBKFreq[59][51]  =  98; GBKFreq[109][74] =  97; GBKFreq[64][174] =  96; GBKFreq[52][151] =  95; GBKFreq[51][176] =  94;\n        GBKFreq[80][157] =  93; GBKFreq[94][31]  =  92; GBKFreq[79][155] =  91; GBKFreq[72][174] =  90; GBKFreq[69][113] =  89;\n        GBKFreq[83][167] =  88; GBKFreq[83][122] =  87; GBKFreq[8][178]  =  86; GBKFreq[70][186] =  85; GBKFreq[59][153] =  84;\n        GBKFreq[84][68]  =  83; GBKFreq[79][39]  =  82; GBKFreq[47][180] =  81; GBKFreq[88][53]  =  80; GBKFreq[57][154] =  79;\n        GBKFreq[47][153] =  78; GBKFreq[3][153]  =  77; GBKFreq[76][134] =  76; GBKFreq[51][166] =  75; GBKFreq[58][176] =  74;\n        GBKFreq[27][138] =  73; GBKFreq[73][126] =  72; GBKFreq[76][185] =  71; GBKFreq[52][186] =  70; GBKFreq[81][151] =  69;\n        GBKFreq[26][50]  =  68; GBKFreq[76][173] =  67; GBKFreq[106][56] =  66; GBKFreq[85][142] =  65; GBKFreq[11][103] =  64;\n        GBKFreq[69][159] =  63; GBKFreq[53][142] =  62; GBKFreq[7][6]    =  61; GBKFreq[84][59]  =  60; GBKFreq[86][3]   =  59;\n        GBKFreq[64][144] =  58; GBKFreq[1][187]  =  57; GBKFreq[82][128] =  56; GBKFreq[3][66]   =  55; GBKFreq[68][133] =  54;\n        GBKFreq[55][167] =  53; GBKFreq[52][130] =  52; GBKFreq[61][133] =  51; GBKFreq[72][181] =  50; GBKFreq[25][98]  =  49;\n        GBKFreq[84][149] =  48; GBKFreq[91][91]  =  47; GBKFreq[47][188] =  46; GBKFreq[68][130] =  45; GBKFreq[22][44]  =  44;\n        GBKFreq[81][121] =  43; GBKFreq[72][140] =  42; GBKFreq[55][133] =  41; GBKFreq[55][185] =  40; GBKFreq[56][105] =  39;\n        GBKFreq[60][30]  =  38; GBKFreq[70][103] =  37; GBKFreq[62][141] =  36; GBKFreq[70][144] =  35; GBKFreq[59][111] =  34;\n        GBKFreq[54][17]  =  33; GBKFreq[18][190] =  32; GBKFreq[65][164] =  31; GBKFreq[83][125] =  30; GBKFreq[61][121] =  29;\n        GBKFreq[48][13]  =  28; GBKFreq[51][189] =  27; GBKFreq[65][68]  =  26; GBKFreq[7][0]    =  25; GBKFreq[76][188] =  24;\n        GBKFreq[85][117] =  23; GBKFreq[45][33]  =  22; GBKFreq[78][187] =  21; GBKFreq[106][48] =  20; GBKFreq[59][52]  =  19;\n        GBKFreq[86][185] =  18; GBKFreq[84][121] =  17; GBKFreq[82][189] =  16; GBKFreq[68][156] =  15; GBKFreq[55][125] =  14;\n        GBKFreq[65][175] =  13; GBKFreq[7][140]  =  12; GBKFreq[50][106] =  11; GBKFreq[59][124] =  10; GBKFreq[67][115] =   9;\n        GBKFreq[82][114] =   8; GBKFreq[74][121] =   7; GBKFreq[106][69] =   6; GBKFreq[94][27]  =   5; GBKFreq[78][98]  =   4;\n        GBKFreq[85][186] =   3; GBKFreq[108][90] =   2; GBKFreq[62][160] =   1; GBKFreq[60][169] =   0;\n        */\n\n        // ------------------------------------------------------------------------------------KR_FREQ\n\n        KR_FREQ[31][43] = 600; KR_FREQ[19][56] = 599; KR_FREQ[38][46] = 598; KR_FREQ[3][3]   = 597; KR_FREQ[29][77] = 596;\n        KR_FREQ[19][33] = 595; KR_FREQ[30][0]  = 594; KR_FREQ[29][89] = 593; KR_FREQ[31][26] = 592; KR_FREQ[31][38] = 591;\n        KR_FREQ[32][85] = 590; KR_FREQ[15][0]  = 589; KR_FREQ[16][54] = 588; KR_FREQ[15][76] = 587; KR_FREQ[31][25] = 586;\n        KR_FREQ[23][13] = 585; KR_FREQ[28][34] = 584; KR_FREQ[18][9]  = 583; KR_FREQ[29][37] = 582; KR_FREQ[22][45] = 581;\n        KR_FREQ[19][46] = 580; KR_FREQ[16][65] = 579; KR_FREQ[23][5]  = 578; KR_FREQ[26][70] = 577; KR_FREQ[31][53] = 576;\n        KR_FREQ[27][12] = 575; KR_FREQ[30][67] = 574; KR_FREQ[31][57] = 573; KR_FREQ[20][20] = 572; KR_FREQ[30][31] = 571;\n        KR_FREQ[20][72] = 570; KR_FREQ[15][51] = 569; KR_FREQ[3][8]   = 568; KR_FREQ[32][53] = 567; KR_FREQ[27][85] = 566;\n        KR_FREQ[25][23] = 565; KR_FREQ[15][44] = 564; KR_FREQ[32][3]  = 563; KR_FREQ[31][68] = 562; KR_FREQ[30][24] = 561;\n        KR_FREQ[29][49] = 560; KR_FREQ[27][49] = 559; KR_FREQ[23][23] = 558; KR_FREQ[31][91] = 557; KR_FREQ[31][46] = 556;\n        KR_FREQ[19][74] = 555; KR_FREQ[27][27] = 554; KR_FREQ[3][17]  = 553; KR_FREQ[20][38] = 552; KR_FREQ[21][82] = 551;\n        KR_FREQ[28][25] = 550; KR_FREQ[32][5]  = 549; KR_FREQ[31][23] = 548; KR_FREQ[25][45] = 547; KR_FREQ[32][87] = 546;\n        KR_FREQ[18][26] = 545; KR_FREQ[24][10] = 544; KR_FREQ[26][82] = 543; KR_FREQ[15][89] = 542; KR_FREQ[28][36] = 541;\n        KR_FREQ[28][31] = 540; KR_FREQ[16][23] = 539; KR_FREQ[16][77] = 538; KR_FREQ[19][84] = 537; KR_FREQ[23][72] = 536;\n        KR_FREQ[38][48] = 535; KR_FREQ[23][2]  = 534; KR_FREQ[30][20] = 533; KR_FREQ[38][47] = 532; KR_FREQ[39][12] = 531;\n        KR_FREQ[23][21] = 530; KR_FREQ[18][17] = 529; KR_FREQ[30][87] = 528; KR_FREQ[29][62] = 527; KR_FREQ[29][87] = 526;\n        KR_FREQ[34][53] = 525; KR_FREQ[32][29] = 524; KR_FREQ[35][0]  = 523; KR_FREQ[24][43] = 522; KR_FREQ[36][44] = 521;\n        KR_FREQ[20][30] = 520; KR_FREQ[39][86] = 519; KR_FREQ[22][14] = 518; KR_FREQ[29][39] = 517; KR_FREQ[28][38] = 516;\n        KR_FREQ[23][79] = 515; KR_FREQ[24][56] = 514; KR_FREQ[29][63] = 513; KR_FREQ[31][45] = 512; KR_FREQ[23][26] = 511;\n        KR_FREQ[15][87] = 510; KR_FREQ[30][74] = 509; KR_FREQ[24][69] = 508; KR_FREQ[20][4]  = 507; KR_FREQ[27][50] = 506;\n        KR_FREQ[30][75] = 505; KR_FREQ[24][13] = 504; KR_FREQ[30][8]  = 503; KR_FREQ[31][6]  = 502; KR_FREQ[25][80] = 501;\n        KR_FREQ[36][8]  = 500; KR_FREQ[15][18] = 499; KR_FREQ[39][23] = 498; KR_FREQ[16][24] = 497; KR_FREQ[31][89] = 496;\n        KR_FREQ[15][71] = 495; KR_FREQ[15][57] = 494; KR_FREQ[30][11] = 493; KR_FREQ[15][36] = 492; KR_FREQ[16][60] = 491;\n        KR_FREQ[24][45] = 490; KR_FREQ[37][35] = 489; KR_FREQ[24][87] = 488; KR_FREQ[20][45] = 487; KR_FREQ[31][90] = 486;\n        KR_FREQ[32][21] = 485; KR_FREQ[19][70] = 484; KR_FREQ[24][15] = 483; KR_FREQ[26][92] = 482; KR_FREQ[37][13] = 481;\n        KR_FREQ[39][2]  = 480; KR_FREQ[23][70] = 479; KR_FREQ[27][25] = 478; KR_FREQ[15][69] = 477; KR_FREQ[19][61] = 476;\n        KR_FREQ[31][58] = 475; KR_FREQ[24][57] = 474; KR_FREQ[36][74] = 473; KR_FREQ[21][6]  = 472; KR_FREQ[30][44] = 471;\n        KR_FREQ[15][91] = 470; KR_FREQ[27][16] = 469; KR_FREQ[29][42] = 468; KR_FREQ[33][86] = 467; KR_FREQ[29][41] = 466;\n        KR_FREQ[20][68] = 465; KR_FREQ[25][47] = 464; KR_FREQ[22][0]  = 463; KR_FREQ[18][14] = 462; KR_FREQ[31][28] = 461;\n        KR_FREQ[15][2]  = 460; KR_FREQ[23][76] = 459; KR_FREQ[38][32] = 458; KR_FREQ[29][82] = 457; KR_FREQ[21][86] = 456;\n        KR_FREQ[24][62] = 455; KR_FREQ[31][64] = 454; KR_FREQ[38][26] = 453; KR_FREQ[32][86] = 452; KR_FREQ[22][32] = 451;\n        KR_FREQ[19][59] = 450; KR_FREQ[34][18] = 449; KR_FREQ[18][54] = 448; KR_FREQ[38][63] = 447; KR_FREQ[36][23] = 446;\n        KR_FREQ[35][35] = 445; KR_FREQ[32][62] = 444; KR_FREQ[28][35] = 443; KR_FREQ[27][13] = 442; KR_FREQ[31][59] = 441;\n        KR_FREQ[29][29] = 440; KR_FREQ[15][64] = 439; KR_FREQ[26][84] = 438; KR_FREQ[21][90] = 437; KR_FREQ[20][24] = 436;\n        KR_FREQ[16][18] = 435; KR_FREQ[22][23] = 434; KR_FREQ[31][14] = 433; KR_FREQ[15][1]  = 432; KR_FREQ[18][63] = 431;\n        KR_FREQ[19][10] = 430; KR_FREQ[25][49] = 429; KR_FREQ[36][57] = 428; KR_FREQ[20][22] = 427; KR_FREQ[15][15] = 426;\n        KR_FREQ[31][51] = 425; KR_FREQ[24][60] = 424; KR_FREQ[31][70] = 423; KR_FREQ[15][7]  = 422; KR_FREQ[28][40] = 421;\n        KR_FREQ[18][41] = 420; KR_FREQ[15][38] = 419; KR_FREQ[32][0]  = 418; KR_FREQ[19][51] = 417; KR_FREQ[34][62] = 416;\n        KR_FREQ[16][27] = 415; KR_FREQ[20][70] = 414; KR_FREQ[22][33] = 413; KR_FREQ[26][73] = 412; KR_FREQ[20][79] = 411;\n        KR_FREQ[23][6]  = 410; KR_FREQ[24][85] = 409; KR_FREQ[38][51] = 408; KR_FREQ[29][88] = 407; KR_FREQ[38][55] = 406;\n        KR_FREQ[32][32] = 405; KR_FREQ[27][18] = 404; KR_FREQ[23][87] = 403; KR_FREQ[35][6]  = 402; KR_FREQ[34][27] = 401;\n        KR_FREQ[39][35] = 400; KR_FREQ[30][88] = 399; KR_FREQ[32][92] = 398; KR_FREQ[32][49] = 397; KR_FREQ[24][61] = 396;\n        KR_FREQ[18][74] = 395; KR_FREQ[23][77] = 394; KR_FREQ[23][50] = 393; KR_FREQ[23][32] = 392; KR_FREQ[23][36] = 391;\n        KR_FREQ[38][38] = 390; KR_FREQ[29][86] = 389; KR_FREQ[36][15] = 388; KR_FREQ[31][50] = 387; KR_FREQ[15][86] = 386;\n        KR_FREQ[39][13] = 385; KR_FREQ[34][26] = 384; KR_FREQ[19][34] = 383; KR_FREQ[16][3]  = 382; KR_FREQ[26][93] = 381;\n        KR_FREQ[19][67] = 380; KR_FREQ[24][72] = 379; KR_FREQ[29][17] = 378; KR_FREQ[23][24] = 377; KR_FREQ[25][19] = 376;\n        KR_FREQ[18][65] = 375; KR_FREQ[30][78] = 374; KR_FREQ[27][52] = 373; KR_FREQ[22][18] = 372; KR_FREQ[16][38] = 371;\n        KR_FREQ[21][26] = 370; KR_FREQ[34][20] = 369; KR_FREQ[15][42] = 368; KR_FREQ[16][71] = 367; KR_FREQ[17][17] = 366;\n        KR_FREQ[24][71] = 365; KR_FREQ[18][84] = 364; KR_FREQ[15][40] = 363; KR_FREQ[31][62] = 362; KR_FREQ[15][8]  = 361;\n        KR_FREQ[16][69] = 360; KR_FREQ[29][79] = 359; KR_FREQ[38][91] = 358; KR_FREQ[31][92] = 357; KR_FREQ[20][77] = 356;\n        KR_FREQ[3][16]  = 355; KR_FREQ[27][87] = 354; KR_FREQ[16][25] = 353; KR_FREQ[36][33] = 352; KR_FREQ[37][76] = 351;\n        KR_FREQ[30][12] = 350; KR_FREQ[26][75] = 349; KR_FREQ[25][14] = 348; KR_FREQ[32][26] = 347; KR_FREQ[23][22] = 346;\n        KR_FREQ[20][90] = 345; KR_FREQ[19][8]  = 344; KR_FREQ[38][41] = 343; KR_FREQ[34][2]  = 342; KR_FREQ[39][4]  = 341;\n        KR_FREQ[27][89] = 340; KR_FREQ[28][41] = 339; KR_FREQ[28][44] = 338; KR_FREQ[24][92] = 337; KR_FREQ[34][65] = 336;\n        KR_FREQ[39][14] = 335; KR_FREQ[21][38] = 334; KR_FREQ[19][31] = 333; KR_FREQ[37][39] = 332; KR_FREQ[33][41] = 331;\n        KR_FREQ[38][4]  = 330; KR_FREQ[23][80] = 329; KR_FREQ[25][24] = 328; KR_FREQ[37][17] = 327; KR_FREQ[22][16] = 326;\n        KR_FREQ[22][46] = 325; KR_FREQ[33][91] = 324; KR_FREQ[24][89] = 323; KR_FREQ[30][52] = 322; KR_FREQ[29][38] = 321;\n        KR_FREQ[38][85] = 320; KR_FREQ[15][12] = 319; KR_FREQ[27][58] = 318; KR_FREQ[29][52] = 317; KR_FREQ[37][38] = 316;\n        KR_FREQ[34][41] = 315; KR_FREQ[31][65] = 314; KR_FREQ[29][53] = 313; KR_FREQ[22][47] = 312; KR_FREQ[22][19] = 311;\n        KR_FREQ[26][0]  = 310; KR_FREQ[37][86] = 309; KR_FREQ[35][4]  = 308; KR_FREQ[36][54] = 307; KR_FREQ[20][76] = 306;\n        KR_FREQ[30][9]  = 305; KR_FREQ[30][33] = 304; KR_FREQ[23][17] = 303; KR_FREQ[23][33] = 302; KR_FREQ[38][52] = 301;\n        KR_FREQ[15][19] = 300; KR_FREQ[28][45] = 299; KR_FREQ[29][78] = 298; KR_FREQ[23][15] = 297; KR_FREQ[33][5]  = 296;\n        KR_FREQ[17][40] = 295; KR_FREQ[30][83] = 294; KR_FREQ[18][1]  = 293; KR_FREQ[30][81] = 292; KR_FREQ[19][40] = 291;\n        KR_FREQ[24][47] = 290; KR_FREQ[17][56] = 289; KR_FREQ[39][80] = 288; KR_FREQ[30][46] = 287; KR_FREQ[16][61] = 286;\n        KR_FREQ[26][78] = 285; KR_FREQ[26][57] = 284; KR_FREQ[20][46] = 283; KR_FREQ[25][15] = 282; KR_FREQ[25][91] = 281;\n        KR_FREQ[21][83] = 280; KR_FREQ[30][77] = 279; KR_FREQ[35][30] = 278; KR_FREQ[30][34] = 277; KR_FREQ[20][69] = 276;\n        KR_FREQ[35][10] = 275; KR_FREQ[29][70] = 274; KR_FREQ[22][50] = 273; KR_FREQ[18][0]  = 272; KR_FREQ[22][64] = 271;\n        KR_FREQ[38][65] = 270; KR_FREQ[22][70] = 269; KR_FREQ[24][58] = 268; KR_FREQ[19][66] = 267; KR_FREQ[30][59] = 266;\n        KR_FREQ[37][14] = 265; KR_FREQ[16][56] = 264; KR_FREQ[29][85] = 263; KR_FREQ[31][15] = 262; KR_FREQ[36][84] = 261;\n        KR_FREQ[39][15] = 260; KR_FREQ[39][90] = 259; KR_FREQ[18][12] = 258; KR_FREQ[21][93] = 257; KR_FREQ[24][66] = 256;\n        KR_FREQ[27][90] = 255; KR_FREQ[25][90] = 254; KR_FREQ[22][24] = 253; KR_FREQ[36][67] = 252; KR_FREQ[33][90] = 251;\n        KR_FREQ[15][60] = 250; KR_FREQ[23][85] = 249; KR_FREQ[34][1]  = 248; KR_FREQ[39][37] = 247; KR_FREQ[21][18] = 246;\n        KR_FREQ[34][4]  = 245; KR_FREQ[28][33] = 244; KR_FREQ[15][13] = 243; KR_FREQ[32][22] = 242; KR_FREQ[30][76] = 241;\n        KR_FREQ[20][21] = 240; KR_FREQ[38][66] = 239; KR_FREQ[32][55] = 238; KR_FREQ[32][89] = 237; KR_FREQ[25][26] = 236;\n        KR_FREQ[16][80] = 235; KR_FREQ[15][43] = 234; KR_FREQ[38][54] = 233; KR_FREQ[39][68] = 232; KR_FREQ[22][88] = 231;\n        KR_FREQ[21][84] = 230; KR_FREQ[21][17] = 229; KR_FREQ[20][28] = 228; KR_FREQ[32][1]  = 227; KR_FREQ[33][87] = 226;\n        KR_FREQ[38][71] = 225; KR_FREQ[37][47] = 224; KR_FREQ[18][77] = 223; KR_FREQ[37][58] = 222; KR_FREQ[34][74] = 221;\n        KR_FREQ[32][54] = 220; KR_FREQ[27][33] = 219; KR_FREQ[32][93] = 218; KR_FREQ[23][51] = 217; KR_FREQ[20][57] = 216;\n        KR_FREQ[22][37] = 215; KR_FREQ[39][10] = 214; KR_FREQ[39][17] = 213; KR_FREQ[33][4]  = 212; KR_FREQ[32][84] = 211;\n        KR_FREQ[34][3]  = 210; KR_FREQ[28][27] = 209; KR_FREQ[15][79] = 208; KR_FREQ[34][21] = 207; KR_FREQ[34][69] = 206;\n        KR_FREQ[21][62] = 205; KR_FREQ[36][24] = 204; KR_FREQ[16][89] = 203; KR_FREQ[18][48] = 202; KR_FREQ[38][15] = 201;\n        KR_FREQ[36][58] = 200; KR_FREQ[21][56] = 199; KR_FREQ[34][48] = 198; KR_FREQ[21][15] = 197; KR_FREQ[39][3]  = 196;\n        KR_FREQ[16][44] = 195; KR_FREQ[18][79] = 194; KR_FREQ[25][13] = 193; KR_FREQ[29][47] = 192; KR_FREQ[38][88] = 191;\n        KR_FREQ[20][71] = 190; KR_FREQ[16][58] = 189; KR_FREQ[35][57] = 188; KR_FREQ[29][30] = 187; KR_FREQ[29][23] = 186;\n        KR_FREQ[34][93] = 185; KR_FREQ[30][85] = 184; KR_FREQ[15][80] = 183; KR_FREQ[32][78] = 182; KR_FREQ[37][82] = 181;\n        KR_FREQ[22][40] = 180; KR_FREQ[21][69] = 179; KR_FREQ[26][85] = 178; KR_FREQ[31][31] = 177; KR_FREQ[28][64] = 176;\n        KR_FREQ[38][13] = 175; KR_FREQ[25][2]  = 174; KR_FREQ[22][34] = 173; KR_FREQ[28][28] = 172; KR_FREQ[24][91] = 171;\n        KR_FREQ[33][74] = 170; KR_FREQ[29][40] = 169; KR_FREQ[15][77] = 168; KR_FREQ[32][80] = 167; KR_FREQ[30][41] = 166;\n        KR_FREQ[23][30] = 165; KR_FREQ[24][63] = 164; KR_FREQ[30][53] = 163; KR_FREQ[39][70] = 162; KR_FREQ[23][61] = 161;\n        KR_FREQ[37][27] = 160; KR_FREQ[16][55] = 159; KR_FREQ[22][74] = 158; KR_FREQ[26][50] = 157; KR_FREQ[16][10] = 156;\n        KR_FREQ[34][63] = 155; KR_FREQ[35][14] = 154; KR_FREQ[17][7]  = 153; KR_FREQ[15][59] = 152; KR_FREQ[27][23] = 151;\n        KR_FREQ[18][70] = 150; KR_FREQ[32][56] = 149; KR_FREQ[37][87] = 148; KR_FREQ[17][61] = 147; KR_FREQ[18][83] = 146;\n        KR_FREQ[23][86] = 145; KR_FREQ[17][31] = 144; KR_FREQ[23][83] = 143; KR_FREQ[35][2]  = 142; KR_FREQ[18][64] = 141;\n        KR_FREQ[27][43] = 140; KR_FREQ[32][42] = 139; KR_FREQ[25][76] = 138; KR_FREQ[19][85] = 137; KR_FREQ[37][81] = 136;\n        KR_FREQ[38][83] = 135; KR_FREQ[35][7]  = 134; KR_FREQ[16][51] = 133; KR_FREQ[27][22] = 132; KR_FREQ[16][76] = 131;\n        KR_FREQ[22][4]  = 130; KR_FREQ[38][84] = 129; KR_FREQ[17][83] = 128; KR_FREQ[24][46] = 127; KR_FREQ[33][15] = 126;\n        KR_FREQ[20][48] = 125; KR_FREQ[17][30] = 124; KR_FREQ[30][93] = 123; KR_FREQ[28][11] = 122; KR_FREQ[28][30] = 121;\n        KR_FREQ[15][62] = 120; KR_FREQ[17][87] = 119; KR_FREQ[32][81] = 118; KR_FREQ[23][37] = 117; KR_FREQ[30][22] = 116;\n        KR_FREQ[32][66] = 115; KR_FREQ[33][78] = 114; KR_FREQ[21][4]  = 113; KR_FREQ[31][17] = 112; KR_FREQ[39][61] = 111;\n        KR_FREQ[18][76] = 110; KR_FREQ[15][85] = 109; KR_FREQ[31][47] = 108; KR_FREQ[19][57] = 107; KR_FREQ[23][55] = 106;\n        KR_FREQ[27][29] = 105; KR_FREQ[29][46] = 104; KR_FREQ[33][0]  = 103; KR_FREQ[16][83] = 102; KR_FREQ[39][78] = 101;\n        KR_FREQ[32][77] = 100; KR_FREQ[36][25] =  99; KR_FREQ[34][19] =  98; KR_FREQ[38][49] =  97; KR_FREQ[19][25] =  96;\n        KR_FREQ[23][53] =  95; KR_FREQ[28][43] =  94; KR_FREQ[31][44] =  93; KR_FREQ[36][34] =  92; KR_FREQ[16][34] =  91;\n        KR_FREQ[35][1]  =  90; KR_FREQ[19][87] =  89; KR_FREQ[18][53] =  88; KR_FREQ[29][54] =  87; KR_FREQ[22][41] =  86;\n        KR_FREQ[38][18] =  85; KR_FREQ[22][2]  =  84; KR_FREQ[20][3]  =  83; KR_FREQ[39][69] =  82; KR_FREQ[30][29] =  81;\n        KR_FREQ[28][19] =  80; KR_FREQ[29][90] =  79; KR_FREQ[17][86] =  78; KR_FREQ[15][9]  =  77; KR_FREQ[39][73] =  76;\n        KR_FREQ[15][37] =  75; KR_FREQ[35][40] =  74; KR_FREQ[33][77] =  73; KR_FREQ[27][86] =  72; KR_FREQ[36][79] =  71;\n        KR_FREQ[23][18] =  70; KR_FREQ[34][87] =  69; KR_FREQ[39][24] =  68; KR_FREQ[26][8]  =  67; KR_FREQ[33][48] =  66;\n        KR_FREQ[39][30] =  65; KR_FREQ[33][28] =  64; KR_FREQ[16][67] =  63; KR_FREQ[31][78] =  62; KR_FREQ[32][23] =  61;\n        KR_FREQ[24][55] =  60; KR_FREQ[30][68] =  59; KR_FREQ[18][60] =  58; KR_FREQ[15][17] =  57; KR_FREQ[23][34] =  56;\n        KR_FREQ[20][49] =  55; KR_FREQ[15][78] =  54; KR_FREQ[24][14] =  53; KR_FREQ[19][41] =  52; KR_FREQ[31][55] =  51;\n        KR_FREQ[21][39] =  50; KR_FREQ[35][9]  =  49; KR_FREQ[30][15] =  48; KR_FREQ[20][52] =  47; KR_FREQ[35][71] =  46;\n        KR_FREQ[20][7]  =  45; KR_FREQ[29][72] =  44; KR_FREQ[37][77] =  43; KR_FREQ[22][35] =  42; KR_FREQ[20][61] =  41;\n        KR_FREQ[31][60] =  40; KR_FREQ[20][93] =  39; KR_FREQ[27][92] =  38; KR_FREQ[28][16] =  37; KR_FREQ[36][26] =  36;\n        KR_FREQ[18][89] =  35; KR_FREQ[21][63] =  34; KR_FREQ[22][52] =  33; KR_FREQ[24][65] =  32; KR_FREQ[31][8]  =  31;\n        KR_FREQ[31][49] =  30; KR_FREQ[33][30] =  29; KR_FREQ[37][15] =  28; KR_FREQ[18][18] =  27; KR_FREQ[25][50] =  26;\n        KR_FREQ[29][20] =  25; KR_FREQ[35][48] =  24; KR_FREQ[38][75] =  23; KR_FREQ[26][83] =  22; KR_FREQ[21][87] =  21;\n        KR_FREQ[27][71] =  20; KR_FREQ[32][91] =  19; KR_FREQ[25][73] =  18; KR_FREQ[16][84] =  17; KR_FREQ[25][31] =  16;\n        KR_FREQ[17][90] =  15; KR_FREQ[18][40] =  14; KR_FREQ[17][77] =  13; KR_FREQ[17][35] =  12; KR_FREQ[23][52] =  11;\n        KR_FREQ[23][35] =  10; KR_FREQ[16][5]  =   9; KR_FREQ[23][58] =   8; KR_FREQ[19][60] =   7; KR_FREQ[30][32] =   6;\n        KR_FREQ[38][34] =   5; KR_FREQ[23][4]  =   4; KR_FREQ[23][1]  =   3; KR_FREQ[27][57] =   2; KR_FREQ[39][38] =   1;\n        KR_FREQ[32][33] =   0;\n\n        // ------------------------------------------------------------------------------------JP_FREQ\n\n        JP_FREQ[3][74]  = 600; JP_FREQ[3][45]  = 599; JP_FREQ[3][3]   = 598; JP_FREQ[3][24]  = 597; JP_FREQ[3][30]  = 596;\n        JP_FREQ[3][42]  = 595; JP_FREQ[3][46]  = 594; JP_FREQ[3][39]  = 593; JP_FREQ[3][11]  = 592; JP_FREQ[3][37]  = 591;\n        JP_FREQ[3][38]  = 590; JP_FREQ[3][31]  = 589; JP_FREQ[3][41]  = 588; JP_FREQ[3][5]   = 587; JP_FREQ[3][10]  = 586;\n        JP_FREQ[3][75]  = 585; JP_FREQ[3][65]  = 584; JP_FREQ[3][72]  = 583; JP_FREQ[37][91] = 582; JP_FREQ[0][27]  = 581;\n        JP_FREQ[3][18]  = 580; JP_FREQ[3][22]  = 579; JP_FREQ[3][61]  = 578; JP_FREQ[3][14]  = 577; JP_FREQ[24][80] = 576;\n        JP_FREQ[4][82]  = 575; JP_FREQ[17][80] = 574; JP_FREQ[30][44] = 573; JP_FREQ[3][73]  = 572; JP_FREQ[3][64]  = 571;\n        JP_FREQ[38][14] = 570; JP_FREQ[33][70] = 569; JP_FREQ[3][1]   = 568; JP_FREQ[3][16]  = 567; JP_FREQ[3][35]  = 566;\n        JP_FREQ[3][40]  = 565; JP_FREQ[4][74]  = 564; JP_FREQ[4][24]  = 563; JP_FREQ[42][59] = 562; JP_FREQ[3][7]   = 561;\n        JP_FREQ[3][71]  = 560; JP_FREQ[3][12]  = 559; JP_FREQ[15][75] = 558; JP_FREQ[3][20]  = 557; JP_FREQ[4][39]  = 556;\n        JP_FREQ[34][69] = 555; JP_FREQ[3][28]  = 554; JP_FREQ[35][24] = 553; JP_FREQ[3][82]  = 552; JP_FREQ[28][47] = 551;\n        JP_FREQ[3][67]  = 550; JP_FREQ[37][16] = 549; JP_FREQ[26][93] = 548; JP_FREQ[4][1]   = 547; JP_FREQ[26][85] = 546;\n        JP_FREQ[31][14] = 545; JP_FREQ[4][3]   = 544; JP_FREQ[4][72]  = 543; JP_FREQ[24][51] = 542; JP_FREQ[27][51] = 541;\n        JP_FREQ[27][49] = 540; JP_FREQ[22][77] = 539; JP_FREQ[27][10] = 538; JP_FREQ[29][68] = 537; JP_FREQ[20][35] = 536;\n        JP_FREQ[41][11] = 535; JP_FREQ[24][70] = 534; JP_FREQ[36][61] = 533; JP_FREQ[31][23] = 532; JP_FREQ[43][16] = 531;\n        JP_FREQ[23][68] = 530; JP_FREQ[32][15] = 529; JP_FREQ[3][32]  = 528; JP_FREQ[19][53] = 527; JP_FREQ[40][83] = 526;\n        JP_FREQ[4][14]  = 525; JP_FREQ[36][9]  = 524; JP_FREQ[4][73]  = 523; JP_FREQ[23][10] = 522; JP_FREQ[3][63]  = 521;\n        JP_FREQ[39][14] = 520; JP_FREQ[3][78]  = 519; JP_FREQ[33][47] = 518; JP_FREQ[21][39] = 517; JP_FREQ[34][46] = 516;\n        JP_FREQ[36][75] = 515; JP_FREQ[41][92] = 514; JP_FREQ[37][93] = 513; JP_FREQ[4][34]  = 512; JP_FREQ[15][86] = 511;\n        JP_FREQ[46][1]  = 510; JP_FREQ[37][65] = 509; JP_FREQ[3][62]  = 508; JP_FREQ[32][73] = 507; JP_FREQ[21][65] = 506;\n        JP_FREQ[29][75] = 505; JP_FREQ[26][51] = 504; JP_FREQ[3][34]  = 503; JP_FREQ[4][10]  = 502; JP_FREQ[30][22] = 501;\n        JP_FREQ[35][73] = 500; JP_FREQ[17][82] = 499; JP_FREQ[45][8]  = 498; JP_FREQ[27][73] = 497; JP_FREQ[18][55] = 496;\n        JP_FREQ[25][2]  = 495; JP_FREQ[3][26]  = 494; JP_FREQ[45][46] = 493; JP_FREQ[4][22]  = 492; JP_FREQ[4][40]  = 491;\n        JP_FREQ[18][10] = 490; JP_FREQ[32][9]  = 489; JP_FREQ[26][49] = 488; JP_FREQ[3][47]  = 487; JP_FREQ[24][65] = 486;\n        JP_FREQ[4][76]  = 485; JP_FREQ[43][67] = 484; JP_FREQ[3][9]   = 483; JP_FREQ[41][37] = 482; JP_FREQ[33][68] = 481;\n        JP_FREQ[43][31] = 480; JP_FREQ[19][55] = 479; JP_FREQ[4][30]  = 478; JP_FREQ[27][33] = 477; JP_FREQ[16][62] = 476;\n        JP_FREQ[36][35] = 475; JP_FREQ[37][15] = 474; JP_FREQ[27][70] = 473; JP_FREQ[22][71] = 472; JP_FREQ[33][45] = 471;\n        JP_FREQ[31][78] = 470; JP_FREQ[43][59] = 469; JP_FREQ[32][19] = 468; JP_FREQ[17][28] = 467; JP_FREQ[40][28] = 466;\n        JP_FREQ[20][93] = 465; JP_FREQ[18][15] = 464; JP_FREQ[4][23]  = 463; JP_FREQ[3][23]  = 462; JP_FREQ[26][64] = 461;\n        JP_FREQ[44][92] = 460; JP_FREQ[17][27] = 459; JP_FREQ[3][56]  = 458; JP_FREQ[25][38] = 457; JP_FREQ[23][31] = 456;\n        JP_FREQ[35][43] = 455; JP_FREQ[4][54]  = 454; JP_FREQ[35][19] = 453; JP_FREQ[22][47] = 452; JP_FREQ[42][0]  = 451;\n        JP_FREQ[23][28] = 450; JP_FREQ[46][33] = 449; JP_FREQ[36][85] = 448; JP_FREQ[31][12] = 447; JP_FREQ[3][76]  = 446;\n        JP_FREQ[4][75]  = 445; JP_FREQ[36][56] = 444; JP_FREQ[4][64]  = 443; JP_FREQ[25][77] = 442; JP_FREQ[15][52] = 441;\n        JP_FREQ[33][73] = 440; JP_FREQ[3][55]  = 439; JP_FREQ[43][82] = 438; JP_FREQ[27][82] = 437; JP_FREQ[20][3]  = 436;\n        JP_FREQ[40][51] = 435; JP_FREQ[3][17]  = 434; JP_FREQ[27][71] = 433; JP_FREQ[4][52]  = 432; JP_FREQ[44][48] = 431;\n        JP_FREQ[27][2]  = 430; JP_FREQ[17][39] = 429; JP_FREQ[31][8]  = 428; JP_FREQ[44][54] = 427; JP_FREQ[43][18] = 426;\n        JP_FREQ[43][77] = 425; JP_FREQ[4][61]  = 424; JP_FREQ[19][91] = 423; JP_FREQ[31][13] = 422; JP_FREQ[44][71] = 421;\n        JP_FREQ[20][0]  = 420; JP_FREQ[23][87] = 419; JP_FREQ[21][14] = 418; JP_FREQ[29][13] = 417; JP_FREQ[3][58]  = 416;\n        JP_FREQ[26][18] = 415; JP_FREQ[4][47]  = 414; JP_FREQ[4][18]  = 413; JP_FREQ[3][53]  = 412; JP_FREQ[26][92] = 411;\n        JP_FREQ[21][7]  = 410; JP_FREQ[4][37]  = 409; JP_FREQ[4][63]  = 408; JP_FREQ[36][51] = 407; JP_FREQ[4][32]  = 406;\n        JP_FREQ[28][73] = 405; JP_FREQ[4][50]  = 404; JP_FREQ[41][60] = 403; JP_FREQ[23][1]  = 402; JP_FREQ[36][92] = 401;\n        JP_FREQ[15][41] = 400; JP_FREQ[21][71] = 399; JP_FREQ[41][30] = 398; JP_FREQ[32][76] = 397; JP_FREQ[17][34] = 396;\n        JP_FREQ[26][15] = 395; JP_FREQ[26][25] = 394; JP_FREQ[31][77] = 393; JP_FREQ[31][3]  = 392; JP_FREQ[46][34] = 391;\n        JP_FREQ[27][84] = 390; JP_FREQ[23][8]  = 389; JP_FREQ[16][0]  = 388; JP_FREQ[28][80] = 387; JP_FREQ[26][54] = 386;\n        JP_FREQ[33][18] = 385; JP_FREQ[31][20] = 384; JP_FREQ[31][62] = 383; JP_FREQ[30][41] = 382; JP_FREQ[33][30] = 381;\n        JP_FREQ[45][45] = 380; JP_FREQ[37][82] = 379; JP_FREQ[15][33] = 378; JP_FREQ[20][12] = 377; JP_FREQ[18][5]  = 376;\n        JP_FREQ[28][86] = 375; JP_FREQ[30][19] = 374; JP_FREQ[42][43] = 373; JP_FREQ[36][31] = 372; JP_FREQ[17][93] = 371;\n        JP_FREQ[4][15]  = 370; JP_FREQ[21][20] = 369; JP_FREQ[23][21] = 368; JP_FREQ[28][72] = 367; JP_FREQ[4][20]  = 366;\n        JP_FREQ[26][55] = 365; JP_FREQ[21][5]  = 364; JP_FREQ[19][16] = 363; JP_FREQ[23][64] = 362; JP_FREQ[40][59] = 361;\n        JP_FREQ[37][26] = 360; JP_FREQ[26][56] = 359; JP_FREQ[4][12]  = 358; JP_FREQ[33][71] = 357; JP_FREQ[32][39] = 356;\n        JP_FREQ[38][40] = 355; JP_FREQ[22][74] = 354; JP_FREQ[3][25]  = 353; JP_FREQ[15][48] = 352; JP_FREQ[41][82] = 351;\n        JP_FREQ[41][9]  = 350; JP_FREQ[25][48] = 349; JP_FREQ[31][71] = 348; JP_FREQ[43][29] = 347; JP_FREQ[26][80] = 346;\n        JP_FREQ[4][5]   = 345; JP_FREQ[18][71] = 344; JP_FREQ[29][0]  = 343; JP_FREQ[43][43] = 342; JP_FREQ[23][81] = 341;\n        JP_FREQ[4][42]  = 340; JP_FREQ[44][28] = 339; JP_FREQ[23][93] = 338; JP_FREQ[17][81] = 337; JP_FREQ[25][25] = 336;\n        JP_FREQ[41][23] = 335; JP_FREQ[34][35] = 334; JP_FREQ[4][53]  = 333; JP_FREQ[28][36] = 332; JP_FREQ[4][41]  = 331;\n        JP_FREQ[25][60] = 330; JP_FREQ[23][20] = 329; JP_FREQ[3][43]  = 328; JP_FREQ[24][79] = 327; JP_FREQ[29][41] = 326;\n        JP_FREQ[30][83] = 325; JP_FREQ[3][50]  = 324; JP_FREQ[22][18] = 323; JP_FREQ[18][3]  = 322; JP_FREQ[39][30] = 321;\n        JP_FREQ[4][28]  = 320; JP_FREQ[21][64] = 319; JP_FREQ[4][68]  = 318; JP_FREQ[17][71] = 317; JP_FREQ[27][0]  = 316;\n        JP_FREQ[39][28] = 315; JP_FREQ[30][13] = 314; JP_FREQ[36][70] = 313; JP_FREQ[20][82] = 312; JP_FREQ[33][38] = 311;\n        JP_FREQ[44][87] = 310; JP_FREQ[34][45] = 309; JP_FREQ[4][26]  = 308; JP_FREQ[24][44] = 307; JP_FREQ[38][67] = 306;\n        JP_FREQ[38][6]  = 305; JP_FREQ[30][68] = 304; JP_FREQ[15][89] = 303; JP_FREQ[24][93] = 302; JP_FREQ[40][41] = 301;\n        JP_FREQ[38][3]  = 300; JP_FREQ[28][23] = 299; JP_FREQ[26][17] = 298; JP_FREQ[4][38]  = 297; JP_FREQ[22][78] = 296;\n        JP_FREQ[15][37] = 295; JP_FREQ[25][85] = 294; JP_FREQ[4][9]   = 293; JP_FREQ[4][7]   = 292; JP_FREQ[27][53] = 291;\n        JP_FREQ[39][29] = 290; JP_FREQ[41][43] = 289; JP_FREQ[25][62] = 288; JP_FREQ[4][48]  = 287; JP_FREQ[28][28] = 286;\n        JP_FREQ[21][40] = 285; JP_FREQ[36][73] = 284; JP_FREQ[26][39] = 283; JP_FREQ[22][54] = 282; JP_FREQ[33][5]  = 281;\n        JP_FREQ[19][21] = 280; JP_FREQ[46][31] = 279; JP_FREQ[20][64] = 278; JP_FREQ[26][63] = 277; JP_FREQ[22][23] = 276;\n        JP_FREQ[25][81] = 275; JP_FREQ[4][62]  = 274; JP_FREQ[37][31] = 273; JP_FREQ[40][52] = 272; JP_FREQ[29][79] = 271;\n        JP_FREQ[41][48] = 270; JP_FREQ[31][57] = 269; JP_FREQ[32][92] = 268; JP_FREQ[36][36] = 267; JP_FREQ[27][7]  = 266;\n        JP_FREQ[35][29] = 265; JP_FREQ[37][34] = 264; JP_FREQ[34][42] = 263; JP_FREQ[27][15] = 262; JP_FREQ[33][27] = 261;\n        JP_FREQ[31][38] = 260; JP_FREQ[19][79] = 259; JP_FREQ[4][31]  = 258; JP_FREQ[4][66]  = 257; JP_FREQ[17][32] = 256;\n        JP_FREQ[26][67] = 255; JP_FREQ[16][30] = 254; JP_FREQ[26][46] = 253; JP_FREQ[24][26] = 252; JP_FREQ[35][10] = 251;\n        JP_FREQ[18][37] = 250; JP_FREQ[3][19]  = 249; JP_FREQ[33][69] = 248; JP_FREQ[31][9]  = 247; JP_FREQ[45][29] = 246;\n        JP_FREQ[3][15]  = 245; JP_FREQ[18][54] = 244; JP_FREQ[3][44]  = 243; JP_FREQ[31][29] = 242; JP_FREQ[18][45] = 241;\n        JP_FREQ[38][28] = 240; JP_FREQ[24][12] = 239; JP_FREQ[35][82] = 238; JP_FREQ[17][43] = 237; JP_FREQ[28][9]  = 236;\n        JP_FREQ[23][25] = 235; JP_FREQ[44][37] = 234; JP_FREQ[23][75] = 233; JP_FREQ[23][92] = 232; JP_FREQ[0][24]  = 231;\n        JP_FREQ[19][74] = 230; JP_FREQ[45][32] = 229; JP_FREQ[16][72] = 228; JP_FREQ[16][93] = 227; JP_FREQ[45][13] = 226;\n        JP_FREQ[24][8]  = 225; JP_FREQ[25][47] = 224; JP_FREQ[28][26] = 223; JP_FREQ[43][81] = 222; JP_FREQ[32][71] = 221;\n        JP_FREQ[18][41] = 220; JP_FREQ[26][62] = 219; JP_FREQ[41][24] = 218; JP_FREQ[40][11] = 217; JP_FREQ[43][57] = 216;\n        JP_FREQ[34][53] = 215; JP_FREQ[20][32] = 214; JP_FREQ[34][43] = 213; JP_FREQ[41][91] = 212; JP_FREQ[29][57] = 211;\n        JP_FREQ[15][43] = 210; JP_FREQ[22][89] = 209; JP_FREQ[33][83] = 208; JP_FREQ[43][20] = 207; JP_FREQ[25][58] = 206;\n        JP_FREQ[30][30] = 205; JP_FREQ[4][56]  = 204; JP_FREQ[17][64] = 203; JP_FREQ[23][0]  = 202; JP_FREQ[44][12] = 201;\n        JP_FREQ[25][37] = 200; JP_FREQ[35][13] = 199; JP_FREQ[20][30] = 198; JP_FREQ[21][84] = 197; JP_FREQ[29][14] = 196;\n        JP_FREQ[30][5]  = 195; JP_FREQ[37][2]  = 194; JP_FREQ[4][78]  = 193; JP_FREQ[29][78] = 192; JP_FREQ[29][84] = 191;\n        JP_FREQ[32][86] = 190; JP_FREQ[20][68] = 189; JP_FREQ[30][39] = 188; JP_FREQ[15][69] = 187; JP_FREQ[4][60]  = 186;\n        JP_FREQ[20][61] = 185; JP_FREQ[41][67] = 184; JP_FREQ[16][35] = 183; JP_FREQ[36][57] = 182; JP_FREQ[39][80] = 181;\n        JP_FREQ[4][59]  = 180; JP_FREQ[4][44]  = 179; JP_FREQ[40][54] = 178; JP_FREQ[30][8]  = 177; JP_FREQ[44][30] = 176;\n        JP_FREQ[31][93] = 175; JP_FREQ[31][47] = 174; JP_FREQ[16][70] = 173; JP_FREQ[21][0]  = 172; JP_FREQ[17][35] = 171;\n        JP_FREQ[21][67] = 170; JP_FREQ[44][18] = 169; JP_FREQ[36][29] = 168; JP_FREQ[18][67] = 167; JP_FREQ[24][28] = 166;\n        JP_FREQ[36][24] = 165; JP_FREQ[23][5]  = 164; JP_FREQ[31][65] = 163; JP_FREQ[26][59] = 162; JP_FREQ[28][2]  = 161;\n        JP_FREQ[39][69] = 160; JP_FREQ[42][40] = 159; JP_FREQ[37][80] = 158; JP_FREQ[15][66] = 157; JP_FREQ[34][38] = 156;\n        JP_FREQ[28][48] = 155; JP_FREQ[37][77] = 154; JP_FREQ[29][34] = 153; JP_FREQ[33][12] = 152; JP_FREQ[4][65]  = 151;\n        JP_FREQ[30][31] = 150; JP_FREQ[27][92] = 149; JP_FREQ[4][2]   = 148; JP_FREQ[4][51]  = 147; JP_FREQ[23][77] = 146;\n        JP_FREQ[4][35]  = 145; JP_FREQ[3][13]  = 144; JP_FREQ[26][26] = 143; JP_FREQ[44][4]  = 142; JP_FREQ[39][53] = 141;\n        JP_FREQ[20][11] = 140; JP_FREQ[40][33] = 139; JP_FREQ[45][7]  = 138; JP_FREQ[4][70]  = 137; JP_FREQ[3][49]  = 136;\n        JP_FREQ[20][59] = 135; JP_FREQ[21][12] = 134; JP_FREQ[33][53] = 133; JP_FREQ[20][14] = 132; JP_FREQ[37][18] = 131;\n        JP_FREQ[18][17] = 130; JP_FREQ[36][23] = 129; JP_FREQ[18][57] = 128; JP_FREQ[26][74] = 127; JP_FREQ[35][2]  = 126;\n        JP_FREQ[38][58] = 125; JP_FREQ[34][68] = 124; JP_FREQ[29][81] = 123; JP_FREQ[20][69] = 122; JP_FREQ[39][86] = 121;\n        JP_FREQ[4][16]  = 120; JP_FREQ[16][49] = 119; JP_FREQ[15][72] = 118; JP_FREQ[26][35] = 117; JP_FREQ[32][14] = 116;\n        JP_FREQ[40][90] = 115; JP_FREQ[33][79] = 114; JP_FREQ[35][4]  = 113; JP_FREQ[23][33] = 112; JP_FREQ[19][19] = 111;\n        JP_FREQ[31][41] = 110; JP_FREQ[44][1]  = 109; JP_FREQ[22][56] = 108; JP_FREQ[31][27] = 107; JP_FREQ[32][18] = 106;\n        JP_FREQ[27][32] = 105; JP_FREQ[37][39] = 104; JP_FREQ[42][11] = 103; JP_FREQ[29][71] = 102; JP_FREQ[32][58] = 101;\n        JP_FREQ[46][10] = 100; JP_FREQ[17][30] =  99; JP_FREQ[38][15] =  98; JP_FREQ[29][60] =  97; JP_FREQ[4][11]  =  96;\n        JP_FREQ[38][31] =  95; JP_FREQ[40][79] =  94; JP_FREQ[28][49] =  93; JP_FREQ[28][84] =  92; JP_FREQ[26][77] =  91;\n        JP_FREQ[22][32] =  90; JP_FREQ[33][17] =  89; JP_FREQ[23][18] =  88; JP_FREQ[32][64] =  87; JP_FREQ[4][6]   =  86;\n        JP_FREQ[33][51] =  85; JP_FREQ[44][77] =  84; JP_FREQ[29][5]  =  83; JP_FREQ[46][25] =  82; JP_FREQ[19][58] =  81;\n        JP_FREQ[4][46]  =  80; JP_FREQ[15][71] =  79; JP_FREQ[18][58] =  78; JP_FREQ[26][45] =  77; JP_FREQ[45][66] =  76;\n        JP_FREQ[34][10] =  75; JP_FREQ[19][37] =  74; JP_FREQ[33][65] =  73; JP_FREQ[44][52] =  72; JP_FREQ[16][38] =  71;\n        JP_FREQ[36][46] =  70; JP_FREQ[20][26] =  69; JP_FREQ[30][37] =  68; JP_FREQ[4][58]  =  67; JP_FREQ[43][2]  =  66;\n        JP_FREQ[30][18] =  65; JP_FREQ[19][35] =  64; JP_FREQ[15][68] =  63; JP_FREQ[3][36]  =  62; JP_FREQ[35][40] =  61;\n        JP_FREQ[36][32] =  60; JP_FREQ[37][14] =  59; JP_FREQ[17][11] =  58; JP_FREQ[19][78] =  57; JP_FREQ[37][11] =  56;\n        JP_FREQ[28][63] =  55; JP_FREQ[29][61] =  54; JP_FREQ[33][3]  =  53; JP_FREQ[41][52] =  52; JP_FREQ[33][63] =  51;\n        JP_FREQ[22][41] =  50; JP_FREQ[4][19]  =  49; JP_FREQ[32][41] =  48; JP_FREQ[24][4]  =  47; JP_FREQ[31][28] =  46;\n        JP_FREQ[43][30] =  45; JP_FREQ[17][3]  =  44; JP_FREQ[43][70] =  43; JP_FREQ[34][19] =  42; JP_FREQ[20][77] =  41;\n        JP_FREQ[18][83] =  40; JP_FREQ[17][15] =  39; JP_FREQ[23][61] =  38; JP_FREQ[40][27] =  37; JP_FREQ[16][48] =  36;\n        JP_FREQ[39][78] =  35; JP_FREQ[41][53] =  34; JP_FREQ[40][91] =  33; JP_FREQ[40][72] =  32; JP_FREQ[18][52] =  31;\n        JP_FREQ[35][66] =  30; JP_FREQ[39][93] =  29; JP_FREQ[19][48] =  28; JP_FREQ[26][36] =  27; JP_FREQ[27][25] =  26;\n        JP_FREQ[42][71] =  25; JP_FREQ[42][85] =  24; JP_FREQ[26][48] =  23; JP_FREQ[28][15] =  22; JP_FREQ[3][66]  =  21;\n        JP_FREQ[25][24] =  20; JP_FREQ[27][43] =  19; JP_FREQ[27][78] =  18; JP_FREQ[45][43] =  17; JP_FREQ[27][72] =  16;\n        JP_FREQ[40][29] =  15; JP_FREQ[41][0]  =  14; JP_FREQ[19][57] =  13; JP_FREQ[15][59] =  12; JP_FREQ[29][29] =  11;\n        JP_FREQ[4][25]  =  10; JP_FREQ[21][42] =   9; JP_FREQ[23][35] =   8; JP_FREQ[33][1]  =   7; JP_FREQ[4][57]  =   6;\n        JP_FREQ[17][60] =   5; JP_FREQ[25][19] =   4; JP_FREQ[22][65] =   3; JP_FREQ[42][29] =   2; JP_FREQ[27][66] =   1;\n        JP_FREQ[26][89] =   0;\n    }\n\n    private static class Encoding {\n        private static final int TOTAL_TYPES = 23;\n\n        // Supported Encoding Types\n        private static final int GB2312        = 0;  // 1980年中国发布了第一个汉字编码标准\n        private static final int GBK           = 1;  // 在GB2312的基础上添加了部分字符(如GB2312发布后才简化的汉字、部分缺少的人名/繁体字/日语/朝鲜语中的汉字)\n        private static final int GB18030       = 2;  // 全称《信息技术中文编码字符集》，在GBK的基础上增加了中日韩语中的汉字和少数名族的文字及字符\n        private static final int HZ            = 3;\n        private static final int BIG5          = 4;\n        private static final int CNS11643      = 5;\n        private static final int UTF8          = 6;\n        private static final int UTF8T         = 7;\n        private static final int UTF8S         = 8;\n        private static final int UNICODE       = 9;\n        private static final int UNICODET      = 10;\n        private static final int UNICODES      = 11;\n        private static final int ISO2022CN     = 12;\n        private static final int ISO2022CN_CNS = 13;\n        private static final int ISO2022CN_GB  = 14;\n        private static final int EUC_KR        = 15;\n        private static final int CP949         = 16;\n        private static final int ISO2022KR     = 17;\n        private static final int JOHAB         = 18;\n        private static final int SJIS          = 19;\n        private static final int EUC_JP        = 20;\n        private static final int ISO2022JP     = 21;\n        private static final int ASCII         = 22;\n\n        private static final String[] JAVA_CHARSET = new String[TOTAL_TYPES]; // Names of the encodings as understood by Java\n        private static final String[] NICE_CHARSET = new String[TOTAL_TYPES]; // Names of the encodings for human viewing\n        private static final String[] HTML_CHARSET = new String[TOTAL_TYPES]; // Names of charsets as used in charset parameter of HTML Meta tag\n        static {\n            // Assign encoding names\n            JAVA_CHARSET[GB2312]        = \"GB2312\";\n            JAVA_CHARSET[GBK]           = \"GBK\";\n            JAVA_CHARSET[GB18030]       = \"GB18030\";\n            JAVA_CHARSET[HZ]            = \"ASCII\"; // What to put here? Sun doesn't support HZ\n            JAVA_CHARSET[ISO2022CN_GB]  = \"ISO2022CN_GB\";\n            JAVA_CHARSET[BIG5]          = \"Big5\";\n            JAVA_CHARSET[CNS11643]      = \"EUC-TW\";\n            JAVA_CHARSET[ISO2022CN_CNS] = \"ISO2022CN_CNS\";\n            JAVA_CHARSET[ISO2022CN]     = \"ISO2022CN\";\n            JAVA_CHARSET[UTF8]          = \"UTF-8\";\n            JAVA_CHARSET[UTF8T]         = \"UTF-8\";\n            JAVA_CHARSET[UTF8S]         = \"UTF-8\";\n            JAVA_CHARSET[UNICODE]       = \"Unicode\";\n            JAVA_CHARSET[UNICODET]      = \"Unicode\";\n            JAVA_CHARSET[UNICODES]      = \"Unicode\";\n            JAVA_CHARSET[EUC_KR]        = \"EUC-KR\";\n            JAVA_CHARSET[CP949]         = \"MS949\";\n            JAVA_CHARSET[ISO2022KR]     = \"ISO2022KR\";\n            JAVA_CHARSET[JOHAB]         = \"Johab\";\n            JAVA_CHARSET[SJIS]          = \"SJIS\";\n            JAVA_CHARSET[EUC_JP]        = \"EUC_JP\";\n            JAVA_CHARSET[ISO2022JP]     = \"ISO2022JP\";\n            JAVA_CHARSET[ASCII]         = \"ASCII\";\n\n            // Assign encoding names\n            HTML_CHARSET[GB2312]        = \"GB2312\";\n            HTML_CHARSET[GBK]           = \"GBK\";\n            HTML_CHARSET[GB18030]       = \"GB18030\";\n            HTML_CHARSET[HZ]            = \"HZ-GB-2312\";\n            HTML_CHARSET[ISO2022CN_GB]  = \"ISO-2022-CN-EXT\";\n            HTML_CHARSET[BIG5]          = \"Big5\";\n            HTML_CHARSET[CNS11643]      = \"EUC-TW\";\n            HTML_CHARSET[ISO2022CN_CNS] = \"ISO-2022-CN-EXT\";\n            HTML_CHARSET[ISO2022CN]     = \"ISO-2022-CN\";\n            HTML_CHARSET[UTF8]          = \"UTF-8\";\n            HTML_CHARSET[UTF8T]         = \"UTF-8\";\n            HTML_CHARSET[UTF8S]         = \"UTF-8\";\n            HTML_CHARSET[UNICODE]       = \"UTF-16\";\n            HTML_CHARSET[UNICODET]      = \"UTF-16\";\n            HTML_CHARSET[UNICODES]      = \"UTF-16\";\n            HTML_CHARSET[EUC_KR]        = \"EUC-KR\";\n            HTML_CHARSET[CP949]         = \"x-windows-949\";\n            HTML_CHARSET[ISO2022KR]     = \"ISO-2022-KR\";\n            HTML_CHARSET[JOHAB]         = \"x-Johab\";\n            HTML_CHARSET[SJIS]          = \"Shift_JIS\";\n            HTML_CHARSET[EUC_JP]        = \"EUC-JP\";\n            HTML_CHARSET[ISO2022JP]     = \"ISO-2022-JP\";\n            HTML_CHARSET[ASCII]         = \"ASCII\";\n\n            // Assign Human readable names\n            NICE_CHARSET[GB2312]        = \"GB-2312\";\n            NICE_CHARSET[GBK]           = \"GBK\";\n            NICE_CHARSET[GB18030]       = \"GB18030\";\n            NICE_CHARSET[HZ]            = \"HZ\";\n            NICE_CHARSET[ISO2022CN_GB]  = \"ISO2022CN-GB\";\n            NICE_CHARSET[BIG5]          = \"Big5\";\n            NICE_CHARSET[CNS11643]      = \"CNS11643\";\n            NICE_CHARSET[ISO2022CN_CNS] = \"ISO2022CN-CNS\";\n            NICE_CHARSET[ISO2022CN]     = \"ISO2022 CN\";\n            NICE_CHARSET[UTF8]          = \"UTF-8\";\n            NICE_CHARSET[UTF8T]         = \"UTF-8 (Trad)\";\n            NICE_CHARSET[UTF8S]         = \"UTF-8 (Simp)\";\n            NICE_CHARSET[UNICODE]       = \"Unicode\";\n            NICE_CHARSET[UNICODET]      = \"Unicode (Trad)\";\n            NICE_CHARSET[UNICODES]      = \"Unicode (Simp)\";\n            NICE_CHARSET[EUC_KR]        = \"EUC-KR\";\n            NICE_CHARSET[CP949]         = \"CP949\";\n            NICE_CHARSET[ISO2022KR]     = \"ISO 2022 KR\";\n            NICE_CHARSET[JOHAB]         = \"Johab\";\n            NICE_CHARSET[SJIS]          = \"Shift-JIS\";\n            NICE_CHARSET[EUC_JP]        = \"EUC-JP\";\n            NICE_CHARSET[ISO2022JP]     = \"ISO 2022 JP\";\n            NICE_CHARSET[ASCII]         = \"ASCII\";\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/charset/CodepageDetector.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n//package cn.ponfee.commons.io.charset;\n//\n//import cn.ponfee.commons.io.CharsetDetector;\n//import info.monitorenter.cpdetector.io.*;\n//\n//import java.io.BufferedInputStream;\n//import java.io.IOException;\n//import java.io.InputStream;\n//import java.nio.charset.Charset;\n//\n///**\n// * <pre>\n// *  <!-- https://nexus.nuiton.org/nexus/content/repositories/thirdparty/ -->\n// *  <dependency>\n// *    <groupId>net.sourceforge.cpdetector</groupId>\n// *    <artifactId>cpdetector</artifactId>\n// *    <version>1.0.7</version>\n// *  </dependency>\n// *\n// *  <!-- 自动依赖了antlr:antlr:2.7.7，排除则会报错\"java.lang.NoClassDefFoundError: antlr/ANTLRException\" -->\n// *  <dependency>\n// *    <groupId>antlr</groupId>\n// *    <artifactId>antlr</artifactId>\n// *    <version>2.7.7</version>\n// *  </dependency>\n// * </pre>\n// *\n// * @author Ponfee\n// */\n//public class CodepageDetector {\n//\n//    public static Charset detect(InputStream input, int length) throws IOException {\n//        CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance();\n//        detector.add(new ByteOrderMarkDetector());   // 通过BOM来测定编码\n//        detector.add(JChardetFacade.getInstance());  // 封装了由Mozilla提供的JChardet\n//        detector.add(UnicodeDetector.getInstance()); // 用于Unicode家族编码的测定\n//        detector.add(ASCIIDetector.getInstance());   // 用于ASCII编码测定\n//        detector.add(new ParsingDetector(false));    // 用于检查HTML、XML等文件或字符流的编码\n//        input = input.markSupported() ? input : new BufferedInputStream(input, length);\n//        String charset = detector.detectCodepage(input, length).name();\n//        return \"void\".equalsIgnoreCase(charset) ? CharsetDetector.DEFAULT_CHARSET : Charset.forName(charset);\n//    }\n//\n//}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/charset/JchardetDetector.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n//package cn.ponfee.commons.io.charset;\n//\n//import cn.ponfee.commons.io.CharsetDetector;\n//import org.apache.commons.lang3.StringUtils;\n//import org.mozilla.intl.chardet.nsDetector;\n//import org.mozilla.intl.chardet.nsICharsetDetectionObserver;\n//\n//import java.io.BufferedInputStream;\n//import java.io.IOException;\n//import java.io.InputStream;\n//import java.nio.charset.Charset;\n//import java.util.Arrays;\n//\n///**\n// * <pre>\n// *  <!-- https://nexus.nuiton.org/nexus/content/repositories/central/ -->\n// *  <dependency>\n// *    <groupId>net.sourceforge.jchardet</groupId>\n// *    <artifactId>jchardet</artifactId>\n// *    <version>1.0</version>\n// *  </dependency>\n// * </pre>\n// *\n// * @author Ponfee\n// */\n//public class JchardetDetector {\n//\n//    public static Charset detect(InputStream input, int length) throws IOException {\n//        nsDetector detector = new nsDetector(nsDetector.ALL);\n//        DetectorObserver observer = new DetectorObserver();\n//        detector.Init(observer);\n//        try (BufferedInputStream bufInput = new BufferedInputStream(input)) {\n//            byte[] buf = new byte[length];\n//            boolean isAscii = true;\n//            int len, count = 0;\n//            while ((len = bufInput.read(buf, 0, buf.length)) != -1) {\n//                if (isAscii) {\n//                    isAscii = detector.isAscii(buf, len);\n//                }\n//                if (!isAscii && detector.DoIt(buf, len, false)) {\n//                    break;\n//                }\n//                count += len;\n//                if (count >= length) {\n//                    break;\n//                }\n//            }\n//            detector.DataEnd();\n//\n//            if (isAscii) {\n//                return CharsetDetector.DEFAULT_CHARSET;\n//            } else if (observer.result != null) {\n//                return Charset.forName(observer.result);\n//            } else {\n//                String[] probableCharsets = detector.getProbableCharsets();\n//                String probableCharset = Arrays.stream(probableCharsets)\n//                    .filter(s -> !StringUtils.startsWithAny(s, \"UTF-16\", \"UTF-32\", \"GB18030\"))\n//                    .findAny()\n//                    .orElse(probableCharsets[0]);\n//                return Charset.forName(probableCharset);\n//            }\n//        }\n//    }\n//\n//    private static class DetectorObserver implements nsICharsetDetectionObserver {\n//        private String result = null;\n//\n//        @Override\n//        public void Notify(String charset) {\n//            this.result = charset;\n//        }\n//    }\n//\n//}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/io/charset/TikaDetector.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io.charset;\n\nimport cn.ponfee.commons.io.CharsetDetector;\nimport cn.ponfee.commons.io.Files;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\n\n/**\n * <pre>\n *  <!-- dependencyManagement -->\n *  <dependency>\n *    <groupId>org.apache.tika</groupId>\n *    <artifactId>tika-bom</artifactId>\n *    <version>2.6.0</version>\n *    <type>pom</type>\n *    <scope>import</scope>\n *  </dependency>\n *\n *  <!-- dependencies -->\n *  <dependency>\n *    <groupId>org.apache.tika</groupId>\n *    <artifactId>tika-parsers-standard-package</artifactId>\n *  </dependency>\n * </pre>\n *\n * @author Ponfee\n */\npublic class TikaDetector {\n\n    public static Charset detect(InputStream input, int length) throws IOException {\n        org.apache.tika.parser.txt.CharsetDetector charsetDetector = new org.apache.tika.parser.txt.CharsetDetector();\n        charsetDetector.setText(Files.readByteArray(input, length));\n        org.apache.tika.parser.txt.CharsetMatch charsetMatch = charsetDetector.detect();\n        return charsetMatch == null ? CharsetDetector.DEFAULT_CHARSET : Charset.forName(charsetMatch.getName());\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/CryptoProvider.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.security.ECDSASigner;\nimport cn.ponfee.commons.jce.security.RSACryptor;\nimport cn.ponfee.commons.jce.security.RSAPrivateKeys;\nimport cn.ponfee.commons.jce.security.RSAPublicKeys;\nimport cn.ponfee.commons.jce.sm.SM2;\nimport cn.ponfee.commons.jce.symmetric.SymmetricCryptor;\nimport cn.ponfee.commons.util.Base64UrlSafe;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.annotation.Nonnull;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.security.interfaces.ECPrivateKey;\nimport java.security.interfaces.ECPublicKey;\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\nimport java.util.Arrays;\nimport java.util.Objects;\n\n/**\n * 加解密服务提供\n * \n * @author Ponfee\n */\npublic abstract class CryptoProvider {\n\n    /**\n     * Encrypts data\n     * \n     * @param original  the origin data\n     * @return encrypted data\n     */\n    public abstract byte[] encrypt(@Nonnull byte[] original);\n\n    /**\n     * Decrypts data\n     * \n     * @param encrypted  the encrypted data\n     * @return origin data\n     */\n    public abstract byte[] decrypt(@Nonnull byte[] encrypted);\n\n    /**\n     * Signs the data\n     * \n     * @param data  the data\n     * @return signature data\n     */\n    public byte[] sign(byte[] data) {\n        throw new UnsupportedOperationException(\"cannot support signature.\");\n    }\n\n    /**\n     * Verify the data signature\n     * \n     * @param data   the origin data\n     * @param signed the signed data\n     * @return {@code true} verify success\n     */\n    public boolean verify(byte[] data, byte[] signed) {\n        throw new UnsupportedOperationException(\"cannot support verify signature.\");\n    }\n\n    /**\n     * Encrypts the string data\n     * \n     * @param plaintext  the plain text\n     * @return encrypted data\n     */\n    public final String encrypt(String plaintext) {\n        return encrypt(plaintext, StandardCharsets.UTF_8);\n    }\n\n    /**\n     * 字符串数据加密\n     * \n     * @param plaintext 明文\n     * @param charset   字符串编码\n     * @return encrypted data\n     */\n    public final String encrypt(String plaintext, Charset charset) {\n        if (plaintext == null) {\n            return null;\n        }\n\n        return Base64UrlSafe.encode(\n            this.encrypt(plaintext.getBytes(charset))\n        );\n    }\n\n    /**\n     * Decrypts data\n     * \n     * @param ciphertext  the encryted data of base64 string\n     * @return origin data of string\n     */\n    public final String decrypt(String ciphertext) {\n        return decrypt(ciphertext, StandardCharsets.UTF_8);\n    }\n\n    /**\n     * Decrypts data\n     * \n     * @param ciphertext  the encryted data of base64 string\n     * @param charset     the origin data charset\n     * @return origin data of string\n     */\n    public final String decrypt(String ciphertext, Charset charset) {\n        if (ciphertext == null) {\n            return null;\n        }\n\n        return new String(\n            decrypt(Base64UrlSafe.decode(ciphertext)), \n            charset\n        );\n    }\n\n    /**\n     * Signs data\n     * \n     * @param data  the data\n     * @return signed data\n     */\n    public final String sign(String data) {\n        return sign(data, Files.UTF_8);\n    }\n\n    /**\n     * Signs data\n     * \n     * @param data  the string data\n     * @param charset the charset of string data\n     * @return signed data\n     */\n    public final String sign(String data, String charset) {\n        if (StringUtils.isEmpty(data)) {\n            return null;\n        }\n        return Base64UrlSafe.encode(\n            sign(data.getBytes(Charset.forName(charset)))\n        );\n    }\n\n    /**\n     * Verifys the data\n     * \n     * @param data the origin data\n     * @param signed the signed data\n     * @return {@code true} verify success\n     */\n    public final boolean verify(String data, String signed) {\n        return verify(data, Files.UTF_8, signed);\n    }\n\n    /**\n     * Verifys the data\n     * \n     * @param data  the data\n     * @param charset the charset\n     * @param signed the signed data\n     * @return {@code true} verify success\n     */\n    public final boolean verify(String data, String charset, String signed) {\n        return verify(\n             data.getBytes(Charset.forName(charset)), \n             Base64UrlSafe.decode(signed)\n        );\n    }\n\n    // -----------------------------------------------------------------------SymmetricCryptor\n    /**\n     * 对称密钥组件\n     * @param symmetricKey {@link SymmetricCryptor}\n     * @return\n     */\n    public static CryptoProvider symmetricKeyProvider(SymmetricCryptor symmetricKey) {\n        // the symmetricKey is thread-safe\n        return new CryptoProvider() {\n            @Override\n            public byte[] encrypt(byte[] original) {\n                return symmetricKey.encrypt(\n                    Objects.requireNonNull(original)\n                );\n            }\n\n            @Override\n            public byte[] decrypt(byte[] encrypted) {\n                return symmetricKey.decrypt(encrypted);\n            }\n        };\n    }\n\n    // -----------------------------------------------------------------------RSA\n    /**\n     * rsa public key密钥组件\n     * \n     * @param pkcs8PublicKey  the string of pkcs8 public key format\n     * @return\n     */\n    public static CryptoProvider rsaPublicKeyProvider(String pkcs8PublicKey) {\n        return new CryptoProvider() {\n            final RSAPublicKey pubKey = RSAPublicKeys.fromPkcs8(pkcs8PublicKey); // thread-safe\n\n            @Override\n            public byte[] encrypt(byte[] original) {\n                return RSACryptor.encrypt(original, pubKey); // 公钥加密\n            }\n\n            @Override\n            public byte[] decrypt(byte[] encrypted) {\n                // cannot support public key decrypt\n                throw new UnsupportedOperationException(\"cannot support decrypt.\");\n            }\n\n            @Override\n            public boolean verify(byte[] data, byte[] signed) {\n                return RSACryptor.verifySha1(data, pubKey, signed);\n            }\n        };\n    }\n\n    /**\n     * pkcs8PrivateKey include public exponent\n     * \n     * forbid use private key encrypt and use public key decrypt\n     * \n     * @param pkcs8PrivateKey  the string of pkcs8 private key format\n     * @return\n     */\n    public static CryptoProvider rsaPrivateKeyProvider(String pkcs8PrivateKey) {\n        RSAPrivateKey priKey = RSAPrivateKeys.fromPkcs8(pkcs8PrivateKey); // thread-safe\n        return rsaProvider(priKey, RSAPrivateKeys.extractPublicKey(priKey));\n    }\n\n    /**\n     * Creates CryptoProvider of RSA \n     * \n     * @param priKey the RSAPrivateKey\n     * @param pubKey the pubKey\n     * @return a CryptoProvider of RSA \n     */\n    public static CryptoProvider rsaProvider(RSAPrivateKey priKey, RSAPublicKey pubKey) {\n        return new CryptoProvider() {\n            @Override\n            public byte[] encrypt(byte[] original) {\n                // only support public key encrypt\n                // forbid encrypt with private key\n                return RSACryptor.encrypt(original, pubKey); // 公钥加密\n            }\n\n            @Override\n            public byte[] decrypt(byte[] encrypted) {\n                // only support private key decrypt\n                return RSACryptor.decrypt(encrypted, priKey); // 私钥解密\n            }\n\n            @Override\n            public byte[] sign(byte[] data) {\n                return RSACryptor.signSha1(data, priKey);\n            }\n\n            @Override\n            public boolean verify(byte[] data, byte[] signed) {\n                return RSACryptor.verifySha1(data, pubKey, signed);\n            }\n        };\n    }\n\n    // -----------------------------------------------------------------------SM2\n    public static CryptoProvider sm2PublicKeyProvider(byte[] publicKey) {\n        return sm2PublicKeyProvider(ECParameters.SM2_BEST, publicKey);\n    }\n\n    public static CryptoProvider sm2PublicKeyProvider(ECParameters ecParameter, \n                                                      byte[] publicKey) {\n        return new CryptoProvider() {\n            final byte[] publicKey0 = Arrays.copyOf(publicKey, publicKey.length);\n\n            @Override\n            public byte[] encrypt(byte[] original) {\n                return SM2.encrypt(ecParameter, publicKey0, original); // 公钥加密\n            }\n\n            @Override\n            public byte[] decrypt(byte[] encrypted) {\n                throw new UnsupportedOperationException(\"cannot support decrypt.\");\n            }\n\n            @Override\n            public boolean verify(byte[] data, byte[] signed) {\n                return SM2.verify(ecParameter, data, signed, publicKey0);\n            }\n        };\n    }\n\n    public static CryptoProvider sm2PrivateKeyProvider(byte[] publicKey, \n                                                       byte[] privateKey) {\n        return sm2PrivateKeyProvider(ECParameters.SM2_BEST, publicKey, privateKey);\n    }\n\n    public static CryptoProvider sm2PrivateKeyProvider(ECParameters ecParameter,\n                                                       byte[] publicKey, \n                                                       byte[] privateKey) {\n        return new CryptoProvider() {\n            final byte[] publicKey0  = Arrays.copyOf(publicKey, publicKey.length);\n            final byte[] privateKey0 = Arrays.copyOf(privateKey, privateKey.length);\n\n            @Override\n            public byte[] encrypt(byte[] original) {\n                return SM2.encrypt(ecParameter, publicKey0, original); // 公钥加密\n            }\n\n            @Override\n            public byte[] decrypt(byte[] encrypted) { // 私钥解密\n                return SM2.decrypt(ecParameter, privateKey0, encrypted);\n            }\n\n            @Override\n            public byte[] sign(byte[] data) { // sign data by SM3WithSM2\n                return SM2.sign(ecParameter, data, publicKey0, privateKey0);\n            }\n\n            @Override\n            public boolean verify(byte[] data, byte[] signed) { // verify the SM3WithSM2 signature\n                return SM2.verify(ecParameter, data, signed, publicKey0);\n            }\n        };\n    }\n\n    // -----------------------------------------------------------------------ECDSASinger\n    public static CryptoProvider ecdsaPublicKeyProvider(byte[] publicKey) {\n        return new CryptoProvider() {\n            final ECPublicKey publicKey0 = ECDSASigner.decodePublicKey(publicKey);\n\n            @Override\n            public byte[] encrypt(byte[] original) {\n                throw new UnsupportedOperationException(\"ECDSA cannot support encrypt.\");\n            }\n\n            @Override\n            public byte[] decrypt(byte[] encrypted) {\n                throw new UnsupportedOperationException(\"ECDSA cannot support decrypt.\");\n            }\n\n            @Override\n            public boolean verify(byte[] data, byte[] signed) {\n                return ECDSASigner.verifySha256(data, signed, publicKey0);\n            }\n        };\n    }\n\n    public static CryptoProvider ecdsaPrivateKeyProvider(byte[] publicKey, byte[] privateKey) {\n        return new CryptoProvider() {\n            final ECPublicKey publicKey0 = ECDSASigner.decodePublicKey(publicKey);\n            final ECPrivateKey privateKey0 = ECDSASigner.decodePrivateKey(privateKey);\n\n            @Override\n            public byte[] encrypt(byte[] original) {\n                throw new UnsupportedOperationException(\"ECDSA cannot support encrypt.\");\n            }\n\n            @Override\n            public byte[] decrypt(byte[] encrypted) {\n                throw new UnsupportedOperationException(\"ECDSA cannot support decrypt.\");\n            }\n\n            @Override\n            public byte[] sign(byte[] data) {\n                return ECDSASigner.signSha256(data, privateKey0);\n            }\n\n            @Override\n            public boolean verify(byte[] data, byte[] signed) {\n                return ECDSASigner.verifySha256(data, signed, publicKey0);\n            }\n        };\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/DigestAlgorithms.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce;\n\n/**\n * The Digest Algorithms<p>\n * \n * SHA-3\n *   <li>https://www.oschina.net/translate/keccak-the-new-sha-3-encryption-standard </li>\n *   <li>http://www.cnblogs.com/dacainiao/p/5554756.html</li>\n *   <li>SHA256算法原理：https://zhuanlan.zhihu.com/p/94619052</li>\n * \n * @author Ponfee\n */\npublic enum DigestAlgorithms {\n\n    MD5(128), // \n\n    RipeMD128(128), RipeMD160(160), RipeMD256(256), RipeMD320(320), // \n\n    SHA1(\"SHA-1\", 160), SHA224(\"SHA-224\", 224), SHA256(\"SHA-256\", 256), // \n    SHA384(\"SHA-384\", 384), SHA512(\"SHA-512\", 512), // \n\n    /**\n     * @see org.bouncycastle.crypto.digests.SM3Digest\n     * @see org.bouncycastle.jcajce.provider.digest.SM3\n     */\n    SM3(256), // \n\n    // SHAKE128 algorithm only support use in org.bouncycastle.crypto.digests.SHAKEDigest\n    //SHAKE128(128), SHAKE256(256),\n\n    // -----------------------SHA-3 Finalists: BLAKE, Grstl, JH, Keccak and Skein\n    /**\n     * @see org.bouncycastle.crypto.digests.Blake2sDigest\n     * @see org.bouncycastle.jcajce.provider.digest.Blake2s\n     */\n    BLAKE2S128(\"BLAKE2S-128\", 128), BLAKE2S160(\"BLAKE2S-160\", 160), //\n    BLAKE2S224(\"BLAKE2S-224\", 224), BLAKE2S256(\"BLAKE2S-256\", 256), //\n\n    /**\n     * @see org.bouncycastle.crypto.digests.Blake2bDigest\n     * @see org.bouncycastle.jcajce.provider.digest.Blake2b\n     */\n    BLAKE2B160(\"BLAKE2B-160\", 160), BLAKE2B256(\"BLAKE2B-256\", 256), //\n    BLAKE2B384(\"BLAKE2B-384\", 384), BLAKE2B512(\"BLAKE2B-512\", 512), //\n\n    /**\n     * @see org.bouncycastle.crypto.digests.KeccakDigest\n     * @see org.bouncycastle.jcajce.provider.digest.Keccak\n     */\n    KECCAK224(\"KECCAK-224\", 224), KECCAK256(\"KECCAK-256\", 256), // \n    KECCAK288(\"KECCAK-288\", 288), KECCAK384(\"KECCAK-384\", 384), // \n    KECCAK512(\"KECCAK-512\", 512), //\n\n    /**\n     * @see org.bouncycastle.crypto.digests.SkeinDigest\n     * @see org.bouncycastle.jcajce.provider.digest.Skein\n     */\n    SKEIN_256_128 (\"Skein-256-128\",  128), SKEIN_256_256  (\"Skein-256-256\",    256), // \n    SKEIN_512_256 (\"Skein-512-256\",  256), SKEIN_512_512  (\"Skein-512-512\",    512), // \n    SKEIN_1024_512(\"Skein-1024-512\", 512), SKEIN_1024_1024(\"Skein-1024-1024\", 1024), // \n\n    /**\n     * @see org.bouncycastle.crypto.digests.SHA3Digest\n     * @see org.bouncycastle.jcajce.provider.digest.SHA3\n     */\n    SHA3_224(\"SHA3-224\", 224), SHA3_256(\"SHA3-256\", 256), // \n    SHA3_384(\"SHA3-384\", 384), SHA3_512(\"SHA3-512\", 512), //\n    ;\n\n    private final String algorithm;\n    private final int byteSize;\n\n    DigestAlgorithms(int bitLen) {\n        this.algorithm = this.name();\n        this.byteSize = bitLen >>> 3;\n    }\n\n    DigestAlgorithms(String algorithm, int bitLen) {\n        this.algorithm = algorithm;\n        this.byteSize = bitLen >>> 3;\n    }\n\n    public String algorithm() {\n        return this.algorithm;\n    }\n\n    public int byteSize() {\n        return byteSize;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/ECParameters.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce;\n\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.commons.codec.binary.Hex;\nimport org.apache.commons.lang3.builder.EqualsBuilder;\nimport org.apache.commons.lang3.builder.HashCodeBuilder;\nimport org.bouncycastle.asn1.ASN1ObjectIdentifier;\nimport org.bouncycastle.asn1.sec.SECNamedCurves;\nimport org.bouncycastle.asn1.x9.X9ECParameters;\nimport org.bouncycastle.crypto.generators.ECKeyPairGenerator;\nimport org.bouncycastle.crypto.params.ECDomainParameters;\nimport org.bouncycastle.crypto.params.ECKeyGenerationParameters;\nimport org.bouncycastle.math.ec.ECCurve;\nimport org.bouncycastle.math.ec.ECPoint;\n\nimport java.lang.reflect.Field;\nimport java.math.BigInteger;\nimport java.security.SecureRandom;\nimport java.util.Hashtable;\nimport java.util.Map;\n\n/** \n * Specifications completely defining an elliptic curve. \n * Used to define an elliptic curve by EllipticCurve, \n * define(ECParamters ecp). \n * NOTE: This is designed for an elliptic curve on the form: \n *   <b>y^2 = x^3 + ax + b (mod p)</b>\n * with fixed generator and precomputed order.\n * \n * {@link SECNamedCurves#getByName(String)}\n * \n * @author Ponfee\n*/\n@SuppressWarnings(\"unchecked\")\npublic class ECParameters implements java.io.Serializable {\n\n    private static final long serialVersionUID = 6479779256927237118L;\n\n    private static final char SEPARATOR = ',';\n\n    public static final ECParameters SM2_BEST = new ECParameters(\n        \"sm2-best\",\n        \"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF\",\n        \"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC\",\n        \"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93\",\n        \"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7\",\n        \"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0\",\n        \"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123\",\n        \"0\" // placeholder\n    );\n\n    public static final ECParameters SM2_CUST = new ECParameters(\n        \"sm2-cust\",\n        \"8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3\",\n        \"787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498\",\n        \"63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A\",\n        \"421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D\",\n        \"0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2\",\n        \"8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7\",\n        \"0\" // placeholder\n   );\n\n    public static final ECParameters secp112r1 = new ECParameters(\n        \"secp112r1\",\n        \"DB7C2ABF62E35E668076BEAD208B\", \n        \"DB7C2ABF62E35E668076BEAD2088\", \n        \"659EF8BA043916EEDE8911702B22\", \n        \"09487239995A5EE76B55F9C2F098\", \n        \"A89CE5AF8724C0A23E0E0FF77500\", \n        \"DB7C2ABF62E35E7628DFAC6561C5\", \n        \"00F50B028E4D696E676875615175290472783FB1\"\n    );\n\n    public static final ECParameters secp160r1 = new ECParameters(\n        \"secp160r1\",\n        \"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF\", \n        \"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC\", \n        \"1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45\", \n        \"4A96B5688EF573284664698968C38BB913CBFC82\", \n        \"23A628553168947D59DCC912042351377AC5FB32\", \n        \"0100000000000000000001F4C8F927AED3CA752257\", \n        \"1053CDE42C14D696E67687561517533BF3F83345\"\n    );\n\n    public static final ECParameters secp256r1 = new ECParameters(\n        \"secp256r1\",\n        \"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF\", \n        \"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC\", \n        \"5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B\", \n        \"6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296\", \n        \"4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5\", \n        \"FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551\", \n        \"C49D360886E704936A6678E1139D26B7819F7E90\"\n    );\n\n    public static final ECParameters secp256k1 = new ECParameters(\n        \"secp256k1\",\n        \"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F\", \n        \"0\", \n        \"7\", \n        \"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798\", \n        \"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8\", \n        \"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141\", \n        \"0\"\n    );\n\n    public static final ECParameters secp521r1 = new ECParameters(\n        \"secp521r1\",\n        \"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\", \n        \"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC\", \n        \"0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00\", \n        \"C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66\", \n        \"11839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650\", \n        \"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409\", \n        \"D09E8800291CB85396CC6717393284AAA0DA64BA\"\n    );\n\n    public static final ImmutableMap<String, ASN1ObjectIdentifier> NAME_OID_MAPPING;\n    public static final ImmutableMap<String, ECParameters> EC_PARAMETERS;\n    static {\n        ImmutableMap.Builder<String, ASN1ObjectIdentifier> nameOids = ImmutableMap.builder();\n        ImmutableMap.Builder<String, ECParameters>       nameParams = ImmutableMap.builder();\n        try {\n            Field field = SECNamedCurves.class.getDeclaredField(\"objIds\");\n            field.setAccessible(true);\n            Hashtable<String, ASN1ObjectIdentifier> table = (Hashtable<String, ASN1ObjectIdentifier>) field.get(null); // static field\n            field.setAccessible(false);\n            for (Map.Entry<String, ASN1ObjectIdentifier> entry : table.entrySet()) {\n                String name = entry.getKey();\n                X9ECParameters params = SECNamedCurves.getByName(name);\n                nameOids.put(name, entry.getValue());\n                nameParams.put(name, new ECParameters(\n                    name,\n                    Numbers.toHex(params.getCurve().getField().getCharacteristic()), \n                    Numbers.toHex(params.getCurve().getA().toBigInteger()), \n                    Numbers.toHex(params.getCurve().getB().toBigInteger()), \n                    Numbers.toHex(params.getG().getXCoord().toBigInteger()), \n                    Numbers.toHex(params.getG().getYCoord().toBigInteger()), \n                    Numbers.toHex(params.getN()), encodeHex(params.getSeed())\n                ));\n            }\n        } catch (Exception ignored) {\n            ignored.printStackTrace();\n        }\n        NAME_OID_MAPPING = nameOids.build();\n        EC_PARAMETERS = nameParams.build();\n    }\n\n    /** init parameter */\n    public final String name;\n    public final BigInteger p; // p为素数域内点的个数\n    public final BigInteger a; // a和b是其内的两个大数\n    public final BigInteger b;\n    public final BigInteger gx; // x,y为基点G的坐标\n    public final BigInteger gy;\n    public final BigInteger n; // n为点基点G的阶(nP=O∞)\n    public final BigInteger S; // secure random seed\n    //public final BigInteger h; // 有时还会用到h(椭圆曲线上所有点的个数p与n相除的整数部分)\n\n    /** build parameter */\n    public transient final ECCurve curve; // the curve\n    public transient final ECPoint pointG; // the base point\n    public transient final ECDomainParameters bcSpec;\n    public transient final ECKeyPairGenerator keyPairGenerator;\n \n    public ECParameters(String name, String p, String a, \n                        String b, String gx, String gy, \n                        String n, String S) {\n        this.name = name;\n        this.p  = hexToBigInteger(p);\n        this.a  = hexToBigInteger(a);\n        this.b  = hexToBigInteger(b);\n        this.gx = hexToBigInteger(gx);\n        this.gy = hexToBigInteger(gy);\n        this.n  = hexToBigInteger(n);\n        this.S  = hexToBigInteger(S);\n\n        ECCurve curve = null;\n        ECPoint pointG = null;\n        ECDomainParameters bcSpec = null;\n        ECKeyPairGenerator keyPairGenerator = null;\n        try {\n            curve = new ECCurve.Fp(this.p, this.a, this.b, null, null);\n            pointG = curve.createPoint(this.gx, this.gy);\n            bcSpec = new ECDomainParameters(curve, pointG, this.n);\n            keyPairGenerator = new ECKeyPairGenerator();\n            keyPairGenerator.init(new ECKeyGenerationParameters(\n                bcSpec, new SecureRandom(SecureRandoms.generateSeed(24))\n            ));\n        } catch (Exception ignored) {\n            // x value invalid in Fp field element\n            //System.err.println(this.toString() + \", error:\" + ignored.getMessage());\n        }\n\n        this.curve = curve;\n        this.pointG = pointG;\n        this.bcSpec = bcSpec;\n        this.keyPairGenerator = keyPairGenerator;\n    }\n\n    @Override\n    public String toString() {\n        return new StringBuilder()\n                   .append(name).append(SEPARATOR)\n                   .append(Numbers.toHex(p)).append(SEPARATOR)\n                   .append(Numbers.toHex(a)).append(SEPARATOR)\n                   .append(Numbers.toHex(b)).append(SEPARATOR)\n                   .append(Numbers.toHex(gx)).append(SEPARATOR)\n                   .append(Numbers.toHex(gy)).append(SEPARATOR)\n                   .append(Numbers.toHex(n)).append(SEPARATOR)\n                   .append(Numbers.toHex(S)).toString();\n    }\n\n    public static ECParameters fromString(String parameter) {\n        String[] array = parameter.split(String.valueOf(SEPARATOR), 8);\n        return new ECParameters(\n            array[0], array[1], array[2], array[3], \n            array[4], array[5], array[6], array[7]\n        );\n    }\n\n    private static String encodeHex(byte[] bytes) {\n        return (bytes == null || bytes.length == 0)\n               ? \"0\" : Hex.encodeHexString(bytes);\n    }\n\n    @Override\n    public int hashCode() {\n        return new HashCodeBuilder().append(p)\n                .append(a).append(b).append(gx).append(gy)\n                .append(n).append(S).toHashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj) {\n            return true;\n        }\n\n        if (!(obj instanceof ECParameters)) {\n            return false;\n        }\n\n        ECParameters other = (ECParameters) obj;\n        return new EqualsBuilder()\n                  .append(this.p, other.p)\n                  .append(this.a, other.a)\n                  .append(this.b, other.b)\n                  .append(this.gx, other.gx)\n                  .append(this.gy, other.gy)\n                  .append(this.n, other.n)\n                  .append(this.S, other.S)\n                  .isEquals();\n        //return EqualsBuilder.reflectionEquals(this, obj, false, null, false, \"name\");\n    }\n\n    private static BigInteger hexToBigInteger(String hex) {\n        return new BigInteger(hex, 16);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/HmacAlgorithms.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce;\n\nimport com.google.common.collect.ImmutableBiMap;\n\n/**\n * The Hamc Algorithms\n * @author Ponfee\n */\npublic enum HmacAlgorithms {\n\n    HmacMD5(128), //\n\n    HmacRipeMD128(128), HmacRipeMD160(160), // \n    HmacRipeMD256(256), HmacRipeMD320(320), // \n\n    HmacSHA1(160), HmacSHA224(224), // \n    HmacSHA256(256), HmacSHA384(384), // \n    HmacSHA512(512), // \n\n    // org.bouncycastle.crypto.digests.SM3Digest cannot support hmac algorithm\n    //HmacSM3(256), \n\n    // org.bouncycastle.crypto.digests.SHAKEDigest cannot support hmac algorithm\n    //HmacSHAKE128(128), HmacSHAKE256(256), \n\n    /**\n     * @see org.bouncycastle.crypto.digests.KeccakDigest\n     * @see org.bouncycastle.jcajce.provider.digest.Keccak\n     */\n    HmacKECCAK224(224), HmacKECCAK288(288), // \n    HmacKECCAK256(256), HmacKECCAK384(384), // \n    HmacKECCAK512(512), //\n\n    HmacSKEIN_256_128(\"Skein-MAC-256-128\", 128), // \n    HmacSKEIN_256_256(\"Skein-MAC-256-256\", 256), // \n    HmacSKEIN_512_256(\"Skein-MAC-512-256\", 256), // \n    HmacSKEIN_512_512(\"Skein-MAC-512-512\", 512), // \n    HmacSKEIN_1024_512(\"Skein-MAC-1024-512\", 512), // \n    HmacSKEIN_1024_1024(\"Skein-MAC-1024-1024\", 1024), // \n\n    /**\n     * @see org.bouncycastle.crypto.digests.SHA3Digest\n     * @see org.bouncycastle.jcajce.provider.digest.SHA3\n     */\n    HmacSHA3_224(\"HmacSHA3-224\", 224), HmacSHA3_256(\"HmacSHA3-256\", 256), // \n    HmacSHA3_384(\"HmacSHA3-384\", 384), HmacSHA3_512(\"HmacSHA3-512\", 512), // \n    ;\n\n    private final String algorithm;\n    private final int byteSize;\n\n    HmacAlgorithms(int bitLen) {\n        this.algorithm = this.name();\n        this.byteSize = bitLen >>> 3;\n    }\n\n    HmacAlgorithms(String algorithm, int bitLen) {\n        this.algorithm = algorithm;\n        this.byteSize = bitLen >>> 3;\n    }\n\n    public String algorithm() {\n        return this.algorithm;\n    }\n\n    public int byteSize() {\n        return this.byteSize;\n    }\n\n    public static final ImmutableBiMap<Integer, HmacAlgorithms> ALGORITHM_MAPPING =\n        ImmutableBiMap.<Integer, HmacAlgorithms> builder()\n            .put(1, HmacAlgorithms.HmacSHA256)\n            .put(2, HmacAlgorithms.HmacSHA512)\n            .put(3, HmacAlgorithms.HmacKECCAK256)\n            .put(4, HmacAlgorithms.HmacKECCAK512)\n            .put(5, HmacAlgorithms.HmacSHA3_256)\n            .put(6, HmacAlgorithms.HmacSHA3_512)\n            .build();\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/Providers.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce;\n\nimport javax.crypto.*;\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.TrustManagerFactory;\nimport java.security.*;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * security providers\n * there has not any method defined except a static method\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"restriction\")\npublic interface Providers {\n\n    static Provider get(String name) {\n        return ProvidersHolder.NAME_HOLDER.get(name);\n    }\n\n    static Provider get(Class<? extends Provider> type) {\n        Provider provider = ProvidersHolder.CLASS_HOLDER.get(type);\n        if (provider != null) {\n            return provider;\n        }\n\n        try {\n            provider = type.getDeclaredConstructor().newInstance();\n            Security.addProvider(provider);\n            ProvidersHolder.NAME_HOLDER.put(provider.getName(), provider);\n        } catch (Exception ignored) {\n            provider = NullProvider.INSTANCE;\n            ignored.printStackTrace();\n        }\n        ProvidersHolder.CLASS_HOLDER.put(type, provider);\n        return provider;\n    }\n\n    // ----------------------------------------------------------\n    static void set(Provider provider) {\n        ProvidersHolder.CURRENT_PROVIDER.set(provider);\n    }\n\n    static void clear() {\n        ProvidersHolder.CURRENT_PROVIDER.remove();\n    }\n\n    static void setGlobal(Provider provider) {\n        ProvidersHolder.globalProvider = provider;\n    }\n\n    static void clearGlobal() {\n        ProvidersHolder.globalProvider = null;\n    }\n\n    // ----------------------------------------------------------\n    static KeyAgreement getKeyAgreement(String algorithm) {\n        return getKeyAgreement(algorithm, null);\n    }\n\n    static KeyAgreement getKeyAgreement(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? KeyAgreement.getInstance(algorithm) \n                 : KeyAgreement.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static KeyGenerator getKeyGenerator(String algorithm) {\n        return getKeyGenerator(algorithm, null);\n    }\n\n    static KeyGenerator getKeyGenerator(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? KeyGenerator.getInstance(algorithm) \n                 : KeyGenerator.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static Cipher getCipher(String algorithm) {\n        return getCipher(algorithm, null);\n    }\n\n    static Cipher getCipher(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? Cipher.getInstance(algorithm) \n                 : Cipher.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static KeyPairGenerator getKeyPairGenerator(String algorithm) {\n        return getKeyPairGenerator(algorithm, null);\n    }\n\n    static KeyPairGenerator getKeyPairGenerator(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? KeyPairGenerator.getInstance(algorithm) \n                 : KeyPairGenerator.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static KeyFactory getKeyFactory(String algorithm) {\n        return getKeyFactory(algorithm, null);\n    }\n\n    static KeyFactory getKeyFactory(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? KeyFactory.getInstance(algorithm) \n                 : KeyFactory.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static Signature getSignature(String algorithm) {\n        return getSignature(algorithm, null);\n    }\n\n    static Signature getSignature(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? Signature.getInstance(algorithm) \n                 : Signature.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static KeyStore getKeyStore(String algorithm) {\n        return getKeyStore(algorithm, null);\n    }\n\n    static KeyStore getKeyStore(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? KeyStore.getInstance(algorithm) \n                 : KeyStore.getInstance(algorithm, provider);\n        } catch (KeyStoreException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static TrustManagerFactory getTrustManagerFactory(String algorithm) {\n        return getTrustManagerFactory(algorithm, null);\n    }\n\n    static TrustManagerFactory getTrustManagerFactory(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? TrustManagerFactory.getInstance(algorithm) \n                 : TrustManagerFactory.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static KeyManagerFactory getKeyManagerFactory(String algorithm) {\n        return getKeyManagerFactory(algorithm, null);\n    }\n\n    static KeyManagerFactory getKeyManagerFactory(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? KeyManagerFactory.getInstance(algorithm) \n                 : KeyManagerFactory.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static SSLContext getSSLContext(String algorithm) {\n        return getSSLContext(algorithm, null);\n    }\n\n    static SSLContext getSSLContext(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? SSLContext.getInstance(algorithm) \n                 : SSLContext.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static CertificateFactory getCertificateFactory(String algorithm) {\n        return getCertificateFactory(algorithm, null);\n    }\n\n    static CertificateFactory getCertificateFactory(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? CertificateFactory.getInstance(algorithm) \n                 : CertificateFactory.getInstance(algorithm, provider);\n        } catch (CertificateException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    static SecretKeyFactory getSecretKeyFactory(String algorithm) {\n        return getSecretKeyFactory(algorithm, null);\n    }\n\n    static SecretKeyFactory getSecretKeyFactory(String algorithm, Provider provider) {\n        provider = ProvidersHolder.getProvider(provider);\n        try {\n            return provider == null \n                 ? SecretKeyFactory.getInstance(algorithm) \n                 : SecretKeyFactory.getInstance(algorithm, provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ------------------------------------------------------------------------------\n    // BouncyCastleProvider.PROVIDER_NAME\n    Provider BC         = get(org.bouncycastle.jce.provider.BouncyCastleProvider.class);\n    Provider BC_PQC     = get(org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider.class);\n    Provider BC_JSSE    = get(org.bouncycastle.jsse.provider.BouncyCastleJsseProvider.class);\n    Provider SunJSSE    = get(com.sun.net.ssl.internal.ssl.Provider.class);\n    Provider SunJCE     = get(com.sun.crypto.provider.SunJCE.class);\n    Provider SunSASL    = get(com.sun.security.sasl.Provider.class);\n    /*\n    Provider XMLDSig    = get(org.jcp.xml.dsig.internal.dom.XMLDSigRI.class);\n    Provider SUN        = get(sun.security.provider.Sun.class);\n    Provider SunRsaSign = get(sun.security.rsa.SunRsaSign.class);\n    Provider SunEC      = get(sun.security.ec.SunEC.class);\n    Provider SunJGSS    = get(sun.security.jgss.SunProvider.class);\n    Provider SunPCSC    = get(sun.security.smartcardio.SunPCSC.class);\n    Provider SunMSCAPI  = get(sun.security.mscapi.SunMSCAPI.class);\n    */\n\n    /**\n     * provider holder\n     */\n    final class ProvidersHolder {\n        // --------------------------------------------------------------------------\n        private static final Map<Class<? extends Provider>, Provider> CLASS_HOLDER = new ConcurrentHashMap<>(16);\n        private static final Map<String, Provider>                    NAME_HOLDER  = new ConcurrentHashMap<>(16);\n        static {\n            Provider[] providers = Security.getProviders();\n            if (providers != null && providers.length > 0) {\n                for (Provider provider : providers) {\n                    CLASS_HOLDER.put(provider.getClass(), provider);\n                    NAME_HOLDER.put(provider.getName(), provider);\n                }\n            }\n        }\n\n        // --------------------------------------------------------------------------\n        private static final ThreadLocal<Provider> CURRENT_PROVIDER = new ThreadLocal<>();\n\n        private static Provider getProvider(Provider provider) {\n            return provider != null\n                ? provider\n                : (provider = CURRENT_PROVIDER.get()) != null\n                ? provider \n                : globalProvider;\n        }\n\n        // --------------------------------------------------------------------------\n        private static Provider globalProvider = null;\n    }\n\n    /**\n     * The NullProvider representing the not exists provider\n     */\n    final class NullProvider extends Provider {\n        private static final long serialVersionUID = 7420890884380155994L;\n        private static final NullProvider INSTANCE = new NullProvider();\n\n        private NullProvider() {\n            super(\"Null\", 1.0D, \"Non provider\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/RSACipherPaddings.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce;\n\n/**\n * RSA加密填充\n * Jdk Sun only support：RSA/ECB/PKCS1Padding\n * \n * @see org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi\n * \n * @author Ponfee\n */\npublic enum RSACipherPaddings {\n\n    ECB_NOPADDING(\"/ECB/NOPADDING\"), // \n    ECB_PKCS1PADDING(\"/ECB/PKCS1PADDING\"), // 原文必须 比RSA钥模长(modulus)短至少11个字节\n    ECB_ISO9796_1PADDING(\"/ECB/ISO9796-1PADDING\"), // \n    ECB_OAEPWITHMD5ANDMGF1PADDING(\"/ECB/OAEPWITHMD5ANDMGF1PADDING\"), // \n    ECB_OAEPPADDING(\"/ECB/OAEPPADDING\"), // RSA_size(rsa) – 41\n    ECB_OAEPWITHSHA1ANDMGF1PADDING(\"/ECB/OAEPWITHSHA1ANDMGF1PADDING\"), // OAEPWITHSHA-1ANDMGF1PADDING\n    ECB_OAEPWITHSHA224ANDMGF1PADDING(\"/ECB/OAEPWITHSHA224ANDMGF1PADDING\"), // OAEPWITHSHA-224ANDMGF1PADDING\n    ECB_OAEPWITHSHA256ANDMGF1PADDING(\"/ECB/OAEPWITHSHA256ANDMGF1PADDING\"), // OAEPWITHSHA-256ANDMGF1PADDING\n    ECB_OAEPWITHSHA384ANDMGF1PADDING(\"/ECB/OAEPWITHSHA384ANDMGF1PADDING\"), // OAEPWITHSHA-384ANDMGF1PADDING\n    ECB_OAEPWITHSHA512ANDMGF1PADDING(\"/ECB/OAEPWITHSHA512ANDMGF1PADDING\"), // OAEPWITHSHA-512ANDMGF1PADDING\n    ECB_OAEPWITHSHA3_224ANDMGF1PADDING(\"/ECB/OAEPWITHSHA3-224ANDMGF1PADDING\"), // \n    ECB_OAEPWITHSHA3_256ANDMGF1PADDING(\"/ECB/OAEPWITHSHA3-256ANDMGF1PADDING\"), // \n    ECB_OAEPWITHSHA3_384ANDMGF1PADDING(\"/ECB/OAEPWITHSHA3-384ANDMGF1PADDING\"), // \n    ECB_OAEPWITHSHA3_512ANDMGF1PADDING(\"/ECB/OAEPWITHSHA3-512ANDMGF1PADDING\"), // \n\n    NONE_NOPADDING(\"/NONE/NOPADDING\"), // \n    NONE_PKCS1PADDING(\"/NONE/PKCS1PADDING\"), // 原文必须 比RSA钥模长(modulus)短至少11个字节\n    NONE_ISO9796_1PADDING(\"/NONE/ISO9796-1PADDING\"), // \n    NONE_OAEPWITHMD5ANDMGF1PADDING(\"/NONE/OAEPWITHMD5ANDMGF1PADDING\"), // \n    NONE_OAEPPADDING(\"/NONE/OAEPPADDING\"), // RSA_size(rsa) – 41\n    NONE_OAEPWITHSHA1ANDMGF1PADDING(\"/NONE/OAEPWITHSHA1ANDMGF1PADDING\"), // OAEPWITHSHA-1ANDMGF1PADDING\n    NONE_OAEPWITHSHA224ANDMGF1PADDING(\"/NONE/OAEPWITHSHA224ANDMGF1PADDING\"), // OAEPWITHSHA-224ANDMGF1PADDING\n    NONE_OAEPWITHSHA256ANDMGF1PADDING(\"/NONE/OAEPWITHSHA256ANDMGF1PADDING\"), // OAEPWITHSHA-256ANDMGF1PADDING\n    NONE_OAEPWITHSHA384ANDMGF1PADDING(\"/NONE/OAEPWITHSHA384ANDMGF1PADDING\"), // OAEPWITHSHA-384ANDMGF1PADDING\n    NONE_OAEPWITHSHA512ANDMGF1PADDING(\"/NONE/OAEPWITHSHA512ANDMGF1PADDING\"), // OAEPWITHSHA-512ANDMGF1PADDING\n    NONE_OAEPWITHSHA3_224ANDMGF1PADDING(\"/NONE/OAEPWITHSHA3-224ANDMGF1PADDING\"), // \n    NONE_OAEPWITHSHA3_256ANDMGF1PADDING(\"/NONE/OAEPWITHSHA3-256ANDMGF1PADDING\"), // \n    NONE_OAEPWITHSHA3_384ANDMGF1PADDING(\"/NONE/OAEPWITHSHA3-384ANDMGF1PADDING\"), // \n    NONE_OAEPWITHSHA3_512ANDMGF1PADDING(\"/NONE/OAEPWITHSHA3-512ANDMGF1PADDING\"), // \n\n    ECB_SSLV23(\"/ECB/SSLV23\"), // \n    NONE_SSLV23(\"/NONE/SSLV23\"); // \n\n    private final String transform;\n\n    RSACipherPaddings(String transform) {\n        this.transform = transform;\n    }\n\n    public String transform() {\n        return transform;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/RSASignAlgorithms.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce;\n\n/**\n * RSA签名算法\n * \n * @author Ponfee\n */\npublic enum RSASignAlgorithms {\n\n    MD5withRSA, SHA1withRSA, SHA256withRSA, // \n    SHA384withRSA, SHA512withRSA\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/cert/CertPKCS1Verifier.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.cert;\n\nimport cn.ponfee.commons.jce.Providers;\n\nimport java.security.InvalidKeyException;\nimport java.security.Signature;\nimport java.security.SignatureException;\nimport java.security.cert.X509CRL;\nimport java.security.cert.X509Certificate;\n\n/**\n * pkcs1 signature verifier\n * \n * @author Ponfee\n */\npublic class CertPKCS1Verifier extends CertSignedVerifier {\n\n    /**\n     * the signature of pkcs1 verifer\n     * @param rootCert the ca root cert\n     * @param crl      the cert revoke list\n     * @param subject  the subject cert\n     * @param info     origin data info\n     * @param signed   signed info\n     */\n    public CertPKCS1Verifier(X509Certificate rootCert, X509CRL crl, \n                             X509Certificate subject, byte[] info, byte[] signed) {\n        super(rootCert, crl, info);\n        this.subjects = new X509Certificate[] { subject };\n        this.signedInfos.add(signed);\n    }\n\n    @Override\n    public void verifySigned() {\n        String subjectCN = null;\n        Signature sign = Providers.getSignature(this.subjects[0].getSigAlgName());\n        try {\n            subjectCN = X509CertUtils.getCertInfo(this.subjects[0], X509CertInfo.SUBJECT_CN);\n            sign.initVerify(this.subjects[0].getPublicKey());\n            sign.update(this.info);\n\n            if (!sign.verify(this.signedInfos.get(0))) {\n                throw new SecurityException(\"[\" + subjectCN + \"]验签不通过\");\n            }\n        } catch (SignatureException e) {\n            throw new SecurityException(\"[\" + subjectCN + \"]证书签名信息错误\", e);\n        } catch (InvalidKeyException e) {\n            throw new SecurityException(\"证书验签出错\", e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/cert/CertPKCS7Verifier.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.cert;\n\nimport cn.ponfee.commons.jce.pkcs.PKCS7Signature;\nimport sun.security.pkcs.PKCS7;\nimport sun.security.pkcs.SignerInfo;\n\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.SignatureException;\nimport java.security.cert.X509CRL;\nimport java.security.cert.X509Certificate;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * pkcs7方式验签\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"restriction\")\npublic class CertPKCS7Verifier extends CertSignedVerifier {\n\n    private final PKCS7 pkcs7;\n\n    /**\n     * 不附原文的多人pkcs7签名\n     * @param rootCert  the root ca cert\n     * @param crl       the cert revoke list\n     * @param pkcs7Data the pkcs7 byte array data\n     * @param info      the origin byte array data\n     */\n    public CertPKCS7Verifier(X509Certificate rootCert, X509CRL crl, \n                             byte[] pkcs7Data, byte[] info) {\n        this(rootCert, crl, PKCS7Signature.getPkcs7(pkcs7Data), info);\n    }\n\n    /**\n     * 附原文的多人pkcs7签名\n     * @param rootCert  the root ca cert\n     * @param crl       the cert revoke list\n     * @param pkcs7Data the pkcs7 byte array data, attached origin byte array data\n     */\n    public CertPKCS7Verifier(X509Certificate rootCert, X509CRL crl, byte[] pkcs7Data) {\n        this(rootCert, crl, PKCS7Signature.getPkcs7(pkcs7Data));\n    }\n\n    /**\n     * 附原文的多人pkcs7签名\n     * @param rootCert  the root ca cert\n     * @param crl       the cert revoke list\n     * @param pkcs7     the pkcs7\n     */\n    public CertPKCS7Verifier(X509Certificate rootCert, X509CRL crl, PKCS7 pkcs7) {\n        this(rootCert, crl, pkcs7, PKCS7Signature.getContent(pkcs7));\n    }\n\n    /**\n     * 附原文的多人pkcs7签名\n     * @param rootCert the root ca cert\n     * @param crl      the cert revoke list\n     * @param pkcs7    the pkck7\n     * @param info     the origin byte array data\n     */\n    public CertPKCS7Verifier(X509Certificate rootCert, X509CRL crl, \n                             PKCS7 pkcs7, byte[] info) {\n        super(rootCert, crl, info);\n\n        this.pkcs7 = pkcs7;\n\n        SignerInfo[] signs = pkcs7.getSignerInfos();\n        Map<BigInteger, X509Certificate> certs = new HashMap<>(signs.length << 1);\n        for (X509Certificate cert : pkcs7.getCertificates()) {\n            certs.put(cert.getSerialNumber(), cert);\n        }\n\n        this.subjects = new X509Certificate[signs.length];\n        for (int i = 0; i < signs.length; i++) {\n            X509Certificate cert = certs.get(signs[i].getCertificateSerialNumber());\n            if (cert == null) {\n                throw new IllegalArgumentException(\"cannot found the sign cert: \" \n                                         + signs[i].getCertificateSerialNumber());\n            } else {\n                this.subjects[i++] = cert;\n                this.signedInfos.add(signs[i].getEncryptedDigest());\n            }\n        }\n    }\n\n    @Override\n    public void verifySigned() {\n        String subjectCN = null;\n        try {\n            for (SignerInfo signer : pkcs7.getSignerInfos()) {\n                subjectCN = X509CertUtils.getCertInfo(signer.getCertificate(pkcs7), \n                                                      X509CertInfo.SUBJECT_CN);\n                if (pkcs7.verify(signer, this.info) == null) {\n                    throw new SecurityException(\"[\" + subjectCN + \"]验签不通过\");\n                }\n            }\n        } catch (SignatureException e) {\n            throw new SecurityException(\"[\" + subjectCN + \"]签名信息错误\", e);\n        } catch (IOException e) {\n            throw new SecurityException(\"获取证书主题异常\", e);\n        } catch (NoSuchAlgorithmException e) {\n            throw new SecurityException(\"证书验签出错\", e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/cert/CertSignedVerifier.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.cert;\n\nimport java.security.SignatureException;\nimport java.security.cert.CertificateExpiredException;\nimport java.security.cert.CertificateNotYetValidException;\nimport java.security.cert.X509CRL;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * 证书验签（template method patterns）\n * \n * @author Ponfee\n */\npublic abstract class CertSignedVerifier {\n\n    protected final X509Certificate rootCert; // 根证书\n    protected final X509CRL crl; // 吊销列表\n    protected final byte[] info; // 原文信息\n    protected final List<byte[]> signedInfos = new ArrayList<>(); // 签名数据\n\n    protected X509Certificate[] subjects; // 多人签名证书\n    private boolean verifySigned = true;\n\n    protected CertSignedVerifier(X509Certificate rootCert, X509CRL crl, byte[] info) {\n        this.rootCert = rootCert;\n        this.crl = crl;\n        this.info = info;\n    }\n\n    /**\n     * 根据加载的根证进行证书验证\n     */\n    public final void verify() {\n        for (X509Certificate subject : subjects) {\n            String subjectCN = X509CertUtils.getCertInfo(subject, X509CertInfo.SUBJECT_CN);\n\n            // 获取根证书\n            if (rootCert == null) {\n                throw new SecurityException(\"[\" + subjectCN + \"]的根证未受信任\");\n            }\n\n            // 校验\n            verifyCertDate(subject);\n            verifyIssuingSign(subject, rootCert);\n            if (crl != null) {\n                verifyCrlRevoke(subject, crl);\n            }\n        }\n\n        // 签名验证\n        if (verifySigned) {\n            verifySigned();\n        }\n    }\n\n    /**\n     * 验证签名\n     */\n    public abstract void verifySigned();\n\n    /**\n     * 校验证书是否过期\n     * @param subject\n     */\n    public static void verifyCertDate(X509Certificate subject) {\n        String subjectCN = null;\n        try {\n            subjectCN = X509CertUtils.getCertInfo(subject, X509CertInfo.SUBJECT_CN);\n            subject.checkValidity(new Date());\n        } catch (CertificateExpiredException e) {\n            throw new SecurityException(\"[\" + subjectCN + \"]已过期\", e);\n        } catch (CertificateNotYetValidException e) {\n            throw new SecurityException(\"[\" + subjectCN + \"]尚未生效\", e);\n        }\n    }\n\n    /**\n     * 校验是否由指定根证签发\n     * @param subject\n     * @param root\n     */\n    public static void verifyIssuingSign(X509Certificate subject, X509Certificate root) {\n        String subjectCN = null;\n        try {\n            subjectCN = X509CertUtils.getCertInfo(subject, X509CertInfo.SUBJECT_CN);\n            subject.verify(root.getPublicKey());\n        } catch (SignatureException e) {\n            throw new SecurityException(\"[\" + subjectCN + \"]的根证未受信任\", e);\n        } catch (Exception e) {\n            throw new SecurityException(\"根证验签出错\", e);\n        }\n\n    }\n\n    /**\n     * 校验是否已被吊销\n     * @param subject\n     * @param crl\n     */\n    public static void verifyCrlRevoke(X509Certificate subject, X509CRL crl) {\n        String subjectCN = X509CertUtils.getCertInfo(subject, X509CertInfo.SUBJECT_CN);\n        if (crl.isRevoked(subject)) {\n            throw new SecurityException(\"[\" + subjectCN + \"]已被吊销\");\n        }\n    }\n\n    public X509Certificate[] getSubjects() {\n        return this.subjects;\n    }\n\n    public byte[] getInfo() {\n        return this.info;\n    }\n\n    public List<byte[]> getSignedInfo() {\n        return this.signedInfos;\n    }\n\n    public void setVerifySigned(boolean verifySigned) {\n        this.verifySigned = verifySigned;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/cert/ObjectIdentifiers.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.cert;\n\n/**\n * 证书扩展信息\n * \n * @author Ponfee\n * @see org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers\n */\npublic final class ObjectIdentifiers {\n\n    public static final int[] SubjectDirectoryAttributes = new int[] { 2, 5, 29, 9 };\n\n    /** Subject Key Identifier */\n    public static final int[] SubjectKeyIdentifier = new int[] { 2, 5, 29, 14 };\n\n    /** Key Usage */\n    public static final int[] KeyUsage = new int[] { 2, 5, 29, 15 };\n\n    /** Private Key Usage Period 私钥使用周期 */\n    public static final int[] PrivateKeyUsagePeriod = new int[] { 2, 5, 29, 16 };\n\n    /** Subject Alternative Name */\n    public static final int[] SubjectAlternativeName = new int[] { 2, 5, 29, 17 };\n\n    /** Issuer Alternative Name */\n    public static final int[] IssuerAlternativeName = new int[] { 2, 5, 29, 18 };\n\n    /** Basic Constraints */\n    public static final int[] BasicConstraints = new int[] { 2, 5, 29, 19 };\n\n    /** CRL Number */\n    public static final int[] CRLNumber = new int[] { 2, 5, 29, 20 };\n\n    /** Reason code */\n    public static final int[] ReasonCode = new int[] { 2, 5, 29, 21 };\n\n    /** Hold Instruction Code */\n    public static final int[] InstructionCode = new int[] { 2, 5, 29, 23 };\n\n    /** Invalidity Date */\n    public static final int[] InvalidityDate = new int[] { 2, 5, 29, 24 };\n\n    /** Delta CRL indicator */\n    public static final int[] DeltaCRLIndicator = new int[] { 2, 5, 29, 27 };\n\n    /** Issuing Distribution Point */\n    public static final int[] IssuingDistributionPoint = new int[] { 2, 5, 29, 28 };\n\n    /** Certificate Issuer */\n    public static final int[] CertificateIssuer = new int[] { 2, 5, 29, 29 };\n\n    /** Name Constraints */\n    public static final int[] NameConstraints = new int[] { 2, 5, 29, 30 };\n\n    /** CRL Distribution Points */\n    public static final int[] CRLDistributionPoints = new int[] { 2, 5, 29, 31 };\n\n    /** Certificate Policies */\n    public static final int[] CertificatePolicies = new int[] { 2, 5, 29, 32 };\n\n    /** Policy Mappings */\n    public static final int[] PolicyMappings = new int[] { 2, 5, 29, 33 };\n\n    /** Authority Key Identifier */\n    public static final int[] AuthorityKeyIdentifier = new int[] { 2, 5, 29, 35 };\n\n    /** Policy Constraints */\n    public static final int[] PolicyConstraints = new int[] { 2, 5, 29, 36 };\n\n    /** Extended Key Usage */\n    public static final int[] ExtendedKeyUsage = new int[] { 2, 5, 29, 37 };\n\n    /** Freshest CRL */\n    public static final int[] FreshestCRL = new int[] { 2, 5, 29, 46 };\n\n    /** Inhibit Any Policy */\n    public static final int[] InhibitAnyPolicy = new int[] { 2, 5, 29, 54 };\n\n    /** Authority Info Access */\n    public static final int[] AuthorityInfoAccess = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 1 };\n\n    /** Subject Info Access */\n    public static final int[] SubjectInfoAccess = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 11 };\n\n    /** Logo Type */\n    public static final int[] LogoType = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 12 };\n\n    /** BiometricInfo */\n    public static final int[] BiometricInfo = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 2 };\n\n    /** QCStatements */\n    public static final int[] QCStatements = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 3 };\n\n    /** Audit identity extension in attribute certificates */\n    public static final int[] AuditIdentity = new int[] { 1, 3, 6, 1, 5, 5, 7, 1, 4 };\n\n    /** NoRevAvail extension in attribute certificates */\n    public static final int[] NoRevAvail = new int[] { 2, 5, 29, 56 };\n\n    /** TargetInformation extension in attribute certificates */\n    public static final int[] TargetInformation = new int[] { 2, 5, 29, 55 };\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/cert/RepairX500Principal.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.cert;\n\nimport javax.security.auth.x500.X500Principal;\nimport java.io.ByteArrayInputStream;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.security.Principal;\n\n/**\n * 解决X500Principal乱码问题\n * \n * @author Ponfee\n */\npublic class RepairX500Principal implements Principal {\n\n    private static final byte[][] OID_ARRAY = {\n        { 0x55, 0x04, 0x06 }, { 0x55, 0x04, 0x08 }, \n        { 0x55, 0x04, 0x07 }, { 0x55, 0x04, 0x0a }, \n        { 0x55, 0x04, 0x0b }, { 0x55, 0x04, 0x03 },\n\n        { 0x2a, (byte) 0x86, 0x48, (byte) 0x86, \n         (byte) 0xf7, 0x0d, 0x01, 0x09, 0x01 }\n    };\n\n    private static final String[] DN_STR = { \"C\", \"ST\", \"L\", \"O\", \"OU\", \"CN\", \"E\" };\n\n    private final ByteArrayInputStream input;\n\n    public RepairX500Principal(X500Principal principal) {\n        input = new ByteArrayInputStream(principal.getEncoded());\n    }\n\n    @Override\n    public String getName() {\n        if (preLen(0x30) != input.available()) {\n            return null;\n        }\n\n        byte[] oid = new byte[9];\n        int oidType;\n        StringBuilder builder = new StringBuilder();\n        for (;;) {\n            if (preLen(0x31) == 0) {\n                break;\n            }\n            if (preLen(0x30) == 0) {\n                break;\n            }\n            int len = preLen(0x06);\n            if (len == 0) {\n                break;\n            }\n            if (len > 9) {\n                oidType = -1;\n                input.skip(len);\n            } else {\n                input.read(oid, 0, len);\n                for (oidType = DN_STR.length - 1; oidType > -1; oidType--) {\n                    for (len = OID_ARRAY[oidType].length - 1; len > -1; len--) {\n                        if (oid[len] != OID_ARRAY[oidType][len]) {\n                            break;\n                        }\n                    }\n                    if (len < 0) {\n                        break;\n                    }\n                }\n            }\n            Charset charset = (input.read() == 0x1e) \n                              ? StandardCharsets.UTF_16BE \n                              : StandardCharsets.UTF_8;\n\n            len = preLen(-1);\n            if (oidType > -1) {\n                byte[] value = new byte[len];\n                    input.read(value, 0, value.length);\n                    if (builder.length() > 0) {\n                        builder.append(',');\n                    }\n                    builder.append(DN_STR[oidType]).append('=')\n                           .append(new String(value, charset));\n            } else {\n                input.skip(len);\n            }\n        }\n        return builder.length() == 0 ? null : builder.toString();\n    }\n\n    private int preLen(int tag) {\n        int itag;\n        if (tag != -1) {\n            itag = input.read();\n            if (itag != tag) {\n                return 0;\n            }\n        }\n        itag = input.read();\n        if (itag < 0x80) {\n            return itag;\n        }\n        if (itag == 0x81) {\n            return input.read();\n        }\n        if (itag == 0x82) {\n            itag = input.read();\n            return (itag << 8) + input.read();\n        }\n        return 0;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/cert/X509CertGenerator.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.cert;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.RSASignAlgorithms;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.UuidUtils;\nimport org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;\nimport org.bouncycastle.pkcs.PKCS10CertificationRequest;\nimport sun.security.pkcs10.PKCS10;\nimport sun.security.util.ObjectIdentifier;\nimport sun.security.x509.X509CertInfo;\nimport sun.security.x509.*;\n\nimport javax.annotation.Nullable;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.PrivateKey;\nimport java.security.PublicKey;\nimport java.security.Signature;\nimport java.security.cert.X509Certificate;\nimport java.util.Date;\nimport java.util.Vector;\n\n/**\n * 证书生成工具类\n * \n * @author Ponfee\n */\n@SuppressWarnings({ \"restriction\" })\npublic class X509CertGenerator {\n\n    // ------------------------create root ca cert of self sign -----------------------------\n    public static X509Certificate createRootCert(String issuer, RSASignAlgorithms sigAlg, PrivateKey privateKey, \n                                                 PublicKey publicKey, Date notBefore, Date notAfter) {\n        return createRootCert(null, issuer, sigAlg, privateKey, publicKey, notBefore, notAfter);\n    }\n\n    /**\n     * 创建CA根证书（自签名）\n     * @param sn\n     * @param issuer\n     * @param sigAlg\n     * @param privateKey\n     * @param publicKey\n     * @param notBefore\n     * @param notAfter\n     * @return\n     */\n    public static X509Certificate createRootCert(BigInteger sn, String issuer, RSASignAlgorithms sigAlg, PrivateKey privateKey, \n                                                 PublicKey publicKey, Date notBefore, Date notAfter) {\n        PKCS10 pkcs10 = createPkcs10(issuer, privateKey, publicKey, sigAlg);\n        X509CertInfo certInfo = createCertInfo(sn, pkcs10, notBefore, notAfter, createExtensions(true));\n        return selfSign(privateKey, certInfo);\n    }\n\n    // ---------------------------------create subject cert of ca sign ------------------------------\n    public static X509Certificate createSubjectCert(X509Certificate caCert, PrivateKey caKey, String subject,\n                                                    RSASignAlgorithms sigAlg, PrivateKey privateKey, \n                                                    PublicKey publicKey, Date notBefore, Date notAfter) {\n        return createSubjectCert(caCert, caKey, null, subject, sigAlg, privateKey, publicKey, notBefore, notAfter);\n    }\n\n    /**\n     * 创建证书并用根证签发\n     * @param caCert\n     * @param caKey\n     * @param sn\n     * @param subject\n     * @param sigAlg\n     * @param privateKey\n     * @param publicKey\n     * @param notBefore\n     * @param notAfter\n     * @return\n     */\n    public static X509Certificate createSubjectCert(X509Certificate caCert, PrivateKey caKey, BigInteger sn,\n                                                    String subject, RSASignAlgorithms sigAlg, PrivateKey privateKey,\n                                                    PublicKey publicKey, Date notBefore, Date notAfter) {\n        PKCS10 pkcs10 = createPkcs10(subject, privateKey, publicKey, sigAlg);\n        X509CertInfo certInfo = createCertInfo(sn, pkcs10, notBefore, notAfter, createExtensions(false));\n        return caSign(caCert, caKey, certInfo);\n    }\n\n    public static X509Certificate createSubjectCert(X509Certificate caCert, PrivateKey caKey, \n                                                    PKCS10 pkcs10, Date notBefore, Date notAfter) {\n        return createSubjectCert(caCert, caKey, null, pkcs10, notBefore, notAfter);\n    }\n\n    /**\n     * pkcs10请求CA签发证书\n     * @param caCert\n     * @param caKey\n     * @param sn\n     * @param pkcs10\n     * @param notBefore\n     * @param notAfter\n     * @return\n     */\n    public static X509Certificate createSubjectCert(X509Certificate caCert, PrivateKey caKey, BigInteger sn,\n                                                    PKCS10 pkcs10, Date notBefore, Date notAfter) {\n        X509CertInfo certInfo = createCertInfo(sn, pkcs10, notBefore, notAfter, createExtensions(false));\n        return caSign(caCert, caKey, certInfo);\n    }\n\n    // -------------------------------------------create pkcs10 ------------------------------------------\n    /**\n     * 创建pkcs10\n     * @param subject\n     * @param privateKey\n     * @param publicKey\n     * @param sigAlg\n     * @return\n     */\n    public static PKCS10 createPkcs10(String subject, PrivateKey privateKey,\n                                      PublicKey publicKey, RSASignAlgorithms sigAlg) {\n        Signature signature = Providers.getSignature(sigAlg.name());\n        try {\n            PKCS10 pkcs10 = new PKCS10(publicKey);\n            signature.initSign(privateKey);\n            pkcs10.encodeAndSign(new X500Name(subject), signature);\n            return pkcs10;\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // -------------------------------------------create cert ext-----------------------------------------\n    /**\n     * 创建默认的扩展信息\n     * @param isCA {@code true} is create CA cert\n     *             {@code false} is create subject cert\n     * @return\n     */\n    public static CertificateExtensions createExtensions(boolean isCA) {\n        try {\n            CertificateExtensions extensions = new CertificateExtensions();\n            //byte[] userData;\n\n            // 密钥用法\n            KeyUsageExtension keyUsage = new KeyUsageExtension();\n            keyUsage.set(KeyUsageExtension.DIGITAL_SIGNATURE, true); // 支持数据签名\n            if (isCA) {\n                //userData = \"Digital Signature, Certificate Signing, Off-line CRL Signing, CRL Signing (86)\".getBytes();\n\n                keyUsage.set(KeyUsageExtension.KEY_ENCIPHERMENT, true); // 支持密钥加密\n                keyUsage.set(KeyUsageExtension.KEY_AGREEMENT, true); // 支持密钥协议\n                keyUsage.set(KeyUsageExtension.KEY_CERTSIGN, true); // 支持证书签名\n                keyUsage.set(KeyUsageExtension.CRL_SIGN, true); // 支持吊销列表签名\n            } else {\n                //userData = \"Digital Signature, Data Encipherment (90)\".getBytes();\n\n                keyUsage.set(KeyUsageExtension.DATA_ENCIPHERMENT, true); // 支持数据加密\n\n                // 增强密钥用法\n                Vector<ObjectIdentifier> extendedKeyUsage = new Vector<>();\n                extendedKeyUsage.add(new ObjectIdentifier(new int[] { 1, 3, 6, 1, 5, 5, 7, 3, 3 })); // 代码签名\n                extensions.set(ExtendedKeyUsageExtension.NAME, new ExtendedKeyUsageExtension(extendedKeyUsage));\n            }\n            extensions.set(KeyUsageExtension.NAME, keyUsage);\n\n            /*// 版本号：v1、v2、v3，此扩展信息必须是v3版本，生成一个extension对象参数分别为oid，是否关键扩展，byte[]型的内容值\n            ObjectIdentifier oid = new ObjectIdentifier(new int[] { 1, 22 }); // 扩展域:第1位最大为2，第2位最大为39，后续不明\n            userData = ObjectUtils.concat(new byte[] { 0x04, (byte) userData.length }, userData); // flag,data length, data\n            // PKCS7Signature验证签名会报错：java.security.SignatureException: Certificate has unsupported critical extension(s)\n            extensions.set(\"UserData\", new sun.security.x509.Extension(oid, true, userData)); */\n\n            return extensions;\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // -------------------------------------------private methods----------------------------------------\n    /**\n     * 根据pkcs10创建证书\n     * @param sn\n     * @param pkcs10\n     * @param notBefore\n     * @param notAfter\n     * @param extensions\n     * @return\n     */\n    private static X509CertInfo createCertInfo(@Nullable BigInteger sn, PKCS10 pkcs10, Date notBefore, \n                                               Date notAfter, CertificateExtensions extensions) {\n        if (sn == null) {\n            //sn = BigInteger.valueOf(ThreadLocalRandom.current().nextLong() & Long.MAX_VALUE);\n            sn = new BigInteger(1, UuidUtils.uuid());\n        }\n        try {\n            // 验证pkcs10\n            PKCS10CertificationRequest req = new PKCS10CertificationRequest(pkcs10.getEncoded());\n            JcaContentVerifierProviderBuilder builder = new JcaContentVerifierProviderBuilder();\n            builder.setProvider(Providers.BC);\n            if (!req.isSignatureValid(builder.build(req.getSubjectPublicKeyInfo()))) {\n                throw new SecurityException(\"Invalid pkcs10 signature data.\");\n            }\n\n            /*org.bouncycastle.jce.PKCS10CertificationRequest req = \n            new org.bouncycastle.jce.PKCS10CertificationRequest(pkcs10.getEncoded());\n            if (!req.verify()) {\n                throw new SecurityException(\"Invalid pkcs10 signature data.\");\n            }*/\n\n            AlgorithmId signAlg = AlgorithmId.get(req.getSignatureAlgorithm().getAlgorithm().getId());\n            X509CertInfo x509certInfo = new X509CertInfo();\n            x509certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));\n            x509certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));\n            x509certInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(signAlg));\n            x509certInfo.set(X509CertInfo.SUBJECT, pkcs10.getSubjectName());\n            x509certInfo.set(X509CertInfo.KEY, new CertificateX509Key(pkcs10.getSubjectPublicKeyInfo()));\n            x509certInfo.set(X509CertInfo.VALIDITY, new CertificateValidity(notBefore, notAfter));\n            if (extensions != null) {\n                x509certInfo.set(X509CertInfo.EXTENSIONS, extensions);\n            }\n            return x509certInfo;\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 自签名证书（根证书）\n     * @param caKey\n     * @param caCertInfo\n     * @return\n     */\n    private static X509Certificate selfSign(PrivateKey caKey, X509CertInfo caCertInfo) {\n        try {\n            CertificateAlgorithmId algId = (CertificateAlgorithmId) caCertInfo.get(X509CertInfo.ALGORITHM_ID);\n            caCertInfo.set(X509CertInfo.ISSUER, caCertInfo.get(X509CertInfo.SUBJECT));\n            X509CertImpl signedCert = new X509CertImpl(caCertInfo);\n            signedCert.sign(caKey, algId.get(CertificateAlgorithmId.ALGORITHM).getName()); // 签名\n            return signedCert;\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * CA签名证书\n     * @param caCert\n     * @param caKey\n     * @param subjectCertInfo\n     * @return\n     */\n    private static X509Certificate caSign(X509Certificate caCert, PrivateKey caKey, X509CertInfo subjectCertInfo) {\n        try {\n            // 从CA的证书中提取签发者的信息\n            X509CertImpl caCertImpl = new X509CertImpl(caCert.getEncoded());\n\n            // 获取X509CertInfo对象\n            X509CertInfo caCertInfo = (X509CertInfo) caCertImpl.get(X509CertImpl.NAME + \".\" + X509CertImpl.INFO);\n\n            // 获取X509Name类型的签发者信息\n            X500Name issuer = (X500Name) caCertInfo.get(X509CertInfo.SUBJECT + \".\" + CertificateIssuerName.DN_NAME);\n\n            subjectCertInfo.set(X509CertInfo.ISSUER, issuer);\n            X509CertImpl signedCert = new X509CertImpl(subjectCertInfo);\n            signedCert.sign(caKey, caCert.getSigAlgName()); // 使用CA私钥对其签名\n            return signedCert;\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/cert/X509CertInfo.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.cert;\n\n/**\n  * <pre>\n  *  CN=测试证书,OU=20121219,O=XXXCA,L=深圳市,ST=广东省,C=CN\n  *    CN：公用名称 (Common Name) 简称：CN 字段，对于 SSL 证书，一般为网站域名；而对于代码签名证书则为申请单位名称；而对于客户端证书则为证书申请者的姓名；\n  *     O：单位名称 (Organization Name) ：简称：ON（O） 字段，对于 SSL 证书，一般为网站域名；而对于代码签名证书则为申请单位名称；而对于客户端单位证书则为证书申请者所在单位名称；\n  *    OU：部门或分部的名称 (Organization Util)，简称：OU字段，一般为机构代码或个人身份证号码\n  *     L：所在城市 (Locality) 简称：L 字段\n  *    ST：所在省份 (State/Provice) 简称：S（ST） 字段\n  *     C：所在国家 (Country) 简称：C 字段，只能是国家字母缩写，如中国：CN\n  * 其它字段：\n  *    电子邮件 (Email) 简称：E 字段\n  *    多个姓名字段 简称：G 字段\n  *    介绍：Description 字段\n  *    电话号码：Phone 字段，格式要求 + 国家区号 城市区号 电话号码，如： +86 732 88888888\n  *    地址：STREET  字段\n  *    邮政编码：PostalCode 字段\n  * </pre>\n  * \n * 证书信息枚举类\n * \n * @author Ponfee\n */\npublic enum X509CertInfo {\n\n    SUBJECT_DN(\"主题\"), ISSUER_DN(\"颁发者主题\"), //\n    CERT_SN(\"序列号\"), VERSION(\"版本\"), ALG_NAME(\"算法名称\"), //\n    START_TM(\"生效时间(格式：yyyy-MM-dd'T'HH:mm:ss.SSSZ)\"), //\n    END_TM(\"失效时间(格式：yyyy-MM-dd'T'HH:mm:ss.SSSZ)\"), //\n    USAGE(\"密钥用法(signature签名，encipherment加密)\"), //\n    PUBLIC_KEY(\"公钥(base64编码)\"), //\n\n    SUBJECT_CN(\"CN\", \"证书主体(CN)\"), SUBJECT_O(\"O\", \"证书主体(O)\"), //\n    SUBJECT_OU(\"OU\", \"证书主体(OU)\"), SUBJECT_L(\"L\", \"证书主体(L)\"), //\n    SUBJECT_ST(\"ST\", \"证书主体(ST)\"), SUBJECT_C(\"C\", \"证书主体(C)\"), //\n\n    ISSUER_CN(\"CN\", \"证书颁发者(CN)\"), ISSUER_O(\"O\", \"证书颁发者(O)\"), //\n    ISSUER_OU(\"OU\", \"证书颁发者(OU)\"), ISSUER_L(\"L\", \"证书颁发者(L)\"), //\n    ISSUER_ST(\"ST\", \"证书颁发者(ST)\"), ISSUER_C(\"C\", \"证书颁发者(C)\"); //\n\n    private final String attr;\n    private final String desc;\n\n    X509CertInfo(String desc) {\n        this(null, desc);\n    }\n\n    X509CertInfo(String attr, String desc) {\n        this.attr = attr;\n        this.desc = desc;\n    }\n\n    public String attr() {\n        return this.attr;\n    }\n\n    public String desc() {\n        return desc;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/cert/X509CertUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.cert;\n\nimport cn.ponfee.commons.io.Closeables;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.Providers;\nimport org.apache.commons.codec.binary.Hex;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.time.FastDateFormat;\nimport org.bouncycastle.asn1.ASN1InputStream;\nimport org.bouncycastle.asn1.ASN1Sequence;\nimport org.bouncycastle.asn1.x509.Certificate;\nimport org.bouncycastle.cert.X509CertificateHolder;\nimport org.bouncycastle.cms.CMSSignedData;\nimport org.bouncycastle.cms.SignerInformation;\nimport org.bouncycastle.cms.SignerInformationStore;\nimport org.bouncycastle.jce.provider.X509CRLObject;\nimport org.bouncycastle.jce.provider.X509CRLParser;\nimport org.bouncycastle.jce.provider.X509CertificateObject;\nimport org.bouncycastle.openssl.jcajce.JcaPEMWriter;\nimport org.bouncycastle.util.Store;\n\nimport java.io.*;\nimport java.nio.charset.StandardCharsets;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509CRL;\nimport java.security.cert.X509CRLEntry;\nimport java.security.cert.X509Certificate;\nimport java.util.Base64;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 证书工具类\n * \n * @author Ponfee\n */\n@SuppressWarnings({ \"deprecation\" })\npublic class X509CertUtils {\n\n    private static final String X509 = \"X.509\";\n    private static final char[] ENDBOUNDARY = \"-----END\".toCharArray();\n    private static final FastDateFormat DATE_FORMAT =\n        FastDateFormat.getInstance(\"yyyy-MM-dd'T'HH:mm:ss.SSSZ\");\n\n    /**\n     * pem加载证书\n     * @param pem\n     * @return\n     */\n    public static X509Certificate loadPemCert(String pem) {\n        return loadX509Cert(pem.getBytes());\n    }\n\n    /**\n     * load from cert bytes or pem bytes\n     * @param bytes\n     * @return\n     */\n    public static X509Certificate loadX509Cert(byte[] bytes) {\n        CertificateFactory cf = Providers.getCertificateFactory(X509);\n        try {\n            // RSA证书\n            return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));\n        } catch (Exception e) {\n            // SM2证书\n            ASN1InputStream input = null;\n            try {\n                if (isBase64(new ByteArrayInputStream(bytes))) {\n                    // base64（pem）编码证书\n                    bytes = base64ToBinary(new ByteArrayInputStream(bytes));\n                }\n                input = new ASN1InputStream(new ByteArrayInputStream(bytes));\n                ASN1Sequence seq = (ASN1Sequence) input.readObject();\n                //X509CertificateStructure struct = new org.bouncycastle.asn1.x509.X509CertificateStructure(seq); // bcmail-jdk16\n                Certificate struct = Certificate.getInstance(seq); // bcmail-jdk15on\n\n                // JDK1.5可以运行，并且可以获取SM2 publicKey\n                // JDK1.6不行：Unknown named curve: 1.2.156.10197.1.301\n                /*DERObject publicKey = struct.getSubjectPublicKeyInfo().getPublicKey();\n                struct.getSubjectPublicKeyInfo().getPublicKeyData();\n                byte[] encodedPublicKey = publicKey.getEncoded();\n                byte[] eP = Arrays.copyOfRange(encodedPublicKey, 5, 69);*/\n\n                return new X509CertificateObject(struct);\n            } catch (Exception ex) {\n                SecurityException se = new SecurityException(e.getMessage() + \"; \" + ex.getMessage());\n                se.setStackTrace(ArrayUtils.addAll(e.getStackTrace(), ex.getStackTrace()));\n                throw se;\n            } finally {\n                Closeables.console(input);\n            }\n        }\n    }\n\n    /**\n     * 根据证书文件流加载证书\n     * @param input\n     * @return\n     * @throws IOException\n     */\n    public static X509Certificate loadX509Cert(InputStream input) throws IOException {\n        return loadX509Cert(IOUtils.toByteArray(input));\n    }\n\n    /**\n     * 通过证书文件路径加载证书\n     * @param certFile\n     * @return\n     * @throws IOException\n     */\n    public static X509Certificate loadX509Cert(File certFile) throws IOException {\n        return loadX509Cert(IOUtils.toByteArray(new FileInputStream(certFile)));\n    }\n\n    /**\n     * certpem = \"-----BEGIN CERTIFICATE-----\\n\" +\n     *           toBase64Encoded(chain[0].getEncoded())) +\n     *           \"\\n-----END CERTIFICATE-----\\n\";\n     * certificate export to pem format text\n     *\n     * java.security.cert.Certificate\n     * X509Certificate,X509CRL,KeyPair,PrivateKey,PublicKey\n     *\n     * @param obj\n     * @return\n     */\n    public static String exportToPem(Object obj) {\n        try (StringWriter writer = new StringWriter();\n             JcaPEMWriter pemWriter = new JcaPEMWriter(writer)\n        ) {\n            pemWriter.writeObject(obj);\n            pemWriter.flush();\n            return writer.toString();\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ---------------------------------------crl--------------------------------------\n    /**\n     * 根据byte流获取吊销列表\n     * @param bytes\n     * @return\n     */\n    public static X509CRL loadX509Crl(byte[] bytes) {\n        ByteArrayInputStream bais;\n        CertificateFactory cf = Providers.getCertificateFactory(X509);\n        try {\n            bais = new ByteArrayInputStream(bytes);\n            //构建X509工厂\n            //生成X509格式的CRL对象并返回\n            return (X509CRL) cf.generateCRL(bais);\n        } catch (Exception e) {\n            X509CRLParser parser = new X509CRLParser();\n            try {\n                ByteArrayInputStream in = new ByteArrayInputStream(bytes);\n                parser.engineInit(in);\n                return (X509CRLObject) parser.engineRead();\n            } catch (Exception ex) {\n                SecurityException se = new SecurityException(e.getMessage() + \"; \" + ex.getMessage());\n                se.setStackTrace(ArrayUtils.addAll(e.getStackTrace(), ex.getStackTrace()));\n                throw se;\n            }\n        }\n    }\n\n    /**\n     * 获取crl\n     * @throws IOException\n     */\n    public static X509CRL loadX509Crl(InputStream is) throws IOException {\n        return loadX509Crl(IOUtils.toByteArray(is));\n    }\n\n    /**\n     * 加载CRL\n     * @param crlFile\n     * @return\n     * @throws IOException\n     */\n    public static X509CRL loadX509Crl(File crlFile) throws IOException {\n        return loadX509Crl(IOUtils.toByteArray(new FileInputStream(crlFile)));\n    }\n\n    /**\n     * 获取证书掉销实体\n     * @param crlFile\n     * @param certFile\n     * @return\n     * @throws IOException\n     */\n    public static X509CRLEntry getX509CrlEntry(File crlFile, File certFile) throws IOException {\n        X509CRL crl = loadX509Crl(crlFile);\n        X509Certificate cert = loadX509Cert(certFile);\n        return crl.getRevokedCertificate(cert);\n    }\n\n    /**\n     * 获取证书扩展项信息\n     * @param cert\n     * @param oid\n     * @return\n     */\n    public static String getCertExtVal(X509Certificate cert, String oid) {\n        byte[] bytes = cert.getExtensionValue(oid);\n        String reuslt = null;\n        if (null != bytes && bytes.length > 0) {\n            //String result = new String(bytes); \n            //if (result.charAt(0) == 12) result = result.substring(2);\n            String value = new String(bytes);\n            reuslt = value.substring(4);\n        }\n        return reuslt;\n    }\n\n    /**\n     * 查询证书信息\n     * @param cert\n     * @param info\n     * @return\n     * @throws IOException\n     */\n    public static String getCertInfo(X509Certificate cert, X509CertInfo info) {\n        try {\n            switch (info) {\n                case VERSION:\n                    return Integer.toString(cert.getVersion());\n                case CERT_SN:\n                    return Hex.encodeHexString(cert.getSerialNumber().toByteArray(), false);\n                case ALG_NAME:\n                    return cert.getSigAlgName();\n                case START_TM:\n                    return DATE_FORMAT.format(cert.getNotBefore());\n                case END_TM:\n                    return DATE_FORMAT.format(cert.getNotAfter());\n                case SUBJECT_DN:\n                    return cert.getSubjectDN().getName();\n                case ISSUER_DN:\n                    return cert.getIssuerDN().getName();\n                case PUBLIC_KEY:\n                    return Base64.getEncoder().encodeToString(cert.getPublicKey().getEncoded());\n                case USAGE:\n                    if (cert.getKeyUsage()[0]) {\n                        return \"signature\";\n                    } else if (cert.getKeyUsage()[3]) {\n                        return \"encipherment\";\n                    } else {\n                        return null;\n                    }\n                case SUBJECT_C:\n                case SUBJECT_CN:\n                case SUBJECT_L:\n                case SUBJECT_O:\n                case SUBJECT_OU:\n                case SUBJECT_ST:\n                    return parseCertDN(cert.getSubjectDN().getName(), info);\n                case ISSUER_C:\n                case ISSUER_CN:\n                case ISSUER_L:\n                case ISSUER_O:\n                case ISSUER_OU:\n                case ISSUER_ST:\n                    return parseCertDN(cert.getIssuerDN().getName(), info);\n                default:\n                    return null;\n            }\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    /**\n     * 筛选证书主题信息\n     * @param certDN\n     * @param ci\n     * @return\n     */\n    private static String parseCertDN(String certDN, X509CertInfo ci) {\n        String type = ci.attr() + \"=\";\n        String[] split = certDN.split(\",\");\n        for (String x : split) {\n            if (x.contains(type)) {\n                return x.trim().substring(type.length());\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 解析PKCS7（SM2证书）\n     * @param p7bytes\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static Map<String, Object> parseP7(byte[] p7bytes) {\n        try {\n            Map<String, Object> result = new HashMap<>(3);\n            CMSSignedData cms = new CMSSignedData(p7bytes);\n            result.put(\"content\", cms.getSignedContent().getContent()); // 原文\n\n            Store<?> certStore = cms.getCertificates();\n            SignerInformationStore signerStore = cms.getSignerInfos();\n            Collection<SignerInformation> signers = signerStore.getSigners();\n            //List<X509CertificateObject> certs = new ArrayList<>(); // 报错\n            X509CertificateObject[] certs = new X509CertificateObject[signers.size()];\n            int i = 0;\n            for (SignerInformation signer : signers) {\n                Collection<X509CertificateHolder> certChain = certStore.getMatches(signer.getSID());\n                Certificate cert = certChain.iterator().next().toASN1Structure(); // bcmail-jdk15on\n                certs[i++] = new X509CertificateObject(cert);\n            }\n            result.put(\"certs\", certs);\n            result.put(\"signers\", cms.getSignerInfos().getSigners()); // 签名值（支持多人签名）\n            return result;\n        } catch (Exception e) {\n            throw new SecurityException(\"解析P7S异常\", e);\n        }\n    }\n\n    // --------------以下是解析Base64(pem)格式证书所用到的方法 start----------------- //\n    private static boolean isBase64(InputStream inputstream) throws IOException {\n        try {\n            if (!inputstream.markSupported()) {\n                byte[] abyte0 = getTotalBytes(new BufferedInputStream(inputstream));\n                inputstream = new ByteArrayInputStream(abyte0);\n            }\n\n            if (inputstream.available() >= 10) {\n                inputstream.mark(10);\n                int i = inputstream.read();\n                int j = inputstream.read();\n                int k = inputstream.read();\n                int l = inputstream.read();\n                int i1 = inputstream.read();\n                int j1 = inputstream.read();\n                int k1 = inputstream.read();\n                int l1 = inputstream.read();\n                int i2 = inputstream.read();\n                int j2 = inputstream.read();\n                inputstream.reset();\n                return i == 45 && j == 45 \n                    && k == 45 && l == 45 \n                    && i1 == 45 && j1 == 66 \n                    && k1 == 69 && l1 == 71 \n                    && i2 == 73 && j2 == 78;\n            } else {\n                return false;\n            }\n        } finally {\n            Closeables.console(inputstream);\n        }\n    }\n\n    private static byte[] getTotalBytes(InputStream input)\n        throws IOException {\n        byte[] abyte0 = new byte[8192];\n        ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);\n        baos.reset();\n        for (int len; (len = input.read(abyte0, 0, abyte0.length)) != Files.EOF;) {\n            baos.write(abyte0, 0, len);\n        }\n        return baos.toByteArray();\n    }\n\n    private static byte[] base64ToBinary(InputStream input) throws IOException {\n        try {\n            long len = 0L;\n            input.mark(input.available());\n            BufferedInputStream bufferedinputstream = new BufferedInputStream(input);\n            BufferedReader reader = new BufferedReader(\n                new InputStreamReader(bufferedinputstream, StandardCharsets.US_ASCII)\n            );\n            String s;\n            if ((s = readLine(reader)) == null || !s.startsWith(\"-----BEGIN\")) {\n                throw new IOException(\"Unsupported encoding\");\n            }\n            len += s.length();\n            StringBuilder sb = new StringBuilder();\n            for (; (s = readLine(reader)) != null && !s.startsWith(\"-----END\"); sb.append(s)) {\n                // do-non\n            }\n\n            if (s == null) {\n                throw new IOException(\"Unsupported encoding\");\n            } else {\n                len += s.length();\n                len += sb.length();\n                input.reset();\n                input.skip(len);\n                return Base64.getDecoder().decode(sb.toString());\n            }\n        } finally {\n            Closeables.console(input);\n        }\n    }\n\n    private static String readLine(BufferedReader bufferedreader)\n        throws IOException {\n        int j = 0;\n        boolean flag = true;\n        boolean flag1 = false;\n        StringBuilder builder = new StringBuilder(80);\n        int i;\n        do {\n            i = bufferedreader.read();\n            if (flag && j < ENDBOUNDARY.length) {\n                flag = (char) i == ENDBOUNDARY[j++];\n            }\n            if (!flag1) {\n                flag1 = flag && j == ENDBOUNDARY.length;\n            }\n            builder.append((char) i);\n        } while (i != -1 && i != 10 && i != 13);\n\n        if (!flag1 && i == -1) {\n            return null;\n        }\n        if (i == 13) {\n            bufferedreader.mark(1);\n            int k = bufferedreader.read();\n            if (k == 10) {\n                builder.append((char) i);\n            } else {\n                bufferedreader.reset();\n            }\n        }\n        return builder.toString();\n    }\n    // --------------以上是解析Base64(pem)格式证书所用到的方法 end----------------- //\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/digest/DigestUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.digest;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.DigestAlgorithms;\nimport cn.ponfee.commons.jce.Providers;\nimport org.apache.commons.codec.binary.Hex;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.Provider;\n\n/**\n * digest算法封装\n * \n * @author Ponfee\n */\npublic final class DigestUtils {\n\n    private static final int BUFF_SIZE = 4096;\n\n    public static byte[] md5(InputStream input) {\n        return digest(DigestAlgorithms.MD5, input);\n    }\n\n    public static byte[] md5(byte[] data) {\n        return digest(DigestAlgorithms.MD5, data);\n    }\n\n    public static String md5Hex(InputStream input) {\n        return Hex.encodeHexString(md5(input));\n    }\n\n    public static String md5Hex(byte[] data) {\n        return Hex.encodeHexString(md5(data));\n    }\n\n    public static String md5Hex(String data) {\n        return md5Hex(data.getBytes());\n    }\n\n    public static String md5Hex(String data, String charset) {\n        return md5Hex(data.getBytes(Charset.forName(charset)));\n    }\n\n    public static byte[] sha1(InputStream input) {\n        return digest(DigestAlgorithms.SHA1, input);\n    }\n\n    public static byte[] sha1(String data) {\n        return digest(DigestAlgorithms.SHA1, data.getBytes());\n    }\n\n    public static byte[] sha1(byte[] data) {\n        return digest(DigestAlgorithms.SHA1, data);\n    }\n\n    public static String sha1Hex(InputStream input) {\n        return Hex.encodeHexString(sha1(input));\n    }\n\n    public static String sha1Hex(byte[] data) {\n        return Hex.encodeHexString(sha1(data));\n    }\n\n    public static String sha1Hex(String data) {\n        return sha1Hex(data.getBytes());\n    }\n\n    public static String sha1Hex(String data, String charset) {\n        return sha1Hex(data.getBytes(Charset.forName(charset)));\n    }\n\n    public static byte[] sha224(byte[] data) {\n        return digest(DigestAlgorithms.SHA224, data);\n    }\n\n    public static String sha224Hex(byte[] data) {\n        return Hex.encodeHexString(sha224(data));\n    }\n\n    public static byte[] sha256(byte[] data) {\n        return digest(DigestAlgorithms.SHA256, data);\n    }\n\n    public static String sha256Hex(byte[] data) {\n        return Hex.encodeHexString(sha256(data));\n    }\n\n    public static String sha256Hex(String data, String charset) {\n        return sha256Hex(data.getBytes(Charset.forName(charset)));\n    }\n\n    public static byte[] sha384(byte[] data) {\n        return digest(DigestAlgorithms.SHA384, data);\n    }\n\n    public static String sha384Hex(byte[] data) {\n        return Hex.encodeHexString(sha384(data));\n    }\n\n    public static byte[] sha512(byte[] input, byte[]... data) {\n        return digest(DigestAlgorithms.SHA512, input, data);\n    }\n\n    public static String sha512Hex(byte[] data) {\n        return Hex.encodeHexString(sha512(data));\n    }\n\n    // ---------------------------------------RipeMD\n    public static byte[] ripeMD128(byte[] data) {\n        return digest(DigestAlgorithms.RipeMD128, Providers.BC, data);\n    }\n\n    public static String ripeMD128Hex(byte[] data) {\n        return Hex.encodeHexString(ripeMD128(data));\n    }\n\n    public static byte[] ripeMD160(byte[] data) {\n        return digest(DigestAlgorithms.RipeMD160, Providers.BC, data);\n    }\n\n    public static String ripeMD160Hex(byte[] data) {\n        return Hex.encodeHexString(ripeMD160(data));\n    }\n\n    public static byte[] ripeMD256(byte[] data) {\n        return digest(DigestAlgorithms.RipeMD256, Providers.BC, data);\n    }\n\n    public static String ripeMD256Hex(byte[] data) {\n        return Hex.encodeHexString(ripeMD256(data));\n    }\n\n    public static byte[] ripeMD320(byte[] data) {\n        return digest(DigestAlgorithms.RipeMD320, Providers.BC, data);\n    }\n\n    public static String ripeMD320Hex(byte[] data) {\n        return Hex.encodeHexString(ripeMD320(data));\n    }\n\n    // ---------------------------------------------digest\n    public static MessageDigest getMessageDigest(DigestAlgorithms alg, Provider provider) {\n        try {\n            return (provider == null)\n                   ? MessageDigest.getInstance(alg.algorithm())\n                   : MessageDigest.getInstance(alg.algorithm(), provider);\n        } catch (NoSuchAlgorithmException e) {\n            throw new IllegalArgumentException(e); // cannot happened\n        }\n    }\n\n    public static byte[] digest(DigestAlgorithms alg, byte[] input) {\n        return digest(alg, null, input);\n    }\n\n    public static byte[] digest(DigestAlgorithms alg, Provider provider, byte[] input) {\n        MessageDigest digest = getMessageDigest(alg, provider);\n        digest.update(input);\n        return digest.digest();\n    }\n\n    public static byte[] digest(DigestAlgorithms alg, byte[] input, byte[]... data) {\n        return digest(alg, null, input, data);\n    }\n\n    /**\n     * 数据摘要\n     * @param alg       digest算法\n     * @param provider\n     * @param data      digest data of byte array\n     * @return\n     */\n    public static byte[] digest(DigestAlgorithms alg, Provider provider,\n                                byte[] input, byte[]... data) {\n        MessageDigest digest = getMessageDigest(alg, provider);\n        digest.update(input);\n        for (byte[] bytes : data) {\n            digest.update(bytes);\n        }\n        return digest.digest();\n    }\n\n    public static byte[] digest(DigestAlgorithms alg, InputStream input) {\n        return digest(alg, null, input);\n    }\n\n    /**\n     * 数据摘要\n     * @param alg        digest 算法\n     * @param provider\n     * @param input      digest data of input stream\n     * @return\n     */\n    public static byte[] digest(DigestAlgorithms alg, Provider provider, \n                                InputStream input) {\n        byte[] buff = new byte[BUFF_SIZE];\n        MessageDigest digest = getMessageDigest(alg, provider);\n\n        try (InputStream in = input) {\n            for (int n; (n = in.read(buff, 0, BUFF_SIZE)) != Files.EOF;) {\n                digest.update(buff, 0, n);\n            }\n            return digest.digest();\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n\n        /*try (InputStream in = input; \n             DigestInputStream dIn = new DigestInputStream(input, digest)\n         ) {\n            while (dIn.read(buff, 0, buff.length) != Files.EOF) {\n                //  do-non\n            }\n            return digest.digest();\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }*/\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/digest/HmacUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.digest;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.HmacAlgorithms;\nimport cn.ponfee.commons.jce.Providers;\nimport org.apache.commons.codec.binary.Hex;\n\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.Provider;\n\n/**\n * HMAC的一个典型应用是用在“质询/响应”（Challenge/Response）身份认证中\n * Hmac算法封装，计算“text”的HMAC：\n * <code>\n *   if (length(K) > blocksize) {\n *       K = H(K) // keys longer than blocksize are shortened\n *   } else if (length(key) < blocksize) {\n *       K += [0x00 * (blocksize - length(K))] // keys shorter than blocksize are zero-padded \n *   }\n *   opad = [0x5c * B] XOR K\n *   ipad = [0x36 * B] XOR K\n *   hash = H(opad + H(ipad + text))\n * </code>\n * 其中：H为散列函数，K为密钥，text为数据，\n *     B表示数据块的字长（the blocksize is that of the underlying hash function）\n * \n * @author Ponfee\n */\npublic final class HmacUtils {\n\n    private static final int BUFF_SIZE = 4096;\n\n    public static byte[] sha1(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacSHA1);\n    }\n\n    public static byte[] sha1(byte[] key, InputStream data) {\n        return crypt(key, data, HmacAlgorithms.HmacSHA1);\n    }\n\n    public static String sha1Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(sha1(key, data));\n    }\n\n    public static String sha1Hex(byte[] key, InputStream data) {\n        return Hex.encodeHexString(sha1(key, data));\n    }\n\n    public static byte[] md5(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacMD5);\n    }\n\n    public static String md5Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(md5(key, data));\n    }\n\n    public static byte[] sha224(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacSHA224);\n    }\n\n    public static String sha224Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(sha224(key, data));\n    }\n\n    public static byte[] sha256(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacSHA256);\n    }\n\n    public static String sha256Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(sha256(key, data));\n    }\n\n    public static byte[] sha384(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacSHA384);\n    }\n\n    public static String sha384Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(sha384(key, data));\n    }\n\n    public static byte[] sha512(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacSHA512);\n    }\n\n    public static String sha512Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(sha512(key, data));\n    }\n\n    public static byte[] ripeMD128(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacRipeMD128, Providers.BC);\n    }\n\n    public static String ripeMD128Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(ripeMD128(key, data));\n    }\n\n    public static byte[] ripeMD160(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacRipeMD160, Providers.BC);\n    }\n\n    public static String ripeMD160Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(ripeMD160(key, data));\n    }\n\n    public static byte[] ripeMD256(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacRipeMD256, Providers.BC);\n    }\n\n    public static String ripeMD256Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(ripeMD256(key, data));\n    }\n\n    public static byte[] ripeMD320(byte[] key, byte[] data) {\n        return crypt(key, data, HmacAlgorithms.HmacRipeMD320, Providers.BC);\n    }\n\n    public static String ripeMD320Hex(byte[] key, byte[] data) {\n        return Hex.encodeHexString(ripeMD320(key, data));\n    }\n\n    public static Mac getInitializedMac(HmacAlgorithms algorithm, byte[] key) {\n        return getInitializedMac(algorithm, null, key);\n    }\n\n    public static Mac getInitializedMac(HmacAlgorithms algorithm, \n                                        Provider provider, byte[] key) {\n        if (key == null) {\n            throw new IllegalArgumentException(\"Null key\");\n        }\n\n        try {\n            Mac mac = (provider == null)\n                      ? Mac.getInstance(algorithm.algorithm()) \n                      : Mac.getInstance(algorithm.algorithm(), provider);\n\n            mac.init(new SecretKeySpec(key, mac.getAlgorithm()));\n            return mac;\n        } catch (final NoSuchAlgorithmException e) {\n            throw new IllegalArgumentException(\"unknown algorithm: \" + algorithm, e);\n        } catch (final InvalidKeyException e) {\n            throw new IllegalArgumentException(\"invalid key: \" + Hex.encodeHexString(key), e);\n        }\n    }\n\n    public static byte[] crypt(byte[] key, byte[] data, HmacAlgorithms alg) {\n        return crypt(key, data, alg, null);\n    }\n\n    public static byte[] crypt(byte[] key, byte[] data, \n                               HmacAlgorithms alg, Provider provider) {\n        return getInitializedMac(alg, provider, key).doFinal(data);\n    }\n\n    public static byte[] crypt(byte[] key, InputStream input, \n                               HmacAlgorithms alg) {\n        return crypt(key, input, alg, null);\n    }\n\n    public static byte[] crypt(byte[] key, InputStream input,\n                               HmacAlgorithms alg, Provider provider) {\n        try (InputStream in = input) {\n            Mac mac = getInitializedMac(alg, provider, key);\n            byte[] buffer = new byte[BUFF_SIZE];\n            for (int n; (n = in.read(buffer, 0, BUFF_SIZE)) != Files.EOF;) {\n                mac.update(buffer, 0, n);\n            }\n            return mac.doFinal();\n        } catch (IOException e) {\n            throw new IllegalArgumentException(\"read data error:\" + e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/Cryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation;\n\n/**\n * This class of the Cryptor base class\n * @author Ponfee\n */\npublic abstract class Cryptor {\n\n    public final byte[] encrypt(byte[] original, Key ek) {\n        return encrypt(original, original.length, ek);\n    }\n\n    /**\n     * encrypt original data in length byte\n     * @param original \n     * @param length the byte length of original\n     * @param ek encrypt key\n     * @return\n     */\n    public abstract byte[] encrypt(byte[] original, int length, Key ek);\n\n    /**\n     * decrypt the cipher use decrypt key\n     * @param cipher\n     * @param dk\n     * @return\n     */\n    public abstract byte[] decrypt(byte[] cipher, Key dk);\n\n    /**\n     * generate cryptor key\n     * @return\n     */\n    public abstract Key generateKey();\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/Key.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * Key interface\n * @author Ponfee\n */\npublic interface Key {\n\n    Key readKey(InputStream in) throws IOException;\n\n    void writeKey(OutputStream out) throws IOException;\n\n    Key getPublic();\n\n    boolean isPublic();\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/NoopCryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation;\n\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * Null Cryptor that do nothing\n * \n * @author Ponfee\n */\npublic final class NoopCryptor extends Cryptor {\n\n    public static final NoopCryptor SINGLETON = new NoopCryptor();\n\n    private NoopCryptor() {}\n\n    @Override\n    public byte[] encrypt(byte[] input, int length, Key ek) {\n        return input;\n    }\n\n    @Override\n    public byte[] decrypt(byte[] cipher, Key dk) {\n        return cipher;\n    }\n\n    @Override\n    public Key generateKey() {\n        return NoopKey.SINGLETON;\n    }\n\n    @Override\n    public String toString() {\n        return NoopCryptor.class.getSimpleName();\n    }\n\n    private static final class NoopKey implements Key {\n\n        private static final NoopKey SINGLETON = new NoopKey();\n\n        private NoopKey() {}\n\n        @Override\n        public Key readKey(InputStream in) {\n            return null;\n        }\n\n        @Override\n        public void writeKey(OutputStream out) {}\n\n        @Override\n        public Key getPublic() {\n            return null;\n        }\n\n        @Override\n        public boolean isPublic() {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/digest/RipeMD160Digest.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.digest;\n\n/**\n * The RipeMD160 digest implementation\n * \n * @author Ponfee\n */\npublic class RipeMD160Digest {\n\n    private static final int[][] ARG_ARRAY = {\n        { 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,\n          7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,\n          11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,\n          11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,\n          9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 \n        },\n        { 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,\n          9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,\n          9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,\n          15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,\n          8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 \n        } \n    };\n\n    private static final int[][] IDX_ARRAY = {\n        { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n          7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,\n          3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,\n          1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,\n          4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 \n        },\n        { 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,\n          6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,\n          15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,\n          8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,\n          12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 \n        } \n    };\n\n    /** 链变量 */\n    private static final int[] CHAIN_VAR = {\n        0x67452301, 0xefcdab89,\n        0x98badcfe, 0x10325476,\n        0xc3d2e1f0\n    };\n\n    /** 分组中每块的大小 */\n    private static final int BLOCK_SIZE = 64;\n\n    /** 摘要byte大小 */\n    private static final int DIGEST_SIZE = 20;\n\n    private final int[] digest = new int[CHAIN_VAR.length];\n    private int[] working;\n    private int wOffset;\n    private int byteCount;\n\n    private RipeMD160Digest() {\n        reset();\n    }\n\n    private RipeMD160Digest(RipeMD160Digest d) {\n        System.arraycopy(d.digest, 0, this.digest, 0, d.digest.length);\n        System.arraycopy(d.working, 0, this.working, 0, d.working.length);\n        this.wOffset = d.wOffset;\n        this.byteCount = d.byteCount;\n    }\n\n    public static RipeMD160Digest getInstance() {\n        return new RipeMD160Digest();\n    }\n\n    public static RipeMD160Digest getInstance(RipeMD160Digest d) {\n        return new RipeMD160Digest(d);\n    }\n\n    public void reset() {\n        System.arraycopy(CHAIN_VAR, 0, digest, 0, CHAIN_VAR.length);\n        working = new int[16];\n        wOffset = 0;\n        byteCount = 0;\n    }\n\n    public void update(byte input) {\n        working[wOffset >> 2] ^= ((int) input) << ((wOffset & 3) << 3);\n        wOffset++;\n        if (wOffset == BLOCK_SIZE) {\n            digestBlock(working);\n            for (int j = 0; j < 16; j++) {\n                working[j] = 0;\n            }\n            wOffset = 0;\n        }\n        byteCount++;\n    }\n\n    public void update(byte[] input) {\n        this.update(input, 0, input.length);\n    }\n\n    public void update(byte[] input, int offset, int len) {\n        len = Math.min(len, input.length - offset);\n        for (int i = offset; i < offset + len; i++) {\n            this.update(input[i]);\n        }\n    }\n\n    public void update(String s) {\n        byte[] array = new byte[s.length()];\n        for (int i = 0; i < array.length; i++) {\n            array[i] = (byte) s.charAt(i);\n        }\n        update(array);\n    }\n\n    public byte[] doFinal() {\n        finish(working, byteCount, 0);\n        byte[] result = new byte[digest.length << 2];\n\n        for (int i = 0; i < DIGEST_SIZE; i++) {\n            result[i] = (byte) (digest[i >> 2] >>> ((i & 3) << 3));\n        }\n\n        reset();\n\n        return result;\n    }\n\n    public byte[] doFinal(byte[] input) {\n        this.update(input, 0, input.length);\n        return doFinal();\n    }\n\n    public byte[] doFinal(byte[] input, int offset, int len) {\n        this.update(input, offset, len);\n        return doFinal();\n    }\n\n    // --------------------------------------------------private methods\n    private void digestBlock(int[] X) {\n        int a, b, c, d, e;\n        int A, B, C, D, E;\n        int i = 0, temp, s;\n\n        A = a = digest[0];\n        B = b = digest[1];\n        C = c = digest[2];\n        D = d = digest[3];\n        E = e = digest[4];\n\n        for (; i < 16; i++) {\n            // The 16 FF functions - round 1 */\n            temp = a + (b ^ c ^ d) + X[IDX_ARRAY[0][i]];\n            a = e;\n            e = d;\n            d = (c << 10) | (c >>> 22);\n            c = b;\n            s = ARG_ARRAY[0][i];\n            b = ((temp << s) | (temp >>> (32 - s))) + a;\n\n            // The 16 JJJ functions - parallel round 1 */\n            temp = A + (B ^ (C | ~D)) + X[IDX_ARRAY[1][i]] + 0x50a28be6;\n            A = E;\n            E = D;\n            D = (C << 10) | (C >>> 22);\n            C = B;\n            s = ARG_ARRAY[1][i];\n            B = ((temp << s) | (temp >>> (32 - s))) + A;\n        }\n\n        for (; i < 32; i++) {\n            // The 16 GG functions - round 2 */\n            temp = a + ((b & c) | (~b & d)) + X[IDX_ARRAY[0][i]] + 0x5a827999;\n            a = e;\n            e = d;\n            d = (c << 10) | (c >>> 22);\n            c = b;\n            s = ARG_ARRAY[0][i];\n            b = ((temp << s) | (temp >>> (32 - s))) + a;\n\n            // The 16 III functions - parallel round 2 */\n            temp = A + ((B & D) | (C & ~D)) + X[IDX_ARRAY[1][i]] + 0x5c4dd124;\n            A = E;\n            E = D;\n            D = (C << 10) | (C >>> 22);\n            C = B;\n            s = ARG_ARRAY[1][i];\n            B = ((temp << s) | (temp >>> (32 - s))) + A;\n        }\n\n        for (; i < 48; i++) {\n            // The 16 HH functions - round 3 */\n            temp = a + ((b | ~c) ^ d) + X[IDX_ARRAY[0][i]] + 0x6ed9eba1;\n            a = e;\n            e = d;\n            d = (c << 10) | (c >>> 22);\n            c = b;\n            s = ARG_ARRAY[0][i];\n            b = ((temp << s) | (temp >>> (32 - s))) + a;\n\n            // The 16 HHH functions - parallel round 3 */\n            temp = A + ((B | ~C) ^ D) + X[IDX_ARRAY[1][i]] + 0x6d703ef3;\n            A = E;\n            E = D;\n            D = (C << 10) | (C >>> 22);\n            C = B;\n            s = ARG_ARRAY[1][i];\n            B = ((temp << s) | (temp >>> (32 - s))) + A;\n        }\n\n        for (; i < 64; i++) {\n            // The 16 II functions - round 4 */\n            temp = a + ((b & d) | (c & ~d)) + X[IDX_ARRAY[0][i]] + 0x8f1bbcdc;\n            a = e;\n            e = d;\n            d = (c << 10) | (c >>> 22);\n            c = b;\n            s = ARG_ARRAY[0][i];\n            b = ((temp << s) | (temp >>> (32 - s))) + a;\n\n            // The 16 GGG functions - parallel round 4 */\n            temp = A + ((B & C) | (~B & D)) + X[IDX_ARRAY[1][i]] + 0x7a6d76e9;\n            A = E;\n            E = D;\n            D = (C << 10) | (C >>> 22);\n            C = B;\n            s = ARG_ARRAY[1][i];\n            B = ((temp << s) | (temp >>> (32 - s))) + A;\n        }\n\n        for (; i < 80; i++) {\n            // The 16 JJ functions - round 5 */\n            temp = a + (b ^ (c | ~d)) + X[IDX_ARRAY[0][i]] + 0xa953fd4e;\n            a = e;\n            e = d;\n            d = (c << 10) | (c >>> 22);\n            c = b;\n            s = ARG_ARRAY[0][i];\n            b = ((temp << s) | (temp >>> (32 - s))) + a;\n\n            // The 16 FFF functions - parallel round 5 */\n            temp = A + (B ^ C ^ D) + X[IDX_ARRAY[1][i]];\n            A = E;\n            E = D;\n            D = (C << 10) | (C >>> 22);\n            C = B;\n            s = ARG_ARRAY[1][i];\n            B = ((temp << s) | (temp >>> (32 - s))) + A;\n        }\n\n        /* combine results */\n        D += c + digest[1]; /* final result for MDbuf[0] */\n        digest[1] = digest[2] + d + E;\n        digest[2] = digest[3] + e + A;\n        digest[3] = digest[4] + a + B;\n        digest[4] = digest[0] + b + C;\n        digest[0] = D;\n    }\n\n    private void finish(int[] array, int lswlen, int mswlen) {\n        /* append the bit m_n == 1 */\n        array[(lswlen >> 2) & 15] ^= 1 << (((lswlen & 3) << 3) + 7);\n\n        if ((lswlen & 63) > 55) {\n            /* length goes to next block */\n            digestBlock(array);\n            for (int i = 0; i < 14; i++) {\n                array[i] = 0;\n            }\n        }\n\n        /* append length in bits*/\n        array[14] = lswlen << 3;\n        array[15] = (lswlen >> 29) | (mswlen << 3);\n        digestBlock(array);\n    }\n\n    public static int getDigestSize() {\n        return DIGEST_SIZE;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/digest/SHA1Digest.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.digest;\n\nimport cn.ponfee.commons.math.Maths;\nimport cn.ponfee.commons.util.Bytes;\n\nimport java.util.Arrays;\n\nimport static cn.ponfee.commons.math.Numbers.ZERO_BYTE;\n\n/**\n * <pre>\n * The SHA-1 digest implementation（maximum 2^64 bit length）\n *\n * 异或⊕：(A ^ B)\n * 同或⊙：(A ^ B ^ 1)  or  !(A ^ B)\n * A ≡ A ⊕ K ⊕ K\n *\n * https://www.cnblogs.com/scu-cjx/p/6878853.html\n *\n * 安全性：SHA1所产生的摘要比MD5长32位。若两种散列函数在结构上没有任何问题的话，SHA1比MD5更安全。\n * 速度：两种方法都是主要考虑以32位处理器为基础的系统结构。但SHA1的运算步骤比MD5多了16步，而且SHA1记录单元的长度比MD5多了32位。因此若是以硬件来实现SHA1，其速度大约比MD5慢了25％。\n * 简易性：两种方法都是相当的简单，在实现上不需要很复杂的程序或是大量存储空间。然而总体上来讲，SHA1对每一步骤的操作描述比MD5简单与MD5不同的是SHA1的原始报文长度不能超过2的64次方，另外SHA1的明文长度从低位开始填充<p>\n *\n * 1、按每512bit（64byte）长度进行分组block，可以划分成L份明文分组，我们用Y0,Y1, ...YL-1表示，对于每一个明文分组，都要重复反复的处理\n *\n * 2、最后一组先补一个字节1000 0000(-128)，直到长度满足对512取模后余数是448（若已经是56byte即448bit，补后有57byte，\n *   因此还需要补64-57+56=63byte，会多出一组）\n *\n * 3、最后补8byte即64bit的原始数据长度long值(位长)，此时为448+64=512bit\n *\n * 4、将512位的明文分组划分为16个子明文分组（sub-block），每个子明文分组为32位，使用W[t]（t=0,1,...,15）来表示这16份子明文分组\n *   W[t]存的是int数据，即4个byte为一组的32位的word字\n *\n * 5、16份子明文分组扩展为80份，记为W[t]（t=0,1,...,79），扩充的方法：\n *   > W[t] = W[t]，当0≤t≤15\n *   > W[t] = (W[t-3] ⊕ W[t-8] ⊕ W[t-14] ⊕ W [t-16]) << 1，当16≤t≤79\n *\n * 6、分组处理：接下来，对输入分组进行80个步骤的处理，目的是根据输入分组的信息来改变内部状态，\n *   在对分组处理时，SHA-1中常数Kt如下：\n *   K0 = 0x5A827999    0≤t≤19\n *   K1 = 0x6ED9EBA1   20≤t≤39\n *   K2 = 0x8F1BBCDC   40≤t≤59\n *   K3 = 0xCA62C1D6   60≤t≤79\n *\n *   5个链变量a,b,c,d,e如下：\n *   a = 0x67452301\n *   b = 0xEFCDAB89\n *   c = 0x98BADCFE\n *   d = 0x10325476\n *   e = 0xC3D2E1F0\n *\n *   SHA1有4轮运算，每一轮包括20个步骤一共80步，当第1轮运算中的第1步骤开始处理时a、b、c、d、e五个链接变量中的值先赋值到另外\n *   5个记录单元a′、b′、c′、d′、e′中，这5个值将保留，用于在第4轮的最后一个步骤完成之后与链接变量a、b、c、d、e进行求和操作\n *\n * 7、SHA-1使用了F0,F1,....,F79这样的一个逻辑函数序列，每一个Ft对3个32位双字b,c,d进行操作，产生一个32位双字的输出。\n *   Ft(b,c,d) = (b&c)|((~b)&d)      0≤t≤19\n *   Ft(b,c,d) = b^c^d              20≤t≤39\n *   Ft(b,c,d) = (b&c)|(b&d)|(c&d)  40≤t≤59\n *   Ft(b,c,d) = b^c^d              60≤t≤79\n *\n * 8、W[0] ~ W[19]处理：（注：S为循环左移位操作）\n *   for (int t=0; t<20; t++) {\n *     tmp=K0+F0(b,c,d)+S(5,a)+e+(sh->W[t]); // 将Kt+Ft(b,c,d)+(a<<5)+e+W[t]的结果赋值给临时变量tmp\n *     e=d;                                  // 将链接变量d初始值赋值给链接变量e\n *     d=c;                                  // 将链接变量c初始值赋值给链接变量d\n *     c=S(30,b);                            // 将链接变量b初始值循环左移30位赋值给链接变量c\n *     b=a; a=tmp;                           // 将链接变量a初始值赋值给链接变量b，再将tmp赋值给a\n *   }\n *\n *   W[20] ~ W[39]处理：\n *   for (int t=20; t<40; t++) {\n *     tmp=K1+F1(b,c,d)+S(5,a)+e+(sh->W[t]);\n *     e=d; d=c;\n *     c=S(30,b);\n *     b=a; a=tmp;\n *   }\n *\n *   W[40] ~ W[59]处理：\n *   for (int t=40; t<60; t++) {\n *     tmp=K2+F2(b,c,d)+S(5,a)+e+(sh->W[t]);\n *     e=d; d=c;\n *     c=S(30,b);\n *     b=a; a=tmp;\n *   }\n *\n *   W[60] ~ W[79]处理：\n *   for (int t=60; t<80; t++) {\n *     tmp=K3+F3(b,c,d)+S(5,a)+e+(sh->W[t]);\n *     e=d; d=c;\n *     c=S(30,b);\n *     b=a; a=tmp;\n *   }\n *   即：Kt+Ft(b,c,d)+S(5,a)+e+Wt, a, S(30,b), c, d  →  a, b, c, d, e\n *\n * 9、将循环80个步骤后的值a,b,c,d,e与原始链变量a′、b′、c′、d′、e′相加作为下一个明文分组的输入重复进行以上操作\n *   sh->a′+=a; sh->b′+=b; sh->c′+=c;\n *   sh->d′+=d; sh->e′+=e;\n *\n * 10、最后一个分组处理完成后，最终得到的a,b,c,d,e即为160位的消息摘要\n * </pre>\n *\n * @author Ponfee\n */\npublic class SHA1Digest {\n\n    /** SHA-1分组中每块的大小 */\n    private static final int BLOCK_SIZE = 64;\n\n    /** SHA-1摘要byte大小 */\n    private static final int DIGEST_SIZE = 20;\n\n    private static final int WORK_SIZE = 80;\n\n    /** 填充的边界 */\n    private static final int PADDING_BOUNDS = 448 >>> 3; // 56，long=8byte=64bit\n\n    /** 4个常数K */\n    private static final int K0 = 0x5A827999,\n                             K1 = 0x6ED9EBA1,\n                             K2 = 0x8F1BBCDC,\n                             K3 = 0xCA62C1D6;\n\n    // ---------------------------------------------fields\n    private final  int[]  work = new int[WORK_SIZE];\n    private final byte[] block = new byte[BLOCK_SIZE];\n\n    private int H0, H1, H2, H3, H4, blockOffset;\n    private long dataByteCount;\n\n    private SHA1Digest() {\n        this.reset();\n    }\n\n    private SHA1Digest(SHA1Digest d) {\n        this.H0 = d.H0;\n        this.H1 = d.H1;\n        this.H2 = d.H2;\n        this.H3 = d.H3;\n        this.H4 = d.H4;\n\n        System.arraycopy(d.block, 0, this.block, 0, BLOCK_SIZE);\n        this.blockOffset = d.blockOffset;\n        this.dataByteCount = d.dataByteCount;\n    }\n\n    public static SHA1Digest getInstance() {\n        return new SHA1Digest();\n    }\n\n    public static SHA1Digest getInstance(SHA1Digest d) {\n        return new SHA1Digest(d);\n    }\n\n    public void update(byte input) {\n        this.block[this.blockOffset++] = input;\n        if (this.blockOffset == BLOCK_SIZE) {\n            this.digestBlock(this.block);\n            this.blockOffset = 0;\n            this.dataByteCount += BLOCK_SIZE;\n        }\n    }\n\n    public void update(byte[] input) {\n        this.update(input, 0, input.length);\n    }\n\n    public void update(byte[] input, int offset, int length) {\n        length = Math.min(input.length - offset, length);\n        for (int i = offset, end = offset + length; i < end; i++) {\n            this.update(input[i]);\n        }\n    }\n\n    public byte[] doFinal(byte[] data) {\n        this.update(data, 0, data.length);\n        return this.doFinal();\n    }\n\n    public byte[] doFinal() {\n        this.dataByteCount += this.blockOffset;\n        this.block[this.blockOffset++] = -128; // 填充：先补1000 0000\n        if (this.blockOffset > PADDING_BOUNDS) {\n            Arrays.fill(this.block, this.blockOffset, BLOCK_SIZE, ZERO_BYTE); // 填充0\n            this.digestBlock(this.block);\n\n            // reset a empty block, repadding 0x00 and bit length start 0\n            this.blockOffset = 0;\n        }\n\n        Arrays.fill(this.block, this.blockOffset, PADDING_BOUNDS, ZERO_BYTE);\n\n        long dataLongBitLen = this.dataByteCount << 3; // bitLen=byteCount*8\n        // dataLongBitLen value to byte array and padding in block tail\n        for (int i = 0, j = (Long.BYTES - 1) << 3; i < Long.BYTES; i++, j -= 8) {\n            this.block[PADDING_BOUNDS + i] = (byte) (dataLongBitLen >>> j);\n        }\n\n        this.digestBlock(this.block);\n\n        byte[] digest = new byte[DIGEST_SIZE];\n        Bytes.put(this.H0, digest,  0);\n        Bytes.put(this.H1, digest,  4);\n        Bytes.put(this.H2, digest,  8);\n        Bytes.put(this.H3, digest, 12);\n        Bytes.put(this.H4, digest, 16);\n\n        this.reset();\n\n        return digest;\n    }\n\n    public void reset() {\n        this.H0 = 0x67452301;\n        this.H1 = 0xEFCDAB89;\n        this.H2 = 0x98BADCFE;\n        this.H3 = 0x10325476;\n        this.H4 = 0xC3D2E1F0;\n\n        this.blockOffset   = 0;\n        this.dataByteCount = 0;\n    }\n\n    public static int getDigestSize() {\n        return DIGEST_SIZE;\n    }\n\n    // --------------------------------------------------private methods\n    private void digestBlock(byte[] block) {\n        int i = 0;\n\n        // sub-block（子明文分组）\n        for (int j = 0; i < 16; j += 4) {\n            work[i++] = Bytes.toInt(block, j);\n        }\n\n        // ext-block（扩展明文分组）\n        for (; i < WORK_SIZE; i++) {\n            work[i] = Maths.rotateLeft(work[i - 3] ^ work[i - 8] ^ work[i - 14] ^ work[i - 16], 1);\n        }\n\n        int A = this.H0, B = this.H1, C = this.H2, D = this.H3, E = this.H4, tmp, t = 0;\n\n        // round first\n        for (; t < 20; t++) {\n            // temp = Ki + fi(B, C, D) + S5(A) + E + Wt\n            tmp = K0 + f0(B, C, D) + Maths.rotateLeft(A, 5) + E + work[t];\n\n            // E = D; D = C; C = S30(B); B = A; A = temp;\n            E = D; D = C; C = Maths.rotateLeft(B, 30); B = A; A = tmp;\n        }\n\n        // round second\n        for (; t < 40; t++) {\n            tmp = K1 + f1(B, C, D) + Maths.rotateLeft(A, 5) + E + work[t];\n            E = D; D = C; C = Maths.rotateLeft(B, 30); B = A; A = tmp;\n        }\n\n        // round third\n        for (; t < 60; t++) {\n            tmp = K2 + f2(B, C, D) + Maths.rotateLeft(A, 5) + E + work[t];\n            E = D; D = C; C = Maths.rotateLeft(B, 30); B = A; A = tmp;\n        }\n\n        // round fourth\n        for (; t < WORK_SIZE; t++) {\n            tmp = K3 + f3(B, C, D) + Maths.rotateLeft(A, 5) + E + work[t];\n            E = D; D = C; C = Maths.rotateLeft(B, 30); B = A; A = tmp;\n        }\n\n        // add chain variable\n        this.H0 += A;\n        this.H1 += B;\n        this.H2 += C;\n        this.H3 += D;\n        this.H4 += E;\n    }\n\n    private static int f0(int b, int c, int d) {\n        return (b & c) | ((~b) & d);\n    }\n\n    private static int f1(int b, int c, int d) {\n        return b ^ c ^ d;\n    }\n\n    private static int f2(int b, int c, int d) {\n        return (b & c) | (b & d) | (c & d);\n    }\n\n    private static int f3(int b, int c, int d) {\n        return f1(b, c, d);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/ecc/ECCryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.ecc;\n\nimport cn.ponfee.commons.jce.HmacAlgorithms;\nimport cn.ponfee.commons.jce.implementation.Cryptor;\nimport cn.ponfee.commons.jce.implementation.Key;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.SecureRandoms;\n\nimport java.math.BigInteger;\nimport java.util.Arrays;\n\nimport static cn.ponfee.commons.jce.Providers.BC;\nimport static cn.ponfee.commons.jce.digest.HmacUtils.crypt;\n\n/**\n * EC Cryptor based xor\n * \n * origin ≡ origin ⊕   key ⊕  key\n * \n * 一、首先：生成随机数dk，在曲线上计算得到dk的倍点beta point， \n *        beta point(public key) = basePointG(public key) * dk，\n *        beta point(public key)作为公钥，dk作为私钥\n * \n * 二、加密：1）生成随机数rk，在曲线上计算得到rk的倍点gamma point，\n *          gamma point(public key) = basePointG(public key) * rk，\n *          由椭圆曲线特性可得出：beta point(public key) * rk = ECPoint S = gamma point(public key) * dk\n * \n *          2）ECPoint S = beta point(public key) * rk，把ECPoint S作为中间对称密钥，\n *            通过HASH函数计算对称加密密钥：key = HmacSHA-512(ECPoint S)\n * \n *          3）加密：origin ⊕  key = cipher \n * \n *          4）打包加密数据Encrypted = {gamma point(public key), cipher}\n * \n * 三、解密：1）解析加密数据Encrypted： {gamma point(public key), cipher}，得到：gamma point(public key)，cipher\n * \n *          2）用第一步的私钥dk与gamma point(public key)进行计算得到：ECPoint S = gamma point(public key) * dk\n * \n *          3）通过HASH函数计算对称加密密钥：key = HmacSHA-512(ECPoint S)\n * \n *          4）解密：cipher ⊕   key = origin\n * \n * @author Ponfee\n */\npublic class ECCryptor extends Cryptor {\n\n    private static final HmacAlgorithms HMAC_ALG = HmacAlgorithms.HmacSHA3_512;\n\n    private final EllipticCurve curve;\n\n    public ECCryptor(EllipticCurve curve) {\n        this.curve = curve;\n    }\n\n    /**\n     * 加密数据逻辑：\n     * origin ≡ origin ⊕ data ⊕ data\n     */\n    @Override\n    public byte[] encrypt(byte[] input, int length, Key ek) {\n        // ek is an Elliptic key (dk=secret, beta=public)\n        ECKey ecKey = (ECKey) ek;\n\n        // 生成随机数rk\n        BigInteger rk;\n        if (ecKey.curve.getN() != null) {\n            rk = SecureRandoms.random(ecKey.curve.getN());\n        } else {\n            rk = SecureRandoms.random(ecKey.curve.getP().bitLength() + 17);\n        }\n\n        // 计算曲线上rk倍点gamma：ECPoint gamma = basePointG(public key) * rk\n        ECPoint gamma = ecKey.curve.getBasePointG().multiply(rk);\n\n        // PCS is compressed point size.\n        int offset = ecKey.curve.getPCS();\n\n        // 导出该rk倍点gamma point(public key)\n        byte[] result = Arrays.copyOf(gamma.compress(), offset + length);\n\n        // 生成需要hash的数据：ECPoint S = beta point(public key) * rk\n        ECPoint secure = ecKey.beta.multiply(rk);\n\n        // 用hash值与原文进行xor操作\n        byte[] keyBytes = Bytes.concat(secure.getX().toByteArray(), \n                                       secure.getY().toByteArray());\n        int count = 1;\n        byte[] hashedKey = crypt(keyBytes, Bytes.toBytes(count), HMAC_ALG, BC);\n        for (int i = 0, keyOffset = 0; i < length; i++) {\n            if (keyOffset == HMAC_ALG.byteSize()) {\n                keyOffset = 0;\n                hashedKey = crypt(keyBytes, Bytes.toBytes(++count), HMAC_ALG, BC);\n            }\n            result[i + offset] = (byte) (input[i] ^ hashedKey[keyOffset++]);\n        }\n        return result;\n    }\n\n    @Override\n    public byte[] decrypt(byte[] input, Key dk) {\n        ECKey ecKey = (ECKey) dk;\n        int offset = ecKey.curve.getPCS();\n\n        // 取出gamma point(public key)\n        byte[] gammacom = Arrays.copyOfRange(input, 0, offset);\n        ECPoint gamma = new ECPoint(gammacom, ecKey.curve);\n\n        // beta point(public key) * rk = ECPoint S = gamma point(public key) * dk\n        // ECPoint S = gamma point(public key) * dk\n        ECPoint secure = gamma.multiply(ecKey.dk);\n\n        byte[] keyBytes;\n        if (secure.isZero()) {\n            keyBytes = Bytes.concat(BigInteger.ZERO.toByteArray(), \n                                    BigInteger.ZERO.toByteArray());\n        } else {\n            keyBytes = Bytes.concat(secure.getX().toByteArray(), \n                                    secure.getY().toByteArray());\n        }\n        int count = 1, length = input.length - offset;\n        byte[] hashedKey = crypt(keyBytes, Bytes.toBytes(count), HMAC_ALG, BC),\n               result = new byte[length];\n        for (int i = 0, keyOffset = 0; i < length; i++) {\n            if (keyOffset == HMAC_ALG.byteSize()) {\n                keyOffset = 0;\n                hashedKey = crypt(keyBytes, Bytes.toBytes(++count), HMAC_ALG, BC);\n            }\n            result[i] = (byte) (input[i + offset] ^ hashedKey[keyOffset++]);\n        }\n        return result;\n    }\n\n    /**\n     * generate ECKey\n     */\n    @Override\n    public Key generateKey() {\n        return new ECKey(curve);\n    }\n\n    @Override\n    public String toString() {\n        return \"ECCryptor - \" + curve.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/ecc/ECKey.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.ecc;\n\nimport cn.ponfee.commons.jce.implementation.Key;\nimport cn.ponfee.commons.util.SecureRandoms;\n\nimport java.io.*;\nimport java.math.BigInteger;\n\n/**\n * This is Elliptic Curve key\n * @author Ponfee\n */\npublic class ECKey implements Key {\n\n    protected boolean secret; // 是否是私钥\n    protected BigInteger dk; // decrypt key\n    protected ECPoint beta; // the public key of ECPoint\n    protected final EllipticCurve curve; // the Elliptic cCurve\n\n    /**\n     * ECKey generates a random secret key (contains also the public key)\n     * @param ec\n     */\n    public ECKey(EllipticCurve ec) {\n        this.curve = ec;\n        this.secret = true;\n\n        // dk is a random num.\n        if (curve.getN() != null) {\n            this.dk = SecureRandoms.random(this.curve.getN());\n        } else {\n            this.dk = SecureRandoms.random(ec.getP().bitLength() + 17);\n        }\n\n        // beta = pointG * dk\n        this.beta = this.curve.getBasePointG().multiply(this.dk); // dk倍点beta\n        this.beta.fastCache();\n    }\n\n    @Override\n    public String toString() {\n        String str = \"\";\n        if (secret) {\n            str = \"Private key: \" + dk + \", \";\n        }\n        return str + \"Public key: \" + beta + \", Curve: \" + curve;\n    }\n\n    @Override\n    public boolean isPublic() {\n        return !secret;\n    }\n\n    @Override\n    public void writeKey(OutputStream out) throws IOException {\n        DataOutputStream output = new DataOutputStream(out);\n        this.curve.writeCurve(output);\n        output.writeBoolean(this.secret);\n        if (this.secret) {\n            byte[] dk0 = this.dk.toByteArray();\n            output.writeInt(dk0.length);\n            output.write(dk0);\n        }\n        byte[] beta0 = this.beta.compress();\n        output.writeInt(beta0.length);\n        output.write(beta0);\n    }\n\n    @Override\n    public Key readKey(InputStream in) throws IOException {\n        DataInputStream input = new DataInputStream(in);\n        ECKey key = new ECKey(new EllipticCurve(input));\n        key.secret = input.readBoolean();\n        if (key.secret) {\n            byte[] dk0 = new byte[input.readInt()];\n            input.read(dk0);\n            key.dk = new BigInteger(1, dk0);\n        }\n        byte[] beta0 = new byte[input.readInt()];\n        input.read(beta0);\n        key.beta = new ECPoint(beta0, key.curve);\n        return key;\n    }\n\n    /**\n     * get the public key\n     */\n    @Override\n    public Key getPublic() {\n        if (!this.secret) {\n            return this;\n        }\n\n        ECKey pubKey = new ECKey(curve);\n        pubKey.beta = beta;\n        pubKey.dk = BigInteger.ZERO;\n        pubKey.secret = false;\n        return pubKey;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/ecc/ECPoint.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.ecc;\n\nimport java.math.BigInteger;\n\n/**\n * The EC point of lie on the curve\n * \n * @author Ponfee\n */\npublic class ECPoint {\n\n    private final EllipticCurve curve;\n    private final boolean zero;\n\n    private BigInteger x;\n    private BigInteger y;\n\n    // fastcache is an array of ECPoints\n    private ECPoint[] fastcache = null;\n\n    public void fastCache() {\n        if (fastcache == null) {\n            // fastcache initialised to 256 EC Points.\n            fastcache = new ECPoint[256];\n            // First point is null.\n            fastcache[0] = new ECPoint(curve);\n            for (int i = 1; i < fastcache.length; i++) { // [1, 256)\n                // add the point repeatedly (Cumulative sum). P,2P,...\n                fastcache[i] = fastcache[i - 1].add(this);\n            }\n        }\n    }\n\n    /** \n     * Constructs a point on an elliptic curve.\n     * @param curve The elliptic curve on wich the point is surposed to lie\n     * @param x     The x coordinate of the point\n     * @param y     The y coordinate of the point\n     */\n    public ECPoint(EllipticCurve curve, BigInteger x, BigInteger y) {\n        this.curve = curve;\n        this.x = x;\n        this.y = y;\n        if (!curve.isOnCurve(this)) {\n            throw new IllegalArgumentException(\"(x,y) is not on this curve!\");\n        }\n        this.zero = false;\n    }\n\n    /**\n     * Decompresses a compressed point stored in a byte-array into a new ECPoint.\n     * @param bytes the array of bytes to be decompressed\n     * @param curve the EllipticCurve the decompressed point is supposed to lie on.\n     */\n    public ECPoint(byte[] bytes, EllipticCurve curve) {\n        this.curve = curve;\n        if (bytes[0] == 2) {\n            this.zero = true;\n            return;\n        }\n\n        boolean ymt = bytes[0] != 0;\n        bytes[0] = 0;\n        this.x = new BigInteger(1, bytes);\n\n        this.y = this.x.multiply(this.x).add(curve.getA()).multiply(this.x)\n                       .add(curve.getB()).modPow(curve.getPSR2(), curve.getP());\n        if (ymt != this.y.testBit(0)) {\n            this.y = curve.getP().subtract(this.y);\n        }\n        this.zero = false;\n    }\n\n    /**\n     * IMPORTANT this renders the values of x and y to be null! \n     * Use this constructor only to create instances of a Zero class!\n     */\n    public ECPoint(EllipticCurve e) {\n        this.x = this.y = BigInteger.ZERO;\n        this.curve = e;\n        this.zero = true;\n    }\n\n    /**\n     * compress the point as byte array data\n     * @return byte array data of this point\n     */\n    public byte[] compress() { // 只导出x坐标，y坐标可由方程计算得到\n        byte[] cmp = new byte[this.curve.getPCS()];\n        if (this.zero) {\n            cmp[0] = 2;\n        }\n        byte[] xb = this.x.toByteArray();\n        System.arraycopy(xb, 0, cmp, this.curve.getPCS() - xb.length, xb.length);\n        if (this.y.testBit(0)) {\n            cmp[0] = 1;\n        }\n        return cmp;\n    }\n\n    /**\n     * 在曲线上计算两点相加的第三个点：point c = point a + point b\n     * @param q The point to be added\n     * @return the sum of this point on the argument\n     */\n    public ECPoint add(ECPoint q) {\n        if (!isSameCurve(q)) {\n            throw new IllegalArgumentException(\n                \"the q point don't lie on the same elliptic curve.\");\n        }\n\n        if (this.isZero()) {\n            return q;\n        } else if (q.isZero()) {\n            return this;\n        }\n\n        BigInteger x1 = this.x, y1 = this.y;\n        BigInteger x2 = q.getX(), y2 = q.getY();\n\n        BigInteger alpha;\n        if (x2.compareTo(x1) == 0) {\n            if (y2.compareTo(y1) != 0) {\n                return new ECPoint(curve); // return a zero point\n            } else {\n                alpha = ((x1.modPow(EllipticCurve.TWO, curve.getP())).multiply(EllipticCurve.THREE)).add(curve.getA());\n                alpha = (alpha.multiply((EllipticCurve.TWO.multiply(y1)).modInverse(curve.getP()))).mod(curve.getP());\n            }\n        } else {\n            BigInteger i = x2.subtract(x1).modInverse(curve.getP());\n            alpha = y2.subtract(y1).multiply(i).mod(curve.getP());\n        }\n\n        BigInteger x3 = (((alpha.modPow(EllipticCurve.TWO, curve.getP())).subtract(x2)).subtract(x1)).mod(curve.getP());\n        BigInteger y3 = ((alpha.multiply(x1.subtract(x3))).subtract(y1)).mod(curve.getP());\n\n        return new ECPoint(curve, x3, y3);\n    }\n\n    /**\n     * 计算k倍点\n     * @param k\n     * @return this * k\n     */\n    public ECPoint multiply(BigInteger k) {\n        ECPoint result = this;\n        for (int i = k.bitCount() - 1; i > 0; i--) {\n            result = result.add(result);\n            if (k.testBit(i)) {\n                result = result.add(this);\n            }\n        }\n        return result;\n    }\n\n    public boolean isZero() {\n        return zero;\n    }\n\n    public BigInteger getX() {\n        return x;\n    }\n\n    public BigInteger getY() {\n        return y;\n    }\n\n    public EllipticCurve getCurve() {\n        return curve;\n    }\n\n    @Override\n    public String toString() {\n        return \"(\" + x.toString() + \", \" + y.toString() + \")\";\n    }\n\n    private boolean isSameCurve(ECPoint p) {\n        return this.curve.equals(p.getCurve());\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/ecc/EllipticCurve.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.ecc;\n\nimport cn.ponfee.commons.jce.ECParameters;\nimport org.apache.commons.lang3.builder.EqualsBuilder;\nimport org.apache.commons.lang3.builder.HashCodeBuilder;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\n\n/**\n * An implementation of an elliptic curve over a finite field.\n * \n * @author Ponfee\n */\npublic class EllipticCurve {\n\n    public static final BigInteger COEFA = new BigInteger(\"4\");\n    public static final BigInteger COEFB = new BigInteger(\"27\");\n    public static final BigInteger TWO = new BigInteger(\"2\");\n    public static final BigInteger THREE = new BigInteger(\"3\");\n\n    private static final int PRIME_SECURITY = 500;\n\n    private final BigInteger a, b, p, n; // n为p的阶\n    private final String name; // the curve name\n    private final int pcs; // the compressed point size.\n\n    private final ECPoint basePointG; // base point G\n    private BigInteger psr2; // (p add one) >> 2\n\n    /** \n     * Constructs an elliptic curve over the finite field of 'mod' elements.\n     * The equation of the curve is on the form : y^2 = x^3 + ax + b.\n     * @param a the value of 'a' where y^2 = x^3 + ax + b\n     * @param b the value of 'b' where y^2 = x^3 + ax + b\n     * @param p the elliptic curve point in finite field\n     */\n    public EllipticCurve(BigInteger a, BigInteger b, BigInteger p) {\n        if (!p.isProbablePrime(PRIME_SECURITY)) {\n            throw new IllegalArgumentException(\"the p is not prime\");\n        }\n        if (isSingular(a, b, p)) {\n            throw new IllegalArgumentException(\"is singular\");\n        }\n\n        this.a = a;\n        this.b = b;\n        this.p = p;\n        this.name = \"cust\";\n\n        byte[] p0 = p.toByteArray();\n        this.pcs = p0[0] == 0 ? p0.length : p0.length + 1;\n        this.n = calculateN();\n        this.basePointG = calculateBasePointG();\n    }\n\n    public EllipticCurve(ECParameters ecp) {\n        if (!ecp.p.isProbablePrime(PRIME_SECURITY)) {\n            throw new IllegalArgumentException(\"the p is not prime\");\n        }\n        if (isSingular(ecp.a, ecp.b, ecp.p)) {\n            throw new IllegalArgumentException(\"the ec parameter is singular\");\n        }\n\n        this.a = ecp.a;\n        this.b = ecp.b;\n        this.p = ecp.p;\n        this.name = ecp.toString();\n\n        byte[] p0 = ecp.p.toByteArray();\n        this.pcs = p0[0] == 0 ? p0.length : p0.length + 1;\n        this.n = ecp.n;\n        this.basePointG = new ECPoint(this, ecp.gx, ecp.gy); // the base point G\n        this.basePointG.fastCache();\n    }\n\n    public EllipticCurve(DataInputStream input) throws IOException {\n        byte[] ab = new byte[input.readInt()];\n        input.read(ab);\n        this.a = new BigInteger(1, ab);\n\n        byte[] bb = new byte[input.readInt()];\n        input.read(bb);\n        this.b = new BigInteger(1, bb);\n\n        byte[] pb = new byte[input.readInt()];\n        input.read(pb);\n        this.p = new BigInteger(1, pb);\n\n        byte[] ob = new byte[input.readInt()];\n        input.read(ob);\n        this.n = new BigInteger(1, ob);\n\n        byte[] gb = new byte[input.readInt()];\n        input.read(gb);\n        this.basePointG = new ECPoint(gb, this);\n\n        byte[] ppb = new byte[input.readInt()];\n        input.read(ppb);\n        this.psr2 = new BigInteger(1, ppb);\n\n        this.pcs = input.readInt();\n\n        this.name = input.readUTF();\n\n        this.basePointG.fastCache();\n    }\n\n    public void writeCurve(DataOutputStream output) throws IOException {\n        byte[] a0 = a.toByteArray();\n        output.writeInt(a0.length);\n        output.write(a0);\n\n        byte[] b0 = b.toByteArray();\n        output.writeInt(b0.length);\n        output.write(b0);\n\n        byte[] p0 = p.toByteArray();\n        output.writeInt(p0.length);\n        output.write(p0);\n\n        byte[] n0 = n.toByteArray();\n        output.writeInt(n0.length);\n        output.write(n0);\n\n        byte[] pointG0 = basePointG.compress();\n        output.writeInt(pointG0.length);\n        output.write(pointG0);\n\n        byte[] ppobf0 = getPSR2().toByteArray();\n        output.writeInt(ppobf0.length);\n        output.write(ppobf0);\n\n        output.writeInt(pcs);\n\n        output.writeUTF(name);\n    }\n\n    public boolean isOnCurve(ECPoint q) {\n        if (q.isZero()) {\n            return true;\n        }\n\n        BigInteger ySquare = (q.getY()).modPow(TWO, p);\n        BigInteger xCube = (q.getX()).modPow(THREE, p);\n        BigInteger dum = ((xCube.add(a.multiply(q.getX()))).add(b)).mod(p);\n        return ySquare.compareTo(dum) == 0;\n    }\n\n    public BigInteger getN() {\n        return n;\n    }\n\n    public ECPoint getZero() {\n        return new ECPoint(this);\n    }\n\n    public BigInteger getA() {\n        return a;\n    }\n\n    public BigInteger getB() {\n        return b;\n    }\n\n    public BigInteger getP() {\n        return p;\n    }\n\n    public int getPCS() {\n        return pcs;\n    }\n\n    public ECPoint getBasePointG() {\n        return basePointG;\n    }\n\n    public BigInteger getPSR2() {\n        if (this.psr2 == null) {\n            this.psr2 = this.p.add(BigInteger.ONE).shiftRight(2);\n        }\n        return this.psr2;\n    }\n\n    @Override\n    public String toString() {\n        if (name == null || name.length() == 0) {\n            return \"y^2 = x^3 + \" + a + \"x + \" + b + \" ( mod \" + p + \" )\";\n        } else {\n            return name;\n        }\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (!(obj instanceof EllipticCurve)) {\n            return false;\n        }\n        EllipticCurve o = (EllipticCurve) obj;\n        return new EqualsBuilder().append(this.a, o.a)\n                                  .append(this.b, o.b)\n                                  .append(this.p, o.p)\n                                  .append(this.n, o.n)\n                                  .isEquals();\n    }\n\n    @Override\n    public int hashCode() {\n        return new HashCodeBuilder().append(this.a)\n                                    .append(this.b)\n                                    .append(this.p)\n                                    .append(this.n)\n                                    .hashCode();\n    }\n\n    private static boolean isSingular(BigInteger a, BigInteger b, BigInteger p) {\n        BigInteger a0 = a.pow(3);\n        BigInteger b0 = b.pow(2);\n        BigInteger result = a0.multiply(COEFA).add(b0.multiply(COEFB)).mod(p);\n        return result.compareTo(BigInteger.ZERO) == 0;\n    }\n\n    /**\n     * calculate mod n\n     * @return\n     */\n    private BigInteger calculateN() {\n        return null; // TODO\n    }\n\n    /**\n     * calculate base point G\n     * @return\n     */\n    private ECPoint calculateBasePointG() {\n        return null; // TODO\n        /*BigInteger x = BigInteger.ONE, y, \n        dum = (x.modPow(THREE, this.p).add(a.multiply(x)).add(b)).mod(p); // x^3 + ax + b\n        long i = 0;\n        do {\n            y = BigInteger.valueOf(i++);\n        } while (y.modPow(TWO, this.p).compareTo(dum) != 0);\n        \n        return new ECPoint(this, x, BigInteger.valueOf(i));*/\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/ecc/package-info.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n/**\n * ECC implementation\n * @author Ponfee\n */\npackage cn.ponfee.commons.jce.implementation.ecc;\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/package-info.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n/**\n * The crypto implementation by self, include digest, rsa and ecc\n * \n * @author Ponfee\n */\npackage cn.ponfee.commons.jce.implementation;\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/rsa/AbstractRSACryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.rsa;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.implementation.Cryptor;\nimport cn.ponfee.commons.jce.implementation.Key;\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.util.SecureRandoms;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.math.BigInteger;\nimport java.util.Arrays;\n\n/**\n * http://blog.51cto.com/xnuil/1698673\n *\n * RSA Cryptor, Without padding\n * RSA私钥解密证明：费马小定理（欧拉定理特例）\n *  等同证明：c^d ≡ m (mod n)\n *      因为：m^e ≡ c (mod n)\n *  于是，c可以写成：c = m^e - kn\n *  将c代入要我们要证明的那个解密规则：(m^e - kn)^d ≡ m (mod n)\n *  等同证明：m^(ed) ≡ m (mod n)\n *  由于：ed ≡ 1 (mod φ(n))\n *  所以：ed = hφ(n)+1\n *  得出：m^(hφ(n)+1) ≡ m (mod n)\n *\n * @author Ponfee\n */\npublic abstract class AbstractRSACryptor extends Cryptor {\n\n    private final boolean isPadding;\n\n    public AbstractRSACryptor(boolean isPadding) {\n        this.isPadding = isPadding;\n    }\n\n    public int getOriginBlockSize(RSAKey rsaKey) {\n        // 减一个byte为了防止溢出(byte array less than mod)\n        // 此时BigInteger(1, byte[getOriginBlockSize(rsaKey)]) < rsaKey.n\n        return rsaKey.n.bitLength() / 8 - 1;\n    }\n\n    public int getCipherBlockSize(RSAKey rsaKey) {\n        return rsaKey.n.bitLength() / 8;\n    }\n\n    // ---------------------------------------------------------------do crypt methods\n    @Override\n    public byte[] encrypt(byte[] input, int length, Key ek) {\n        RSAKey rsaKey = (RSAKey) ek;\n        BigInteger exponent = this.getExponent(rsaKey);\n        //return new BigInteger(1, input).modPow(exponent, rsaKey.n).toByteArray();\n\n        int originBlockSize = this.getOriginBlockSize(rsaKey), // 加密前原文数据块的大小\n            cipherBlockSize = this.getCipherBlockSize(rsaKey); // 加密后密文数据块大小\n\n        ByteArrayOutputStream out = new ByteArrayOutputStream(input.length);\n        byte[] origin, encrypted;\n\n        try {\n            for (int offset = 0, len = input.length, to; offset < len; offset += originBlockSize) {\n                to = Math.min(len, offset + originBlockSize);\n                if (isPadding) {\n                    // 切割并填充原文数据块\n                    origin = encodeBlock(input, offset, to, cipherBlockSize, rsaKey);\n                } else {\n                    // 切割原文数据块\n                    origin = Arrays.copyOfRange(input, offset, to);\n                }\n\n                // 加密：encrypted = origin^e mod n\n                encrypted = new BigInteger(1, origin).modPow(exponent, rsaKey.n).toByteArray();\n\n                // 固定密文长度\n                fixedByteArray(encrypted, cipherBlockSize, out);\n            }\n            return out.toByteArray();\n        } catch (IOException e) {\n            throw new SecurityException(e); // cannot happen\n        }\n    }\n\n    @Override\n    public byte[] decrypt(byte[] input, Key dk) {\n        RSAKey rsaKey = (RSAKey) dk;\n        BigInteger exponent = this.getExponent(rsaKey);\n        //return new BigInteger(1, input).modPow(exponent, rsaKey.n).toByteArray();\n\n        int cipherBlockSize = this.getCipherBlockSize(rsaKey),\n            originBlockSize = this.getOriginBlockSize(rsaKey);\n        ByteArrayOutputStream output = new ByteArrayOutputStream(input.length);\n        byte[] encrypted, origin;\n\n        try {\n            for (int offset = 0, len = input.length; offset < len; offset += cipherBlockSize) {\n                // 切割密文数据块\n                encrypted = Arrays.copyOfRange(input, offset, Math.min(len, offset + cipherBlockSize));\n\n                // 解密：origin = encrypted^d mod n\n                origin = new BigInteger(1, encrypted).modPow(exponent, rsaKey.n).toByteArray();\n\n                if (isPadding) {\n                    // 解码数据块\n                    decodeBlock(origin, cipherBlockSize, output);\n                } else {\n                    if (offset + cipherBlockSize < len) { // 判断是否是最后一轮循环\n                        // 固定明文长度\n                        fixedByteArray(origin, originBlockSize, output);\n                    } else {\n                        // 去掉原文前缀0\n                        trimByteArray(origin, output);\n                    }\n                }\n            }\n            return output.toByteArray();\n        } catch (IOException e) {\n            throw new SecurityException(e); // cannot happened\n        }\n    }\n\n    public void encrypt(InputStream input, Key ek, OutputStream output) {\n        RSAKey rsaKey = (RSAKey) ek;\n        BigInteger exponent = this.getExponent(rsaKey);\n        int cipherBlockSize = this.getCipherBlockSize(rsaKey);\n\n        byte[] buffer = new byte[getOriginBlockSize(rsaKey)], origin, encrypted;\n\n        try {\n            for (int len; (len = input.read(buffer)) != Files.EOF;) {\n                if (isPadding) {\n                    // 切割并填充原文数据块\n                    origin = encodeBlock(buffer, 0, len, cipherBlockSize, rsaKey);\n                } else {\n                    // 切割原文数据块\n                    origin = Arrays.copyOfRange(buffer, 0, len);\n                }\n\n                // 加密：encrypted = origin^e mod n\n                encrypted = new BigInteger(1, origin).modPow(exponent, rsaKey.n).toByteArray();\n\n                // 固定密文长度\n                fixedByteArray(encrypted, cipherBlockSize, output);\n            }\n            output.flush();\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public void decrypt(InputStream input, Key dk, OutputStream output) {\n        RSAKey rsaKey = (RSAKey) dk;\n        BigInteger exponent = this.getExponent(rsaKey);\n\n        int cipherBlockSize = this.getCipherBlockSize(rsaKey),\n            originBlockSize = this.getOriginBlockSize(rsaKey);\n        byte[] buffer = new byte[cipherBlockSize], encrypted, origin;\n\n        try {\n            int len, offset = 0, inputLen = input.available();\n            for (; (len = input.read(buffer)) != Files.EOF; offset += cipherBlockSize) {\n                // 切割密文数据块\n                encrypted = Arrays.copyOfRange(buffer, 0, len);\n\n                // 解密：origin = encrypted^d mod n\n                origin = new BigInteger(1, encrypted).modPow(exponent, rsaKey.n).toByteArray();\n\n                if (isPadding) {\n                    // 解码数据块\n                    decodeBlock(origin, cipherBlockSize, output);\n                } else {\n                    if (offset + cipherBlockSize < inputLen) {\n                        // 固定明文长度\n                        fixedByteArray(origin, originBlockSize, output);\n                    } else {\n                        // 去掉原文前缀0\n                        trimByteArray(origin, output);\n                    }\n                }\n            }\n            output.flush();\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public final BigInteger getExponent(RSAKey rsaKey) {\n        return rsaKey.secret ? rsaKey.d : rsaKey.e;\n    }\n\n    /**\n     * This method generates a new key for the crypto.\n     * @return the new key generated\n     */\n    @Override\n    public final Key generateKey() {\n        return generateKey(2048);\n    }\n\n    public final Key generateKey(int keySize) {\n        return new RSAKey(keySize);\n    }\n\n    @Override\n    public final String toString() {\n        return this.getClass().getSimpleName();\n    }\n\n    // ---------------------------------------------------------------private methods\n    /**\n     * When the BigInteger convert to byte array, if head more than two zero\n     * then was automatic trim remain one zero, if head has not zreo then automatic\n     * add a zreo. So we should manual control handle it, recover the origin byte\n     * array of this BigInteger.\n     *\n     * @param data        the data\n     * @param fixedSize   the result of byte array length\n     * @param out         the output stream\n     * @throws IOException if occur IOException\n     * @see cn.ponfee.commons.util.Bytes#toBinary(byte...)\n     * @see cn.ponfee.commons.util.Bytes#tailCopy(byte[], int, int, byte[], int, int)\n     */\n    private static void fixedByteArray(byte[] data, int fixedSize, OutputStream out)\n        throws IOException {\n        if (data.length < fixedSize) {\n            // 当最前面有多个0时，此时会被舍去只留下一个0来充当符号位，所以要加前缀0来补全\n            // 加前缀0补全到固定字节数：encryptedBlockSize\n            for (int i = 0, heading = fixedSize - data.length; i < heading; i++) {\n                out.write(Numbers.ZERO_BYTE);\n            }\n            out.write(data, 0, data.length);\n        } else {\n            // 当最前面的位为1时，BigInteger会通过加一个byte 0来充当符号位，此时需要手动舍去\n            out.write(data, data.length - fixedSize, fixedSize);\n        }\n    }\n\n    /**\n     * 当最前面的位为1时，BigInteger会通过加一个byte 0来充当符号位，此时需要手动舍去\n     * this method is unsafe, will be lose the prefix byte 0(one or more)\n     *\n     * @param data  the decrypted origin data\n     * @param out   the output stream\n     * @throws IOException if occur IOException\n     * @see cn.ponfee.commons.util.Bytes#toBinary(byte...)\n     */\n    private static void trimByteArray(byte[] data, OutputStream out)\n        throws IOException {\n        int i = 0, len = data.length;\n        for (; i < len; i++) {\n            if (data[i] != Numbers.ZERO_BYTE) {\n                break;\n            }\n        }\n        if (i < len) {\n            out.write(data, i, len - i);\n        }\n    }\n\n    /**\n     * 原文进行编码填充\n     *\n     * EB = 00 || BT || PS || 00 || D\n     * BT：公钥为0x02；私钥为0x00或0x01\n     * PS：BT为0则PS全部为0x00；BT为0x01则全部为0xFF；BT为0x02则为随机数，但不能为0\n     *\n     * 对于BT为00的，数据D就不能以00字节开头，因为这时候PS填充的也是00，\n     * 会分不清哪些是填充数据哪些是明文数据<p>\n     *\n     * 如果你使用私钥加密，建议你BT使用01，保证了安全性\n     * 对于BT为02和01的，PS至少要有8个字节长\n     *\n     * @param input  数据\n     * @param from   开始位置\n     * @param to     结束位置\n     * @param cipherBlockSize 模字节长（modulo/8）\n     * @param rsaKey 密钥\n     * @return the encrypting block with pkcs1 padding\n     */\n    private static byte[] encodeBlock(byte[] input, int from, int to,\n                                      int cipherBlockSize, RSAKey rsaKey) {\n        int length = to - from;\n        if (length > cipherBlockSize) {\n            throw new IllegalArgumentException(\"input data too large\");\n        } else if (cipherBlockSize - length - 3 < 8) {\n            throw new IllegalArgumentException(\"the padding too small\");\n        }\n\n        ByteArrayOutputStream baos = new ByteArrayOutputStream(cipherBlockSize);\n        baos.write(Numbers.ZERO_BYTE); // 0x00\n\n        if (rsaKey.secret) {\n            // 私钥填充\n            baos.write(0x01); // BT\n            for (int i = 2, pLen = cipherBlockSize - length - 1; i < pLen; i++) {\n                baos.write(0xFF);\n            }\n        } else {\n            // 公钥填充，规定此处至少要8个字节\n            baos.write(0x02); // BT\n            byte b;\n            for (int i = 2, pLen = cipherBlockSize - length - 1; i < pLen; i++) {\n                do {\n                    b = (byte) SecureRandoms.nextInt();\n                } while (b == Numbers.ZERO_BYTE);\n                baos.write(b);\n            }\n        }\n\n        baos.write(Numbers.ZERO_BYTE); // 0x00\n\n        baos.write(input, from, length); // D\n        return baos.toByteArray();\n    }\n\n    /**\n     * 解码原文填充（前缀0被舍去，只有127位）\n     * @param input the input\n     * @param cipherBlockSize the cipherBlockSize\n     * @param out the out\n     * @throws IOException if occur IOException\n     * @see cn.ponfee.commons.util.Bytes#toBinary(byte...)\n     */\n    private static void decodeBlock(byte[] input, int cipherBlockSize, OutputStream out)\n        throws IOException {\n        // BigInteger to byte array will be removed the prefix 0 (one or more)\n        // or added one byte 0\n        // 0x00 [0x01 | 0x02], so signum is definite on [0x01 | 0x02] then\n        // was removed the first 0x00\n        int removedZeroLen;\n        if (input[0] == Numbers.ZERO_BYTE) {\n            removedZeroLen = 0;\n        } else {\n            removedZeroLen = 1;\n        }\n\n        // 输入数据长度必须等于数据块长\n        if (input.length != cipherBlockSize - removedZeroLen) {\n            throw new IllegalArgumentException(\"block incorrect size\");\n        }\n\n        // check BT\n        byte type = input[1 - removedZeroLen];\n        if (type != 1 && type != 2) {\n            throw new IllegalArgumentException(\"unknown block type\");\n        }\n\n        // PS\n        int start = 2 - removedZeroLen;\n        for (; start != input.length; start++) {\n            byte pad = input[start];\n            if (pad == 0) {\n                break;\n            }\n            if (type == 1 && pad != (byte) 0xff) { // private key padding\n                throw new IllegalArgumentException(\"invalid block padding\");\n            }\n        }\n\n        // get D\n        start++; // data should start at the next byte\n        if (start > input.length || start < 11 - removedZeroLen) {\n            throw new IllegalArgumentException(\"invalid block data\");\n        }\n        out.write(input, start, input.length - start);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSAHashCryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.rsa;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.HmacAlgorithms;\nimport cn.ponfee.commons.jce.implementation.Key;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.SecureRandoms;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.math.BigInteger;\nimport java.util.Arrays;\n\nimport static cn.ponfee.commons.jce.Providers.BC;\nimport static cn.ponfee.commons.jce.digest.HmacUtils.crypt;\n\n/**\n * RSA Cryptor based sha512 xor \n * @author Ponfee\n */\npublic class RSAHashCryptor extends AbstractRSACryptor {\n\n    public RSAHashCryptor() {\n        super(false);\n    }\n\n    private static final HmacAlgorithms HMAC_ALG = HmacAlgorithms.HmacSHA3_512;\n\n    \n    /**\n     * (origin ⊕ passwd) ⊕ passwd = origin\n     * @param input\n     * @param length\n     * @param ek\n     * @return\n     */\n    @Override\n    public byte[] encrypt(byte[] input, int length, Key ek) {\n        RSAKey rsaKey = (RSAKey) ek;\n        int keyByteLen = rsaKey.n.bitLength() / 8, count = 1;\n        BigInteger exponent = getExponent(rsaKey);\n\n        // 生成随机对称密钥\n        BigInteger key = SecureRandoms.random(rsaKey.n); // mod是以1XX开头，key是以01X开头\n\n        // 对密钥进行RSA加密，encryptedKey = key^e mod n\n        byte[] encryptedKey = key.modPow(exponent, rsaKey.n).toByteArray();\n\n        byte[] result = new byte[keyByteLen + length];\n        // mod pow之后可能被去0或加0\n        Bytes.tailCopy(encryptedKey, 0, encryptedKey.length, result, 0, keyByteLen);\n\n        // 对密钥进行HASH\n        byte[] keyArray = key.toByteArray();\n        byte[] hashedKey = crypt(keyArray, Bytes.toBytes(count), HMAC_ALG, BC);\n        for (int keyOffset = 0, i = 0; i < length; i++) {\n            if (keyOffset == HMAC_ALG.byteSize()) {\n                keyOffset = 0;\n                hashedKey = crypt(keyArray, Bytes.toBytes(++count), HMAC_ALG, BC);\n            }\n            result[keyByteLen + i] = (byte) (input[i] ^ hashedKey[keyOffset++]);\n        }\n        return result;\n    }\n\n    @Override\n    public byte[] decrypt(byte[] input, Key dk) {\n        RSAKey rsaKey = (RSAKey) dk;\n        int keyByteLen = rsaKey.n.bitLength() / 8, count = 1;\n        BigInteger exponent = getExponent(rsaKey);\n\n        // 获取被加密的对称密钥数据\n        byte[] encryptedKey = Arrays.copyOfRange(input, 0, keyByteLen);\n\n        // 解密被加密的密钥数据，key = encryptedKey^d mod n\n        BigInteger key = new BigInteger(1, encryptedKey).modPow(exponent, rsaKey.n);\n\n        // 对密钥进行HASH\n        byte[] keyArray = key.toByteArray();\n        byte[] hashedKey = crypt(keyArray, Bytes.toBytes(count), HMAC_ALG, BC);\n        byte[] result = new byte[input.length - keyByteLen];\n        for (int keyOffset = 0, rLen = result.length, i = 0; i < rLen; i++) {\n            if (keyOffset == HMAC_ALG.byteSize()) {\n                keyOffset = 0;\n                hashedKey = crypt(keyArray, Bytes.toBytes(++count), HMAC_ALG, BC);\n            }\n            result[i] = (byte) (input[keyByteLen + i] ^ hashedKey[keyOffset++]);\n        }\n        return result;\n    }\n\n    @Override\n    public void encrypt(InputStream input, Key ek, OutputStream output) {\n        RSAKey rsaKey = (RSAKey) ek;\n        int keyByteLen = rsaKey.n.bitLength() / 8, count = 1;\n        BigInteger exponent = getExponent(rsaKey);\n\n        // 生成随机对称密钥\n        BigInteger key = SecureRandoms.random(rsaKey.n);\n\n        // 对密钥进行RSA加密，encryptedKey = key^e mod n\n        byte[] encryptedKey = key.modPow(exponent, rsaKey.n).toByteArray();\n\n        byte[] encryptedKey0 = new byte[keyByteLen]; // mod pow之后可能被去0或加0\n        Bytes.tailCopy(encryptedKey, 0, encryptedKey.length, encryptedKey0, 0, keyByteLen);\n\n        byte[] keyArray = key.toByteArray();\n        byte[] hashedKey = crypt(keyArray, Bytes.toBytes(count), HMAC_ALG, BC);\n        try {\n            output.write(encryptedKey0); // encrypted key\n            byte[] buffer = new byte[this.getOriginBlockSize(rsaKey)];\n            for (int keyOffset = 0, len, i; (len = input.read(buffer)) != Files.EOF;) {\n                for (i = 0; i < len; i++) {\n                    if (keyOffset == HMAC_ALG.byteSize()) {\n                        keyOffset = 0;\n                        hashedKey = crypt(keyArray, Bytes.toBytes(++count), HMAC_ALG, BC);\n                    }\n                    output.write((byte) (buffer[i] ^ hashedKey[keyOffset++]));\n                }\n            }\n            output.flush();\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    @Override\n    public void decrypt(InputStream input, Key dk, OutputStream output) {\n        RSAKey rsaKey = (RSAKey) dk;\n        int keyByteLen = rsaKey.n.bitLength() / 8, count = 1;\n        BigInteger exponent = getExponent(rsaKey);\n        try {\n            if (input.available() < keyByteLen) {\n                throw new IllegalArgumentException(\"Invalid cipher data\");\n            }\n\n            // 获取被加密的对称密钥数据\n            byte[] encryptedKey = new byte[keyByteLen];\n            input.read(encryptedKey);\n\n            // 解密被加密的密钥数据，key = encryptedKey^d mod n\n            BigInteger key = new BigInteger(1, encryptedKey).modPow(exponent, rsaKey.n);\n\n            byte[] buffer = new byte[this.getCipherBlockSize(rsaKey)];\n            byte[] keyArray = key.toByteArray();\n            byte[] hashedKey = crypt(keyArray, Bytes.toBytes(count), HMAC_ALG, BC);\n            for (int keyOffset = 0, len, i; (len = input.read(buffer)) != Files.EOF;) {\n                for (i = 0; i < len; i++) {\n                    if (keyOffset == HMAC_ALG.byteSize()) {\n                        keyOffset = 0;\n                        hashedKey = crypt(keyArray, Bytes.toBytes(++count), HMAC_ALG, BC);\n                    }\n                    output.write((byte) (buffer[i] ^ hashedKey[keyOffset++]));\n                }\n            }\n            output.flush();\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    @Override\n    public int getOriginBlockSize(RSAKey rsaKey) {\n        return 4096;\n    }\n\n    @Override\n    public int getCipherBlockSize(RSAKey rsaKey) {\n        return this.getOriginBlockSize(rsaKey);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSAKey.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.rsa;\n\nimport cn.ponfee.commons.jce.implementation.Key;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport com.google.common.base.Preconditions;\nimport org.apache.commons.io.IOUtils;\nimport sun.security.util.DerInputStream;\nimport sun.security.util.DerOutputStream;\nimport sun.security.util.DerValue;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.math.BigInteger;\nimport java.security.SecureRandom;\n\nimport static java.math.BigInteger.ONE;\n\n/**\n * <pre>\n * The RSA Key\n *\n * https://blog.csdn.net/seccloud/article/details/8188812\n * https://blog.csdn.net/zntsbkhhh/article/details/109581852\n * https://www.zhihu.com/question/54779059\n * \n * (1)选择两个不同的大素数p和q\n * (2)计算公共模数(n=pq)和欧拉数(eular=(p-1)(q-1))\n * (3)选择公钥指数e\n * (4)计算inverse(d)\n * (5)生成公钥和私钥\n * \n * \n * 公钥KU：\n *   n：两素数p和q的乘积（p和q必须保密）（n为模值）\n *   e：与(p-1)*(q-1)互质（e称为公钥指数）\n *\n * 私钥KR：\n *   n：两素数p和q的乘积（p和q必须保密）（n为模值）\n *   d：满足(d*e) mod ((p-1)*(q-1)) = 1（d称为私钥指数）\n *\n * 加密过程  C=M^e mod n  （C为密文）\n * 解密过程  M=C^d mod n  （M为明文）\n * </pre>\n * \n * @see org.bouncycastle.crypto.generators.RSAKeyPairGenerator\n * @author Ponfee\n */\n@SuppressWarnings(\"restriction\")\npublic class RSAKey implements Key {\n\n    private static final SecureRandom SECURE_RANDOM =\n        new SecureRandom(SecureRandoms.generateSeed(24));\n\n    // RSA公钥指数：Should RSA public exponent be only in (3, 5, 17, 257 or 65537)，最常用的是3,17,65537\n    // 学术界普遍认为绝对不能选用e=3作为RSA公钥指数\n    // 选取小公钥指数主要是为了提高加密或签名验证的性能\n    public static final int RSA_F4 = 65537;\n\n    public final BigInteger n;\n    public final BigInteger e;\n\n    public final BigInteger d;\n    public final BigInteger p;\n    public final BigInteger q;\n    public final BigInteger pe;\n    public final BigInteger qe;\n    public final BigInteger coeff;\n\n    public final boolean secret;\n\n    public RSAKey(int keySize) {\n        this(keySize, RSA_F4);\n    }\n\n    public RSAKey(int keySize, int e) {\n        this.secret = true;\n        KeyPair pair = generateKey(keySize, e);\n        this.e = pair.e;\n        this.p = pair.p;\n        this.q = pair.q;\n        this.n = pair.n;\n        this.pe = pair.pe;\n        this.qe = pair.qe;\n        this.d = pair.d;\n        this.coeff = pair.coeff;\n    }\n\n    public RSAKey(BigInteger n, BigInteger e,\n                  BigInteger d, BigInteger p, BigInteger q,\n                  BigInteger pe, BigInteger qe, BigInteger coeff) {\n\n        Preconditions.checkArgument(n != null && e != null && d != null \n                                    && p != null && q != null && pe != null \n                                    && qe != null && coeff != null);\n        this.secret = true;\n        this.n = n;\n        this.e = e;\n        this.d = d;\n        this.p = p;\n        this.q = q;\n        this.pe = pe;\n        this.qe = qe;\n        this.coeff = coeff;\n    }\n\n    public RSAKey(BigInteger n, BigInteger e) {\n        Preconditions.checkArgument(n != null && e != null);\n        this.secret = false;\n        this.n = n;\n        this.e = e;\n\n        this.d = null;\n        this.p = null;\n        this.q = null;\n        this.pe = null;\n        this.qe = null;\n        this.coeff = null;\n    }\n\n    @Override\n    public boolean isPublic() {\n        return !secret;\n    }\n\n    public boolean isSecret() {\n        return secret;\n    }\n\n    /**\n     * get the public key\n     */\n    @Override\n    public RSAKey getPublic() {\n        return new RSAKey(n, e);\n    }\n\n    // Secret: (secret, n, e, d, p, q, pe, qe, coeff)\n    // Public: (secret, n, e)\n    @Override\n    public void writeKey(OutputStream out) throws IOException {\n        DerOutputStream der = new DerOutputStream();\n        der.putInteger(this.secret ? 0 : 1);\n        der.putInteger(this.n);\n        der.putInteger(this.e);\n        if (this.secret) {\n            der.putInteger(this.d);\n            der.putInteger(this.p);\n            der.putInteger(this.q);\n            der.putInteger(this.pe);\n            der.putInteger(this.qe);\n            der.putInteger(this.coeff);\n        }\n        DerValue dervalue = new DerValue((byte) 48, der.toByteArray());\n        out.write(dervalue.toByteArray());\n        der.close();\n    }\n\n    // Secret: (secret, n, e, d, p, q, pe, qe, coeff)\n    // Public: (secret, n, e)\n    @Override\n    public RSAKey readKey(InputStream in) throws IOException {\n        DerValue der = new DerInputStream(IOUtils.toByteArray(in)).getDerValue();\n        if (der.getTag() != 48) {\n            throw new IOException(\"Not a SEQUENCE\");\n        }\n\n        DerInputStream derIn = der.getData();\n        boolean secret = der.getInteger() == 0;\n        BigInteger n = getBigInteger(derIn);\n        BigInteger e = getBigInteger(derIn);\n        RSAKey rsaKey;\n        if (secret) {\n            BigInteger d = getBigInteger(derIn);\n            BigInteger p = getBigInteger(derIn);\n            BigInteger q = getBigInteger(derIn);\n            BigInteger pe = getBigInteger(derIn);\n            BigInteger qe = getBigInteger(derIn);\n            BigInteger coeff = getBigInteger(derIn);\n            rsaKey = new RSAKey(n, e, d, p, q, pe, qe, coeff);\n        } else {\n            rsaKey = new RSAKey(n, e);\n        }\n        if (derIn.available() != 0) {\n            throw new IOException(\"Extra data available\");\n        }\n        return rsaKey;\n    }\n\n    private static BigInteger getBigInteger(DerInputStream derIn) {\n        BigInteger biginteger;\n        try {\n            biginteger = derIn.getBigInteger();\n        } catch (IOException e) {\n            throw new IllegalArgumentException(e);\n        }\n        if (biginteger.signum() < 0) {\n            biginteger = new BigInteger(1, biginteger.toByteArray());\n        }\n        return biginteger;\n    }\n\n    /**\n     * Generate the key pair\n     * \n     * @param keySize\n     * @param e\n     * @return\n     */\n    public static KeyPair generateKey(int keySize, int e) {\n        KeyPair keyPair = new KeyPair();\n        keyPair.e = BigInteger.valueOf(e);\n        int i = (keySize + 1) >> 1;\n        int j = keySize - i;\n        do {\n            keyPair.p = BigInteger.probablePrime(i, SECURE_RANDOM);\n            do {\n                keyPair.q = BigInteger.probablePrime(j, SECURE_RANDOM);\n                if (keyPair.p.compareTo(keyPair.q) < 0) {\n                    BigInteger temp = keyPair.p;\n                    keyPair.p = keyPair.q;\n                    keyPair.q = temp;\n                }\n                keyPair.n = keyPair.p.multiply(keyPair.q);\n            } while (keyPair.n.bitLength() != keySize);\n            keyPair.p1 = keyPair.p.subtract(ONE);\n            keyPair.q1 = keyPair.q.subtract(ONE);\n            keyPair.phi = keyPair.p1.multiply(keyPair.q1);\n        } while (!keyPair.e.gcd(keyPair.phi).equals(ONE));\n\n        keyPair.d = keyPair.e.modInverse(keyPair.phi);\n        keyPair.pe = keyPair.d.mod(keyPair.p1);\n        keyPair.qe = keyPair.d.mod(keyPair.q1);\n        keyPair.coeff = keyPair.q.modInverse(keyPair.p);\n\n        return keyPair;\n        // new sun.security.rsa.RSAPublicKeyImpl(n, e);\n        // new sun.security.rsa.RSAPrivateCrtKeyImpl(n, e, d, p, q, pe, qe, coeff);\n        // return new java.security.KeyPair(RSAPublicKeyImpl, RSAPrivateCrtKeyImpl);\n    }\n\n    /*public static KeyPair generateKey(int keySize, int e) {\n        KeyPair keyPair = new KeyPair();\n        int qs = keySize >> 1;\n        keyPair.e = BigInteger.valueOf(e);\n        for (;;) {\n            do {\n                keyPair.p = new BigInteger(keySize - qs, 1, SECURE_RANDOM);\n            } while (keyPair.p.subtract(ONE).gcd(keyPair.e).compareTo(ONE) != 0\n                     || !keyPair.p.isProbablePrime(10));\n\n            do {\n                keyPair.q = new BigInteger(qs, 1, SECURE_RANDOM);\n            } while (keyPair.q.subtract(ONE).gcd(keyPair.e).compareTo(ONE) != 0\n                     || !keyPair.q.isProbablePrime(10));\n\n            if (keyPair.p.compareTo(keyPair.q) <= 0) {\n                BigInteger t = keyPair.p;\n                keyPair.p = keyPair.q;\n                keyPair.q = t;\n            }\n            keyPair.p1 = keyPair.p.subtract(ONE);\n            keyPair.q1 = keyPair.q.subtract(ONE);\n            keyPair.phi = keyPair.p1.multiply(keyPair.q1);\n            if (keyPair.phi.gcd(keyPair.e).compareTo(ONE) == 0) {\n                keyPair.n = keyPair.p.multiply(keyPair.q);\n                keyPair.d = keyPair.e.modInverse(keyPair.phi);\n                keyPair.pe = keyPair.d.mod(keyPair.p1);\n                keyPair.qe = keyPair.d.mod(keyPair.q1);\n                keyPair.coeff = keyPair.q.modInverse(keyPair.p);\n                break;\n            }\n        }\n        return keyPair;\n    }*/\n\n    private static class KeyPair {\n        private BigInteger n;\n        private BigInteger e;\n\n        private BigInteger d;\n        private BigInteger p;\n        private BigInteger q;\n        private BigInteger pe;\n        private BigInteger qe;\n        private BigInteger coeff;\n\n        private BigInteger p1;\n        private BigInteger q1;\n        private BigInteger phi;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSANoPaddingCryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.rsa;\n\n/**\n * RSA crypto without padding\n * \n * @author Ponfee\n */\npublic class RSANoPaddingCryptor extends AbstractRSACryptor {\n\n    public RSANoPaddingCryptor() {\n        super(false);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSAPKCS1PaddingCryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.rsa;\n\n/**\n * RSA crypto with PKCS1 padding\n * \n * @author Ponfee\n */\npublic class RSAPKCS1PaddingCryptor extends AbstractRSACryptor {\n\n    public RSAPKCS1PaddingCryptor() {\n        super(true);\n    }\n\n    @Override\n    public int getOriginBlockSize(RSAKey rsaKey) {\n        return rsaKey.n.bitLength() / 8 - 11;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/rsa/RSASigner.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.rsa;\n\nimport cn.ponfee.commons.jce.DigestAlgorithms;\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport com.google.common.collect.ImmutableMap;\nimport org.bouncycastle.asn1.ASN1Encoding;\nimport org.bouncycastle.asn1.ASN1ObjectIdentifier;\nimport org.bouncycastle.asn1.DERNull;\nimport org.bouncycastle.asn1.nist.NISTObjectIdentifiers;\nimport org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;\nimport org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;\nimport org.bouncycastle.asn1.x509.AlgorithmIdentifier;\nimport org.bouncycastle.asn1.x509.DigestInfo;\nimport org.bouncycastle.asn1.x509.X509ObjectIdentifiers;\nimport org.bouncycastle.crypto.AsymmetricBlockCipher;\nimport org.bouncycastle.crypto.InvalidCipherTextException;\nimport org.bouncycastle.crypto.encodings.PKCS1Encoding;\nimport org.bouncycastle.crypto.engines.RSABlindedEngine;\nimport org.bouncycastle.crypto.params.RSAKeyParameters;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Map;\n\n/**\n * RSA sign\n * https://www.cnblogs.com/jintianhu/p/5051169.html\n * \n * @see org.bouncycastle.crypto.signers.RSADigestSigner\n * \n * @author Ponfee\n */\npublic class RSASigner {\n\n    private static final Map<String, ASN1ObjectIdentifier> HASH_OID_MAPPING =\n        ImmutableMap.<String, ASN1ObjectIdentifier> builder() // \n            .put(\"RIPEMD128\", TeleTrusTObjectIdentifiers.ripemd128) // \n            .put(\"RIPEMD160\", TeleTrusTObjectIdentifiers.ripemd160) // \n            .put(\"RIPEMD256\", TeleTrusTObjectIdentifiers.ripemd256) // \n\n            .put(\"SHA-1\", X509ObjectIdentifiers.id_SHA1) // \n            .put(\"SHA-224\", NISTObjectIdentifiers.id_sha224) // \n            .put(\"SHA-256\", NISTObjectIdentifiers.id_sha256) // \n            .put(\"SHA-384\", NISTObjectIdentifiers.id_sha384) // \n            .put(\"SHA-512\", NISTObjectIdentifiers.id_sha512) // \n            .put(\"SHA-512/224\", NISTObjectIdentifiers.id_sha512_224) // \n            .put(\"SHA-512/256\", NISTObjectIdentifiers.id_sha512_256) // \n\n            .put(\"SHA3-224\", NISTObjectIdentifiers.id_sha3_224) // \n            .put(\"SHA3-256\", NISTObjectIdentifiers.id_sha3_256) // \n            .put(\"SHA3-384\", NISTObjectIdentifiers.id_sha3_384) // \n            .put(\"SHA3-512\", NISTObjectIdentifiers.id_sha3_512) // \n\n            .put(\"MD2\", PKCSObjectIdentifiers.md2) // \n            .put(\"MD4\", PKCSObjectIdentifiers.md4) // \n            .put(\"MD5\", PKCSObjectIdentifiers.md5) // \n            .build();\n\n    private final AsymmetricBlockCipher rsaEngine = new PKCS1Encoding(new RSABlindedEngine());\n    private final RSAKey rsaKey;\n\n    public RSASigner(RSAKey rsaKey) {\n        this.rsaKey = rsaKey;\n        if (rsaKey.secret) {\n            // 签名\n            rsaEngine.init(true, new RSAKeyParameters(true, rsaKey.n, rsaKey.d));\n        } else {\n            // 验签\n            rsaEngine.init(false, new RSAKeyParameters(false, rsaKey.n, rsaKey.e));\n        }\n    }\n\n    public byte[] signSha1(byte[] data) {\n        return sign(data, DigestAlgorithms.SHA1);\n    }\n\n    public boolean verifySha1(byte[] data, byte[] signature) {\n        return verify(data, signature, DigestAlgorithms.SHA1);\n    }\n\n    public byte[] signSha256(byte[] data) {\n        return sign(data, DigestAlgorithms.SHA256);\n    }\n\n    public boolean verifySha256(byte[] data, byte[] signature) {\n        return verify(data, signature, DigestAlgorithms.SHA256);\n    }\n\n    public byte[] sign(byte[] data, DigestAlgorithms alg) {\n        if (!this.rsaKey.isSecret()) {\n            throw new IllegalArgumentException(\"Sign must use private key.\");\n        }\n\n        ASN1ObjectIdentifier oid = HASH_OID_MAPPING.get(alg.algorithm());\n        if (oid == null) {\n            throw new IllegalArgumentException(\"Invalid hash algorithm \" + alg.name());\n        }\n\n        // data hash\n        byte[] hash = DigestUtils.digest(alg, data);\n\n        try {\n            byte[] result = derEncode(hash, oid);\n            return rsaEngine.processBlock(result, 0, result.length);\n        } catch (InvalidCipherTextException | IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public boolean verify(byte[] data, byte[] signature, DigestAlgorithms alg) {\n        if (this.rsaKey.isSecret()) {\n            throw new IllegalArgumentException(\"Verify signature must use public key.\");\n        }\n\n        ASN1ObjectIdentifier oid = HASH_OID_MAPPING.get(alg.algorithm());\n        if (oid == null) {\n            throw new IllegalArgumentException(\"Invalid hash algorithm \" + alg.name());\n        }\n\n        // hash data\n        byte[] hash = DigestUtils.digest(alg, data);\n\n        byte[] sig;\n        byte[] expected;\n        try {\n            expected = derEncode(hash, oid);\n            sig = rsaEngine.processBlock(signature, 0, signature.length);\n        } catch (InvalidCipherTextException | IOException e) {\n            return false;\n        }\n\n        if (sig.length == expected.length) {\n            return Arrays.equals(sig, expected);\n        } else if (sig.length == expected.length - 2) { // NULL left out\n            int sigOffset = sig.length - hash.length - 2;\n            int expOffset = expected.length - hash.length - 2;\n\n            expected[1] -= 2; // adjust lengths\n            expected[3] -= 2;\n            for (int i = 0; i < sigOffset; i++) {\n                // check header less NULL\n                if (sig[i] != expected[i]) {\n                    return false;\n                }\n            }\n\n            for (int i = 0; i < hash.length; i++) {\n                // check hash data\n                if (sig[sigOffset + i] != expected[expOffset + i]) {\n                    return false;\n                }\n            }\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * DER :: {\n     *     BERTags.SEQUENCE|BERTags.CONSTRUCTED, \n     *     totalLength,\n     *     BERTags.OBJECT_IDENTIFIER,   -- {@link ASN1ObjectIdentifier#encode}\n     *     algLength,\n     *     algBody,\n     *     BERTags.OCTET_STRING,        -- {@link org.bouncycastle.asn1.DEROctetString#encode}\n     *     digestLength,\n     *     digestBody\n     * }\n     * \n     * @param hash\n     * @param digestOid\n     * @return the byte array of der encoded\n     * @throws IOException\n     * @see org.bouncycastle.asn1.DERSequence\n     * @see org.bouncycastle.asn1.x509.DigestInfo\n     * @see org.bouncycastle.asn1.BERTags\n     */\n    private byte[] derEncode(byte[] hash, ASN1ObjectIdentifier digestOid) \n        throws IOException {\n        AlgorithmIdentifier algId = new AlgorithmIdentifier(digestOid, DERNull.INSTANCE);\n        DigestInfo dInfo = new DigestInfo(algId, hash);\n        return dInfo.getEncoded(ASN1Encoding.DER);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/rsa/package-info.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n/**\n * RSA implementation\n * @author Ponfee\n */\npackage cn.ponfee.commons.jce.implementation.rsa;\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/implementation/symmetric/RC4.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.implementation.symmetric;\n\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.util.Arrays;\n\n/**\n * RC4 implementation\n * \n * 给定一个短的密码，储存在key[MAX]数组里，还有一个数组S[256]，令S[i]=i。\n * 然后利用数组key来对数组S做一个置换，也就是对S数组里的数重新排列，排列算法为\n * KSA：密钥调度算法\n * j := 0\n * for i from 0 to 255\n *   j := (j + S[i] + key[i mod keyLen]) mod sboxLen\n *   swap values of S[i] and S[j]\n * endfor\n * \n * @author Ponfee\n */\npublic class RC4 {\n\n    private final static int STATE_LENGTH = 256;\n\n    /** variables to hold the state of the RC4  during encryption and decryption */\n    private final byte[] sBox;\n\n    /**\n     * Constructs a RC4 Cryptor, input the specified key byte array \n     * and init the sbox in this methods\n     * \n     * @param keyBytes\n     */\n    public RC4(byte[] keyBytes) {\n        // KSA：密钥调度算法\n        // 生成并填充s-box\n        this.sBox = new byte[STATE_LENGTH];\n        for (int i = 0; i < STATE_LENGTH; i++) {\n            this.sBox[i] = (byte) i;\n        }\n\n        // 置换s-box\n        for (int i = 0, j = 0, k = 0, keyLen = keyBytes.length; i < STATE_LENGTH; i++) {\n            j = (j + sBox[i] + keyBytes[k++]) & 0xFF; // & 0xFF -> modulo s-box len\n\n            ArrayUtils.swap(this.sBox, i, j);\n\n            if (k == keyLen) {\n                k = 0;\n            }\n        }\n    }\n\n    // -----------------------------------------------------------crypt one byte\n\n    public byte encrypt(byte in) {\n        byte[] sBox = Arrays.copyOf(this.sBox, this.sBox.length);\n        int x = 1;\n        int y = sBox[x] & 0xFF;\n\n        ArrayUtils.swap(sBox, x, y);\n\n        // xor\n        return (byte) (in ^ sBox[(sBox[x] + sBox[y]) & 0xFF]);\n    }\n\n    public byte decrypt(byte in) {\n        return this.encrypt(in);\n    }\n\n    // -----------------------------------------------------------crypt byte array\n\n    public byte[] encrypt(byte[] in) {\n        byte[] out = new byte[in.length];\n        this.doCrypt(in, 0, in.length, out, 0);\n        return out;\n    }\n\n    public byte[] decrypt(byte[] in) {\n        return this.encrypt(in);\n    }\n\n    // -----------------------------------------------------------private methods\n\n    private void doCrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {\n        byte[] sBox = Arrays.copyOf(this.sBox, this.sBox.length);\n\n        // RPGA：伪随机生成算法，不断的重排S盒来产生任意长度的密钥流\n        for (int i = 0, x = 0, y = 0; i < len; i++) {\n            x = (x + 1) & 0xFF;\n            y = (sBox[x] + y) & 0xFF;\n\n            ArrayUtils.swap(sBox, x, y);\n\n            // xor\n            out[i + outOff] = (byte) (in[i + inOff] ^ sBox[(sBox[x] + sBox[y]) & 0xFF]);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/package-info.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n/**\n * <pre>\n * Java Cryptography Extension提供用于加密、密钥生成和协商\n * 以及 Message Authentication Code（MAC）算法的实现\n * \n * http://www.freebuf.com/articles/others-articles/136742.html\n * https://www.jianshu.com/p/b10a892879a0\n * \n * 1、密码：\n *   你知道什么：口令（密码）、口令摘要、质询/响应\n *   你有什么：认证令牌（质询/响应令牌、时间令牌），PIN双因素认证、SSL与认证令牌、智能卡\n *   你是什么：生物特征认证，FAR(False Accept Ratio)，FRR(False Reject Ratio)\n * \n * 2、对称加密：\n *   优点：效率高\n *   缺点：密钥成几何数增长、需要事先协商密钥\n *   类型：分组密码（DES、3DES、AES），序列密码（RC4）、盐加密（PBE）\n *   分组模式：ECB、CBC、OFB、CFB\n *   填充：NoPadding, PKCS5Padding, PKCS7Padding, PADDING_ISO10126\n *   AES要支持256位密钥：http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html\n *                  解压后替换jre/lib/security/目录下的jar文件即可\n * \n * 3、非对称加密：\n *   优点：密钥分发安全，公开公钥即可\n *   缺点：效率低\n *   算法：\n *      DH：基于离散对数的实现，主要用于密钥交换\n *      RSA：基于大整数因式分解的实现，Ron [R]ivest, Adi [S]hamir, Leonard [A]dleman（三人）\n *          https://www.kancloud.cn/kancloud/rsa_algorithm/48488\n *      DSA：基于整数有限域离散对数难题（特点是两个素数公开），Digital Signature Algorithm，顾名思义只用于数字签名\n *      ECC：基于椭圆曲线算法，指在取代RSA\n *   填充：RSA_PKCS1_PADDING（blocklen=keysize/8–11）、RSA_PKCS1_OAEP_PADDING(keysize-41)、RSA_NO_PADDING\n *   签名/验签：PKCS1及填充、PKCS7格式（附原文|不附原文）\n *   PKCS: Public-Key Cryptography Standards\n *   PKI: Public-Key Infrastructure\n * \n * 4、对称与非对称结合：数字信封envelop，结构体，（带签名|不带签名）\n * \n * 5、数字证书：ASN1、X509、p7b、p7r、p10、p12、PEM、DER等概念\n * \n * 6、BASE64编码：3个字节切分为4个字节后每字节最高位补00  0 ~ 63, “=”，并与编码表对照\n *         前生：解决邮件只能发ASCII码问题\n *         应用：二进制字节流数据文本化（某些场景的网络传输及文本表示）\n * \n * 7、哈希算法：指纹、摘要，用于防篡改等\n *    MD5：前身MD2、MD3和MD4，安全性低，算法原理（填充、记录长度、数据处理）\n *    SHA-1：已被严重质疑\n *    SHA-2：SHA-224、SHA-256、SHA-384、SHA-512，算法跟SHA-1基本上仍然相似\n *    SHA-3：之前名为Keccak算法，是一个加密杂凑算法\n *    RIPEMD-160：哈希加密算法，用于替代128位哈希函数 MD4、MD5 和 RIPEMD\n * \n * 8、密码安全：BCrypt、SCrypt、PBKDF2, Argon2\n * \n * 9、时间戳、签章\n * \n * 10、区块链：\n *     https://anders.com/blockchain，https://www.zhihu.com/question/22075219\n *     SHA256：\n *          block_header = version + previous_block_hash + merkle_root + time + target_bits + nonce\n *          for i in range(0, 2^32):\n *              if sha256(sha256(block_header)) < target_bits:\n *                  break\n *              else:\n *                  continue\n\n *          version：block的版本（静态常数）\n *          previous_block_hash：上一个block的hash值（前一区块已经是打包好的）\n *          merkle_root：需要写入的交易记录的hash树的值（根据本次交易包含的交易列表得到）\n *          time：更新时间（utc时间：取打包时的时间，也不需要很精确，前后几十秒也都可以）\n *          target_bits：当前难度\n *          nonce：从0试到最大值2^32\n *\n *          target_bits：TARGET_MAX/difficulty，创世区块时的difficulty=1\n *          TARGET_MAX=0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\n *\n *\n *          https://www.liaoxuefeng.com/article/1124144362997184\n *          ┌─────────────────────────────────────────────┐\n *          │Tx: abcd...1234                              │\n *          ├─────────────────────┬───────────────────────┤\n *          │         TxIn        │         TxOut         │\n *          ├─────────────┬───────┼──────┬────────────────┤\n *          │prev hash    │index  │btc   │pkScript        │\n *          ├─────────────┼───────┼──────┼────────────────┤\n *          │2016...a3c5  │3      │0.15  │OP_DUP a1b2...  │<─┐\n *          ├─────────────┼───────┼──────┼────────────────┤  │\n *          │2015...b6d8  │1      │0.08  │OP_DUP c3d4...  │  │\n *          └─────────────┴───────┴──────┴────────────────┘  │\n *          ┌────────────────────────────────────────────────┘\n *          │  ┌─────────────────────────────────────────────┐\n *          │  │Tx: a1b2...e8f9                              │\n *          │  ├─────────────────────┬───────────────────────┤\n *          │  │         TxIn        │         TxOut         │\n *          │  ├─────────────┬───────┼──────┬────────────────┤\n *          │  │prev hash    │index  │btc   │pkScript        │\n *          │  ├─────────────┼───────┼──────┼────────────────┤\n *          └──│abcd...1234  │0      │0.15  │OP_DUP 3456...  │\n *             ├─────────────┼───────┼──────┼────────────────┤\n *             │cdef...5678  │2      │0.02  │OP_DUP 9876...  │\n *             └─────────────┴───────┴──────┴────────────────┘\n *\n *\n *     ECC：公钥160位的指纹作为钱包地址，一笔交易就是一个地址的比特币转移到另一个地址\n *     Base58：end of 4 bytes long checksum\n * \n * 11、国密系列：\n *   SM1：为对称加密，其加密强度与AES相当。该算法不公开，调用该算法时，需要通过加密芯片的接口进行调用\n *   SM2：基于ECC的非对称加密算法，该算法已公开。ECC 256位（SM2采用的就是ECC 256位的一种）\n *       安全强度比RSA 2048位高，但运算速度快于RSA\n *       方程：y² = x³ + ax + b\n *       1、用户A选定一条椭圆曲线Ep(a,b)，并取椭圆曲线上一点，作为基点G。\n *       2、用户A选择一个私有密钥k，并生成公开密钥K=kG。\n *       3、用户A将Ep(a,b)和点K，G传给用户B。\n *       4、用户B接到信息后 ，将待传输的明文编码到Ep(a,b)上一点M（编码方法很多，这里不作讨论），并产生一个随机整数r（r<n）。\n *       5、用户B计算点C1=M+rK；C2=rG。\n *       6、用户B将C1、C2传给用户A。\n *       7、用户A接到信息后，计算C1-kC2，结果就是点M。因为C1-kC2=M+rK-k(rG)=M+rK-r(kG)=M，再对点M进行解码就可以得到明文。\n *   SM3：摘要算法，该算法已公开，校验结果为256位\n *   SM4：无线局域网标准的分组数据对称加密算法，该算法已公开，密钥长度和分组长度均为128位（16 byte）\n *   签名算法：SM3WithSM2\n * \n * 12、Windows证书管理：\n *    当前用户的证书管理：certmgr.msc\n * \n * 13、TOP：基于时间的一次性密码（Time-based One-time Password），如动态口令\n *    TC = floor((unixtime(now) − unixtime(T0)) / TS) // unixtime(now)：当前unix时间戳，unixtime(T0)：约定的起始时间点的时间戳，TS：哈希有效期的时间长度\n *    TOTP = HASH(SecretKey, TC)\n * </pre>\n *\n * @author Ponfee\n */\npackage cn.ponfee.commons.jce;\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/passwd/BCrypt.java",
    "content": "package cn.ponfee.commons.jce.passwd;\n\nimport cn.ponfee.commons.util.Base64UrlSafe;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport com.google.common.base.Preconditions;\n\nimport java.util.Arrays;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\n/**\n* BCrypt implements OpenBSD-style Blowfish password hashing using\n* the scheme described in \"A Future-Adaptable Password Scheme\" by\n* Niels Provos and David Mazieres.\n* <p>\n* This password hashing system tries to thwart off-line password\n* cracking using a computationally-intensive hashing algorithm,\n* based on Bruce Schneier's Blowfish cipher. The work factor of\n* the algorithm is parameterised, so it can be increased as\n* computers get faster.\n* <p>\n* \n* Maven position：\n*  <dependency>\n*   <groupId>de.svenkubiak</groupId>\n*   <artifactId>jBCrypt</artifactId>\n*   <version>0.4.1</version>\n* </dependency>\n* \n* http://www.mindrot.org/projects/jBCrypt/#download\n* https://en.m.wikipedia.org/wiki/Bcrypt\n*    $1$: MD5\n*    $2$: Bcrypt\n* $sha1$: SHA-1\n*    $5$: SHA-256\n*    $6$: SHA-512\n* \n* Crypt {@link org.apache.commons.codec.digest.Crypt}\n* \n* @author Damien Miller\n* @version 0.5\n* \n* Reference from internet and with optimization\n*/\npublic final class BCrypt {\n    private BCrypt() {}\n\n    private static final char SEPARATOR = '$';\n\n    // Blowfish parameters\n    private static final int BLOWFISH_NUM_ROUNDS = 16;\n\n    // Initial contents of key schedule\n    private static final int[] P_ORIGIN = {\n        0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,\n        0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,\n        0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,\n        0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,\n        0x9216d5d9, 0x8979fb1b\n    };\n\n    private static final int[] S_ORIGIN = {\n        0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,\n        0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,\n        0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,\n        0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,\n        0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,\n        0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,\n        0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,\n        0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,\n        0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,\n        0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,\n        0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,\n        0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,\n        0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,\n        0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,\n        0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,\n        0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,\n        0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,\n        0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,\n        0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,\n        0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,\n        0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,\n        0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,\n        0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,\n        0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,\n        0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,\n        0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,\n        0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,\n        0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,\n        0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,\n        0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,\n        0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,\n        0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,\n        0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,\n        0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,\n        0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,\n        0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,\n        0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,\n        0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,\n        0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,\n        0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,\n        0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,\n        0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,\n        0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,\n        0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,\n        0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,\n        0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,\n        0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,\n        0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,\n        0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,\n        0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,\n        0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,\n        0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,\n        0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,\n        0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,\n        0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,\n        0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,\n        0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,\n        0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,\n        0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,\n        0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,\n        0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,\n        0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,\n        0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,\n        0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,\n        0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,\n        0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,\n        0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,\n        0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,\n        0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,\n        0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,\n        0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,\n        0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,\n        0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,\n        0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,\n        0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,\n        0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,\n        0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,\n        0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,\n        0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,\n        0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,\n        0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,\n        0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,\n        0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,\n        0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,\n        0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,\n        0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,\n        0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,\n        0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,\n        0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,\n        0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,\n        0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,\n        0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,\n        0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,\n        0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,\n        0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,\n        0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,\n        0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,\n        0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,\n        0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,\n        0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,\n        0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,\n        0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,\n        0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,\n        0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,\n        0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,\n        0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,\n        0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,\n        0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,\n        0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,\n        0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,\n        0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,\n        0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,\n        0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,\n        0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,\n        0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,\n        0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,\n        0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,\n        0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,\n        0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,\n        0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,\n        0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,\n        0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,\n        0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,\n        0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,\n        0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,\n        0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,\n        0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,\n        0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,\n        0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,\n        0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,\n        0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,\n        0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,\n        0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,\n        0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,\n        0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,\n        0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,\n        0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,\n        0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,\n        0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,\n        0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,\n        0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,\n        0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,\n        0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,\n        0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,\n        0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,\n        0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,\n        0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,\n        0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,\n        0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,\n        0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,\n        0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,\n        0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,\n        0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,\n        0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,\n        0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,\n        0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,\n        0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,\n        0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,\n        0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,\n        0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,\n        0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,\n        0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,\n        0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,\n        0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,\n        0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,\n        0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,\n        0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,\n        0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,\n        0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,\n        0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,\n        0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,\n        0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,\n        0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,\n        0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,\n        0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,\n        0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,\n        0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,\n        0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,\n        0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,\n        0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,\n        0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,\n        0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,\n        0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,\n        0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,\n        0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,\n        0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,\n        0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,\n        0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,\n        0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,\n        0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,\n        0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,\n        0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,\n        0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,\n        0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,\n        0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,\n        0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,\n        0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,\n        0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,\n        0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,\n        0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,\n        0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,\n        0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,\n        0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,\n        0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,\n        0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,\n        0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,\n        0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,\n        0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,\n        0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,\n        0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,\n        0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,\n        0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,\n        0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,\n        0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,\n        0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,\n        0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,\n        0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,\n        0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,\n        0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,\n        0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,\n        0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,\n        0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,\n        0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,\n        0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,\n        0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,\n        0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,\n        0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,\n        0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,\n        0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,\n        0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,\n        0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,\n        0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,\n        0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,\n        0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,\n        0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,\n        0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,\n        0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,\n        0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,\n        0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,\n        0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,\n        0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,\n        0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,\n        0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,\n        0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,\n        0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,\n        0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,\n        0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,\n        0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,\n        0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,\n        0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,\n        0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,\n        0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,\n        0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,\n        0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,\n        0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,\n        0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6\n    };\n\n    // bcrypt IV: \"OrpheanBeholderScryDoubt\". The C implementation calls\n    // this \"ciphertext\", but it is really plaintext or an IV. We keep\n    // the name to make code comparison easier.\n    private static final int[] CIPHERTEXT = {\n        0x4f727068, 0x65616e42, 0x65686f6c,\n        0x64657253, 0x63727944, 0x6f756274\n    };\n\n    public static String create(String passwd) {\n        return create(passwd.getBytes(UTF_8), 10);\n    }\n\n    public static String create(byte[] passwd) {\n        return create(passwd, 10);\n    }\n\n    public static String create(String passwd, int logrounds) {\n        return create(passwd.getBytes(UTF_8), logrounds);\n    }\n\n    /**\n     * Hash a password using the OpenBSD bcrypt scheme\n     * @param passwd  the password to hash\n     * @param logrounds  the binary logarithm of the number\n     *                   <code>Math.pow(2,logrounds)</code>\n     * @return the hashed password (fixed 62 length string)\n     */\n    public static String create(byte[] passwd, int logrounds) {\n        Preconditions.checkArgument(logrounds >= 2 && logrounds <= 20, \n                                    \"logrounds must between 2 and 20.\");\n\n        StringBuilder builder = new StringBuilder(62).append(SEPARATOR)\n                                        .append(\"2a\").append(SEPARATOR);\n        if (logrounds < 10) {\n            builder.append('0');\n        }\n        builder.append(logrounds).append(SEPARATOR);\n\n        byte[] salt = SecureRandoms.nextBytes(16);\n        builder.append(Base64UrlSafe.encode(salt)).append(SEPARATOR);\n\n        byte[] hashed = crypt(passwd, salt, logrounds);\n        return builder.append(Base64UrlSafe.encode(hashed)).toString();\n    }\n\n    public static boolean check(String passwd, String hashed) {\n        return check(passwd.getBytes(UTF_8), hashed);\n    }\n\n    /**\n     * Check that a plaintext password matches a previously hashed one\n     * @param passwd  the plaintext password to verify\n     * @param hashed  the previously-hashed password\n     * @return  true if the passwords match, false otherwise\n     */\n    public static boolean check(byte[] passwd, String hashed) {\n        String[] parts = hashed.split(\"\\\\\" + SEPARATOR);\n        if (parts.length != 5 || !\"2a\".equals(parts[1])) {\n            throw new IllegalArgumentException(\"Invalid hashed value: \" + hashed);\n        }\n\n        int logrounds = Integer.parseInt(parts[2]);\n        byte[] salt = Base64UrlSafe.decode(parts[3]);\n        byte[] actual = Base64UrlSafe.decode(parts[4]);\n\n        // crypt passwd by salt\n        byte[] expect = crypt(passwd, salt, logrounds);\n\n        return Arrays.equals(actual, expect);\n    }\n\n    public static byte[] crypt(byte[] passwd, byte[] salt, int logrounds) {\n        int[] ciphertext = Arrays.copyOf(CIPHERTEXT, CIPHERTEXT.length);\n        return crypt(passwd, salt, logrounds, ciphertext);\n    }\n\n    // ---------------------------------------------------------------------------private methods\n    /**\n     * Perform the central password hashing step in the bcrypt scheme\n     * @param passwd   the password to hash\n     * @param salt       the binary salt to hash with the password\n     * @param logrounds  the binary logarithm of the number\n     *                   of rounds for hashing to apply\n     * @param cdata      the plaintext to encrypt\n     * @return  an array containing the binary hashed password\n     */\n    public static byte[] crypt(byte[] passwd, byte[] salt, int logrounds, int[] cdata) {\n        if (logrounds < 2 || logrounds > 20) {\n            throw new IllegalArgumentException(\"logrounds must between 2 and 20.\");\n        }\n        if (salt.length != 16) {\n            throw new IllegalArgumentException(\"bad salt length\");\n        }\n\n        int[] P = Arrays.copyOf(P_ORIGIN, P_ORIGIN.length), \n              S = Arrays.copyOf(S_ORIGIN, S_ORIGIN.length);\n\n        ekskey(P, S, salt, passwd);\n\n        // Math.pow(2,5), left move logrounds bit\n        int i, j, clen = cdata.length, rounds = 1 << logrounds;\n        for (i = 0; i < rounds; i++) {\n            key(P, S, passwd);\n            key(P, S, salt);\n        }\n\n        for (i = 0; i < 64; i++) {\n            for (j = 0; j < (clen >> 1); j++) {\n                encipher(P, S, cdata, j << 1);\n            }\n        }\n\n        byte[] result = new byte[clen << 2];\n        for (i = 0, j = 0; i < clen; i++) {\n            result[j++] = (byte) ((cdata[i] >> 24) & 0xff);\n            result[j++] = (byte) ((cdata[i] >> 16) & 0xff);\n            result[j++] = (byte) ((cdata[i] >> 8)  & 0xff);\n            result[j++] = (byte) ( cdata[i]        & 0xff);\n        }\n        return result;\n    }\n\n    /**\n     * Perform the \"enhanced key schedule\" step described by\n     * Provos and Mazieres in \"A Future-Adaptable Password Scheme\"\n     * http://www.openbsd.org/papers/bcrypt-paper.ps\n     * @param P  Mix password into the internal P-array of state\n     *           Encrypt state using the lower 8 bytes of salt, \n     *           and store the 8 byte result in P1|P2\n     * @param S  Mix encrypted state into the internal S-boxes of state\n     * @param data  salt information\n     * @param key   password information\n     */\n    private static void ekskey(int[] P, int[] S, byte[] data, byte[] key) {\n        int[] koffp = { 0 }, doffp = { 0 }, lr = { 0, 0 };\n        int i, plen = P.length, slen = S.length;\n\n        for (i = 0; i < plen; i++) {\n            P[i] = P[i] ^ streamtoword(key, koffp);\n        }\n\n        for (i = 0; i < plen; i += 2) {\n            lr[0] ^= streamtoword(data, doffp);\n            lr[1] ^= streamtoword(data, doffp);\n            encipher(P, S, lr, 0);\n            P[i]     = lr[0];\n            P[i + 1] = lr[1];\n        }\n\n        for (i = 0; i < slen; i += 2) {\n            lr[0] ^= streamtoword(data, doffp);\n            lr[1] ^= streamtoword(data, doffp);\n            encipher(P, S, lr, 0);\n            S[i]     = lr[0];\n            S[i + 1] = lr[1];\n        }\n    }\n\n    /**\n     * Cycically extract a word of key material\n     * @param data  the string to extract the data from\n     * @param offp  a \"pointer\" (as a one-entry array) to the\n     * current offset into data\n     * @return  the next word of material from data\n     */\n    private static int streamtoword(byte[] data, int[] offp) {\n        int word = 0, off = offp[0];\n        for (int i = 0; i < 4; i++) {\n            word = (word << 8) | (data[off] & 0xff);\n            off = (off + 1) % data.length;\n        }\n\n        offp[0] = off;\n        return word;\n    }\n\n    /**\n     * Key the Blowfish cipher\n     * @param P  Mix password into the internal P-array of state\n     *           Encrypt state using the lower 8 bytes of salt, \n     *           and store the 8 byte result in P1|P2\n     * @param S  Mix encrypted state into the internal S-boxes of state\n     * @param key   an array containing the key\n     */\n    private static void key(int[] P, int[] S, byte[] key) {\n        int[] koffp = { 0 }, lr = { 0, 0 };\n        int i, plen = P.length, slen = S.length;\n\n        for (i = 0; i < plen; i++) {\n            P[i] = P[i] ^ streamtoword(key, koffp);\n        }\n\n        for (i = 0; i < plen; i += 2) {\n            encipher(P, S, lr, 0);\n            P[i]     = lr[0];\n            P[i + 1] = lr[1];\n        }\n\n        for (i = 0; i < slen; i += 2) {\n            encipher(P, S, lr, 0);\n            S[i]     = lr[0];\n            S[i + 1] = lr[1];\n        }\n    }\n\n    /**\n     * Blowfish encipher a single 64-bit block encoded as\n     * two 32-bit halves\n     * @param P  Mix password into the internal P-array of state\n     *           Encrypt state using the lower 8 bytes of salt, \n     *           and store the 8 byte result in P1|P2\n     * @param S  Mix encrypted state into the internal S-boxes of state\n     * @param lr    an array containing the two 32-bit half blocks\n     * @param off   the position in the array of the blocks\n     */\n    private static void encipher(int[] P, int[] S, int[] lr, int off) {\n        int k = lr[off] ^ P[0], r = lr[off + 1];\n        for (int i = 0, n; i <= BLOWFISH_NUM_ROUNDS - 2;) {\n            // Feistel substitution on left word\n            n = S[(k >> 24) & 0xff];\n            n += S[0x100 | ((k >> 16) & 0xff)];\n            n ^= S[0x200 | ((k >> 8) & 0xff)];\n            n += S[0x300 | (k & 0xff)];\n            r ^= n ^ P[++i];\n\n            // Feistel substitution on right word\n            n = S[(r >> 24) & 0xff];\n            n += S[0x100 | ((r >> 16) & 0xff)];\n            n ^= S[0x200 | ((r >> 8) & 0xff)];\n            n += S[0x300 | (r & 0xff)];\n            k ^= n ^ P[++i];\n        }\n        lr[off]     = r ^ P[BLOWFISH_NUM_ROUNDS + 1];\n        lr[off + 1] = k;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/passwd/Crypt.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.passwd;\n\nimport cn.ponfee.commons.jce.HmacAlgorithms;\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.digest.HmacUtils;\nimport cn.ponfee.commons.util.Base64UrlSafe;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport com.google.common.base.Preconditions;\n\nimport javax.crypto.Mac;\nimport java.security.Provider;\nimport java.util.Arrays;\n\nimport static cn.ponfee.commons.jce.HmacAlgorithms.ALGORITHM_MAPPING;\n\n/**\n * The passwd crypt based hmac\n * \n * @author Ponfee\n */\npublic class Crypt {\n\n    private static final char SEPARATOR = '$';\n\n    public static String create(String passwd) {\n        return create(HmacAlgorithms.HmacSHA256, passwd, 32, Providers.BC);\n    }\n\n    /**\n     * create crypt\n     * @param alg\n     * @param passwd\n     * @param rounds    the loop hmac count, between 1 and 255\n     * @param provider\n     * @return\n     */\n    public static String create(HmacAlgorithms alg, String passwd, \n                                int rounds, Provider provider) {\n        Preconditions.checkArgument(rounds >= 1 && rounds <= 0xFF, \n                                    \"iterations must between 1 and 255\");\n\n        byte[] salt = SecureRandoms.nextBytes(16);\n        int algIdx = ALGORITHM_MAPPING.inverse().get(alg) & 0xF; // maximum is 0xf\n        byte[] hashed = crypt(alg, passwd.getBytes(), salt, rounds, provider);\n\n        return new StringBuilder(6 + ((salt.length + hashed.length) << 2) / 3 + 4)\n                    .append(SEPARATOR).append(Integer.toString((algIdx << 8) | rounds, 16))\n                    .append(SEPARATOR).append(Base64UrlSafe.encode(salt))\n                    .append(SEPARATOR).append(Base64UrlSafe.encode(hashed))\n                    .toString();\n    }\n\n    public static boolean check(String passwd, String hashed) {\n        return check(passwd, hashed, null);\n    }\n\n    /**\n     * check the passwd crypt\n     * @param passwd\n     * @param hashed\n     * @param provider\n     * @return {@code true} is success\n     */\n    public static boolean check(String passwd, String hashed, Provider provider) {\n        String[] parts = hashed.split(\"\\\\\" + SEPARATOR);\n        if (parts.length != 4) {\n            throw new IllegalArgumentException(\"Invalid hashed value\");\n        }\n\n        int params = Integer.parseInt(parts[1], 16);\n        HmacAlgorithms alg = ALGORITHM_MAPPING.get(params >> 8 & 0xF);\n        byte[] salt = Base64UrlSafe.decode(parts[2]);\n        byte[] testHash = crypt(alg, passwd.getBytes(), salt, params & 0xFF, provider);\n\n        // compare\n        return Arrays.equals(Base64UrlSafe.decode(parts[3]), testHash);\n    }\n\n    /**\n     * crypt with hmac\n     * @param alg\n     * @param password\n     * @param salt\n     * @param rounds\n     * @param provider\n     * @return\n     */\n    private static byte[] crypt(HmacAlgorithms alg, byte[] password, \n                                byte[] salt, int rounds, Provider provider) {\n        Mac mac = HmacUtils.getInitializedMac(alg, provider, salt);\n        password = mac.doFinal(password);\n        for (int i = 1; i < rounds; i++) {\n            mac.update(Bytes.toBytes(i));\n            password = mac.doFinal(password);\n        }\n        return password;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/passwd/PBKDF2.java",
    "content": "package cn.ponfee.commons.jce.passwd;\n\nimport cn.ponfee.commons.jce.HmacAlgorithms;\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.util.Base64UrlSafe;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport com.google.common.base.Preconditions;\n\nimport javax.crypto.SecretKeyFactory;\nimport javax.crypto.spec.PBEKeySpec;\nimport java.security.spec.InvalidKeySpecException;\nimport java.util.Arrays;\n\nimport static cn.ponfee.commons.jce.HmacAlgorithms.ALGORITHM_MAPPING;\n\n/**\n * PBKDF2 salted password hashing.\n * Author: havoc AT defuse.ca\n * www: http://crackstation.net/hashing-security.htm\n * \n * The OpenJDK implementation does only provide a PBKDF2HmacSHA1Factory.java which has the \"HmacSHA1\" \n * digest harcoded. As far as I tested, the Oracle JDK is not different in that sense.\n * \n * @author havoc AT defuse.ca\n * Reference from internet and with optimization\n * \n * Password-Based Key Derivation Function 2\n */\npublic final class PBKDF2 {\n    private PBKDF2() {}\n\n    private static final char SEPARATOR = '$';\n\n    /**\n     * \n     * @param password\n     * @return\n     */\n    public static String create(String password) {\n        return create(HmacAlgorithms.HmacSHA256, password.toCharArray());\n    }\n\n    public static String create(HmacAlgorithms alg, String password) {\n        return create(alg, password.toCharArray());\n    }\n\n    /**\n     * fix  salt            16 byte\n     *      iterationCount  32\n     *      dkLen           32 byte\n     * @param alg\n     * @param password\n     * @return\n     */\n    public static String create(HmacAlgorithms alg, char[] password) {\n        return create(alg, password, 16, 32, 32);\n    }\n\n    /**\n     * Returns a salted PBKDF2 hash of the password.\n     * @param alg                HmacAlgorithm, HmacAlgorithm.HmacMD5 is invalid\n     * @param password           the password to hash\n     * @param saltByteSize       the byte length of random slat\n     * @param iterationCount     the iteration count (slowness factor)\n     * @param dkLen              Intended length, in octets, of the derived key.\n     * @return a salted PBKDF2 hash of the password\n     */\n    public static String create(HmacAlgorithms alg, char[] password, int saltByteSize,\n                                int iterationCount, int dkLen) {\n        Preconditions.checkArgument(iterationCount >= 1 && iterationCount <= 0xffff, \n                                    \"iterations must between 1 and 65535\");\n        // Generate a random salt\n        byte[] salt = SecureRandoms.nextBytes(saltByteSize);\n\n        // Hash the password\n        byte[] hash = pbkdf2(alg, password, salt, iterationCount, dkLen);\n\n        int algIdx = ALGORITHM_MAPPING.inverse().get(alg) & 0xF; // maximum is 0xf\n        String params = Integer.toString(algIdx << 16L | iterationCount, 16);\n\n        // format iterations:salt:hash\n        return new StringBuilder(8 + ((salt.length + hash.length) << 2) / 3 + 4)\n                .append(SEPARATOR).append(params)\n                .append(SEPARATOR).append(Base64UrlSafe.encode(salt))\n                .append(SEPARATOR).append(Base64UrlSafe.encode(hash))\n                .toString();\n    }\n\n    /**\n     * Validates a password using a hash.\n     * @param password the password to check\n     * @param correctHash the hash of the valid password\n     * @return true if the password is correct, false if not\n     */\n    public static boolean check(String password, String correctHash) {\n        return check(password.toCharArray(), correctHash);\n    }\n\n    /**\n     * Validates a password using a hash.\n     * @param password the password to check\n     * @param correctHash the hash of the valid password\n     * @return true if the password is correct, false if not\n     */\n    public static boolean check(char[] password, String correctHash) {\n        // Decode the hash into its parameters\n        String[] parts = correctHash.split(\"\\\\\" + SEPARATOR);\n        if (parts.length != 4) {\n            throw new IllegalArgumentException(\"Invalid hashed value\");\n        }\n\n        int params = Integer.parseInt(parts[1], 16);\n        HmacAlgorithms alg = ALGORITHM_MAPPING.get(params >> 16 & 0xf);\n        int iterations = params & 0xffff;\n        byte[] salt = Base64UrlSafe.decode(parts[2]);\n        byte[] hash = Base64UrlSafe.decode(parts[3]);\n\n        // Compute the hash of the provided password, using the same salt, \n        // iteration count, and hash length\n        byte[] testHash = pbkdf2(alg, password, salt, iterations, hash.length);\n\n        // Compare the hashes in constant time. The password is correct if\n        // both hashes match.\n        return Arrays.equals(hash, testHash);\n    }\n\n    /**\n     * Computes the PBKDF2 hash of a password.\n     * @param alg             the HmacAlgorithm\n     * @param password        the password to hash\n     * @param salt            the salt\n     * @param iterationCount  the iteration count (slowness factor)\n     * @param dkLen           the length of the hash to compute in bytes\n     * @return the PBDKF2 hash of the password\n     */\n    public static byte[] pbkdf2(HmacAlgorithms alg, char[] password, byte[] salt,\n                                int iterationCount, int dkLen) {\n        PBEKeySpec spec = new PBEKeySpec(password, salt, iterationCount, dkLen << 3);\n        try {\n            SecretKeyFactory skf = Providers.getSecretKeyFactory(\"PBKDF2With\" + alg.algorithm());\n            return skf.generateSecret(spec).getEncoded();\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/passwd/SCrypt.java",
    "content": "// Copyright (C) 2011 - Will Glozer. All rights reserved.\n\npackage cn.ponfee.commons.jce.passwd;\n\nimport cn.ponfee.commons.jce.HmacAlgorithms;\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.digest.HmacUtils;\nimport cn.ponfee.commons.util.Base64UrlSafe;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport com.google.common.base.Preconditions;\n\nimport javax.crypto.Mac;\nimport javax.crypto.ShortBufferException;\nimport java.util.Arrays;\n\nimport static cn.ponfee.commons.jce.HmacAlgorithms.ALGORITHM_MAPPING;\nimport static java.lang.Integer.MAX_VALUE;\nimport static java.lang.System.arraycopy;\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\n/**\n * An implementation of the <a href=\"http://www.tarsnap.com/scrypt/scrypt.pdf\"/>scrypt</a> \n * key derivation function. This class will attempt to load a native\n * library containing the optimized C implementation from \n * <a href=\"http://www.tarsnap.com/scrypt.html\">http://www.tarsnap.com/scrypt.html</a> \n * and fall back to the pure Java version if that fails.\n * \n * @author Will Glozer\n * \n * Reference from internet and with optimization\n */\npublic final class SCrypt {\n    private SCrypt() {}\n\n    private static final char SEPARATOR = '$';\n\n    // -------------------------------------------------------------------scrypt\n    public static String create(String passwd, int N, int r, int p) {\n        return create(HmacAlgorithms.HmacSHA256, passwd, N, r, p, 32);\n    }\n\n    /**\n     * Hash the supplied plaintext password and generate output in the format\n     * described in {@link #scrypt(HmacAlgorithms, byte[], byte[], int, int, int, int)}.\n     *\n     * @param alg HmacAlgorithm.\n     * @param passwd Password.\n     * @param N CPU cost parameter.         between 0x01 and 0x0F, 2^15=32768\n     * @param r Memory cost parameter.      between 0x01 and 0xFF\n     * @param p Parallelization parameter.  between 0x01 and 0xFF\n     * @param dkLen Intended length, in octets, of the derived key.\n     * @return The hashed password.\n     */\n    public static String create(HmacAlgorithms alg, String passwd, \n                                int N, int r, int p, int dkLen) {\n        Preconditions.checkArgument(N > 0 && N <= 0xF,  \"N must between 1 and 15\");\n        Preconditions.checkArgument(r > 0 && r <= 0xFF, \"r must between 1 and 255\");\n        Preconditions.checkArgument(p > 0 && p <= 0xFF, \"p must between 1 and 255\");\n\n        int algIdx = ALGORITHM_MAPPING.inverse().get(alg) & 0xF; // maximum is 0xF\n        byte[] salt = SecureRandoms.nextBytes(16);\n        byte[] derived = scrypt(alg, passwd.getBytes(UTF_8), salt, 1 << N, r, p, dkLen);\n        String params = Integer.toString(algIdx << 20 | N << 16 | r << 8 | p, 16);\n\n        return new StringBuilder(12 + ((salt.length + derived.length) << 2) / 3 + 4)\n                        .append(SEPARATOR).append(\"s0\").append(SEPARATOR)\n                        .append(params).append(SEPARATOR)\n                        .append(Base64UrlSafe.encode(salt)).append(SEPARATOR)\n                        .append(Base64UrlSafe.encode(derived)).toString();\n    }\n\n    /**\n     * Compare the supplied plaintext password to a hashed password.\n     * \n     * @param passwd Plaintext password.\n     * @param hashed scrypt hashed password.\n     * @return true if passwd matches hashed value.\n     */\n    public static boolean check(String passwd, String hashed) {\n        String[] parts = hashed.split(\"\\\\\" + SEPARATOR);\n\n        if (parts.length != 5 || !\"s0\".equals(parts[1])) {\n            throw new IllegalArgumentException(\"Invalid hashed value\");\n        }\n\n        int params = Integer.parseInt(parts[2], 16);\n        byte[] salt = Base64UrlSafe.decode(parts[3]);\n        byte[] actual = Base64UrlSafe.decode(parts[4]);\n\n        int algIdx = (params >> 20) & 0xF ,\n                 N = (params >> 16) & 0xF ,\n                 r = (params >>  8) & 0xFF,\n                 p = (params      ) & 0xFF;\n\n        byte[] except = scrypt(ALGORITHM_MAPPING.get(algIdx), \n                               passwd.getBytes(UTF_8), salt, \n                               1 << N, r, p, actual.length);\n\n        return Arrays.equals(actual, except);\n    }\n\n    /**\n     * Implementation of PBKDF2 (RFC2898).\n     * \n     * @param alg HmacAlgorithm.\n     * @param P password of byte array.\n     * @param S Salt.\n     * @param c Iteration count.\n     * @param dkLen Intended length, in octets, of the derived key.\n     * @return the byte array of DK\n     */\n    public static byte[] pbkdf2(HmacAlgorithms alg, byte[] P, \n                                byte[] S, int c, int dkLen) {\n        Mac mac = HmacUtils.getInitializedMac(alg, Providers.BC, P);\n        int hLen = mac.getMacLength();\n\n        // ((long) 1 << 32) - 1 == 4294967295L\n        if (dkLen > 4294967295L * hLen) {\n            throw new SecurityException(\"Requested key length too long\");\n        }\n\n        byte[] U = new byte[hLen];\n        byte[] T = new byte[hLen];\n        byte[] block = new byte[S.length + 4];\n\n        int n = (int) Math.ceil((double) dkLen / hLen);\n        int r = dkLen - (n - 1) * hLen;\n\n        arraycopy(S, 0, block, 0, S.length);\n\n        byte[] DK = new byte[dkLen];\n        for (int i = 1; i <= n; i++) {\n            block[S.length    ] = (byte) (i >> 24 & 0xff);\n            block[S.length + 1] = (byte) (i >> 16 & 0xff);\n            block[S.length + 2] = (byte) (i >> 8  & 0xff);\n            block[S.length + 3] = (byte) (i       & 0xff);\n\n            mac.update(block);\n            try {\n                mac.doFinal(U, 0);\n                arraycopy(U, 0, T, 0, hLen);\n                for (int j = 1, k; j < c; j++) {\n                    mac.update(U);\n                    mac.doFinal(U, 0);\n                    for (k = 0; k < hLen; k++) {\n                        T[k] ^= U[k];\n                    }\n                }\n            } catch (ShortBufferException | IllegalStateException e) {\n                throw new SecurityException(e);\n            }\n\n            arraycopy(T, 0, DK, (i - 1) * hLen, (i == n ? r : hLen));\n        }\n        return DK;\n    }\n\n    /**\n     * Pure Java implementation of the \n     * <a href=\"http://www.tarsnap.com/scrypt/scrypt.pdf\"/>scrypt KDF</a>.\n     * \n     * @param alg HmacAlgorithm.\n     * @param P Password.\n     * @param S Salt.\n     * @param N CPU cost parameter.\n     * @param r Memory cost parameter.\n     * @param p Parallelization parameter.\n     * @param dkLen Intended length of the derived key.\n     * @return The derived key.\n     */\n    public static byte[] scrypt(HmacAlgorithms alg, byte[] P, byte[] S, \n                                int N, int r, int p, int dkLen) {\n        if (r > MAX_VALUE / 128 / p) {\n            throw new IllegalArgumentException(\"Parameter r is too large\");\n        }\n\n        if (N > MAX_VALUE / 128 / r) {\n            throw new IllegalArgumentException(\"Parameter N is too large\");\n        }\n\n        byte[] B  = pbkdf2(alg, P, S, 1, (p << 7) * r);\n        byte[] XY = new byte[r << 8],\n               V  = new byte[(r << 7) * N];\n\n        for (int i = 0; i < p; i++) {\n            smix(B, (i << 7) * r, r, N, V, XY);\n        }\n\n        return pbkdf2(alg, P, B, 1, dkLen);\n    }\n\n    // ------------------------------------------------------------------------------private methods\n    private static void smix(byte[] B, int Bi, int r, int N, byte[] V, byte[] XY) {\n        int i, Xi = 0, Yi = r << 7;\n\n        arraycopy(B, Bi, XY, Xi, Yi);\n\n        for (i = 0; i < N; i++) {\n            arraycopy(XY, Xi, V, i * Yi, Yi);\n            blockmix_salsa8(XY, Xi, Yi, r);\n        }\n\n        for (i = 0; i < N; i++) {\n            int j = integerify(XY, Xi, r) & (N - 1);\n            blockxor(V, j * Yi, XY, Xi, Yi);\n            blockmix_salsa8(XY, Xi, Yi, r);\n        }\n\n        arraycopy(XY, Xi, B, Bi, Yi);\n    }\n\n    private static void blockmix_salsa8(byte[] BY, int Bi, int Yi, int r) {\n        byte[] X = new byte[64];\n\n        arraycopy(BY, Bi + ((2 * r - 1) << 6), X, 0, 64);\n\n        int i, n, m;\n        for (i = 0, n = r << 1; i < n; i++) {\n            m = i << 6;\n            blockxor(BY, m, X, 0, 64);\n            salsa20_8(X);\n            arraycopy(X, 0, BY, Yi + m, 64);\n        }\n\n        for (i = 0; i < r; i++) {\n            arraycopy(BY, Yi + (i << 7), BY, Bi + (i << 6), 64);\n        }\n\n        for (i = 0; i < r; i++) {\n            arraycopy(BY, Yi + (((i << 1) + 1) << 6), BY, Bi + ((i + r) << 6), 64);\n        }\n    }\n\n    private static int R(int a, int b) {\n        return (a << b) | (a >>> (32 - b));\n    }\n\n    private static void salsa20_8(byte[] B) {\n        int[] B32 = new int[16], x = new int[16];\n\n        int i;\n        for (i = 0; i < 16; i++) {\n            B32[i]  = (B[(i << 2)    ] & 0xff)      ;\n            B32[i] |= (B[(i << 2) + 1] & 0xff) <<  8;\n            B32[i] |= (B[(i << 2) + 2] & 0xff) << 16;\n            B32[i] |= (B[(i << 2) + 3] & 0xff) << 24;\n        }\n\n        arraycopy(B32, 0, x, 0, 16);\n\n        for (i = 8; i > 0; i -= 2) {\n             x[4] ^=  R(x[0] + x[12],  7);\n             x[8] ^=  R(x[4] +  x[0],  9);\n            x[12] ^=  R(x[8] +  x[4], 13);\n             x[0] ^= R(x[12] +  x[8], 18);\n             x[9] ^=  R(x[5] +  x[1],  7);\n            x[13] ^=  R(x[9] +  x[5],  9);\n             x[1] ^= R(x[13] +  x[9], 13);\n             x[5] ^=  R(x[1] + x[13], 18);\n            x[14] ^= R(x[10] +  x[6],  7);\n             x[2] ^= R(x[14] + x[10],  9);\n             x[6] ^=  R(x[2] + x[14], 13);\n            x[10] ^=  R(x[6] +  x[2], 18);\n             x[3] ^= R(x[15] + x[11],  7);\n             x[7] ^=  R(x[3] + x[15],  9);\n            x[11] ^=  R(x[7] +  x[3], 13);\n            x[15] ^= R(x[11] +  x[7], 18);\n             x[1] ^=  R(x[0] +  x[3],  7);\n             x[2] ^=  R(x[1] +  x[0],  9);\n             x[3] ^=  R(x[2] +  x[1], 13);\n             x[0] ^=  R(x[3] +  x[2], 18);\n             x[6] ^=  R(x[5] +  x[4],  7);\n             x[7] ^=  R(x[6] +  x[5],  9);\n             x[4] ^=  R(x[7] +  x[6], 13);\n             x[5] ^=  R(x[4] +  x[7], 18);\n            x[11] ^= R(x[10] +  x[9],  7);\n             x[8] ^= R(x[11] + x[10],  9);\n             x[9] ^=  R(x[8] + x[11], 13);\n            x[10] ^=  R(x[9] +  x[8], 18);\n            x[12] ^= R(x[15] + x[14],  7);\n            x[13] ^= R(x[12] + x[15],  9);\n            x[14] ^= R(x[13] + x[12], 13);\n            x[15] ^= R(x[14] + x[13], 18);\n        }\n\n        for (i = 0; i < 16; ++i) {\n            B32[i] = x[i] + B32[i];\n        }\n\n        for (i = 0; i < 16; i++) {\n            B[(i << 2)    ] = (byte) (B32[i]       & 0xff);\n            B[(i << 2) + 1] = (byte) (B32[i] >> 8  & 0xff);\n            B[(i << 2) + 2] = (byte) (B32[i] >> 16 & 0xff);\n            B[(i << 2) + 3] = (byte) (B32[i] >> 24 & 0xff);\n        }\n    }\n\n    private static void blockxor(byte[] S, int Si, byte[] D, int Di, int len) {\n        for (int i = 0; i < len; i++) {\n            D[Di + i] ^= S[Si + i];\n        }\n    }\n\n    private static int integerify(byte[] B, int Bi, int r) {\n        Bi += ((2 * r - 1) << 6);\n        return ((B[Bi    ] & 0xff)      )\n             | ((B[Bi + 1] & 0xff) <<  8)\n             | ((B[Bi + 2] & 0xff) << 16)\n             | ((B[Bi + 3] & 0xff) << 24);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/pkcs/CryptoMessageSyntax.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.pkcs;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.bouncycastle.asn1.ASN1ObjectIdentifier;\nimport org.bouncycastle.cert.X509CertificateHolder;\nimport org.bouncycastle.cert.jcajce.JcaCertStore;\nimport org.bouncycastle.cms.*;\nimport org.bouncycastle.cms.jcajce.*;\nimport org.bouncycastle.operator.ContentSigner;\nimport org.bouncycastle.operator.DigestCalculatorProvider;\nimport org.bouncycastle.operator.OperatorCreationException;\nimport org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;\nimport org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;\nimport org.bouncycastle.util.Store;\n\nimport java.io.IOException;\nimport java.security.PrivateKey;\nimport java.security.cert.CertificateEncodingException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static cn.ponfee.commons.jce.Providers.BC;\n\n/**\n * 加密消息语法：Cryptography Message Syntax\n * \n * @author Ponfee\n */\npublic final class CryptoMessageSyntax {\n\n    // ---------------------------------------------------------------------------sign/verify\n    /**\n     * 附原文签名（单人）\n     * @param data\n     * @param key\n     * @param certChain\n     * @return\n     */\n    public static byte[] sign(byte[] data, PrivateKey key, X509Certificate[] certChain) {\n        return sign(data, Collections.singletonList(key), Collections.singletonList(certChain));\n    }\n\n    /**\n     * 附原文签名（多人）\n     * @param data\n     * @param keys\n     * @param certs  证书链（多人list）\n     * @return\n     */\n    public static byte[] sign(byte[] data, List<PrivateKey> keys, List<X509Certificate[]> certs) {\n        try {\n            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();\n            DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();\n            for (int i = 0; i < keys.size(); i++) {\n                gen.addCertificates(new JcaCertStore(Arrays.asList(certs.get(i))));\n\n                ContentSigner signer = new JcaContentSignerBuilder(certs.get(i)[0].getSigAlgName())\n                                                         .setProvider(BC).build(keys.get(i));\n                JcaSignerInfoGeneratorBuilder jsBuilder = new JcaSignerInfoGeneratorBuilder(dcp);\n                gen.addSignerInfoGenerator(jsBuilder.build(signer, certs.get(i)[0]));\n            }\n            return gen.generate(new CMSProcessableByteArray(data), true).getEncoded(); // true附原文\n        } catch (OperatorCreationException | CertificateEncodingException | CMSException | IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 验签（附原文）\n     * @param signed\n     * @return\n     */\n    public static void verify(byte[] signed) {\n        try {\n            CMSSignedData sign = new CMSSignedData(signed); // 构建PKCS#7签名数据处理对象\n            Store<?> store = sign.getCertificates();\n            JcaSimpleSignerInfoVerifierBuilder builder = new JcaSimpleSignerInfoVerifierBuilder()\n                                                                                 .setProvider(BC);\n            for (SignerInformation signer : sign.getSignerInfos()) {\n                @SuppressWarnings(\"unchecked\") \n                Collection<X509CertificateHolder> chain = store.getMatches(signer.getSID()); // 证书链\n                X509CertificateHolder cert = chain.iterator().next(); // 证书链的第一个为subject cert\n                if (!signer.verify(builder.build(cert))) {\n                    String sn = Hex.encodeHexString(cert.getSerialNumber().toByteArray());\n                    String dn = cert.getSubject().toString();\n                    throw new SecurityException(\"signature verify fail[\" + sn + \", \" + dn + \"]\");\n                }\n            }\n        } catch (OperatorCreationException | CertificateException | CMSException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ---------------------------------------------------------------------------envelop/unenvelop\n    /**\n     * 构造数字信封\n     * \n     * PKCSObjectIdentifiers#des_EDE3_CBC\n     * PKCSObjectIdentifiers.RC2_CBC\n     * \n     * new ASN1ObjectIdentifier(\"1.2.840.113549.3.2\"); // RSA_RC2\n     * new ASN1ObjectIdentifier(\"1.2.840.113549.3.4\"); // RSA_RC4\n     * \n     * new ASN1ObjectIdentifier(\"1.3.14.3.2.7\"); // DES_CBC\n     * new ASN1ObjectIdentifier(\"1.2.840.113549.3.7\"); // DESede_CBC\n     * \n     * new ASN1ObjectIdentifier(\"2.16.840.1.101.3.4.1.2\"); // AES128_CBC\n     * \n     * @param data\n     * @param cert\n     * @param alg\n     * @return\n     */\n    public static byte[] envelop(byte[] data, X509Certificate cert, ASN1ObjectIdentifier alg) {\n        try {\n            //添加数字信封\n            CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();\n            edGen.addRecipientInfoGenerator(\n                new JceKeyTransRecipientInfoGenerator(cert).setProvider(BC)\n            );\n\n            return edGen.generate(\n                new CMSProcessableByteArray(data),\n                new JceCMSContentEncryptorBuilder(alg).setProvider(BC).build()\n            ).getEncoded();\n        } catch (CertificateEncodingException | CMSException | IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 解数字信封\n     * @param enveloped\n     * @param privateKey\n     * @return\n     */\n    public static byte[] unenvelop(byte[] enveloped, X509Certificate cert, PrivateKey privateKey) {\n        try {\n            RecipientInformationStore ris = new CMSEnvelopedData(enveloped).getRecipientInfos();\n\n            for (RecipientInformation rin : ris.getRecipients()) {\n                KeyTransRecipientId rid = (KeyTransRecipientId) rin.getRID();\n                // 匹配\n                if (cert.getSerialNumber().equals(rid.getSerialNumber())) {\n                    // 解密\n                    return rin.getContent(\n                        new JceKeyTransEnvelopedRecipient(privateKey).setProvider(BC)\n                    );\n                }\n            }\n            return null;\n        } catch (CMSException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/pkcs/PKCS1Signature.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.pkcs;\n\nimport cn.ponfee.commons.jce.Providers;\n\nimport java.security.GeneralSecurityException;\nimport java.security.PrivateKey;\nimport java.security.Signature;\nimport java.security.cert.X509Certificate;\n\n/**\n * pkcs1方式的签名/验签工具类\n * \n * @author Ponfee\n */\npublic class PKCS1Signature {\n\n    /**\n     * 签名\n     * @param data        the byte array data to sign\n     * @param privateKey  the private key\n     * @param cert        the certificate\n     * @return the signature of private key signed\n     */\n    public static byte[] sign(byte[] data, PrivateKey privateKey, X509Certificate cert) {\n        Signature signature = Providers.getSignature(cert.getSigAlgName());\n        try {\n            signature.initSign(privateKey);\n            signature.update(data);\n            return signature.sign();\n        } catch (GeneralSecurityException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 验签\n     * @param data      the origin byte array data\n     * @param signed    the byte array of signature\n     * @param cert      the certificate\n     * @return {@code true} is success\n     */\n    public static boolean verify(byte[] data, byte[] signed, X509Certificate cert) {\n        Signature sign = Providers.getSignature(cert.getSigAlgName());\n        try {\n            sign.initVerify(cert.getPublicKey());\n            sign.update(data);\n            return sign.verify(signed);\n        } catch (GeneralSecurityException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/pkcs/PKCS7Signature.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.pkcs;\n\nimport cn.ponfee.commons.jce.Providers;\nimport org.apache.commons.codec.binary.Hex;\nimport sun.security.pkcs.ContentInfo;\nimport sun.security.pkcs.PKCS7;\nimport sun.security.pkcs.ParsingException;\nimport sun.security.pkcs.SignerInfo;\nimport sun.security.util.DerValue;\nimport sun.security.x509.AlgorithmId;\nimport sun.security.x509.X500Name;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PrivateKey;\nimport java.security.Signature;\nimport java.security.SignatureException;\nimport java.security.cert.X509Certificate;\n\n/**\n * pkcs7工具类\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"restriction\")\npublic class PKCS7Signature {\n\n    /*private static final Map<String, String> HASH_SIGN_ALG = ImmutableMap.<String, String>builder()\n        .put(\"1.2.840.113549.1.1.4\", \"MD5\")\n        .put(\"1.2.840.113549.1.1.5\", \"SHA-1\")\n        .put(\"1.2.840.113549.1.1.11\", \"SHA-256\")\n        .put(\"1.2.840.113549.1.1.12\", \"SHA-384\")\n        .put(\"1.2.840.113549.1.1.13\", \"SHA-512\")\n        .build();*/\n\n    /**\n     * byte流数据签名（单人）\n     * @param privKey\n     * @param cert\n     * @param data \n     * @param attach 是否附原文\n     * @return\n     */\n    public static byte[] sign(PrivateKey privKey, X509Certificate cert, byte[] data, boolean attach) {\n        return sign(new PrivateKey[] { privKey }, new X509Certificate[] { cert }, data, attach);\n    }\n\n    /**\n     * byte流数据签名（多人）\n     * @param privKeys\n     * @param certs\n     * @param data\n     * @param attach\n     * @return\n     */\n    public static byte[] sign(PrivateKey[] privKeys, X509Certificate[] certs, byte[] data, boolean attach) {\n        ContentInfo contentInfo;\n        if (attach) {\n            contentInfo = new ContentInfo(data);\n        } else {\n            contentInfo = new ContentInfo(ContentInfo.DATA_OID, null);\n        }\n        return sign(contentInfo, data, certs, privKeys);\n    }\n\n    /**\n     * 文本签名（单人）\n     * @param privKey\n     * @param cert\n     * @param data\n     * @param attach 是否附原文\n     * @return\n     */\n    public static byte[] sign(PrivateKey privKey, X509Certificate cert, String data, boolean attach) {\n        return sign(new PrivateKey[] { privKey }, new X509Certificate[] { cert }, data, attach);\n    }\n\n    /**\n     * 文本签名（多人）\n     * @param privKeys\n     * @param certs\n     * @param data\n     * @param attach\n     * @return\n     */\n    public static byte[] sign(PrivateKey[] privKeys, X509Certificate[] certs, String data, boolean attach) {\n        try {\n            DerValue dv = null;\n            if (attach) {\n                dv = new DerValue(data);\n            }\n            ContentInfo contentInfo = new ContentInfo(ContentInfo.DATA_OID, dv);\n            return sign(contentInfo, data.getBytes(), certs, privKeys);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 附原文的验签（pkcs7方式验签，可验证CMS格式签名）\n     * @param pkcs7Data  the pkcs7 byte array data, with origin\n     * @return the origin byte data\n     */\n    public static byte[] verify(byte[] pkcs7Data) {\n        PKCS7 pkcs7 = getPkcs7(pkcs7Data);\n        byte[] data = getContent(pkcs7);\n        verify(pkcs7, data);\n        return data;\n    }\n\n    /**\n     * 不附原文的验签（pkcs7方式验签，可验证CMS格式签名）\n     * @param pkcs7Data  the pkcs7 byte array data, without origin\n     * @param data  the origin byte data\n     * @return\n     */\n    public static void verify(byte[] pkcs7Data, byte[] data) {\n        verify(getPkcs7(pkcs7Data), data);\n    }\n\n    public static void verify(PKCS7 pkcs7, byte[] data) {\n        if (data == null || data.length == 0) {\n            throw new IllegalArgumentException(\"the origin data cannot be null.\");\n        }\n\n        try {\n            for (SignerInfo signed : pkcs7.getSignerInfos()) {\n                if (pkcs7.verify(signed, data) == null) {\n                    String certSN = Hex.encodeHexString(signed.getCertificateSerialNumber().toByteArray());\n                    String subjectDN = signed.getCertificate(pkcs7).getSubjectX500Principal().getName();\n                    throw new SecurityException(\"验签失败[certSN：\" + certSN + \"；subjectDN：\" + subjectDN + \"]\");\n                }\n            }\n        } catch (NoSuchAlgorithmException | SignatureException | IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 签名方法体\n     * @param contentInfo\n     * @param certs\n     * @param keys\n     * @return\n     */\n    private static byte[] sign(ContentInfo contentInfo, byte[] data, \n                               X509Certificate[] certs, PrivateKey[] keys) {\n        SignerInfo[] signs = new SignerInfo[keys.length];\n        AlgorithmId[] digestAlgorithmIds = new AlgorithmId[keys.length];\n        for (int i = 0; i < keys.length; i++) {\n            X509Certificate cert = certs[i];\n            PrivateKey privKey = keys[i];\n            try {\n                /*AlgorithmId digAlg = AlgorithmId.get(HASH_SIGN_ALG.get(cert.getSigAlgOID()));\n                AlgorithmId encAlg = new AlgorithmId(AlgorithmId.RSAEncryption_oid);*/\n                AlgorithmId digAlg = AlgorithmId.get(AlgorithmId.getDigAlgFromSigAlg(cert.getSigAlgName()));\n                AlgorithmId encAlg = AlgorithmId.get(AlgorithmId.getEncAlgFromSigAlg(cert.getSigAlgName()));\n                digestAlgorithmIds[i] = digAlg;\n                X500Name name = new X500Name(cert.getIssuerX500Principal().getEncoded());\n\n                Signature signer = Providers.getSignature(cert.getSigAlgName());\n                signer.initSign(privKey);\n                signer.update(data); // signer.update(data, 0, data.length);\n                signs[i] = new SignerInfo(name, cert.getSerialNumber(), digAlg, encAlg, signer.sign());\n            } catch (Exception e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n        // 构造PKCS7数据\n        PKCS7 pkcs7 = new PKCS7(digestAlgorithmIds, contentInfo, certs, signs);\n        try {\n            ByteArrayOutputStream out = new ByteArrayOutputStream();\n            pkcs7.encodeSignedData(out);\n            out.flush();\n            return out.toByteArray();\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * get the pkcs7 from byte array data\n     * @param pkcs7Data\n     * @return\n     */\n    public static PKCS7 getPkcs7(byte[] pkcs7Data) {\n        try {\n            return new PKCS7(pkcs7Data);\n        } catch (ParsingException e) {\n            throw new IllegalArgumentException(\"Invalid pacs7 data\", e);\n        }\n    }\n\n    /**\n     * get the origin byte array data from pkcs7\n     * @param pkcs7\n     * @return\n     */\n    public static byte[] getContent(PKCS7 pkcs7) {\n        ContentInfo contentInfo = pkcs7.getContentInfo();\n        try {\n            byte[] data;\n            if (contentInfo.getContent() == null) {\n                data = contentInfo.getData();\n            } else {\n                try {\n                    data = contentInfo.getContent().getOctetString();\n                } catch (Exception e) {\n                    data = contentInfo.getContent().getDataBytes();\n                }\n            }\n            return data;\n        } catch (IOException e) {\n            throw new SecurityException(\"Get content from pkcs7 occur error\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/security/DHKeyExchanger.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.security;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.symmetric.Algorithm;\nimport org.apache.commons.lang3.tuple.ImmutablePair;\nimport org.apache.commons.lang3.tuple.Pair;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.KeyAgreement;\nimport javax.crypto.SecretKey;\nimport javax.crypto.interfaces.DHPrivateKey;\nimport javax.crypto.interfaces.DHPublicKey;\nimport java.security.*;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.security.spec.X509EncodedKeySpec;\n\n/**\n * Diffie-Hellman Key Exchange\n * Key-Agreement\n * \n * 1、生成质数p\n * 2、找到p的原根，满足： g mod p, g^2 mod p, ..., g^(p-1) mod p\n *    是各不相同的整数，并且以某种排列方式组成了从1到p-1的所有整数\n * 3、对于一个整数b和质数p的一个原根g，可以找到惟一的指数i，使得\n *    b=g^i mod p， 0<=i<=p-1，指数i称为b的以g为基数的模p的离散对数或者指数，\n *    该值被记为ind(g ,p(b))\n * \n * 4、用户A选择一个随机数作为私钥XA<p，并计算公钥YA=g^XA mod p\n * 5、用户B选择一个随机数作为私钥XB<p，并计算公钥YB=g^XB mod p\n * 6、(YB)^XA mod p = K = (YA)^XB mod p\n * 7、 K = YB^XA mod p\n *      = (g^XB mod p)^XA mod p\n *      = (g^XB)^XA mod p          <-->  (a^b) mod p = ((a mod p)^b) mod p\n *      = (g^XA mod p)^XB mod p\n *      = (YA)^XB mod p\n *\n * g^(a*b) mod p = g^(b*a) mod p\n *\n * @author Ponfee\n */\npublic final class DHKeyExchanger {\n\n    private static final String ALGORITHM = \"DH\"; // DH算法名称\n\n    // -------------------------------------------------------------初始化甲方密钥对\n    public static Pair<DHPublicKey, DHPrivateKey> initPartAKey() {\n        return initPartAKey(1024);\n    }\n\n    /**\n     * 初始化甲方密钥\n     * @param keySize must be a multiple of 64, ranging from 512 to 1024 (inclusive).\n     * @return\n     */\n    public static Pair<DHPublicKey, DHPrivateKey> initPartAKey(int keySize) {\n        KeyPairGenerator keyPairGenerator = Providers.getKeyPairGenerator(ALGORITHM);\n        keyPairGenerator.initialize(keySize);\n        KeyPair pair = keyPairGenerator.generateKeyPair();\n        return ImmutablePair.of(\n            (DHPublicKey) pair.getPublic(), // 甲方公钥\n            (DHPrivateKey) pair.getPrivate() // // 甲方私钥\n        );\n    }\n\n    // -------------------------------------------------------------初始化已方密钥对\n    /**\n     * 初始化乙方密钥\n     * @param partAPubKey 甲方公钥\n     * @return\n     */\n    public static Pair<DHPublicKey, DHPrivateKey> initPartBKey(byte[] partAPubKey) {\n        return initPartBKey(decodePublicKey(partAPubKey));\n    }\n\n    /**\n     * 初始化乙方密钥\n     * \n     * @param partAPublicKey 甲方公钥\n     * @return\n     */\n    public static Pair<DHPublicKey, DHPrivateKey> initPartBKey(DHPublicKey partAPublicKey) {\n        // 由甲方公钥构建乙方密钥\n        KeyPairGenerator keyPairGen = Providers.getKeyPairGenerator(partAPublicKey.getAlgorithm());\n        try {\n            keyPairGen.initialize(partAPublicKey.getParams());\n        } catch (InvalidAlgorithmParameterException e) {\n            throw new SecurityException(e);\n        }\n\n        KeyPair keyPair = keyPairGen.generateKeyPair();\n        return ImmutablePair.of(\n            (DHPublicKey) keyPair.getPublic(), // 乙方公钥\n            (DHPrivateKey) keyPair.getPrivate() // // 乙方私钥\n        );\n    }\n\n    // -------------------------------------------------------------密钥序列化\n    /**\n     * DHPublicKey convert to byte array\n     * @param key the DHPublicKey\n     * @return byte array encoded of DHPublicKey\n     */\n    public static byte[] encode(DHPublicKey key) {\n        return key.getEncoded();\n    }\n\n    /**\n     * DHPrivateKey convert to byte array\n     * @param key the DHPrivateKey\n     * @return byte array encoded of DHPrivateKey\n     */\n    public static byte[] encode(DHPrivateKey key) {\n        return key.getEncoded();\n    }\n\n    // -------------------------------------------------------------密钥反序列化\n    /**\n     * 取得私钥\n     * @param privateKey\n     * @return\n     */\n    public static DHPrivateKey decodePrivateKey(byte[] privateKey) {\n        KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM);\n        try {\n            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);\n            return (DHPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 取得公钥\n     * @param publicKey\n     * @return\n     */\n    public static DHPublicKey decodePublicKey(byte[] publicKey) {\n        KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM);\n        try {\n            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey);\n            return (DHPublicKey) keyFactory.generatePublic(x509KeySpec);\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 双方公私钥生成（协商）对称密钥\n     * @param bPriKey\n     * @param aPubKey\n     * @return\n     */\n    public static SecretKey genSecretKey(byte[] bPriKey, byte[] aPubKey) {\n        return genSecretKey(decodePrivateKey(bPriKey), decodePublicKey(aPubKey));\n    }\n\n    /**\n     * 双方公私钥生成（协商）对称密钥\n     * @param bPriKey\n     * @param aPubKey\n     * @return\n     */\n    public static SecretKey genSecretKey(DHPrivateKey bPriKey, DHPublicKey aPubKey) {\n        KeyAgreement keyAgree = Providers.getKeyAgreement(aPubKey.getAlgorithm());\n        try {\n            keyAgree.init(bPriKey);\n            keyAgree.doPhase(aPubKey, true);\n            // 生成对称密钥，使用3DES对称加密，192位的AES被限制出口\n            return keyAgree.generateSecret(Algorithm.DESede.name());\n        } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 加密<br>\n     * @param data 待加密数据\n     * @param secretKey 双方公私钥协商的对称密钥\n     * @return\n     */\n    public static byte[] encrypt(byte[] data, SecretKey secretKey) {\n        Cipher cipher = Providers.getCipher(secretKey.getAlgorithm());\n        try {\n            cipher.init(Cipher.ENCRYPT_MODE, secretKey);\n            return cipher.doFinal(data);\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 解密<br>\n     * @param data 待解密数据\n     * @param secretKey 双方公私钥协商的对称密钥\n     * @return\n     */\n    public static byte[] decrypt(byte[] data, SecretKey secretKey) {\n        Cipher cipher = Providers.getCipher(secretKey.getAlgorithm());\n        try {\n            cipher.init(Cipher.DECRYPT_MODE, secretKey);\n            return cipher.doFinal(data);\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/security/DSASigner.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.security;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.UuidUtils;\nimport org.apache.commons.lang3.tuple.ImmutablePair;\nimport org.apache.commons.lang3.tuple.Pair;\n\nimport java.security.*;\nimport java.security.interfaces.DSAPrivateKey;\nimport java.security.interfaces.DSAPublicKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.security.spec.X509EncodedKeySpec;\n\n/**\n * 基于整数有限域离散对数难题\n *\n * DSA签名/验签（只用于数字签名）\n * \n * @author Ponfee\n */\npublic final class DSASigner {\n\n    private static final String ALGORITHM = \"DSA\";\n\n    /**\n     * 默认生成密钥\n     * @return 密钥对象\n     */\n    public static Pair<DSAPublicKey, DSAPrivateKey> initKey() {\n        return initKey(UuidUtils.uuid32(), 1024);\n    }\n\n    /**\n     * 生成密钥\n     * @param seed 种子\n     * @param keySize   must be a multiple of 64, r\n     *                  anging from 512 to 1024 (inclusive).\n     * @return 密钥对象\n     */\n    public static Pair<DSAPublicKey, DSAPrivateKey> initKey(String seed, int keySize) {\n        KeyPairGenerator keygen = Providers.getKeyPairGenerator(ALGORITHM);\n        // 初始化随机产生器\n        SecureRandom secureRandom = new SecureRandom();\n        secureRandom.setSeed(seed.getBytes());\n        keygen.initialize(keySize, secureRandom);\n        KeyPair pair = keygen.genKeyPair();\n        return ImmutablePair.of(\n            (DSAPublicKey) pair.getPublic(), (DSAPrivateKey) pair.getPrivate()\n        );\n    }\n\n    public static DSAPrivateKey decodePrivateKey(byte[] privateKey) {\n        KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM);\n        try {\n            return (DSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey));\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public static DSAPublicKey decodePublicKey(byte[] publicKey) {\n        KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM);\n        try {\n            return (DSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 用私钥对信息生成数字签名\n     * @param data 原文数据\n     * @param privateKey 私钥\n     * @return  签名结果\n     */\n    public static byte[] sign(byte[] data, byte[] privateKey) {\n        return sign(data, decodePrivateKey(privateKey));\n    }\n\n    public static byte[] sign(byte[] data, DSAPrivateKey privateKey) {\n        Signature signature = Providers.getSignature(privateKey.getAlgorithm());\n        try {\n            signature.initSign(privateKey);\n            signature.update(data);\n            return signature.sign();\n        } catch (InvalidKeyException | SignatureException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 校验数字签名\n     * @param origin 原文数据\n     * @param publicKey 公钥\n     * @param signed 签名数据\n     * @return 校验成功返回true 失败返回false\n     * \n     */\n    public static boolean verify(byte[] origin, byte[] publicKey, byte[] signed) {\n        return verify(origin, decodePublicKey(publicKey), signed);\n    }\n\n    public static boolean verify(byte[] origin, DSAPublicKey publicKey, byte[] signed) {\n        Signature signature = Providers.getSignature(publicKey.getAlgorithm());\n        try {\n            signature.initVerify(publicKey);\n            signature.update(origin);\n            return signature.verify(signed);\n        } catch (InvalidKeyException | SignatureException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/security/ECDHKeyExchanger.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.security;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.symmetric.Algorithm;\nimport org.apache.commons.lang3.tuple.ImmutablePair;\nimport org.apache.commons.lang3.tuple.Pair;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.KeyAgreement;\nimport javax.crypto.SecretKey;\nimport java.security.*;\nimport java.security.interfaces.ECPrivateKey;\nimport java.security.interfaces.ECPublicKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.security.spec.X509EncodedKeySpec;\n\n/**\n * ECDH Key Exchange\n * Key-Agreement\n * \n * @author Ponfee\n */\npublic final class ECDHKeyExchanger {\n\n    private static final String ALGORITHM = \"ECDH\"; // ECDH算法名称\n\n    public static Pair<ECPublicKey, ECPrivateKey> initPartAKey() {\n        return initPartAKey(256);\n    }\n\n    /**\n     * 初始化甲方密钥\n     * \n     * @param keySize the key size: 192/224/256/384/521\n     * @return the key map\n     */\n    public static Pair<ECPublicKey, ECPrivateKey> initPartAKey(int keySize) {\n        KeyPairGenerator keyPairGenerator = Providers.getKeyPairGenerator(ALGORITHM);\n        keyPairGenerator.initialize(keySize); // must be 256\n        KeyPair pair = keyPairGenerator.generateKeyPair();\n        return ImmutablePair.of(\n           (ECPublicKey) pair.getPublic(), // 甲方公钥\n           (ECPrivateKey) pair.getPrivate() // 甲方私钥\n       );\n    }\n\n    public static Pair<ECPublicKey, ECPrivateKey> initPartBKey(byte[] partAPubKey) {\n        return initPartBKey(decodePublicKey(partAPubKey));\n    }\n\n    /**\n     * 初始化乙方密钥\n     * @param partAPublicKey 甲方公钥\n     * @return\n     */\n    public static Pair<ECPublicKey, ECPrivateKey> initPartBKey(ECPublicKey partAPublicKey) {\n        // 由甲方公钥构建乙方密钥\n        KeyPairGenerator keyPairGen = Providers.getKeyPairGenerator(partAPublicKey.getAlgorithm());\n        try {\n            keyPairGen.initialize(partAPublicKey.getParams());\n        } catch (InvalidAlgorithmParameterException e) {\n            throw new SecurityException(e);\n        }\n\n        KeyPair keyPair = keyPairGen.generateKeyPair();\n        return ImmutablePair.of(\n            (ECPublicKey) keyPair.getPublic(), // 乙方公钥\n            (ECPrivateKey) keyPair.getPrivate() // 乙方私钥\n        );\n    }\n\n    /**\n     * ECPublicKey convert to byte array\n     * @param key the ECPublicKey\n     * @return byte array encoded of ECPublicKey\n     */\n    public static byte[] encode(ECPublicKey key) {\n        return key.getEncoded();\n    }\n\n    /**\n     * ECPrivateKey convert to byte array\n     * @param key the ECPrivateKey\n     * @return byte array encoded of ECPrivateKey\n     */\n    public static byte[] encode(ECPrivateKey key) {\n        return key.getEncoded();\n    }\n\n    /**\n     * 取得私钥\n     * @param privateKey\n     * @return\n     */\n    public static ECPrivateKey decodePrivateKey(byte[] privateKey) {\n        KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM);\n        try {\n            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);\n            return (ECPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 取得公钥\n     * @param publicKey\n     * @return\n     */\n    public static ECPublicKey decodePublicKey(byte[] publicKey) {\n        KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM);\n        try {\n            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey);\n            return (ECPublicKey) keyFactory.generatePublic(x509KeySpec);\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 双方公私钥生成（协商）对称密钥\n     * @param bPriKey\n     * @param aPubKey\n     * @return\n     */\n    public static SecretKey genSecretKey(byte[] bPriKey, byte[] aPubKey) {\n        return genSecretKey(decodePrivateKey(bPriKey), decodePublicKey(aPubKey));\n    }\n\n    /**\n     * 双方公私钥生成（协商）对称密钥\n     * @param bPriKey\n     * @param aPubKey\n     * @return\n     */\n    public static SecretKey genSecretKey(ECPrivateKey bPriKey, ECPublicKey aPubKey) {\n        KeyAgreement keyAgree = Providers.getKeyAgreement(aPubKey.getAlgorithm());\n        try {\n            keyAgree.init(bPriKey);\n            keyAgree.doPhase(aPubKey, true);\n            // 生成对称密钥，使用3DES对称加密，192位的AES被限制出口\n            return keyAgree.generateSecret(Algorithm.DESede.name());\n        } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 加密<br>\n     * @param data 待加密数据\n     * @param secretKey 双方公私钥协商的对称密钥\n     * @return\n     */\n    public static byte[] encrypt(byte[] data, SecretKey secretKey) {\n        Cipher cipher = Providers.getCipher(secretKey.getAlgorithm());\n        try {\n            cipher.init(Cipher.ENCRYPT_MODE, secretKey);\n            return cipher.doFinal(data);\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 解密<br>\n     * @param data 待解密数据\n     * @param secretKey 双方公私钥协商的对称密钥\n     * @return\n     */\n    public static byte[] decrypt(byte[] data, SecretKey secretKey) {\n        Cipher cipher = Providers.getCipher(secretKey.getAlgorithm());\n        try {\n            cipher.init(Cipher.DECRYPT_MODE, secretKey);\n            return cipher.doFinal(data);\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/security/ECDSASigner.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.security;\n\nimport cn.ponfee.commons.jce.Providers;\nimport org.apache.commons.lang3.tuple.ImmutablePair;\nimport org.apache.commons.lang3.tuple.Pair;\n\nimport java.security.*;\nimport java.security.interfaces.ECPrivateKey;\nimport java.security.interfaces.ECPublicKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.security.spec.X509EncodedKeySpec;\n\n/**\n * ECDSA签名算法工具类\n * http://blog.csdn.net/qq_30866297/article/details/51465439\n * \n * @author Ponfee\n */\npublic final class ECDSASigner {\n\n    public enum ECDSASignAlgorithms {\n        SHA1withECDSA, SHA256withECDSA, // \n        SHA384withECDSA, SHA512withECDSA\n    }\n\n    private static final String ALGORITHM = \"EC\";\n\n    public static Pair<ECPublicKey, ECPrivateKey> generateKeyPair() {\n        return generateKeyPair(256);\n    }\n\n    /**\n     * 密钥生成\n     * @param keySize  the key size: 192/224/256/384/521/571\n     * @return ec key map\n     */\n    public static Pair<ECPublicKey, ECPrivateKey> generateKeyPair(int keySize) {\n        KeyPairGenerator keyPairGen = Providers.getKeyPairGenerator(ALGORITHM);\n        keyPairGen.initialize(keySize);\n        KeyPair keyPair = keyPairGen.generateKeyPair();\n        return ImmutablePair.of(\n            (ECPublicKey) keyPair.getPublic(), (ECPrivateKey) keyPair.getPrivate()\n        );\n    }\n\n    /**\n     * ECPublicKey convert to byte array\n     * @param key  the ECPublicKey\n     * @return byte array encoded of ECPublicKey\n     */\n    public static byte[] encode(ECPublicKey key) {\n        return key.getEncoded();\n    }\n\n    /**\n     * ECPrivateKey convert to byte array\n     * @param key  the ECPrivateKey\n     * @return byte array encoded of ECPrivateKey\n     */\n    public static byte[] encode(ECPrivateKey key) {\n        return key.getEncoded();\n    }\n\n    /**\n     * get ECPublicKey from byte array\n     * @param publicKey\n     * @return\n     */\n    public static ECPublicKey decodePublicKey(byte[] publicKey) {\n        KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM);\n        try {\n            return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * get ECPrivateKey from byte array\n     * @param privateKey\n     * @return\n     */\n    public static ECPrivateKey decodePrivateKey(byte[] privateKey) {\n        KeyFactory keyFactory = Providers.getKeyFactory(ALGORITHM);\n        try {\n            return (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey));\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public static byte[] signSha1(byte[] data, ECPrivateKey privateKey) {\n        return sign(data, privateKey, ECDSASignAlgorithms.SHA1withECDSA);\n    }\n\n    public static boolean verifySha1(byte[] data, byte[] signed, ECPublicKey publicKey) {\n        return verify(data, signed, publicKey, ECDSASignAlgorithms.SHA1withECDSA);\n    }\n\n    public static byte[] signSha256(byte[] data, ECPrivateKey privateKey) {\n        return sign(data, privateKey, ECDSASignAlgorithms.SHA256withECDSA);\n    }\n\n    public static boolean verifySha256(byte[] data, byte[] signed, ECPublicKey publicKey) {\n        return verify(data, signed, publicKey, ECDSASignAlgorithms.SHA256withECDSA);\n    }\n\n    public static byte[] signSha512(byte[] data, ECPrivateKey privateKey) {\n        return sign(data, privateKey, ECDSASignAlgorithms.SHA512withECDSA);\n    }\n\n    public static boolean verifySha512(byte[] data, byte[] signed, ECPublicKey publicKey) {\n        return verify(data, signed, publicKey, ECDSASignAlgorithms.SHA512withECDSA);\n    }\n\n    private static byte[] sign(byte[] data, ECPrivateKey privateKey, \n                               ECDSASignAlgorithms algorithm) {\n        Signature signature = Providers.getSignature(algorithm.name());\n        try {\n            signature.initSign(privateKey);\n            signature.update(data);\n            return signature.sign();\n        } catch (InvalidKeyException | SignatureException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    private static boolean verify(byte[] data, byte[] signed, ECPublicKey publicKey, \n                                  ECDSASignAlgorithms algorithm) {\n        Signature signature = Providers.getSignature(algorithm.name());\n        try {\n            signature.initVerify(publicKey);\n            signature.update(data);\n            return signature.verify(signed);\n        } catch (InvalidKeyException | SignatureException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /*public static <T extends Key & ECKey> byte[] encrypt(byte[] data, T key) {\n        return docrypt(data, key, Cipher.ENCRYPT_MODE);\n    }\n\n    public static <T extends Key & ECKey> byte[] decrypt(byte[] encrypted, T key) {\n        return docrypt(encrypted, key, Cipher.DECRYPT_MODE);\n    }\n\n    private static byte[] docrypt(byte[] data, Key key, int cryptMode) {\n        try {\n            Cipher cipher = Providers.getCipher(key.getAlgorithm());\n            cipher.init(cryptMode, key);\n            return cipher.doFinal(data);\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }*/\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/security/KeyStoreResolver.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.security;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.cert.X509CertUtils;\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport cn.ponfee.commons.util.SecureRandoms;\n\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.security.*;\nimport java.security.cert.Certificate;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.Enumeration;\nimport java.util.List;\n\n/**\n * 密钥库解析类\n * \n * @author Ponfee\n */\npublic class KeyStoreResolver {\n\n    private static final SecureRandom SECURE_RANDOM =\n        new SecureRandom(SecureRandoms.generateSeed(20));\n\n    public enum KeyStoreType {\n        JKS, PKCS12\n    }\n\n    private final KeyStore keyStore;\n\n    public KeyStoreResolver(KeyStoreType type) {\n        this(type, null);\n    }\n\n    public KeyStoreResolver(KeyStoreType type, String storePassword) {\n        this(type, (InputStream) null, storePassword);\n    }\n\n    public KeyStoreResolver(KeyStoreType type, byte[] keyStore, String storePassword) {\n        this(type, new ByteArrayInputStream(keyStore), storePassword);\n    }\n\n    /**\n     * 创建密钥库\n     * @param type           密钥库类型\n     * @param input          密钥库输入流数据\n     * @param storePassword  用于解锁密钥库\n     */\n    public KeyStoreResolver(KeyStoreType type, InputStream input, String storePassword) {\n        this.keyStore = Providers.getKeyStore(type.name());\n        try (InputStream inputStream = input) {\n            this.keyStore.load(inputStream, toCharArray(storePassword));\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 添加证书\n     * @param alias    别名\n     * @param cert     证书\n     */\n    public void setCertificateEntry(String alias, Certificate cert) {\n        try {\n            checkAliasNotExists(alias);\n            this.keyStore.setCertificateEntry(alias, cert);\n        } catch (KeyStoreException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 设置私钥\n     * @param alias          别名\n     * @param key            私钥\n     * @param keyPassword    私钥加锁密码\n     * @param chain\n     */\n    public final void setKeyEntry(String alias, PrivateKey key, \n                                  String keyPassword, Certificate[] chain) {\n        try {\n            checkAliasNotExists(alias);\n            this.keyStore.setKeyEntry(alias, key, keyPassword.toCharArray(), chain);\n        } catch (KeyStoreException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * set key entry\n     * \n     * @param alias\n     * @param encryptedPkcs8Key\n     * @param chain\n     * @see RSAPrivateKeys#toEncryptedPkcs8(java.security.interfaces.RSAPrivateKey, String)\n     */\n    public final void setKeyEntry(String alias, byte[] encryptedPkcs8Key, \n                                  Certificate[] chain) {\n        try {\n            checkAliasNotExists(alias);\n            this.keyStore.setKeyEntry(alias, encryptedPkcs8Key, chain);\n        } catch (KeyStoreException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public byte[] export(String storePassword) {\n        ByteArrayOutputStream out = new ByteArrayOutputStream();\n        export(out, storePassword);\n        return out.toByteArray();\n    }\n\n    /**\n     * 导出密钥库\n     * @param out             目标输出流\n     * @param storePassword   设置要导出密钥库的密码\n     */\n    public void export(OutputStream out, String storePassword) {\n        try {\n            keyStore.store(out, toCharArray(storePassword));\n            out.flush();\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 枚举密钥库条目\n     * @return\n     */\n    public List<String> listAlias() {\n        try {\n            List<String> alias = new ArrayList<>();\n            Enumeration<String> e = keyStore.aliases();\n            while (e.hasMoreElements()) {\n                alias.add(e.nextElement());\n            }\n            return alias;\n        } catch (KeyStoreException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public void delAlias(String alias) {\n        try {\n            if (keyStore.containsAlias(alias)) {\n                keyStore.deleteEntry(alias);// 删除别名对应的条目\n            }\n        } catch (KeyStoreException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public String getFirstAlias() {\n        try {\n            return keyStore.aliases().nextElement();\n        } catch (KeyStoreException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public Certificate getCertificate() {\n        return getCertificate(getFirstAlias());\n    }\n\n    /**\n     * 获取证书\n     * \n     * @param alias\n     * @return\n     */\n    public Certificate getCertificate(String alias) {\n        try {\n            //if (!keyStore.isCertificateEntry(alias)) { // pfx cert isNotCertificateEntry \n            //    throw new SecurityException(alias + \" is not certificate entry.\");\n            //}\n            return keyStore.getCertificate(alias);\n        } catch (KeyStoreException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public PrivateKey getPrivateKey(String keyPassword) {\n        return getPrivateKey(getFirstAlias(), keyPassword);\n    }\n\n    /**\n     * 获取私钥\n     * @param alias         别名\n     * @param keyPassword   the password for recovering the PrivateKey\n     * @return\n     */\n    public PrivateKey getPrivateKey(String alias, String keyPassword) {\n        try {\n            if (!keyStore.isKeyEntry(alias)) {\n                throw new SecurityException(\"alias[\" + alias + \"] is not key entry.\");\n            }\n            return (PrivateKey) keyStore.getKey(alias, toCharArray(keyPassword));\n        } catch (UnrecoverableKeyException e) {\n            throw new SecurityException(\"invalid key password: \" + keyPassword, e);\n        } catch (KeyStoreException | NoSuchAlgorithmException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public X509Certificate[] getX509CertChain() {\n        return getX509CertChain(getFirstAlias());\n    }\n\n    /**\n     * 获取证书链\n     * @param alias\n     * @return\n     */\n    public X509Certificate[] getX509CertChain(String alias) {\n        try {\n            if (!keyStore.isKeyEntry(alias)) {\n                throw new SecurityException(\"alias[\" + alias + \"] is not key entry.\");\n            }\n            Certificate[] certs = keyStore.getCertificateChain(alias);\n            X509Certificate[] x509Certchain = new X509Certificate[certs.length];\n            for (int i = 0; i < certs.length; i++) {\n                x509Certchain[i] = (X509Certificate) certs[i];\n            }\n            return x509Certchain;\n        } catch (KeyStoreException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public SSLContext getSSLContext(String keyPassword) {\n        return this.getSSLContext(keyPassword, null);\n    }\n\n    /**\n     * 获取SSLContext\n     * @param keyPassword   the password for recovering the PrivateKey\n     * @param trustStore    受信任的证书库\n     * @return\n     */\n    public SSLContext getSSLContext(String keyPassword, KeyStore trustStore) {\n        String algorithm = \"SunX509\";\n        try {\n            TrustManager[] trusts = null;\n            if (trustStore != null) {\n                TrustManagerFactory tmf = Providers.getTrustManagerFactory(algorithm);\n                tmf.init(trustStore);\n                trusts = tmf.getTrustManagers();\n            }\n\n            KeyManagerFactory kmf = Providers.getKeyManagerFactory(algorithm);\n            kmf.init(this.keyStore, toCharArray(keyPassword));\n\n            SSLContext context = Providers.getSSLContext(\"TLS\");\n            context.init(kmf.getKeyManagers(), trusts, SECURE_RANDOM);\n            return context;\n        } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyManagementException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public KeyStore getKeyStore() {\n        return keyStore;\n    }\n\n    public static KeyStoreResolver loadFromPem(String pem) {\n        KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.JKS);\n        // X509CertUtils.loadFromPem(pem) <==> X509CertUtils.loadX509Cert(pem.getBytes())\n        resolver.setCertificateEntry(DigestUtils.md5Hex(pem), X509CertUtils.loadPemCert(pem));\n        return resolver;\n    }\n\n    private void checkAliasNotExists(String alias) throws KeyStoreException {\n        if (keyStore.containsAlias(alias)) {\n            throw new SecurityException(\"alias[\" + alias + \"] is exists.\");\n        }\n    }\n\n    private static char[] toCharArray(String str) {\n        if (null == str || str.length() == 0) {\n            return null;\n        } else {\n            return str.toCharArray();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/security/RSACryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.security;\n\nimport cn.ponfee.commons.io.Closeables;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.RSASignAlgorithms;\n\nimport javax.crypto.Cipher;\nimport java.io.ByteArrayOutputStream;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.Serializable;\nimport java.security.*;\nimport java.security.interfaces.RSAKey;\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\n\nimport static cn.ponfee.commons.jce.RSACipherPaddings.ECB_PKCS1PADDING;\nimport static cn.ponfee.commons.jce.RSACipherPaddings.NONE_NOPADDING;\n\n/**\n * <pre>\n * 基于大整数因式分解的数学难题（费马小定理）\n * n=p*q, p,q互质\n *\n * RSA Cryptor\n * 加/解密\n * 签名/验签\n * </pre>\n *\n * @author Ponfee\n */\npublic final class RSACryptor {\n    private RSACryptor() {}\n\n    static final String ALG_RSA = \"RSA\";\n\n    public static RSAKeyPair generateKeyPair() {\n        return generateKeyPair(1024);\n    }\n\n    /**\n     * 密钥生成\n     * @param keySize   the RSA key size, optional is 512 or 1028\n     *                  or 2048 or 4096\n     * @return RSAKeyPair\n     */\n    public static RSAKeyPair generateKeyPair(int keySize) {\n        KeyPairGenerator keyPairGen = Providers.getKeyPairGenerator(ALG_RSA);\n        keyPairGen.initialize(keySize);\n        KeyPair pair = keyPairGen.generateKeyPair();\n        return new RSAKeyPair(\n            (RSAPrivateKey) pair.getPrivate(), (RSAPublicKey) pair.getPublic()\n        );\n    }\n\n    // ---------------------------------------sign/verify---------------------------------------\n    /**\n     * MD5 sign\n     * @param data\n     * @param privateKey\n     * @return\n     */\n    public static byte[] signMd5(byte[] data, RSAPrivateKey privateKey) {\n        return sign(data, privateKey, RSASignAlgorithms.MD5withRSA);\n    }\n\n    /**\n     * SHA1 sign\n     * @param data\n     * @param privateKey\n     * @return\n     */\n    public static byte[] signSha1(byte[] data, RSAPrivateKey privateKey) {\n        return sign(data, privateKey, RSASignAlgorithms.SHA1withRSA);\n    }\n\n    /**\n     * SHA256 sign\n     * @param data\n     * @param privateKey\n     * @return\n     */\n    public static byte[] signSha256(byte[] data, RSAPrivateKey privateKey) {\n        return sign(data, privateKey, RSASignAlgorithms.SHA256withRSA);\n    }\n\n    /**\n     * verify MD5 signature\n     * @param data\n     * @param publicKey\n     * @param signed\n     * @return\n     */\n    public static boolean verifyMd5(byte[] data, RSAPublicKey publicKey, byte[] signed) {\n        return verify(data, publicKey, signed, RSASignAlgorithms.MD5withRSA);\n    }\n\n    /**\n     * verify SHA1 signature\n     * @param data\n     * @param publicKey\n     * @param signed\n     * @return\n     */\n    public static boolean verifySha1(byte[] data, RSAPublicKey publicKey, byte[] signed) {\n        return verify(data, publicKey, signed, RSASignAlgorithms.SHA1withRSA);\n    }\n\n    /**\n     * verify SHA256 signature\n     * @param data\n     * @param publicKey\n     * @param signed\n     * @return\n     */\n    public static boolean verifySha256(byte[] data, RSAPublicKey publicKey, byte[] signed) {\n        return verify(data, publicKey, signed, RSASignAlgorithms.SHA256withRSA);\n    }\n\n    /**\n     * <pre>\n     *   1、可以通过修改生成密钥的长度来调整密文长度\n     *   2、不管明文长度是多少，RSA生成的密文长度总是固定的\n     *   3、明文长度不能超过密钥长度：\n     *     1）SUN JDK默认的RSA加密实现不允许明文长度超过密钥长度减去11字节（byte）：比如1024位（bit）的密钥，\n     *        则待加密的明文最长为1024/8-11=117（byte）\n     *     2）BouncyCastle提供的加密算法能够支持到的RSA明文长度最长为密钥长度\n     *   4、每次生成的密文都不一致证明加密算法安全：这是因为在加密前使用RSA/None/PKCS1Padding对明文信息进行了\n     *      随机数填充，为了防止已知明文攻击，随机长度的填充来防止攻击者知道明文的长度。\n     *   5、javax.crypto.Cipher是线程不安全的\n     * </pre>\n     * \n     * 大数据分块加密\n     * @param data 源数据\n     * @param key\n     * @return\n     */\n    public static <T extends Key & RSAKey> byte[] encrypt(byte[] data, T key) {\n        return docrypt(data, key, Cipher.ENCRYPT_MODE, true);\n    }\n\n    public static <T extends Key & RSAKey> byte[] encryptNoPadding(byte[] data, T key) {\n        return docrypt(data, key, Cipher.ENCRYPT_MODE, false);\n    }\n\n    public static <T extends Key & RSAKey> void encrypt(InputStream input, T key, \n                                                        OutputStream out) {\n        docrypt(input, key, out, Cipher.ENCRYPT_MODE, true);\n    }\n\n    public static <T extends Key & RSAKey> void encryptNoPadding(InputStream input, \n                                                                 T key, OutputStream out) {\n        docrypt(input, key, out, Cipher.ENCRYPT_MODE, false);\n    }\n\n    /**\n     * 大数据分块解密\n     * @param encrypted\n     * @param key\n     * @return\n     */\n    public static <T extends Key & RSAKey> byte[] decrypt(byte[] encrypted, T key) {\n        return docrypt(encrypted, key, Cipher.DECRYPT_MODE, true);\n    }\n\n    public static <T extends Key & RSAKey> byte[] decryptNoPadding(byte[] encrypted, T key) {\n        return docrypt(encrypted, key, Cipher.DECRYPT_MODE, false);\n    }\n\n    public static <T extends Key & RSAKey> void decrypt(InputStream input, T key, \n                                                        OutputStream out) {\n        docrypt(input, key, out, Cipher.DECRYPT_MODE, true);\n    }\n\n    public static <T extends Key & RSAKey> void decryptNoPadding(InputStream input, \n                                                                 T key, OutputStream out) {\n        docrypt(input, key, out, Cipher.DECRYPT_MODE, false);\n    }\n\n    // -----------------------------------private methods-------------------------------------\n    private static <T extends Key & RSAKey> int getBlockSize(int cryptMode, T key) {\n        return (cryptMode == Cipher.ENCRYPT_MODE)\n               ? key.getModulus().bitLength() / 8 - 11\n               : key.getModulus().bitLength() / 8;\n    }\n\n    private static <T extends Key & RSAKey> void docrypt(InputStream input, T key, OutputStream out, \n                                                         int cryptMode, boolean isPadding) {\n        // Providers.getCipher(key.getAlgorithm())\n        Cipher cipher = Providers.getCipher(\n            key.getAlgorithm() + (isPadding ? ECB_PKCS1PADDING.transform() : NONE_NOPADDING.transform())\n        );\n        try {\n            cipher.init(cryptMode, key);\n            byte[] buffer = new byte[getBlockSize(cryptMode, key)];\n            for (int len; (len = input.read(buffer)) != Files.EOF;) {\n                out.write(cipher.doFinal(buffer, 0, len));\n            }\n            out.flush();\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        } finally {\n            Closeables.console(input);\n        }\n    }\n\n    /**\n     * JDK supported:\n     * Cipher cipher = Cipher.getInstance(\"RSA/ECB/PKCS1Padding\");\n     * \n     * BC supported: \n     *   Cipher.getInstance(\"RSA/None/NoPadding\", Providers.BC);\n     *   Cipher.getInstance(\"RSA/ECB/NOPADDING\", Providers.BC);\n     *   ...\n     * @param data\n     * @param key\n     * @param cryptMode\n     * @param isPadding\n     * @return\n     */\n    private static <T extends Key & RSAKey> byte[] docrypt(byte[] data, T key, \n                                                           int cryptMode, boolean isPadding) {\n        int blockSize = getBlockSize(cryptMode, key);\n        // Providers.getCipher(key.getAlgorithm())\n        Cipher cipher = Providers.getCipher(\n            key.getAlgorithm() + (isPadding ? ECB_PKCS1PADDING.transform() : NONE_NOPADDING.transform())\n        );\n        try {\n            cipher.init(cryptMode, key);\n            ByteArrayOutputStream out = new ByteArrayOutputStream(data.length);\n            byte[] block;\n            for (int offSet = 0, len = data.length; offSet < len; offSet += blockSize) {\n                block = cipher.doFinal(data, offSet, Math.min(blockSize, len - offSet));\n                out.write(block, 0, block.length);\n            }\n            out.flush();\n            return out.toByteArray();\n        } catch (Exception e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 数据签名\n     * @param data\n     * @param privateKey\n     * @param alg\n     * @return\n     */\n    private static byte[] sign(byte[] data, RSAPrivateKey privateKey, RSASignAlgorithms alg) {\n        Signature signature = Providers.getSignature(alg.name());\n        try {\n            signature.initSign(privateKey);\n            signature.update(data);\n            return signature.sign();\n        } catch (SignatureException | InvalidKeyException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * 验证签名\n     * @param data\n     * @param publicKey\n     * @param signed\n     * @param alg\n     * @return\n     */\n    private static boolean verify(byte[] data, RSAPublicKey publicKey, \n                                  byte[] signed, RSASignAlgorithms alg) {\n\n        Signature signature = Providers.getSignature(alg.name());\n        try {\n            signature.initVerify(publicKey);\n            signature.update(data);\n            return signature.verify(signed);\n        } catch (InvalidKeyException | SignatureException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * RSA密钥对\n     */\n    public static final class RSAKeyPair implements Serializable {\n        private static final long serialVersionUID = -1592700389671199076L;\n        private final RSAPrivateKey privateKey;\n        private final RSAPublicKey publicKey;\n\n        private RSAKeyPair(RSAPrivateKey privateKey, RSAPublicKey publicKey) {\n            this.privateKey = privateKey;\n            this.publicKey = publicKey;\n        }\n\n        public RSAPrivateKey getPrivateKey() {\n            return privateKey;\n        }\n\n        public RSAPublicKey getPublicKey() {\n            return publicKey;\n        }\n\n        public String toPkcs8PrivateKey() {\n            return RSAPrivateKeys.toPkcs8(privateKey);\n        }\n\n        public String toPkcs1PrivateKey() {\n            return RSAPrivateKeys.toPkcs1(privateKey);\n        }\n\n        public String toPkcs8PublicKey() {\n            return RSAPublicKeys.toPkcs8(publicKey);\n        }\n\n        public String toPkcs1PublicKey() {\n            return RSAPublicKeys.toPkcs1(publicKey);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/security/RSAPrivateKeys.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.security;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.cert.X509CertUtils;\nimport org.bouncycastle.asn1.*;\nimport org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;\nimport org.bouncycastle.asn1.pkcs.PrivateKeyInfo;\nimport org.bouncycastle.openssl.PEMKeyPair;\nimport org.bouncycastle.openssl.PEMParser;\nimport org.bouncycastle.openssl.PKCS8Generator;\nimport org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;\nimport org.bouncycastle.openssl.jcajce.JcaPEMWriter;\nimport org.bouncycastle.operator.InputDecryptorProvider;\nimport org.bouncycastle.operator.OperatorCreationException;\nimport org.bouncycastle.operator.OutputEncryptor;\nimport org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;\nimport org.bouncycastle.pkcs.PKCSException;\nimport org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;\nimport org.bouncycastle.pkcs.jcajce.JcePKCSPBEOutputEncryptorBuilder;\nimport org.bouncycastle.util.io.pem.PemObject;\n\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.io.StringWriter;\nimport java.math.BigInteger;\nimport java.security.KeyFactory;\nimport java.security.interfaces.RSAPrivateCrtKey;\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.security.spec.RSAPrivateKeySpec;\nimport java.util.Base64;\n\n/**\n * <pre>\n *   PEM格式：\n *    PKCS#1：RSA PRIVATE KEY\n *    PKCS#8：PRIVATE KEY\n *    PKCS#8加密：ENCRYPTED PRIVATE KEY\n * \n * @see org.bouncycastle.openssl.PEMParser\n *   (\"CERTIFICATE REQUEST\",      new PKCS10CertificationRequestParser());\n *   (\"NEW CERTIFICATE REQUEST\",  new PKCS10CertificationRequestParser());\n *   (\"CERTIFICATE\",              new X509CertificateParser());\n *   (\"TRUSTED CERTIFICATE\",      new X509TrustedCertificateParser());\n *   (\"X509 CERTIFICATE\",         new X509CertificateParser());\n *   (\"X509 CRL\",                 new X509CRLParser());\n *   (\"PKCS7\",                    new PKCS7Parser());\n *   (\"CMS\",                      new PKCS7Parser());\n *   (\"ATTRIBUTE CERTIFICATE\",    new X509AttributeCertificateParser());\n *   (\"EC PARAMETERS\",            new ECCurveParamsParser());\n *   (\"PUBLIC KEY\",               new PublicKeyParser());\n *   (\"RSA PUBLIC KEY\",           new RSAPublicKeyParser());\n *   (\"RSA PRIVATE KEY\",          new KeyPairParser(new RSAKeyPairParser()));\n *   (\"DSA PRIVATE KEY\",          new KeyPairParser(new DSAKeyPairParser()));\n *   (\"EC PRIVATE KEY\",           new KeyPairParser(new ECDSAKeyPairParser()));\n *   (\"ENCRYPTED PRIVATE KEY\",    new EncryptedPrivateKeyParser());\n *   (\"PRIVATE KEY\",              new PrivateKeyParser());\n * </pre>\n * \n * RSA Private Key Convert\n * \n * @author Ponfee\n */\npublic final class RSAPrivateKeys {\n    private RSAPrivateKeys() {}\n\n    /**\n     * Build RSAPrivateKey with modulus and privateExponent\n     *\n     * @param modulus\n     * @param privateExponent\n     * @return the RSAPrivateKey\n     */\n    public static RSAPrivateKey toRSAPrivateKey(BigInteger modulus, BigInteger privateExponent) {\n        try {\n            return (RSAPrivateKey) Providers.getKeyFactory(RSACryptor.ALG_RSA).generatePrivate(\n                new RSAPrivateKeySpec(modulus, privateExponent) // RSAPrivateCrtKeySpec\n            );\n        } catch (Exception ex) {\n            throw new SecurityException(ex);\n        }\n    }\n\n    /**\n     * 对于某些jdk不支持私钥加密及验签，所以要反转私钥为公钥\n     * 私钥伪造公钥来支持加密及验签\n     * \n     * @param privateKey\n     * @return\n     */\n    public static RSAPublicKey inverse(RSAPrivateKey privateKey) {\n        return RSAPublicKeys.toRSAPublicKey(\n            privateKey.getModulus(), privateKey.getPrivateExponent()\n        );\n    }\n\n    // ------------------------------------------------------------EXTRACT PUBLIC KEY FROM PRIVATE KEY\n    /**\n     * extract public key from private key\n     * \n     * @param privateKey\n     * @return\n     */\n    public static RSAPublicKey extractPublicKey(RSAPrivateKey privateKey) {\n        if (!(privateKey instanceof RSAPrivateCrtKey)) {\n            throw new ClassCastException(\"The key expect a java.security.interfaces.RSAPrivateCrtKey, \"\n                                       + \"but is \" + privateKey.getClass().getCanonicalName());\n        }\n        RSAPrivateCrtKey key = (RSAPrivateCrtKey) privateKey;\n        return RSAPublicKeys.toRSAPublicKey(key.getModulus(), key.getPublicExponent());\n    }\n\n    // ------------------------------------------------------------PRIVATE KEY PKCS1 FORMAT\n    /**\n     * MIICXAIBAAKBgQCo20qAU4iyZIInpu2XzNXYHhFv6FVC/N1vsfz4ZrwX3VQaFsXf720QBkuP34Y31jy/6B+OB7DzklDBTnJXltCX2XdHyBY5WQYMX9rsQrfbvUL47u676FD1T8o1/e+cEOGS75mKQIQjyt1zCZOl26Hy6x4TPeBSdVzFNYSr7KNjLQIDAQABAoGALFd51v0YtpACRdtmJSjbNyeeOJ7wVOkGVWCOJ8UCu9mZTkiQqd+76itdCGkQW/VceqDAOJH4e93+auTozeuC1w/srrUuPASUsE/5VLwPBvR90kToC28B59wAdl31nD0KM8COq/9EdrkVkz6XO7KAik9gr3PLHCXu4i7tzf9djlkCQQDhagX7hsjJZ554Pr0uBhXHwMmhiLPOK1b3884Wc1rHTMShVGF3DJH6stJV5hXwzjXBwSA8zCbxGDsqVdmbQBkPAkEAv8Sv4GtdXCucN0GsZcRhvOmGhNkhQU7W3qkPqLaAvBzfCzT/Kty4YEWTlF+sCP1+/Chl7AHf4FQ+3ivNkftoAwJAEZ0YRJQ+okY/gsPcQnllQEuXNdEZw7VtQUjCxMxUvpgIEVcnmobX7VAF0YJ+GmfymWY+36FQNaygCunUbCYxDwJAQaKxS8+Tmbt3cVYyCnbnuP/4wbmLb03rrzQQHv+wGjKLiMtv1pzLInBN7ce9Gyqgbu/oypltpdtP1T0K1D9HPwJBAKskq4+amIGnJ7FxGiPXAi0+Y96QPbAR/WjXiIaLRvwRa4Jwy8U6E6HHfYYTeuuB7h1ga6kyzfB7nUeGyeWSSkI=\n     * \n     * convert private key to base64 pkcs1 format\n     * @param privateKey\n     * @return pkcs1  encoded base64 pkcs1 format private key\n     */\n    public static String toPkcs1(RSAPrivateKey privateKey) {\n        PrivateKeyInfo privKeyInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());\n        try {\n            return Base64.getEncoder().encodeToString(\n                privKeyInfo.parsePrivateKey().toASN1Primitive().getEncoded()\n            );\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * parse private key from pkcs1 format\n     * @param pkcs1PrivateKey\n     * @return RSAPrivateKey\n     */\n    public static RSAPrivateKey fromPkcs1(String pkcs1PrivateKey) {\n        ASN1EncodableVector v1 = new ASN1EncodableVector();\n        v1.add(new ASN1ObjectIdentifier(PKCSObjectIdentifiers.rsaEncryption.getId()));\n        v1.add(DERNull.INSTANCE);\n\n        ASN1EncodableVector v2 = new ASN1EncodableVector();\n        v2.add(new ASN1Integer(0));\n        v2.add(new DERSequence(v1));\n        v2.add(new DEROctetString(Base64.getDecoder().decode(pkcs1PrivateKey)));\n        ASN1Sequence seq = new DERSequence(v2);\n        try {\n            return fromPkcs8(Base64.getEncoder().encodeToString(seq.getEncoded()));\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ------------------------------------------------------------PRIVATE KEY PKCS1 PEM FORMAT\n    /**\n     * -----BEGIN RSA PRIVATE KEY-----\n     * MIICXAIBAAKBgQCo20qAU4iyZIInpu2XzNXYHhFv6FVC/N1vsfz4ZrwX3VQaFsXf\n     * 720QBkuP34Y31jy/6B+OB7DzklDBTnJXltCX2XdHyBY5WQYMX9rsQrfbvUL47u67\n     * 6FD1T8o1/e+cEOGS75mKQIQjyt1zCZOl26Hy6x4TPeBSdVzFNYSr7KNjLQIDAQAB\n     * AoGALFd51v0YtpACRdtmJSjbNyeeOJ7wVOkGVWCOJ8UCu9mZTkiQqd+76itdCGkQ\n     * W/VceqDAOJH4e93+auTozeuC1w/srrUuPASUsE/5VLwPBvR90kToC28B59wAdl31\n     * nD0KM8COq/9EdrkVkz6XO7KAik9gr3PLHCXu4i7tzf9djlkCQQDhagX7hsjJZ554\n     * Pr0uBhXHwMmhiLPOK1b3884Wc1rHTMShVGF3DJH6stJV5hXwzjXBwSA8zCbxGDsq\n     * VdmbQBkPAkEAv8Sv4GtdXCucN0GsZcRhvOmGhNkhQU7W3qkPqLaAvBzfCzT/Kty4\n     * YEWTlF+sCP1+/Chl7AHf4FQ+3ivNkftoAwJAEZ0YRJQ+okY/gsPcQnllQEuXNdEZ\n     * w7VtQUjCxMxUvpgIEVcnmobX7VAF0YJ+GmfymWY+36FQNaygCunUbCYxDwJAQaKx\n     * S8+Tmbt3cVYyCnbnuP/4wbmLb03rrzQQHv+wGjKLiMtv1pzLInBN7ce9Gyqgbu/o\n     * ypltpdtP1T0K1D9HPwJBAKskq4+amIGnJ7FxGiPXAi0+Y96QPbAR/WjXiIaLRvwR\n     * a4Jwy8U6E6HHfYYTeuuB7h1ga6kyzfB7nUeGyeWSSkI=\n     * -----END RSA PRIVATE KEY-----\n     * <p>\n     * \n     * new PemObject(\"RSA PRIVATE KEY\", toPkcs1Encode(privateKey))\n     * \n     * convert private key to pem format\n     * @param privateKey\n     * @return  encoded base64 pkcs1 pem fromat private key\n     */\n    public static String toPkcs1Pem(RSAPrivateKey privateKey) {\n        return X509CertUtils.exportToPem(privateKey);\n    }\n\n    /**\n     * parse private key from pem format\n     * @param pemPrivateKey  encoded pem format private key\n     * @return\n     */\n    public static RSAPrivateKey fromPkcs1Pem(String pemPrivateKey) {\n        try (Reader reader = new StringReader(pemPrivateKey); \n             PEMParser pemParser = new PEMParser(reader)\n        ) {\n            PEMKeyPair keyPair = (PEMKeyPair) pemParser.readObject();\n            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(Providers.BC);\n            //PublicKey publicKey = converter.getPublicKey(keyPair.getPublicKeyInfo());\n            return (RSAPrivateKey) converter.getPrivateKey(keyPair.getPrivateKeyInfo());\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ------------------------------------------------------------PRIVATE KEY PKCS8 FORMAT\n    /**\n     * MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKjbSoBTiLJkgiem7ZfM1dgeEW/oVUL83W+x/PhmvBfdVBoWxd/vbRAGS4/fhjfWPL/oH44HsPOSUMFOcleW0JfZd0fIFjlZBgxf2uxCt9u9Qvju7rvoUPVPyjX975wQ4ZLvmYpAhCPK3XMJk6XbofLrHhM94FJ1XMU1hKvso2MtAgMBAAECgYAsV3nW/Ri2kAJF22YlKNs3J544nvBU6QZVYI4nxQK72ZlOSJCp37vqK10IaRBb9Vx6oMA4kfh73f5q5OjN64LXD+yutS48BJSwT/lUvA8G9H3SROgLbwHn3AB2XfWcPQozwI6r/0R2uRWTPpc7soCKT2Cvc8scJe7iLu3N/12OWQJBAOFqBfuGyMlnnng+vS4GFcfAyaGIs84rVvfzzhZzWsdMxKFUYXcMkfqy0lXmFfDONcHBIDzMJvEYOypV2ZtAGQ8CQQC/xK/ga11cK5w3QaxlxGG86YaE2SFBTtbeqQ+otoC8HN8LNP8q3LhgRZOUX6wI/X78KGXsAd/gVD7eK82R+2gDAkARnRhElD6iRj+Cw9xCeWVAS5c10RnDtW1BSMLEzFS+mAgRVyeahtftUAXRgn4aZ/KZZj7foVA1rKAK6dRsJjEPAkBBorFLz5OZu3dxVjIKdue4//jBuYtvTeuvNBAe/7AaMouIy2/WnMsicE3tx70bKqBu7+jKmW2l20/VPQrUP0c/AkEAqySrj5qYgacnsXEaI9cCLT5j3pA9sBH9aNeIhotG/BFrgnDLxToTocd9hhN664HuHWBrqTLN8HudR4bJ5ZJKQg==\n     * \n     * convert private key to pkcs8 format\n     * @param privateKey\n     * @return pkcs8      base64 pkcs8 format private key\n     */\n    public static String toPkcs8(RSAPrivateKey privateKey) {\n        return Base64.getEncoder().encodeToString(privateKey.getEncoded());\n    }\n\n    /**\n     * parse private key from pkcs8 format\n     * @param pkcs8PrivateKey   encoded base64 pkcs8 fromat private key\n     * @return RSAPrivateKey\n     */\n    public static RSAPrivateKey fromPkcs8(String pkcs8PrivateKey) {\n        byte[] bytes = Base64.getDecoder().decode(pkcs8PrivateKey);\n        KeyFactory keyFactory = Providers.getKeyFactory(RSACryptor.ALG_RSA);\n        try {\n            return (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ------------------------------------------------------------Transform PRIVATE KEY ENCRYPTED PKCS8 PEM FORMAT\n    /**\n     * -----BEGIN ENCRYPTED PRIVATE KEY-----\n     * MIICrjAoBgoqhkiG9w0BDAEDMBoEFA4ymXPHyGS9n5BRIibZHRkJ+idqAgIEAASC\n     * AoA5TutO4A/F9MX4h3278DBIihEEetPGU7GxbmCySVsJraL8tXueMEZrLSDWC9rl\n     * StJR82Umv0H8fpiMKlzYyHQjmqclY7e367+tQ87EqEenZNCCC1uveYLfBM7kZ+4/\n     * m276IY6sNuJqYp3k8RrIBfYG9KCCQ7ywWiORuKvGbNAytFW9H+Z9JAyO5E70ysWe\n     * pvfeLCFbRgJ0BOkm1aSyk81MN/alZ9d6a3d/UJLnnV42u7dS++mONVi66y6gKoON\n     * Y5xVX2ICPRGoIvZmXeeQYzH02XFpTn9+vQ//KG4iGErXjNaWLSWpLyCY2qHQWwQ1\n     * YB43aX7xYOiKUN24PMiwGKwjhBaKzakMt4lcmGNd6ZcwPC+ghhkmpPcCu4gSabQk\n     * Etv5tmkChBLIjRgxmmnlEYLDl68e8vth5RquJvwB4zBOQkDo9tPcwWnOk4vbvGJP\n     * w1WXfwHU4X4oy+FOiOTe7+lOTN6CeXxfx8a91h5zS1tA16bQLAgTA7oJGPD3yHpF\n     * aBYXPNzwIOpAUkEqCSbZuuYZ5uidjrm7rV8nSjXu0fkYxzpXbQAps+NBtlKHFXNC\n     * JWvj2g6UFtk/RoT4ghgtTx11DZUh+GsKbCjLM52omDDNLK8K+GOEFzElWiZIEWfh\n     * yoUAN9KuuCprC3RsqV4K70nQewuiX5NBt9xYcaKVBQ5jRgL0xnVpquyFeFXrY0Ge\n     * EDgOZTSUVVxlbNQ+iwBb52cD2cFmPcIszSNpQ85cS8eISdYiwaW42yUa7LYpO98S\n     * jyTNnLzOMRt04gcBcp71EOWEheE9Ui6AqweSA6LUHulSbOK4+4oKtdKH5KjdcWYI\n     * WDgTEoIGOC3se36z3v5Mlr8h\n     * -----END ENCRYPTED PRIVATE KEY-----\n     * <p>\n     * \n     * convert private key to encrypted pem format\n     * @param privateKey    the private key\n     * @param outEncryptor  the out encryptor\n     * @return\n     */\n    public static String toEncryptedPkcs8Pem(RSAPrivateKey privateKey,\n                                             OutputEncryptor outEncryptor) {\n        try (StringWriter stringWriter = new StringWriter(); \n             JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)\n        ) {\n            PrivateKeyInfo privKeyInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());\n            pemWriter.writeObject(new PKCS8Generator(privKeyInfo, outEncryptor));\n            pemWriter.flush();\n            return stringWriter.toString();\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * convert private key to encrypted pem format\n     * default {@link PKCSObjectIdentifiers#pbeWithSHAAnd3_KeyTripleDES_CBC}\n     * algorithm for encrypt\n     * \n     * @param privateKey\n     * @param password\n     * @return private key pkcs8 pem format\n     */\n    public static String toEncryptedPkcs8Pem(RSAPrivateKey privateKey, String password) {\n        JcePKCSPBEOutputEncryptorBuilder builder = new JcePKCSPBEOutputEncryptorBuilder(\n            PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC\n        );\n        try {\n            return toEncryptedPkcs8Pem(privateKey, builder.build(password.toCharArray()));\n        } catch (OperatorCreationException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * Encrypts private key to pkcs#8 format\n     * \n     * @param privateKey the private key\n     * @param password   the password\n     * @return a string of encrypted private key pkcs#8 format\n     */\n    public static String toEncryptedPkcs8(RSAPrivateKey privateKey, String password) {\n        JcePKCSPBEOutputEncryptorBuilder builder = new JcePKCSPBEOutputEncryptorBuilder(\n            PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC\n        );\n        try {\n            return toEncryptedPkcs8(privateKey, builder.build(password.toCharArray()));\n        } catch (OperatorCreationException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * Encrypts private key to pkcs#8 format\n     * \n     * @param privateKey the private key\n     * @param outEncryptor the OutputEncryptor\n     * @return a string of encrypted private key pkcs#8 format\n     */\n    public static String toEncryptedPkcs8(RSAPrivateKey privateKey, OutputEncryptor outEncryptor) {\n        try {\n            PrivateKeyInfo privKeyInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());\n            PemObject pem = new PKCS8Generator(privKeyInfo, outEncryptor).generate();\n            return Base64.getEncoder().encodeToString(pem.getContent());\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ------------------------------------------------------------Parse PRIVATE KEY ENCRYPTED PKCS8 PEM FORMAT\n    /**\n     * Parse private key from encrypted pem format\n     * \n     * @param encryptedPem he encrypted pem\n     * @param inputDecryptor the InputDecryptorProvider\n     * @return RSAPrivateKey\n     */\n    public static RSAPrivateKey fromEncryptedPkcs8Pem(String encryptedPem, \n                                                      InputDecryptorProvider inputDecryptor) {\n        try (Reader reader = new StringReader(encryptedPem); \n             PEMParser pemParser = new PEMParser(reader)\n        ) {\n            PKCS8EncryptedPrivateKeyInfo encrypted = (PKCS8EncryptedPrivateKeyInfo) pemParser.readObject();\n            PrivateKeyInfo pkInfo = encrypted.decryptPrivateKeyInfo(inputDecryptor);\n            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(Providers.BC);\n            return (RSAPrivateKey) converter.getPrivateKey(pkInfo);\n        } catch (IOException | PKCSException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * Parse private key from encrypted pem format <p>\n     * default {@link JcePKCSPBEInputDecryptorProviderBuilder} input decryptor<p>\n     * \n     * @param encryptedPem the encrypted pem\n     * @param password     the password\n     * @return RSAPrivateKey\n     */\n    public static RSAPrivateKey fromEncryptedPkcs8Pem(String encryptedPem, String password) {\n        JcePKCSPBEInputDecryptorProviderBuilder builder = new JcePKCSPBEInputDecryptorProviderBuilder();\n        return fromEncryptedPkcs8Pem(encryptedPem, builder.build(password.toCharArray()));\n    }\n\n    public static RSAPrivateKey fromEncryptedPkcs8(String encryptedPrivateKey, String password) {\n        return fromEncryptedPkcs8(Base64.getDecoder().decode(encryptedPrivateKey), password);\n    }\n\n    /**\n     * Parse private key from encrypted format\n     * \n     * @param encryptedPrivateKey the encryptedPrivateKey\n     * @param password  the password\n     * @return RSAPrivateKey\n     */\n    public static RSAPrivateKey fromEncryptedPkcs8(byte[] encryptedPrivateKey, String password) {\n        JcePKCSPBEInputDecryptorProviderBuilder builder = new JcePKCSPBEInputDecryptorProviderBuilder();\n        return fromEncryptedPkcs8(encryptedPrivateKey, builder.build(password.toCharArray()));\n    }\n\n    public static RSAPrivateKey fromEncryptedPkcs8(String encryptedPrivateKey,\n                                                   InputDecryptorProvider inputDecryptor) {\n        return fromEncryptedPkcs8(Base64.getDecoder().decode(encryptedPrivateKey), inputDecryptor);\n    }\n\n    /**\n     * Parse private key from encrypted format\n     * \n     * @param encryptedPrivateKey the encryptedPrivateKey\n     * @param inputDecryptor the InputDecryptorProvider\n     * @return RSAPrivateKey\n     */\n    public static RSAPrivateKey fromEncryptedPkcs8(byte[] encryptedPrivateKey,\n                                                   InputDecryptorProvider inputDecryptor) {\n        try {\n            PKCS8EncryptedPrivateKeyInfo encrypted = new PKCS8EncryptedPrivateKeyInfo(encryptedPrivateKey);\n            PrivateKeyInfo pkInfo = encrypted.decryptPrivateKeyInfo(inputDecryptor);\n            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(Providers.BC);\n            return (RSAPrivateKey) converter.getPrivateKey(pkInfo);\n        } catch (IOException | PKCSException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/security/RSAPublicKeys.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.security;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.cert.X509CertUtils;\nimport org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;\nimport org.bouncycastle.crypto.params.RSAKeyParameters;\nimport org.bouncycastle.crypto.util.PublicKeyFactory;\nimport org.bouncycastle.openssl.PEMParser;\n\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.math.BigInteger;\nimport java.security.KeyFactory;\nimport java.security.cert.Certificate;\nimport java.security.interfaces.RSAKey;\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.RSAPublicKeySpec;\nimport java.security.spec.X509EncodedKeySpec;\nimport java.util.Base64;\n\n/**\n * PKCS#8 PEM：PUBLIC KEY\n * \n * RSA Public Key convert\n * \n * @author Ponfee\n */\npublic final class RSAPublicKeys {\n    private RSAPublicKeys() {}\n\n    /**\n     * Build RSAPublicKey with modulus and publicExponent\n     *\n     * @param modulus\n     * @param publicExponent\n     * @return the RSAPublicKey\n     */\n    public static RSAPublicKey toRSAPublicKey(BigInteger modulus, BigInteger publicExponent) {\n        try {\n            return (RSAPublicKey) Providers.getKeyFactory(RSACryptor.ALG_RSA).generatePublic(\n                new RSAPublicKeySpec(modulus, publicExponent)\n            );\n        } catch (Exception ex) {\n            throw new SecurityException(ex);\n        }\n    }\n  \n    /**\n     * 证书中获取公钥\n     * @param cert\n     * @return\n     */\n    public static RSAPublicKey getPublicKey(Certificate cert) {\n        return (RSAPublicKey) cert.getPublicKey();\n    }\n\n    /**\n     * 对于某些jdk不支持公钥解密及签名，所以要反转公钥为私钥\n     * 公钥伪造成私钥来支持解密及签名\n     * \n     * @param publicKey\n     * @return\n     */\n    public static RSAPrivateKey inverse(RSAPublicKey publicKey) {\n        return RSAPrivateKeys.toRSAPrivateKey(\n            publicKey.getModulus(), publicKey.getPublicExponent()\n        );\n    }\n\n    // ------------------------------------------------------------PUBLIC KEY PKCS1 FORMAT\n    /**\n     * MIGJAoGBAKVpbo/Wum3G5ciustuKNGvPX/rgkdZw33QGqBR5UOKUoD5/h/IeQlS7ladX+oa+ciVCXyP854Zq+0RVQ7x87DfAohLmyXlIGOJ7KLJZkUWDYSG0WsPbnTOEmxQcRzqEV5g9pVHIjgPH6N/j6HHKRs5xDEd3pVpoRBZKEncbZ85xAgMBAAE=\n     * <p>\n     * \n     * ASN1 Encode\n     * \n     * The RSA Public key PEM file is specific for RSA keys.<p>\n     * convert public key to pkcs1 format<p>\n     * @param publicKey\n     * @return\n     */\n    public static String toPkcs1(RSAPublicKey publicKey) {\n        SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());\n        try {\n            return Base64.getEncoder().encodeToString(spkInfo.parsePublicKey().getEncoded());\n        } catch (IOException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * parse public key from pkcs1 format\n     * @param pkcs1PublicKey  encoded base64 pkcs1 public key\n     * @return\n     */\n    public static RSAPublicKey fromPkcs1(String pkcs1PublicKey) {\n        byte[] bytes = Base64.getDecoder().decode(pkcs1PublicKey);\n        try {\n            org.bouncycastle.asn1.pkcs.RSAPublicKey pk = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(bytes);\n            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(pk.getModulus(), pk.getPublicExponent());\n            return (RSAPublicKey) Providers.getKeyFactory(RSACryptor.ALG_RSA).generatePublic(keySpec);\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ------------------------------------------------------------PUBLIC KEY X509 PKCS8 FORMAT\n    /**\n     * MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClaW6P1rptxuXIrrLbijRrz1/64JHWcN90BqgUeVDilKA+f4fyHkJUu5WnV/qGvnIlQl8j/OeGavtEVUO8fOw3wKIS5sl5SBjieyiyWZFFg2EhtFrD250zhJsUHEc6hFeYPaVRyI4Dx+jf4+hxykbOcQxHd6VaaEQWShJ3G2fOcQIDAQAB\n     * \n     * DER Encode\n     * \n     * convert public key to x509 pkcs8 fromat\n     * @param publicKey\n     * @return\n     */\n    public static String toPkcs8(RSAPublicKey publicKey) {\n        return Base64.getEncoder().encodeToString(publicKey.getEncoded());\n    }\n\n    /**\n     * parse public key from base64 X509 pkcs8 fromat\n     * @param pkcs8PublicKey  encoded base64 x509 pkcs8 fromat\n     * @return RSAPublicKey\n     */\n    public static RSAPublicKey fromPkcs8(String pkcs8PublicKey) {\n        byte[] keyBytes = Base64.getDecoder().decode(pkcs8PublicKey);\n        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);\n        KeyFactory keyFactory = Providers.getKeyFactory(RSACryptor.ALG_RSA);\n        try {\n            return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ------------------------------------------------------------PUBLIC KEY X509 PKCS8 PEM FORMAT\n    /**\n     * -----BEGIN PUBLIC KEY-----\n     * MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClaW6P1rptxuXIrrLbijRrz1/6\n     * 4JHWcN90BqgUeVDilKA+f4fyHkJUu5WnV/qGvnIlQl8j/OeGavtEVUO8fOw3wKIS\n     * 5sl5SBjieyiyWZFFg2EhtFrD250zhJsUHEc6hFeYPaVRyI4Dx+jf4+hxykbOcQxH\n     * d6VaaEQWShJ3G2fOcQIDAQAB\n     * -----END PUBLIC KEY-----\n     * <p>\n     * \n     * new PemObject(\"RSA PUBLIC KEY\", toPkcs8Encode(publicKey))\n     * \n     * convert public key to pem fromat (pkcs8)\n     * @param publicKey\n     * @return\n     */\n    public static String toPkcs8Pem(RSAPublicKey publicKey) {\n        return X509CertUtils.exportToPem(publicKey);\n    }\n\n    /**\n     * parse public key from pem format\n     * @param pemPublicKey  encoded pem public key\n     * @return\n     */\n    public static RSAPublicKey fromPkcs8Pem(String pemPublicKey) {\n        try (Reader reader = new StringReader(pemPublicKey); \n             PEMParser pemParser = new PEMParser(reader)\n        ) {\n            SubjectPublicKeyInfo subPkInfo = (SubjectPublicKeyInfo) pemParser.readObject();\n            RSAKeyParameters param = (RSAKeyParameters) PublicKeyFactory.createKey(subPkInfo);\n            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(param.getModulus(), param.getExponent());\n            return (RSAPublicKey) Providers.getKeyFactory(RSACryptor.ALG_RSA).generatePublic(keySpec);\n        } catch (IOException | InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * Gets the rsa key length\n     * \n     * @param rsaKey the rsa key\n     * @return a int number of key bit length\n     */\n    public static int getKeyLength(RSAKey rsaKey) {\n        return rsaKey.getModulus().bitLength();\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/sm/SM2.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.sm;\n\nimport cn.ponfee.commons.jce.ECParameters;\nimport cn.ponfee.commons.util.Base64UrlSafe;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.bouncycastle.crypto.AsymmetricCipherKeyPair;\nimport org.bouncycastle.crypto.params.ECPrivateKeyParameters;\nimport org.bouncycastle.crypto.params.ECPublicKeyParameters;\nimport org.bouncycastle.math.ec.ECPoint;\n\nimport java.math.BigInteger;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 基于椭圆曲线\n *\n * new BigInteger(\"0\") // 0为十进制数字符串表示\n * SM2 asymmetric cipher implementation\n * support encrypt/decrypt\n * and sign/verify signature\n * \n * reference the internet code and refactor optimization\n * \n * @author Ponfee\n */\npublic final class SM2 {\n\n    public static final String PRIVATE_KEY = \"SM2PrivateKey\";\n    public static final String PUBLIC_KEY = \"SM2PublicKey\";\n    private static final int KEY_LENGTH = SM3Digest.getDigestSize();\n\n    private final byte[] key = new byte[KEY_LENGTH];\n    private final SM3Digest sm3keybase = SM3Digest.getInstance(); // sm3 keybase\n    private final SM3Digest sm3c3 = SM3Digest.getInstance(); // sm3 c3\n    private final byte[] x, y;\n\n    private byte keyOffset;\n    private int count;\n\n    private SM2(ECPoint publicKey, BigInteger privateKey, BigInteger n) {\n        Objects.requireNonNull(publicKey, \"public key cannot be null.\");\n        Objects.requireNonNull(privateKey, \"private key cannot be null.\");\n\n        ECPoint point = publicKey.multiply(privateKey); // S = [h]point\n\n        byte[] x1 = point.normalize().getXCoord().toBigInteger().toByteArray();\n        byte[] y1 = point.normalize().getYCoord().toBigInteger().toByteArray();\n        int byteCount = (int) Math.ceil(n.bitLength() / 8.0D);\n\n        this.x = new byte[byteCount];\n        this.y = new byte[byteCount];\n        Bytes.tailCopy(x1, 0, x1.length, this.x, 0, this.x.length);\n        Bytes.tailCopy(y1, 0, y1.length, this.y, 0, this.y.length);\n\n        this.reset();\n    }\n\n    private void reset() {\n        this.sm3keybase.reset();\n        this.sm3keybase.update(this.x);\n        this.sm3keybase.update(this.y);\n\n        this.sm3c3.reset();\n        this.sm3c3.update(this.x);\n\n        this.count = 1;\n        nextKey();\n    }\n\n    private void nextKey() {\n        SM3Digest sm3keycur = SM3Digest.getInstance(this.sm3keybase);\n        sm3keycur.update(Bytes.toBytes(count));\n        sm3keycur.doFinal(key, 0); // update key\n        this.keyOffset = 0;\n        this.count++;\n    }\n\n    private void encrypt(byte[] data) {\n        this.sm3c3.update(data);\n\n        for (int i = 0, len = data.length; i < len;) {\n            if (keyOffset == KEY_LENGTH) {\n                nextKey();\n            }\n            data[i++] ^= key[keyOffset++];\n        }\n    }\n\n    private void decrypt(byte[] data) {\n        for (int i = 0, len = data.length; i < len;) {\n            if (keyOffset == KEY_LENGTH) {\n                nextKey();\n            }\n            data[i++] ^= key[keyOffset++];\n        }\n\n        this.sm3c3.update(data);\n    }\n\n    private byte[] doFinal() {\n        this.sm3c3.update(this.y);\n        byte[] digest = this.sm3c3.doFinal();\n        reset();\n        return digest;\n    }\n\n    public static Map<String, byte[]> generateKeyPair() {\n        return generateKeyPair(ECParameters.SM2_BEST);\n    }\n\n    /**\n     * generate the SM2 key pair\n     * a public key and a private key\n     * @param ecParam the ec parameter\n     * @return sm2 key store the map\n     */\n    public static Map<String, byte[]> generateKeyPair(ECParameters ecParam) {\n        AsymmetricCipherKeyPair key = ecParam.keyPairGenerator.generateKeyPair();\n        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();\n        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();\n\n        BigInteger priKey = ecpriv.getD(); // k\n        ECPoint pubKey = ecpub.getQ(); // K = [k]POINT_G\n\n        return ImmutableMap.of(PRIVATE_KEY, priKey.toByteArray(), \n                               PUBLIC_KEY, pubKey.getEncoded(false));\n    }\n\n    public static byte[] getPublicKey(Map<String, byte[]> keyMap) {\n        return keyMap.get(PUBLIC_KEY);\n    }\n\n    public static byte[] getPrivateKey(Map<String, byte[]> keyMap) {\n        return keyMap.get(PRIVATE_KEY);\n    }\n\n    public static ECPoint getPublicKey(byte[] publicKey) {\n        return getPublicKey(ECParameters.SM2_BEST, publicKey);\n    }\n\n    public static ECPoint getPublicKey(ECParameters ecParam, byte[] publicKey) {\n        return ecParam.curve.decodePoint(publicKey);\n    }\n\n    public static BigInteger getPrivateKey(byte[] privateKey) {\n        return new BigInteger(1, privateKey);\n    }\n\n    public static byte[] encrypt(byte[] publicKey, byte[] data) {\n        return encrypt(ECParameters.SM2_BEST, publicKey, data);\n    }\n\n    /**\n     * encrypt data by public key\n     * @param ecParam the ec parameter\n     * @param publicKey SM2 public key, point K = [k]POINT_G\n     * @param data the data to be encrypt\n     * @return encrypted byte array\n     */\n    public static byte[] encrypt(ECParameters ecParam, byte[] publicKey, byte[] data) {\n        if (ArrayUtils.isEmpty(publicKey) || ArrayUtils.isEmpty(data)) {\n            return null;\n        }\n\n        // create C1 point\n        AsymmetricCipherKeyPair key = ecParam.keyPairGenerator.generateKeyPair(); // point M\n        ECPublicKeyParameters ecPub = (ECPublicKeyParameters) key.getPublic();\n        ECPrivateKeyParameters ecPri = (ECPrivateKeyParameters) key.getPrivate();\n\n        SM2 sm2 = new SM2(ecParam.curve.decodePoint(publicKey), ecPri.getD(), ecParam.n);\n\n        byte[] c1 = ecPub.getQ().getEncoded(false); // generate random r, C1=M+rK\n        byte[] c2 = Arrays.copyOf(data, data.length); // C2=rG\n        sm2.encrypt(c2); // 加密数据\n\n        byte[] c3 = sm2.doFinal(); // 摘要\n\n        // return: C1(65) + C2(data.length) + C3(32)\n        // C1 = {0x04, X byte array, Y byte array}\n        return Bytes.concat(c1, c2, c3);\n    }\n\n    public static byte[] decrypt(byte[] privateKey, byte[] encrypted) {\n        return decrypt(ECParameters.SM2_BEST, privateKey, encrypted);\n    }\n\n    /**\n     * decrypt the encrypted byte array data by private key\n     * @param ecParam the ec parameter\n     * @param privateKey SM2 private key\n     * @param encrypted the encrypted byte array data\n     * @return the origin byte array data\n     */\n    public static byte[] decrypt(ECParameters ecParam, \n                                 byte[] privateKey, byte[] encrypted) {\n        if (ArrayUtils.isEmpty(privateKey) || ArrayUtils.isEmpty(encrypted)) {\n            return null;\n        }\n\n        // 分解加密数据\n        // C1公钥 = 1位标志位+64位公钥（共65位）\n        // C2数据 = encrypted.length-C1-C3\n        // C3摘要 = 32\n        int c1Len = 65, c3Len = 32, c2Len = encrypted.length - (c1Len + c3Len);\n        byte[] c1 = Arrays.copyOf(encrypted, c1Len);\n        byte[] c2 = Arrays.copyOfRange(encrypted, c1Len, c1Len + c2Len);\n        byte[] c3 = Arrays.copyOfRange(encrypted, c1Len + c2Len, encrypted.length);\n\n        SM2 sm2 = new SM2(getPublicKey(ecParam, c1), \n                          getPrivateKey(privateKey), ecParam.n);\n\n        sm2.decrypt(c2); // 解密\n\n        if (!Arrays.equals(c3, sm2.doFinal())) {\n            throw new SecurityException(\"Invalid SM3 digest.\");\n        }\n\n        //返回解密结果\n        return c2;\n    }\n\n    // ----------------------------------------------------------------signature sign\n    public static byte[] sign(byte[] data, byte[] publicKey, \n                              byte[] privateKey) {\n        return sign(data, null, publicKey, privateKey);\n    }\n\n    public static byte[] sign(byte[] data, byte[] ida, \n                              byte[] publicKey, byte[] privateKey) {\n        return sign(ECParameters.SM2_BEST, data, ida, publicKey, privateKey);\n    }\n\n    public static byte[] sign(ECParameters ecParam, byte[] data, \n                              byte[] publicKey, byte[] privateKey) {\n        return sign(ecParam, data, null, publicKey, privateKey);\n    }\n\n    /**\n     * sm2 sign\n     * @param ecParam the ec parameter\n     * @param data 签名信息\n     * @param ida  签名方唯一标识，如：Alice@gmail.com\n     * @param publicKey 公钥\n     * @param privateKey 私钥\n     * @return 签名信息\n     */\n    public static byte[] sign(ECParameters ecParam, byte[] data, byte[] ida, \n                              byte[] publicKey, byte[] privateKey) {\n        ECPoint pubKey = getPublicKey(ecParam, publicKey);\n        BigInteger priKey = getPrivateKey(privateKey);\n\n        SM3Digest sm3 = SM3Digest.getInstance();\n        sm3.update(calcZ(sm3, ecParam, ida, pubKey));\n        sm3.update(data);\n        BigInteger e = new BigInteger(1, sm3.doFinal()), k ,r;\n        do {\n            k = SecureRandoms.random(ecParam.n);\n            ECPoint p = ecParam.pointG.multiply(k).normalize();\n            r = e.add(p.getXCoord().toBigInteger()).mod(ecParam.n);\n        } while (r.equals(BigInteger.ZERO) || r.add(k).equals(ecParam.n));\n\n        BigInteger n1 = priKey.add(BigInteger.ONE).modInverse(ecParam.n);\n        BigInteger n2 = k.subtract(r.multiply(priKey));\n        BigInteger n3 = n1.multiply(n2.mod(ecParam.n));\n        BigInteger s = n3.mod(ecParam.n);\n\n        return new Signature(r, s, ecParam.n).toByteArray();\n    }\n\n    // ----------------------------------------------------------------signature verify\n    public static boolean verify(byte[] data, byte[] signed, byte[] publicKey) {\n        return verify(data, null, signed, publicKey);\n    }\n\n    public static boolean verify(byte[] data, byte[] ida, \n                                 byte[] signed, byte[] publicKey) {\n        return verify(ECParameters.SM2_BEST, data, ida, signed, publicKey);\n    }\n\n    public static boolean verify(ECParameters ecParam, byte[] data, \n                                 byte[] signed, byte[] publicKey) {\n        return verify(ecParam, data, null, signed, publicKey);\n    }\n\n    /**\n     * verify signature\n     * @param ecParam the ec parameter\n     * @param data\n     * @param ida\n     * @param signed\n     * @param publicKey\n     * @return\n     */\n    public static boolean verify(ECParameters ecParam, byte[] data, byte[] ida, \n                                 byte[] signed, byte[] publicKey) {\n        Signature signature = new Signature(signed, ecParam.n);\n        if (   isNotBetween(signature.r, BigInteger.ONE, ecParam.n)\n            || isNotBetween(signature.s, BigInteger.ONE, ecParam.n)) {\n            return false;\n        }\n\n        ECPoint pubKey = getPublicKey(ecParam, publicKey);\n\n        SM3Digest sm3 = SM3Digest.getInstance();\n        sm3.update(calcZ(sm3, ecParam, ida, pubKey));\n        sm3.update(data);\n        BigInteger e = new BigInteger(1, sm3.doFinal());\n        BigInteger t = signature.r.add(signature.s).mod(ecParam.n);\n\n        if (t.equals(BigInteger.ZERO)) {\n            return false;\n        }\n\n        ECPoint p1 = ecParam.pointG.multiply(signature.s).normalize();\n        ECPoint p2 = pubKey.multiply(t).normalize();\n        BigInteger x1 = p1.add(p2).normalize().getXCoord().toBigInteger();\n        BigInteger r = e.add(x1).mod(ecParam.n);\n        return r.equals(signature.r);\n    }\n\n    public static boolean checkPublicKey(byte[] publicKey) {\n        return checkPublicKey(getPublicKey(publicKey));\n    }\n\n    public static boolean checkPublicKey(ECParameters ecParam, byte[] publicKey) {\n        return checkPublicKey(ecParam, getPublicKey(ecParam, publicKey));\n    }\n\n    public static boolean checkPublicKey(ECPoint publicKey) {\n        return checkPublicKey(ECParameters.SM2_BEST, publicKey);\n    }\n\n    public static boolean checkPublicKey(ECParameters ecParam, ECPoint publicKey) {\n        if (publicKey.isInfinity()) {\n            return false;\n        }\n\n        BigInteger x = publicKey.getXCoord().toBigInteger();\n        BigInteger y = publicKey.getYCoord().toBigInteger();\n\n        if (   isNotBetween(x, BigInteger.ZERO, ecParam.p)\n            || isNotBetween(y, BigInteger.ZERO, ecParam.p)) {\n            return false;\n        }\n\n        BigInteger x1 = x.pow(3).add(ecParam.a.multiply(x))\n                                .add(ecParam.b).mod(ecParam.p);\n        BigInteger y1 = y.pow(2).mod(ecParam.p);\n\n        return y1.equals(x1) && publicKey.multiply(ecParam.n).isInfinity();\n    }\n\n    static byte[] calcZ(SM3Digest sm3, ECParameters ecParam, ECPoint pubKey) {\n        return calcZ(sm3, ecParam, null, pubKey);\n    }\n\n    /**\n     * 取得用户标识字节数组\n     * @param ecParam the ec parameter\n     * @param ida\n     * @param pubKey\n     * @return\n     */\n    static byte[] calcZ(SM3Digest sm3, ECParameters ecParam, byte[] ida, ECPoint pubKey) {\n        sm3.reset();\n        if (ida != null && ida.length > 0) {\n            int idaBitLen = ida.length << 3; // ida.length*8\n            sm3.update((byte) (idaBitLen & 0xFF00));\n            sm3.update((byte) (idaBitLen & 0x00FF));\n            sm3.update(ida);\n        }\n        sm3.update(ecParam.a.toByteArray());\n        sm3.update(ecParam.b.toByteArray());\n        sm3.update(ecParam.gx.toByteArray());\n        sm3.update(ecParam.gy.toByteArray());\n        sm3.update(pubKey.getXCoord().toBigInteger().toByteArray());\n        sm3.update(pubKey.getYCoord().toBigInteger().toByteArray());\n        return sm3.doFinal();\n    }\n\n    // -------------------------------------------------------------------private methods\n    /**\n     * check the number is not between min(inclusion) and max(exclusion)\n     * @param number the value\n     * @param min   the minimum number, inclusion\n     * @param max   the maximum number, exclusion\n     * @return {@code true} is not between\n     */\n    private static boolean isNotBetween(BigInteger number, BigInteger min, BigInteger max) {\n        return number.compareTo(min) < 0 || number.compareTo(max) >= 0;\n    }\n\n    /**\n     * SM2WithSM3 signature\n     */\n    private static class Signature implements java.io.Serializable {\n        private static final long serialVersionUID = -2732762291362285185L;\n\n        final BigInteger r;\n        final BigInteger s;\n        final BigInteger n;\n\n        Signature(BigInteger r, BigInteger s, BigInteger n) {\n            this.r = r;\n            this.s = s;\n            this.n = n;\n        }\n\n        Signature(byte[] signed, BigInteger n) {\n            this.n = n;\n            int byteCount = (int) Math.ceil(this.n.bitLength() / 8.0D);\n            this.r = new BigInteger(1, Arrays.copyOfRange(signed, 0, byteCount));\n            this.s = new BigInteger(1, Arrays.copyOfRange(signed, byteCount, byteCount << 1));\n        }\n\n        byte[] toByteArray() {\n            int byteCount = (int) Math.ceil(this.n.bitLength() / 8.0D);\n            byte[] out = new byte[byteCount << 1];\n            byte[] r1 = this.r.toByteArray();\n            byte[] s1 = this.s.toByteArray();\n            Bytes.tailCopy(r1, 0, r1.length, out, 0, byteCount);\n            Bytes.tailCopy(s1, 0, s1.length, out, byteCount, byteCount);\n            return out;\n        }\n\n        @Override\n        public String toString() {\n            return Base64UrlSafe.encode(toByteArray());\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/sm/SM2KeyExchanger.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.sm;\n\nimport cn.ponfee.commons.jce.ECParameters;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport org.bouncycastle.math.ec.ECPoint;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.Serializable;\nimport java.math.BigInteger;\nimport java.util.Arrays;\n\n/**\n * SM2 key exchange implementation\n * the final, partA and partB get the same symmetric key\n * \n * reference the internet code and refactor optimization\n * \n * @author Ponfee\n */\npublic class SM2KeyExchanger implements Serializable {\n\n    private static final long serialVersionUID = 8553046425593791291L;\n    private static final BigInteger TWO = BigInteger.valueOf(2);\n\n    private BigInteger rA;\n    private ECPoint RA;\n    private ECPoint V;\n    private byte[] key;\n\n    private final ECParameters ecParam;\n    private final BigInteger w;\n    private final ECPoint publicKey;\n    private final BigInteger privateKey;\n    private final byte[] Z;\n\n    public SM2KeyExchanger(ECPoint publicKey, BigInteger privateKey) {\n        this(null, publicKey, privateKey, ECParameters.SM2_BEST);\n    }\n\n    public SM2KeyExchanger(byte[] ida, ECPoint publicKey, BigInteger privateKey) {\n        this(ida, publicKey, privateKey, ECParameters.SM2_BEST);\n    }\n\n    public SM2KeyExchanger(ECPoint publicKey, BigInteger privateKey, \n                           ECParameters ecParam) {\n        this(null, publicKey, privateKey, ecParam);\n    }\n\n    public SM2KeyExchanger(byte[] ida, ECPoint publicKey, BigInteger privateKey, \n                           ECParameters ecParam) {\n        this.ecParam = ecParam;\n        this.w = TWO.pow((int) Math.ceil(ecParam.n.bitLength() * 1.0 / 2) - 1);\n        this.publicKey = publicKey;\n        this.privateKey = privateKey;\n        this.Z = SM2.calcZ(SM3Digest.getInstance(), ecParam, ida, publicKey);\n    }\n\n    /**\n     * 密钥协商第一步（甲方）\n     * @return TransportEntity\n     */\n    public TransportEntity step1PartA() {\n        rA = SecureRandoms.random(ecParam.n);\n        RA = ecParam.pointG.multiply(rA).normalize();\n        return new TransportEntity(RA.getEncoded(false), null, Z, publicKey);\n    }\n\n    /**\n     * 密钥协商第二步（乙方）\n     * @param entity1 传输实体\n     * @return TransportEntity\n     */\n    public TransportEntity step2PartB(TransportEntity entity1) {\n        BigInteger rB = SecureRandoms.random(ecParam.n);\n        ECPoint RB = ecParam.pointG.multiply(rB).normalize();\n        this.rA = rB;\n        this.RA = RB;\n\n        BigInteger x2 = RB.getXCoord().toBigInteger();\n        x2 = w.add(x2.and(w.subtract(BigInteger.ONE)));\n\n        BigInteger tB = privateKey.add(x2.multiply(rB)).mod(ecParam.n);\n        ECPoint RA = ecParam.curve.decodePoint(entity1.R).normalize();\n\n        BigInteger x1 = RA.getXCoord().toBigInteger();\n        x1 = w.add(x1.and(w.subtract(BigInteger.ONE)));\n\n        ECPoint aPublicKey = ecParam.curve.decodePoint(entity1.K).normalize();\n        ECPoint temp = aPublicKey.add(RA.multiply(x1).normalize()).normalize();\n        ECPoint V = temp.multiply(ecParam.bcSpec.getH().multiply(tB)).normalize();\n        if (V.isInfinity()) {\n            throw new IllegalStateException();\n        }\n        this.V = V;\n\n        byte[] xV = V.getXCoord().toBigInteger().toByteArray();\n        byte[] yV = V.getYCoord().toBigInteger().toByteArray();\n        key = kdf(Bytes.concat(xV, yV, entity1.Z, this.Z), 16);\n\n        SM3Digest sm3 = SM3Digest.getInstance();\n        byte[] data = digest(sm3, xV, entity1.Z, this.Z, RA, RB);\n\n        sm3.update((byte) 0x02);\n        sm3.update(yV);\n        sm3.update(data);\n        byte[] sB = sm3.doFinal();\n\n        return new TransportEntity(RB.getEncoded(false), sB, this.Z, publicKey);\n    }\n\n    /**\n     * 密钥协商第三步（甲方）\n     * @param entity2 传输实体\n     * @return TransportEntity\n     */\n    public TransportEntity step3PartA(TransportEntity entity2) {\n        BigInteger x1 = RA.getXCoord().toBigInteger();\n        x1 = w.add(x1.and(w.subtract(BigInteger.ONE)));\n\n        BigInteger tA = privateKey.add(x1.multiply(rA)).mod(ecParam.n);\n        ECPoint RB = ecParam.curve.decodePoint(entity2.R).normalize();\n\n        BigInteger x2 = RB.getXCoord().toBigInteger();\n        x2 = w.add(x2.and(w.subtract(BigInteger.ONE)));\n\n        ECPoint bPublicKey = ecParam.curve.decodePoint(entity2.K).normalize();\n        ECPoint temp = bPublicKey.add(RB.multiply(x2).normalize()).normalize();\n        ECPoint U = temp.multiply(ecParam.bcSpec.getH().multiply(tA)).normalize();\n        if (U.isInfinity()) {\n            throw new IllegalStateException();\n        }\n        this.V = U;\n\n        byte[] xU = U.getXCoord().toBigInteger().toByteArray();\n        byte[] yU = U.getYCoord().toBigInteger().toByteArray();\n        key = kdf(Bytes.concat(xU, yU, this.Z, entity2.Z), 16);\n\n        SM3Digest sm3 = SM3Digest.getInstance();\n        byte[] data = digest(sm3, xU, this.Z, entity2.Z, RA, RB);\n\n        sm3.update((byte) 0x02);\n        sm3.update(yU);\n        sm3.update(data);\n        data = sm3.doFinal();\n        if (!Arrays.equals(entity2.S, data)) {\n            return null;\n        }\n\n        data = digest(sm3, xU, this.Z, entity2.Z, RA, RB);\n\n        sm3.update((byte) 0x03);\n        sm3.update(yU);\n        sm3.update(data);\n        byte[] sA = sm3.doFinal();\n        return new TransportEntity(RA.getEncoded(false), sA, this.Z, publicKey);\n    }\n\n    /**\n     * 密钥协商最后一（第四）步（乙方）\n     * @param entity3 传输实体\n     */\n    public boolean step4PartB(TransportEntity entity3) {\n        byte[] xV = V.getXCoord().toBigInteger().toByteArray();\n        byte[] yV = V.getYCoord().toBigInteger().toByteArray();\n        ECPoint RA = ecParam.curve.decodePoint(entity3.R).normalize();\n\n        SM3Digest sm3 = SM3Digest.getInstance();\n        byte[] data = digest(sm3, xV, entity3.Z, this.Z, RA, this.RA);\n\n        sm3.update((byte) 0x03);\n        sm3.update(yV);\n        sm3.update(data);\n        return Arrays.equals(entity3.S, sm3.doFinal());\n    }\n\n    \n    public byte[] getKey() {\n        return key;\n    }\n\n    /**\n     * 传输实体类\n     */\n    public static class TransportEntity implements Serializable {\n        private static final long serialVersionUID = 3657694935421411649L;\n\n        private final byte[] R; // R点\n        private final byte[] S; // 验证S\n        private final byte[] Z; // 用户标识\n        private final byte[] K; // 公钥\n\n        TransportEntity(byte[] r, byte[] s, byte[] z, ECPoint pKey) {\n            this(r, s, z, pKey.getEncoded(false));\n        }\n\n        TransportEntity(byte[] r, byte[] s, byte[] z, byte[] publicKey) {\n            R = r;\n            S = s;\n            Z = z;\n            K = publicKey;\n        }\n\n        public byte[] getR() {\n            return R;\n        }\n\n        public byte[] getS() {\n            return S;\n        }\n\n        public byte[] getZ() {\n            return Z;\n        }\n\n        public byte[] getK() {\n            return K;\n        }\n    }\n\n    /**\n     * 连接数据\n     * @param x\n     * @param z1\n     * @param z2\n     * @param a\n     * @param b\n     * @return\n     */\n    private static byte[] digest(SM3Digest sm3, byte[] x, byte[] z1, \n                                 byte[] z2, ECPoint a, ECPoint b) {\n        sm3.reset();\n        sm3.update(x);\n        sm3.update(z1);\n        sm3.update(z2);\n        sm3.update(a.getXCoord().toBigInteger().toByteArray());\n        sm3.update(a.getYCoord().toBigInteger().toByteArray());\n        sm3.update(b.getXCoord().toBigInteger().toByteArray());\n        sm3.update(b.getYCoord().toBigInteger().toByteArray());\n        return sm3.doFinal();\n    }\n\n    /**\n     * 密钥派生函数\n     * @param Z\n     * @param klen 生成klen字节数长度的密钥\n     * @return\n     */\n    private static byte[] kdf(byte[] Z, int klen) {\n        int ct = 1, end = (int) Math.ceil(klen * 1.0D / 32.0D);\n        SM3Digest sm3 = SM3Digest.getInstance();\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        for (int i = 1; i < end; i++) {\n            sm3.update(Z);\n            byte[] data = sm3.doFinal(Bytes.toBytes(ct));\n            baos.write(data, 0, data.length);\n            ct++;\n        }\n\n        sm3.update(Z);\n        sm3.update(Bytes.toBytes(ct));\n        byte[] last = sm3.doFinal();\n        int len = klen & 0x1F; // klen % 32\n        baos.write(last, 0, (len == 0) ? last.length : len);\n        return baos.toByteArray();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/sm/SM3Digest.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.sm;\n\nimport java.util.Arrays;\n\n/**\n * SM3 digest implementation\n * \n * reference the internet code and refactor optimization\n * \n * @author Ponfee\n */\npublic class SM3Digest {\n\n    /** SM3值的长度 */\n    private static final int DIGEST_SIZE = 32;\n\n    /** SM3分组块大小 */\n    private static final int BLOCK_SIZE = 64;\n\n    /** 缓冲区长度 */\n    private static final int BUFFER_LENGTH = BLOCK_SIZE;\n\n    private final byte[] xBuf = new byte[BUFFER_LENGTH], // 缓冲区\n                           iv = new byte[SM3.IV.length]; // 初始向量\n\n    private int xBufOffset, // 缓冲区偏移量\n                  cntBlock; // block数量\n\n    private SM3Digest() {\n        this.reset();\n    }\n\n    private SM3Digest(SM3Digest t) {\n        System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length);\n        System.arraycopy(t.iv, 0, this.iv, 0, t.iv.length);\n        this.xBufOffset = t.xBufOffset;\n        this.cntBlock = t.cntBlock;\n    }\n\n    public static SM3Digest getInstance() {\n        return new SM3Digest();\n    }\n\n    public static SM3Digest getInstance(SM3Digest t) {\n        return new SM3Digest(t);\n    }\n\n    public void update(byte[] in) {\n        this.update(in, 0, in.length);\n    }\n\n    /**\n     * 明文输入\n     * @param input 明文输入缓冲区\n     * @param inputOffset 缓冲区偏移量\n     * @param len 明文长度\n     */\n    public void update(byte[] input, int inputOffset, int len) {\n        int partLen = BUFFER_LENGTH - xBufOffset, \n            dPos = inputOffset;\n        if (partLen < len) {\n            System.arraycopy(input, dPos, xBuf, xBufOffset, partLen);\n            len -= partLen;\n            dPos += partLen;\n            doUpdate();\n            while (len > BUFFER_LENGTH) {\n                System.arraycopy(input, dPos, xBuf, 0, BUFFER_LENGTH);\n                len -= BUFFER_LENGTH;\n                dPos += BUFFER_LENGTH;\n                doUpdate();\n            }\n        }\n\n        System.arraycopy(input, dPos, xBuf, xBufOffset, len);\n        xBufOffset += len;\n    }\n\n    public void update(byte in) {\n        update(new byte[] { in }, 0, 1);\n    }\n\n    /**\n     * SM3结果输出\n     * @param out       保存SM3结构的缓冲区\n     * @param outOffset 缓冲区偏移量\n     */\n    public void doFinal(byte[] out, int outOffset) {\n        byte[] tmp = this.doFinal();\n        System.arraycopy(tmp, 0, out, outOffset, tmp.length);\n    }\n\n    public byte[] doFinal(byte[] in) {\n        this.update(in, 0, in.length);\n        return this.doFinal();\n    }\n\n    public byte[] doFinal() {\n        byte[] B = new byte[BLOCK_SIZE];\n        byte[] buffer = new byte[xBufOffset];\n        System.arraycopy(xBuf, 0, buffer, 0, buffer.length);\n        byte[] tmp = SM3.padding(buffer, cntBlock);\n        for (int i = 0; i < tmp.length; i += BLOCK_SIZE) {\n            System.arraycopy(tmp, i, B, 0, B.length);\n            this.doHash(B);\n        }\n        byte[] v = Arrays.copyOf(iv, iv.length);\n        this.reset();\n        return v;\n    }\n\n    public void reset() {\n        xBufOffset = 0;\n        cntBlock = 0;\n        System.arraycopy(SM3.IV, 0, iv, 0, SM3.IV.length);\n    }\n\n    public static int getDigestSize() {\n        return DIGEST_SIZE;\n    }\n\n    private void doUpdate() {\n        byte[] B = new byte[BLOCK_SIZE];\n        for (int i = 0; i < BUFFER_LENGTH; i += BLOCK_SIZE) {\n            System.arraycopy(xBuf, i, B, 0, B.length);\n            doHash(B);\n        }\n        xBufOffset = 0;\n    }\n\n    private void doHash(byte[] B) {\n        byte[] tmp = SM3.cf(iv, B);\n        System.arraycopy(tmp, 0, iv, 0, iv.length);\n        cntBlock++;\n    }\n\n    private static class SM3 {\n        static final byte[] IV = {\n            (byte) 0x73, (byte) 0x80, (byte) 0x16, (byte) 0x6f, (byte) 0x49,\n            (byte) 0x14, (byte) 0xb2, (byte) 0xb9, (byte) 0x17, (byte) 0x24,\n            (byte) 0x42, (byte) 0xd7, (byte) 0xda, (byte) 0x8a, (byte) 0x06,\n            (byte) 0x00, (byte) 0xa9, (byte) 0x6f, (byte) 0x30, (byte) 0xbc,\n            (byte) 0x16, (byte) 0x31, (byte) 0x38, (byte) 0xaa, (byte) 0xe3,\n            (byte) 0x8d, (byte) 0xee, (byte) 0x4d, (byte) 0xb0, (byte) 0xfb,\n            (byte) 0x0e, (byte) 0x4e\n        };\n\n        static final int[] T_J = new int[64];\n        static {\n            for (int i = 0; i < 16; i++) {\n                T_J[i] = 0x79cc4519;\n            }\n\n            for (int i = 16; i < 64; i++) {\n                T_J[i] = 0x7a879d8a;\n            }\n        }\n\n        static byte[] cf(byte[] V, byte[] B) {\n            return convert(cf(convert(V), convert(B)));\n        }\n\n        /**\n         * 对最后一个分组字节数据padding\n         * @param in\n         * @param bLen 分组个数\n         * @return\n         */\n        static byte[] padding(byte[] in, int bLen) {\n            int k = 448 - (((in.length << 3) + 1) & 0x1FF); // % 512\n            if (k < 0) {\n                k = 960 - (((in.length << 3) + 1) & 0x1FF);\n            }\n            k += 1;\n            byte[] padd = new byte[k / 8];\n            padd[0] = (byte) 0x80;\n            long n = (in.length << 3) + (bLen << 9);\n            byte[] out = new byte[in.length + k / 8 + 8];\n            int pos = 0;\n            System.arraycopy(in, 0, out, 0, in.length);\n            pos += in.length;\n            System.arraycopy(padd, 0, out, pos, padd.length);\n            pos += padd.length;\n            byte[] tmp = back(longToByteArray(n));\n            System.arraycopy(tmp, 0, out, pos, tmp.length);\n            return out;\n        }\n\n        static int[] convert(byte[] arr) {\n            int[] out = new int[arr.length >>> 2];\n            byte[] tmp = new byte[4];\n            for (int i = 0; i < arr.length; i += 4) {\n                System.arraycopy(arr, i, tmp, 0, 4);\n                out[i >>> 2] = bigEndianByteToInt(tmp);\n            }\n            return out;\n        }\n\n        static byte[] convert(int[] arr) {\n            byte[] out = new byte[arr.length << 2];\n            byte[] tmp;\n            for (int i = 0; i < arr.length; i++) {\n                tmp = bigEndianIntToByte(arr[i]);\n                System.arraycopy(tmp, 0, out, i << 2, 4);\n            }\n            return out;\n        }\n\n        static int[] cf(int[] V, int[] B) {\n            int a = V[0], b = V[1], c = V[2], d = V[3],\n                e = V[4], f = V[5], g = V[6], h = V[7];\n\n            int ss1, ss2, tt1, tt2;\n            int[][] arr = expand(B);\n            int[] w = arr[0], w1 = arr[1];\n\n            for (int j = 0; j < 64; j++) {\n                ss1 = (bitCycleLeft(a, 12) + e + bitCycleLeft(T_J[j], j));\n                ss1 = bitCycleLeft(ss1, 7);\n                ss2 = ss1 ^ bitCycleLeft(a, 12);\n                tt1 = FFj(a, b, c, j) + d + ss2 + w1[j];\n                tt2 = GGj(e, f, g, j) + h + ss1 + w[j];\n                d = c;\n                c = bitCycleLeft(b, 9);\n                b = a;\n                a = tt1;\n                h = g;\n                g = bitCycleLeft(f, 19);\n                f = e;\n                e = P0(tt2);\n            }\n\n            int[] out = new int[8];\n            out[0] = a ^ V[0];\n            out[1] = b ^ V[1];\n            out[2] = c ^ V[2];\n            out[3] = d ^ V[3];\n            out[4] = e ^ V[4];\n            out[5] = f ^ V[5];\n            out[6] = g ^ V[6];\n            out[7] = h ^ V[7];\n\n            return out;\n        }\n\n        static int[][] expand(int[] B) {\n            int[] W  = new int[68];\n            int[] W1 = new int[64];\n            System.arraycopy(B, 0, W, 0, B.length);\n\n            for (int i = 16; i < 68; i++) {\n                W[i] = P1(W[i - 16] \n                     ^ W[i - 9] \n                     ^ bitCycleLeft(W[i - 3], 15))\n                     ^ bitCycleLeft(W[i - 13], 7) \n                     ^ W[i - 6];\n            }\n\n            for (int i = 0; i < 64; i++) {\n                W1[i] = W[i] ^ W[i + 4];\n            }\n\n            return new int[][] { W, W1 };\n        }\n\n        static byte[] bigEndianIntToByte(int num) {\n            return back(intToByteArray(num));\n        }\n\n        static int bigEndianByteToInt(byte[] bytes) {\n            return byteArrayToInt(back(bytes));\n        }\n\n        static int FFj(int X, int Y, int Z, int j) {\n            if (j >= 0 && j <= 15) {\n                return FF1j(X, Y, Z);\n            } else {\n                return FF2j(X, Y, Z);\n            }\n        }\n\n        static int GGj(int X, int Y, int Z, int j) {\n            if (j >= 0 && j <= 15) {\n                return GG1j(X, Y, Z);\n            } else {\n                return GG2j(X, Y, Z);\n            }\n        }\n\n        // 逻辑位运算函数\n        static int FF1j(int X, int Y, int Z) {\n            return X ^ Y ^ Z;\n        }\n\n        static int FF2j(int X, int Y, int Z) {\n            return ((X & Y) | (X & Z) | (Y & Z));\n        }\n\n        static int GG1j(int X, int Y, int Z) {\n            return X ^ Y ^ Z;\n        }\n\n        static int GG2j(int X, int Y, int Z) {\n            return (X & Y) | (~X & Z);\n        }\n\n        static int P0(int X) {\n            return X ^ bitCycleLeft(X,  9) ^ bitCycleLeft(X, 17);\n        }\n\n        static int P1(int X) {\n            return X ^ bitCycleLeft(X, 15) ^ bitCycleLeft(X, 23);\n        }\n\n        /**\n         * 字节数组逆序\n         * \n         * @param in\n         * @return\n         */\n        static byte[] back(byte[] in) {\n            byte[] out = new byte[in.length];\n            for (int i = 0; i < out.length; i++) {\n                out[i] = in[out.length - i - 1];\n            }\n            return out;\n        }\n\n        static int bitCycleLeft(int n, int bitLen) {\n            bitLen &= 0x1F; // bitLen %= 32;\n            byte[] tmp = bigEndianIntToByte(n);\n            int byteLen = bitLen / 8;\n            int len = bitLen & 0x07; // bitLen % 8\n            if (byteLen > 0) {\n                tmp = byteCycleLeft(tmp, byteLen);\n            }\n            if (len > 0) {\n                tmp = bitSmall8CycleLeft(tmp, len);\n            }\n            return bigEndianByteToInt(tmp);\n        }\n\n        static byte[] bitSmall8CycleLeft(byte[] in, int len) {\n            byte[] tmp = new byte[in.length];\n            int t1, t2, t3;\n            for (int i = 0; i < tmp.length; i++) {\n                t1 = (byte) ((in[i] & 0x000000ff) << len);\n                t2 = (byte) ((in[(i + 1) % tmp.length] & 0x000000ff) >> (8 - len));\n                t3 = (byte) (t1 | t2);\n                tmp[i] = (byte) t3;\n            }\n            return tmp;\n        }\n\n        static byte[] byteCycleLeft(byte[] in, int byteLen) {\n            byte[] tmp = new byte[in.length];\n            System.arraycopy(in, byteLen, tmp, 0, in.length - byteLen);\n            System.arraycopy(in, 0, tmp, in.length - byteLen, byteLen);\n            return tmp;\n        }\n\n        /**\n         * 整形转换成网络传输的字节流（字节数组）型数据\n         * @param num 一个整型数据\n         * @return 4个字节的自己数组\n         */\n        static byte[] intToByteArray(int num) {\n            return new byte[] {\n                (byte) (num       ), (byte) (num >>>  8),\n                (byte) (num >>> 16), (byte) (num >>> 24)\n            };\n        }\n\n        /**\n         * 四个字节的字节数据转换成一个整形数据\n         * @param bytes 4个字节的字节数组\n         * @return 一个整型数据\n         */\n        static int byteArrayToInt(byte[] bytes) {\n            return (bytes[3]       ) << 24 // 转int后左移24位，刚好剩下原来的8位，故不用&0xFF\n                 | (bytes[2] & 0xFF) << 16 // 默认转int\n                 | (bytes[1] & 0xFF) <<  8\n                 | (bytes[0] & 0xFF);\n        }\n        \n        /**\n         * 长整形转换成网络传输的字节流（字节数组）型数据\n         * @param value 一个长整型数据\n         * @return 4个字节的自己数组\n         */\n        static byte[] longToByteArray(long value) {\n            return new byte[] {\n                (byte) (value       ), (byte) (value >>>  8), \n                (byte) (value >>> 16), (byte) (value >>> 24), \n                (byte) (value >>> 32), (byte) (value >>> 40), \n                (byte) (value >>> 48), (byte) (value >>> 56) \n            };\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/sm/SM4.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.sm;\n\nimport com.google.common.base.Preconditions;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.bouncycastle.util.Arrays;\n\nimport javax.annotation.Nonnull;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\n\n/**\n * SM4 symmetric cryptor implementation\n * \n * reference the internet code and refactor optimization\n * \n * @author Ponfee\n */\npublic final class SM4 {\n    private SM4(){}\n\n    private static final int ENCRYPT_MODE = 1;\n    private static final int DECRYPT_MODE = 2;\n\n    // S盒\n    private static final byte[] SBOX_TABLE = {\n        (byte) 0xd6, (byte) 0x90, (byte) 0xe9, (byte) 0xfe, (byte) 0xcc, (byte) 0xe1,\n        (byte) 0x3d, (byte) 0xb7, (byte) 0x16, (byte) 0xb6, (byte) 0x14, (byte) 0xc2,\n        (byte) 0x28, (byte) 0xfb, (byte) 0x2c, (byte) 0x05, (byte) 0x2b, (byte) 0x67,\n        (byte) 0x9a, (byte) 0x76, (byte) 0x2a, (byte) 0xbe, (byte) 0x04, (byte) 0xc3,\n        (byte) 0xaa, (byte) 0x44, (byte) 0x13, (byte) 0x26, (byte) 0x49, (byte) 0x86,\n        (byte) 0x06, (byte) 0x99, (byte) 0x9c, (byte) 0x42, (byte) 0x50, (byte) 0xf4,\n        (byte) 0x91, (byte) 0xef, (byte) 0x98, (byte) 0x7a, (byte) 0x33, (byte) 0x54,\n        (byte) 0x0b, (byte) 0x43, (byte) 0xed, (byte) 0xcf, (byte) 0xac, (byte) 0x62,\n        (byte) 0xe4, (byte) 0xb3, (byte) 0x1c, (byte) 0xa9, (byte) 0xc9, (byte) 0x08,\n        (byte) 0xe8, (byte) 0x95, (byte) 0x80, (byte) 0xdf, (byte) 0x94, (byte) 0xfa,\n        (byte) 0x75, (byte) 0x8f, (byte) 0x3f, (byte) 0xa6, (byte) 0x47, (byte) 0x07,\n        (byte) 0xa7, (byte) 0xfc, (byte) 0xf3, (byte) 0x73, (byte) 0x17, (byte) 0xba,\n        (byte) 0x83, (byte) 0x59, (byte) 0x3c, (byte) 0x19, (byte) 0xe6, (byte) 0x85,\n        (byte) 0x4f, (byte) 0xa8, (byte) 0x68, (byte) 0x6b, (byte) 0x81, (byte) 0xb2,\n        (byte) 0x71, (byte) 0x64, (byte) 0xda, (byte) 0x8b, (byte) 0xf8, (byte) 0xeb,\n        (byte) 0x0f, (byte) 0x4b, (byte) 0x70, (byte) 0x56, (byte) 0x9d, (byte) 0x35,\n        (byte) 0x1e, (byte) 0x24, (byte) 0x0e, (byte) 0x5e, (byte) 0x63, (byte) 0x58,\n        (byte) 0xd1, (byte) 0xa2, (byte) 0x25, (byte) 0x22, (byte) 0x7c, (byte) 0x3b,\n        (byte) 0x01, (byte) 0x21, (byte) 0x78, (byte) 0x87, (byte) 0xd4, (byte) 0x00,\n        (byte) 0x46, (byte) 0x57, (byte) 0x9f, (byte) 0xd3, (byte) 0x27, (byte) 0x52,\n        (byte) 0x4c, (byte) 0x36, (byte) 0x02, (byte) 0xe7, (byte) 0xa0, (byte) 0xc4,\n        (byte) 0xc8, (byte) 0x9e, (byte) 0xea, (byte) 0xbf, (byte) 0x8a, (byte) 0xd2,\n        (byte) 0x40, (byte) 0xc7, (byte) 0x38, (byte) 0xb5, (byte) 0xa3, (byte) 0xf7,\n        (byte) 0xf2, (byte) 0xce, (byte) 0xf9, (byte) 0x61, (byte) 0x15, (byte) 0xa1,\n        (byte) 0xe0, (byte) 0xae, (byte) 0x5d, (byte) 0xa4, (byte) 0x9b, (byte) 0x34,\n        (byte) 0x1a, (byte) 0x55, (byte) 0xad, (byte) 0x93, (byte) 0x32, (byte) 0x30,\n        (byte) 0xf5, (byte) 0x8c, (byte) 0xb1, (byte) 0xe3, (byte) 0x1d, (byte) 0xf6,\n        (byte) 0xe2, (byte) 0x2e, (byte) 0x82, (byte) 0x66, (byte) 0xca, (byte) 0x60,\n        (byte) 0xc0, (byte) 0x29, (byte) 0x23, (byte) 0xab, (byte) 0x0d, (byte) 0x53,\n        (byte) 0x4e, (byte) 0x6f, (byte) 0xd5, (byte) 0xdb, (byte) 0x37, (byte) 0x45,\n        (byte) 0xde, (byte) 0xfd, (byte) 0x8e, (byte) 0x2f, (byte) 0x03, (byte) 0xff,\n        (byte) 0x6a, (byte) 0x72, (byte) 0x6d, (byte) 0x6c, (byte) 0x5b, (byte) 0x51,\n        (byte) 0x8d, (byte) 0x1b, (byte) 0xaf, (byte) 0x92, (byte) 0xbb, (byte) 0xdd,\n        (byte) 0xbc, (byte) 0x7f, (byte) 0x11, (byte) 0xd9, (byte) 0x5c, (byte) 0x41,\n        (byte) 0x1f, (byte) 0x10, (byte) 0x5a, (byte) 0xd8, (byte) 0x0a, (byte) 0xc1,\n        (byte) 0x31, (byte) 0x88, (byte) 0xa5, (byte) 0xcd, (byte) 0x7b, (byte) 0xbd,\n        (byte) 0x2d, (byte) 0x74, (byte) 0xd0, (byte) 0x12, (byte) 0xb8, (byte) 0xe5,\n        (byte) 0xb4, (byte) 0xb0, (byte) 0x89, (byte) 0x69, (byte) 0x97, (byte) 0x4a,\n        (byte) 0x0c, (byte) 0x96, (byte) 0x77, (byte) 0x7e, (byte) 0x65, (byte) 0xb9,\n        (byte) 0xf1, (byte) 0x09, (byte) 0xc5, (byte) 0x6e, (byte) 0xc6, (byte) 0x84,\n        (byte) 0x18, (byte) 0xf0, (byte) 0x7d, (byte) 0xec, (byte) 0x3a, (byte) 0xdc,\n        (byte) 0x4d, (byte) 0x20, (byte) 0x79, (byte) 0xee, (byte) 0x5f, (byte) 0x3e,\n        (byte) 0xd7, (byte) 0xcb, (byte) 0x39, (byte) 0x48\n    };\n\n    private static final int[] FK = {\n        0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc\n    };\n\n    private static final int[] CK = {\n        0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,\n        0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,\n        0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,\n        0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,\n        0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,\n        0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,\n        0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,\n        0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 \n    };\n\n    public static byte[] encrypt(byte[] key, byte[] input) {\n        return crypt(ENCRYPT_MODE, true, key, input);\n    }\n\n    public static byte[] encrypt(boolean isPadding, \n                                 byte[] key, byte[] input) {\n        return crypt(ENCRYPT_MODE, isPadding, key, input);\n    }\n\n    public static byte[] decrypt(byte[] key, byte[] input) {\n        return crypt(DECRYPT_MODE, true, key, input);\n    }\n\n    public static byte[] decrypt(boolean isPadding, \n                                 byte[] key, byte[] input) {\n        return crypt(DECRYPT_MODE, isPadding, key, input);\n    }\n\n    public static byte[] encrypt(byte[] key, byte[] iv, byte[] input) {\n        return crypt(ENCRYPT_MODE, true, key, iv, input);\n    }\n\n    public static byte[] encrypt(boolean isPadding, byte[] key, \n                                 byte[] iv, byte[] input) {\n        return crypt(ENCRYPT_MODE, isPadding, key, iv, input);\n    }\n\n    public static byte[] decrypt(byte[] key, byte[] iv, byte[] input) {\n        return crypt(DECRYPT_MODE, true, key, iv, input);\n    }\n\n    public static byte[] decrypt(boolean isPadding, byte[] key, \n                                 byte[] iv, byte[] input) {\n        return crypt(DECRYPT_MODE, isPadding, key, iv, input);\n    }\n\n    /**\n     * 不带向量的加密（ECB模式）\n     *\n     * @param mode\n     * @param isPadding\n     * @param key\n     * @param input\n     * @return\n     */\n    private static byte[] crypt(int mode, boolean isPadding, \n                                byte[] key, byte[] input) {\n        Preconditions.checkArgument(ArrayUtils.isNotEmpty(input), \n                                    \"input cannot not null.\");\n\n        long[] sk = setKey(mode, key);\n\n        if (isPadding && (mode == ENCRYPT_MODE)) {\n            input = padding(input, ENCRYPT_MODE);\n        }\n        ByteArrayInputStream bins = new ByteArrayInputStream(input);\n        ByteArrayOutputStream bous = new ByteArrayOutputStream();\n        for (int length = input.length; length > 0; length -= 16) {\n            byte[] in = new byte[16];\n            byte[] out = new byte[16];\n            bins.read(in, 0, in.length);\n            oneRound(sk, in, out);\n            bous.write(out, 0, out.length);\n        }\n\n        byte[] output = bous.toByteArray();\n        if (isPadding && mode == DECRYPT_MODE) {\n            output = padding(output, DECRYPT_MODE);\n        }\n        return output;\n    }\n\n    /**\n     * 带向量的加密（CBC模式）\n     *\n     * @param mode\n     * @param isPadding\n     * @param key\n     * @param iv\n     * @param input\n     * @return\n     */\n    private static byte[] crypt(int mode, boolean isPadding, byte[] key, \n                                @Nonnull byte[] iv, byte[] input) {\n        if (ArrayUtils.isEmpty(input)) {\n            throw new IllegalArgumentException(\"Input cannot not null.\");\n        }\n        if (iv == null || iv.length != 16) {\n            throw new IllegalArgumentException(\"Iv must be 16 byte array.\");\n        }\n\n        long[] sk = setKey(mode, key);\n        iv = Arrays.copyOf(iv, iv.length);\n        if (isPadding && mode == ENCRYPT_MODE) {\n            input = padding(input, ENCRYPT_MODE);\n        }\n\n        ByteArrayInputStream bins  = new ByteArrayInputStream(input);\n        ByteArrayOutputStream bous = new ByteArrayOutputStream();\n        int i, length = input.length;\n        if (mode == ENCRYPT_MODE) {\n            for (; length > 0; length -= 16) {\n                byte[] in = new byte[16];\n                byte[] out = new byte[16];\n                byte[] out1 = new byte[16];\n\n                bins.read(in, 0, in.length);\n                for (i = 0; i < 16; i++) {\n                    out[i] = ((byte) (in[i] ^ iv[i]));\n                }\n                oneRound(sk, out, out1);\n                System.arraycopy(out1, 0, iv, 0, 16);\n                bous.write(out1, 0, out1.length);\n            }\n        } else {\n            byte[] temp = new byte[16];\n            for (; length > 0; length -= 16) {\n                byte[] in = new byte[16];\n                byte[] out = new byte[16];\n                byte[] out1 = new byte[16];\n\n                bins.read(in, 0, in.length);\n                System.arraycopy(in, 0, temp, 0, 16);\n                oneRound(sk, in, out);\n                for (i = 0; i < 16; i++) {\n                    out1[i] = ((byte) (out[i] ^ iv[i]));\n                }\n                System.arraycopy(temp, 0, iv, 0, 16);\n                bous.write(out1, 0, out1.length);\n            }\n        }\n\n        byte[] output = bous.toByteArray();\n        if (isPadding && mode == DECRYPT_MODE) {\n            output = padding(output, DECRYPT_MODE);\n        }\n        return output;\n    }\n\n    private static long toLong(byte[] bytes, int offset) {\n        return ((long) (bytes[  offset] & 0xFF) << 24)\n             | ((long) (bytes[++offset] & 0xFF) << 16)\n             | ((long) (bytes[++offset] & 0xFF) <<  8)\n             | ((long) (bytes[++offset] & 0xFF)      )\n             & (             0xFFFFFFFFL             );\n    }\n\n    private static void toByteArray(long n, byte[] bytes, int offset) {\n        bytes[  offset] = (byte) (n >>> 24);\n        bytes[++offset] = (byte) (n >>> 16);\n        bytes[++offset] = (byte) (n >>>  8);\n        bytes[++offset] = (byte) (n       );\n    }\n\n    /**\n     * shift left round\n     * @param x\n     * @param n\n     * @return\n     */\n    private static long rotateLeft(long x, int n) {\n        // ((x & 0xFFFFFFFF) << n) | (x >>> (32 - n));\n        return (x << n) | (x >>> (32 - n));\n    }\n\n    private static void swap(long[] sk, int i) {\n        int j = 31 - i;\n        long t = sk[i];\n         sk[i] = sk[j];\n         sk[j] = t;\n    }\n\n    private static byte sm4Sbox(byte inch) {\n        return SBOX_TABLE[inch & 0xFF];\n    }\n\n    private static long sm4Lt(long ka) {\n        byte[] a = new byte[4];\n        toByteArray(ka, a, 0);\n        byte[] b = {\n            sm4Sbox(a[0]), sm4Sbox(a[1]),\n            sm4Sbox(a[2]), sm4Sbox(a[3]),\n        };\n        long x = toLong(b, 0);\n        return x \n             ^ rotateLeft(x, 2) \n             ^ rotateLeft(x, 10) \n             ^ rotateLeft(x, 18) \n             ^ rotateLeft(x, 24);\n    }\n\n    private static long sm4F(long x0, long x1, long x2, long x3, long rk) {\n        return x0 ^ sm4Lt(x1 ^ x2 ^ x3 ^ rk);\n    }\n\n    private static long sm4CalciRK(long ka) {\n        byte[] a = new byte[4];\n        toByteArray(ka, a, 0);\n\n        byte[] b = {\n            sm4Sbox(a[0]), sm4Sbox(a[1]),\n            sm4Sbox(a[2]), sm4Sbox(a[3])\n        };\n        long x = toLong(b, 0);\n        return x ^ rotateLeft(x, 13) ^ rotateLeft(x, 23);\n    }\n\n    private static long[] setKey(int mode, @Nonnull byte[] key) {\n        if (key == null || key.length != 16) {\n            throw new IllegalArgumentException(\"Key must be 16 byte array.\");\n        }\n\n        key = Arrays.copyOf(key, key.length);\n        long[] MK = {\n            toLong(key, 0), toLong(key, 4),\n            toLong(key, 8), toLong(key, 12)\n        };\n        long[] k = new long[36];\n        k[0] = MK[0] ^ (long) FK[0];\n        k[1] = MK[1] ^ (long) FK[1];\n        k[2] = MK[2] ^ (long) FK[2];\n        k[3] = MK[3] ^ (long) FK[3];\n        long[] SK = new long[32];\n        int i = 0;\n        for (; i < 32; i++) {\n            k[(i + 4)] = k[i] \n                       ^ sm4CalciRK(k[(i + 1)] \n                       ^ k[(i + 2)] \n                       ^ k[(i + 3)] \n                       ^ (long) CK[i]);\n            SK[i] = k[(i + 4)];\n        }\n\n        if (mode == DECRYPT_MODE) {\n            for (i = 0; i < 16; i++) {\n                swap(SK, i);\n            }\n        }\n        return SK;\n    }\n\n    private static void oneRound(long[] sk, byte[] input, byte[] output) {\n        long[] ulbuf = new long[36];\n        ulbuf[0] = toLong(input, 0);\n        ulbuf[1] = toLong(input, 4);\n        ulbuf[2] = toLong(input, 8);\n        ulbuf[3] = toLong(input, 12);\n        for (int i = 0; i < 32; i++) {\n            ulbuf[(i + 4)] = sm4F(ulbuf[i], \n                                  ulbuf[(i + 1)], \n                                  ulbuf[(i + 2)], \n                                  ulbuf[(i + 3)], \n                                  sk[i]);\n        }\n        toByteArray(ulbuf[35], output, 0);\n        toByteArray(ulbuf[34], output, 4);\n        toByteArray(ulbuf[33], output, 8);\n        toByteArray(ulbuf[32], output, 12);\n    }\n\n    private static byte[] padding(byte[] input, int mode) {\n        if (input == null) {\n            return null;\n        }\n\n        byte[] result;\n        if (mode == ENCRYPT_MODE) {\n            int p = 16 - input.length & 0xF; // % 16\n            result = new byte[input.length + p];\n            System.arraycopy(input, 0, result, 0, input.length);\n            for (int i = 0; i < p; i++) {\n                result[input.length + i] = (byte) p;\n            }\n        } else {\n            int p = input[input.length - 1];\n            result = new byte[input.length - p];\n            System.arraycopy(input, 0, result, 0, input.length - p);\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/symmetric/Algorithm.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.symmetric;\n\n/**\n * <pre>\n *   对称密钥算法bit(位)\n *     DES                  key size must be equal to 64\n *     DESede(TripleDES)    key size must be equal to 112 or 168\n *     AES                  key size must be equal to 128, 192 or 256, but 192 and 256 bits may be unsupport\n *     Blowfish             key size must be multiple of 8, and can only range from 32 to 448 (inclusive)\n *     RC2                  key size must be between 40 and 1024 bits(block cipher, 曾经被考虑作为DES算法的替代品, 比DES快)\n *     RC4(ARCFOUR)         key size must be between 40 and 1024 bits(stream cipher)\n * </pre>\n *\n * AES进入最后一轮候选算法有：Rijndael/Serpent/Twofish/RC6/MARS，最终Rijndael算法获胜\n *\n * 速度排名：IDEA < DES < GASTI28 < GOST < AES < RC4 < TEA < Blowfish\n * \n * 1、DES（Data Encryption Standard）：对称算法，数据加密标准，速度较快，适用于加密大量数据的场合； \n * 2、3DES（Triple DES）：是基于DES的对称算法，对一块数据用三个不同的密钥进行三次加密，强度更高；\n * 3、RC2和RC4：对称算法，用变长密钥对大量数据进行加密，比 DES快；\n * 4、IDEA（International Data Encryption Algorithm）国际数据加密算法，使用128位密钥提供非常强的安全性；\n * 5、RSA：由 RSA 公司发明，是一个支持变长密钥的公共密钥算法，需要加密的文件块的长度也是可变的，非对称算法； \n * 6、DSA（Digital Signature Algorithm）：数字签名算法，是一种标准的 DSS（数字签名标准），严格来说不算加密算法；\n * 7、AES（Advanced Encryption Standard）：高级加密标准，对称算法，是下一代的加密算法标准，速度快，安全级别高，在21世纪AES标准的一个实现是Rijndael算法；\n * 8、BLOWFISH，它使用变长的密钥，长度可达448位，运行速度很快；\n * 10、PKCS:The Public-Key Cryptography Standards (PKCS)是由美国RSA数据安全公司及其合作伙伴制定的一组公钥密码学标准，\n *    其中包括证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。\n * 11、SSF33，SSF28，SCB2(SM1)：国家密码局的隐蔽不公开的商用算法，在国内民用和商用的，除这些都不容许使用外，其他的都可以使用；\n * 12、ECC（Elliptic Curves Cryptography）：椭圆曲线密码编码学。\n * 13、TEA(Tiny Encryption Algorithm)简单高效的加密算法，加密解密速度快，实现简单。但安全性不如DES，QQ一直用tea加密\n * \n * https://bouncycastle.org/documentation.html\n * https://downloads.bouncycastle.org/fips-java/BC-FJA-UserGuide-1.0.2.pdf\n * https://downloads.bouncycastle.org/fips-java/BC-FJA-(D)TLSUserGuide-1.0.9.pdf\n * \n * @see org.bouncycastle.jcajce.provider.symmetric.ARC4\n * \n * @author Ponfee\n */\npublic enum Algorithm {\n\n    AES, DES, DESede, Blowfish, RC2, RC4, // RC4: ARC4, ARCFOUR\n    RC5, IDEA, TEA, TDEA, Camellia, CAST5, //\n    GOST, GOST3411, GOST28147, SEED, //\n    Serpent, SHACAL2, Twofish, SM4 //\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/symmetric/Mode.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.symmetric;\n\n/**\n * 对称加密分组模式<p>\n * 推荐使用CBC和CTR模式<p>\n * CFB,OFB,CTR模式不需要padding<p>\n * \n * ECB：最基本的加密模式，也就是通常理解的加密，相同的明文将永远加密成相同的密文，无初始向量，容易受到密码本重放攻击，一般情况下很少用。\n * \n * CBC：明文被加密前要与前面的密文进行异或运算后再加密，因此只要选择不同的初始向量，相同的密文加密后会形成不同的密文，这是目前应用最广泛的模式。\n *     CBC加密后的密文是上下文相关的，但明文的错误不会传递到后续分组，但如果一个分组丢失，后面的分组将全部作废(同步错误)。\n * \n * CFB：类似于自同步序列密码，分组加密后，按8位分组将密文和明文进行移位异或后得到输出同时反馈回移位寄存器，优点最小可以按字节进行加解密，\n *     也可以是n位的，CFB也是上下文相关的，CFB模式下，明文的一个错误会影响后面的密文(错误扩散)。\n * \n * OFB：将分组密码作为同步序列密码运行，和CFB相似，不过OFB用的是前一个n位密文输出分组反馈回移位寄存器，OFB没有错误扩散问题。\n * \n * @author Ponfee\n */\npublic enum Mode {\n    ECB, CBC, CFB, OFB, CTR, // \n    EAX, OCB, CFB8, CFB64, //\n    CFB128, OpenPGPCFB, GCM, CCM //\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/symmetric/PBECryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.symmetric;\n\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.PBEParameterSpec;\nimport java.security.Provider;\nimport java.security.spec.AlgorithmParameterSpec;\n\n/**\n * <pre>\n *  |---------------------------------------|-------------------|---------------------------|\n *  |               Algorithm               | secret key length | default secret key length |\n *  |---------------------------------------|-------------------|---------------------------|\n *  | PBEWithMD5AndDES                      |        56         |            56             |\n *  |---------------------------------------|-------------------|---------------------------|\n *  | PBEWithMD5AndTripleDES                |      112,168      |            168            |\n *  |---------------------------------------|-------------------|---------------------------|\n *  | PBEWithSHA1AndDESede                  |      112,168      |            168            |\n *  |---------------------------------------|-------------------|---------------------------|\n *  | PBEWithSHA1AndRC2_40                  |     40 to 1024    |            128            |\n *  |---------------------------------------|-------------------|---------------------------|\n * </pre>\n * \n * String是常量（即创建之后就无法更改），会保存到常量池中，如果有其他进程\n * 可以dump这个进程的内存，那么密码就会随着常量池被dump出去从而泄露。\n * 而char[]可以写入其他的信息从而改变，即是被dump了也会减少泄露密码的风险。\n * <p>\n * \n * PBE Cryptors\n * \n * @author Ponfee\n */\npublic class PBECryptor extends SymmetricCryptor {\n\n    public enum PBEAlgorithm {\n        PBEWithMD5AndDES, //\n        PBEWithSHA1AndDESede, // best\n        PBEWithSHA1AndRC2_40, //\n        PBEWithMD5AndTripleDES, //\n    }\n\n    public PBECryptor(SecretKey secretKey, Mode mode, Padding padding, \n                      AlgorithmParameterSpec parameter, Provider provider) {\n        // (provider == null) ? Providers.SunJCE : provider\n        super(secretKey, mode, padding, parameter, provider);\n    }\n\n    // --------------------------getter\n    public char[] getPass() {\n        return new String(getKey()).toCharArray();\n    }\n\n    public byte[] getSalt() {\n        return ((PBEParameterSpec) parameter).getSalt();\n    }\n\n    public int getIterations() {\n        return ((PBEParameterSpec) parameter).getIterationCount();\n    }\n\n    @Override\n    public byte[] getParameterAsBytes() {\n        throw new UnsupportedOperationException();\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/symmetric/PBECryptorBuilder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.symmetric;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.symmetric.PBECryptor.PBEAlgorithm;\nimport org.apache.commons.text.RandomStringGenerator;\n\nimport javax.crypto.SecretKey;\nimport javax.crypto.SecretKeyFactory;\nimport javax.crypto.spec.PBEKeySpec;\nimport javax.crypto.spec.PBEParameterSpec;\nimport java.security.Provider;\nimport java.security.spec.AlgorithmParameterSpec;\nimport java.security.spec.InvalidKeySpecException;\n\n/**\n * PBE Cryptor builder\n * \n * @author Ponfee\n */\npublic class PBECryptorBuilder {\n\n    private static final RandomStringGenerator GENERATOR =\n        new RandomStringGenerator.Builder().withinRange('!', '~').build();\n\n    private final SecretKey secretKey; // 密钥\n    private final Provider provider;\n    private Mode mode; // 分组加密模式\n    private Padding padding; // 填充\n    private AlgorithmParameterSpec parameter; // 填充向量\n\n    private PBECryptorBuilder(PBEAlgorithm algorithm, char[] pass, Provider provider) {\n        try {\n            // new SecretKeySpec(new String(pass).getBytes(), algName); // 也可用此方法来构造具体的密钥\n            SecretKeyFactory factory = Providers.getSecretKeyFactory(algorithm.name(), provider);\n            this.secretKey = factory.generateSecret(new PBEKeySpec(pass));\n            this.provider = provider;\n        } catch (InvalidKeySpecException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    public static PBECryptorBuilder newBuilder(PBEAlgorithm algorithm) {\n        return newBuilder(algorithm, 24);\n    }\n\n    public static PBECryptorBuilder newBuilder(PBEAlgorithm algorithm, int passSize) {\n        return newBuilder(algorithm, GENERATOR.generate(passSize).toCharArray());\n    }\n\n    public static PBECryptorBuilder newBuilder(PBEAlgorithm algorithm, char[] pass) {\n        return newBuilder(algorithm, pass, null);\n    }\n\n    public static PBECryptorBuilder newBuilder(PBEAlgorithm algorithm, char[] pass, Provider provider) {\n        return new PBECryptorBuilder(algorithm, pass, provider);\n    }\n\n    public PBECryptorBuilder mode(Mode mode) {\n        this.mode = mode;\n        return this;\n    }\n\n    public PBECryptorBuilder padding(Padding padding) {\n        this.padding = padding;\n        return this;\n    }\n\n    public PBECryptorBuilder parameter(byte[] salt,\n                                       int iterations) {\n        this.parameter = new PBEParameterSpec(salt, iterations);\n        return this;\n    }\n\n    public PBECryptor build() {\n        if (mode != null && padding == null) {\n            // 设置了mode必须指定padding\n            throw new IllegalArgumentException(\"padding cannot be null within mode crypto.\");\n        } else if (mode == null && padding != null) {\n            // 没有设置mode，不能指定padding\n            throw new IllegalArgumentException(\"padding must be null without mode crypto.\");\n        }\n        return new PBECryptor(secretKey, mode, padding, parameter, provider);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/symmetric/Padding.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.symmetric;\n\n/**\n * encrypt padding\n * pkcs7Padding must be has BouncyCastleProvider support\n * PKCS7Padding：缺几个字节就补几个字节的0\n * PKCS5Padding：缺几个字节就补充几个字节的几，如缺6个字节就补充6个字节的6\n * \n * @author Ponfee\n */\npublic enum Padding {\n\n    NoPadding, PKCS5Padding, PKCS7Padding, // \n    ISO10126_Padding(\"ISO10126Padding\"), //\n    ISO10126_2Padding(\"ISO10126-2Padding\"), // \n    ISO7816_4Padding(\"ISO7816-4Padding\"), // \n    X9_23Padding(\"X9.23Padding\"), TBCPadding, //\n    CS1Padding, CS2Padding, CS3Padding // CS1Padding, CS2Padding may cannot support\n\n    ;\n\n    private final String padding;\n\n    Padding() {\n        this.padding = this.name();\n    }\n\n    Padding(String padding) {\n        this.padding = padding;\n    }\n\n    public String padding() {\n        return this.padding;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/symmetric/SymmetricCryptor.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.symmetric;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.util.Base64UrlSafe;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.IvParameterSpec;\nimport java.security.GeneralSecurityException;\nimport java.security.Provider;\nimport java.security.spec.AlgorithmParameterSpec;\n\n/**\n * AES\n * http://blog.csdn.net/qq_28205153/article/details/55798628\n * http://blog.csdn.net/lrwwll/article/details/78069013\n * https://coolshell.cn//wp-content/uploads/2010/10/rijndael_ingles2004.swf\n * 对称加密\n *  加密：C = E(K, P)\n *  解密：P = D(K, C)\n *  \n * AES         密钥长度（32位比特字)    分组长度(32位比特字)    加密轮数\n * AES-128          4                         4                  10\n * AES-192          6                         4                  12\n * AES-256          8                         4                  14\n * \n * 1、明文按16字节（4个32位比特字）分组，P[0],P[1],...,P[15]，不足则填充\n * 2、16字节（4个32位比特字）密钥进行分组，分成44组每组1个比特字（W[0],W[1],...,W[43]），\n *   前4组为原始密钥用于初始密钥加：W[0]=K[0] K[1] K[2] K[3],...,W[3]=K[12] K[13] K[14] K[15]。\n *   后面40个字分为10组，每组4个字（128比特）分别用于10轮加密运算中的轮密钥加\n * 3、W[4]~W[43]通过轮密钥加来生成，将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作，\n *   密钥Ki中每个字W[4i],W[4i+1],W[4i+2],W[4i+3]为32位比特字，包含4个字节\n * 4、先将明文和原始密钥进行一次异或加密操作\n * 5、加密的第1轮到第9轮的轮函数一样，包括4个操作：字节代换、行位移、列混合和轮密钥加。最后一轮迭代不执行列混合。\n * 6、字节代换：把该字节的高4位作为行值，低4位作为列值，取出S盒或者逆S盒中对应的行的元素作为输出\n * \n * @author Ponfee\n */\npublic class SymmetricCryptor {\n\n    /** \n     * 分组对称加密模式时padding不能为null \n     */\n    private final Mode mode;\n\n    /** \n     * 1、RC2、RC4分组对称加密模式时padding必须为NoPadding\n     * 2、无分组模式时padding必须为null\n     * 3、其它算法无限制 \n     */\n    private final Padding padding;\n\n    /** \n     * 1、ECB模式时iv必须为null\n     * 2、无分组对称加密模式时iv必须为null\n     * 3、有分组对称加密模式时必须要有iv\n     * 4、iv must be 16 bytes long\n     */\n    protected final AlgorithmParameterSpec parameter;\n\n    /** 加密提供方 */\n    private final Provider provider;\n\n    /** 密钥 */\n    private final SecretKey secretKey;\n\n    /** the cipher transformation */\n    private final String transformation;\n\n    protected SymmetricCryptor(SecretKey secretKey, Mode mode, Padding padding,\n                               AlgorithmParameterSpec parameter, Provider provider) {\n        this.secretKey = secretKey;\n        this.mode = mode;\n        this.padding = padding;\n        this.parameter = parameter;\n        this.provider = provider;\n        if (mode != null) {\n            this.transformation = new StringBuilder(getAlgorithm())\n                                      .append(\"/\").append(mode.name())\n                                      .append(\"/\").append(padding.padding())\n                                      .toString();\n        } else {\n            this.transformation = getAlgorithm();\n        }\n    }\n\n    public final byte[] encrypt(byte[] data) {\n        return this.docrypt(data, Cipher.ENCRYPT_MODE);\n    }\n\n    public final byte[] decrypt(byte[] encrypted) {\n        return this.docrypt(encrypted, Cipher.DECRYPT_MODE);\n    }\n\n    /**\n     * 加密解\n     * @param bytes     待加密/密文数据\n     * @param cryptMode 密码模式：1加密；2解密；\n     * @return\n     */\n    private byte[] docrypt(byte[] bytes, int cryptMode) {\n        try {\n            Cipher cipher = Providers.getCipher(transformation, provider);\n            cipher.init(cryptMode, secretKey, parameter);\n            return cipher.doFinal(bytes);\n        } catch (GeneralSecurityException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    // ----------------------------------getter\n    /**\n     * Returns encrypt algorithm string\n     * \n     * @return algorithm string\n     */\n    public final String getAlgorithm() {\n        return secretKey.getAlgorithm();\n    }\n\n    /**\n     * Returns key byte array data\n     * \n     * @return key byte array data\n     */\n    public final byte[] getKey() {\n        return secretKey.getEncoded();\n    }\n\n    public final String getKeyAsBase64() {\n        return Base64UrlSafe.encode(getKey());\n    }\n\n    /**\n     * Returns iv parameter byte array data\n     * \n     * @return iv parameter byte array data\n     */\n    public byte[] getParameterAsBytes() {\n        return ((IvParameterSpec) parameter).getIV();\n    }\n\n    public final String getParameterAsBase64() {\n        return Base64UrlSafe.encode(getParameterAsBytes());\n    }\n\n    public final Mode getMode() {\n        return mode;\n    }\n\n    public final Padding getPadding() {\n        return padding;\n    }\n\n    public final Provider getProvider() {\n        return provider;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/jce/symmetric/SymmetricCryptorBuilder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.jce.symmetric;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.util.SecureRandoms;\n\nimport javax.crypto.KeyGenerator;\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.IvParameterSpec;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.security.Provider;\n\n/**\n * 对称加密构建类\n * \n * @author Ponfee\n */\npublic final class SymmetricCryptorBuilder {\n\n    private final SecretKey secretKey; // 密钥\n\n    /**加密服务提供方 {@link cn.ponfee.commons.jce.Providers} */\n    private final Provider provider;\n\n    private Mode mode; // 分组加密模式\n    private Padding padding; // 填充\n    private IvParameterSpec parameter; // 填充向量\n\n\n    private SymmetricCryptorBuilder(Algorithm alg, byte[] key, Provider provider) {\n        if (key == null) {\n            KeyGenerator keyGenerator = Providers.getKeyGenerator(alg.name(), provider);\n            this.secretKey = keyGenerator.generateKey();\n        } else {\n            this.secretKey = new SecretKeySpec(key, alg.name());\n        }\n        this.provider = provider;\n    }\n\n    public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm) {\n        return newBuilder(algorithm, null, null);\n    }\n\n    public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, Provider provider) {\n        return newBuilder(algorithm, null, provider);\n    }\n\n    public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, int keySize) {\n        return newBuilder(algorithm, SecureRandoms.nextBytes(keySize), null);\n    }\n\n    public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, int keySize, Provider provider) {\n        return newBuilder(algorithm, SecureRandoms.nextBytes(keySize), provider);\n    }\n\n    public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, byte[] key) {\n        return newBuilder(algorithm, key, null);\n    }\n\n    public static SymmetricCryptorBuilder newBuilder(Algorithm algorithm, byte[] key, Provider provider) {\n        return new SymmetricCryptorBuilder(algorithm, key, provider);\n    }\n\n    public SymmetricCryptorBuilder mode(Mode mode) {\n        this.mode = mode;\n        return this;\n    }\n\n    public SymmetricCryptorBuilder padding(Padding padding) {\n        this.padding = padding;\n        return this;\n    }\n\n    public SymmetricCryptorBuilder parameter(byte[] parameter) {\n        this.parameter = new IvParameterSpec(parameter);\n        return this;\n    }\n\n    public SymmetricCryptor build() {\n        if (mode != null && padding == null) {\n            // 设置了mode必须指定padding\n            throw new IllegalArgumentException(\"padding cannot be null within mode crypto.\");\n        } else if (mode == null && padding != null) {\n            // 没有设置mode，不能指定padding\n            throw new IllegalArgumentException(\"padding must be null without mode crypto.\");\n        }\n\n        return new SymmetricCryptor(secretKey, mode, padding, parameter, provider);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/json/FastjsonMoney.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.json;\n\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport com.alibaba.fastjson.JSONObject;\nimport com.alibaba.fastjson.parser.DefaultJSONParser;\nimport com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;\nimport com.alibaba.fastjson.serializer.JSONSerializer;\nimport com.alibaba.fastjson.serializer.ObjectSerializer;\nimport com.alibaba.fastjson.serializer.SerializeWriter;\nimport org.javamoney.moneta.Money;\n\nimport javax.money.Monetary;\nimport java.io.IOException;\nimport java.lang.reflect.Type;\n\n/**\n * The Fastjson Money Serializer & Deserializer\n *\n * @author Ponfee\n */\npublic class FastjsonMoney implements ObjectSerializer, ObjectDeserializer {\n\n    private static final String CURRENCY = \"currency\";\n    private static final String NUMBER = \"number\";\n\n    @Override\n    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {\n        SerializeWriter writer = serializer.getWriter();\n        if (object == null) {\n            serializer.writeNull();\n        } else {\n            Money money = (Money) object;\n            writer.write(\"{\\\"\" + CURRENCY + \"\\\":\\\"\");\n            writer.write(money.getCurrency().getCurrencyCode());\n            writer.write(\"\\\",\\\"\" + NUMBER + \"\\\":\");\n            writer.writeLong(money.getNumberStripped().movePointRight(money.getCurrency().getDefaultFractionDigits()).longValueExact());\n            writer.write(\"}\");\n        }\n    }\n\n    @Override\n    public Money deserialze(DefaultJSONParser parser, Type type, Object fieldName) {\n        if (GenericUtils.getRawType(type) != Money.class) {\n            throw new UnsupportedOperationException(\"Cannot supported deserialize type: \" + type);\n        }\n        JSONObject jsonObject = parser.parseObject();\n        String currencyCode = jsonObject.getString(CURRENCY);\n        long number = jsonObject.getLongValue(NUMBER);\n        return Money.ofMinor(Monetary.getCurrency(currencyCode), number);\n    }\n\n    @Override\n    public int getFastMatchToken() {\n        return 0 /*JSONToken.RBRACKET*/;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/json/FastjsonPropertyFilter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.json;\n\nimport com.alibaba.fastjson.serializer.PropertyFilter;\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport javax.annotation.Nonnull;\n\n/**\n * <pre>\n * Object to json specified fields whether includes or excludes\n * \n * {@code\n *   Map<String, Object> map = ImmutableMap.of(\"a\", 1, \"b\", true, \"c\", \"x\");\n *   JSON.toJSONString(map, JsonPropertyFilter.include(\"a\", \"b\"))\n *   JSON.toJSONString(map, JsonPropertyFilter.exclude(\"a\", \"b\"))\n *   \n *   OR \n *   \n *   JSON.toJSONString(map, new SimplePropertyPreFilter(\"a\", \"b\"))\n * }\n * </pre>\n *\n * @author Ponfee\n */\npublic class FastjsonPropertyFilter implements PropertyFilter {\n\n    private final boolean isIncludes;\n    private final boolean forceNonNull; // if true, then non null field will be serialize\n    private final String[] fields;\n\n    public FastjsonPropertyFilter(@Nonnull PropertyFilterType type, boolean forceNonNull, @Nonnull String... fields) {\n        this.isIncludes = type == PropertyFilterType.INCLUDES;\n        this.forceNonNull = forceNonNull;\n        this.fields = fields;\n    }\n\n    @Override\n    public boolean apply(Object source, String name, Object value) {\n        if (forceNonNull && value != null) {\n            return true;\n        }\n\n        // 异或：(A ^ B)\n        // 同或：(A ^ B ^ 1) or !(A ^ B)\n        //return isIncludes ^ ArrayUtils.contains(fields, name) ^ true;\n        //return !(isIncludes ^ ArrayUtils.contains(fields, name));\n        return isIncludes == ArrayUtils.contains(fields, name);\n    }\n\n    // ----------------------------------------------------------------------static methods\n    public static FastjsonPropertyFilter exclude(@Nonnull String... fields) {\n        return exclude(false, fields);\n    }\n\n    public static FastjsonPropertyFilter exclude(boolean forceNonNull, @Nonnull String... fields) {\n        return new FastjsonPropertyFilter(PropertyFilterType.EXCLUDES, forceNonNull, fields);\n    }\n\n    public static FastjsonPropertyFilter include(@Nonnull String... fields) {\n        return include(false, fields);\n    }\n\n    public static FastjsonPropertyFilter include(boolean forceNonNull, @Nonnull String... fields) {\n        return new FastjsonPropertyFilter(PropertyFilterType.INCLUDES, forceNonNull, fields);\n    }\n\n    private enum PropertyFilterType {\n        INCLUDES, EXCLUDES\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/json/FastjsonTypeReferences.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.json;\n\nimport com.alibaba.fastjson.TypeReference;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * The Fastjson TypeReference holder\n *\n * @author Ponfee\n * @see sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl\n * @see org.springframework.core.ParameterizedTypeReference\n */\npublic final class FastjsonTypeReferences {\n\n    public static final TypeReference<Map<String, Object>> MAP_NORMAL = new TypeReference<Map<String, Object>>() {};\n    public static final TypeReference<Map<String, String>> MAP_STRING = new TypeReference<Map<String, String>>() {};\n\n    public static final TypeReference<List<Object>> LIST_OBJECT = new TypeReference<List<Object>>() {};\n    public static final TypeReference<List<String>> LIST_STRING = new TypeReference<List<String>>() {};\n\n    public static final TypeReference<Set<Object>> SET_OBJECT = new TypeReference<Set<Object>>() {};\n    public static final TypeReference<Set<String>> SET_STRING = new TypeReference<Set<String>>() {};\n\n    public static final TypeReference<List<Map<String, Object>>> LIST_MAP_NORMAL = new TypeReference<List<Map<String, Object>>>() {};\n    public static final TypeReference<List<Map<String, String>>> LIST_MAP_STRING = new TypeReference<List<Map<String, String>>>() {};\n\n    public static final TypeReference<Set<Map<String, Object>>> SET_MAP_NORMAL = new TypeReference<Set<Map<String, Object>>>() {};\n    public static final TypeReference<Set<Map<String, String>>> SET_MAP_STRING = new TypeReference<Set<Map<String, String>>>() {};\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/json/JacksonCurrencyUnit.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.json;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.money.CurrencyUnit;\nimport javax.money.Monetary;\nimport java.io.IOException;\n\n/**\n * The Jackson Currency unit Serializer & Deserializer\n *\n * @author Ponfee\n */\npublic class JacksonCurrencyUnit {\n\n    public static final JacksonCurrencyUnit INSTANCE = new JacksonCurrencyUnit();\n\n    private final JsonSerializer<CurrencyUnit> serializer;\n    private final JsonDeserializer<CurrencyUnit> deserializer;\n\n    private JacksonCurrencyUnit() {\n        this.serializer = new Serializer();\n        this.deserializer = new Deserializer();\n    }\n\n    public JsonSerializer<CurrencyUnit> serializer() {\n        return this.serializer;\n    }\n\n    public JsonDeserializer<CurrencyUnit> deserializer() {\n        return this.deserializer;\n    }\n\n    private static class Serializer extends JsonSerializer<CurrencyUnit> {\n        @Override\n        public void serialize(CurrencyUnit currencyUnit, JsonGenerator generator, SerializerProvider provider) throws IOException {\n            if (currencyUnit == null) {\n                generator.writeNull();\n            } else {\n                generator.writeString(currencyUnit.getCurrencyCode());\n            }\n        }\n    }\n\n    private static class Deserializer extends JsonDeserializer<CurrencyUnit> {\n        @Override\n        public CurrencyUnit deserialize(JsonParser parser, DeserializationContext context) throws IOException {\n            String currencyCode = parser.getValueAsString();\n            if (StringUtils.isBlank(currencyCode)) {\n                return null;\n            }\n            return Monetary.getCurrency(currencyCode);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/json/JacksonDate.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.json;\n\nimport cn.ponfee.commons.date.JavaUtilDateFormat;\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.io.IOException;\nimport java.text.DateFormat;\nimport java.text.ParseException;\nimport java.util.Date;\n\n/**\n * The Jackson Money Serializer & Deserializer\n *\n * @author Ponfee\n */\npublic class JacksonDate {\n\n    public static final JacksonDate INSTANCE = new JacksonDate(JavaUtilDateFormat.DEFAULT);\n\n    private final JsonSerializer<Date> serializer;\n    private final JsonDeserializer<Date> deserializer;\n\n    public JacksonDate(DateFormat format) {\n        this.serializer = new Serializer(format);\n        this.deserializer = new Deserializer(format);\n    }\n\n    public JsonSerializer<Date> serializer() {\n        return this.serializer;\n    }\n\n    public JsonDeserializer<Date> deserializer() {\n        return this.deserializer;\n    }\n\n    private static class Serializer extends JsonSerializer<Date> {\n        private final DateFormat format;\n\n        private Serializer(DateFormat format) {\n            this.format = format;\n        }\n\n        @Override\n        public void serialize(Date date, JsonGenerator generator, SerializerProvider provider) throws IOException {\n            if (date == null) {\n                generator.writeNull();\n            } else {\n                generator.writeString(format.format(date));\n            }\n        }\n    }\n\n    private static class Deserializer extends JsonDeserializer<Date> {\n        private final DateFormat format;\n\n        private Deserializer(DateFormat format) {\n            this.format = format;\n        }\n\n        @Override\n        public Date deserialize(JsonParser p, DeserializationContext ctx) throws IOException {\n            String text = p.getText();\n            if (StringUtils.isBlank(text)) {\n                return null;\n            }\n\n            try {\n                return format.parse(text);\n            } catch (ParseException e) {\n                throw new IllegalArgumentException(\"Invalid date format: \" + text);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/json/JacksonMoney.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.json;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport com.fasterxml.jackson.databind.node.BaseJsonNode;\nimport com.fasterxml.jackson.databind.node.NullNode;\nimport org.javamoney.moneta.Money;\n\nimport javax.money.CurrencyUnit;\nimport javax.money.Monetary;\nimport java.io.IOException;\n\n/**\n * The Jackson Money Serializer & Deserializer\n *\n * @author Ponfee\n */\npublic class JacksonMoney {\n\n    public static final JacksonMoney INSTANCE = new JacksonMoney();\n\n    private static final String CURRENCY = \"currency\";\n    private static final String NUMBER = \"number\";\n\n    private final JsonSerializer<Money> serializer;\n    private final JsonDeserializer<Money> deserializer;\n\n    private JacksonMoney() {\n        this.serializer = new Serializer();\n        this.deserializer = new Deserializer();\n    }\n\n    public JsonSerializer<Money> serializer() {\n        return this.serializer;\n    }\n\n    public JsonDeserializer<Money> deserializer() {\n        return this.deserializer;\n    }\n\n    private static class Serializer extends JsonSerializer<Money> {\n        @Override\n        public void serialize(Money money, JsonGenerator generator, SerializerProvider provider) throws IOException {\n            if (money == null) {\n                generator.writeNull();\n            } else {\n                CurrencyUnit currency = money.getCurrency();\n                generator.writeStartObject();\n                generator.writeStringField(CURRENCY, currency.getCurrencyCode());\n                generator.writeNumberField(NUMBER, money.getNumberStripped().movePointRight(currency.getDefaultFractionDigits()).longValueExact());\n                generator.writeEndObject();\n            }\n        }\n    }\n\n    private static class Deserializer extends JsonDeserializer<Money> {\n        @Override\n        public Money deserialize(JsonParser parser, DeserializationContext context) throws IOException {\n            BaseJsonNode jsonNode = parser.readValueAsTree();\n            if (jsonNode == null || jsonNode instanceof NullNode) {\n                return null;\n            }\n            String currency = jsonNode.required(CURRENCY).textValue();\n            long number = jsonNode.required(NUMBER).longValue();\n            return Money.ofMinor(Monetary.getCurrency(currency), number);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/json/JacksonTypeReferences.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.json;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * The Jackson TypeReference holder\n *  \n * @author Ponfee\n */\npublic final class JacksonTypeReferences {\n\n    public static final TypeReference<Map<String, Object>> MAP_NORMAL = new TypeReference<Map<String, Object>>() {};\n    public static final TypeReference<Map<String, String>> MAP_STRING = new TypeReference<Map<String, String>>() {};\n\n    public static final TypeReference<List<Object>> LIST_OBJECT = new TypeReference<List<Object>>() {};\n    public static final TypeReference<List<String>> LIST_STRING = new TypeReference<List<String>>() {};\n\n    public static final TypeReference<Set<Object>> SET_OBJECT = new TypeReference<Set<Object>>() {};\n    public static final TypeReference<Set<String>> SET_STRING = new TypeReference<Set<String>>() {};\n\n    public static final TypeReference<List<Map<String, Object>>> LIST_MAP_NORMAL = new TypeReference<List<Map<String, Object>>>() {};\n    public static final TypeReference<List<Map<String, String>>> LIST_MAP_STRING = new TypeReference<List<Map<String, String>>>() {};\n\n    public static final TypeReference<Set<Map<String, Object>>> SET_MAP_NORMAL = new TypeReference<Set<Map<String, Object>>>() {};\n    public static final TypeReference<Set<Map<String, String>>> SET_MAP_STRING = new TypeReference<Set<Map<String, String>>>() {};\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/json/Jsons.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.json;\n\nimport cn.ponfee.commons.date.*;\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.core.*;\nimport com.fasterxml.jackson.core.json.JsonWriteFeature;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.*;\nimport com.fasterxml.jackson.databind.module.SimpleModule;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\nimport com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;\nimport com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;\nimport com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;\nimport com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;\nimport com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.springframework.util.Assert;\n\nimport javax.annotation.concurrent.ThreadSafe;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Type;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.Date;\nimport java.util.Map;\n\n/**\n * The json utility based jackson\n *\n * @author Ponfee\n * @ThreadSafe\n */\n@ThreadSafe\npublic final class Jsons {\n\n    public static final TypeReference<Map<String, Object>> MAP_NORMAL = new TypeReference<Map<String, Object>>() {};\n\n    /**\n     * 标准：忽略对象中值为null的属性\n     */\n    public static final Jsons NORMAL = new Jsons(JsonInclude.Include.NON_NULL);\n\n    /**\n     * 不排除任何属性\n     */\n    public static final Jsons ALL = new Jsons(null);\n\n    /**\n     * Jackson ObjectMapper(thread safe)\n     */\n    private final ObjectMapper mapper;\n\n    private Jsons(JsonInclude.Include include) {\n        this.mapper = createObjectMapper(include);\n    }\n\n    // --------------------------------------------------------serialization\n\n    /**\n     * Converts object to json, and write to output stream\n     *\n     * @param output the output stream\n     * @param target the target object\n     */\n    public void write(OutputStream output, Object target) {\n        try {\n            mapper.writeValue(output, target);\n        } catch (IOException e) {\n            ExceptionUtils.rethrow(e);\n        }\n    }\n\n    /**\n     * Converts an object(POJO, Array, Collection, ...) to json string\n     *\n     * @param target target object\n     * @return json string\n     */\n    public String string(Object target) {\n        try {\n            return mapper.writeValueAsString(target);\n        } catch (IOException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    /**\n     * Serialize the byte array of json\n     *\n     * @param target object\n     * @return byte[] array\n     */\n    public byte[] bytes(Object target) {\n        try {\n            return mapper.writeValueAsBytes(target);\n        } catch (IOException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    // --------------------------------------------------------deserialization\n\n    /**\n     * Deserialize the json string to java object\n     *\n     * @param json     json string\n     * @param javaType JavaType\n     * @return the javaType's object\n     * @see ObjectMapper#getTypeFactory()\n     * @see ObjectMapper#constructType(Type)\n     * @see com.fasterxml.jackson.databind.type.TypeFactory#constructGeneralizedType(JavaType, Class)\n     */\n    public <T> T parse(String json, JavaType javaType) {\n        if (StringUtils.isEmpty(json)) {\n            return null;\n        }\n        try {\n            return mapper.readValue(json, javaType);\n        } catch (Exception e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    /**\n     * Deserialize the json byte array to java object\n     *\n     * @param json     json byte array\n     * @param javaType JavaType\n     * @return the javaType's object\n     */\n    public <T> T parse(byte[] json, JavaType javaType) {\n        if (json == null || json.length == 0) {\n            return null;\n        }\n        try {\n            return mapper.readValue(json, javaType);\n        } catch (Exception e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    public <T> T parse(String json, Class<T> target) {\n        return parse(json, mapper.constructType(target));\n    }\n\n    public <T> T parse(byte[] json, Class<T> target) {\n        return parse(json, mapper.constructType(target));\n    }\n\n    public <T> T parse(String json, Type type) {\n        return parse(json, mapper.constructType(type));\n    }\n\n    public <T> T parse(byte[] json, Type type) {\n        return parse(json, mapper.constructType(type));\n    }\n\n    public <T> T parse(String json, TypeReference<T> type) {\n        return parse(json, mapper.constructType(type));\n    }\n\n    public <T> T parse(byte[] json, TypeReference<T> type) {\n        return parse(json, mapper.constructType(type));\n    }\n\n    // ----------------------------------------------------static methods\n    public static String toJson(Object target) {\n        return NORMAL.string(target);\n    }\n\n    public static byte[] toBytes(Object target) {\n        return NORMAL.bytes(target);\n    }\n\n    public static Object[] parseArray(String body, Class<?>... types) {\n        if (body == null) {\n            return null;\n        }\n\n        ObjectMapper mapper = NORMAL.mapper;\n        JsonNode rootNode = readTree(mapper, body);\n        Assert.isTrue(rootNode.isArray(), \"Not array json data.\");\n        ArrayNode arrayNode = (ArrayNode) rootNode;\n\n        if (types.length == 1 && arrayNode.size() > 1) {\n            return new Object[]{parse(mapper, arrayNode, types[0])};\n        }\n\n        Object[] result = new Object[types.length];\n        for (int i = 0; i < types.length; i++) {\n            result[i] = parse(mapper, arrayNode.get(i), types[i]);\n        }\n        return result;\n    }\n\n    public static Object[] parseMethodArgs(String body, Method method) {\n        if (body == null) {\n            return null;\n        }\n\n        // 不推荐使用fastjson，项目中尽量统一使用一种JSON序列化方式\n        //return com.alibaba.fastjson.JSON.parseArray(body, method.getGenericParameterTypes()).toArray();\n\n        Type[] genericArgumentTypes = method.getGenericParameterTypes();\n        int argumentCount = genericArgumentTypes.length;\n        if (/*method.getParameterCount()*/argumentCount == 0) {\n            return null;\n        }\n\n        ObjectMapper mapper = NORMAL.mapper;\n        JsonNode rootNode = readTree(mapper, body);\n        if (rootNode.isArray()) {\n            ArrayNode arrayNode = (ArrayNode) rootNode;\n\n            // 方法只有一个参数，但请求参数长度大于1\n            // [\"a\", \"b\"]     -> method(Object[] arg) -> arg=[\"a\", \"b\"]\n            // [[\"a\"], [\"b\"]] -> method(Object[] arg) -> arg=[[\"a\"], [\"b\"]]\n            if (argumentCount == 1 && arrayNode.size() > 1) {\n                return new Object[]{parse(mapper, arrayNode, genericArgumentTypes[0])};\n            }\n\n            // 其它情况，在调用方将参数(requestParameters)用数组包一层：new Object[]{ arg-1, arg-2, ..., arg-n }\n            // [[\"a\", \"b\"]]   -> method(Object[] arg)                 -> arg =[\"a\", \"b\"]\n            // [[\"a\"], [\"b\"]] -> method(Object[] arg1, Object[] arg2) -> arg1=[\"a\"], arg2=[\"b\"]\n            // [\"a\", \"b\"]     -> method(Object[] arg1, Object[] arg2) -> arg1=[\"a\"], arg2=[\"b\"]  # ACCEPT_SINGLE_VALUE_AS_ARRAY作用：将字符串“a”转为数组arg1[]\n            Assert.isTrue(\n                argumentCount == arrayNode.size(),\n                () -> \"Method arguments size: \" + argumentCount + \", but actual size: \" + arrayNode.size()\n            );\n\n            Object[] methodArguments = new Object[argumentCount];\n            for (int i = 0; i < argumentCount; i++) {\n                methodArguments[i] = parse(mapper, arrayNode.get(i), genericArgumentTypes[i]);\n            }\n            return methodArguments;\n        } else {\n            Assert.isTrue(argumentCount == 1, \"Single object request parameter not support multiple arguments method.\");\n            return new Object[]{parse(mapper, rootNode, genericArgumentTypes[0])};\n        }\n    }\n\n    public static <T> T fromJson(String json, JavaType javaType) {\n        return NORMAL.parse(json, javaType);\n    }\n\n    public static <T> T fromJson(byte[] json, JavaType javaType) {\n        return NORMAL.parse(json, javaType);\n    }\n\n    public static <T> T fromJson(String json, Class<T> target) {\n        return NORMAL.parse(json, target);\n    }\n\n    public static <T> T fromJson(byte[] json, Class<T> target) {\n        return NORMAL.parse(json, target);\n    }\n\n    public static <T> T fromJson(String json, Type target) {\n        return NORMAL.parse(json, target);\n    }\n\n    public static <T> T fromJson(byte[] json, Type target) {\n        return NORMAL.parse(json, target);\n    }\n\n    public static <T> T fromJson(String json, TypeReference<T> type) {\n        return NORMAL.parse(json, type);\n    }\n\n    public static <T> T fromJson(byte[] json, TypeReference<T> type) {\n        return NORMAL.parse(json, type);\n    }\n\n    public static ObjectMapper createObjectMapper(JsonInclude.Include include) {\n        JsonFactory jsonFactory = new JsonFactoryBuilder()\n            .disable(JsonFactory.Feature.INTERN_FIELD_NAMES)\n            .build();\n        ObjectMapper mapper = new ObjectMapper(jsonFactory);\n        // 设置序列化时的特性\n        if (include != null) {\n            mapper.setSerializationInclusion(include);\n        }\n        configObjectMapper(mapper);\n        return mapper;\n    }\n\n    public static void configObjectMapper(ObjectMapper mapper) {\n        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 反序列化时忽略未知属性\n        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);    // Date不序列化为时间戳\n        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);          // 解决报错：No serializer found for class XXX and no properties discovered to create BeanSerializer\n        mapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);    // BigDecimal禁用科学计数格式输出\n        mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, false);     // 禁止无双引号字段\n        mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, false);            // 禁止单引号字段\n        mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), true); // 字段加双引号\n\n        // java.util.Date：registerModule > JsonFormat(会使用setTimeZone) > setDateFormat(会使用setTimeZone)\n        //   1）如果同时配置了setDateFormat和registerModule，则使用registerModule\n        //   2）如果设置了setTimeZone，则会调用setDateFormat#setTimeZone(注：setTimeZone对registerModule无影响)\n        //   3）如果实体字段使用了JsonFormat注解，则setDateFormat不生效(会使用jackson内置的格式化器，默认为0时区，此时要setTimeZone)\n        //   4）JsonFormat注解对registerModule无影响(registerModule优先级最高)\n        mapper.setTimeZone(JavaUtilDateFormat.DEFAULT.getTimeZone()); // TimeZone.getDefault()\n        mapper.setDateFormat(JavaUtilDateFormat.DEFAULT);\n        //mapper.setConfig(mapper.getDeserializationConfig().with(mapper.getDateFormat()));\n        //mapper.setConfig(mapper.getSerializationConfig().with(mapper.getDateFormat()));\n\n        SimpleModule module = new SimpleModule();\n        module.addSerializer(Date.class, JacksonDate.INSTANCE.serializer());\n        module.addDeserializer(Date.class, JacksonDate.INSTANCE.deserializer());\n        //module.addSerializer(Money.class, JacksonMoney.INSTANCE.serializer());\n        //module.addDeserializer(Money.class, JacksonMoney.INSTANCE.deserializer());\n        mapper.registerModule(module);\n\n        // java.time.LocalDateTime\n        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(Dates.DATE_PATTERN);\n        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(\"HH:mm:ss\");\n        JavaTimeModule javaTimeModule = new JavaTimeModule();\n        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(LocalDateTimeFormat.PATTERN_11));\n        javaTimeModule.addDeserializer(LocalDateTime.class, CustomLocalDateTimeDeserializer.INSTANCE);\n        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));\n        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));\n        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));\n        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));\n        mapper.registerModule(javaTimeModule);\n\n        //mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);\n    }\n\n    private static JsonNode readTree(ObjectMapper mapper, String body) {\n        try {\n            return mapper.readTree(body);\n        } catch (JsonProcessingException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    private static Object parse(ObjectMapper mapper, JsonNode jsonNode, Type type) {\n        try {\n            return mapper\n                .readerFor(mapper.getTypeFactory().constructType(type))\n                .with(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)\n                .readValue(mapper.treeAsTokens(jsonNode));\n        } catch (IOException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/limit/current/CurrentLimiter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.limit.current;\n\nimport java.util.Date;\n\n/**\n * 流量限制：限流器（隔板）\n * \n * https://www.cnblogs.com/softidea/p/6229543.html\n *\n * @author Ponfee\n */\npublic interface CurrentLimiter {\n\n    /**\n     * 校验并追踪\n     * @param key\n     * @return\n     */\n    boolean checkpoint(String key);\n\n    /**\n     * 校验并追踪\n     * @param key\n     * @param requestThreshold\n     * @return\n     */\n    boolean checkpoint(String key, long requestThreshold);\n\n    /**\n     * 按区间统计\n     * @param key\n     * @param from\n     * @param to\n     * @return\n     */\n    long countByRange(String key, Date from, Date to);\n\n    /**\n     * 设置一分钟（60s）的访问限制量\n     * @param key\n     * @param threshold\n     * @return\n     */\n    void setRequestThreshold(String key, long threshold);\n\n    /**\n     * 获取配置的访问量\n     * @param key\n     * @return\n     */\n    long getRequestThreshold(String key);\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/limit/current/GuavaCurrentLimiter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.limit.current;\n\nimport cn.ponfee.commons.util.SynchronizedCaches;\nimport com.google.common.util.concurrent.RateLimiter;\n\nimport java.util.Date;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\n/**\n * The rate limiter based guava RateLimiter\n *\n * @author Ponfee\n */\npublic class GuavaCurrentLimiter implements CurrentLimiter {\n\n    private static final ConcurrentMap<String, RateLimiter> LIMITER_MAP = new ConcurrentHashMap<>();\n\n    @Override\n    public boolean checkpoint(String key) {\n        RateLimiter limiter = LIMITER_MAP.get(key);\n        return limiter == null || limiter.tryAcquire();\n    }\n\n    @Override\n    public boolean checkpoint(String key, long requestThreshold) {\n        if (requestThreshold < 0) {\n            return true; // 小于0表示无限制\n        } else if (requestThreshold == 0) {\n            return false; // 禁止访问\n        }\n\n        RateLimiter limiter = SynchronizedCaches.get(key, LIMITER_MAP, () -> RateLimiter.create(requestThreshold));\n\n        if (((Double) limiter.getRate()).longValue() != requestThreshold) {\n            synchronized (limiter) {\n                if (((Double) limiter.getRate()).longValue() != requestThreshold) {\n                    limiter.setRate(requestThreshold);\n                }\n            }\n        }\n        return limiter.tryAcquire();\n    }\n\n    @Override\n    public long countByRange(String key, Date from, Date to) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setRequestThreshold(String key, long threshold) {\n        if (threshold < -1) {\n            LIMITER_MAP.remove(key);\n        }\n\n        SynchronizedCaches.get(key, LIMITER_MAP, () -> RateLimiter.create(threshold)).setRate(threshold);\n    }\n\n    @Override\n    public long getRequestThreshold(String key) {\n        RateLimiter limiter = LIMITER_MAP.get(key);\n        if (limiter == null) {\n            return -1;\n        }\n        return ((Double) limiter.getRate()).longValue();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/limit/request/ConcurrentMapRequestLimiter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.limit.request;\n\nimport cn.ponfee.commons.util.Asserts;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * The request limiter based ConcurrentHashMap\n * \n * Warning: distribute depoly with multiple server nodes maybe occur problem\n * \n * @author Ponfee\n */\npublic final class ConcurrentMapRequestLimiter extends RequestLimiter {\n\n    private final ConcurrentMap<String, CacheValue<?>> cache = new ConcurrentHashMap<>();\n    private final Lock lock = new ReentrantLock(); // 定时清理加锁\n\n    private ConcurrentMapRequestLimiter(ScheduledExecutorService scheduler) {\n        Asserts.notNull(scheduler, \"Scheduler cannot be null.\");\n        scheduler.scheduleAtFixedRate(() -> {\n            if (!lock.tryLock()) {\n                return;\n            }\n            try {\n                long now = System.currentTimeMillis();\n                cache.entrySet().removeIf(x -> x.getValue().isExpire(now));\n            } finally {\n                lock.unlock();\n            }\n        }, 60, 120, TimeUnit.SECONDS);\n    }\n\n    // ---------------------------------------------------------------------request limit\n    @Override\n    public ConcurrentMapRequestLimiter limitFrequency(String key, int period, String message)\n        throws RequestLimitException {\n        checkLimit(CHECK_FREQ_KEY + key, period, 1, message);\n        return this;\n    }\n\n    @Override\n    public ConcurrentMapRequestLimiter limitThreshold(String key, int period, \n                                                      int limit, String message) \n        throws RequestLimitException {\n        checkLimit(CHECK_THRE_KEY + key, period, limit, message);\n        return this;\n    }\n\n    // ---------------------------------------------------------------------cache sms code\n    @Override\n    public void cacheCode(String key, String code, int ttl) {\n        add(CACHE_CODE_KEY + key, code, ttl);\n        remove(CHECK_CODE_KEY + key);\n    }\n\n    @Override\n    public ConcurrentMapRequestLimiter checkCode(String key, String code, int limit)\n        throws RequestLimitException {\n        if (StringUtils.isEmpty(code)) {\n            throw new RequestLimitException(\"验证码不能为空！\");\n        }\n\n        String cacheKey = CACHE_CODE_KEY + key;\n\n        // 1、判断验证码是否已失效\n        CacheValue<String> actual = get(cacheKey);\n        if (actual == null || actual.get() == null) {\n            throw new RequestLimitException(\"验证码失效，请重新获取！\");\n        }\n\n        String checkKey = CHECK_CODE_KEY + key;\n\n        // 2、检查是否验证超过限定次数\n        CacheValue<?> times = incrementAndGet(checkKey, actual.expireTimeMillis);\n        if (times.count() > limit) {\n            remove(cacheKey, checkKey); // 超过验证次数，删除缓存中的验证码\n            throw new RequestLimitException(\"验证错误次数过多，请重新获取！\");\n        }\n\n        // 3、检查验证码是否匹配\n        if (!actual.get().equals(code)) {\n            throw new RequestLimitException(\"验证码错误！\");\n        }\n\n        // 验证成功，删除缓存key\n        remove(cacheKey, checkKey);\n        return this;\n    }\n\n    // ---------------------------------------------------------------------cache captcha\n    @Override\n    public void cacheCaptcha(String key, String captcha, int expire) {\n        add(CACHE_CAPTCHA_KEY + key, captcha, expire);\n    }\n\n    @Override\n    public boolean checkCaptcha(String key, String captcha, boolean caseSensitive) {\n        CacheValue<String> value = getAndRemove(CACHE_CAPTCHA_KEY + key);\n\n        if (value == null || value.get() == null) {\n            return false;\n        }\n\n        return caseSensitive \n               ? value.get().equals(captcha)\n               : value.get().equalsIgnoreCase(captcha);\n    }\n\n    // ---------------------------------------------------------------------action\n    @Override\n    public void recordAction(String key, int period) {\n        incrementAndGet(TRACE_ACTION_KEY + key, expire(period));\n    }\n\n    @Override\n    public long countAction(String key) {\n        CacheValue<Void> cache = get(TRACE_ACTION_KEY + key);\n        return cache == null ? 0 : cache.count();\n    }\n\n    @Override\n    public void resetAction(String key) {\n        remove(TRACE_ACTION_KEY + key);\n    }\n\n    // ---------------------------------------------------------------------private methods\n    private void checkLimit(String key, int ttl, int limit, String message)\n        throws RequestLimitException {\n        CacheValue<?> cache = incrementAndGet(key, expire(ttl));\n        if (cache.count() > limit) {\n            throw new RequestLimitException(message);\n        }\n    }\n\n    private CacheValue<?> incrementAndGet(String key, long expireTimeMillis) {\n        CacheValue<?> value = cache.get(key);\n        if (value == null || value.isExpire()) {\n            synchronized (cache) {\n                value = cache.get(key);\n                if (value == null || value.isExpire()) { // 失效则重置\n                    value = new CacheValue<>(null, expireTimeMillis);\n                    cache.put(key, value);\n                    return value;\n                }\n            }\n        }\n        value.increment();\n        return value;\n    }\n\n    private void remove(String... keys) {\n        for (String key : keys) {\n            cache.remove(key);\n        }\n    }\n\n    private <T> CacheValue<T> getAndRemove(String key) {\n        CacheValue<T> value = (CacheValue<T>) cache.remove(key);\n        return value == null || value.isExpire() ? null : value;\n    }\n\n    private <T> void add(String key, T value, int ttl) {\n        cache.put(key, new CacheValue<>(value, expire(ttl)));\n    }\n\n    private <T> CacheValue<T> get(String key) {\n        CacheValue<T> value = (CacheValue<T>) cache.get(key);\n        if (value == null) {\n            return null;\n        } else if (value.isExpire()) {\n            cache.remove(key);\n            return null;\n        } else {\n            return value;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/limit/request/HttpSessionRequestLimiter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.limit.request;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.servlet.http.HttpSession;\n\n/**\n * The request limiter based http session\n * \n * Warning: User clear cookie maybe occur problem\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"unchecked\")\npublic class HttpSessionRequestLimiter extends RequestLimiter {\n\n    private final HttpSession session;\n\n    private HttpSessionRequestLimiter(HttpSession session) {\n        this.session = session;\n    }\n\n    public static HttpSessionRequestLimiter create(HttpSession session) {\n        return new HttpSessionRequestLimiter(session);\n    }\n\n    // ---------------------------------------------------------------------request limit\n    /**\n     * Client user (web browser) can clear session(cookie),\n     * so this limit can't really effect\n     * \n     * @deprecated\n     */\n    @Override @Deprecated\n    public HttpSessionRequestLimiter limitFrequency(String key, int period, String message)\n        throws RequestLimitException {\n        checkLimit(CHECK_FREQ_KEY + key, period, 1, message);\n        return this;\n        //throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public HttpSessionRequestLimiter limitThreshold(String key, int period, \n                                                              int limit, String message) \n        throws RequestLimitException {\n        checkLimit(CHECK_THRE_KEY + key, period, limit, message);\n        return this;\n        //throw new UnsupportedOperationException();\n    }\n\n    // ---------------------------------------------------------------------cache sms code\n    @Override\n    public void cacheCode(String key, String code, int ttl) {\n        add(CACHE_CODE_KEY + key, code, ttl);\n        remove(CHECK_CODE_KEY + key);\n    }\n\n    @Override\n    public HttpSessionRequestLimiter checkCode(String key, String code, int limit)\n        throws RequestLimitException {\n        if (StringUtils.isEmpty(code)) {\n            throw new RequestLimitException(\"验证码不能为空！\");\n        }\n\n        String cacheKey = CACHE_CODE_KEY + key;\n\n        // 1、判断验证码是否已失效\n        CacheValue<String> actual = get(cacheKey);\n        if (actual == null || actual.get() == null) {\n            throw new RequestLimitException(\"验证码失效，请重新获取！\");\n        }\n\n        String checkKey = CHECK_CODE_KEY + key;\n\n        // 2、检查是否验证超过限定次数\n        CacheValue<?> times = incrementAndGet(checkKey, actual.expireTimeMillis);\n        if (times.count() > limit) {\n            remove(cacheKey, checkKey); // 超过验证次数，删除缓存中的验证码\n            throw new RequestLimitException(\"验证错误次数过多，请重新获取！\");\n        }\n\n        // 3、检查验证码是否匹配\n        if (!actual.get().equals(code)) {\n            throw new RequestLimitException(\"验证码错误！\");\n        }\n\n        // 验证成功，删除缓存key\n        remove(cacheKey, checkKey);\n        return this;\n    }\n\n    // ---------------------------------------------------------------------cache captcha\n    @Override\n    public void cacheCaptcha(String key, String captcha, int expire) {\n        add(CACHE_CAPTCHA_KEY + key, captcha, expire);\n    }\n\n    @Override\n    public boolean checkCaptcha(String key, String captcha, boolean caseSensitive) {\n        CacheValue<String> value = getAndRemove(CACHE_CAPTCHA_KEY + key);\n\n        if (value == null || value.get() == null) {\n            return false;\n        }\n\n        return caseSensitive \n               ? value.get().equals(captcha)\n               : value.get().equalsIgnoreCase(captcha);\n    }\n\n    // ---------------------------------------------------------------------action\n    /**\n     * Client user (web browser) can clear session(cookie),\n     * so this limit can't really effect\n     * \n     * @deprecated\n     */\n    @Override @Deprecated\n    public void recordAction(String key, int period) {\n        incrementAndGet(TRACE_ACTION_KEY + key, expire(period));\n    }\n\n    @Override @Deprecated\n    public long countAction(String key) {\n        CacheValue<Void> cache = get(TRACE_ACTION_KEY + key);\n        return cache == null ? 0 : cache.count();\n    }\n\n    @Override @Deprecated\n    public void resetAction(String key) {\n        remove(TRACE_ACTION_KEY + key);\n    }\n\n    // ---------------------------------------------------------------------private methods\n    private void checkLimit(String key, int ttl, int limit, String message)\n        throws RequestLimitException {\n        CacheValue<?> cache = incrementAndGet(key, expire(ttl));\n        if (cache.count() > limit) {\n            throw new RequestLimitException(message);\n        }\n    }\n\n    private CacheValue<?> incrementAndGet(String key, long expireTimeMillis) {\n        synchronized (session) {\n            CacheValue<?> cache = (CacheValue<?>) session.getAttribute(key);\n            if (cache == null || cache.isExpire()) { // 失效则重置\n                cache = new CacheValue<>(null, expireTimeMillis);\n                session.setAttribute(key, cache);\n            } else {\n                cache.increment();\n            }\n            return cache;\n        }\n    }\n\n    private void remove(String... keys) {\n        for (String key : keys) {\n            session.removeAttribute(key);\n        }\n    }\n\n    private <T> CacheValue<T> getAndRemove(String key) {\n        CacheValue<T> cache = (CacheValue<T>) session.getAttribute(key);\n        if (cache == null) {\n            return null;\n        } else {\n            session.removeAttribute(key);\n            return cache.isExpire() ? null : cache;\n        }\n    }\n\n    private <T> void add(String key, T value, int ttl) {\n        session.setAttribute(key, new CacheValue<>(value, expire(ttl)));\n    }\n\n    private <T> CacheValue<T> get(String key) {\n        CacheValue<T> cache = (CacheValue<T>) session.getAttribute(key);\n        if (cache == null) {\n            return null;\n        } else if (cache.isExpire()) {\n            session.removeAttribute(key);\n            return null;\n        } else {\n            return cache;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/limit/request/RequestLimitException.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.limit.request;\n\n/**\n * 请求超限异常\n * @author Ponfee\n */\npublic class RequestLimitException extends Exception {\n\n    private static final long serialVersionUID = 2493768018114069549L;\n\n    /**\n     * @param message 错误信息\n     */\n    public RequestLimitException(String message) {\n        super(message);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/limit/request/RequestLimiter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.limit.request;\n\nimport cn.ponfee.commons.jce.HmacAlgorithms;\nimport cn.ponfee.commons.jce.digest.HmacUtils;\nimport org.apache.commons.codec.binary.Hex;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.crypto.Mac;\nimport java.io.Serializable;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Request limiter, like as send sms and so on\n * \n * @author Ponfee\n */\npublic abstract class RequestLimiter {\n\n    private static final byte[] SALT_PREFIX = \"{;a*9)p<?T\".getBytes();\n\n    /** limit operation key */\n    public static final String CHECK_FREQ_KEY = \"req:lmt:fre:\";\n    public static final String CHECK_THRE_KEY = \"req:lmt:thr:\";\n\n    /** validation code verify key */\n    public static final String CACHE_CODE_KEY = \"req:cah:code:\";\n    public static final String CHECK_CODE_KEY = \"req:chk:code:\";\n\n    /** image captcha code verify key */\n    public static final String CACHE_CAPTCHA_KEY = \"req:cah:cap:\";\n\n    /** count operation action key */\n    public static final String TRACE_ACTION_KEY = \"req:cnt:act:\";\n\n    // ----------------------------------------------------------------用于请求限制\n    public final RequestLimiter limitFrequency(String key, int period) \n        throws RequestLimitException {\n        return limitFrequency(key, period, \"请求频繁，请\" + format(period) + \"后再试！\");\n    }\n\n    /**\n     * 访问频率限制：一个周期内最多允许访问1次<p>\n     * 比如短信60秒内只能发送一次\n     * \n     * @param key the key\n     * @param period the period\n     * @param message the message\n     * \n     * @return the caller, chain program\n     * \n     * @throws RequestLimitException if over limit occurs \n     */\n    public abstract RequestLimiter limitFrequency(String key, int period, String message) \n        throws RequestLimitException;\n\n    public final RequestLimiter limitThreshold(String key, int period, int limit) \n        throws RequestLimitException {\n        return limitThreshold(key, period, limit, \n                              \"请求超限，请\" + RequestLimiter.format(period) + \"后再试！\");\n    }\n\n    /**\n     * 访问次数限制：一个周期内最多允许访问limit次\n     * 比如一个手机号一天只能发10次\n     * \n     * @param key    the key\n     * @param period the period\n     * @param limit  the limit\n     * @param message  the message\n     * \n     * @return the caller, chain program\n     * \n     * @throws RequestLimitException if over limit occurs \n     */\n    public abstract RequestLimiter limitThreshold(String key, int period, \n                                                  int limit, String message) \n        throws RequestLimitException;\n\n    // ----------------------------------------------------------------用于验证码校验（如手机验证码）\n    /**\n     * cache for the server generate validation code\n     * \n     * @param key   the cache key\n     * @param code  the validation code of server generate\n     * @param ttl   the expire time\n     */\n    public abstract void cacheCode(String key, String code, int ttl);\n\n    /**\n     * check the validation code of user input is equals server cache\n     * \n     * @param key   the cache key\n     * @param code  the validation code of user input\n     * @param limit the maximum fail input times\n     * \n     * @return the caller, chain program\n     * \n     * @throws RequestLimitException if over limit occurs\n     */\n    public abstract RequestLimiter checkCode(String key, String code, int limit) \n        throws RequestLimitException;\n\n    // ----------------------------------------------------------------用于缓存图片验证码\n    /**\n     * cache captcha of server generate\n     * \n     * @param key\n     * @param captcha the image captcha code of server generate\n     * @param expire  缓存有效时间\n     */\n    public abstract void cacheCaptcha(String key, String captcha, int expire);\n\n    public final boolean checkCaptcha(String key, String captcha) {\n        return this.checkCaptcha(key, captcha, false);\n    }\n\n    /**\n     * check captcha of user input\n     * \n     * @param key  the cache key\n     * @param captcha  the captcha\n     * @param caseSensitive  is case sensitive\n     * \n     * @return true|false\n     */\n    public abstract boolean checkCaptcha(String key, String captcha, \n                                         boolean caseSensitive);\n\n    // ------------------------------------------------------------------------行为计数（用于登录失败限制）\n    /**\n     * 计数周期内的行为<p>\n     * 用于登录失败达到一定次数后锁定账户等场景<p>\n     *\n     * @param key\n     * @param period\n     */\n    public abstract void recordAction(String key, int period);\n\n    /**\n     * 统计周期内的行为量<p>\n     * 用于登录失败达到一定次数后锁定账户等场景<p>\n     * \n     * @param key the key\n     * @return action count number\n     */\n    public abstract long countAction(String key);\n\n    /**\n     * 重置行为\n     * \n     * @param key the key\n     */\n    public abstract void resetAction(String key);\n\n    // ----------------------------------------------------------------用于验证码校验\n    /**\n     * 生成nonce校验码（返回到用户端）\n     *\n     * @param code a string like as captcha code\n     * @param salt a string like as mobile phone\n     * \n     * @return a check code\n     */\n    public static String buildNonce(String code, String salt) {\n        //byte[] key = Bytes.fromLong(new Random(code.hashCode()).nextLong()); // 第一个nextLong值是固定的\n        byte[] key = code.getBytes();\n        Mac mac = HmacUtils.getInitializedMac(HmacAlgorithms.HmacMD5, key);\n        mac.update(SALT_PREFIX);\n        return Hex.encodeHexString(mac.doFinal(salt.getBytes()));\n    }\n\n    /**\n     * 校验nonce\n     * \n     * @param nonce the nonce\n     * @param code  the code\n     * @param salt  the salt\n     * \n     * @return {@code true} is verify success\n     */\n    public static boolean verifyNonce(String nonce, String code, String salt) {\n        return StringUtils.isNotEmpty(nonce) && nonce.equals(buildNonce(code, salt));\n    }\n\n    /**\n     * 时间格式化，5/6 rate\n     * \n     * @param seconds\n     * @return\n     */\n    public static String format(int seconds) {\n        int days = seconds / 86400;\n\n        // 年\n        if (days > 365) {\n            return (days / 365 + ((days % 365) / 30 + 10) / 12) + \"年\";\n        }\n\n        // 月\n        if (days > 30) {\n            return (days / 30 + (days % 30 + 25) / 30) + \"个月\";\n        }\n\n        // 日\n        seconds %= 86400;\n        int hours = seconds / 3600;\n        if (days > 0) {\n            return (days + (hours + 20) / 24) + \"天\";\n        }\n\n        // 时\n        seconds %= 3600;\n        int minutes = seconds / 60;\n        if (hours > 0) {\n            return (hours + (minutes + 50) / 60) + \"小时\";\n        }\n\n        // 分\n        seconds %= 60;\n        if (minutes > 0) {\n            return (minutes + (seconds + 50) / 60) + \"分钟\";\n        }\n\n        // 秒\n        return seconds + \"秒\";\n    }\n\n    static long expire(int ttl) {\n        return System.currentTimeMillis() + ttl * 1000L;\n    }\n\n    static class CacheValue<T> implements Serializable {\n        private static final long serialVersionUID = 8615157453929878610L;\n\n        final T value;\n        final long expireTimeMillis;\n        final AtomicInteger count;\n\n        CacheValue(T value, long expireTimeMillis) {\n            this.value = value;\n            this.expireTimeMillis = expireTimeMillis;\n            this.count = new AtomicInteger(1);\n        }\n\n        int increment() {\n            return count.incrementAndGet();\n        }\n\n        int count() {\n            return count.get();\n        }\n\n        T get() {\n            return value;\n        }\n\n        boolean isExpire() {\n            return expireTimeMillis < System.currentTimeMillis();\n        }\n\n        boolean isExpire(long timeMillis) {\n            return expireTimeMillis < timeMillis;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/log/LogAnnotation.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.log;\n\nimport java.lang.annotation.*;\n\n/**\n * 如果是日志入库，则无法用在service的只读事务方法上，需要新开启嵌套的事务\n * <p>\n * \n * Ali开发手册：\n * 应用中的扩展日志（如打点、临时监控、访问日志等）命名方式：appName_logType_logName.log。\n * logType:日志类型，推荐分类有stats/desc/monitor/visit等；logName:日志描述。\n * 这种命名的好处：通过文件名就可知道日志文件属于什么应用，什么类型，什么目的，也有利于归类查找。\n * \n * 可以使用warn日志级别来记录用户输入参数错误的情况，避免用户投诉时，无所适从。\n * 注意日志输出的级别，error级别只记录系统逻辑出错、异常等重要的错误信息。\n * 如非必要，请不要在此场景打出error级别。\n * <p>\n * \n * 日志注解\n * \n * @author Ponfee\n */\n@Target({ ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface LogAnnotation {\n\n    LogType type() default LogType.UNDEFINED;\n\n    boolean enabled() default false; // 是否开启熔断\n\n    String desc() default \"\";\n\n    enum LogType {\n        UNDEFINED(0x0, null), ADD(0x1, \"新增\"), UPDATE(0x2, \"更新\"), \n        DELETE(0x3, \"删除\"), QUERY(0x4, \"查询\");\n\n        private final int type;\n\n        private final String comment;\n\n        LogType(int type, String comment) {\n            this.type = type;\n            this.comment = comment;\n        }\n\n        public String comment() {\n            return comment;\n        }\n\n        public int type() {\n            return type;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/log/LogInfo.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.log;\n\nimport cn.ponfee.commons.log.LogAnnotation.LogType;\nimport cn.ponfee.commons.model.ToJsonString;\n\n/**\n * 日志信息\n * \n * @author Ponfee\n */\npublic class LogInfo extends ToJsonString implements java.io.Serializable {\n    private static final long serialVersionUID = -4824757481106145723L;\n\n    private LogType type; // 日志类型\n    private String desc; // 日志描述\n    private String methodName; // 方法名称\n    private Object args; // 调用参数\n    private Object retVal; // 返回值\n    private String exception; // 异常信息\n    private int costTime; // 调用耗时（毫秒）\n\n    public LogInfo() {}\n\n    public LogInfo(String methodName) {\n        this.methodName = methodName;\n    }\n\n    public LogType getType() {\n        return type;\n    }\n\n    public void setType(LogType type) {\n        this.type = type;\n    }\n\n    public String getDesc() {\n        return desc;\n    }\n\n    public void setDesc(String desc) {\n        this.desc = desc;\n    }\n\n    public String getMethodName() {\n        return methodName;\n    }\n\n    public void setMethodName(String methodName) {\n        this.methodName = methodName;\n    }\n\n    public Object getArgs() {\n        return args;\n    }\n\n    public void setArgs(Object args) {\n        this.args = args;\n    }\n\n    public Object getRetVal() {\n        return retVal;\n    }\n\n    public void setRetVal(Object retVal) {\n        this.retVal = retVal;\n    }\n\n    public String getException() {\n        return exception;\n    }\n\n    public void setException(String exception) {\n        this.exception = exception;\n    }\n\n    public int getCostTime() {\n        return costTime;\n    }\n\n    public void setCostTime(int costTime) {\n        this.costTime = costTime;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/log/LogRecorder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.log;\n\nimport cn.ponfee.commons.exception.Throwables;\nimport cn.ponfee.commons.limit.current.CurrentLimiter;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport com.google.common.base.Preconditions;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <pre>\n *   1.开启spring切面特性：<aop:aspectj-autoproxy />\n *   2.编写子类：\n *     `@Component\n *     `@Aspect\n *     public class TestLogger extends LogRecorder {\n *         `@Around(value = \"execution(public * cn.xxx.service.impl..*Impl..*(..)) \n *                  && `@annotation(log)\", argNames = \"pjp,log\")\n *         `@Override\n *         public Object around(ProceedingJoinPoint pjp, LogAnnotation log) throws Throwable {\n *             return super.around(pjp, log);\n *         }\n *     }\n * </pre>\n * \n * 日志记录切处理\n * \n * @author Ponfee\n */\npublic abstract class LogRecorder {\n\n    private static final int DEFAULT_ALARM_THRESHOLD_MILLIS = 2000;\n    private static final Logger logger = LoggerFactory.getLogger(LogRecorder.class);\n\n    private final int alarmThresholdMillis; // 告警阀值\n    private final CurrentLimiter limiter; // 访问频率限制\n\n    public LogRecorder() {\n        this(DEFAULT_ALARM_THRESHOLD_MILLIS);\n    }\n\n    public LogRecorder(int alarmThresholdMillis) {\n        this(alarmThresholdMillis, null);\n    }\n\n    public LogRecorder(CurrentLimiter circuitBreaker) {\n        this(DEFAULT_ALARM_THRESHOLD_MILLIS, circuitBreaker);\n    }\n\n    public LogRecorder(int alarmThresholdMillis, CurrentLimiter circuitBreaker) {\n        Preconditions.checkArgument(alarmThresholdMillis > 0);\n        this.alarmThresholdMillis = alarmThresholdMillis;\n        this.limiter = circuitBreaker;\n    }\n\n    /**\n     * 日志拦截\n     * @param pjp\n     * @return\n     * @throws Throwable\n     */\n    public Object around(ProceedingJoinPoint pjp) throws Throwable {\n        return this.around(pjp, null);\n    }\n\n    /**\n     * 日志拦截\n     * @param pjp\n     * @param log\n     * @return\n     * @throws Throwable\n     */\n    public Object around(ProceedingJoinPoint pjp, LogAnnotation log) throws Throwable {\n        MethodSignature ms = (MethodSignature) pjp.getSignature();\n        String methodName = ms.getMethod().toGenericString();\n\n        // request volume threshold\n        if (limiter != null && log != null && log.enabled()\n            && !limiter.checkpoint(methodName)) {\n            throw new IllegalStateException(\"request denied\");\n        }\n\n        LogInfo logInfo = new LogInfo(methodName);\n        if (log != null) {\n            logInfo.setType(log.type());\n            logInfo.setDesc(log.desc());\n        }\n\n        String logs = getLogs(log);\n        logInfo.setArgs(pjp.getArgs());\n        if (logger.isInfoEnabled()) {\n            logger.info(\"[exec-before]-[{}]{}-{}\", methodName, logs, ObjectUtils.toString(logInfo.getArgs()));\n        }\n        long start = System.currentTimeMillis();\n        try {\n            Object retVal = pjp.proceed();\n            logInfo.setCostTime((int) (System.currentTimeMillis() - start));\n            logInfo.setRetVal(retVal);\n            if (logger.isInfoEnabled()) {\n                logger.info(\"[exec-after]-[{}]{}-[{}]\", methodName, logs, ObjectUtils.toString(retVal));\n            }\n            if (logger.isWarnEnabled() && logInfo.getCostTime() > alarmThresholdMillis) {\n                logger.warn(\"[exec-time]-[{}]{}-[cost {}]\", methodName, logs, logInfo.getCostTime()); // 执行时间告警\n            }\n            return retVal;\n        } catch (Throwable e) {\n            logger.error(\"[exec-throw]-[{}]{}-{}\", methodName, logs, ObjectUtils.toString(logInfo.getArgs()), e);\n            logInfo.setCostTime((int) (System.currentTimeMillis() - start));\n            logInfo.setException(Throwables.getRootCauseStackTrace(e));\n            throw e; // 向外抛\n        } finally {\n            try {\n                log(logInfo);\n            } catch (Throwable ex) {\n                logger.error(\"Handle log info occur error.\", ex);\n            }\n        }\n    }\n\n    /**\n     * 日志记录（可用于记录到日志表）\n     * @param logInfo\n     */\n    protected void log(LogInfo logInfo) {\n        // no-thing to do\n    }\n\n    private String getLogs(LogAnnotation log) {\n        if (log == null) {\n            return \"\";\n        }\n\n        StringBuilder builder = new StringBuilder(\"-[\");\n        builder.append(log.type());\n        if (log.desc() != null) {\n            builder.append(',').append(log.desc());\n        }\n        return builder.append(']').toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/math/FailureRatioActuary.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.math;\n\nimport java.util.BitSet;\nimport java.util.List;\nimport java.util.stream.IntStream;\n\n/**\n * Failure ratio\n *\n * @author Ponfee\n */\npublic class FailureRatioActuary {\n\n    private final BitSet bitset;\n    private final int size;\n    private int position = 0;\n\n    public FailureRatioActuary(int size) {\n        this.size = (size + 63) / 64 * 64;\n        this.bitset = new BitSet(size);\n        IntStream.range(0, size).forEach(bitset::set);\n    }\n\n    public int size() {\n        //Assert.state(size == bitset.size(), () -> \"Illegal size, except: \" + size + \", actual: \" + bitset.size());\n        return size;\n    }\n\n    public void set(boolean value) {\n        bitset.set(position++, value);\n        if (position == size) {\n            position = 0;\n        }\n    }\n\n    public void set(Boolean value) {\n        bitset.set(position++, value != null && value);\n        if (position == size) {\n            position = 0;\n        }\n    }\n\n    public <T> double ratio(T[] array, ToBooleanFunction<T> mapper) {\n        for (T val : array) {\n            set(mapper.apply(val));\n        }\n        return ratio();\n    }\n\n    public <T> double set(List<T> array, ToBooleanFunction<T> mapper) {\n        for (T val : array) {\n            set(mapper.apply(val));\n        }\n        return ratio();\n    }\n\n    public double ratio(boolean[] array) {\n        for (boolean val : array) {\n            set(val);\n        }\n        return ratio();\n    }\n\n    public double ratio() {\n        return ((double) (size - bitset.cardinality())) / size;\n    }\n\n    @Override\n    public String toString() {\n        return \"(\" + position + \", \" + bitset + \")\";\n    }\n\n    @FunctionalInterface\n    public interface ToBooleanFunction<T> {\n        boolean apply(T value);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/math/Maths.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.math;\n\nimport cn.ponfee.commons.util.Asserts;\nimport org.springframework.util.Assert;\n\n/**\n * 数学算术\n * 取模：Modulo Operation\n *\n * @author Ponfee\n */\npublic class Maths {\n\n    /**\n     * 以2为底n的对数\n     * \n     * @param n the value\n     * @return a value of log(n)/log(2)\n     */\n    public static strictfp double log2(double n) {\n        return log(n, 2);\n    }\n\n    /**\n     * 求以base为底n的对数\n     * {@link java.lang.Math#log10(double) }  求以10为底n的对数（lg）\n     * {@link java.lang.Math#log(double)   }  以e为底n的对数（自然对数，ln）\n     * {@link java.lang.Math#log1p(double) }  以e为底n+1的对数\n     * \n     * @param n     a value\n     * @param base  底数\n     * @return a double of logarithm\n     */\n    public static strictfp double log(double n, double base) {\n        return Math.log(n) / Math.log(base);\n    }\n\n    /**\n     * rotate shift left，循环左移位操作：0<=n<=32\n     * \n     * @param x the value\n     * @param n shift bit len\n     * @return a number of rotate left result\n     */\n    public static int rotateLeft(int x, int n) {\n        Assert.isTrue(n >= 0 && n <= 32, \"N must be range [0, 32].\");\n        return (x << n) | (x >>> (32 - n));\n    }\n\n    /**\n     * <pre>\n     * Returns a long value of bit count mask\n     * calculate the bit counts mask long value\n     *   a: (1 << bits) - 1\n     *   b: -1L ^ (-1L << bits)\n     *   c: ~(-1L << bits)\n     *   d: Long.MAX_VALUE >>> (63 - bits)\n     *\n     *  bitsMask(0)  -> 0                   -> 0000000000000000000000000000000000000000000000000000000000000000\n     *  bitsMask(1)  -> 1                   -> 0000000000000000000000000000000000000000000000000000000000000001\n     *  bitsMask(2)  -> 3                   -> 0000000000000000000000000000000000000000000000000000000000000011\n     *  bitsMask(10) -> 1023                -> 0000000000000000000000000000000000000000000000000000001111111111\n     *  bitsMask(20) -> 1048575             -> 0000000000000000000000000000000000000000000011111111111111111111\n     *  bitsMask(63) -> 9223372036854775807 -> 0111111111111111111111111111111111111111111111111111111111111111\n     *  bitsMask(64) -> -1                  -> 1111111111111111111111111111111111111111111111111111111111111111\n     * </pre>\n     * \n     * @param bits the bit count\n     * @return a long value\n     */\n    public static long bitsMask(int bits) {\n        Asserts.range(bits, 0, Long.SIZE, \"bits must range [0,64].\");\n        return bits == Long.SIZE ? -1 : ~(-1L << bits);\n    }\n\n    /**\n     * Returns a long value for {@code base}<sup>{@code exponent}</sup>.\n     * \n     * @param base      the base\n     * @param exponent  the exponent\n     * @return a long value for {@code base}<sup>{@code exponent}</sup>.\n     */\n    public static long pow(long base, int exponent) {\n        Assert.isTrue(base >= 1, \"Base number cannot  less than 1.\");\n        Assert.isTrue(exponent >= 0, \"Exponent number cannot  less than 1.\");\n        if (exponent == 0) {\n            return 1;\n        }\n\n        long result = base;\n        while (--exponent > 0) {\n            result *= base;\n        }\n        return result;\n    }\n\n    public static int abs(int a) {\n        // Integer.MIN_VALUE & 0x7FFFFFFF = 0\n        return (a == Integer.MIN_VALUE) ? Integer.MAX_VALUE : (a < 0) ? -a : a;\n    }\n\n    public static long abs(long a) {\n        return (a == Long.MIN_VALUE) ? Long.MAX_VALUE : (a < 0) ? -a : a;\n    }\n\n    /**\n     * Returns square root of specified double value<p>\n     * Use binary search method\n     *\n     * @param value the value\n     * @return square root\n     */\n    public static strictfp double sqrtBinary(double value) {\n        if (value < 0.0D) {\n            return Double.NaN;\n        }\n        if (value == 0.0D || value == 1.0D) {\n            return value;\n        }\n\n        double lower, upper, root, square;\n        if (value > 1.0D) {\n            lower = 1.0D;\n            upper = value;\n        } else {\n            lower = value;\n            upper = 1.0D;\n        }\n\n        while ((root = lower + (upper - lower) / 2) != lower && root != upper && (square = root * root) != value) {\n            if (square > value) {\n                upper = root;\n            } else {\n                lower = root;\n            }\n        }\n\n        // cannot find a more rounded value\n        return root;\n    }\n\n    /**\n     * Returns square root of specified double value<p>\n     * Use newton iteration method: X(n+1)=[X(n)+p/Xn]/2\n     *\n     * @param value the value\n     * @return square root\n     */\n    public static strictfp double sqrtNewton(double value) {\n        if (value < 0) {\n            return Double.NaN;\n        }\n        if (value == 0.0D) {\n            return value;\n        }\n\n        double r = value / 2, t;\n        do {\n            t = r;\n            r = (t + value / t) / 2;\n        } while (t != r);\n        return r;\n    }\n\n    // ------------------------------------------------------------------------int plus/minus\n    public static int plus(int a, int b) {\n        if (a > 0 && b > 0) {\n            return Integer.MAX_VALUE - b < a ? Integer.MAX_VALUE : a + b;\n        } else if (a < 0 && b < 0) {\n            return Integer.MIN_VALUE - b > a ? Integer.MIN_VALUE : a + b;\n        } else {\n            return a + b;\n        }\n    }\n\n    public static int minus(int a, int b) {\n        if (a > 0 && b < 0) {\n            return Integer.MAX_VALUE + b < a ? Integer.MAX_VALUE : a - b;\n        } else if (a < 0 && b > 0) {\n            return Integer.MIN_VALUE + b > a ? Integer.MIN_VALUE : a - b;\n        } else {\n            return a - b;\n        }\n    }\n\n    // ------------------------------------------------------------------------long plus/minus\n    public static long plus(long a, long b) {\n        if (a > 0 && b > 0) {\n            return Long.MAX_VALUE - b < a ? Long.MAX_VALUE : a + b;\n        } else if (a < 0 && b < 0) {\n            return Long.MIN_VALUE - b > a ? Long.MIN_VALUE : a + b;\n        } else {\n            return a + b;\n        }\n    }\n\n    public static long minus(long a, long b) {\n        if (a > 0 && b < 0) {\n            return Long.MAX_VALUE + b < a ? Long.MAX_VALUE : a - b;\n        } else if (a < 0 && b > 0) {\n            return Long.MIN_VALUE + b > a ? Long.MIN_VALUE : a - b;\n        } else {\n            return a - b;\n        }\n    }\n\n    /**\n     * Returns the greatest common divisor\n     *\n     * @param a the first number\n     * @param b the second number\n     * @return gcd\n     */\n    public static int gcd(int a, int b) {\n        if (a < 0 || b < 0) {\n            throw new ArithmeticException();\n        }\n\n        if (a == 0 || b == 0) {\n            return Math.abs(a - b);\n        }\n\n        for (int c; (c = a % b) != 0;) {\n            a = b;\n            b = c;\n        }\n        return b;\n    }\n\n    /**\n     * Returns the greatest common divisor in array\n     *\n     * @param array the int array\n     * @return gcd\n     */\n    public static int gcd(int[] array) {\n        int result = array[0];\n        for (int i = 1; i < array.length; i++) {\n            result = gcd(result, array[i]);\n        }\n\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/math/Numbers.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.math;\n\nimport cn.ponfee.commons.base.Symbol;\nimport cn.ponfee.commons.base.tuple.Tuple2;\nimport com.google.common.base.Strings;\nimport com.google.common.primitives.Chars;\nimport org.apache.commons.codec.binary.Hex;\nimport org.springframework.util.Assert;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.math.RoundingMode;\nimport java.text.DecimalFormat;\nimport java.text.NumberFormat;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.LongStream;\n\nimport static org.apache.commons.lang3.ObjectUtils.defaultIfNull;\n\n/**\n * <pre>\n * Number utility\n *\n * 十进制：10\n * 二进制：0B10\n * 八进制：010\n * 十六进制：0X10\n * 小数点：1e-9\n * </pre>\n *\n * @author Ponfee\n */\npublic final class Numbers {\n\n    public static final int     ZERO_INT     = 0;\n    public static final Integer ZERO_INTEGER = ZERO_INT;\n    public static final byte    ZERO_BYTE    = 0x00;\n    public static final double  ZERO_DOUBLE  = 0.0D;\n    public static final double  ONE_DOUBLE   = 1.0D;\n\n    // --------------------------------------------------------------character convert\n    public static char toChar(Object obj) {\n        return toChar(obj, Symbol.Char.ZERO);\n    }\n\n    public static char toChar(Object obj, char defaultVal) {\n        Character value = toWrapChar(obj);\n        return value == null ? defaultVal : value;\n    }\n\n    public static Character toWrapChar(Object obj) {\n        if (obj == null) {\n            return null;\n        } else if (obj instanceof Character) {\n            return (Character) obj;\n        } else if (obj instanceof Number) {\n            return (char) ((Number) obj).intValue();\n        } else if (obj instanceof byte[]) {\n            return Chars.fromByteArray((byte[]) obj);\n        } else if (obj instanceof Boolean) {\n            return (char) (((boolean) obj) ? 0xFF : 0x00);\n        } else {\n            String str = obj.toString();\n            return str.length() == 1 ? str.charAt(0) : null;\n        }\n    }\n\n    // -----------------------------------------------------------------boolean convert\n    public static boolean toBoolean(Object obj) {\n        return toBoolean(obj, false);\n    }\n\n    public static boolean toBoolean(Object obj, boolean defaultVal) {\n        Boolean value = toWrapBoolean(obj);\n        return value == null ? defaultVal : value;\n    }\n\n    public static Boolean toWrapBoolean(Object obj) {\n        if (obj == null) {\n            return null;\n        } else if (obj instanceof Boolean) {\n            return (Boolean) obj;\n        } else if (obj instanceof Number) {\n            return ((Number) obj).byteValue() != ZERO_BYTE;\n        } else {\n            return Boolean.parseBoolean(obj.toString());\n        }\n    }\n\n    // -----------------------------------------------------------------byte convert\n    public static byte toByte(Object obj) {\n        return toByte(obj, (byte) 0);\n    }\n\n    public static byte toByte(Object obj, byte defaultVal) {\n        if (obj instanceof Number) {\n            return ((Number) obj).byteValue();\n        }\n        Long value = parseLong(obj);\n        return value == null ? defaultVal : value.byteValue();\n    }\n\n    public static Byte toWrapByte(Object obj) {\n        if (obj instanceof Byte) {\n            return (Byte) obj;\n        }\n        if (obj instanceof Number) {\n            return ((Number) obj).byteValue();\n        }\n        Long value = parseLong(obj);\n        return value == null ? null : value.byteValue();\n    }\n\n    // -----------------------------------------------------------------short convert\n    public static short toShort(Object obj) {\n        return toShort(obj, (short) 0);\n    }\n\n    public static short toShort(Object obj, short defaultVal) {\n        if (obj instanceof Number) {\n            return ((Number) obj).shortValue();\n        }\n        Long value = parseLong(obj);\n        return value == null ? defaultVal : value.shortValue();\n    }\n\n    public static Short toWrapShort(Object obj) {\n        if (obj instanceof Short) {\n            return (Short) obj;\n        }\n        if (obj instanceof Number) {\n            return ((Number) obj).shortValue();\n        }\n        Long value = parseLong(obj);\n        return value == null ? null : value.shortValue();\n    }\n\n    // -----------------------------------------------------------------int convert\n    public static int toInt(Object obj) {\n        return toInt(obj, 0);\n    }\n\n    public static int toInt(Object obj, int defaultVal) {\n        if (obj instanceof Number) {\n            return ((Number) obj).intValue();\n        }\n        Long value = parseLong(obj);\n        return value == null ? defaultVal : value.intValue();\n    }\n\n    public static Integer toWrapInt(Object obj) {\n        if (obj instanceof Integer) {\n            return (Integer) obj;\n        }\n        if (obj instanceof Number) {\n            return ((Number) obj).intValue();\n        }\n        Long value = parseLong(obj);\n        return value == null ? null : value.intValue();\n    }\n\n    // -----------------------------------------------------------------long convert\n    public static long toLong(Object obj) {\n        return toLong(obj, 0L);\n    }\n\n    public static long toLong(Object obj, long defaultVal) {\n        if (obj instanceof Number) {\n            return ((Number) obj).longValue();\n        }\n        Long value = parseLong(obj);\n        return value == null ? defaultVal : value;\n    }\n\n    public static Long toWrapLong(Object obj) {\n        if (obj instanceof Long) {\n            return (Long) obj;\n        }\n        if (obj instanceof Number) {\n            return ((Number) obj).longValue();\n        }\n        return parseLong(obj);\n    }\n\n    // -----------------------------------------------------------------float convert\n    public static float toFloat(Object obj) {\n        return toFloat(obj, 0.0F);\n    }\n\n    public static float toFloat(Object obj, float defaultVal) {\n        if (obj instanceof Number) {\n            return ((Number) obj).floatValue();\n        }\n        Double value = parseDouble(obj);\n        return value == null ? defaultVal : value.floatValue();\n    }\n\n    public static Float toWrapFloat(Object obj) {\n        if (obj instanceof Float) {\n            return (Float) obj;\n        }\n        if (obj instanceof Number) {\n            return ((Number) obj).floatValue();\n        }\n        Double value = parseDouble(obj);\n        return value == null ? null : value.floatValue();\n    }\n\n    // -----------------------------------------------------------------double convert\n    public static double toDouble(Object obj) {\n        return toDouble(obj, 0.0D);\n    }\n\n    public static double toDouble(Object obj, double defaultVal) {\n        if (obj instanceof Number) {\n            return ((Number) obj).doubleValue();\n        }\n        Double value = parseDouble(obj);\n        return value == null ? defaultVal : value;\n    }\n\n    public static Double toWrapDouble(Object obj) {\n        if (obj instanceof Double) {\n            return (Double) obj;\n        }\n        if (obj instanceof Number) {\n            return ((Number) obj).doubleValue();\n        }\n        return parseDouble(obj);\n    }\n\n    // ---------------------------------------------------------------------number format\n    /**\n     * 数字精度化\n     *\n     * @param value\n     * @param scale\n     * @return\n     */\n    public static double scale(Object value, int scale) {\n        double val = toDouble(value);\n\n        if (scale < 0) {\n            return val;\n        }\n\n        return BigDecimal.valueOf(val)\n                         .setScale(scale, RoundingMode.HALF_UP)\n                         .doubleValue();\n    }\n\n    /**\n     * 向下转单位\n     *\n     * @param value\n     * @param pow\n     * @return\n     */\n    public static double lower(double value, int pow) {\n        return BigDecimal.valueOf(value / Math.pow(10, pow)).doubleValue();\n    }\n\n    public static double lower(double value, int pow, int scale) {\n        return BigDecimal.valueOf(value / Math.pow(10, pow))\n                         .setScale(scale, RoundingMode.HALF_UP)\n                         .doubleValue();\n    }\n\n    /**\n     * 向上转单位\n     *\n     * @param value\n     * @param pow\n     * @return\n     */\n    public static double upper(double value, int pow) {\n        return BigDecimal.valueOf(value * Math.pow(10, pow)).doubleValue();\n    }\n\n    public static double upper(double value, int pow, int scale) {\n        return BigDecimal.valueOf(value * Math.pow(10, pow))\n                         .setScale(scale, RoundingMode.HALF_UP)\n                         .doubleValue();\n    }\n\n    /**\n     * 百分比\n     *\n     * @param numerator\n     * @param denominator\n     * @param scale\n     * @return\n     */\n    public static String percent(double numerator, double denominator, int scale) {\n        if (denominator == 0.0D) {\n            return \"--\";\n        }\n\n        return percent(numerator / denominator, scale);\n    }\n\n    /**\n     * 百分比\n     *\n     * @param value\n     * @param scale\n     * @return\n     */\n    public static String percent(double value, int scale) {\n        if (Double.isNaN(value) || Double.isInfinite(value)) {\n            return \"--\";\n        }\n\n        String format = \"#,##0\";\n        if (scale > 0) {\n            // StringUtils.leftPad(\"\", scale, '0'); String.format(\"%0\" + scale + \"d\", 0);\n            format += \".\" + Strings.repeat(\"0\", scale);\n        }\n        return new DecimalFormat(format + \"%\").format(value);\n    }\n\n    /**\n     * 数字格式化\n     *\n     * @param obj\n     * @return\n     */\n    public static String format(Object obj) {\n        return format(obj, \"###,###.###\");\n    }\n\n    /**\n     * 数字格式化\n     *\n     * @param obj\n     * @param format\n     * @return\n     */\n    public static String format(Object obj, String format) {\n        NumberFormat fmt = new DecimalFormat(format);\n        if (obj instanceof CharSequence) {\n            String str = obj.toString().replace(\",\", \"\");\n            if (str.endsWith(\"%\")) {\n                str = str.substring(0, str.length() - 1);\n                return fmt.format(Double.parseDouble(str)) + \"%\";\n            } else {\n                return fmt.format(Double.parseDouble(str));\n            }\n        } else {\n            return fmt.format(obj);\n        }\n    }\n\n    /**\n     * Returns a string value of double\n     *\n     * @param d      the double value\n     * @param scale  the scale\n     * @return a string\n     */\n    public static String format(double d, int scale) {\n        NumberFormat nf = NumberFormat.getInstance();\n        nf.setMaximumFractionDigits(scale);\n        nf.setGroupingUsed(false);\n        return nf.format(d);\n    }\n\n    /**\n     * 区间取值\n     *\n     * @param value\n     * @param min\n     * @param max\n     * @return\n     */\n    public static int bounds(Integer value, int min, int max) {\n        if (value == null || value < min) {\n            return min;\n        } else if (value > max) {\n            return max;\n        } else {\n            return value;\n        }\n    }\n\n    public static int sum(Integer a, Integer b) {\n        return defaultIfNull(a, 0) + defaultIfNull(b, 0);\n    }\n\n    public static long sum(Long a, Long b) {\n        return defaultIfNull(a, 0L) + defaultIfNull(b, 0L);\n    }\n\n    public static double sum(Double a, Double b) {\n        return defaultIfNull(a, 0.0D) + defaultIfNull(b, 0.0D);\n    }\n\n    public static boolean isNullOrZero(Long value) {\n        return value == null || value == 0L;\n    }\n\n    public static boolean isNullOrZero(Integer value) {\n        return value == null || value == 0;\n    }\n\n    /**\n     * 分片\n     *\n     * <pre>\n     *   slice(0 , 2)  ->  [0, 0]\n     *   slice(2 , 3)  ->  [1, 1, 0]\n     *   slice(3 , 1)  ->  [3]\n     *   slice(9 , 3)  ->  [3, 3, 3]\n     *   slice(10, 3)  ->  [4, 3, 3]\n     *   slice(11, 3)  ->  [4, 4, 3]\n     *   slice(12, 3)  ->  [4, 4, 4]\n     * </pre>\n     *\n     * @param quantity\n     * @param segment\n     * @return\n     */\n    public static int[] slice(int quantity, int segment) {\n        int[] result = new int[segment];\n        int quotient = quantity / segment;\n        int remainder = quantity % segment;\n        int moreValue = quotient + 1;\n        Arrays.fill(result, 0, remainder, moreValue);\n        Arrays.fill(result, remainder, segment, quotient);\n        return result;\n    }\n\n    /**\n     * Partition the number\n     * <pre>\n     *   partition( 0, 2)  ->  [(0, 0)]\n     *   partition( 2, 3)  ->  [(0, 0), (1, 1)]\n     *   partition( 3, 1)  ->  [(0, 2)]\n     *   partition( 9, 3)  ->  [(0, 2), (3, 5), (6, 8)]\n     *   partition(10, 3)  ->  [(0, 3), (4, 6), (7, 9)]\n     *   partition(11, 3)  ->  [(0, 3), (4, 7), (8, 10)]\n     *   partition(12, 3)  ->  [(0, 3), (4, 7), (8, 11)]\n     * </pre>\n     *\n     * @param number the number\n     * @param size   the size\n     * @return array\n     */\n    public static List<Tuple2<Integer, Integer>> partition(int number, int size) {\n        Assert.isTrue(number >= 0, \"Number must be greater than 0.\");\n        Assert.isTrue(size > 0, \"Size must be greater than 0.\");\n        if (number == 0) {\n            return Collections.singletonList(Tuple2.of(0, 0));\n        }\n\n        List<Tuple2<Integer, Integer>> result = new ArrayList<>(size);\n        int last = -1;\n        for (int a : slice(number, size)) {\n            if (a == 0) {\n                break;\n            }\n            result.add(Tuple2.of(last += 1, last += a - 1));\n        }\n\n        return result;\n    }\n\n    /**\n     * Split the bill for coupon amount<br/>\n     * split(new int[]{249, 249, 249, 3}, 748)  -> [249, 249, 248, 2]\n     *\n     * @param bills the bills\n     * @param value the coupon amount value\n     * @return split result\n     */\n    public static long[] split(long[] bills, long value) {\n        long total = LongStream.of(bills).sum();\n        if (total < value) {\n            throw new IllegalArgumentException(\"Total bill amount[\" + total + \"] cannot less than coupon amount[\" + value + \"]\");\n        }\n\n        long[] result = new long[bills.length];\n        if (bills.length == 0 || value == 0) {\n            return result;\n        }\n\n        double rate;\n        int i = 0, n = bills.length - 1;\n        for (; i < n; i++) {\n            // rate <= 1.0\n            rate = value / (double) total;\n\n            // 不能用Math.round：面值为748分钱的券 去平摊账单 [249, 249, 249, 3]，最后金额为3分钱的账单项要平摊掉4分钱\n            //result[i] = Math.min(Math.round(bills[i] * rate), value);\n            // 因为result[i]是ceil后的结果，所以按比率上来算value减得会更多，即rate只会递减，所以不会出现溢出(后面的费用项不够抵扣)的情况\n            result[i] = Math.min((int) Math.ceil(bills[i] * rate), value);\n            value -= result[i];\n            total -= bills[i];\n\n            if (value == 0) {\n                break;\n            }\n        }\n\n        // the last bill item\n        if (i == n) {\n            result[i] = value;\n        }\n        return result;\n    }\n\n    /**\n     * Returns the Long object is equals the Integer object\n     *\n     * @param a the Long a\n     * @param b the Integer b\n     * @return if is equals then return {@code true}\n     */\n    public static boolean equals(Long a, Integer b) {\n        if (a == null && b == null) {\n            return true;\n        }\n        return a != null && b != null && a.longValue() == b.intValue();\n    }\n\n    /**\n     * To upper hex string and remove prefix 0\n     *\n     * @param num the BigInteger\n     * @return upper hex string\n     */\n    public static String toHex(BigInteger num) {\n        String hex = Hex.encodeHexString(num.toByteArray(), false);\n        if (hex.matches(\"^0+$\")) {\n            return \"0\";\n        }\n        return hex.replaceFirst(\"^0*\", \"\");\n    }\n\n    // --------------------------------------------------------------------------金额汉化\n    private static final String[] CN_UPPER_NUMBER = {\n        \"零\", \"壹\", \"贰\", \"叁\", \"肆\", \"伍\", \"陆\", \"柒\", \"捌\", \"玖\"\n    };\n    private static final String[] CN_UPPER_MONETARY_UNIT = {\n        \"分\", \"角\", \"元\", \"拾\", \"佰\", \"仟\", \"万\", \"拾\", \"佰\",\n        \"仟\", \"亿\", \"拾\", \"佰\", \"仟\", \"兆\", \"拾\", \"佰\", \"仟\"\n    };\n    private static final BigDecimal MAX_VALUE = new BigDecimal(\"9999999999999999.995\");\n\n    /**\n     * 金额汉化（单位元）\n     *\n     * @param amount\n     * @return a string of chineseize amount\n     */\n    public static String chinesize(BigDecimal amount) {\n        if (amount.compareTo(MAX_VALUE) >= 0) {\n            throw new IllegalArgumentException(\"The amount value too large.\");\n        }\n        int signum = amount.signum(); // 正负数：0,1,-1\n        if (signum == 0) {\n            return \"零元整\";\n        }\n\n        // * 100\n        long number = amount.movePointRight(2).setScale(0, RoundingMode.HALF_UP)\n                            .abs().longValue();\n        int scale = (int) (number % 100), numIndex;\n        if (scale == 0) {\n            numIndex = 2;\n            number = number / 100;\n        } else if (scale % 10 == 0) {\n            numIndex = 1;\n            number = number / 10;\n        } else {\n            numIndex = 0;\n        }\n        boolean getZero = numIndex != 0;\n\n        StringBuilder builder = new StringBuilder();\n        for (int zeroSize = 0, numUnit; number > 0; number = number / 10, ++numIndex) {\n            numUnit = (int) (number % 10); // get the last number\n            if (numUnit > 0) {\n                if ((numIndex == 9) && (zeroSize >= 3)) {\n                    builder.insert(0, CN_UPPER_MONETARY_UNIT[6]);\n                }\n                if ((numIndex == 13) && (zeroSize >= 3)) {\n                    builder.insert(0, CN_UPPER_MONETARY_UNIT[10]);\n                }\n                builder.insert(0, CN_UPPER_MONETARY_UNIT[numIndex]);\n                builder.insert(0, CN_UPPER_NUMBER[numUnit]);\n                getZero = false;\n                zeroSize = 0;\n            } else {\n                ++zeroSize;\n                if (!getZero) {\n                    builder.insert(0, CN_UPPER_NUMBER[numUnit]);\n                }\n                if (numIndex == 2) {\n                    if (number > 0) {\n                        builder.insert(0, CN_UPPER_MONETARY_UNIT[numIndex]);\n                    }\n                } else if ( (((numIndex - 2) & 0x03) == 0) && (number % 1000 > 0) ) {\n                    builder.insert(0, CN_UPPER_MONETARY_UNIT[numIndex]);\n                }\n                getZero = true;\n            }\n        }\n\n        if (signum == -1) {\n            builder.insert(0, \"负\"); // 负数\n        }\n\n        if (scale == 0) {\n            builder.append(\"整\"); // 整数\n        }\n        return builder.toString();\n    }\n\n    // -------------------------------------------------------private methods\n    private static Long parseLong(Object obj) {\n        if (obj == null) {\n            return null;\n        }\n        try {\n            String val = obj.toString();\n            return val.indexOf('.') == -1\n                    ? Long.parseLong(val)\n                    : (long) Double.parseDouble(val);\n        } catch (NumberFormatException ignored) {\n            return null;\n        }\n    }\n\n    private static Double parseDouble(Object obj) {\n        if (obj == null) {\n            return null;\n        }\n        try {\n            return Double.parseDouble(obj.toString());\n        } catch (NumberFormatException ignored) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/math/WrappedBigDecimal.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.math;\n\nimport java.math.BigDecimal;\nimport java.math.MathContext;\n\n/**\n * 包装BigDecimal，用于lamda方法体内计算\n * \n * @author Ponfee\n */\npublic class WrappedBigDecimal {\n\n    private BigDecimal decimal;\n\n    public WrappedBigDecimal(Number num) {\n        this.decimal = BigDecimal.valueOf(num.doubleValue());\n    }\n\n    public synchronized void add(Number num) {\n        this.decimal = this.decimal.add(BigDecimal.valueOf(num.doubleValue()));\n    }\n\n    public synchronized void divide(BigDecimal divisor) {\n        this.decimal = this.decimal.divide(divisor);\n    }\n\n    public synchronized void remainder(BigDecimal divisor) {\n        this.decimal = this.decimal.remainder(divisor);\n    }\n\n    public synchronized void abs(MathContext mc) {\n        this.decimal = this.decimal.abs(mc);\n    }\n\n    public double getDouble() {\n        return this.decimal.doubleValue();\n    }\n\n    public int getInt() {\n        return this.decimal.intValue();\n    }\n\n    public long getLong() {\n        return this.decimal.longValue();\n    }\n\n    public float getFloat() {\n        return this.decimal.floatValue();\n    }\n\n    @Override\n    public String toString() {\n        return this.decimal.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/AbstractDataConverter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.reflect.BeanCopiers;\nimport cn.ponfee.commons.reflect.BeanMaps;\nimport org.springframework.cglib.beans.BeanCopier;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport static cn.ponfee.commons.reflect.GenericUtils.getActualTypeArgument;\nimport static cn.ponfee.commons.util.ObjectUtils.isNotBeanType;\nimport static cn.ponfee.commons.util.ObjectUtils.newInstance;\n\n/**\n * Converts model object to the data transfer object\n * \n * @param <S> source\n * @param <T> target\n * \n * @author Ponfee\n */\npublic abstract class AbstractDataConverter<S, T> implements Function<S, T> {\n\n    private final Class<T> targetType;\n    private final BeanCopier copier;\n\n    public AbstractDataConverter() {\n        this.copier = createBeanCopier(\n            getActualTypeArgument(getClass(), 0), \n            this.targetType = getActualTypeArgument(getClass(), 1)\n        );\n    }\n\n    /**\n     * Returns an target object copy source the argument object<p>\n     * \n     * Sub class can override this method<p>\n     * \n     * @param source the object\n     * @return a target object\n     */\n    public T convert(S source) {\n        if (source == null) {\n            return null;\n        }\n        return convert(source, this.targetType, this.copier);\n    }\n\n    // -------------------------------------------------------------final methods\n    public final void copyProperties(S source, T target) {\n        copy(source, target, this.copier);\n    }\n\n    public final List<T> convert(List<S> list) {\n        if (list == null) {\n            return null;\n        }\n\n        return list.stream().map(this).collect(Collectors.toList());\n    }\n\n    public final Page<T> convert(Page<S> page) {\n        if (page == null) {\n            return null;\n        }\n\n        return page.map(this);\n    }\n\n    public final Result<T> convertResultBean(Result<S> result) {\n        if (result == null) {\n            return null;\n        }\n        return result.from(convert(result.getData()));\n    }\n\n    public final Result<List<T>> convertResultList(Result<List<S>> result) {\n        if (result == null) {\n            return null;\n        }\n        return result.from(convert(result.getData()));\n    }\n\n    public final Result<Page<T>> convertResultPage(Result<Page<S>> result) {\n        if (result == null) {\n            return null;\n        }\n        return result.from(convert(result.getData()));\n    }\n\n    // ----------------------------------------------other methods\n    @Override\n    public final T apply(S source) {\n        return this.convert(source);\n    }\n\n    // -----------------------------------------------static methods\n    public static <S, T> T convert(S source, Class<T> targetType) {\n        return convert(source, targetType, null);\n    }\n\n    @SuppressWarnings({ \"unchecked\" })\n    public static <S, T> T convert(S source, Class<T> targetType, BeanCopier copier) {\n        if (source == null || targetType.isInstance(source)) {\n            return (T) source;\n        }\n\n        // convert\n        if (Map.class.isAssignableFrom(targetType)) {\n            return (T) (source instanceof Map ? source : BeanMaps.CGLIB.toMap(source));\n        } else if (source instanceof Map) {\n            return BeanMaps.CGLIB.toBean((Map<String, Object>) source, targetType);\n        } else {\n            T target = newInstance(targetType);\n            if (copier != null) {\n                copier.copy(source, target, null);\n            } else {\n                BeanCopiers.copy(source, target);\n            }\n            return target;\n        }\n    }\n\n    public static <S, T> void copy(S source, T target) {\n        copy(source, target, null);\n    }\n\n    @SuppressWarnings({ \"unchecked\", \"rawtypes\" })\n    public static <S, T> void copy(S source, T target, BeanCopier copier) {\n        if (source == null || target == null) {\n            return;\n        }\n\n        // convert the source object from source type to target type\n        if (target instanceof Map) {\n            if (source instanceof Map) {\n                ((Map) target).putAll((Map<?, ?>) source);\n            } else {\n                ((Map) target).putAll(BeanMaps.CGLIB.toMap(source));\n            }\n        } else if (source instanceof Map) {\n            BeanMaps.CGLIB.copyFromMap((Map) source, target);\n        } else if (copier != null) {\n            copier.copy(source, target, null);\n        } else {\n            BeanCopiers.copy(source, target);\n        }\n    }\n\n    public static <S, T> T convert(S source, Function<S, T> converter) {\n        if (source == null) {\n            return null;\n        }\n        return converter.apply(source);\n    }\n\n    public static <S, T> List<T> convert(List<S> list, Function<S, T> converter) {\n        if (list == null) {\n            return null;\n        }\n        return list.stream().map(converter).collect(Collectors.toList());\n    }\n\n    public static <S, T> Page<T> convert(Page<S> page, Function<S, T> converter) {\n        if (page == null) {\n            return null;\n        }\n        return page.map(converter);\n    }\n\n    public static <S, T> Result<T> convertResultBean(Result<S> result, Function<S, T> converter) {\n        if (result == null) {\n            return null;\n        }\n        return result.from(converter.apply(result.getData()));\n    }\n\n    public static <S, T> Result<List<T>> convertResultList(Result<List<S>> result, Function<S, T> converter) {\n        if (result == null) {\n            return null;\n        }\n        return result.from(convert(result.getData(), converter));\n    }\n\n    public static <S, T> Result<Page<T>> convertResultPage(Result<Page<S>> result, Function<S, T> converter) {\n        if (result == null) {\n            return null;\n        }\n        return result.from(convert(result.getData(), converter));\n    }\n\n    // -----------------------------------------------------------------------------------private methods\n    private static BeanCopier createBeanCopier(Class<?> sourceType, Class<?> targetType) {\n        if (isNotBeanType(sourceType) || isNotBeanType(targetType)) {\n            return null;\n        }\n\n        try {\n            return BeanCopiers.get(sourceType, targetType);\n        } catch (Exception e) {\n            throw new UnsupportedOperationException(\"Create BeanCopier occur error.\", e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/BaseEntity.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.util.Date;\n\n/**\n * Base class for Persistent Object(PO or DO)\n *\n * @author Ponfee\n */\n@Getter\n@Setter\npublic abstract class BaseEntity implements java.io.Serializable {\n    private static final long serialVersionUID = -3387171222355207376L;\n\n    private Long id;             // database table primary key\n    private Integer version = 1; // data version\n    private Date createdAt;      // created time\n    private Date updatedAt;      // last updated time\n\n    /**\n     * Base entity with biz-no filed\n     *\n     * @param <N> biz-no field type\n     */\n    @Getter\n    @Setter\n    public static abstract class Number<N> extends BaseEntity {\n        private static final long serialVersionUID = -6907471185117485946L;\n\n        private N no; // biz-no\n    }\n\n    /**\n     * Base entity with creator filed\n     *\n     * @param <U> creator field type\n     */\n    @Getter\n    @Setter\n    public static abstract class Creator<U> extends BaseEntity {\n        private static final long serialVersionUID = -812853678840369113L;\n\n        private U createdBy; // created user\n    }\n\n    /**\n     * Base entity with creator and updater filed\n     *\n     * @param <U> updater field type(userid or username)\n     */\n    @Getter\n    @Setter\n    public static abstract class Updater<U> extends Creator<U> {\n        private static final long serialVersionUID = 5333847915253038118L;\n\n        private U updatedBy; // last updated user\n    }\n\n    /**\n     * Base entity with biz-no, creator and updater filed\n     *\n     * @param <N> biz-no field type\n     * @param <U> creator and updater field type(userid or username)\n     */\n    @Getter\n    @Setter\n    public static abstract class All<N, U> extends Updater<U> {\n        private static final long serialVersionUID = -939331524501110803L;\n\n        private N no; // biz-no\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/CodeMsg.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\n/**\n * The code and message for {@link Result}\n *\n * @author Ponfee\n */\npublic interface CodeMsg {\n\n    int getCode();\n\n    boolean isSuccess();\n\n    String getMsg();\n\n    /**\n     * 中止当前运行的Java虚拟机，返回值给调用方(如bash)\n     * <p>0正常退出；非0异常退出；\n     * \n     * @see System#exit(int)\n     */\n    enum SystemExit implements CodeMsg {\n\n        SUCCESS(0),\n        FAILURE(1),\n        ;\n\n        private final int code;\n        private final boolean success;\n\n        SystemExit(int code) {\n            this.code = code;\n            this.success = code == 0;\n        }\n\n        @Override\n        public int getCode() {\n            return code;\n        }\n\n        @Override\n        public boolean isSuccess() {\n            return success;\n        }\n\n        @Override\n        public String getMsg() {\n            return super.name();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/Form.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.util.List;\n\n/**\n * 表单\n * \n * @author Ponfee\n */\npublic class Form implements java.io.Serializable {\n\n    private static final long serialVersionUID = 3335254023919017587L;\n\n    private List<Parameter> parameters;\n\n    public List<Parameter> getParameters() {\n        return parameters;\n    }\n\n    public void setParameters(List<Parameter> parameters) {\n        this.parameters = parameters;\n    }\n\n    public static class Parameter implements java.io.Serializable {\n        private static final long serialVersionUID = 4322704347383719451L;\n\n        private String  name;     // 参数名\n        private Type    type;     // 表单类型\n        private String  label;    // 标签名\n        private boolean required; // 是否必填\n        private boolean multiple; // 是否可多选\n\n        public String getName() {\n            return name;\n        }\n\n        public void setName(String name) {\n            this.name = name;\n        }\n\n        public Type getType() {\n            return type;\n        }\n\n        public void setType(Type type) {\n            this.type = type;\n        }\n\n        public String getLabel() {\n            return label;\n        }\n\n        public void setLabel(String label) {\n            this.label = label;\n        }\n\n        public boolean isRequired() {\n            return required;\n        }\n\n        public void setRequired(boolean required) {\n            this.required = required;\n        }\n\n        public boolean isMultiple() {\n            return multiple;\n        }\n\n        public void setMultiple(boolean multiple) {\n            this.multiple = multiple;\n        }\n    }\n\n    public enum Type {\n        INPUT, PASSWORD, TEXTAREA, RADIO,  //\n        CHECKBOX, SELECT, COMBOX, DATEBOX, // \n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/MapDataConverter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.reflect.Fields;\n\nimport java.util.Arrays;\nimport java.util.Dictionary;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Converts model object to map, specified source fields\n * \n * @param <S> source\n * \n * @author Ponfee\n */\npublic class MapDataConverter<S> extends AbstractDataConverter<S, Map<String, Object>> {\n\n    private final String[] fields;\n\n    public MapDataConverter(String... fields) {\n        this.fields = fields;\n    }\n\n    @Override\n    public Map<String, Object> convert(S source) {\n        return convert(source, fields);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <S> Map<String, Object> convert(S source, String... fields) {\n        Function<? super String, ?> vm;\n        if (source instanceof Map) {\n            vm = ((Map<String, Object>) source)::get;\n        } else if (source instanceof Dictionary) {\n            vm = ((Dictionary<String, Object>) source)::get;\n        } else {\n            vm = field -> Fields.get(source, field);\n        }\n        return Arrays.stream(fields).collect(Collectors.toMap(Function.identity(), vm));\n    }\n\n    public static <S> List<Map<String, Object>> convert(List<S> list, String... fields) {\n        if (list == null) {\n            return null;\n        }\n        return list.stream().map(e -> convert(e, fields)).collect(Collectors.toList());\n    }\n\n    public static <S> Page<Map<String, Object>> convert(Page<S> page, String... fields) {\n        if (page == null) {\n            return null;\n        }\n        return page.map(x -> convert(x, fields));\n    }\n\n    public static <S> Result<Map<String, Object>> convertResultBean(Result<S> result, String... fields) {\n        if (result == null) {\n            return null;\n        }\n        return result.from(convert(result.getData(), fields));\n    }\n\n    public static <S> Result<List<Map<String, Object>>> convertResultList(Result<List<S>> result, String... fields) {\n        if (result == null) {\n            return null;\n        }\n        return result.from(convert(result.getData(), fields));\n    }\n\n    public static <S> Result<Page<Map<String, Object>>> convertResultPage(Result<Page<S>> result, String... fields) {\n        if (result == null) {\n            return null;\n        }\n        return result.from(convert(result.getData(), fields));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/Null.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.io.Serializable;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Method;\n\n/**\n * The {@code Null} class is representing unable instance object\n * \n * @author Ponfee\n */\npublic final class Null implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    public static final Constructor<Null> BROKEN_CONSTRUCTOR;\n    public static final Method BROKEN_METHOD;\n    static {\n        try {\n            BROKEN_CONSTRUCTOR = Null.class.getDeclaredConstructor();\n            BROKEN_METHOD = Null.class.getDeclaredMethod(\"broken\");\n        } catch (Exception e) {\n            // cannot happen\n            throw new Error(e);\n        }\n    }\n\n    private Null() {\n        throw new AssertionError(\"Null cannot create instance.\");\n    }\n\n    private void broken() {\n        throw new AssertionError(\"Forbid invoke this method.\");\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/Page.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport com.google.common.collect.Lists;\nimport lombok.Data;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.springframework.cglib.beans.BeanCopier;\n\nimport java.beans.Transient;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * <pre>\n * 参考guthub开源的mybatis分页工具\n *   项目地址: \n *     http://git.oschina.net/free/Mybatis_PageHelper\n *     https://github.com/pagehelper/Mybatis-PageHelper\n * \n *   属性配置：\n *     http://bbs.csdn.net/topics/360010907\n * \n *   Mapper插件：\n *     https://github.com/abel533/Mapper\n * \n *   Spring Boot集成 MyBatis, 分页插件 PageHelper, 通用 Mapper\n *     https://github.com/abel533/MyBatis-Spring-Boot\n * </pre>\n * \n * @author Ponfee\n */\n@Data\npublic class Page<T> implements java.io.Serializable {\n    private static final long serialVersionUID = 1313118491812094979L;\n\n    /**\n     * https://mapstruct.org/\n     */\n    private static final BeanCopier COPIER = BeanCopier.create(Page.class, Page.class, false);\n\n    private int pageNum; // 当前页（start 1）\n    private int pageSize; // 每页的数量\n    private int size; // 当前页的数量\n    private long startRow; // 当前页面第一个元素在数据库中的行号（start 1）\n    private long endRow; // 当前页面最后一个元素在数据库中的行号\n    private long total; // 总记录数\n    private int pages; // 总页数\n    private List<T> rows; // 结果集\n    private int prePage; // 前一页\n    private int nextPage; // 下一页\n    private Boolean firstPage = Boolean.TRUE; // 是否为第一页（Pojo中bool类型一律不加is且用Boolean包装类型）\n    private Boolean lastPage = Boolean.FALSE; // 是否为最后一页\n    private Boolean hasPreviousPage = Boolean.FALSE; // 是否有前一页\n    private Boolean hasNextPage = Boolean.FALSE; // 是否有下一页\n    private int navigatePages; // 导航页码数\n    private int[] navigatePageNums; // 所有导航页号\n    private int navigateFirstPage; // 导航条上的第一页\n    private int navigateLastPage; // 导航条上的最后一页\n\n    public static <T> Page<T> empty() {\n        return new Page<>();\n    }\n\n    public static <T> Page<T> of(List<T> list) {\n        return new Page<>(list);\n    }\n\n    public static <T> Page<T> of(List<T> list, int navigatePages) {\n        return new Page<>(list, navigatePages);\n    }\n\n    public Page() {\n        this(new ArrayList<>());\n    }\n\n    /**\n     * 包装Page对象\n     * @param list\n     */\n    public Page(List<T> list) {\n        this(list, 8);\n    }\n\n    /**\n     * 包装Page对象\n     * @param list          page结果\n     * @param navigatePages 页码数量\n     */\n    public Page(List<T> list, int navigatePages) {\n        if (list instanceof com.github.pagehelper.Page) {\n            com.github.pagehelper.Page<T> page = (com.github.pagehelper.Page<T>) list;\n            this.pageNum = page.getPageNum();\n            this.pageSize = page.getPageSize();\n\n            this.pages = page.getPages();\n            this.rows = copy(page);\n            this.size = page.size();\n            this.total = page.getTotal();\n            if (this.size == 0) {\n                this.startRow = 0;\n                this.endRow = 0;\n            } else {\n                this.startRow = page.getStartRow() + 1; // 由于结果是>startRow的，所以实际的需要+1\n                this.endRow = this.startRow - 1 + this.size; // 计算实际的endRow（最后一页的时候特殊）\n            }\n        } else {\n            if (list == null) {\n                list = new ArrayList<>();\n            }\n            this.pageNum = 1;\n            this.pageSize = list.size();\n\n            this.pages = this.pageSize > 0 ? 1 : 0;\n            this.rows = list;\n            this.size = list.size();\n            this.total = list.size();\n            this.startRow = 0;\n            this.endRow = list.size() > 0 ? list.size() - 1 : 0;\n        }\n\n        this.navigatePages = navigatePages;\n        calcNavigatePageNums(); // 计算导航页\n        calcPage(); // 计算前后页，第一页，最后一页\n        judgePageBoudary(); // 判断页面边界\n    }\n\n    private List<T> copy(com.github.pagehelper.Page<T> page) {\n        return Lists.newArrayList(page);\n    }\n\n    /**\n     * 计算导航页\n     */\n    private void calcNavigatePageNums() {\n        if (pages <= navigatePages) { // 当总页数小于或等于导航页码数时\n            navigatePageNums = new int[pages];\n            for (int i = 0; i < pages; i++) {\n                navigatePageNums[i] = i + 1;\n            }\n        } else { // 当总页数大于导航页码数时\n            navigatePageNums = new int[navigatePages];\n            int startNum = pageNum - navigatePages / 2;\n            int endNum = pageNum + navigatePages / 2;\n\n            if (startNum < 1) {\n                startNum = 1;\n                // (最前navigatePages页\n                for (int i = 0; i < navigatePages; i++) {\n                    navigatePageNums[i] = startNum++;\n                }\n            } else if (endNum > pages) {\n                endNum = pages;\n                // 最后navigatePages页\n                for (int i = navigatePages - 1; i >= 0; i--) {\n                    navigatePageNums[i] = endNum--;\n                }\n            } else {\n                // 所有中间页\n                for (int i = 0; i < navigatePages; i++) {\n                    navigatePageNums[i] = startNum++;\n                }\n            }\n        }\n    }\n\n    /**\n     * 计算前后页，第一页，最后一页\n     */\n    private void calcPage() {\n        if (navigatePageNums != null && navigatePageNums.length > 0) {\n            navigateFirstPage = navigatePageNums[0];\n            navigateLastPage = navigatePageNums[navigatePageNums.length - 1];\n            if (pageNum > 1) {\n                prePage = pageNum - 1;\n            }\n            if (pageNum < pages) {\n                nextPage = pageNum + 1;\n            }\n        }\n    }\n\n    /**\n     * 判定页面边界\n     */\n    private void judgePageBoudary() {\n        firstPage = pageNum == 1;\n        lastPage = pageNum == pages;\n        hasPreviousPage = pageNum > 1;\n        hasNextPage = pageNum < pages;\n    }\n\n    /**\n     * 判断是否无数据\n     * \n     * @return\n     */\n    @Transient\n    public boolean isEmpty() {\n        return CollectionUtils.isEmpty(rows);\n    }\n\n    /**\n     * 处理\n     * @param action\n     */\n    public void forEach(Consumer<T> action) {\n        if (isEmpty()) {\n            return;\n        }\n        rows.forEach(action);\n    }\n\n    /**\n     * 转换\n     * \n     * @param mapper\n     * @return\n     */\n    public <E> Page<E> map(Function<T, E> mapper) {\n        Objects.requireNonNull(mapper);\n        Page<E> page = this.copy();\n        if (isEmpty()) {\n            return page;\n        }\n        page.setRows(rows.stream().map(mapper).collect(Collectors.toList()));\n        return page;\n    }\n\n    public <E> Page<E> copy() {\n        Page<E> page = new Page<>();\n        COPIER.copy(this, page, null);\n        return page;\n    }\n\n    @Override\n    public String toString() {\n        return new StringBuilder(280)\n            .append(getClass().getCanonicalName()).append(\"@\")\n            .append(Integer.toHexString(hashCode())).append(\"{\")\n            .append(\"pageNum=\").append(pageNum)\n            .append(\",pageSize=\").append(pageSize)\n            .append(\",size=\").append(size)\n            .append(\",startRow=\").append(startRow)\n            .append(\",endRow=\").append(endRow)\n            .append(\",total=\").append(total)\n            .append(\",pages=\").append(pages)\n            .append(\",rows=\").append(rowsToString())\n            .append(\",prePage=\").append(prePage)\n            .append(\",nextPage=\").append(nextPage)\n            .append(\",navigatePages=\").append(navigatePages)\n            .append(\",navigatePageNums=\").append(Arrays.toString(navigatePageNums))\n            .append(\"}\").toString();\n    }\n\n    // ------------------------------------------------------------------private methods\n    private String rowsToString() {\n        // GenericUtils.getFieldGenericType(ClassUtils.getField(Page.class, \"rows\"))\n        if (rows.isEmpty()) {\n            return \"List<>(0)\";\n        }\n\n        T row = rows.stream()/*.limit(10)*/.filter(Objects::nonNull).findAny().orElse(null);\n        if (row == null) {\n            return \"List<>(\" + rows.size() + \")\";\n        }\n\n        return \"List<\" + ClassUtils.getClassName(row.getClass()) + \">(\" + rows.size() + \")\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/PageBoundsResolver.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 分页解析器\n * \n * @author Ponfee\n */\npublic final class PageBoundsResolver {\n\n    private PageBoundsResolver() {}\n\n    /**\n     * 多个数据源查询结果分页\n     * \n     * @param pageNum 页号\n     * @param pageSize 页大小\n     * @param subTotalCounts 各数据源查询结果集行总计\n     * @return a list of page bounds\n     */\n    public static List<PageBounds> resolve(int pageNum, int pageSize, long... subTotalCounts) {\n        if (subTotalCounts == null || subTotalCounts.length == 0) {\n            return null;\n        }\n\n        // 总记录数\n        long totalCounts = 0;\n        for (long subTotalCount : subTotalCounts) {\n            totalCounts += subTotalCount;\n        }\n        if (totalCounts < 1) {\n            return null;\n        }\n\n        // pageSize小于1时表示查询全部\n        if (pageSize < 1) {\n            List<PageBounds> bounds = new ArrayList<>();\n            for (int i = 0; i < subTotalCounts.length; i++) {\n                // index, subTotalCounts, offset=0, limit=subTotalCounts\n                bounds.add(new PageBounds(i, subTotalCounts[i], 0, (int) subTotalCounts[i]));\n            }\n            return bounds;\n        }\n\n        // normalize pageNum, offset value\n        if (pageNum < 1) {\n            pageNum = 1;\n        }\n        long offset = PageHandler.computeOffset(pageNum, pageSize);\n        if (offset >= totalCounts) { // 超出总记录数，则取最后一页\n            pageNum = PageHandler.computeTotalPages(totalCounts, pageSize);\n            offset = PageHandler.computeOffset(pageNum, pageSize);\n        }\n\n        // 分页计算\n        List<PageBounds> bounds = new ArrayList<>();\n        long start = offset, end = start + pageSize, cursor = 0;\n        for (int limit, i = 0; i < subTotalCounts.length; cursor += subTotalCounts[i], i++) {\n            if (start >= cursor + subTotalCounts[i]) {\n                continue;\n            }\n\n            offset = start - cursor;\n            if (end > cursor + subTotalCounts[i]) {\n                limit = (int) (cursor + subTotalCounts[i] - start);\n                bounds.add(new PageBounds(i, subTotalCounts[i], offset, limit));\n                start = cursor + subTotalCounts[i];\n            } else {\n                limit = (int) (end - start);\n                bounds.add(new PageBounds(i, subTotalCounts[i], offset, limit));\n                break;\n            }\n        }\n        return bounds;\n    }\n\n    /**\n     * 单个数据源查询结果分页\n     * \n     * @param pageNum the page number\n     * @param pageSize the page size\n     * @param totalCounts the total counts\n     * @return a page bounds\n     */\n    public static PageBounds resolve(int pageNum, int pageSize, long totalCounts) {\n        List<PageBounds> list = resolve(pageNum, pageSize, new long[] { totalCounts });\n\n        if (list == null || list.isEmpty()) {\n            return null;\n        } else {\n            return list.get(0);\n        }\n    }\n\n    /**\n     * 分页对象\n     */\n    public static final class PageBounds {\n        private final int index; // 数据源下标（start 0）\n        private final long total; // 总记录数\n        private final long offset; // 偏移量（start 0）\n        private final int limit; // 数据行数\n\n        PageBounds(int index, long total, long offset, int limit) {\n            this.index = index;\n            this.total = total;\n            this.offset = offset;\n            this.limit = limit;\n        }\n\n        public int getIndex() {\n            return index;\n        }\n\n        public long getTotal() {\n            return total;\n        }\n\n        public long getOffset() {\n            return offset;\n        }\n\n        public int getLimit() {\n            return limit;\n        }\n\n        @Override\n        public String toString() {\n            return \"PageBounds [index=\" + index + \", total=\" + total \n                 + \", offset=\" + offset + \", limit=\" + limit + \"]\";\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/PageHandler.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.reflect.Fields;\nimport com.github.pagehelper.PageHelper;\nimport com.google.common.collect.ImmutableMap;\n\nimport javax.annotation.Nonnull;\nimport java.util.Dictionary;\nimport java.util.Map;\n\n/**\n * 分页参数处理类\n * 基于github上的mybatis分页工具\n * https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md\n * \n * @author Ponfee\n */\npublic final class PageHandler {\n\n    public static final String DEFAULT_PAGE_NUM = \"pageNum\";\n    public static final String DEFAULT_PAGE_SIZE = \"pageSize\";\n    public static final String DEFAULT_OFFSET = \"offset\";\n    public static final String DEFAULT_LIMIT = \"limit\";\n\n    public static final Map<String, Object> QUERY_ALL_PARAMS = ImmutableMap.of(\n        DEFAULT_PAGE_NUM, 1, DEFAULT_PAGE_SIZE, 0, // start page number is 1\n        DEFAULT_OFFSET, 0, DEFAULT_LIMIT, 0 // start offset is 0\n    );\n\n    public static final PageHandler NORMAL = new PageHandler(\n        DEFAULT_PAGE_NUM, DEFAULT_PAGE_SIZE, DEFAULT_OFFSET, DEFAULT_LIMIT\n    );\n    public static final int MAX_SIZE = 1000;\n\n    private final String paramPageNum;\n    private final String paramPageSize;\n    private final String paramOffset;\n    private final String paramLimit;\n\n    public PageHandler(String paramPageNum, String paramPageSize, \n                       String paramOffset, String paramLimit) {\n        this.paramPageNum = paramPageNum;\n        this.paramPageSize = paramPageSize;\n        this.paramOffset = paramOffset;\n        this.paramLimit = paramLimit;\n    }\n\n    /**\n     * Handles the page parameters\n     *  \n     * @param params the params\n     */\n    public void handle(@Nonnull Object params) {\n        Integer pageSize = getInt(params, paramPageSize);\n        Integer limit = getInt(params, paramLimit);\n\n        // 默认通过pageSize查询\n        if (nullOrNegative(pageSize) && nullOrNegative(limit)) {\n            pageSize = 0;\n        }\n\n        // 分页处理，pageSizeZero：默认值为false，当该参数设置为true时，如果pageSize=0或\n        // RowBounds.limit=0就会查询出全部的结果（相当于没有执行分页查询，但是返回结果仍然是Page类型）\n        if (pageSize != null && pageSize > -1) { // first priority use page size\n            startPage(\n                getInt(params, paramPageNum), Numbers.bounds(pageSize, 0, MAX_SIZE)\n            );\n        } else {\n            offsetPage(\n                getInt(params, paramOffset), Numbers.bounds(limit, 0, MAX_SIZE)\n            );\n        }\n    }\n\n    /**\n     * Page query with pageNum and pageSize\n     * pageSize=0时查询全部数据\n     * \n     * @param pageNum the pageNum\n     * @param pageSize the pageSize\n     */\n    public static void startPage(Integer pageNum, Integer pageSize) {\n        if (pageNum == null || pageNum < 1) {\n            pageNum = 1;\n        }\n        if (nullOrNegative(pageSize)) {\n            pageSize = 0;\n        }\n        PageHelper.startPage(pageNum, pageSize);\n    }\n\n    /**\n     * Page query with offset and limit\n     * RowBounds.limit=0则会查询出全部的结果\n     * \n     * @param offset the offset\n     * @param limit the limit\n     */\n    public static void offsetPage(Integer offset, Integer limit) {\n        if (nullOrNegative(offset)) {\n            offset = 0;\n        }\n        if (nullOrNegative(limit)) {\n            limit = 0;\n        }\n        PageHelper.offsetPage(offset, limit);\n    }\n\n    public static int computeTotalPages(long totalRecords, int pageSize) {\n        return pageSize == 0 ? 0 : (int) ((totalRecords + pageSize - 1) / pageSize);\n    }\n\n    public static int computePageNum(long offset, int limit) {\n        return limit == 0 ? 0 : (int) (offset / limit + 1);\n    }\n\n    public static int computeOffset(long pageNum, int pageSize) {\n        return (int) ((pageNum - 1) * pageSize);\n    }\n\n    /**\n     * Gets page number from java bean or map or dictionary\n     * \n     * @param params the params collect object\n     * @param name the param name\n     * @return a value of name\n     */\n    private static Integer getInt(Object params, String name) {\n        try {\n            Object value;\n            if (params instanceof Map) {\n                value = ((Map<?, ?>) params).get(name);\n            } else if (params instanceof Dictionary) {\n                value = ((Dictionary<?, ?>) params).get(name);\n            } else {\n                value = Fields.get(params, name); // as java bean\n            }\n            return Numbers.toWrapInt(value);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    private static boolean nullOrNegative(Integer val) {\n        return val == null || val < 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/PageParameter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport com.google.common.collect.ImmutableList;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static cn.ponfee.commons.model.PageHandler.*;\n\n/**\n * 分页请求参数封装类（不能继承Map，否则会被内置Map解析器优先处理）\n * \n * @see org.springframework.web.method.annotation.MapMethodProcessor#supportsParameter(org.springframework.core.MethodParameter)\n * @see org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)\n * \n * @author Ponfee\n */\npublic class PageParameter extends TypedParameter {\n\n    private static final long serialVersionUID = 6176654946390797217L;\n\n    public static final List<String> PAGE_PARAMS = ImmutableList.of(\n        DEFAULT_PAGE_NUM, DEFAULT_PAGE_SIZE, DEFAULT_OFFSET, DEFAULT_LIMIT\n    );\n\n    public static final String SORT_PARAM = \"sort\";\n\n    private static final String[] ORDER_DIRECTION = { \"ASC\", \"DESC\" };\n\n    private int pageNum  = -1;\n    private int pageSize = -1;\n\n    private int offset   = -1;\n    private int limit    = -1;\n\n    private String sort  = null;\n\n    // -----------------------------------------------------constructors\n    public PageParameter() {\n        this(new LinkedHashMap<>());\n    }\n\n    public PageParameter(int initialCapacity) {\n        this(new LinkedHashMap<>(initialCapacity));\n    }\n\n    public PageParameter(int initialCapacity, int loadFactor) {\n        this(new LinkedHashMap<>(initialCapacity, loadFactor));\n    }\n\n    public PageParameter(Map<String, Object> params) {\n        super(params);\n    }\n\n    // ----------------------------------------------------- methods\n    public PageParameter searchAll() {\n        this.setPageNum(1);\n        this.setPageSize(0);\n        this.setLimit(0);\n        this.setOffset(0);\n        return this;\n    }\n\n    // prevent sql inject\n    public void validateSort(String... allows) {\n        if (ArrayUtils.isEmpty(allows)) {\n            return;\n        }\n\n        String sort = this.getString(SORT_PARAM);\n        if (StringUtils.isBlank(sort)) {\n            return;\n        }\n\n        String[] orders = sort.split(\",\");\n        for (String order : orders) {\n            if (StringUtils.isBlank(order)) {\n                continue;\n            }\n\n            String[] array = order.trim()/*.replaceAll(\"\\\\s{2,}\", \" \")*/.split(\" \", 2);\n            if (   !ArrayUtils.contains(allows, array[0].trim())\n                || (array.length == 2 && !ArrayUtils.contains(ORDER_DIRECTION, array[1].trim().toUpperCase()))\n            ) {\n                throw new IllegalArgumentException(\"Illegal sort param: \" + sort);\n            }\n        }\n    }\n\n    // ----------------------------------------------page operators\n    public int getPageNum() {\n        return this.pageNum;\n    }\n\n    public void setPageNum(int pageNum) {\n        this.pageNum = pageNum;\n        this.put(DEFAULT_PAGE_NUM, pageNum);\n    }\n\n    public int getPageSize() {\n        return this.pageSize;\n    }\n\n    public void setPageSize(int pageSize) {\n        this.pageSize = pageSize;\n        this.put(DEFAULT_PAGE_SIZE, pageSize);\n    }\n\n    public int getOffset() {\n        return this.offset;\n    }\n\n    public void setOffset(int offset) {\n        this.offset = offset;\n        this.put(DEFAULT_OFFSET, offset);\n    }\n\n    public int getLimit() {\n        return this.limit;\n    }\n\n    public void setLimit(int limit) {\n        this.limit = limit;\n        this.put(DEFAULT_LIMIT, limit);\n    }\n\n    public String getSort() {\n        return this.sort;\n    }\n\n    public void setSort(String sort) {\n        this.sort = sort;\n        this.put(SORT_PARAM, sort);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/PaginationHtmlBuilder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.http.HttpParams;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.text.MessageFormat;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport static org.apache.commons.lang3.StringUtils.EMPTY;\n\n/**\n * Pagination html builder\n * \n * @author Ponfee\n */\npublic final class PaginationHtmlBuilder {\n\n    public static final String CDN_JQUERY = \"<script src=\\\"https://libs.baidu.com/jquery/2.1.4/jquery.min.js\\\"></script>\";\n    public static final String CDN_BASE64 = \"<script src=\\\"https://cdn.bootcss.com/Base64/1.1.0/base64.min.js\\\"></script>\";\n\n    private final String title;\n    private final String url;\n    private final int pageNum;\n    private final int pageSize;\n    private final long totalRecords;\n    private final int totalPages;\n\n    private String scripts = EMPTY;\n    private String form    = EMPTY;\n    private String table   = EMPTY;\n    private String params  = EMPTY;\n    private String foot    = EMPTY;\n\n    private PaginationHtmlBuilder(String title, String url, int pageNum, \n                                  int pageSize, long totalRecords, int totalPages) {\n        this.title = Optional.ofNullable(title).orElse(EMPTY);\n        this.url = Optional.ofNullable(url).orElse(EMPTY);\n        this.pageNum = pageNum;\n        this.pageSize = pageSize;\n        this.totalRecords = totalRecords;\n        this.totalPages = totalPages;\n    }\n\n    public static PaginationHtmlBuilder newBuilder(String title, String url, int pageNum, \n                                                   int pageSize, long totalRecords, int totalPages) {\n        return new PaginationHtmlBuilder(title, url, pageNum, pageSize, totalRecords, totalPages);\n    }\n\n    public static PaginationHtmlBuilder newBuilder(String title, String url, Page<?> page) {\n        return new PaginationHtmlBuilder(title, url, page.getPageNum(), \n                                         page.getPageSize(), page.getTotal(), page.getPages());\n    }\n\n    public PaginationHtmlBuilder scripts(String scripts) {\n        this.scripts = Optional.ofNullable(scripts).orElse(EMPTY);\n        return this;\n    }\n\n    public PaginationHtmlBuilder form(String form) {\n        this.form = Optional.ofNullable(form).orElse(EMPTY);\n        return this;\n    }\n\n    public PaginationHtmlBuilder table(String table) {\n        this.table = Optional.ofNullable(table).orElse(EMPTY);\n        return this;\n    }\n\n    public PaginationHtmlBuilder params(String params) {\n        this.params = Optional.ofNullable(params).orElse(EMPTY);\n        return this;\n    }\n\n    public PaginationHtmlBuilder params(Map<String, Object> params) {\n        params = new HashMap<>(params);\n        params.remove(PageHandler.DEFAULT_PAGE_NUM);\n        params.remove(PageHandler.DEFAULT_PAGE_SIZE);\n        return this.params(HttpParams.buildParams(params));\n    }\n\n    public PaginationHtmlBuilder params(PageParameter pageParams) {\n        return this.params(pageParams.params());\n    }\n\n    public PaginationHtmlBuilder foot(String foot) {\n        this.foot = Optional.ofNullable(foot).orElse(EMPTY);\n        return this;\n    }\n\n    public String build() {\n        return MessageFormat.format(\n            PAGINATION_HTML, \n            title,\n            scripts,\n            url,\n            form,\n            table,\n            buildPageArrow(url, pageNum - 1, pageSize, totalPages, params),\n            buildInputBox(PageHandler.DEFAULT_PAGE_NUM, pageNum),\n            buildPageArrow(url, pageNum + 1, pageSize, totalPages, params),\n            totalRecords,\n            totalPages,\n            buildInputBox(PageHandler.DEFAULT_PAGE_SIZE, pageSize),\n            foot\n        );\n    }\n\n    // -------------------------------------------------------------------------------\n    private static final String PAGINATION_HTML = new StringBuilder(8192)\n        .append(\"<!DOCTYPE html>                                                                   \\n\")\n        .append(\"<html>                                                                            \\n\")\n        .append(\"  <head lang=\\\"en\\\">                                                              \\n\")\n        .append(\"    <meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\" />     \\n\")\n        .append(\"    <title>{0}</title>                                                            \\n\")\n        .append(\"    '<style>                                                                      \\n\")\n        .append(\"      * {font-family: Microsoft YaHei;}                                           \\n\")\n        .append(\"      .grid {overflow-x: auto;background-color: #fff;color: #555;}                \\n\")\n        .append(\"      .grid table {                                                               \\n\")\n        .append(\"        width:100%;font-size:12px;border-collapse:collapse;border-style:hidden;   \\n\")\n        .append(\"      }                                                                           \\n\")\n        .append(\"      .grid table, div.grid table caption, div.grid table tr {                    \\n\")\n        .append(\"        border: 1px solid #6d6d6d;                                                \\n\")\n        .append(\"      }                                                                           \\n\")\n        .append(\"      .grid table tr td, div.grid table tr th {border: 1px solid #6d6d6d;}        \\n\")\n        .append(\"      .grid table caption {                                                       \\n\")\n        .append(\"        font-size:14px; padding:5px;                                              \\n\")\n        .append(\"        background:#e6e6fa; font-weight:bolder; border-bottom:none;               \\n\")\n        .append(\"      }                                                                           \\n\")\n        .append(\"      .grid table thead th {padding: 5px;background: #ccc;}                       \\n\")\n        .append(\"      .grid table td {text-align: center;padding: 3px;}                           \\n\")\n        .append(\"      .grid table td.text-left, .grid table th.text-left {text-align:left;}       \\n\")\n        .append(\"      .grid table td.text-right, .grid table th.text-right {text-align:right;}    \\n\")\n        .append(\"      .grid table td.text-center, .grid table th.text-center {text-align:center;} \\n\")\n        .append(\"      .grid table tfoot th {padding: 5px;}                                        \\n\")\n        .append(\"      .grid table tr:nth-child(odd) td{background:#fff;}                          \\n\")\n        .append(\"      .grid table tr:nth-child(even) td{background: #e8e8e8}                      \\n\")\n        .append(\"      .grid p.remark {font-size: 14px;}                                           \\n\")\n        .append(\"      .grid .nowrap {                                                             \\n\")\n        .append(\"        white-space:nowrap; word-break:keep-all;                                  \\n\")\n        .append(\"        overflow:hidden; text-overflow:ellipsis; max-width:200px;                 \\n\")\n        .append(\"      }                                                                           \\n\")\n        .append(\"                                                                                  \\n\")\n        .append(\"      .container {                                                                \\n\")\n        .append(\"        background:#fdfdfd; padding:1rem; margin:3rem auto;                       \\n\")\n        .append(\"        border-radius:0.2rem; counter-reset:page; text-align:center;              \\n\")\n        .append(\"      }                                                                           \\n\")\n        .append(\"      .container:after {clear:both; content:\\\"\\\"; display:table;}                 \\n\")\n        .append(\"      .container ul {width:100%;width:45rem;}                                     \\n\")\n        .append(\"      .page ul, li {list-style:none; display:inline; padding-left:0px;}           \\n\")\n        .append(\"      .page li {counter-increment:page;}                                          \\n\")\n        .append(\"      .page li:hover a, .page li.active a {                                       \\n\")\n        .append(\"        color:#fdfdfd; background-color:#1d1f20; border:solid 1px #1d1f20;        \\n\")\n        .append(\"      }                                                                           \\n\")\n        .append(\"      .page li:first-child a:after {content:\\\"<\\\";}                               \\n\")\n        .append(\"      .page li:nth-child(2) {counter-reset:page;}                                 \\n\")\n        .append(\"      .page li:last-child a:after {content:\\\"\\\";}                                 \\n\")\n        .append(\"      .page li a {                                                                \\n\")\n        .append(\"        border:solid 1px #d6d6d6; border-radius:0.2rem; color:#7d7d7d;            \\n\")\n        .append(\"        text-decoration:none; text-transform:uppercase; display:inline-block;     \\n\")\n        .append(\"        text-align:center; padding:0.5rem 0.9rem;                                 \\n\")\n        .append(\"      }                                                                           \\n\")\n        .append(\"      .page li              a       {display:none;}                               \\n\")\n        .append(\"      .page li:first-child  a       {display:inline-block;}                       \\n\")\n        .append(\"      .page li:first-child  a:after {content:\\\"<\\\";}                              \\n\")\n        .append(\"      .page li:nth-child(2) a       {display:inline-block;}                       \\n\")\n        .append(\"      .page li:nth-child(3) a       {display:inline-block;}                       \\n\")\n        .append(\"      .page li:nth-child(4) a       {display:inline-block;}                       \\n\")\n        .append(\"      .page li:nth-child(5) a       {display:inline-block;}                       \\n\")\n        .append(\"      .page li:nth-child(6) a       {display:inline-block;}                       \\n\")\n        .append(\"      .page li:nth-child(7) a       {display:inline-block;}                       \\n\")\n        .append(\"      .page li:nth-child(8) a       {display:inline-block;}                       \\n\")\n        .append(\"      .page li:last-child   a       {display:inline-block;}                       \\n\")\n        .append(\"      .page li:last-child   a:after {content:\\\">\\\";}                              \\n\")\n        .append(\"      .page li:nth-last-child(2) a  {display:inline-block;}                       \\n\")\n        .append(\"      .page li:nth-last-child(3)    {display:inline-block;}                       \\n\")\n        .append(\"    </style>'                                                                     \\n\")\n        .append(\"    {1}                                                                           \\n\")\n        .append(\"  </head>                                                                         \\n\")\n        .append(\"  <body>                                                                          \\n\")\n        .append(\"    <form method=\\\"GET\\\" name=\\\"search_form\\\" url=\\\"{2}\\\" style=\\\"padding:5px;\\\"> \\n\")\n        .append(\"      {3}                                                                         \\n\")\n        .append(\"      {4}                                                                         \\n\")\n        .append(\"      <div class=\\\"container\\\">                                                   \\n\")\n        .append(\"        <div class=\\\"page\\\">                                                      \\n\")\n        .append(\"          <ul>                                                                    \\n\")\n        .append(\"            {5}                                                                   \\n\")\n        .append(\"            <a href=\\\"javascript:void(0)\\\">{6}</a>                                \\n\")\n        .append(\"            {7}                                                                   \\n\")\n        .append(\"          </ul>                                                                   \\n\")\n        .append(\"          ([ <b>{8}</b> ]records, [ <b>{9}</b> ]pages, [{10}]records/page)        \\n\")\n        .append(\"        </div>                                                                    \\n\")\n        .append(\"      </div>                                                                      \\n\")\n        .append(\"    </form>                                                                       \\n\")\n        .append(\"    {11}                                                                          \\n\")\n        .append(\"  </body>                                                                         \\n\")\n        .append(\"</html>                                                                           \\n\")\n        .toString().replaceAll(\"\\\\s+\\n\", \"\\n\");\n\n    // -------------------------------------------------------------------------------\n    private static final String INPUT_BOX = \n        \"<input type=\\\"text\\\" name=\\\"{0}\\\" value=\\\"{1}\\\" style=\\\"width:40px;height:32px;text-align:center;margin:5px;font-weight:bold;\\\"/>\";\n    private static String buildInputBox(String name, Object value) {\n        return MessageFormat.format(INPUT_BOX, name, value);\n    }\n\n    // -------------------------------------------------------------------------------\n    private static final String PAGE_ARROW = \n        \"<li><a href=\\\"{0}?pageNum={1}&pageSize={2}{3}\\\"></a></li>\";\n    private static String buildPageArrow(String url, int pageNum, int pageSize, \n                                         int totalPages, String params) {\n        if (pageNum < 1 || pageNum > totalPages) {\n            return EMPTY;\n        }\n        params = StringUtils.isBlank(params) ? \"\" : \"&\" + params;\n        return MessageFormat.format(PAGE_ARROW, url, pageNum, pageSize, params);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/RemovableTypedKeyValue.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.math.Numbers;\n\nimport java.util.Objects;\n\n/**\n * Removable typed dictionary key-value\n *\n * @param <K> the key type\n * @param <V> the value type\n * @author Ponfee\n */\npublic interface RemovableTypedKeyValue<K, V> extends TypedKeyValue<K, V> {\n\n    V removeKey(K key);\n\n    default String removeString(K key) {\n        return removeString(key, null);\n    }\n\n    default String removeString(K key, String defaultVal) {\n        return Objects.toString(removeKey(key), defaultVal);\n    }\n\n    default boolean removeBoolean(K key, boolean defaultValue) {\n        return Numbers.toBoolean(removeKey(key), defaultValue);\n    }\n\n    default Boolean removeBoolean(K key) {\n        return Numbers.toWrapBoolean(removeKey(key));\n    }\n\n    default int removeInt(K key, int defaultValue) {\n        return Numbers.toInt(removeKey(key), defaultValue);\n    }\n\n    default Integer removeInt(K key) {\n        return Numbers.toWrapInt(removeKey(key));\n    }\n\n    default long removeLong(K key, long defaultValue) {\n        return Numbers.toLong(removeKey(key), defaultValue);\n    }\n\n    default Long removeLong(K key) {\n        return Numbers.toWrapLong(removeKey(key));\n    }\n\n    default float removeFloat(K key, float defaultValue) {\n        return Numbers.toFloat(removeKey(key), defaultValue);\n    }\n\n    default Float removeFloat(K key) {\n        return Numbers.toWrapFloat(removeKey(key));\n    }\n\n    default double removeDouble(K key, double defaultValue) {\n        return Numbers.toDouble(removeKey(key), defaultValue);\n    }\n\n    default Double removeDouble(K key) {\n        return Numbers.toWrapDouble(removeKey(key));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/RemovableTypedMap.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.util.Map;\n\n/**\n * Removable typed for {@link Map}\n *\n * @param <K> the key type\n * @param <V> the value type\n * @author Ponfee\n */\npublic interface RemovableTypedMap<K, V> extends Map<K, V>, RemovableTypedKeyValue<K, V> {\n\n    @Override\n    default V getValue(K key) {\n        return this.get(key);\n    }\n\n    @Override\n    default V removeKey(K key) {\n        return this.remove(key);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/Result.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.beans.Transient;\nimport java.util.function.Function;\n\n/**\n * Representing the result-data structure\n *\n * @see org.springframework.http.ResponseEntity#status(org.springframework.http.HttpStatus)\n *\n * @param <T> the data type\n * @author Ponfee\n */\npublic class Result<T> extends ToJsonString implements CodeMsg, java.io.Serializable {\n\n    private static final long serialVersionUID = -2804195259517755050L;\n\n    private Integer    code; // 状态码\n    private boolean success; // 是否成功\n    private String      msg; // 返回信息\n    private T          data; // 结果数据\n\n    // -----------------------------------------------constructor methods\n\n    public Result() {\n        // No operation: retain no-arg constructor for help deserialization\n    }\n\n    public Result(int code, boolean success, String msg, T data) {\n        this.code = code;\n        this.success = success;\n        this.msg = msg;\n        this.data = data;\n    }\n\n    // -----------------------------------------------others methods\n\n    @SuppressWarnings(\"unchecked\")\n    public <E> Result<E> cast() {\n        return (Result<E>) this;\n    }\n\n    public <E> Result<E> from(E data) {\n        return new Result<>(code, success, msg, data);\n    }\n\n    public <E> Result<E> map(Function<T, E> mapper) {\n        return new Result<>(code, success, msg, mapper.apply(data));\n    }\n\n    // -----------------------------------------------static success methods\n\n    /**\n     * Returns success 200 code\n     *\n     * @return Result success object\n     */\n    public static Result<Void> success() {\n        return Success.INSTANCE;\n    }\n\n    /**\n     * Returns success 200 code\n     *\n     * @param data the data\n     * @param <T>  the data type\n     * @return Result success object\n     */\n    public static <T> Result<T> success(T data) {\n        return new Result<>(Success.CODE, true, Success.MSG, data);\n    }\n\n    // -----------------------------------------------static failure methods\n\n    public static <T> Result<T> failure(CodeMsg cm) {\n        if (cm.isSuccess()) {\n            throw new IllegalStateException(\"Failure state must be 'false'.\");\n        }\n        return new Result<>(cm.getCode(), cm.isSuccess(), cm.getMsg(), null);\n    }\n\n    public static <T> Result<T> failure(int code) {\n        return new Result<>(code, false, null, null);\n    }\n\n    public static <T> Result<T> failure(int code, String msg) {\n        return new Result<>(code, false, msg, null);\n    }\n\n    // -----------------------------------------------of operations\n\n    public static <T> Result<T> of(CodeMsg cm) {\n        return new Result<>(cm.getCode(), cm.isSuccess(), cm.getMsg(), null);\n    }\n\n    public static <T> Result<T> of(CodeMsg cm, T data) {\n        return new Result<>(cm.getCode(), cm.isSuccess(), cm.getMsg(), data);\n    }\n\n    public static <T> Result<T> of(int code, boolean success, String msg) {\n        return new Result<>(code, success, msg, null);\n    }\n\n    public static <T> Result<T> of(int code, boolean success, String msg, T data) {\n        return new Result<>(code, success, msg, data);\n    }\n\n    // -----------------------------------------------database update or delete affected rows\n\n    public static <T> Result<T> assertAffectedOne(int actualAffectedRows) {\n        return assertAffectedRows(actualAffectedRows, 1);\n    }\n\n    public static <T> Result<T> assertAffectedRows(int actualAffectedRows, int exceptAffectedRows) {\n        return actualAffectedRows == exceptAffectedRows ? (Result<T>) Success.INSTANCE : failure(ResultCode.OPS_CONFLICT);\n    }\n\n    public static <T> Result<T> assertOperatedState(boolean state) {\n        return state ? (Result<T>) Success.INSTANCE : failure(ResultCode.OPS_CONFLICT);\n    }\n\n    // -----------------------------------------------getter/setter\n\n    @Override\n    public int getCode() {\n        return code;\n    }\n\n    @Override\n    public boolean isSuccess() {\n        return success;\n    }\n\n    @Transient\n    public boolean isFailure() {\n        return !success;\n    }\n\n    @Override\n    public String getMsg() {\n        return msg;\n    }\n\n    public T getData() {\n        return data;\n    }\n\n    public void setCode(int code) {\n        this.code = code;\n    }\n\n    public void setSuccess(boolean success) {\n        this.success = success;\n    }\n\n    public void setMsg(String msg) {\n        this.msg = msg;\n    }\n\n    public void setData(T data) {\n        this.data = data;\n    }\n\n    // -----------------------------------------------static class\n\n    /**\n     * Success result\n     */\n    private static final class Success extends Result<Void> {\n        private static final long serialVersionUID = 6740650053476768729L;\n        private static final int CODE = ResultCode.OK.getCode();\n        private static final String MSG = \"OK\";\n        private static final Result<Void> INSTANCE = new Success();\n\n        private Success() {\n            super(CODE, true, MSG, null);\n        }\n\n        @Override\n        public void setCode(int code) {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override\n        public void setMsg(String msg) {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override\n        public void setData(Void data) {\n            throw new UnsupportedOperationException();\n        }\n\n        private Object readResolve() {\n            return INSTANCE;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/ResultCode.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.http.HttpStatus;\n\nimport static java.net.HttpURLConnection.*;\n\n/**\n * <pre>\n * Http response code: {@link java.net.HttpURLConnection#HTTP_OK}\n *  100 => \"HTTP/1.1 100 Continue\",\n *  101 => \"HTTP/1.1 101 Switching Protocols\",\n *  200 => \"HTTP/1.1 200 OK\",\n *  201 => \"HTTP/1.1 201 Created\",\n *  202 => \"HTTP/1.1 202 Accepted\",\n *  203 => \"HTTP/1.1 203 Non-Authoritative Information\",\n *  204 => \"HTTP/1.1 204 No Content\",\n *  205 => \"HTTP/1.1 205 Reset Content\",\n *  206 => \"HTTP/1.1 206 Partial Content\",\n *  300 => \"HTTP/1.1 300 Multiple Choices\",\n *  301 => \"HTTP/1.1 301 Moved Permanently\",\n *  302 => \"HTTP/1.1 302 Found\",\n *  303 => \"HTTP/1.1 303 See Other\",\n *  304 => \"HTTP/1.1 304 Not Modified\",\n *  305 => \"HTTP/1.1 305 Use Proxy\",\n *  307 => \"HTTP/1.1 307 Temporary Redirect\",\n *  400 => \"HTTP/1.1 400 Bad Request\",\n *  401 => \"HTTP/1.1 401 Unauthorized\",\n *  402 => \"HTTP/1.1 402 Payment Required\",\n *  403 => \"HTTP/1.1 403 Forbidden\",\n *  404 => \"HTTP/1.1 404 Not Found\",\n *  405 => \"HTTP/1.1 405 Method Not Allowed\",\n *  406 => \"HTTP/1.1 406 Not Acceptable\",\n *  407 => \"HTTP/1.1 407 Proxy Authentication Required\",\n *  408 => \"HTTP/1.1 408 Request Time-out\",\n *  409 => \"HTTP/1.1 409 Conflict\",\n *  410 => \"HTTP/1.1 410 Gone\",\n *  411 => \"HTTP/1.1 411 Length Required\",\n *  412 => \"HTTP/1.1 412 Precondition Failed\",\n *  413 => \"HTTP/1.1 413 Request Entity Too Large\",\n *  414 => \"HTTP/1.1 414 Request-URI Too Large\",\n *  415 => \"HTTP/1.1 415 Unsupported Media Type\",\n *  416 => \"HTTP/1.1 416 Requested range not satisfiable\",\n *  417 => \"HTTP/1.1 417 Expectation Failed\",\n *  500 => \"HTTP/1.1 500 Internal Server Error\",\n *  501 => \"HTTP/1.1 501 Not Implemented\",\n *  502 => \"HTTP/1.1 502 Bad Gateway\",\n *  503 => \"HTTP/1.1 503 Service Unavailable\",\n *  504 => \"HTTP/1.1 504 Gateway Time-out\"\n * \n * 2开头-请求成功-表示成功处理了请求的状态代码。\n *  200 (成功)       服务器已成功处理了请求。通常，这表示服务器提供了请求的网页。\n *  201 (已创建)     请求成功并且服务器创建了新的资源。\n *  202 (已接受)     服务器已接受请求，但尚未处理。\n *  203 (非授权信息)  服务器已成功处理了请求，但返回的信息可能来自另一来源。\n *  204 (无内容)     服务器成功处理了请求，但没有返回任何内容。\n *  205 (重置内容)   没有新的内容，但浏览器应该重置它所显示的内容(用来强制浏览器清除表单输入内容)。\n *  206 (部分内容)   服务器成功处理了部分GET请求。\n * \n * 3开头 (请求被重定向)表示要完成请求，需要进一步操作。通常，这些状态代码用来重定向。\n *  300   (多种选择)  针对请求，服务器可执行多种操作。服务器可根据请求者 (user agent) 选择一项操作，或提供操作列表供请求者选择。\n *  301   (永久移动)  请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时，会自动将请求者转到新位置。\n *  302   (临时移动)  服务器目前从不同位置的网页响应请求，但请求者应继续使用原有位置来进行以后的请求。\n *  303   (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时，服务器返回此代码。\n *  304   (未修改) 自从上次请求后，请求的网页未修改过。服务器返回此响应时，不会返回网页内容。\n *  305   (使用代理) 请求者只能使用代理访问请求的网页。如果服务器返回此响应，还表示请求者应使用代理。\n *  307   (临时重定向)  服务器目前从不同位置的网页响应请求，但请求者应继续使用原有位置来进行以后的请求。\n * \n * 4开头 (请求错误)这些状态代码表示请求可能出错，妨碍了服务器的处理。\n *  400   (错误请求) 服务器不理解请求的语法。\n *  401   (未授权) 请求要求身份验证。对于需要登录的网页，服务器可能返回此响应。\n *  403   (禁止) 服务器拒绝请求。\n *  404   (未找到) 服务器找不到请求的网页。\n *  405   (方法禁用) 禁用请求中指定的方法。\n *  406   (不接受) 无法使用请求的内容特性响应请求的网页。\n *  407   (需要代理授权) 此状态代码与 401(未授权)类似，但指定请求者应当授权使用代理。\n *  408   (请求超时)  服务器等候请求时发生超时。\n *  409   (冲突)  服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息。\n *  410   (已删除)  如果请求的资源已永久删除，服务器就会返回此响应。\n *  411   (需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。\n *  412   (未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。\n *  413   (请求实体过大) 服务器无法处理请求，因为请求实体过大，超出服务器的处理能力。\n *  414   (请求的 URI 过长) 请求的 URI(通常为网址)过长，服务器无法处理。\n *  415   (不支持的媒体类型) 请求的格式不受请求页面的支持。\n *  416   (请求范围不符合要求) 如果页面无法提供请求的范围，则服务器会返回此状态代码。\n *  417   (未满足期望值) 服务器未满足\"期望\"请求标头字段的要求。\n * \n * 5开头(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误，而不是请求出错。\n *  500   (服务器内部错误)  服务器遇到错误，无法完成请求。\n *  501   (尚未实施) 服务器不具备完成请求的功能。例如，服务器无法识别请求方法时可能会返回此代码。\n *  502   (错误网关) 服务器作为网关或代理，从上游服务器收到无效响应。\n *  503   (服务不可用) 服务器目前无法使用(由于超载或停机维护)。通常，这只是暂时状态。\n *  504   (网关超时)  服务器作为网关或代理，但是没有及时从上游服务器收到请求。\n *  505   (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。\n *\n * 公用错误码区间[000 ~ 999]\n * </pre>\n * \n * @see org.springframework.http.HttpStatus\n * @see cn.ponfee.commons.http.HttpStatus\n * @see java.net.HttpURLConnection#HTTP_OK\n *  \n * @author Ponfee\n */\npublic final class ResultCode implements CodeMsg {\n\n    private static final int SYS_CODE_MIN = 100;\n    private static final int SYS_CODE_MAX = 599;\n    private static final String SYS_ERROR = \"Must in predefine reserved code [\" + SYS_CODE_MIN + \", \" + SYS_CODE_MAX + \"]\";\n    private static final String BIZ_ERROR = \"Cannot defined in reserved code [\" + SYS_CODE_MIN + \", \" + SYS_CODE_MAX + \"]\";\n\n    /** 公用结果码 */\n    public static final ResultCode OK                 = of0(HTTP_OK,                \"OK\");\n    public static final ResultCode CREATED            = of0(HTTP_CREATED,           \"已创建\"); // POST\n    public static final ResultCode ACCEPTED           = of0(HTTP_ACCEPTED,          \"已接受，等待处理\");\n    public static final ResultCode NOT_AUTHORITATIVE  = of0(HTTP_NOT_AUTHORITATIVE, \"非授权信息\");\n    public static final ResultCode NO_CONTENT         = of0(HTTP_NO_CONTENT,        \"已处理，无返回内容\"); // PUT, PATCH, DELETE\n    public static final ResultCode REST_CONTENT       = of0(HTTP_RESET,             \"重置内容\");\n    public static final ResultCode PARTIAL_CONTENT    = of0(HTTP_PARTIAL,           \"部分内容\");\n\n    public static final ResultCode REDIRECT           = of0(300, \"Multiple Choices\");\n\n    public static final ResultCode BAD_REQUEST        = of0(400, \"参数错误\");\n    public static final ResultCode UNAUTHORIZED       = of0(401, \"未授权\");     // \n    public static final ResultCode FORBIDDEN          = of0(403, \"拒绝访问\");   // BLACKLIST\n    public static final ResultCode NOT_FOUND          = of0(404, \"资源未找到\"); // GET return null\n    public static final ResultCode NOT_ALLOWED        = of0(405, \"方法不允许\");\n    public static final ResultCode NOT_ACCEPTABLE     = of0(406, \"请求格式错误\");\n    public static final ResultCode REQUEST_TIMEOUT    = of0(408, \"请求超时\");\n    public static final ResultCode OPS_CONFLICT       = of0(409, \"数据不存在或版本冲突\"); // DATABASE UPDATE DELETE FAIL\n    public static final ResultCode UNSUPPORT_MEDIA    = of0(415, \"格式不支持\");\n\n    public static final ResultCode SERVER_ERROR       = of0(500, \"服务器错误\");\n    public static final ResultCode BAD_GATEWAY        = of0(502, \"网关错误\");\n    public static final ResultCode SERVER_UNAVAILABLE = of0(503, \"服务不可用\");\n    public static final ResultCode GATEWAY_TIMEOUT    = of0(504, \"网关超时\");\n    public static final ResultCode SERVER_UNSUPPORTED = of0(505, \"服务不支持\");\n\n    private final int code;\n    private final boolean success;\n    private final String msg;\n\n    private ResultCode(int code, String msg) {\n        this.code = code;\n        this.success = HttpStatus.Series.valueOf(code) == HttpStatus.Series.SUCCESSFUL;\n        this.msg = msg;\n    }\n\n    /**\n     * inner create, only call in this class\n     * use in assign the commons code \n     * @param code\n     * @param msg\n     * @return\n     */\n    private static ResultCode of0(int code, String msg) {\n        if (code < SYS_CODE_MIN || code > SYS_CODE_MAX) {\n            throw new IllegalArgumentException(SYS_ERROR);\n        }\n        return new ResultCode(code, msg);\n    }\n\n    /**\n     * others place cannot set the code in commons code range[000 ~ 999]\n     * \n     * @param code\n     * @param msg\n     * @return\n     */\n    public static ResultCode of(int code, String msg) {\n        if (code >= SYS_CODE_MIN && code <= SYS_CODE_MAX) {\n            throw new IllegalArgumentException(BIZ_ERROR);\n        }\n        return new ResultCode(code, msg);\n    }\n\n    @Override\n    public int getCode() {\n        return code;\n    }\n\n    @Override\n    public boolean isSuccess() {\n        return success;\n    }\n\n    @Override\n    public String getMsg() {\n        return msg;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/SearchAfter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\n/**\n * Search after\n * \n * @author Ponfee\n */\npublic class SearchAfter<T> implements java.io.Serializable {\n\n    private static final long serialVersionUID = 4870755106055211046L;\n\n    private final SortField sortField;\n    private final T value;\n\n    public SearchAfter(SortField sortField, T value) {\n        this.sortField = sortField;\n        this.value = value;\n    }\n\n    public SortField getSortField() {\n        return sortField;\n    }\n\n    public T getValue() {\n        return value;\n    }\n\n    public SearchAfter<T> copy(T value) {\n        return new SearchAfter<>(this.sortField, value);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/SortField.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.model.SortOrder.NullHandling;\n\n/**\n * SortOrder and Field\n * \n * @author Ponfee\n */\npublic class SortField implements java.io.Serializable {\n\n    private static final long serialVersionUID = -2400506091734529951L;\n\n    private final String field;\n    private final SortOrder sortOrder;\n    private final boolean ignoreCase;\n    private final NullHandling nullHandling;\n\n    public SortField(String field, SortOrder sortOrder) {\n        this(field, sortOrder, false, null);\n    }\n\n    public SortField(String field, SortOrder sortOrder, \n                     boolean ignoreCase, NullHandling nullHandling) {\n        this.field = field;\n        this.sortOrder = sortOrder;\n        this.ignoreCase = ignoreCase;\n        this.nullHandling = nullHandling;\n    }\n\n    public String getField() {\n        return field;\n    }\n\n    public SortOrder getSortOrder() {\n        return sortOrder;\n    }\n\n    public boolean isIgnoreCase() {\n        return ignoreCase;\n    }\n\n    public NullHandling getNullHandling() {\n        return nullHandling;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/SortOrder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\n/**\n * Query Order by\n * \n * @author Ponfee\n */\npublic enum SortOrder {\n\n    ASC, DESC;\n\n    public static SortOrder of(String name) {\n        return \"ASC\".equalsIgnoreCase(name) ? ASC : DESC;\n    }\n\n    public enum NullHandling {\n        NATIVE, NULLS_FIRST, NULLS_LAST\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/ToJsonString.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.json.Jsons;\n\n/**\n * Override {@code Object#toString()} method, implemented to json string.\n *\n * @author Ponfee\n */\npublic abstract class ToJsonString {\n\n    @Override\n    public String toString() {\n        return Jsons.toJson(this);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/TypedHashMap.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Typed {@link HashMap} with pre-defined get methods\n * \n * @author Ponfee\n */\npublic class TypedHashMap<K, V> extends HashMap<K, V> implements TypedMap<K, V> {\n\n    private static final long serialVersionUID = -4207327688392334942L;\n\n    public TypedHashMap() {\n        super();\n    }\n\n    public TypedHashMap(int initialCapacity) {\n        super(initialCapacity);\n    }\n\n    public TypedHashMap(Map<? extends K, ? extends V> m) {\n        super(m);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/TypedKeyValue.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport cn.ponfee.commons.math.Numbers;\n\nimport java.util.Objects;\nimport java.util.function.Function;\n\n/**\n * Get the value with typed for dictionary key-value\n *\n * @param <K> the key type\n * @param <V> the value type\n * @author Ponfee\n */\npublic interface TypedKeyValue<K, V> {\n\n    V getValue(K key);\n\n    default boolean hasKey(K key) {\n        return getValue(key) != null;\n    }\n\n    // --------------------------------------------------------object\n\n    default V getRequired(K key) {\n        return getRequired(key, Function.identity());\n    }\n\n    default V get(K key, V defaultVal) {\n        V value = getValue(key);\n        return value == null ? defaultVal : value;\n    }\n\n    // --------------------------------------------------------string\n    default String getRequiredString(K key) {\n        return getRequired(key, Object::toString);\n    }\n\n    default String getString(K key) {\n        return getString(key, null);\n    }\n\n    default String getString(K key, String defaultVal) {\n        return Objects.toString(getValue(key), defaultVal);\n    }\n\n    // --------------------------------------------------------boolean\n    default boolean getRequiredBoolean(K key) {\n        Object value = getValue(key);\n        if (value instanceof Boolean) {\n            return (boolean) value;\n        }\n        if (value == null) {\n            throw new IllegalArgumentException(\"Not presented value of '\" + key + \"'\");\n        }\n        switch (value.toString()) {\n            case \"TRUE\" : case \"True\" : case \"true\" : return true;\n            case \"FALSE\": case \"False\": case \"false\": return false;\n            default: throw new IllegalArgumentException(\"Invalid boolean value: \" + value);\n        }\n    }\n\n    default boolean getBoolean(K key, boolean defaultValue) {\n        return Numbers.toBoolean(getValue(key), defaultValue);\n    }\n\n    default Boolean getBoolean(K key) {\n        return Numbers.toWrapBoolean(getValue(key));\n    }\n\n    // --------------------------------------------------------------int\n    default int getRequiredInt(K key) {\n        return getRequired(key, Numbers::toInt);\n    }\n\n    default int getInt(K key, int defaultValue) {\n        return Numbers.toInt(getValue(key), defaultValue);\n    }\n\n    default Integer getInt(K key) {\n        return Numbers.toWrapInt(getValue(key));\n    }\n\n    // --------------------------------------------------------------long\n    default long getRequiredLong(K key) {\n        return getRequired(key, Numbers::toLong);\n    }\n\n    default long getLong(K key, long defaultValue) {\n        return Numbers.toLong(getValue(key), defaultValue);\n    }\n\n    default Long getLong(K key) {\n        return Numbers.toWrapLong(getValue(key));\n    }\n\n    // --------------------------------------------------------------float\n    default float getRequiredFloat(K key) {\n        return getRequired(key, Numbers::toFloat);\n    }\n\n    default float getFloat(K key, float defaultValue) {\n        return Numbers.toFloat(getValue(key), defaultValue);\n    }\n\n    default Float getFloat(K key) {\n        return Numbers.toWrapFloat(getValue(key));\n    }\n\n    // --------------------------------------------------------------double\n    default double getRequiredDouble(K key) {\n        return getRequired(key, Numbers::toDouble);\n    }\n\n    default double getDouble(K key, double defaultValue) {\n        return Numbers.toDouble(getValue(key), defaultValue);\n\n    }\n\n    default Double getDouble(K key) {\n        return Numbers.toWrapDouble(getValue(key));\n    }\n\n    // ---------------------------------------------------- methods\n    default <R> R getRequired(K key, Function<V, R> mapper) {\n        V value = getValue(key);\n        if (value == null) {\n            throw new IllegalArgumentException(\"Not presented value of '\" + key + \"'\");\n        }\n        return mapper.apply(value);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/TypedLinkedHashMap.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * Typed {@link LinkedHashMap} with pre-defined get methods\n * \n * @author Ponfee\n */\npublic class TypedLinkedHashMap<K, V> extends LinkedHashMap<K, V> implements TypedMap<K, V> {\n\n    private static final long serialVersionUID = -4207327688392334942L;\n\n    public TypedLinkedHashMap() {\n        super();\n    }\n\n    public TypedLinkedHashMap(int initialCapacity) {\n        super(initialCapacity);\n    }\n\n    public TypedLinkedHashMap(Map<? extends K, ? extends V> m) {\n        super(m);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/TypedLinkedMultiValueMap.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.springframework.util.LinkedMultiValueMap;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Typed {@link LinkedMultiValueMap} with pre-defined get methods\n * \n * @author Ponfee\n */\npublic class TypedLinkedMultiValueMap<K, V> extends LinkedMultiValueMap<K, V> implements RemovableTypedKeyValue<K, V> {\n\n    private static final long serialVersionUID = 4369022038293264189L;\n\n    public TypedLinkedMultiValueMap() {\n        super();\n    }\n\n    public TypedLinkedMultiValueMap(int initialCapacity) {\n        super(initialCapacity);\n    }\n\n    public TypedLinkedMultiValueMap(Map<K, List<V>> otherMap) {\n        super(otherMap);\n    }\n\n    @Override\n    public V getValue(K key) {\n        return getFirst(key);\n    }\n\n    @Override\n    public V removeKey(K key) {\n        List<V> values = remove(key);\n        return CollectionUtils.isEmpty(values) ? null : values.get(0);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/TypedMap.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.util.Map;\n\n/**\n * Get the value with typed for {@link Map}\n * \n * @author Ponfee\n * @param <K> the key type\n * @param <V> the value type\n */\npublic interface TypedMap<K, V> extends Map<K, V>, TypedKeyValue<K, V> {\n\n    @Override\n    default V getValue(K key) {\n        return this.get(key);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/TypedMapWrapper.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.io.Serializable;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * Wrapped the {@link Map} for with gets typed value trait\n *\n * @param <K>\n * @param <V>\n * @author Ponfee\n */\npublic class TypedMapWrapper<K, V> implements TypedMap<K, V>, Serializable, Cloneable {\n\n    private static final long serialVersionUID = 6899012847958938043L;\n\n    private final Map<K, V> target;\n\n    public TypedMapWrapper(Map<K, V> otherMap) {\n        this.target = otherMap == null ? Collections.emptyMap() : otherMap;\n    }\n\n    @Override\n    public int size() {\n        return this.target.size();\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return this.target.isEmpty();\n    }\n\n    @Override\n    public boolean containsKey(Object key) {\n        return this.target.containsKey(key);\n    }\n\n    @Override\n    public boolean containsValue(Object value) {\n        return this.target.containsValue(value);\n    }\n\n    @Override\n    public V get(Object key) {\n        return this.target.get(key);\n    }\n\n    @Override\n    public V put(K key, V value) {\n        return this.target.put(key, value);\n    }\n\n    @Override\n    public V remove(Object key) {\n        return this.target.remove(key);\n    }\n\n    @Override\n    public void putAll(Map<? extends K, ? extends V> m) {\n        this.target.putAll(m);\n    }\n\n    @Override\n    public void clear() {\n        this.target.clear();\n    }\n\n    @Override\n    public Set<K> keySet() {\n        return this.target.keySet();\n    }\n\n    @Override\n    public Collection<V> values() {\n        return this.target.values();\n    }\n\n    @Override\n    public Set<Entry<K, V>> entrySet() {\n        return this.target.entrySet();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return this.target.equals(obj);\n    }\n\n    @Override\n    public int hashCode() {\n        return this.target.hashCode();\n    }\n\n    @Override\n    public String toString() {\n        return this.target.toString();\n    }\n\n    public static <K, V> TypedMapWrapper<K, V> empty() {\n        return new TypedMapWrapper<>(Collections.emptyMap());\n    }\n\n    @Override\n    public TypedMapWrapper<K, V> clone() {\n        return new TypedMapWrapper<>(this.target);\n    }\n\n    public TypedMapWrapper<K, V> copy() {\n        return new TypedMapWrapper<>(target.entrySet().stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue)));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/model/TypedParameter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.model;\n\nimport java.util.Map;\n\n/**\n * 通用Map请求参数类（不继承Map是为了阻断被内置的Map解析器先一步处理）\n * \n * @author Ponfee\n */\npublic class TypedParameter implements RemovableTypedKeyValue<String, Object>, java.io.Serializable {\n\n    private static final long serialVersionUID = 1898625104491344717L;\n\n    private final Map<String, Object> params;\n\n    public TypedParameter(Map<String, Object> params) {\n        this.params = params;\n    }\n\n    public Object put(String key, Object value) {\n        return this.params.put(key, value);\n    }\n\n    @Override\n    public Object getValue(String key) {\n        return this.params.get(key);\n    }\n\n    @Override\n    public Object removeKey(String key) {\n        return this.params.remove(key);\n    }\n\n    public Map<String, Object> params() {\n        return this.params;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/mybatis/MultipleSqlSessionTemplate.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.mybatis;\n\nimport cn.ponfee.commons.data.lookup.MultipleDataSourceContext;\nimport org.apache.ibatis.cursor.Cursor;\nimport org.apache.ibatis.exceptions.PersistenceException;\nimport org.apache.ibatis.executor.BatchResult;\nimport org.apache.ibatis.session.*;\nimport org.mybatis.spring.MyBatisExceptionTranslator;\nimport org.mybatis.spring.SqlSessionTemplate;\nimport org.springframework.dao.support.PersistenceExceptionTranslator;\n\nimport java.lang.reflect.Proxy;\nimport java.sql.Connection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;\nimport static org.mybatis.spring.SqlSessionUtils.*;\n\n/**\n * Mutiple datasource for SqlSessionTemplate\n * \n * @author Ponfee\n */\npublic class MultipleSqlSessionTemplate extends SqlSessionTemplate {\n\n    private final SqlSessionFactory defaultTargetSqlSessionFactory;\n    private final ExecutorType defaultTargetExecutorType;\n    private final PersistenceExceptionTranslator defaultTargetExceptionTranslator;\n\n    private final Map<Object, SqlSessionFactory> targetSqlSessionFactories;\n \n    private final SqlSession sqlSessionProxy;\n\n    public MultipleSqlSessionTemplate(SqlSessionFactory defaultTargetSqlSessionFactory, \n                                      Map<Object, SqlSessionFactory> targetSqlSessionFactories) {\n        super(defaultTargetSqlSessionFactory);\n\n        this.targetSqlSessionFactories = Objects.requireNonNull(targetSqlSessionFactories);\n\n        this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;\n        this.defaultTargetExecutorType = defaultTargetSqlSessionFactory.getConfiguration().getDefaultExecutorType();\n        this.defaultTargetExceptionTranslator = new MyBatisExceptionTranslator(\n            defaultTargetSqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true\n        );\n        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(\n            SqlSessionFactory.class.getClassLoader(), \n            new Class[] {SqlSession.class }, \n            (proxy, method, args) -> {\n                SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();\n                SqlSession sqlSession = getSqlSession(\n                    sqlSessionFactory, defaultTargetExecutorType, defaultTargetExceptionTranslator\n                );\n                try {\n                    Object result = method.invoke(sqlSession, args);\n                    if (!isSqlSessionTransactional(sqlSession, sqlSessionFactory)) {\n                        // force commit even on non-dirty sessions because some databases require\n                        // a commit/rollback before calling close()\n                        sqlSession.commit(true);\n                    }\n                    return result;\n                } catch (Throwable t) {\n                    Throwable unwrapped = unwrapThrowable(t);\n                    if (defaultTargetExceptionTranslator != null && unwrapped instanceof PersistenceException) {\n                        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22\n                        closeSqlSession(sqlSession, sqlSessionFactory);\n                        sqlSession = null;\n                        Throwable translated = defaultTargetExceptionTranslator.translateExceptionIfPossible(\n                            (PersistenceException) unwrapped\n                        );\n                        if (translated != null) {\n                            unwrapped = translated;\n                        }\n                    }\n                    throw unwrapped;\n                } finally {\n                    if (sqlSession != null) {\n                        closeSqlSession(sqlSession, sqlSessionFactory);\n                    }\n                }\n            }\n        );\n    }\n\n    @Override\n    public SqlSessionFactory getSqlSessionFactory() {\n        return Optional.ofNullable(\n            targetSqlSessionFactories.get(MultipleDataSourceContext.get())\n        ).orElse(\n            defaultTargetSqlSessionFactory\n        );\n    }\n\n    @Override\n    public ExecutorType getExecutorType() {\n        return this.defaultTargetExecutorType;\n    }\n\n    @Override\n    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {\n        return this.defaultTargetExceptionTranslator;\n    }\n\n    @Override\n    public <T> T selectOne(String statement) {\n        return this.sqlSessionProxy.selectOne(statement);\n    }\n\n    @Override\n    public <T> T selectOne(String statement, Object parameter) {\n        return this.sqlSessionProxy.selectOne(statement, parameter);\n    }\n\n    @Override\n    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {\n        return this.sqlSessionProxy.selectMap(statement, mapKey);\n    }\n\n    @Override\n    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {\n        return this.sqlSessionProxy.selectMap(statement, parameter, mapKey);\n    }\n\n    @Override\n    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {\n        return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);\n    }\n\n    @Override\n    public <T> Cursor<T> selectCursor(String statement) {\n        return this.sqlSessionProxy.selectCursor(statement);\n    }\n\n    @Override\n    public <T> Cursor<T> selectCursor(String statement, Object parameter) {\n        return this.sqlSessionProxy.selectCursor(statement, parameter);\n    }\n\n    @Override\n    public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {\n        return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds);\n    }\n\n    @Override\n    public <E> List<E> selectList(String statement) {\n        return this.sqlSessionProxy.selectList(statement);\n    }\n\n    @Override\n    public <E> List<E> selectList(String statement, Object parameter) {\n        return this.sqlSessionProxy.selectList(statement, parameter);\n    }\n\n    @Override\n    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {\n        return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);\n    }\n\n    @Override\n    public void select(String statement, ResultHandler handler) {\n        this.sqlSessionProxy.select(statement, handler);\n    }\n\n    @Override\n    public void select(String statement, Object parameter, ResultHandler handler) {\n        this.sqlSessionProxy.select(statement, parameter, handler);\n    }\n\n    @Override\n    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {\n        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);\n    }\n\n    @Override\n    public int insert(String statement) {\n        return this.sqlSessionProxy.insert(statement);\n    }\n\n    @Override\n    public int insert(String statement, Object parameter) {\n        return this.sqlSessionProxy.insert(statement, parameter);\n    }\n\n    @Override\n    public int update(String statement) {\n        return this.sqlSessionProxy.update(statement);\n    }\n\n    @Override\n    public int update(String statement, Object parameter) {\n        return this.sqlSessionProxy.update(statement, parameter);\n    }\n\n    @Override\n    public int delete(String statement) {\n        return this.sqlSessionProxy.delete(statement);\n    }\n\n    @Override\n    public int delete(String statement, Object parameter) {\n        return this.sqlSessionProxy.delete(statement, parameter);\n    }\n\n    @Override\n    public <T> T getMapper(Class<T> type) {\n        return getConfiguration().getMapper(type, this);\n    }\n\n    @Override\n    public void commit() {\n        throw new UnsupportedOperationException(\"Manual commit is not allowed over a Spring managed SqlSession\");\n    }\n\n    @Override\n    public void commit(boolean force) {\n        throw new UnsupportedOperationException(\"Manual commit is not allowed over a Spring managed SqlSession\");\n    }\n\n    @Override\n    public void rollback() {\n        throw new UnsupportedOperationException(\"Manual rollback is not allowed over a Spring managed SqlSession\");\n    }\n\n    @Override\n    public void rollback(boolean force) {\n        throw new UnsupportedOperationException(\"Manual rollback is not allowed over a Spring managed SqlSession\");\n    }\n\n    @Override\n    public void close() {\n        throw new UnsupportedOperationException(\"Manual close is not allowed over a Spring managed SqlSession\");\n    }\n\n    @Override\n    public void clearCache() {\n        this.sqlSessionProxy.clearCache();\n    }\n\n    @Override\n    public Configuration getConfiguration() {\n        return this.defaultTargetSqlSessionFactory.getConfiguration();\n    }\n\n    @Override\n    public Connection getConnection() {\n        return this.sqlSessionProxy.getConnection();\n    }\n\n    @Override\n    public List<BatchResult> flushStatements() {\n        return this.sqlSessionProxy.flushStatements();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/mybatis/PackagesSqlSessionFactoryBean.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.mybatis;\n\nimport cn.ponfee.commons.resource.ResourceScanner;\nimport org.mybatis.spring.SqlSessionFactoryBean;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Mybatis typeAliasesPackage config\n * \n * p:typeAliasesPackage=\"cn.ponfee.data.**.model\"\n * \n * @author Ponfee\n */\npublic class PackagesSqlSessionFactoryBean extends SqlSessionFactoryBean {\n\n    private static final Logger logger = LoggerFactory.getLogger(PackagesSqlSessionFactoryBean.class);\n\n    @Override\n    public void setTypeAliasesPackage(String typeAliasesPackage) {\n        Set<String> result = new HashSet<>();\n        for (Class<?> type : new ResourceScanner(typeAliasesPackage.replace('.', '/')).scan4class()) {\n            result.add(type.getPackage().getName());\n        }\n\n        if (result.isEmpty()) {\n            logger.warn(\"TypeAliasesPackage not scanned: \" + typeAliasesPackage);\n        } else {\n            super.setTypeAliasesPackage(String.join(\",\", result));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/mybatis/SqlHelper.java",
    "content": "package cn.ponfee.commons.mybatis;\n/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2014-2016 abel533@gmail.com\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.mapping.BoundSql;\nimport org.apache.ibatis.mapping.MappedStatement;\nimport org.apache.ibatis.mapping.ParameterMapping;\nimport org.apache.ibatis.mapping.ParameterMode;\nimport org.apache.ibatis.reflection.MetaObject;\nimport org.apache.ibatis.reflection.SystemMetaObject;\nimport org.apache.ibatis.session.Configuration;\nimport org.apache.ibatis.session.ResultHandler;\nimport org.apache.ibatis.session.RowBounds;\nimport org.apache.ibatis.session.SqlSession;\nimport org.apache.ibatis.type.JdbcType;\nimport org.apache.ibatis.type.TypeHandlerRegistry;\n\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Mybatis - 获取Mybatis查询sql工具\n *\n * https://gitee.com/free/Mybatis_Utils/tree/master/SqlHelper\n * \n * <code>\n * SqlHelper.getNamespaceSql(\n *   sqlSession, \n *   \"com.github.pagehelper.mapper.CountryMapper.listAll\"\n * )\n *             ||\n *             \\/\n * SELECT * FROM t_sched_job\n * </code>\n *\n *\n * <code>\n * sqlHelper.getMapperSql(\n *   sqlSession,\n *   \"cn.ponfee.dao.mapper.SchedJobMapper.query4page\",\n *   Arrays.asList(1, 2),\n *   Arrays.asList(3, 4),\n *   \"id\"\n * )\n *             ||\n *             \\/\n * SELECT * FROM t_sched_log \n * WHERE status IN (1,2) AND id NOT IN (3,4)\n * ORDER BY id\n * </code>\n * \n * @author liuzh/abel533/isea533\n */\npublic class SqlHelper {\n\n    /**\n     * 通过接口获取sql\n     *\n     * @param mapper\n     * @param methodName\n     * @param args\n     * @return\n     */\n    public static String getMapperSql(Object mapper, String methodName, Object... args) {\n        MetaObject metaObject = SystemMetaObject.forObject(mapper);\n        SqlSession session = (SqlSession) metaObject.getValue(\"h.sqlSession\");\n        Class<?> mapperInterface = (Class<?>) metaObject.getValue(\"h.mapperInterface\");\n        String fullMethodName = mapperInterface.getCanonicalName() + \".\" + methodName;\n        if (args == null || args.length == 0) {\n            return getNamespaceSql(session, fullMethodName, null);\n        } else {\n            return getMapperSql(session, mapperInterface, methodName, args);\n        }\n    }\n\n    /**\n     * 通过Mapper方法名获取sql\n     *\n     * @param session\n     * @param fullMapperMethodName\n     * @param args\n     * @return\n     */\n    public static String getMapperSql(SqlSession session, String fullMapperMethodName, Object... args) {\n        if (args == null || args.length == 0) {\n            return getNamespaceSql(session, fullMapperMethodName, null);\n        }\n        String methodName = fullMapperMethodName.substring(fullMapperMethodName.lastIndexOf('.') + 1);\n        Class<?> mapperInterface;\n        try {\n            mapperInterface = Class.forName(fullMapperMethodName.substring(0, fullMapperMethodName.lastIndexOf('.')));\n            return getMapperSql(session, mapperInterface, methodName, args);\n        } catch (ClassNotFoundException e) {\n            throw new IllegalArgumentException(\"参数\" + fullMapperMethodName + \"无效！\");\n        }\n    }\n\n    /**\n     * 通过Mapper接口和方法名\n     *\n     * @param session\n     * @param mapperInterface\n     * @param methodName\n     * @param args\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static String getMapperSql(SqlSession session, Class<?> mapperInterface, \n                                      String methodName, Object... args) {\n        String fullMapperMethodName = mapperInterface.getCanonicalName() + \".\" + methodName;\n        if (args == null || args.length == 0) {\n            return getNamespaceSql(session, fullMapperMethodName, null);\n        }\n        Method method = getDeclaredMethods(mapperInterface, methodName);\n        Map<String, Object> params = new HashMap<>();\n        Class<?>[] argTypes = method.getParameterTypes();\n        for (int i = 0; i < argTypes.length; i++) {\n            if (   !RowBounds.class.isAssignableFrom(argTypes[i]) \n                && !ResultHandler.class.isAssignableFrom(argTypes[i])) {\n                String paramName = \"param\" + (params.size() + 1);\n                paramName = getParamNameFromAnnotation(method, i, paramName);\n                params.put(paramName, i >= args.length ? null : args[i]);\n            }\n        }\n        if (args != null && args.length == 1) {\n            Object params0 = wrapCollection(args[0]);\n            if (params0 instanceof Map) {\n                params.putAll((Map<String, ?>) params0);\n            }\n        }\n        return getNamespaceSql(session, fullMapperMethodName, params);\n    }\n\n    /**\n     * 通过命名空间方式获取sql\n     *\n     * @param session\n     * @param namespace\n     * @return\n     */\n    public static String getNamespaceSql(SqlSession session, String namespace) {\n        return getNamespaceSql(session, namespace, null);\n    }\n\n    /**\n     * 通过命名空间方式获取sql\n     *\n     * @param session\n     * @param namespace\n     * @param params\n     * @return\n     */\n    public static String getNamespaceSql(SqlSession session, String namespace, Object params) {\n        params = wrapCollection(params);\n        Configuration configuration = session.getConfiguration();\n        MappedStatement mappedStatement = configuration.getMappedStatement(namespace);\n        TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();\n        BoundSql boundSql = mappedStatement.getBoundSql(params);\n        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();\n        String sql = boundSql.getSql();\n        if (parameterMappings != null) {\n            for (ParameterMapping parameterMapping : parameterMappings) {\n                if (parameterMapping.getMode() != ParameterMode.OUT) {\n                    Object value;\n                    String propertyName = parameterMapping.getProperty();\n                    if (boundSql.hasAdditionalParameter(propertyName)) {\n                        value = boundSql.getAdditionalParameter(propertyName);\n                    } else if (params == null) {\n                        value = null;\n                    } else if (typeHandlerRegistry.hasTypeHandler(params.getClass())) {\n                        value = params;\n                    } else {\n                        MetaObject metaObject = configuration.newMetaObject(params);\n                        value = metaObject.getValue(propertyName);\n                    }\n                    JdbcType jdbcType = parameterMapping.getJdbcType();\n                    if (value == null && jdbcType == null) {\n                        jdbcType = configuration.getJdbcTypeForNull();\n                    }\n                    sql = replaceParameter(sql, value, jdbcType, parameterMapping.getJavaType());\n                }\n            }\n        }\n        return sql;\n    }\n\n    /**\n     * 根据类型替换参数\n     * 仅作为数字和字符串两种类型进行处理，需要特殊处理的可以继续完善这里\n     *\n     * @param sql\n     * @param value\n     * @param jdbcType\n     * @param javaType\n     * @return\n     */\n    private static String replaceParameter(String sql, Object value, \n                                           JdbcType jdbcType, Class<?> javaType) {\n        String strValue = String.valueOf(value);\n        if (jdbcType != null) {\n            switch (jdbcType) {\n                //数字\n                case BIT:\n                case TINYINT:\n                case SMALLINT:\n                case INTEGER:\n                case BIGINT:\n                case FLOAT:\n                case REAL:\n                case DOUBLE:\n                case NUMERIC:\n                case DECIMAL:\n                    break;\n                //日期\n                case DATE:\n                case TIME:\n                case TIMESTAMP:\n                    //其他，包含字符串和其他特殊类型\n                default:\n                    strValue = \"'\" + strValue + \"'\";\n\n            }\n        } else if (Number.class.isAssignableFrom(javaType)) {\n            //不加单引号\n        } else {\n            strValue = \"'\" + strValue + \"'\";\n        }\n        return sql.replaceFirst(\"\\\\?\", strValue);\n    }\n\n    /**\n     * 获取指定的方法\n     *\n     * @param clazz\n     * @param methodName\n     * @return\n     */\n    private static Method getDeclaredMethods(Class<?> clazz, String methodName) {\n        Method[] methods = clazz.getDeclaredMethods();\n        for (Method method : methods) {\n            if (method.getName().equals(methodName)) {\n                return method;\n            }\n        }\n        throw new IllegalArgumentException(\"方法\" + methodName + \"不存在！\");\n    }\n\n    /**\n     * 获取参数注解名\n     *\n     * @param method\n     * @param i\n     * @param paramName\n     * @return\n     */\n    private static String getParamNameFromAnnotation(Method method, int i, String paramName) {\n        final Object[] paramAnnos = method.getParameterAnnotations()[i];\n        for (Object paramAnno : paramAnnos) {\n            if (paramAnno instanceof Param) {\n                paramName = ((Param) paramAnno).value();\n            }\n        }\n        return paramName;\n    }\n\n    /**\n     * 简单包装参数\n     *\n     * @param object\n     * @return\n     */\n    private static Object wrapCollection(final Object object) {\n        if (object instanceof List) {\n            Map<String, Object> map = new HashMap<>();\n            map.put(\"list\", object);\n            return map;\n        } else if (object != null && object.getClass().isArray()) {\n            Map<String, Object> map = new HashMap<>();\n            map.put(\"array\", object);\n            return map;\n        }\n        return object;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/mybatis/SqlMapper.java",
    "content": "package cn.ponfee.commons.mybatis;\n\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.ibatis.builder.StaticSqlSource;\nimport org.apache.ibatis.exceptions.TooManyResultsException;\nimport org.apache.ibatis.mapping.MappedStatement;\nimport org.apache.ibatis.mapping.ResultMap;\nimport org.apache.ibatis.mapping.SqlCommandType;\nimport org.apache.ibatis.mapping.SqlSource;\nimport org.apache.ibatis.scripting.LanguageDriver;\nimport org.apache.ibatis.session.Configuration;\nimport org.apache.ibatis.session.SqlSession;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.BiFunction;\n\nimport static cn.ponfee.commons.util.ObjectUtils.typeOf;\n\n/**\n * <pre>\n * MyBatis执行sql工具，在写SQL的时候建议使用参数形式的可以是${}或#{}\n * 不建议将参数直接拼到字符串中，当大量这么使用的时候由于缓存MappedStatement而占用更多的内存\n * https://gitee.com/free/Mybatis_Utils/tree/master/SqlMapper\n *\n * Mybatis-generator、通用Mapper、Mybatis-Plus对比：\n *   https://www.jianshu.com/p/7be6da536f8f\n *   https://blog.csdn.net/m0_37524586/article/details/88351833\n *\n * </pre>\n *\n * @author liuzh\n * @since 2015-03-10\n */\npublic class SqlMapper {\n\n    private final SqlSession sqlSession;\n    private final MSUtils msUtils;\n\n    /**\n     * 构造方法，默认缓存MappedStatement\n     *\n     * SqlSessionTemplate implements SqlSession\n     * \n     * @param sqlSession\n     */\n    public SqlMapper(SqlSession sqlSession) {\n        this.sqlSession = sqlSession;\n        this.msUtils = new MSUtils(sqlSession.getConfiguration());\n    }\n\n    /**\n     * 查询返回一个结果，多个结果时抛出异常\n     *\n     * @param sql 执行的sql\n     * @return\n     */\n    public Map<String, Object> selectOne(String sql) {\n        return asSingleItem(selectList(sql));\n    }\n\n    /**\n     * 查询返回一个结果，多个结果时抛出异常\n     *\n     * @param sql   执行的sql\n     * @param param 参数\n     * @return\n     */\n    public Map<String, Object> selectOne(String sql, Object param) {\n        return asSingleItem(selectList(sql, param));\n    }\n\n    /**\n     * 查询返回一个结果，多个结果时抛出异常\n     *\n     * @param sql        执行的sql\n     * @param resultType 返回的结果类型\n     * @return\n     */\n    public <T> T selectOne(String sql, Class<T> resultType) {\n        return asSingleItem(selectList(sql, resultType));\n    }\n\n    /**\n     * 查询返回一个结果，多个结果时抛出异常\n     *\n     * @param sql        执行的sql\n     * @param param      参数\n     * @param resultType 返回的结果类型\n     * @return\n     */\n    public <T> T selectOne(String sql, Object param, Class<T> resultType) {\n        return asSingleItem(selectList(sql, param, resultType));\n    }\n\n    /**\n     * 查询返回List<Map<String, Object>>\n     *\n     * @param sql 执行的sql\n     * @return\n     */\n    public List<Map<String, Object>> selectList(String sql) {\n        return sqlSession.selectList(msUtils.select(sql));\n    }\n\n    /**\n     * 查询返回List<Map<String, Object>>\n     *\n     * @param sql   执行的sql\n     * @param param 参数\n     * @return\n     */\n    public List<Map<String, Object>> selectList(String sql, Object param) {\n        return sqlSession.selectList(\n            msUtils.selectDynamic(sql, typeOf(param)), param\n        );\n    }\n\n    /**\n     * 查询返回指定的结果类型\n     *\n     * @param sql        执行的sql\n     * @param resultType 返回的结果类型\n     * @return\n     */\n    public <T> List<T> selectList(String sql, Class<T> resultType) {\n        String msId = resultType == null \n                    ? msUtils.select(sql) \n                    : msUtils.select(sql, resultType);\n        return sqlSession.selectList(msId);\n    }\n\n    /**\n     * 查询返回指定的结果类型\n     *\n     * @param sql        执行的sql\n     * @param param      参数\n     * @param resultType 返回的结果类型\n     * @return\n     */\n    public <T> List<T> selectList(String sql, Object param, Class<T> resultType) {\n        String msId = resultType == null\n                    ? msUtils.selectDynamic(sql, typeOf(param))\n                    : msUtils.selectDynamic(sql, typeOf(param), resultType);\n        return sqlSession.selectList(msId, param);\n    }\n\n    /**\n     * <pre>\n     *  Query full scroll data\n     *  \n     *  &lt;script&gt;\n     *    SELECT id, name FROM t_test \n     *    WHERE name IS NOT NULL &lt;if test='id!=null'&gt;AND id&gt;#{id}&lt;/if&gt;\n     *    ORDER BY id ASC \n     *    LIMIT 5000\n     *  &lt;/script&gt;\n     * </pre>\n     * \n     * @param sql        the mybatis sql script\n     * @param param      the param\n     * @param resultType the result type\n     * @param action     the action\n     */\n    public <P, R> void selectScroll(String sql, P param, Class<R> resultType, \n                                    BiFunction<P, List<R>, P> action) {\n        List<R> list; boolean hasNext;\n        do {\n            list = selectList(sql, param, resultType);\n            hasNext = CollectionUtils.isNotEmpty(list);\n            if (hasNext) {\n                param = action.apply(param, list);\n            }\n        } while (hasNext);\n    }\n\n    /**\n     * 插入数据\n     *\n     * @param sql 执行的sql\n     * @return\n     */\n    public int insert(String sql) {\n        return sqlSession.insert(msUtils.insert(sql));\n    }\n\n    /**\n     * 插入数据\n     *\n     * @param sql   执行的sql\n     * @param param 参数\n     * @return\n     */\n    public int insert(String sql, Object param) {\n        return sqlSession.insert(\n            msUtils.insertDynamic(sql, typeOf(param)), param\n        );\n    }\n\n    /**\n     * 更新数据\n     *\n     * @param sql 执行的sql\n     * @return\n     */\n    public int update(String sql) {\n        return sqlSession.update(msUtils.update(sql));\n    }\n\n    /**\n     * 更新数据\n     *\n     * @param sql   执行的sql\n     * @param param 参数\n     * @return\n     */\n    public int update(String sql, Object param) {\n        return sqlSession.update(\n            msUtils.updateDynamic(sql, typeOf(param)), param\n        );\n    }\n\n    /**\n     * 删除数据\n     *\n     * @param sql 执行的sql\n     * @return\n     */\n    public int delete(String sql) {\n        return sqlSession.delete(msUtils.delete(sql));\n    }\n\n    /**\n     * 删除数据\n     *\n     * @param sql   执行的sql\n     * @param param 参数\n     * @return\n     */\n    public int delete(String sql, Object param) {\n        return sqlSession.delete(\n            msUtils.deleteDynamic(sql, typeOf(param)), param\n        );\n    }\n\n    /**\n     * 获取List中最多只有一个的数据\n     *\n     * @param list List结果\n     * @param <T>  泛型类型\n     * @return\n     */\n    private <T> T asSingleItem(List<T> list) {\n        int rowSize = list == null ? 0 : list.size();\n        if (rowSize > 1) {\n            throw new TooManyResultsException(\"Expected one row (or null) to be returned by selectOne(), but found: \" + list.size());\n        }\n        return rowSize == 1 ? list.get(0) : null;\n    }\n\n    // ---------------------------------------------------------------------private class\n    private static class MSUtils {\n        private final Configuration configuration;\n        private final LanguageDriver languageDriver;\n\n        private MSUtils(Configuration configuration) {\n            this.configuration = configuration;\n            this.languageDriver = configuration.getDefaultScriptingLanguageInstance();\n        }\n\n        /**\n         * 创建MSID\n         *\n         * @param sql 执行的sql\n         * @param sql 执行的sqlCommandType\n         * @return\n         */\n        private String newMsId(String sql, SqlCommandType sqlCommandType) {\n            return new StringBuilder(sqlCommandType.toString())\n                .append(\".\").append(sql.hashCode()).toString();\n        }\n\n        /**\n         * 是否已经存在该ID\n         *\n         * @param msId\n         * @return\n         */\n        private boolean hasMappedStatement(String msId) {\n            return configuration.hasStatement(msId, false);\n        }\n\n        /**\n         * 创建一个查询的MS\n         *\n         * @param msId\n         * @param sqlSource  执行的sqlSource\n         * @param resultType 返回的结果类型\n         */\n        private void newSelectMappedStatement(String msId, SqlSource sqlSource, final Class<?> resultType) {\n            List<ResultMap> list = Collections.singletonList(\n                new ResultMap.Builder(\n                    configuration, \"defaultResultMap\", resultType, new ArrayList<>(0)\n                ).build()\n            );\n            MappedStatement ms = new MappedStatement.Builder(\n                configuration, msId, sqlSource, SqlCommandType.SELECT\n            ).resultMaps(list).build();\n            //缓存\n            configuration.addMappedStatement(ms);\n        }\n\n        /**\n         * 创建一个简单的MS\n         *\n         * @param msId\n         * @param sqlSource      执行的sqlSource\n         * @param sqlCommandType 执行的sqlCommandType\n         */\n        private void newUpdateMappedStatement(String msId, SqlSource sqlSource, \n                                              SqlCommandType sqlCommandType) {\n            List<ResultMap> list = Collections.singletonList(\n                new ResultMap.Builder(configuration, \"defaultResultMap\", int.class, new ArrayList<>(0)).build()\n            );\n            MappedStatement ms = new MappedStatement.Builder(\n                configuration, msId, sqlSource, sqlCommandType\n            ).resultMaps(list).build();\n            configuration.addMappedStatement(ms);\n        }\n\n        private String select(String sql) {\n            String msId = newMsId(sql, SqlCommandType.SELECT);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);\n            newSelectMappedStatement(msId, sqlSource, Map.class);\n            return msId;\n        }\n\n        private String selectDynamic(String sql, Class<?> parameterType) {\n            String msId = newMsId(sql + parameterType, SqlCommandType.SELECT);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);\n            newSelectMappedStatement(msId, sqlSource, Map.class);\n            return msId;\n        }\n\n        private String select(String sql, Class<?> resultType) {\n            String msId = newMsId(resultType + sql, SqlCommandType.SELECT);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);\n            newSelectMappedStatement(msId, sqlSource, resultType);\n            return msId;\n        }\n\n        private String selectDynamic(String sql, Class<?> parameterType, Class<?> resultType) {\n            String msId = newMsId(resultType + sql + parameterType, SqlCommandType.SELECT);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);\n            newSelectMappedStatement(msId, sqlSource, resultType);\n            return msId;\n        }\n\n        private String insert(String sql) {\n            String msId = newMsId(sql, SqlCommandType.INSERT);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);\n            newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT);\n            return msId;\n        }\n\n        private String insertDynamic(String sql, Class<?> parameterType) {\n            String msId = newMsId(sql + parameterType, SqlCommandType.INSERT);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);\n            newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT);\n            return msId;\n        }\n\n        private String update(String sql) {\n            String msId = newMsId(sql, SqlCommandType.UPDATE);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);\n            newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE);\n            return msId;\n        }\n\n        private String updateDynamic(String sql, Class<?> parameterType) {\n            String msId = newMsId(sql + parameterType, SqlCommandType.UPDATE);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);\n            newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE);\n            return msId;\n        }\n\n        private String delete(String sql) {\n            String msId = newMsId(sql, SqlCommandType.DELETE);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);\n            newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE);\n            return msId;\n        }\n\n        private String deleteDynamic(String sql, Class<?> parameterType) {\n            String msId = newMsId(sql + parameterType, SqlCommandType.DELETE);\n            if (hasMappedStatement(msId)) {\n                return msId;\n            }\n            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);\n            newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE);\n            return msId;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/parser/DateUDF.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.parser;\n\nimport cn.ponfee.commons.date.Dates;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.Date;\nimport java.util.regex.Pattern;\n\n/**\n * https://www.cnblogs.com/qcfeng/p/7553500.html\n * \n * Date user defined functions\n * \n * @author Ponfee\n */\npublic final class DateUDF {\n\n    private static final Pattern PATTERN = Pattern.compile(\"[\\\\-+]?\\\\d+[YyMDdHhmWw]\");\n\n    // -------------------------------------------------now\n    public static String now(String format) {\n        return format(now(), format);\n    }\n\n    public static String now(String format, String offset) {\n        return format(compute(now(), offset), format);\n    }\n\n    // -------------------------------------------------day\n    public static String startDay(String format) {\n        return format(Dates.startOfDay(now()), format);\n    }\n\n    public static String startDay(String format, String offset) {\n        return format(Dates.startOfDay(compute(now(), offset)), format);\n    }\n\n    public static String endDay(String format) {\n        return format(Dates.endOfDay(now()), format);\n    }\n\n    public static String endDay(String format, String offset) {\n        return format(Dates.endOfDay(compute(now(), offset)), format);\n    }\n\n    // -------------------------------------------------week\n    public static String startWeek(String format) {\n        return format(Dates.startOfWeek(now()), format);\n    }\n\n    public static String startWeek(String format, String offset) {\n        return format(Dates.startOfWeek(compute(now(), offset)), format);\n    }\n\n    public static String endWeek(String format) {\n        return format(Dates.endOfWeek(now()), format);\n    }\n\n    public static String endWeek(String format, String offset) {\n        return format(Dates.endOfWeek(compute(now(), offset)), format);\n    }\n\n    // -------------------------------------------------month\n    public static String startMonth(String format) {\n        return format(Dates.startOfMonth(now()), format);\n    }\n\n    public static String startMonth(String format, String offset) {\n        return format(Dates.startOfMonth(compute(now(), offset)), format);\n    }\n\n    public static String endMonth(String format) {\n        return format(Dates.endOfMonth(now()), format);\n    }\n\n    public static String endMonth(String format, String offset) {\n        return format(Dates.endOfMonth(compute(now(), offset)), format);\n    }\n\n    // -------------------------------------------------year\n    public static String startYear(String format) {\n        return format(Dates.startOfYear(now()), format);\n    }\n\n    public static String startYear(String format, String offset) {\n        return format(Dates.startOfYear(compute(now(), offset)), format);\n    }\n\n    public static String endYear(String format) {\n        return format(Dates.endOfYear(now()), format);\n    }\n\n    public static String endYear(String format, String offset) {\n        return format(Dates.endOfYear(compute(now(), offset)), format);\n    }\n\n    // -------------------------------------------------private methods\n    private static Date now() {\n        return new Date();\n    }\n\n    private static String format(Date date, String format) {\n        if (date == null) {\n            return null;\n        } else if (\"timestamp\".equalsIgnoreCase(format)) {\n            return String.valueOf(date.getTime());\n        } else {\n            return Dates.format(date, format);\n        }\n    }\n\n    private static Date compute(Date dateTime, String offset) {\n        if (!PATTERN.matcher(offset).matches()) {\n            throw new IllegalArgumentException(\"Invalid offset: \" + offset);\n        }\n\n        int amount = Integer.parseInt(StringUtils.substring(offset, 0, -1));\n        switch (offset.charAt(offset.length() - 1)) {\n            case 'y':\n            case 'Y':\n                return Dates.plusYears(dateTime, amount);\n            case 'M':\n                return Dates.plusMonths(dateTime, amount);\n            case 'w':\n            case 'W':\n                return Dates.plusWeeks(dateTime, amount);\n            case 'd':\n            case 'D':\n                return Dates.plusDays(dateTime, amount);\n            case 'H':\n            case 'h':\n                return Dates.plusHours(dateTime, amount);\n            case 'm':\n                return Dates.plusMinutes(dateTime, amount);\n            default:\n                throw new IllegalArgumentException(\"Invalid offset: \" + offset);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/parser/ELParser.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.parser;\n\nimport org.apache.commons.collections4.MapUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.expression.ExpressionParser;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.StandardEvaluationContext;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static com.google.common.base.CaseFormat.LOWER_CAMEL;\nimport static com.google.common.base.CaseFormat.LOWER_UNDERSCORE;\n\n/**\n * EL parser utility.\n * \n * ELParser.parse(\"test |${abc}| a |${12}|  |$[ yyyyMM \\n]|  xx  |$[start_year( yyyyMMddHHmmss, -1y )]| aa |$[now( timestamp\\n, -1y)]|\", ImmutableMap.of(\"abc\", 123, \"test.1\", \"xxx\"))\n * \n * @author Ponfee\n */\npublic class ELParser {\n\n    private static final Pattern PARAMS_PATTERN = Pattern.compile(\"\\\\$\\\\{\\\\s*([a-zA-Z0-9_\\\\-.]+)\\\\s*\\\\}\");\n    private static final Pattern DATETM_PATTERN = Pattern.compile(\"\\\\$\\\\[\\\\s*([a-zA-Z0-9_,\\\\-+.()\\\\s]+)\\\\s*\\\\]\");\n    private static final Pattern SPEL_PATTERN   = Pattern.compile(\"\\\\{\\\\{\\\\s*([^{}]+)\\\\s*\\\\}\\\\}\");\n\n    public static String parse(String text) {\n        return parseDateUDF(text);\n    }\n\n    public static String parse(String text, Map<String, ?> params) {\n        text = parseParams(text, params);\n        text = parseSpel(text, params);\n        text = parseDateUDF(text);\n        return text;\n    }\n\n    // ----------------------------------------------------------------------------- private methods\n    private static String parseParams(String text, Map<String, ?> params) {\n        if (MapUtils.isEmpty(params)) {\n            return text;\n        }\n        String result = text;\n        Matcher matcher = PARAMS_PATTERN.matcher(text);\n        while (matcher.find()) {\n            String name = matcher.group(1);\n            if (params.containsKey(name)) {\n                // matcher.group() -> matcher.group(0)\n                result = StringUtils.replace(result, matcher.group(), Objects.toString(params.get(name), \"\"));\n            }\n        }\n        return result;\n    }\n\n    private static String parseDateUDF(String text) {\n        Matcher matcher = DATETM_PATTERN.matcher(text);\n        String result = text;\n        while (matcher.find()) {\n            String val = translate(matcher.group(1));\n            if (val != null) {\n                result = StringUtils.replace(result, matcher.group(), val);\n            }\n        }\n        return result;\n    }\n\n    /**\n     * SPEL Java: parser.parseExpression(\"T(java.lang.Math).PI\").getValue(double.class);\n     *            parser.parseExpression(\"T(java.lang.Math).random()\").getValue(double.class)\n     *            parser.parseExpression(\"'Hi,everybody'\").getValue(String.class)\n     * \n     * SPEL Xml : parser.parseExpression(\"#{T(java.lang.Math).PI}\").getValue(double.class);\n     *            parser.parseExpression(\"#{T(java.lang.Math).random()}\").getValue(double.class)\n     *            parser.parseExpression(\"#{'Hi,everybody'}\").getValue(String.class)\n     * @param text\n     * @param params\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    private static String parseSpel(String text, Map<String, ?> params) {\n        if (MapUtils.isEmpty(params)) {\n            return text;\n        }\n\n        StandardEvaluationContext context = new StandardEvaluationContext();\n        context.setVariables((Map<String, Object>) params); // #key\n        //context.setVariables(\"name\", (Map<String, Object>) params); // #name[key]\n        //context.setRootObject(params); // #root.field\n        ExpressionParser parser = new SpelExpressionParser();\n        String result = text;\n        Matcher matcher = SPEL_PATTERN.matcher(text);\n        while (matcher.find()) {\n            result = StringUtils.replace(\n                result, matcher.group(), \n                Objects.toString(parser.parseExpression(matcher.group(1)).getValue(context), null)\n            );\n        }\n        return result;\n    }\n\n    private static String translate(String text) {\n        try {\n            text = text.trim();\n            int argsStart = text.indexOf('(');\n\n            if (argsStart < 1) {\n                // $[yyyyMMdd]\n                return DateUDF.now(text);\n            }\n\n            if (!text.endsWith(\")\")) {\n                return null;\n            }\n\n            String methodName = LOWER_UNDERSCORE.to(LOWER_CAMEL, text.substring(0, argsStart).trim().toLowerCase());\n            String[] params = text.substring(argsStart + 1, text.lastIndexOf(')')).split(\",\");\n            if (params.length == 1) {\n                // e.g. $[now(timestamp)] or $[start_day(yyyyMMddHHmmss)]\n                return (String) getPublicStaticMethod(DateUDF.class, methodName, String.class)\n                    .invoke(null, params[0].trim());\n            } else if (params.length == 2) {\n                // e.g. $[start_year(yyyyMMddHHmmss, -1y)]\n                return (String) getPublicStaticMethod(DateUDF.class, methodName, String.class, String.class)\n                    .invoke(null, params[0].trim(), params[1].trim());\n            } else {\n                return null;\n            }\n        } catch (Exception ignored) {\n            return null;\n        }\n    }\n\n    private static Method getPublicStaticMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) throws Exception {\n        Method method = ArrayUtils.isEmpty(parameterTypes) \n                      ? clazz.getDeclaredMethod(methodName) \n                      : clazz.getDeclaredMethod(methodName, parameterTypes);\n        int modifiers = method.getModifiers();\n        return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)) ? method : null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/pdf/PdfWaterMark.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.pdf;\n\nimport com.itextpdf.text.*;\nimport com.itextpdf.text.pdf.*;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * pdf加水印\n * \n * @author Ponfee\n */\npublic class PdfWaterMark {\n\n    /**\n     * 添加水印图片\n     * @param pdf\n     * @param img\n     * @param out\n     */\n    public static void waterImgMark(InputStream pdf, byte[] img, OutputStream out) {\n        PdfStamper stamper = null;\n        PdfReader reader = null;\n        try {\n            reader = new PdfReader(pdf);\n            stamper = new PdfStamper(reader, out);\n            Image image = Image.getInstance(img);// 水印图片\n            Rectangle pageRect;\n            for (int n = stamper.getReader().getNumberOfPages() + 1, i = 1; i < n; i++) {\n                pageRect = stamper.getReader().getPageSizeWithRotation(i);\n                PdfContentByte content = stamper.getUnderContent(i);\n                content.saveState();\n                image.setAbsolutePosition(pageRect.getWidth() - image.getWidth(), 0); // 水印添加到右下角\n                content.addImage(image);\n            }\n            out.flush();\n        } catch (DocumentException | IOException e) {\n            throw new RuntimeException(e);\n        } finally {\n            if (stamper != null) {\n                try {\n                    stamper.close();\n                } catch (DocumentException | IOException ignored) {\n                    ignored.printStackTrace();\n                }\n            }\n            if (reader != null) {\n                reader.close();\n            }\n        }\n    }\n\n    /**\n     * 添加水印文字\n     * @param pdf\n     * @param words\n     * @param out\n     */\n    public static void waterWordMark(InputStream pdf, String words, OutputStream out) {\n        PdfStamper stamper = null;\n        PdfReader reader = null;\n        try {\n            reader = new PdfReader(pdf);\n            stamper = new PdfStamper(reader, out);\n            //stamper.setEncryption(\"user\".getBytes(), \"owner\".getBytes(), PdfWriter.ALLOW_COPY | PdfWriter.ALLOW_PRINTING, PdfWriter.STANDARD_ENCRYPTION_40);\n\n            // 1、使用iTextAsian.jar中的字体：BaseFont.createFont(\"STSong-Light\", \"UniGB-UCS2-H\",BaseFont.NOT_EMBEDDED);\n            // 2、使用Windows系统字体（TrueType）：BaseFont.createFont(\"C:/WINDOWS/Fonts/SIMYOU.TTF\", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);\n            // 3、使用资源字体（ClassPath）：BaseFont.createFont(\"/SIMYOU.TTF\", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);\n            BaseFont base = BaseFont.createFont(\"code/ponfee/commons/pdf/aspose/simfang.ttf\", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);\n\n            PdfGState pdfGState = new PdfGState();\n            pdfGState.setStrokeOpacity(0.2f); // 设置线条透明度为0.2\n            pdfGState.setFillOpacity(0.2f); // 设置填充透明度为0.2\n            for (int totalPage = stamper.getReader().getNumberOfPages(), i = 1; i <= totalPage; i++) {\n                Rectangle pageRect = stamper.getReader().getPageSizeWithRotation(i);\n                float x = pageRect.getWidth() / 2; // 计算水印X坐标\n                float y = pageRect.getHeight() / 2; // 计算水印Y坐标\n                PdfContentByte content = stamper.getUnderContent(i); // content = pdfStamper.getOverContent(i);\n                content.saveState();\n                content.setGState(pdfGState);\n                content.beginText();\n                content.setColorFill(BaseColor.LIGHT_GRAY);\n                content.setFontAndSize(base, 60);\n                content.showTextAligned(Element.ALIGN_CENTER, words, x, y, 45); // 水印文字成45度角倾斜\n                content.endText();\n            }\n            out.flush();\n        } catch (DocumentException | IOException e) {\n            throw new RuntimeException(e);\n        } finally {\n            if (stamper != null) {\n                try {\n                    stamper.close();\n                } catch (DocumentException | IOException ignored) {\n                    ignored.printStackTrace();\n                }\n            }\n            if (reader != null) {\n                reader.close();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/pdf/sign/PdfSignature.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.pdf.sign;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.Providers;\nimport com.itextpdf.text.Image;\nimport com.itextpdf.text.Rectangle;\nimport com.itextpdf.text.pdf.PdfReader;\nimport com.itextpdf.text.pdf.PdfSignatureAppearance;\nimport com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;\nimport com.itextpdf.text.pdf.PdfStamper;\nimport com.itextpdf.text.pdf.PdfStream;\nimport com.itextpdf.text.pdf.security.BouncyCastleDigest;\nimport com.itextpdf.text.pdf.security.ExternalSignature;\nimport com.itextpdf.text.pdf.security.MakeSignature;\nimport com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;\nimport com.itextpdf.text.pdf.security.PrivateKeySignature;\nimport sun.security.x509.AlgorithmId;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.security.cert.X509Certificate;\nimport java.util.Calendar;\n\n/**\n * pdf签章\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"restriction\")\npublic class PdfSignature {\n\n    public static final float SIGN_AREA_RATE = 0.8f;\n\n    /**\n     * Sign pdf\n     * \n     * @param pdfInput  pdf输入\n     * @param pdfOutput pdf输出\n     * @param stamp     签章图片信息 {@link Stamp}\n     * @param signer    签名人信息 {@link Signer}\n     */\n    public static void sign(InputStream pdfInput, OutputStream pdfOutput, Stamp stamp, Signer signer) {\n        PdfReader reader = null;\n        PdfStamper stamper = null;\n        try {\n            reader = new PdfReader(pdfInput);\n            stamper = PdfStamper.createSignature(reader, pdfOutput, '\\0', null, true); // true：允许对同一文档多次签名\n            stamper.getWriter().setCompressionLevel(PdfStream.DEFAULT_COMPRESSION);\n            Calendar calendar = Calendar.getInstance();\n\n            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();\n            //appearance.setReason(\"\");appearance.setLocation(\"\");appearance.setContact(\"\");\n            appearance.setVisibleSignature(\n                calcRectangle(signer.getImage(), stamp), \n                stamp.getPageNo(), \n                String.valueOf(calendar.getTimeInMillis())\n            );\n\n            appearance.setSignDate(calendar); // 设置签名时间为当前日期\n            appearance.setSignatureGraphic(signer.getImage());\n            appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);\n            appearance.setRenderingMode(RenderingMode.GRAPHIC);\n\n            ExternalSignature signature = new PrivateKeySignature(\n                signer.getPriKey(), \n                AlgorithmId.getDigAlgFromSigAlg(((X509Certificate) signer.getCertChain()[0]).getSigAlgName()), // 获取摘要算法\n                Providers.BC.getName()\n            );\n\n            MakeSignature.signDetached(\n                appearance, new BouncyCastleDigest(), signature, signer.getCertChain(), \n                null, null, null, 0, CryptoStandard.CMS\n            );\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        } finally {\n            if (reader != null) {\n                try {\n                    reader.close();\n                } catch (Exception ignored) {\n                    ignored.printStackTrace();\n                }\n            }\n            if (stamper != null) {\n                try {\n                    stamper.close();\n                } catch (Exception ignored) {\n                    ignored.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * 多次签章\n     * \n     * @param pdf    the pdf byte array data\n     * @param stamps the stamp array\n     * @param signer the signer\n     * @return\n     */\n    public static byte[] sign(byte[] pdf, Stamp[] stamps, Signer signer) {\n        for (Stamp stamp : stamps) {\n            ByteArrayOutputStream baos = new ByteArrayOutputStream(Files.BUFF_SIZE);\n            sign(new ByteArrayInputStream(pdf), baos, stamp, signer);\n            pdf = baos.toByteArray();\n        }\n        return pdf;\n    }\n\n    /**\n     * 计算签名位置\n     * \n     * @param image the image\n     * @param stamp the stamp\n     * @return a Rectangle\n     */\n    private static Rectangle calcRectangle(Image image, Stamp stamp) {\n        return new Rectangle(\n            stamp.getLeft(), \n            stamp.getBottom(), \n            stamp.getLeft() + image.getWidth() * SIGN_AREA_RATE, \n            stamp.getBottom() + image.getTop() * SIGN_AREA_RATE\n        );\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/pdf/sign/Signer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.pdf.sign;\n\nimport cn.ponfee.commons.util.ImageUtils;\nimport com.itextpdf.text.BadElementException;\nimport com.itextpdf.text.Image;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.security.PrivateKey;\nimport java.security.cert.Certificate;\n\n/**\n * 签名人信息\n * \n * @author Ponfee\n */\npublic class Signer {\n\n    private final PrivateKey priKey;\n    private final Certificate[] certChain;\n    private final Image image;\n\n    public Signer(PrivateKey priKey, Certificate[] certChain, \n                  byte[] img, boolean transparent) {\n        this.priKey = priKey;\n        this.certChain = certChain;\n        if (transparent) { // 图片透明化处理\n            img = ImageUtils.transparent(new ByteArrayInputStream(img), 250, 235);\n        }\n        try {\n            this.image = Image.getInstance(img);\n        } catch (BadElementException | IOException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    public PrivateKey getPriKey() {\n        return priKey;\n    }\n\n    public Certificate[] getCertChain() {\n        return certChain;\n    }\n\n    public Image getImage() {\n        return image;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/pdf/sign/Stamp.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.pdf.sign;\n\n/**\n * 印章信息\n * \n * @author Ponfee\n */\npublic class Stamp implements java.io.Serializable {\n    private static final long serialVersionUID = -6348664154098224106L;\n\n    private final int   pageNo;\n    private final float left;\n    private final float bottom;\n\n    public Stamp(int pageNo, float left, float bottom) {\n        this.pageNo = pageNo;\n        this.left = left;\n        this.bottom = bottom;\n    }\n\n    public int getPageNo() {\n        return pageNo;\n    }\n\n    public float getLeft() {\n        return left;\n    }\n\n    public float getBottom() {\n        return bottom;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/reflect/BeanConverts.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.reflect;\n\nimport com.google.common.collect.ImmutableMap;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Extract bean to array, list and map\n * \n * @author Ponfee\n */\npublic final class BeanConverts {\n\n    /**\n     * 将对象指定字段转为map\n     *\n     * @param bean\n     * @param fields\n     * @param <E>\n     * @return\n     */\n    public static <E> Object[] toArray(E bean, String... fields) {\n        if (bean == null || fields == null) {\n            return null;\n        }\n        return Stream.of(fields).map(f -> Fields.get(bean, f)).toArray();\n    }\n\n    /**\n     * 获取对象指定字段的值\n     *\n     * @param beans\n     * @param field\n     * @param <T>\n     * @param <E>\n     * @return\n     */\n    @SuppressWarnings({ \"unchecked\" })\n    public static <T, E> List<T> toList(List<E> beans, String field) {\n        if (beans == null) {\n            return null;\n        }\n        return beans.stream().map(e -> (T) Fields.get(e, field)).collect(Collectors.toList());\n    }\n\n    public static <E> List<Object[]> toList(List<E> beans, String... fields) {\n        if (beans == null || fields == null || fields.length == 0) {\n            return null;\n        }\n\n        return beans.stream()\n                    .map(e -> Stream.of(fields).map(f -> Fields.get(e, f)).toArray())\n                    .collect(Collectors.toList());\n    }\n\n    /**\n     * 指定对象字段keyField的值作为key，字段valueField的值作为value\n     * \n     * @param bean\n     * @param keyField\n     * @param valueField\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <K, V, E> Map<K, V> toMap(E bean, String keyField, String valueField) {\n        if (bean == null) {\n            return null;\n        }\n        return ImmutableMap.of(\n            (K) Fields.get(bean, keyField), (V) Fields.get(bean, valueField)\n        );\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <K, V, E> Map<K, V> toMap(List<E> beans, String keyField, String valueField) {\n        if (beans == null) {\n            return null;\n        }\n        return beans.stream()\n                    .collect(Collectors.toMap(e -> (K) Fields.get(e, keyField), e -> (V) Fields.get(e, valueField)));\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <K, E> Map<K, E> toMap(List<E> beans, String keyField) {\n        if (beans == null) {\n            return null;\n        }\n        return beans.stream()\n                    .collect(Collectors.toMap(e -> (K) Fields.get(e, keyField), Function.identity()));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/reflect/BeanCopiers.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.reflect;\n\nimport cn.ponfee.commons.util.SynchronizedCaches;\nimport org.apache.commons.lang3.tuple.Pair;\nimport org.springframework.cglib.beans.BeanCopier;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\n/**\n * The bean copier utility based cglib\n * @see <a href=\"https://mapstruct.org/documentation/installation/\">mapstruct官方文档</a>\n * \n * @author Ponfee\n */\npublic class BeanCopiers {\n\n    private static final Map<Pair<Class<?>, Class<?>>, BeanCopier> COPIER_CACHES = new HashMap<>();\n\n    public static BeanCopier get(Class<?> sourceType, Class<?> targetType) {\n        Pair<Class<?>, Class<?>> key = Pair.of(sourceType, targetType);\n        return SynchronizedCaches.get(key, COPIER_CACHES, () -> BeanCopier.create(sourceType, targetType, false));\n    }\n\n    /**\n     * Copy properties from source to target\n     *\n     * <pre>\n     * 1、名称相同而类型不同的属性不会被拷贝\n     * 2、注意：即使源类型是基本类型(int、short、char等)，目标类型是其包装类型(Integer、Short、Character等)，或反之，都不会被拷贝\n     * 3、源类和目标类有相同的属性(两者的getter都存在)，但目标类的setter不存在：创建BeanCopier的时候抛异常\n     * 4、当类型不一致是需要convert进行类型转换：org.springframework.cglib.core.Converter\n     * \n     * BeanUtils/PropertyUtils -> commons-beanutils:commons-beanutils\n     * </pre>\n     * \n     * @param source source object\n     * @param target target object\n     * \n     * @see org.apache.commons.beanutils.BeanUtils#copyProperties(Object, Object);\n     * @see org.apache.commons.beanutils.PropertyUtils#copyProperties(Object, Object);\n     * @see org.springframework.beans.BeanUtils#copyProperties(Object, Object)\n     * @see org.springframework.cglib.beans.BeanCopier#create(Class, Class, boolean)\n     * @see mapstruct\n     */\n    public static void copy(Object source, Object target) {\n        get(source.getClass(), target.getClass()).copy(source, target, null);\n    }\n\n    public static <T> T copy(Object source, Supplier<T> supplier) {\n        T target = supplier.get();\n        copy(source, target);\n        return target;\n    }\n\n    public static <T> T copy(Object source, Class<T> targetType) {\n        T target = ClassUtils.newInstance(targetType);\n        copy(source, target);\n        return target;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/reflect/BeanMaps.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.reflect;\n\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.SynchronizedCaches;\nimport com.google.common.collect.ImmutableList;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.springframework.cglib.beans.BeanMap;\n\nimport java.beans.BeanInfo;\nimport java.beans.Introspector;\nimport java.beans.PropertyDescriptor;\nimport java.lang.reflect.Field;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static com.google.common.base.CaseFormat.*;\n\n/**\n * Utility of Java Bean and Map mutual conversion\n *\n * @author Ponfee\n */\npublic enum BeanMaps {\n\n    /**\n     * Based Cglib\n     */\n    CGLIB() {\n        @Override @SuppressWarnings(\"unchecked\")\n        public Map<String, Object> toMap(Object bean) {\n            if (bean == null) {\n                return null;\n            }\n            return BeanMap.create(bean);\n        }\n\n        @Override\n        public void copyFromMap(Map<String, Object> sourceMap, Object targetBean) {\n            BeanMap.create(targetBean).putAll(sourceMap);\n        }\n    },\n\n    /**\n     * Based Unsafe class\n     */\n    FIELDS() {\n        private final Map<Class<?>, List<Field>> cachedFields = new HashMap<>();\n\n        @Override\n        public Map<String, Object> toMap(Object bean) {\n            if (bean == null) {\n                return null;\n            }\n            List<Field> fields = getFields(bean.getClass());\n            Map<String, Object> map = new HashMap<>(fields.size());\n            fields.forEach(f -> map.put(f.getName(), Fields.get(bean, f)));\n            return map;\n        }\n\n        @Override\n        public void copyFromMap(Map<String, Object> sourceMap, Object targetBean) {\n            Class<?> clazz = targetBean.getClass();\n            for (Field field : getFields(clazz)) {\n                String name = field.getName();\n                if (sourceMap.containsKey(name)) {\n                    Class<?> type = GenericUtils.getFieldActualType(clazz, field);\n                    Fields.put(targetBean, field, ObjectUtils.cast(sourceMap.get(name), type));\n                }\n            }\n        }\n\n        private List<Field> getFields(Class<?> beanType) {\n            return SynchronizedCaches.get(beanType, cachedFields, type -> {\n                List<Field> list = ClassUtils.listFields(type);\n                return CollectionUtils.isEmpty(list) ? Collections.emptyList() : ImmutableList.copyOf(list);\n            });\n        }\n    },\n\n    /**\n     * Based java.beans.Introspector\n     */\n    PROPS() {\n        @Override\n        public Map<String, Object> toMap(Object bean) {\n            try {\n                BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());\n                Map<String, Object> map = new HashMap<>();\n                String name;\n                for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {\n                    if (!\"class\".equals((name = prop.getName()))) { // getClass()\n                        map.put(name, prop.getReadMethod().invoke(bean));\n                    }\n                }\n                return map;\n            } catch (Exception e) {\n                throw new IllegalStateException(e);\n            }\n        }\n\n        @Override\n        public void copyFromMap(Map<String, Object> sourceMap, Object targetBean) {\n            String name;\n            Object value;\n            Class<?> type;\n            try {\n                BeanInfo beanInfo = Introspector.getBeanInfo(targetBean.getClass());\n                for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {\n                    if (\"class\".equals(name = prop.getName())) { // exclude getClass()\n                        continue;\n                    }\n\n                    String name0 = name;\n                    if (   !sourceMap.containsKey(name)\n                        && !sourceMap.containsKey(name = LOWER_CAMEL.to(LOWER_UNDERSCORE, name0))\n                        && !sourceMap.containsKey(name = LOWER_CAMEL.to(LOWER_HYPHEN, name0))\n                    ) {\n                        continue;\n                    }\n\n                    value = sourceMap.get(name);\n                    if ((type = prop.getPropertyType()).isPrimitive() && ObjectUtils.isEmpty(value)) {\n                        continue; // 基本类型时：value为null或为空字符串时跳过\n                    }\n\n                    // set value into bean field\n                    prop.getWriteMethod().invoke(targetBean, ObjectUtils.cast(value, type));\n                }\n            } catch (Exception e) {\n                throw new IllegalStateException(e);\n            }\n        }\n    };\n\n    /**\n     * Returns a map from bean field-value\n     *\n     * @param bean the bean object\n     * @return a map\n     */\n    public abstract Map<String, Object> toMap(Object bean);\n\n    /**\n     * Copies map key-value to bean object field-value\n     *\n     * @param sourceMap  the source map\n     * @param targetBean the target bean\n     */\n    public abstract void copyFromMap(Map<String, Object> sourceMap, Object targetBean);\n\n    /**\n     * Returns a bean object of specified Class<T> instance,\n     * and copy map key-value to bean object field-value\n     *\n     * @param map      the map\n     * @param beanType the type, must be has no args default constructor\n     * @return a bean object of specified Class<T> instance\n     */\n    public final <T> T toBean(Map<String, Object> map, Class<T> beanType) {\n        T bean = ObjectUtils.newInstance(beanType);\n        this.copyFromMap(map, bean);\n        return bean;\n    }\n\n    /**\n     * Copies bean object field-value to map key-value\n     *\n     * @param sourceBean the source bean\n     * @param targetMap  the target map\n     */\n    public final void copyFromBean(Object sourceBean, Map<String, Object> targetMap) {\n        targetMap.putAll(this.toMap(sourceBean));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/reflect/ClassUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.reflect;\n\nimport cn.ponfee.commons.base.PrimitiveTypes;\nimport cn.ponfee.commons.base.tuple.Tuple2;\nimport cn.ponfee.commons.base.tuple.Tuple3;\nimport cn.ponfee.commons.collect.ArrayHashKey;\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.model.Null;\nimport cn.ponfee.commons.base.Predicates;\nimport cn.ponfee.commons.util.Asserts;\nimport cn.ponfee.commons.util.Strings;\nimport cn.ponfee.commons.util.SynchronizedCaches;\nimport cn.ponfee.commons.util.URLCodes;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.springframework.asm.*;\nimport org.springframework.objenesis.ObjenesisHelper;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.net.URL;\nimport java.util.*;\n\n/**\n * 基于asm的Class工具类\n *\n * @author Ponfee\n */\npublic final class ClassUtils {\n\n    private static final Map<Object, Constructor<?>> CONSTRUCTOR_CACHE = new HashMap<>();\n    private static final Map<Object, Method> METHOD_CACHE = new HashMap<>();\n\n    /*\n    public static final Pattern QUALIFIED_CLASS_NAME_PATTERN = Pattern.compile(\"^([a-zA-Z_$][a-zA-Z\\\\d_$]*\\\\.)*[a-zA-Z_$][a-zA-Z\\\\d_$]*$\");\n    private static final GroovyClassLoader GROOVY_CLASS_LOADER = new GroovyClassLoader();\n    public static <T> Class<T> getClass(String text) {\n        String key = DigestUtils.md5Hex(text);\n        Class<?> clazz = SynchronizedCaches.get(key, CLASS_CACHE, () -> {\n            if (QUALIFIED_CLASS_NAME_PATTERN.matcher(text).matches()) {\n                try {\n                    return Class.forName(text);\n                } catch (Exception ignored) {\n                    ignored.printStackTrace();\n                }\n            }\n            try {\n                return GROOVY_CLASS_LOADER.parseClass(text);\n            } catch (Exception ignored) {\n                ignored.printStackTrace();\n                return Null.class;\n            }\n        });\n        return clazz == Null.class ? null : (Class<T>) clazz;\n    }\n    */\n\n    /**\n     * 获取方法的参数名（编译未清除）<p>\n     * ClassUtils.getMethodParamNames(ClassUtils.class.getMethod(\"newInstance\", Class.class, Class.class, Object.class)) -> [type, parameterType, arg]\n     *\n     * @param method the method\n     * @return method args name\n     * @see org.springframework.core.LocalVariableTableParameterNameDiscoverer#getParameterNames(Method)\n     */\n    public static String[] getMethodParamNames(Method method) {\n        // 获取ClassReader\n        ClassReader classReader;\n        try {\n            // 第一种方式(cannot use in jar file)\n            /*String name = getClassFilePath(method.getDeclaringClass());\n            classReader = new ClassReader(new FileInputStream(name));*/\n\n            // 第二种方式（sometimes was wrong）\n            //classReader = new ClassReader(getClassName(method.getDeclaringClass()));\n\n            // 第三种方式\n            Class<?> clazz = method.getDeclaringClass();\n            classReader = new ClassReader(clazz.getResourceAsStream(clazz.getSimpleName() + \".class\"));\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n\n        String[] paramNames = new String[method.getParameterTypes().length];\n        classReader.accept(new ClassVisitor(Opcodes.ASM5, new ClassWriter(ClassWriter.COMPUTE_MAXS)) {\n            @Override\n            public MethodVisitor visitMethod(int access, String name, String desc, String sign, String[] ex) {\n                if (!name.equals(method.getName()) || !isSameType(Type.getArgumentTypes(desc), method.getParameterTypes())) {\n                    return super.visitMethod(access, name, desc, sign, ex); // 方法名相同并且参数个数相同\n                }\n\n                return new MethodVisitor(Opcodes.ASM5, cv.visitMethod(access, name, desc, sign, ex)) {\n                    @Override\n                    public void visitLocalVariable(String name, String desc, String sign, Label start, Label end, int index) {\n                        int i = index;\n                        if (!Modifier.isStatic(method.getModifiers())) {\n                            i -= 1; // 非静态方法第一个参数是“this”\n                        }\n                        if (i >= 0 && i < paramNames.length) {\n                            paramNames[i] = name;\n                        }\n                        super.visitLocalVariable(name, desc, sign, start, end, index);\n                    }\n                };\n            }\n        }, 0);\n\n        return paramNames;\n    }\n\n    /**\n     * 获取方法签名<p>\n     * ClassUtils.getMethodSignature(ClassUtils.class.getMethod(\"newInstance\", Class.class, Class.class, Object.class)) -> public static java.lang.Object cn.ponfee.commons.reflect.ClassUtils.newInstance(java.lang.Class type, java.lang.Class parameterType, java.lang.Object arg)\n     *\n     * @param method the method\n     * @return the method string\n     * @see java.lang.reflect.Method#toString()\n     * @see java.lang.reflect.Method#toGenericString()\n     */\n    public static String getMethodSignature(Method method) {\n        String[] names = getMethodParamNames(method);\n        Class<?>[] types = method.getParameterTypes();\n\n        List<String> params = new ArrayList<>();\n        for (int i = 0; i < types.length; i++) {\n            params.add(getClassName(types[i]) + \" \" + names[i]);\n        }\n\n        return new StringBuilder(Modifier.toString(method.getModifiers() & Modifier.methodModifiers()))\n                .append(' ').append(getClassName(method.getReturnType()))\n                .append(' ').append(getClassName(method.getDeclaringClass()))\n                .append('.').append(method.getName())\n                .append('(').append(Strings.join(params, \",\")).append(')')\n                .toString();\n    }\n\n    /**\n     * Returns the member field(include super class)\n     *\n     * @param clazz the type\n     * @param fieldName the field name\n     * @return member field object\n     */\n    public static Field getField(Class<?> clazz, String fieldName) {\n        if (clazz.isInterface() || clazz == Object.class) {\n            return null;\n        }\n\n        Exception firstOccurException = null;\n        do {\n            try {\n                Field field = clazz.getDeclaredField(fieldName);\n                if (!Modifier.isStatic(field.getModifiers())) {\n                    return field;\n                }\n            } catch (Exception e) {\n                if (firstOccurException == null) {\n                    firstOccurException = e;\n                }\n            }\n            clazz = clazz.getSuperclass();\n        } while (clazz != null && clazz != Object.class);\n\n        // not found\n        throw new RuntimeException(firstOccurException);\n    }\n\n    /**\n     * Returns member field list include super class(exclude transient field)\n     *\n     * @param clazz the class\n     * @return a list filled fields\n     */\n    public static List<Field> listFields(Class<?> clazz) {\n        if (clazz.isInterface() || clazz == Object.class) {\n            return null; // error class args\n        }\n\n        List<Field> list = new ArrayList<>();\n        do {\n            try {\n                for (Field field : clazz.getDeclaredFields()) {\n                    int mdf = field.getModifiers();\n                    if (!Modifier.isStatic(mdf) && !Modifier.isTransient(mdf)) {\n                        list.add(field);\n                    }\n                }\n            } catch (Exception ignored) {\n                // ignored\n            }\n            clazz = clazz.getSuperclass();\n        } while (clazz != null && clazz != Object.class);\n\n        return list;\n    }\n\n    /**\n     * Returns the static field, find in class pointer chain\n     *\n     * @param clazz the clazz\n     * @param staticFieldName the static field name\n     * @return static field object\n     */\n    public static Tuple2<Class<?>, Field> getStaticFieldInClassChain(Class<?> clazz, String staticFieldName) {\n        if (clazz == Object.class) {\n            return null;\n        }\n\n        Exception firstOccurException = null;\n        Queue<Class<?>> queue = Collects.newLinkedList(clazz);\n        while (!queue.isEmpty()) {\n            for (int i = queue.size(); i > 0; i--) {\n                Class<?> type = queue.poll();\n                try {\n                    Field field = type.getDeclaredField(staticFieldName);\n                    if (Modifier.isStatic(field.getModifiers())) {\n                        return Tuple2.of(type, field);\n                    }\n                } catch (Exception e) {\n                    if (firstOccurException == null) {\n                        firstOccurException = e;\n                    }\n                }\n                // 可能是父类/父接口定义的属性（如：Tuple1.HASH_FACTOR，非继承，而是查找Class的指针链）\n                if (type.getSuperclass() != Object.class) {\n                    queue.offer(type.getSuperclass());\n                }\n                Arrays.stream(type.getInterfaces()).forEach(queue::offer);\n            }\n        }\n\n        // not found\n        throw new RuntimeException(firstOccurException);\n    }\n\n    /**\n     * Returns the static field\n     *\n     * @param clazz the clazz\n     * @param staticFieldName the static field name\n     * @return static field object\n     */\n    public static Field getStaticField(Class<?> clazz, String staticFieldName) {\n        if (clazz == Object.class) {\n            return null;\n        }\n        try {\n            Field field = clazz.getDeclaredField(staticFieldName);\n            if (Modifier.isStatic(field.getModifiers())) {\n                return field;\n            } else {\n                throw new RuntimeException(\"Non-static field \" + getClassName(clazz) + \"#\" + staticFieldName);\n            }\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 获取类名称<p>\n     * ClassUtils.getClassName(ClassUtils.class)  ->  cn.ponfee.commons.reflect.ClassUtils\n     *\n     * @param clazz the class\n     * @return class full name\n     */\n    public static String getClassName(Class<?> clazz) {\n        String name = clazz.getCanonicalName();\n        if (name == null) {\n            name = clazz.getName();\n        }\n\n        return name;\n    }\n\n    /**\n     * 包名称转目录路径名<p>\n     * getPackagePath(\"cn.ponfee.commons.reflect\")  ->  code/ponfee/commons/reflect\n     *\n     * @param packageName the package name\n     * @return\n     * @see org.springframework.util.ClassUtils#convertClassNameToResourcePath\n     */\n    public static String getPackagePath(String packageName) {\n        return packageName.replace('.', '/') + \"/\";\n    }\n\n    /**\n     * 包名称转目录路径名<p>\n     * ClassUtils.getPackagePath(ClassUtils.class)  ->  code/ponfee/commons/reflect\n     *\n     * @param clazz the class\n     * @return spec class file path\n     */\n    public static String getPackagePath(Class<?> clazz) {\n        String className = getClassName(clazz);\n        if (className.indexOf('.') < 0) {\n            return \"\"; // none package name\n        }\n\n        return getPackagePath(className.substring(0, className.lastIndexOf('.')));\n    }\n\n    /**\n     * 获取类文件的路径（文件）<p>\n     * ClassUtils.getClassFilePath(ClassUtils.class)  ->  /Users/ponfee/scm/github/commons-core/target/classes/code/ponfee/commons/reflect/ClassUtils.class<p>\n     * ClassUtils.getClassFilePath(org.apache.commons.lang3.StringUtils.class) ->  /Users/ponfee/.m2/repository/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar!/org/apache/commons/lang3/StringUtils.class\n     *\n     * @param clazz the class\n     * @return spec class file path\n     */\n    public static String getClassFilePath(Class<?> clazz) {\n        URL url = clazz.getProtectionDomain().getCodeSource().getLocation();\n        String path = new File(URLCodes.decodeURI(url.getPath(), Files.UTF_8)).getAbsolutePath();\n\n        if (path.toLowerCase().endsWith(\".jar\")) {\n            path += \"!\";\n        }\n        return path + File.separator + getClassName(clazz).replace('.', File.separatorChar) + \".class\";\n    }\n\n    /**\n     * 获取指定类的类路径（目录）<p>\n     * ClassUtils.getClasspath(ClassUtils.class)   ->  /Users/ponfee/scm/github/commons-core/target/classes/<p>\n     * ClassUtils.getClasspath(org.apache.commons.lang3.StringUtils.class)  ->  /Users/ponfee/.m2/repository/org/apache/commons/commons-lang3/3.12.0/\n     *\n     * @param clazz the class\n     * @return spec classpath\n     */\n    public static String getClasspath(Class<?> clazz) {\n        URL url = clazz.getProtectionDomain().getCodeSource().getLocation();\n        String path = URLCodes.decodeURI(url.getPath(), Files.UTF_8);\n        if (path.toLowerCase().endsWith(\".jar\")) {\n            path = path.substring(0, path.lastIndexOf(\"/\") + 1);\n        }\n        return new File(path).getAbsolutePath() + File.separator;\n    }\n\n    /**\n     * 获取当前的类路径（目录）<p>\n     * ClassUtils.getClasspath()  ->  /Users/ponfee/scm/github/commons-core/target/test-classes/\n     *\n     * @return current main classpath\n     */\n    public static String getClasspath() {\n        String path = Thread.currentThread().getContextClassLoader().getResource(\"\").getPath();\n        path = URLCodes.decodeURI(new File(path).getAbsolutePath(), Files.UTF_8);\n        return path + File.separator;\n    }\n\n    // -----------------------------------------------------------------------------constructor & instance\n    @SuppressWarnings(\"unchecked\")\n    public static <T> Constructor<T> getConstructor(Class<T> type, Class<?>... parameterTypes) {\n        boolean noArgs = ArrayUtils.isEmpty(parameterTypes);\n        Object key = noArgs ? type : Tuple2.of(type, ArrayHashKey.of((Object[]) parameterTypes));\n        Constructor<T> constructor = (Constructor<T>) SynchronizedCaches.get(key, CONSTRUCTOR_CACHE, () -> {\n            try {\n                return getConstructor0(type, parameterTypes);\n            } catch (Exception ignored) {\n                // No such constructor, use placeholder\n                return Null.BROKEN_CONSTRUCTOR;\n            }\n        });\n        return constructor == Null.BROKEN_CONSTRUCTOR ? null : constructor;\n    }\n\n    public static <T> T newInstance(Constructor<T> constructor) {\n        return newInstance(constructor, null);\n    }\n\n    public static <T> T newInstance(Constructor<T> constructor, Object[] args) {\n        checkObjectArray(args);\n\n        if (!constructor.isAccessible()) {\n            constructor.setAccessible(true);\n        }\n        try {\n            return ArrayUtils.isEmpty(args) ? constructor.newInstance() : constructor.newInstance(args);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static <T> T newInstance(Class<T> type, Class<?>[] parameterTypes, Object[] args) {\n        checkObjectArray(args);\n        checkSameLength(parameterTypes, args);\n        if (ArrayUtils.isEmpty(parameterTypes)) {\n            // no args constructor\n            return newInstance(type, null);\n        }\n\n        Constructor<T> constructor = getConstructor(type, parameterTypes);\n        if (constructor == null) {\n            throw new RuntimeException(\"No such constructor: \" + getClassName(type) + toString(parameterTypes));\n        }\n        return newInstance(constructor, args);\n    }\n\n    public static <T> T newInstance(Class<T> type) {\n        return newInstance(type, null);\n    }\n\n    /**\n     * 泛型参数的构造函数需要使用 {{@link #newInstance(Class, Class[], Object[])}} <p>\n     * ClassUtils.newInstance(Tuple3.class, new Object[]{1, 2, 3}) <p>\n     * ClassUtils.newInstance(Tuple2.class, new Object[]{new String[]{\"a\", \"b\"}, new Integer[]{1, 2}}) <p>\n     *\n     * @param type the type\n     * @param args the args\n     * @param <T>\n     * @return\n     */\n    public static <T> T newInstance(Class<T> type, Object[] args) {\n        checkObjectArray(args);\n        if (ArrayUtils.isEmpty(args)) {\n            Constructor<T> constructor = getConstructor(type);\n            return constructor != null ? newInstance(constructor, null) : ObjenesisHelper.newInstance(type);\n        }\n\n        Class<?>[] parameterTypes = parseParameterTypes(args);\n        Constructor<T> constructor = obtainConstructor(type, parameterTypes);\n        if (constructor == null) {\n            throw new RuntimeException(\"Not found constructor: \" + getClassName(type) + toString(parameterTypes));\n        }\n        return newInstance(constructor, args);\n    }\n\n    // -------------------------------------------------------------------------------------------method & invoke\n    public static Method getMethod(Object caller, String methodName, Class<?>... parameterTypes) {\n        Tuple2<Class<?>, Predicates> tuple = obtainClass(caller);\n        Class<?> type = tuple.a;\n        boolean noArgs = ArrayUtils.isEmpty(parameterTypes);\n        Object key = noArgs ? Tuple2.of(type, methodName) : Tuple3.of(type, methodName, ArrayHashKey.of((Object[]) parameterTypes));\n        Method method = SynchronizedCaches.get(key, METHOD_CACHE, () -> {\n            try {\n                Method m = getMethod0(type, methodName, parameterTypes);\n                return (tuple.b.equals(Modifier.isStatic(m.getModifiers())) && !m.isSynthetic()) ? m : null;\n            } catch (Exception ignored) {\n                // No such method, use placeholder\n                return Null.BROKEN_METHOD;\n            }\n        });\n        return method == Null.BROKEN_METHOD ? null : method;\n    }\n\n    public static <T> T invoke(Object caller, Method method) {\n        return invoke(caller, method, null);\n    }\n\n    public static <T> T invoke(Object caller, Method method, Object[] args) {\n        checkObjectArray(args);\n        if (!method.isAccessible()) {\n            method.setAccessible(true);\n        }\n        try {\n            return (T) (ArrayUtils.isEmpty(args) ? method.invoke(caller) : method.invoke(caller, args));\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static <T> T invoke(Object caller, String methodName) {\n        return invoke(caller, methodName, null, null);\n    }\n\n    public static <T> T invoke(Object caller, String methodName, Class<?>[] parameterTypes, Object[] args) {\n        checkObjectArray(args);\n        checkSameLength(parameterTypes, args);\n        Method method = getMethod(caller, methodName, parameterTypes);\n        if (method == null) {\n            throw new RuntimeException(\n                \"No such method: \" + getClassName(caller.getClass()) + \"#\" + methodName + toString(parameterTypes)\n            );\n        }\n        return invoke(caller, method, args);\n    }\n\n    public static <T> T invoke(Object caller, String methodName, Object[] args) {\n        checkObjectArray(args);\n        if (ArrayUtils.isEmpty(args)) {\n            return invoke(caller, methodName, null, null);\n        }\n\n        Class<?>[] parameterTypes = parseParameterTypes(args);\n        Method method = obtainMethod(caller, methodName, parameterTypes);\n        if (method == null) {\n            Class<?> clazz = (caller instanceof Class<?>) ? (Class<?>) caller : caller.getClass();\n            throw new RuntimeException(\"Not found method: \" + getClassName(clazz) + \"#\" + methodName + toString(parameterTypes));\n        }\n        return invoke(caller, method, args);\n    }\n\n    public static Tuple2<Class<?>, Predicates> obtainClass(Object obj) {\n        if (obj instanceof Class<?> && obj != Class.class) {\n            // 静态方法\n            // 普通Class类实例(如String.class)：只处理其所表示类的静态方法，如“String.valueOf(1)”。不支持Class类中的实例方法，如“String.class.getName()”\n            return Tuple2.of((Class<?>) obj, Predicates.Y);\n        } else {\n            // 实例方法\n            // 对于Class.class对象：只处理Class类中的实例方法，如“Class.class.getName()”。不支持Class类中的静态方法，如“Class.forName(\"cn.ponfee.commons.base.tuple.Tuple0\")”\n            return Tuple2.of(obj.getClass(), Predicates.N);\n        }\n    }\n\n    // -------------------------------------------------------------------------------------------private methods\n    private static void checkSameLength(Object[] a, Object[] b) {\n        if (ArrayUtils.isEmpty(a) && ArrayUtils.isEmpty(b)) {\n            return;\n        }\n        if (a.length != b.length) {\n            throw new RuntimeException(\"Two array are different length: \" + a.length + \", \" + b.length);\n        }\n    }\n\n    private static void checkObjectArray(Object[] array) {\n        if (array != null && array.getClass() != Object[].class) {\n            throw new RuntimeException(\"Args must Object[] type, but actual is \" + array.getClass().getSimpleName());\n        }\n    }\n\n    private static Class<?>[] parseParameterTypes(Object[] args) {\n        Asserts.isTrue(ArrayUtils.isNotEmpty(args), \"Should be always non empty.\");\n        Class<?>[] parameterTypes = new Class<?>[args.length];\n        for (int i = 0, n = args.length; i < n; i++) {\n            parameterTypes[i] = (args[i] == null) ? null : args[i].getClass();\n        }\n        return parameterTypes;\n    }\n\n    private static Method getMethod0(Class<?> type, String methodName, Class<?>[] parameterTypes) throws Exception {\n        try {\n            return type.getMethod(methodName, parameterTypes);\n        } catch (Exception e) {\n            try {\n                return type.getDeclaredMethod(methodName, parameterTypes);\n            } catch (Exception ignored) {\n                // ignored\n            }\n            throw e;\n        }\n    }\n\n    private static <T> Constructor<T> getConstructor0(Class<T> type, Class<?>[] parameterTypes) throws Exception {\n        try {\n            return type.getConstructor(parameterTypes);\n        } catch (Exception e) {\n            try {\n                return type.getDeclaredConstructor(parameterTypes);\n            } catch (Exception ignored) {\n                // ignored\n            }\n            throw e;\n        }\n    }\n\n    // -----------------------------------------------obtain constructor & method\n    private static <T> Constructor<T> obtainConstructor(Class<T> type, Class<?>[] actualTypes) {\n        Asserts.isTrue(ArrayUtils.isNotEmpty(actualTypes), \"Should be always non empty.\");\n        Constructor<T> constructor = obtainConstructor((Constructor<T>[]) type.getConstructors(), actualTypes);\n        if (constructor != null) {\n            return constructor;\n        }\n        return obtainConstructor((Constructor<T>[]) type.getDeclaredConstructors(), actualTypes);\n    }\n\n    private static <T> Constructor<T> obtainConstructor(Constructor<T>[] constructors, Class<?>[] actualTypes) {\n        if (ArrayUtils.isEmpty(constructors)) {\n            return null;\n        }\n        for (Constructor<T> constructor : constructors) {\n            if (matches(constructor.getParameterTypes(), actualTypes)) {\n                return constructor;\n            }\n        }\n        return null;\n    }\n\n    private static Method obtainMethod(Object caller, String methodName, Class<?>[] actualTypes) {\n        Asserts.isTrue(ArrayUtils.isNotEmpty(actualTypes), \"Should be always non empty.\");\n        Tuple2<Class<?>, Predicates> tuple = obtainClass(caller);\n        // getMethod：获取类的所有public方法，包括自身的和从父类、接口继承的\n        Method method = obtainMethod(tuple.a.getMethods(), methodName, tuple.b, actualTypes);\n        if (method != null) {\n            return method;\n        }\n        // getDeclaredMethods：获取类自身声明的方法，包含public、protected和private\n        return obtainMethod(tuple.a.getDeclaredMethods(), methodName, tuple.b, actualTypes);\n    }\n\n    private static Method obtainMethod(Method[] methods, String methodName,\n                                       Predicates flag, Class<?>[] actualTypes) {\n        if (ArrayUtils.isEmpty(methods)) {\n            return null;\n        }\n        for (Method method : methods) {\n            boolean matches = method.getName().equals(methodName)\n                           && !method.isSynthetic()\n                           && flag.equals(Modifier.isStatic(method.getModifiers()))\n                           && matches(method.getParameterTypes(), actualTypes);\n            if (matches) {\n                return method;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 方法匹配\n     *\n     * @param definedTypes 方法体中定义的参数类型\n     * @param actualTypes 调用方法实际传入的参数类型\n     * @return\n     */\n    private static boolean matches(Class<?>[] definedTypes, Class<?>[] actualTypes) {\n        if (definedTypes.length != actualTypes.length) {\n            return false;\n        }\n        for (int i = 0, n = definedTypes.length; i < n; i++) {\n            Class<?> definedType = definedTypes[i], actualType = actualTypes[i];\n            if (definedType.isPrimitive()) {\n                // 方法参数为基本数据类型\n                PrimitiveTypes ept = PrimitiveTypes.ofPrimitive(definedType);\n                PrimitiveTypes apt = PrimitiveTypes.ofPrimitiveOrWrapper(actualType);\n                if (apt == null || !apt.isCastable(ept)) {\n                    return false;\n                }\n            } else if (actualType != null && !definedType.isAssignableFrom(actualType)) {\n                // actualType为空则可转任何对象类型（非基本数据类型）\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * 比较参数类型是否一致<p>\n     *\n     * @param types   asm的类型({@link Type})\n     * @param classes java 类型({@link Class})\n     * @return {@code true} if the Type array each of equals the Class array\n     */\n    private static boolean isSameType(Type[] types, Class<?>[] classes) {\n        if (types.length != classes.length) {\n            return false;\n        }\n\n        for (int i = 0; i < types.length; i++) {\n            if (!Type.getType(classes[i]).equals(types[i])) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    private static String toString(Class<?>[] parameterTypes) {\n        return ArrayUtils.isEmpty(parameterTypes)\n            ? \"()\"\n            : \"(\" + Strings.join(Arrays.asList(parameterTypes), \", \") + \")\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/reflect/Fields.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.reflect;\n\nimport cn.ponfee.commons.base.tuple.Tuple2;\nimport cn.ponfee.commons.base.Predicates;\nimport sun.misc.Unsafe;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\n\n/**\n * 高效的反射工具类（基于sun.misc.Unsafe）\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"restriction\")\npublic final class Fields {\n\n    // sun.misc.Unsafe.getUnsafe() will be throws \"java.lang.SecurityException: Unsafe\"\n    // caller code must use in BootstrapClassLoader to load (JAVA_HOME/jre/lib)\n    // but application code load by sun.misc.Launcher.AppClassLoader\n    private static final Unsafe UNSAFE;\n    static {\n        try {\n            Field f = Unsafe.class.getDeclaredField(\"theUnsafe\");\n            f.setAccessible(true);\n            UNSAFE = (Unsafe) f.get(null); // If the underlying field is a static field, \n                                           // the {@code obj} argument is ignored; it may be null.\n                                           // Set static field's value {@code f.set(null, value)}\n\n            // restore the accessible value\n            f.setAccessible(false);\n        } catch (NoSuchFieldException | IllegalAccessException e) {\n            throw new RuntimeException(\"failed to get unsafe instance\", e);\n        }\n    }\n\n    private static final long BASE_OFFSET = UNSAFE.arrayBaseOffset(Object[].class); // 16\n    private static final int  INDEX_SCALE = UNSAFE.arrayIndexScale(Object[].class); // 4\n    private static final int ADDRESS_SIZE = UNSAFE.addressSize();                   // 8(64 bit cpu)\n\n    public static long addressOf(Object obj) {\n        return addressOf(new Object[]{obj}, 0);\n    }\n\n    /**\n     * Returns the object reference pointer address of jvm\n     *\n     * @param array the obj array\n     * @param index the array position\n     * @return reference pointer address\n     */\n    public static long addressOf(Object[] array, int index) {\n        switch (INDEX_SCALE) {\n            case 4:\n                return (UNSAFE.getInt(array, BASE_OFFSET + (long) index * INDEX_SCALE) & 0xFFFFFFFFL) * ADDRESS_SIZE;\n            case 8:\n                return UNSAFE.getLong(array, BASE_OFFSET + (long) index * INDEX_SCALE) * ADDRESS_SIZE;\n            default:\n                throw new Error(\"Unsupported address size: \" + INDEX_SCALE);\n        }\n    }\n\n    /**\n     * put field to target object\n     * @param target 目标对象\n     * @param name 字段名\n     * @param value 字段值\n     */\n    public static void put(Object target, String name, Object value) {\n        try {\n            Tuple2<?, Field> tuple = getField(target, name);\n            put(tuple.a, tuple.b, value);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * put field to target object if value is null\n     * @param target 目标对象\n     * @param name 字段名\n     * @param value 字段值\n     */\n    public static void putIfNull(Object target, String name, Object value) {\n        try {\n            Tuple2<?, Field> tuple = getField(target, name);\n            putIfNull(tuple.a, tuple.b, value);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * put field to target object if value is null\n     * @param target\n     * @param field\n     * @param value\n     */\n    public static void putIfNull(Object target, Field field, Object value) {\n        if (get(target, field) == null) {\n            put(target, field, value);\n        }\n    }\n\n    /**\n     * put field to target object\n     * @param target target object\n     * @param field object field\n     * @param value field value\n     */\n    public static void put(Object target, Field field, Object value) {\n        //field.setAccessible(true); unnecessary set accessible to true\n        long fieldOffset = getFieldOffset(field);\n\n        Class<?> type = GenericUtils.getFieldActualType(target.getClass(), field);\n        if (Boolean.TYPE.equals(type)) {\n            UNSAFE.putBoolean(target, fieldOffset, (boolean) value);\n        } else if (Byte.TYPE.equals(type)) {\n            UNSAFE.putByte(target, fieldOffset, (byte) value);\n        } else if (Character.TYPE.equals(type)) {\n            UNSAFE.putChar(target, fieldOffset, (char) value);\n        } else if (Short.TYPE.equals(type)) {\n            UNSAFE.putShort(target, fieldOffset, (short) value);\n        } else if (Integer.TYPE.equals(type)) {\n            UNSAFE.putInt(target, fieldOffset, (int) value);\n        } else if (Long.TYPE.equals(type)) {\n            UNSAFE.putLong(target, fieldOffset, (long) value);\n        } else if (Double.TYPE.equals(type)) {\n            UNSAFE.putDouble(target, fieldOffset, (double) value);\n        } else if (Float.TYPE.equals(type)) {\n            UNSAFE.putFloat(target, fieldOffset, (float) value);\n        } else {\n            UNSAFE.putObject(target, fieldOffset, value);\n        }\n    }\n\n    /**\n     * get field of target object\n     * @param target 目标对象\n     * @param name field name\n     * @return the field value\n     */\n    public static Object get(Object target, String name) {\n        try {\n            Tuple2<?, Field> tuple = getField(target, name);\n            return get(tuple.a, tuple.b);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * get field of target object\n     * @param target 目标对象\n     * @param field  字段\n     * @return\n     */\n    public static Object get(Object target, Field field) {\n        long fieldOffset = getFieldOffset(field);\n        Class<?> type = GenericUtils.getFieldActualType(target.getClass(), field);\n        if (Boolean.TYPE.equals(type)) {\n            return UNSAFE.getBoolean(target, fieldOffset);\n        } else if (Byte.TYPE.equals(type)) {\n            return UNSAFE.getByte(target, fieldOffset);\n        } else if (Character.TYPE.equals(type)) {\n            return UNSAFE.getChar(target, fieldOffset);\n        } else if (Short.TYPE.equals(type)) {\n            return UNSAFE.getShort(target, fieldOffset);\n        } else if (Integer.TYPE.equals(type)) {\n            return UNSAFE.getInt(target, fieldOffset);\n        } else if (Long.TYPE.equals(type)) {\n            return UNSAFE.getLong(target, fieldOffset);\n        } else if (Double.TYPE.equals(type)) {\n            return UNSAFE.getDouble(target, fieldOffset);\n        } else if (Float.TYPE.equals(type)) {\n            return UNSAFE.getFloat(target, fieldOffset);\n        } else {\n            return UNSAFE.getObject(target, fieldOffset);\n        }\n    }\n\n    /**\n     * put of volatile\n     * @param target\n     * @param field\n     * @param value\n     */\n    public static void putVolatile(Object target, Field field, Object value) {\n        //field.setAccessible(true); unnecessary set accessible to true\n        long fieldOffset = getFieldOffset(field);\n\n        Class<?> type = GenericUtils.getFieldActualType(target.getClass(), field);\n        if (Boolean.TYPE.equals(type)) {\n            UNSAFE.putBooleanVolatile(target, fieldOffset, (boolean) value);\n        } else if (Byte.TYPE.equals(type)) {\n            UNSAFE.putByteVolatile(target, fieldOffset, (byte) value);\n        } else if (Character.TYPE.equals(type)) {\n            UNSAFE.putCharVolatile(target, fieldOffset, (char) value);\n        } else if (Short.TYPE.equals(type)) {\n            UNSAFE.putShortVolatile(target, fieldOffset, (short) value);\n        } else if (Integer.TYPE.equals(type)) {\n            UNSAFE.putIntVolatile(target, fieldOffset, (int) value);\n        } else if (Long.TYPE.equals(type)) {\n            UNSAFE.putLongVolatile(target, fieldOffset, (long) value);\n        } else if (Double.TYPE.equals(type)) {\n            UNSAFE.putDoubleVolatile(target, fieldOffset, (double) value);\n        } else if (Float.TYPE.equals(type)) {\n            UNSAFE.putFloatVolatile(target, fieldOffset, (float) value);\n        } else {\n            UNSAFE.putObjectVolatile(target, fieldOffset, value);\n        }\n    }\n\n    /**\n     * 支持volatile语义\n     * @param target\n     * @param name\n     * @return\n     */\n    public static Object getVolatile(Object target, String name) {\n        try {\n            Tuple2<?, Field> tuple = getField(target, name);\n            return getVolatile(tuple.a, tuple.b);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 支持volatile语义\n     * @param target\n     * @param field\n     * @return\n     */\n    public static Object getVolatile(Object target, Field field) {\n        long fieldOffset = getFieldOffset(field);\n        Class<?> type = GenericUtils.getFieldActualType(target.getClass(), field);\n        if (Boolean.TYPE.equals(type)) {\n            return UNSAFE.getBooleanVolatile(target, fieldOffset);\n        } else if (Byte.TYPE.equals(type)) {\n            return UNSAFE.getByteVolatile(target, fieldOffset);\n        } else if (Character.TYPE.equals(type)) {\n            return UNSAFE.getCharVolatile(target, fieldOffset);\n        } else if (Short.TYPE.equals(type)) {\n            return UNSAFE.getShortVolatile(target, fieldOffset);\n        } else if (Integer.TYPE.equals(type)) {\n            return UNSAFE.getIntVolatile(target, fieldOffset);\n        } else if (Long.TYPE.equals(type)) {\n            return UNSAFE.getLongVolatile(target, fieldOffset);\n        } else if (Double.TYPE.equals(type)) {\n            return UNSAFE.getDoubleVolatile(target, fieldOffset);\n        } else if (Float.TYPE.equals(type)) {\n            return UNSAFE.getFloatVolatile(target, fieldOffset);\n        } else {\n            return UNSAFE.getObjectVolatile(target, fieldOffset);\n        }\n    }\n\n    // ----------------------------------------------------------------private methods\n    private static Tuple2<?, Field> getField(Object obj, String name) {\n        Tuple2<Class<?>, Predicates> tuple = ClassUtils.obtainClass(obj);\n        if (tuple.b.state()) {\n            // static\n            // field.get(null);\n            // field.set(null, value); 使用field设置final属性会报错，只能使用Unsafe\n            return ClassUtils.getStaticFieldInClassChain(tuple.a, name);\n            //return Tuple2.of(obj, ClassUtils.getStaticField(tuple.a, name));\n        } else {\n            // member\n            return Tuple2.of(obj, ClassUtils.getField(tuple.a, name));\n        }\n    }\n\n    private static long getFieldOffset(Field field) {\n        return Modifier.isStatic(field.getModifiers())\n             ? UNSAFE.staticFieldOffset(field)\n             : UNSAFE.objectFieldOffset(field);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/reflect/GenericUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.reflect;\n\nimport cn.ponfee.commons.util.SynchronizedCaches;\nimport com.google.common.base.Preconditions;\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.lang.reflect.*;\nimport java.util.*;\nimport java.util.Map.Entry;\n\n/**\n * 泛型工具类\n * \n * https://segmentfault.com/a/1190000018319217\n * \n * @author Ponfee\n */\npublic final class GenericUtils {\n\n    private static final Map<Class<?>, Map<String, Class<?>>> VARIABLE_TYPE_MAPPING = new HashMap<>();\n\n    /**\n     * map泛型协变\n     * @param origin\n     * @return\n     */\n    public static Map<String, String> covariant(Map<String, ?> origin) {\n        if (origin == null) {\n            return null;\n        }\n\n        Map<String, String> target = new HashMap<>(origin.size());\n        for (Entry<String, ?> entry : origin.entrySet()) {\n            target.put(entry.getKey(), Objects.toString(entry.getValue(), null));\n        }\n        return target;\n    }\n\n    public static Map<String, String> covariant(Properties properties) {\n        return (Map) properties;\n    }\n    \n    // ----------------------------------------------------------------------------class actual type argument\n    /**\n     * 获取泛型的实际类型参数\n     * \n     * @param clazz\n     * @return\n     */\n    public static <T> Class<T> getActualTypeArgument(Class<?> clazz) {\n        return getActualTypeArgument(clazz, 0);\n    }\n\n    /**\n     * public class GenericClass extends GenericSuperClass<Long,Integer,...,String> implements GenericInterface<String,Short,..,Long> {}\n     * \n     * @param clazz\n     * @param genericArgsIndex\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> Class<T> getActualTypeArgument(Class<?> clazz, int genericArgsIndex) {\n        int index = 0;\n        for (Type type : getGenericTypes(clazz)) {\n            if (type instanceof ParameterizedType) {\n                Type[] acts = ((ParameterizedType) type).getActualTypeArguments();\n                if (acts.length + index < genericArgsIndex) {\n                    index += acts.length;\n                } else {\n                    return getActualType(null, acts[genericArgsIndex - index]);\n                }\n            }\n        }\n\n        return (Class<T>) Object.class;\n    }\n\n    // ----------------------------------------------------------------------------method actual arg type argument\n    public static <T> Class<T> getActualArgTypeArgument(Method method, int methodArgsIndex) {\n        return getActualArgTypeArgument(method, methodArgsIndex, 0);\n    }\n\n    /**\n     * public void genericMethod(List<Long> list, Map<String, String> map){}\n     * \n     * @param method            方法对象\n     * @param methodArgsIndex 方法参数索引号\n     * @param genericArgsIndex  泛型参数索引号\n     * @return\n     */\n    public static <T> Class<T> getActualArgTypeArgument(Method method, int methodArgsIndex, int genericArgsIndex) {\n        return getActualTypeArgument(method.getGenericParameterTypes()[methodArgsIndex], genericArgsIndex);\n    }\n\n    // ----------------------------------------------------------------------------method actual return type argument\n    public static <T> Class<T> getActualReturnTypeArgument(Method method) {\n        return getActualReturnTypeArgument(method, 0);\n    }\n\n    /**\n     * public List<String> genericMethod(){}\n     * \n     * @param method the method\n     * @param genericArgsIndex the generic argument index\n     * @return\n     */\n    public static <T> Class<T> getActualReturnTypeArgument(Method method, int genericArgsIndex) {\n        return getActualTypeArgument(method.getGenericReturnType(), genericArgsIndex);\n    }\n\n    // ----------------------------------------------------------------------------\n    public static <T> Class<T> getActualTypeArgument(Field field) {\n        return getActualTypeArgument(field, 0);\n    }\n\n    /**\n     * private List<Long> list; -> Long\n     * \n     * @param field            the class field\n     * @param genericArgsIndex the genericArgsIndex\n     * @return\n     */\n    public static <T> Class<T> getActualTypeArgument(Field field, int genericArgsIndex) {\n        return getActualTypeArgument(field.getGenericType(), genericArgsIndex);\n    }\n\n    // -------------------------------------------------------------------get actual variable type\n    public static <T> Class<T> getFieldActualType(Class<?> clazz, String fieldName) {\n        Field field = ClassUtils.getField(clazz, fieldName);\n        if (field == null) {\n            throw new IllegalArgumentException(\"Type \" + ClassUtils.getClassName(clazz) + \" not exists field '\" + fieldName + \"'\");\n        }\n        return getFieldActualType(clazz, field);\n    }\n\n    /**\n     * <pre>{@code\n     * public abstract class BaseEntity<I> {\n     *   private I id;\n     * }\n     * }</pre>\n     *\n     * <pre>{@code\n     * public class BeanClass extends BaseEntity<String> {}\n     * }</pre>\n     *\n     * @param clazz the sub class\n     * @param field the super class defined field\n     * @return a Class of field actual type\n     */\n    public static <T> Class<T> getFieldActualType(Class<?> clazz, Field field) {\n        return Modifier.isStatic(field.getModifiers())\n             ? (Class<T>) field.getType()\n             : getActualType(clazz, field.getGenericType());\n    }\n\n    /**\n     * Returns method arg actual type\n     * <pre>{@code\n     * public abstract class ClassA<T> {\n     *   public void method(T arg) {}\n     * }\n     * }</pre>\n     * <pre>{@code\n     * public class ClassB extends classA<String>{}\n     * }</pre>\n     * \n     * @param clazz            the sub class\n     * @param method           the super class defined method\n     * @param methodArgsIndex  the method arg index\n     * @return a Class of method arg actual type\n     */\n    public static <T> Class<T> getMethodArgActualType(Class<?> clazz, Method method, int methodArgsIndex) {\n        return getActualType(clazz, method.getGenericParameterTypes()[methodArgsIndex]);\n    }\n\n    /**\n     * Returns method return actual type\n     * <pre>{@code\n     * public abstract class ClassA<T> {\n     *   public T method() {}\n     * }\n     * }</pre>\n     * <pre>{@code\n     * public class ClassB extends classA<String>{}\n     * }</pre>\n     *\n     * @param clazz  the sub class\n     * @param method the super class defined method\n     * @return a Class of method return actual type\n     */\n    public static <T> Class<T> getMethodReturnActualType(Class<?> clazz, Method method) {\n        return getActualType(clazz, method.getGenericReturnType());\n    }\n\n    // public class ClassA extends ClassB<Map<U,V>> implements interfaceC<List<X>>, interfaceD<Y> {}\n    public static List<Type> getGenericTypes(Class<?> clazz) {\n        if (clazz == null || clazz == Object.class) {\n            return Collections.emptyList();\n        }\n        List<Type> types = new ArrayList<>();\n        if (!clazz.isInterface()) {\n            types.add(clazz.getGenericSuperclass()); // Map<U,V>\n        }\n        Collections.addAll(types, clazz.getGenericInterfaces()); // List<X>, Y\n        return types;\n    }\n\n    public static Map<String, Class<?>> getActualTypeVariableMapping(Class<?> clazz) {\n        Map<String, Class<?>> result = new HashMap<>();\n        for (Type type : getGenericTypes(clazz)) {\n            resolveMapping(result, type);\n        }\n        return result.isEmpty() ? Collections.emptyMap() : result;\n    }\n\n    public static Class<?> getRawType(Type type) {\n        if (type instanceof Class<?>) {\n            return (Class<?>) type;\n        } else if (type instanceof ParameterizedType) {\n            // cn.ponfee.commons.tree.NodePath<java.lang.Integer>\n            return (Class<?>) ((ParameterizedType) type).getRawType();\n        } else {\n            throw new UnsupportedOperationException(\"Unsupported type: \" + type);\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> Class<T> getActualTypeArgument(Type type, int genericArgsIndex) {\n        Preconditions.checkArgument(genericArgsIndex >= 0, \"Generic args index cannot be negative.\");\n        if (!(type instanceof ParameterizedType)) {\n            return (Class<T>) Object.class;\n        }\n\n        Type[] types = ((ParameterizedType) type).getActualTypeArguments();\n        return genericArgsIndex >= types.length \n             ? (Class<T>) Object.class \n             : getActualType(null, types[genericArgsIndex]);\n    }\n\n    // -------------------------------------------------------------------private methods\n\n    @SuppressWarnings(\"unchecked\")\n    private static <T> Class<T> getActualType(Class<?> clazz, Type type) {\n        if (type instanceof Class<?>) {\n            // private String name;\n            return (Class<T>) type;\n        } else if (type instanceof ParameterizedType) {\n            // public class Sup<E> {\n            //   private List<E>      list1; -> java.util.List\n            //   private List<String> list2; -> java.util.List\n            // }\n            return getActualType(clazz, ((ParameterizedType) type).getRawType());\n        } else if (type instanceof GenericArrayType) {\n            // private E[] array;\n            Type etype = ((GenericArrayType) type).getGenericComponentType(); // E: element type\n            return (Class<T>) Array.newInstance(getActualType(clazz, etype), 0).getClass();\n        } else if (type instanceof TypeVariable) {\n            // public class Sup<E> {\n            //   private E id;\n            // }\n            // public class Sub extends Sup<Long> {}\n            return getVariableActualType(clazz, (TypeVariable<?>) type);\n        } else if (type instanceof WildcardType) {\n            WildcardType wtype = (WildcardType) type;\n            if (ArrayUtils.isNotEmpty(wtype.getLowerBounds())) {\n                // 下限List<? super A>\n                return getActualType(clazz, wtype.getLowerBounds()[0]);\n            } else if (ArrayUtils.isNotEmpty(wtype.getUpperBounds())) {\n                // 上限List<? extends A>\n                return getActualType(clazz, wtype.getUpperBounds()[0]);\n            } else {\n                // List<?>\n                return (Class<T>) Object.class;\n            }\n        } else {\n            return (Class<T>) Object.class;\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static <T> Class<T> getVariableActualType(Class<?> clazz, TypeVariable<?> var) {\n        if (clazz == null) {\n            return (Class<T>) Object.class;\n        }\n\n        return (Class<T>) SynchronizedCaches.get(clazz, VARIABLE_TYPE_MAPPING, GenericUtils::getActualTypeVariableMapping)\n                                            .getOrDefault(getTypeVariableName(null, var).get(0), Object.class);\n    }\n\n    private static void resolveMapping(Map<String, Class<?>> result, Type type) {\n        if (!(type instanceof ParameterizedType)) {\n            return;\n        }\n\n        ParameterizedType ptype = (ParameterizedType) type;\n        Class<?> rawType = (Class<?>) ptype.getRawType();\n        // (Map<U,V>).getRawType() -> Map\n        TypeVariable<?>[] vars = rawType.getTypeParameters();\n        Type[] acts = ptype.getActualTypeArguments();\n        for (int i = 0; i < acts.length; i++) {\n            Class<?> varType = getActualType(null, acts[i]);\n            getTypeVariableName(rawType, vars[i]).forEach(e -> result.put(e, varType));\n            resolveMapping(result, acts[i]);\n        }\n    }\n\n    private static List<String> getTypeVariableName(Class<?> clazz, TypeVariable<?> var) {\n        List<String> names = new ArrayList<>();\n        getTypeVariableName(names, clazz, var);\n        return names;\n    }\n\n    // Class, Method, Constructor\n    private static void getTypeVariableName(List<String> names, Class<?> clazz, TypeVariable<?> var) {\n        names.add(var.getGenericDeclaration().toString() + \"[\" + var.getName() + \"]\");\n        if (clazz == null || clazz == Object.class) {\n            return;\n        }\n        for (Type type : getGenericTypes(clazz)) {\n            if (!(type instanceof ParameterizedType)) {\n                continue;\n            }\n\n            ParameterizedType ptype = (ParameterizedType) type;\n            Type[] types = ptype.getActualTypeArguments();\n            if (ArrayUtils.isEmpty(types)) {\n                continue;\n            }\n\n            for (int i = 0; i < types.length; i++) {\n                if (!(types[i] instanceof TypeVariable<?>)) {\n                    continue;\n                }\n                // find the type variable origin difined class\n                if (((TypeVariable<?>) types[i]).getName().equals(var.getTypeName())) {\n                    clazz = (Class<?>) ptype.getRawType();\n                    getTypeVariableName(names, clazz, clazz.getTypeParameters()[i]);\n                    break;\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/resource/ClassPathResourceLoader.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.resource;\n\nimport cn.ponfee.commons.io.Closeables;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.IOUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.*;\nimport java.net.JarURLConnection;\nimport java.net.URL;\nimport java.net.URLDecoder;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Enumeration;\nimport java.util.List;\nimport java.util.jar.JarEntry;\nimport java.util.jar.JarFile;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\nimport static org.apache.commons.lang3.ArrayUtils.isEmpty;\nimport static org.apache.commons.lang3.StringUtils.join;\n\n/**\n * 类资源加载器\n *\n * @author Ponfee\n */\nfinal class ClassPathResourceLoader {\n\n    private static final String URL_PROTOCOL_FILE = \"file\";\n    private static final String URL_PROTOCOL_JAR = \"jar\";\n    private static final String URL_PROTOCOL_ZIP = \"zip\";\n    private static final String JAR_URL_SEPARATOR = \"!/\";\n\n    private static final Logger LOG = LoggerFactory.getLogger(ClassPathResourceLoader.class);\n\n    /**\n     * 加载资源文件\n     * @param filePath\n     * @param contextClass\n     * @param encoding\n     * @return\n     */\n    Resource getResource(String filePath, Class<?> contextClass, String encoding) {\n        Enumeration<URL> urls;\n        JarFile jar = null;\n        ZipFile zip = null;\n        try {\n            if (contextClass != null) {\n                urls = contextClass.getClassLoader().getResources(filePath);\n            } else {\n                urls = Thread.currentThread().getContextClassLoader().getResources(filePath);\n            }\n\n            while (urls.hasMoreElements()) {\n                URL url = urls.nextElement();\n                switch (url.getProtocol()) {\n                    case URL_PROTOCOL_FILE:\n                        String path = URLDecoder.decode(url.getFile(), encoding);\n                        // 判断是否是指定类所在Jar包中的文件：path.length()-filePath.length() == path.lastIndexOf(filePath)\n                        if (checkWithoutClass(contextClass, path.substring(0, path.length() - filePath.length()), encoding)) {\n                            continue;\n                        }\n                        return new Resource(path, new File(path).getName(), new FileInputStream(path));\n                    case URL_PROTOCOL_JAR:\n                        jar = ((JarURLConnection) url.openConnection()).getJarFile();\n                        // 判断是否是指定类所在Jar包中的文件\n                        if (checkWithoutClass(contextClass, jar.getName(), encoding)) {\n                            continue;\n                        }\n                        Enumeration<JarEntry> entries = jar.entries();\n                        while (entries.hasMoreElements()) {\n                            // 获取jar里的一个实体：可以是目录或其他如META-INF等文件\n                            JarEntry entry = entries.nextElement();\n                            if (!filePath.equals(entry.getName())) {\n                                continue;\n                            }\n                            String fileName = entry.getName();\n                            fileName = fileName.replace(\"\\\\\", \"/\");\n                            if (fileName.contains(\"/\")) {\n                                fileName = fileName.substring(fileName.lastIndexOf(\"/\") + 1);\n                            }\n                            return new Resource(URLDecoder.decode(url.getFile(), encoding),\n                                                fileName, transform(jar.getInputStream(entry)));\n                        }\n                        jar.close();\n                        jar = null;\n                        break;\n                    case URL_PROTOCOL_ZIP: // as a zip file in weblogic environment\n                        String zipPath = URLDecoder.decode(url.getFile(), encoding);\n                        if (zipPath.startsWith(ResourceLoaderFacade.FS_PREFIX)) {\n                            zipPath = zipPath.substring(ResourceLoaderFacade.FS_PREFIX.length());\n                        }\n                        if (!zipPath.contains(JAR_URL_SEPARATOR)) {\n                            continue;\n                        }\n                        zipPath = zipPath.substring(0, zipPath.lastIndexOf(JAR_URL_SEPARATOR));\n                        // 判断是否是指定类所在Jar包中的文件\n                        if (checkWithoutClass(contextClass, zipPath, encoding)) {\n                            continue;\n                        }\n                        zip = new ZipFile(zipPath);\n                        // org.apache.tools.zip.ZipEntry;\n                        // org.apache.tools.zip.ZipFile;\n                        //Enumeration<ZipEntry> entries = zip.getEntries();\n                        Enumeration<? extends ZipEntry> entries0 = zip.entries();\n                        while (entries0.hasMoreElements()) {\n                            ZipEntry entry = entries0.nextElement();\n                            if (!filePath.equals(entry.getName())) {\n                                continue;\n                            }\n                            String fileName = entry.getName();\n                            fileName = fileName.replace(\"\\\\\", \"/\");\n                            if (fileName.contains(\"/\")) {\n                                fileName = fileName.substring(fileName.lastIndexOf(\"/\") + 1);\n                            }\n                            return new Resource(\n                                URLDecoder.decode(url.getFile(), encoding),\n                                fileName, transform(zip.getInputStream(entry))\n                            );\n                        }\n                        zip.close();\n                        zip = null;\n                        break;\n                    default:\n                        throw new UnsupportedOperationException(\"Unsupported protocol: \" + url.getProtocol());\n                }\n            }\n            return null;\n        } catch (IOException e) {\n            LOG.error(\"load resource from jar file occur error\", e);\n            return null;\n        } finally {\n            Closeables.console(jar);\n            Closeables.console(zip);\n        }\n    }\n\n    List<Resource> listResources(String directory, String[] extensions, boolean recursive,\n                                 Class<?> contextClass, String encoding) {\n        List<Resource> list = new ArrayList<>();\n        JarFile jar = null;\n        ZipFile zip = null;\n        Enumeration<URL> dirs;\n        try {\n            if (contextClass != null) {\n                dirs = contextClass.getClassLoader().getResources(directory);\n            } else {\n                dirs = Thread.currentThread().getContextClassLoader().getResources(directory);\n            }\n\n            while (dirs.hasMoreElements()) {\n                URL url = dirs.nextElement();\n                switch (url.getProtocol()) {\n                    case URL_PROTOCOL_FILE:\n                        String path = URLDecoder.decode(url.getFile(), encoding);\n                        // 判断是否是指定类所在Jar包中的文件：path.length()-directory.length() == path.lastIndexOf(directory)\n                        if (checkWithoutClass(contextClass, path.substring(0, path.length() - directory.length()), encoding)) {\n                            continue;\n                        }\n                        Collection<File> files = FileUtils.listFiles(new File(path), extensions, recursive);\n                        if (files != null && !files.isEmpty()) {\n                            for (File file : files) {\n                                list.add(new Resource(file.getAbsolutePath(), file.getName(), new FileInputStream(file)));\n                            }\n                        }\n                        break;\n                    case URL_PROTOCOL_JAR:\n                        jar = ((JarURLConnection) url.openConnection()).getJarFile();\n                        // 判断是否是指定类所在Jar包中的文件\n                        if (checkWithoutClass(contextClass, jar.getName(), encoding)) {\n                            continue;\n                        }\n                        Enumeration<JarEntry> entries = jar.entries();\n                        while (entries.hasMoreElements()) {\n                            JarEntry entry = entries.nextElement();\n                            if (entry.isDirectory()) {\n                                continue;\n                            }\n                            String name = entry.getName();\n\n                            // 1、全目录匹配 或 当可递归时子目录\n                            // 2、匹配后缀\n                            int idx = name.lastIndexOf('/');\n                            boolean isDir = (idx != -1 && name.substring(0, idx).equals(directory))\n                                            || (recursive && name.startsWith(directory));\n                            boolean isSufx = isEmpty(extensions)\n                                             || name.toLowerCase().matches(\"^(.+\\\\.)(\" + join(extensions, \"|\") + \")$\");\n                            if (isDir && isSufx) {\n                                list.add(new Resource(URLDecoder.decode(url.getFile(), encoding),\n                                                      entry.getName(),\n                                                      transform(jar.getInputStream(entry))));\n                            }\n                        }\n                        jar.close();\n                        jar = null;\n                        break;\n                    case URL_PROTOCOL_ZIP: // weblogic is zip file\n                        String zipPath = URLDecoder.decode(url.getFile(), encoding);\n                        if (zipPath.startsWith(ResourceLoaderFacade.FS_PREFIX)) {\n                            zipPath = zipPath.substring(ResourceLoaderFacade.FS_PREFIX.length());\n                        }\n                        if (!zipPath.contains(JAR_URL_SEPARATOR)) {\n                            continue;\n                        }\n                        zipPath = zipPath.substring(0, zipPath.lastIndexOf(JAR_URL_SEPARATOR));\n                        // 判断是否是指定类所在Jar包中的文件\n                        if (checkWithoutClass(contextClass, zipPath, encoding)) {\n                            continue;\n                        }\n                        zip = new ZipFile(zipPath);\n                        //Enumeration<ZipEntry> entries = zip.getEntries();\n                        Enumeration<? extends ZipEntry> entries0 = zip.entries();\n                        while (entries0.hasMoreElements()) {\n                            ZipEntry entry = entries0.nextElement();\n                            String name = entry.getName();\n                            int idx = name.lastIndexOf('/');\n                            // 1、全目录匹配 或 当可递归时子目录\n                            // 2、匹配后缀\n                            boolean isDir = (idx != -1 && name.substring(0, idx).equals(directory))\n                                            || (recursive && name.startsWith(directory));\n                            boolean isSuffix = isEmpty(extensions)\n                                            || name.toLowerCase().matches(\"^(.+\\\\.)(\" + join(extensions, \"|\") + \")$\");\n                            if (isDir && isSuffix) {\n                                list.add(new Resource(\n                                    URLDecoder.decode(url.getFile(), encoding),\n                                    entry.getName(), transform(zip.getInputStream(entry))\n                                ));\n                            }\n                        }\n                        zip.close();\n                        zip = null;\n                        break;\n                    default:\n                        throw new UnsupportedOperationException(\"un supported process \" + url.getProtocol());\n                }\n            }\n            return list;\n        } catch (IOException e) {\n            LOG.error(\"load resource from jar file occur error\", e);\n            return list;\n        } finally {\n            Closeables.console(jar);\n            Closeables.console(zip);\n        }\n    }\n\n    /**\n     * 判断资源文件是否在contextClass的classpath中（jar包或class目录）\n     * @param contextClass\n     * @param filepath\n     * @param encoding\n     * @return\n     * @throws IOException\n     */\n    private static boolean checkWithoutClass(Class<?> contextClass, String filepath,\n                                             String encoding) throws IOException {\n        if (contextClass == null) {\n            return false;\n        }\n\n        String destPath = contextClass.getProtectionDomain().getCodeSource().getLocation().getFile();\n        destPath = URLDecoder.decode(destPath, encoding);\n        return !new File(destPath).getCanonicalFile().equals(new File(filepath).getCanonicalFile());\n    }\n\n    /**\n     * 流转换\n     * @param input\n     * @return\n     * @throws IOException\n     */\n    private static ByteArrayInputStream transform(InputStream input) throws IOException {\n        if (input instanceof ByteArrayInputStream) {\n            return (ByteArrayInputStream) input;\n        }\n        try {\n            return new ByteArrayInputStream(IOUtils.toByteArray(input));\n        } finally {\n            Closeables.console(input);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/resource/FileSystemResourceLoader.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.resource;\n\nimport org.apache.commons.io.FileUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 文件资源加载器\n * \n * @author Ponfee\n */\nfinal class FileSystemResourceLoader {\n\n    private static final Logger LOG = LoggerFactory.getLogger(FileSystemResourceLoader.class);\n\n    Resource getResource(String filePath, String encoding) {\n        try {\n            File f = new File(filePath);\n            return new Resource(f.getAbsolutePath(), f.getName(), new FileInputStream(f));\n        } catch (FileNotFoundException e) {\n            LOG.error(\"file not found: \" + filePath, e);\n            return null;\n        }\n    }\n\n    List<Resource> listResources(String directory, String[] extensions, boolean recursive) {\n        List<Resource> list = new ArrayList<>();\n        try {\n            File fileDir = new File(directory);\n            Collection<File> files = FileUtils.listFiles(fileDir, extensions, recursive);\n            if (files != null && !files.isEmpty()) {\n                for (File f : files) {\n                    list.add(new Resource(f.getAbsolutePath(), f.getName(), new FileInputStream(f)));\n                }\n            }\n        } catch (FileNotFoundException e) {\n            LOG.error(\"file not found: \" + directory, e);\n        }\n        return list;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/resource/Resource.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.resource;\n\nimport cn.ponfee.commons.io.Closeables;\n\nimport java.io.Closeable;\nimport java.io.InputStream;\n\n/**\n * 资源类\n * \n * @author Ponfee\n */\npublic class Resource implements Closeable {\n\n    private final String filePath;\n    private final String fileName;\n    private InputStream stream;\n\n    public Resource(String filePath, String fileName, InputStream stream) {\n        this.filePath = filePath;\n        this.fileName = fileName;\n        this.stream = stream;\n    }\n\n    public String getFilePath() {\n        return filePath;\n    }\n\n    public String getFileName() {\n        return fileName;\n    }\n\n    public InputStream getStream() {\n        return stream;\n    }\n\n    @Override\n    public String toString() {\n        return \"Resource [filePath=\" + filePath + \", fileName=\" + fileName + \", stream=\" + stream + \"]\";\n    }\n\n    @Override\n    public void close() {\n        Closeables.console(stream);\n        stream = null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/resource/ResourceLoaderFacade.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.resource;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.util.Strings;\nimport org.apache.commons.lang3.ObjectUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.annotation.Nonnull;\nimport javax.servlet.ServletContext;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * 资源文件加载门面类\n * <pre>\n *  {@link Class#getResourceAsStream(String)           } ：path不以“/”开头则从此类所在的jar包下获取，以“/”开头则从classpath根下获取(内部还是由ClassLoader获取资源)\n *  {@link ClassLoader#getResourceAsStream(String)     } ：从classpath根下获取(path不能以“/”开头，否则报错)\n *  {@link ServletContext#getResourceAsStream(String)  } ：从WebAPP根目录下取资源，'/'开头和不以'/'开头情况一样\n * </pre>\n *\n * <ul>\n *   <li>classpath:<p>以'/'开头表示在jar包中的绝对路径（内部还是由ClassLoader获取），不以'/'开头表示在jar包中与指定类的相对路径\n *   </li>\n *   <li>webapp:</li>\n *   <li>file:</li>\n * </ul>\n * <p>default classpath:\n *\n * <pre>\n *  ResourceLoaderFacade.getResource(\"StringUtils.class\", StringUtils.class);\n *  ResourceLoaderFacade.getResource(\"/mybatis-conf.xml\", ResourceLoaderFacade.class); // 类所在jar包中的绝对路径\n *  ResourceLoaderFacade.getResource(\"mybatis-conf.xml\", ResourceLoaderFacade.class); // 类所在jar包中且相对该类的路径\n *  ResourceLoaderFacade.getResource(\"/log4j2.xml\");\n *  ResourceLoaderFacade.getResource(\"log4j2.xml\");\n *  ResourceLoaderFacade.getResource(\"file:d:/import.txt\");\n * </pre>\n *\n * @author Ponfee\n */\npublic final class ResourceLoaderFacade {\n\n    //private static final String CP_ALL_PREFIX = \"classpath*:\";\n    private static final String WEB_PREFIX = \"webapp:\";\n    static final String FS_PREFIX = \"file:\";\n    private static final Pattern PATTERN = Pattern.compile(\"^(\\\\s*(?i)(classpath|webapp|file):\\\\s*)?(.+)$\");\n\n    private static final ClassPathResourceLoader  CP_LOADER = new ClassPathResourceLoader();\n    private static final FileSystemResourceLoader FS_LOADER = new FileSystemResourceLoader();\n    private static final WebappResourceLoader    WEB_LOADER = new WebappResourceLoader();\n\n    public static void setServletContext(@Nonnull ServletContext servletContext) {\n        if (servletContext != null) {\n            WEB_LOADER.setServletContext(servletContext);\n        }\n    }\n\n    public static Resource getResource(String filePath, Class<?> contextClass) {\n        return getResource(filePath, contextClass, null);\n    }\n\n    public static Resource getResource(String filePath, String encoding) {\n        return getResource(filePath, null, encoding);\n    }\n\n    public static Resource getResource(String filePath) {\n        return getResource(filePath, null, null);\n    }\n\n    /**\n     * 文件资源加载\n     * @param filePath      \"/\"表示根路径开始，其它为相对路径\n     * @param contextClass\n     * @param encoding\n     * @return\n     */\n    public static Resource getResource(String filePath, Class<?> contextClass, String encoding) {\n        Matcher matcher = PATTERN.matcher(filePath);\n        if (!matcher.matches()) {\n            throw new IllegalArgumentException(\"Invalid file path: \" + filePath);\n        }\n        if (StringUtils.isEmpty(encoding)) {\n            encoding = Files.UTF_8;\n        }\n\n        filePath = Strings.cleanPath(matcher.group(3).trim());\n        switch (ObjectUtils.defaultIfNull(matcher.group(1), \"\").toLowerCase()) {\n            case FS_PREFIX:\n                return FS_LOADER.getResource(filePath, encoding);\n            case WEB_PREFIX:\n                return WEB_LOADER.getResource(resolveWebapp(filePath), encoding);\n            default:\n                // 内部用的classLoader加载，不能以“/”开头，XX.class.getResourceAsStream(\"/com/x/file/myfile.xml\")才能以“/”开头\n                // \"/\"开头表示取根路径，非\"/\"开头则加上contextClass的包路径（如果contextClass不为空）\n                filePath = resolveClasspath(filePath, contextClass);\n                return CP_LOADER.getResource(filePath, contextClass, encoding); // 默认为classpath\n        }\n    }\n\n    /**\n     * 路径默认为空串\n     *\n     * @param extensions\n     * @param contextClass\n     * @return\n     */\n    public static List<Resource> listResources(String[] extensions, Class<?> contextClass) {\n        return listResources(\"\", extensions, false, contextClass, Files.UTF_8);\n    }\n\n    public static List<Resource> listResources(String dir, String[] extensions, boolean recursive) {\n        return listResources(dir, extensions, recursive, null, Files.UTF_8);\n    }\n\n    public static List<Resource> listResources(String dir, String[] extensions,\n                                               boolean recursive, String encoding) {\n        return listResources(dir, extensions, recursive, null, encoding);\n    }\n\n    /**\n     * 路径匹配过滤加载\n     * @param dir         \"/\"表示根路径开始，其它为相对路径\n     * @param extensions\n     * @param recursive\n     * @param contextClass\n     * @param encoding\n     * @return\n     */\n    public static List<Resource> listResources(String dir, String[] extensions, boolean recursive,\n                                               Class<?> contextClass, String encoding) {\n        if (StringUtils.isBlank(dir)) {\n            dir = \".\";\n        }\n\n        Matcher matcher = PATTERN.matcher(dir);\n        if (!matcher.matches()) {\n            throw new IllegalArgumentException(\"Invalid directory: \" + dir);\n        }\n        if (StringUtils.isEmpty(encoding)) {\n            encoding = Files.UTF_8;\n        }\n\n        dir = Strings.cleanPath(matcher.group(3).trim());\n        switch (ObjectUtils.defaultIfNull(matcher.group(1), \"\").toLowerCase()) {\n            case FS_PREFIX:\n                return FS_LOADER.listResources(dir, extensions, recursive);\n            case WEB_PREFIX:\n                return WEB_LOADER.listResources(resolveWebapp(dir), extensions, recursive, encoding);\n            default:\n                // 内部用的classLoader加载，不能以“/”开头，XX.class.getResourceAsStream(\"/com/x/file/myfile.xml\")才能以“/”开头\n                // \"/\"开头表示取根路径，非\"/\"开头则加上contextClass的包路径（如果contextClass不为空）\n                dir = resolveClasspath(dir, contextClass);\n                return CP_LOADER.listResources(dir, extensions, recursive, contextClass, encoding); // default classpath\n        }\n    }\n\n    private static String resolveWebapp(String path) {\n        if (!path.startsWith(\"/\")) {\n            path = \"/\" + path;\n        }\n        return path;\n    }\n\n    private static String resolveClasspath(String path, Class<?> contextClass) {\n        if (path.startsWith(\"/\")) {\n            path = path.substring(1);\n        } else if (contextClass != null) {\n            path = ClassUtils.getPackagePath(contextClass) + \"/\" + path;\n        }\n        return path;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/resource/ResourceScanner.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.resource;\n\nimport cn.ponfee.commons.exception.Throwables.ThrowingFunction;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.support.PathMatchingResourcePatternResolver;\nimport org.springframework.core.type.classreading.CachingMetadataReaderFactory;\nimport org.springframework.core.type.classreading.MetadataReader;\nimport org.springframework.core.type.classreading.MetadataReaderFactory;\nimport org.springframework.core.type.filter.AnnotationTypeFilter;\nimport org.springframework.core.type.filter.AssignableTypeFilter;\nimport org.springframework.core.type.filter.TypeFilter;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.annotation.Annotation;\nimport java.nio.charset.Charset;\nimport java.util.*;\n\nimport static org.springframework.core.io.support.ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;\n\n/**\n * <pre>\n *  资源扫描文件，用法：\n *   new ResourceScanner(\"/*.template\").scan4text()\n *   new ResourceScanner(\"/**∕tika*.xml\").scan4text()\n *\n *   // findAllClassPathResources：“/*” 等同 “*”，“/”开头会被截取path.substring(1)\n *   new ResourceScanner(\"*.xml\").scan4bytes()\n *   new ResourceScanner(\"/*.xml\").scan4bytes()\n *   new ResourceScanner(\"**∕*.xml\").scan4bytes()\n *   new ResourceScanner(\"/**∕*.xml\").scan4bytes()\n *   new ResourceScanner(\"/log4j2.xml.template\").scan4bytes()\n *   new ResourceScanner(\"log4j2.xml.template\").scan4bytes()\n *\n *   new ResourceScanner(\"/cn/ponfee/commons/jce/*.class\").scan4bytes()\n *   new ResourceScanner(\"/cn/ponfee/commons/jce/**∕*.class\").scan4bytes()\n *\n *   new ResourceScanner(\"/cn/ponfee/commons/base/**∕*.class\").scan4class()\n *   new ResourceScanner(\"/cn/ponfee/commons/**∕*.class\").scan4class(null, new Class[] {Service.class})\n *   new ResourceScanner(\"/cn/ponfee/commons/**∕*.class\").scan4class(null, new Class[] {Component.class})\n *   new ResourceScanner(\"/cn/ponfee/commons/**∕*.class\").scan4class(new Class[]{Tuple.class}, null)\n * </pre>\n *\n * @author Ponfee\n * @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner\n */\npublic class ResourceScanner {\n    private static final Logger LOG = LoggerFactory.getLogger(ResourceScanner.class);\n\n    /**\n     * Prefix of resource schema.\n     *\n     * @see org.springframework.core.io.support.ResourcePatternResolver#CLASSPATH_ALL_URL_PREFIX\n     * @see org.springframework.util.ResourceUtils#CLASSPATH_URL_PREFIX\n     * @see org.springframework.util.ResourceUtils#FILE_URL_PREFIX\n     * @see org.springframework.util.ResourceUtils#JAR_URL_PREFIX\n     * @see org.springframework.util.ResourceUtils#WAR_URL_PREFIX\n     */\n    private final String urlPrefix;\n\n    private final List<String> locationPatterns;\n\n    public ResourceScanner(String... locationPatterns) {\n        this(CLASSPATH_ALL_URL_PREFIX, locationPatterns);\n    }\n\n    public ResourceScanner(String urlPrefix, String[] locationPatterns) {\n        if (ArrayUtils.isEmpty(locationPatterns)) {\n            locationPatterns = new String[]{\"*\"};\n        }\n        this.urlPrefix = urlPrefix;\n        this.locationPatterns = Arrays.asList(locationPatterns);\n    }\n\n    /**\n     * 类扫描\n     *\n     * @return result of class set\n     */\n    public Set<Class<?>> scan4class() {\n        return scan4class(null, null);\n    }\n\n    /**\n     * 类扫描\n     *\n     * @param assignableTypes 扫描指定的子类\n     * @param annotationTypes 扫描包含指定注解的类\n     * @return result of class set\n     */\n    public Set<Class<?>> scan4class(Class<?>[] assignableTypes, Class<? extends Annotation>[] annotationTypes) {\n        List<TypeFilter> typeFilters = new LinkedList<>();\n\n        if (ArrayUtils.isNotEmpty(assignableTypes)) {\n            Arrays.stream(assignableTypes).map(AssignableTypeFilter::new).forEach(typeFilters::add);\n        }\n        if (ArrayUtils.isNotEmpty(annotationTypes)) {\n            // considerMetaAnnotations=true: @Service -> @Component\n            Arrays.stream(annotationTypes).map(AnnotationTypeFilter::new).forEach(typeFilters::add);\n        }\n\n        Set<Class<?>> result = new HashSet<>();\n        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();\n        MetadataReaderFactory factory = new CachingMetadataReaderFactory(resolver);\n        try {\n            for (String locationPattern : this.locationPatterns) {\n                for (Resource resource : resolver.getResources(urlPrefix + locationPattern)) {\n                    if (!resource.isReadable()) {\n                        continue;\n                    }\n                    MetadataReader reader = factory.getMetadataReader(resource);\n                    if (!matches(typeFilters, reader, factory)) {\n                        continue;\n                    }\n                    try {\n                        result.add(Class.forName(reader.getClassMetadata().getClassName()));\n                    } catch (Throwable e) {\n                        LOG.error(\"Load class occur error.\", e);\n                    }\n                }\n            }\n            return result;\n        } catch (IOException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    /**\n     * Scan as byte array\n     *\n     * @return type of Map<String, byte[]> result\n     */\n    public Map<String, byte[]> scan4bytes() {\n        return scan(IOUtils::toByteArray);\n    }\n\n    /**\n     * Scan as string\n     *\n     * @return type of Map<String, String> result\n     */\n    public Map<String, String> scan4text() {\n        return scan4text(Charset.defaultCharset());\n    }\n\n    /**\n     * Scan as string\n     *\n     * @param charset the charset\n     * @return type of Map<String, String> result\n     */\n    public Map<String, String> scan4text(Charset charset) {\n        return scan(e -> IOUtils.toString(e, charset));\n    }\n\n    // --------------------------------------------------------------------------private methods\n\n    private <T> Map<String, T> scan(ThrowingFunction<InputStream, T, ?> mapper) {\n        Map<String, T> result = new HashMap<>(16);\n        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();\n        try {\n            for (String locationPattern : locationPatterns) {\n                for (Resource resource : resolver.getResources(urlPrefix + locationPattern)) {\n                    if (!resource.isReadable()) {\n                        continue;\n                    }\n                    try (InputStream input = resource.getInputStream()) {\n                        result.put(resource.getFilename(), mapper.apply(input));\n                    } catch (Throwable e) {\n                        LOG.error(\"Resource scan location pattern failed: \" + locationPattern, e);\n                    }\n                }\n            }\n        } catch (IOException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n        return result;\n    }\n\n    private static boolean matches(List<TypeFilter> filters, MetadataReader reader, MetadataReaderFactory factory) throws IOException {\n        if (filters.isEmpty()) {\n            return true;\n        }\n        for (TypeFilter filter : filters) {\n            if (filter.match(reader, factory)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/resource/WebappResourceLoader.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.resource;\n\nimport org.apache.commons.io.FileUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.servlet.ServletContext;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * webapp资源加载器\n * \n * @author Ponfee\n */\nfinal class WebappResourceLoader {\n    private static final Logger LOG = LoggerFactory.getLogger(WebappResourceLoader.class);\n\n    private ServletContext servletContext;\n\n    WebappResourceLoader() {}\n\n    void setServletContext(ServletContext servletContext) {\n        this.servletContext = servletContext;\n    }\n\n    ServletContext getServletContext() {\n        return this.servletContext;\n    }\n\n    Resource getResource(String filePath, String encoding) {\n        try {\n            File f = new File(servletContext.getRealPath(filePath));\n            return new Resource(f.getAbsolutePath(), f.getName(), new FileInputStream(f));\n        } catch (FileNotFoundException e) {\n            LOG.error(\"file not found [\" + filePath + \"]\", e);\n            return null;\n        }\n    }\n\n    List<Resource> listResources(String directory, String[] extensions, boolean recursive, String encoding) {\n        List<Resource> list = new ArrayList<>();\n        try {\n            File fileDir = new File(servletContext.getRealPath(directory));\n            Collection<File> files = FileUtils.listFiles(fileDir, extensions, recursive);\n            if (files != null && !files.isEmpty()) {\n                for (File f : files) {\n                    list.add(new Resource(f.getAbsolutePath(), f.getName(), new FileInputStream(f)));\n                }\n            }\n        } catch (FileNotFoundException e) {\n            LOG.error(\"file not found [\" + directory + \"]\", e);\n        }\n        return list;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/DataColumn.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema;\n\nimport java.io.Serializable;\n\n/**\n * Column for table meta config\n * \n * @author Ponfee\n */\npublic class DataColumn implements Serializable {\n\n    private static final long serialVersionUID = 7044462319527084588L;\n\n    private String   name;  // 列名（如Mysql表的列名）\n    private DataType type;  // 类型\n    private String   alias; // 别名（表头标题）\n\n    public DataColumn() {}\n\n    public DataColumn(String name, DataType type, String alias) {\n        this.name = name;\n        this.type = type;\n        this.alias = alias;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public DataType getType() {\n        return type;\n    }\n\n    public void setType(DataType type) {\n        this.type = type;\n    }\n\n    public String getAlias() {\n        return alias;\n    }\n\n    public void setAlias(String alias) {\n        this.alias = alias;\n    }\n\n    public static DataColumn of(String name, DataType type, String alias) {\n        return new DataColumn(name, type, alias);\n    }\n\n    public static DataColumn of(String name, DataType type) {\n        return new DataColumn(name, type, null);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/DataStructure.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema;\n\n/**\n * 数据格式标记类：结构化的数据\n * \n * @author Ponfee\n */\npublic interface DataStructure extends java.io.Serializable {\n\n    default String structure() {\n        return DataStructures.ofType(this.getClass()).name();\n    }\n\n    NormalStructure toNormal();\n\n    TableStructure toTable();\n\n    PlainStructure toPlain();\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/DataStructures.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema;\n\nimport cn.ponfee.commons.exception.ServerException;\nimport cn.ponfee.commons.json.Jsons;\nimport com.google.common.base.CaseFormat;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.lang.reflect.Method;\nimport java.text.ParseException;\nimport java.util.Collections;\nimport java.util.Optional;\n\n/**\n * 数据格式处理\n * \n * @author Ponfee\n */\npublic enum DataStructures {\n\n    /** 标准 */\n    NORMAL(NormalStructure.class) {\n        @Override\n        public NormalStructure empty() {\n            return new NormalStructure(0);\n        }\n    }, // \n\n    /** 表格 */\n    TABLE(TableStructure.class) {\n        private final DataColumn[] empty = new DataColumn[0];\n\n        @Override\n        public DataStructure empty() {\n            return new TableStructure(empty, Collections.emptyList());\n        }\n\n        @Override\n        public DataStructure parse(String text) {\n            TableStructure table = (TableStructure) Jsons.fromJson(text, this.type());\n            if (table.getColumns() == null && table.getDataset() == null) {\n                throw new IllegalArgumentException(\"Invalid table structure: \" + text);\n            }\n            return table;\n        }\n    }, //\n\n    /** 原文 */\n    PLAIN(PlainStructure.class) {\n        private final PlainStructure empty = new PlainStructure(\"\");\n\n        @Override\n        public DataStructure empty() {\n            return empty;\n        }\n    }, //\n\n    ;\n\n    private static final DataStructures DEFAULT_STRUCTURE = NORMAL;\n\n    private final Class<? extends DataStructure> type;\n\n    DataStructures(Class<? extends DataStructure> type) {\n        this.type = type;\n    }\n\n    public DataStructure parse(String text) {\n        return Jsons.fromJson(text, this.type());\n    }\n\n    public abstract DataStructure empty();\n\n    public Class<? extends DataStructure> type() {\n        return this.type;\n    }\n\n    public static DataStructures ofType(Class<? extends DataStructure> type) {\n        if (type == null) {\n            return DEFAULT_STRUCTURE;\n        }\n\n        for (DataStructures ds : DataStructures.values()) {\n            if (ds.type == type) {\n                return ds;\n            }\n        }\n\n        throw new UnsupportedOperationException(\"Unknown structure type: \" + type);\n    }\n\n    public static DataStructures ofName(String name) {\n        if (StringUtils.isBlank(name)) {\n            return DEFAULT_STRUCTURE;\n        }\n\n        for (DataStructures ds : DataStructures.values()) {\n            if (ds.name().equalsIgnoreCase(name)) {\n                return ds;\n            }\n        }\n\n        throw new UnsupportedOperationException(\"Unknown structure type: \" + name);\n    }\n\n    public static DataStructure empty(String name) {\n        return ofName(name).empty();\n    }\n\n    // ---------------------------------------------------------------------------detect text to data structure\n\n    public static DataStructure detect(String text, boolean strict) throws ParseException {\n        for (DataStructures ds : DataStructures.values()) {\n            try {\n                return ds.parse(text);\n            } catch (Exception ignored) {\n                ignored.printStackTrace();\n            }\n        }\n\n        if (!strict) {\n            return new PlainStructure(text);\n        }\n\n        throw new ParseException(\"Unresolvable text data: \" + text, 0);\n    }\n\n    // ---------------------------------------------------------------------------convert source structure to target structure\n\n    public static <S extends DataStructure, T extends DataStructure> T convert(S source, Class<T> targetType) {\n        return convert(source, ofType(targetType).name());\n    }\n\n    public static <S extends DataStructure, T extends DataStructure> T convert(S source, DataStructures targetType) {\n        return convert(source, (targetType == null ? DEFAULT_STRUCTURE : targetType).name());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <S extends DataStructure, T extends DataStructure> T convert(S source, String structure) {\n        if (source == null) {\n            return null;\n        }\n\n        DataStructures sourceType = ofType(source.getClass());\n        String structure0 = Optional.ofNullable(structure).filter(StringUtils::isNotBlank)\n                                    .map(String::toUpperCase).orElse(DEFAULT_STRUCTURE.name());\n        if (structure0.equals(sourceType.name())) {\n            return (T) source;\n        }\n\n        // toNormal(), toTable(), toPlain()\n        String methodName = \"to\" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, structure0);\n\n        Method method;\n        try {\n            method = source.getClass().getDeclaredMethod(methodName);\n        } catch (Exception e) {\n            throw new UnsupportedOperationException(\"Unknown structure type: \" + structure, e);\n        }\n\n        try {\n            return (T) method.invoke(source);\n        } catch (Exception e) {\n            if (StringUtils.isBlank(structure)) {\n                return (T) source.toPlain();\n            }\n            throw new ServerException(\"Structure type convert failed, expect: \" + structure + \", actual: \" + sourceType.name(), e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/DataTable.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * Data table structure: a DataColumn array of columns \n * and two-dimensional array dataset<p>\n * \n * @author Ponfee\n */\npublic class DataTable implements Serializable {\n\n    private static final long serialVersionUID = 3710299712677057559L;\n\n    private String            name; // 表名\n    private String           alias; // 表别名\n    private DataColumn[]   columns; // 数据列元数据信息\n    private List<Object[]> dataset; // 数据集\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getAlias() {\n        return alias;\n    }\n\n    public void setAlias(String alias) {\n        this.alias = alias;\n    }\n\n    public DataColumn[] getColumns() {\n        return columns;\n    }\n\n    public void setColumns(DataColumn[] columns) {\n        this.columns = columns;\n    }\n\n    public List<Object[]> getDataset() {\n        return dataset;\n    }\n\n    public void setDataset(List<Object[]> dataset) {\n        this.dataset = dataset;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/DataType.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema;\n\nimport cn.ponfee.commons.date.JavaUtilDateFormat;\nimport cn.ponfee.commons.math.Numbers;\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.commons.lang3.ObjectUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.sql.Types;\nimport java.text.DateFormat;\nimport java.text.ParseException;\nimport java.util.Date;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.regex.Pattern;\n\n/**\n * Data type enum\n * \n * If the data value cannot convert the target type then fix is null\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"unchecked\")\npublic enum DataType {\n\n    BOOLEAN(\"布尔\") {\n        @Override\n        protected <T> T parseObject0(Object value) {\n            if (value instanceof Boolean) {\n                return (T) value;\n            }\n            return (T) BOOLEAN_MAPPING.get(value.toString());\n        }\n    },\n    DECIMAL(\"小数\") {\n        @Override\n        protected <T> T parseObject0(Object value) {\n            return (T) Numbers.toWrapDouble(value);\n        }\n    },\n    INTEGER(\"整数\") {\n        @Override\n        protected <T> T parseObject0(Object value) {\n            return (T) Numbers.toWrapLong(value);\n        }\n    },\n    STRING(\"字符串\") {\n        @Override\n        protected <T> T parseObject0(Object value) {\n            return (T) value.toString();\n        }\n\n        @Override\n        protected String toString0(Object value) {\n            return value.toString();\n        }\n\n        @Override\n        public boolean test0(Object value) {\n            return true;\n        }\n    },\n    DATE(\"日期\") {\n        private final JavaUtilDateFormat format = new JavaUtilDateFormat(\"yyyy-MM-dd\");\n\n        @Override\n        protected <T> T parseObject0(Object value) {\n            return (T) parseToDate(this.format, value);\n        }\n\n        @Override\n        protected String convert0(Object value) {\n            return toString0(value);\n        }\n\n        @Override\n        protected String toString0(Object value) {\n            return dateToString(this.format, value);\n        }\n    },\n    DATE_TIME(\"日期时间\") {\n\n        @Override\n        protected <T> T parseObject0(Object value) {\n            return (T) parseToDate(JavaUtilDateFormat.DEFAULT, value);\n        }\n\n        @Override\n        protected String convert0(Object value) {\n            return toString0(value);\n        }\n\n        @Override\n        protected String toString0(Object value) {\n            return dateToString(JavaUtilDateFormat.DEFAULT, value);\n        }\n    },\n    TIMESTAMP(\"时间戳（毫秒）\") {\n        @Override\n        protected <T> T parseObject0(Object value) {\n            if (value instanceof Date) {\n                return (T) (Long) ((Date) value).getTime();\n            }\n            return (T) Numbers.toWrapLong(value);\n        }\n    },\n\n    ;\n\n    private final String description;\n\n    DataType(String description) {\n        this.description = description;\n    }\n\n    protected abstract <T> T parseObject0(Object value);\n\n    protected String toString0(Object value) {\n        return Objects.toString(parseObject0(value), null);\n    }\n\n    /**\n     * Default is call inner method {@link #parseObject0(Object)}, <P>\n     * but {@link #DATE} and {@link #DATE_TIME} override this \n     * method for convert a string with date format<P>\n     * \n     * @param value the vlaue\n     * @return an object of convert result\n     */\n    protected <T> T convert0(Object value) {\n        return parseObject0(value);\n    }\n\n    protected boolean test0(Object value) {\n        return parseObject0(value) != null;\n    }\n\n    // -----------------------------------------------------------public methods\n    public final <T> T parseObject(Object value) {\n        return value == null ? null : parseObject0(value);\n    }\n\n    public final String toString(Object value) {\n        return value == null ? null : toString0(value);\n    }\n\n    /**\n     * Like parseObject, except {@link #DATE} or {@link #DATE_TIME} convert to date format string\n     * \n     * @param value the value\n     * @return an target object, except {@link #DATE} or {@link #DATE_TIME} is a string of date format\n     */\n    public final Object convert(Object value) {\n        return value == null ? null : ObjectUtils.defaultIfNull(convert0(value), value);\n    }\n\n    public final boolean test(Object value) {\n        // allow null(empty) value\n        if (value == null) {\n            return true;\n        }\n        if ((value instanceof String) && StringUtils.isEmpty((String) value)) {\n            return true;\n        }\n        return test0(value);\n    }\n\n    public String description() {\n        return this.description;\n    }\n\n    public static DataType of(String name) {\n        for (DataType dt : DataType.values()) {\n            if (dt.name().equalsIgnoreCase(name)) {\n                return dt;\n            }\n        }\n        return STRING;\n    }\n\n    // -------------------------------------------------------------------------detect data type\n    private static final Map<String, Boolean> BOOLEAN_MAPPING = ImmutableMap.<String, Boolean> builder()\n        .put(\"TRUE\", Boolean.TRUE)\n        .put(\"True\", Boolean.TRUE)\n        .put(\"true\", Boolean.TRUE)\n        .put(\"FALSE\", Boolean.FALSE)\n        .put(\"False\", Boolean.FALSE)\n        .put(\"false\", Boolean.FALSE)\n        .build();\n\n    private static final Pattern PATTERN_INTEGER = Pattern.compile(\"^[-+]?(([1-9]\\\\d*)|0)$\");\n    private static final Pattern PATTERN_DECIMAL = Pattern.compile(\"^[-+]?(([1-9]\\\\d*)|0)\\\\.\\\\d+$\");\n\n    public static DataType detect(String value) {\n        if (StringUtils.isBlank(value)) {\n            return STRING;\n        }\n\n        if (BOOLEAN_MAPPING.containsKey(value)) {\n            return BOOLEAN;\n        }\n\n        if (PATTERN_INTEGER.matcher(value).matches() && INTEGER.parseObject0(value) != null) {\n            return INTEGER;\n        }\n\n        if (PATTERN_DECIMAL.matcher(value).matches() && DECIMAL.parseObject0(value) != null) {\n            return DECIMAL;\n        }\n\n        if (value.length() == 10 && DATE.parseObject0(value) != null) {\n            return DATE;\n        }\n\n        if (value.length() == 19 && DATE_TIME.parseObject0(value) != null) {\n            return DATE_TIME;\n        }\n\n        return STRING;\n    }\n\n    public static DataType ofDatabaseType(int type) {\n        switch (type) {\n\n            case Types.DATE:\n                return DATE_TIME;\n            case Types.TIMESTAMP:\n                return TIMESTAMP;\n\n            case Types.BIT:\n            case Types.BOOLEAN:\n                return BOOLEAN;\n\n            case Types.REAL:\n            case Types.TINYINT:\n            case Types.SMALLINT:\n            case Types.INTEGER:\n            case Types.BIGINT:\n                return INTEGER;\n\n            case Types.FLOAT:\n            case Types.DOUBLE:\n            case Types.DECIMAL:\n                return DECIMAL;\n\n            default:\n                // VARCHAR, CHAR, NVARCHAR, NCHAR, LONGVARCHAR, LONGNVARCHAR\n                return STRING;\n        }\n    }\n\n    // ---------------------------------------------------------------------private methods\n    private static String dateToString(DateFormat format, Object value) {\n        if (value instanceof Date) {\n            return format.format((Date) value);\n        }\n        String text = value.toString();\n        if (StringUtils.isBlank(text)) {\n            return null;\n        }\n        try {\n            return format.format(format.parse(text));\n        } catch (ParseException ignored) {\n            return null;\n        }\n    }\n\n    private static Date parseToDate(DateFormat format, Object value) {\n        if (value instanceof Date) {\n            return (Date) value;\n        }\n        if (value instanceof Number) {\n            return new Date(((Number) value).longValue());\n        }\n        String text = value.toString();\n        if (StringUtils.isBlank(text)) {\n            return null;\n        }\n        try {\n            return format.parse(text);\n        } catch (ParseException ignored) {\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/GridTable.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.io.Serializable;\nimport java.util.Arrays;\n\n/**\n * Grid table for front view\n * \n * @author Ponfee\n */\npublic class GridTable implements Serializable {\n\n    private static final long serialVersionUID = 4900630719709337101L;\n\n    private Columns[]       columns; // 表头\n    private NormalStructure dataset; // 表体\n\n    public static GridTable of(TableStructure ts) {\n        if (ts == null) {\n            return null;\n        }\n\n        GridTable table = new GridTable();\n        table.setColumns(Columns.convert(ts.getColumns()));\n        table.setDataset(ts.toNormal());\n        return table;\n    }\n\n    public static class Columns implements Serializable {\n        private static final long serialVersionUID = 1L;\n\n        private String title;\n        private String dataIndex;\n        private String key;\n\n        public Columns() {}\n\n        public Columns(String title, String name) {\n            this.title = StringUtils.isBlank(title) ? name : title;\n            this.dataIndex = name;\n            this.key = name;\n        }\n\n        public String getTitle() {\n            return title;\n        }\n\n        public void setTitle(String title) {\n            this.title = title;\n        }\n\n        public String getDataIndex() {\n            return dataIndex;\n        }\n\n        public void setDataIndex(String dataIndex) {\n            this.dataIndex = dataIndex;\n        }\n\n        public String getKey() {\n            return key;\n        }\n\n        public void setKey(String key) {\n            this.key = key;\n        }\n\n        public static Columns convert(DataColumn column) {\n            return new Columns(column.getAlias(), column.getName());\n        }\n\n        public static Columns[] convert(DataColumn[] columns) {\n            if (columns == null) {\n                return null;\n            }\n            return Arrays.stream(columns).map(Columns::convert).toArray(Columns[]::new);\n        }\n    }\n\n    // ------------------------------------------------------------------------getter/setter\n    public Columns[] getColumns() {\n        return columns;\n    }\n\n    public void setColumns(Columns[] columns) {\n        this.columns = columns;\n    }\n\n    public NormalStructure getDataset() {\n        return dataset;\n    }\n\n    public void setDataset(NormalStructure dataset) {\n        this.dataset = dataset;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/NormalStructure.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema;\n\nimport cn.ponfee.commons.json.Jsons;\n\nimport java.util.*;\nimport java.util.Map.Entry;\n\n/**\n * [\n *   {\"name\":\"alice\", \"age\":10},\n *   {\"name\":\"bob\",   \"age\":18},\n *   {\"name\":\"tom\",   \"age\":31}\n * ]\n * \n * @author Ponfee\n */\npublic final class NormalStructure extends ArrayList<LinkedHashMap<String, Object>> implements DataStructure {\n\n    private static final long serialVersionUID = 9067243551591375987L;\n\n    public NormalStructure() {}\n\n    public NormalStructure(int minCapacity) {\n        super(minCapacity);\n    }\n\n    @Override\n    public NormalStructure toNormal() {\n        return this;\n    }\n\n    @Override\n    public TableStructure toTable() {\n        List<Object[]> dataset = new ArrayList<>(this.size());\n        int r = 0, c = 0; // r: row index, c: column index\n\n        // first row\n        LinkedHashMap<String, Object> map = this.get(r++);\n        DataColumn[] columns = new DataColumn[map.size()];\n        Object[] row = new Object[map.size()];\n        for (Iterator<Entry<String, Object>> iter = map.entrySet().iterator(); iter.hasNext(); c++) {\n            Entry<String, Object> entry = iter.next();\n            DataType type = DataType.detect(Objects.toString(entry.getValue(), null));\n            columns[c] = new DataColumn(entry.getKey(), type, null);\n            row[c] = columns[c].getType().convert(entry.getValue());\n        }\n        dataset.add(row);\n\n        for (int n = this.size(); r < n; r++) {\n            map = this.get(r);\n            row = new Object[map.size()];\n            for (c = 0; c < columns.length; c++) {\n                row[c] = map.get(columns[c].getName()); // columns[c].getType().convert(map.get(columns[c].getName()));\n            }\n            dataset.add(row);\n        }\n\n        return new TableStructure(columns, dataset);\n    }\n\n    @Override\n    public PlainStructure toPlain() {\n        return new PlainStructure(Jsons.toJson(this));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/PlainStructure.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema;\n\nimport cn.ponfee.commons.exception.ServerException;\nimport com.alibaba.fastjson.annotation.JSONType;\nimport com.alibaba.fastjson.parser.DefaultJSONParser;\nimport com.alibaba.fastjson.parser.JSONToken;\nimport com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;\nimport com.alibaba.fastjson.serializer.JSONSerializer;\nimport com.alibaba.fastjson.serializer.ObjectSerializer;\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize;\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.util.Objects;\n\n/**\n * 原文格式：As a string\n * \n * @author Ponfee\n */\n@JSONType(serializer = PlainStructure.FastjsonSerializer.class, deserializer = PlainStructure.FastjsonSerializer.class) // fastjson\n@JsonSerialize(using = PlainStructure.JacksonSerializer.class)     // jackson\n@JsonDeserialize(using = PlainStructure.JacksonDeserializer.class) // jackson\npublic final class PlainStructure implements DataStructure, CharSequence {\n    private static final long serialVersionUID = 1L;\n\n    private final String plain;\n\n    public PlainStructure(String plain) {\n        // if plain is null, shoudle make the PlainStructure object is null;\n        this.plain = Objects.requireNonNull(plain);\n    }\n\n    @Override\n    public NormalStructure toNormal() {\n        try {\n            return (NormalStructure) DataStructures.NORMAL.parse(this.plain);\n        } catch (Exception e) {\n            throw new ServerException(\"Convert to normal structure fail: \" + this.plain, e);\n        }\n    }\n\n    @Override\n    public TableStructure toTable() {\n        try {\n            return (TableStructure) DataStructures.TABLE.parse(this.plain);\n        } catch (Exception e) {\n            throw new ServerException(\"Convert to table structure fail: \" + this.plain, e);\n        }\n    }\n\n    @Override\n    public PlainStructure toPlain() {\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return this.plain;\n    }\n\n    @Override\n    public int length() {\n        return this.plain.length();\n    }\n\n    @Override\n    public char charAt(int index) {\n        return this.plain.charAt(index);\n    }\n\n    @Override\n    public CharSequence subSequence(int start, int end) {\n        return this.plain.subSequence(start, end);\n    }\n\n    // -----------------------------------------------------custom fastjson serialize/deserialize\n    public static class FastjsonSerializer implements ObjectSerializer, ObjectDeserializer {\n        @Override\n        public void write(JSONSerializer serializer, Object value, \n                          Object fieldName,Type fieldType, int features) {\n            if (value == null) {\n                serializer.writeNull();\n            } else {\n                serializer.write(value.toString());\n            }\n        }\n\n        @Override @SuppressWarnings(\"unchecked\")\n        public PlainStructure deserialze(DefaultJSONParser parser, Type type, Object fieldName) {\n            if (type != PlainStructure.class) {\n                throw new UnsupportedOperationException(\n                    \"Only supported deserialize PlainStructure, cannot supported: \" + type\n                );\n            }\n\n            String value = parser.getLexer().stringVal();\n            // 解决报错问题：not close json text, token : string\n            parser.getLexer().nextToken(JSONToken.LITERAL_STRING);\n            return value == null ? null : new PlainStructure(value);\n        }\n\n        @Override\n        public int getFastMatchToken() {\n            return 0;\n        }\n    }\n\n    // -----------------------------------------------------custom jackson serialize/deserialize\n    public static class JacksonSerializer extends JsonSerializer<PlainStructure> {\n        @Override\n        public void serialize(PlainStructure value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {\n            if (value == null) {\n                jsonGenerator.writeNull();\n            } else {\n                jsonGenerator.writeString(value.toString());\n            }\n        }\n    }\n\n    public static class JacksonDeserializer extends JsonDeserializer<PlainStructure> {\n        @Override\n        public PlainStructure deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {\n            String value = p.getText();\n            return value == null ? null : new PlainStructure(value);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/TableStructure.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema;\n\nimport cn.ponfee.commons.json.Jsons;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\n\n/**\n * {\n *   \"columns\":[\n *     {\"name\":\"name\",\"type\":\"STRING\", \"alias\":\"姓名\"},\n *     {\"name\":\"age\", \"type\":\"INTEGER\",\"alias\":\"年龄\"}\n *   ],\n *   \"dataset\":[\n *     [\"alice\",10],\n *     [\"bob\",  18],\n *     [\"tom\",  31]\n *   ],\n * }\n * \n * @author Ponfee\n */\npublic final class TableStructure implements DataStructure {\n    private static final long serialVersionUID = 1L;\n\n    private DataColumn[]   columns; // 数据列元数据信息\n    private List<Object[]> dataset; // 数据集二维表数据\n\n    public TableStructure() {}\n\n    public TableStructure(DataColumn[] columns, List<Object[]> dataset) {\n        this.columns = columns;\n        this.dataset = dataset;\n    }\n\n    @Override\n    public NormalStructure toNormal() {\n        NormalStructure list = new NormalStructure();\n        for (Object[] row : dataset) {\n            LinkedHashMap<String, Object> map = new LinkedHashMap<>(row.length);\n            for (int i = 0; i < row.length; i++) {\n                map.put(columns[i].getName(), row[i]); // columns[i].getType().convert(row[i])\n            }\n            list.add(map);\n        }\n        return list;\n    }\n\n    @Override\n    public TableStructure toTable() {\n        return this;\n    }\n\n    @Override\n    public PlainStructure toPlain() {\n        return new PlainStructure(Jsons.toJson(this));\n    }\n\n    public static TableStructure of(DataTable table) {\n        return new TableStructure(table.getColumns(), table.getDataset());\n    }\n\n    // ---------------------------------------------------------------------getter/setter\n    public DataColumn[] getColumns() {\n        return columns;\n    }\n\n    public void setColumns(DataColumn[] columns) {\n        this.columns = columns;\n    }\n\n    public List<Object[]> getDataset() {\n        return dataset;\n    }\n\n    public void setDataset(List<Object[]> dataset) {\n        this.dataset = dataset;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/json/JsonExtractUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema.json;\n\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.collect.Maps;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.model.Null;\nimport cn.ponfee.commons.schema.*;\nimport cn.ponfee.commons.tree.NodePath;\nimport cn.ponfee.commons.tree.PlainNode;\nimport cn.ponfee.commons.tree.TreeNode;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport com.alibaba.fastjson.JSON;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.tuple.Pair;\n\nimport javax.annotation.Nonnull;\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.text.ParseException;\nimport java.util.*;\nimport java.util.Map.Entry;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * The utility class for extract json schema and data\n *\n * @author Ponfee\n */\npublic final class JsonExtractUtils {\n\n    static final String ARRAY_EMPTY  = \"[--]\";\n    static final String ARRAY_ARRAY  = \"[[]]\";\n    static final String ARRAY_OBJECT = \"[{}]\";\n    static final String ARRAY_BASIC  = \"[()]\";\n    static final String ARRAY_INDEX  = \"[%02d]\";\n\n    static final Map<String, Object> NULL_VALUE_MAPPING = Collections.unmodifiableMap(\n        Maps.toMap(\n            ARRAY_EMPTY,  null,\n            ARRAY_ARRAY,  Collections.singletonList(Collections.emptyList()),\n            ARRAY_OBJECT, Collections.singletonList(Collections.emptyMap()),\n            ARRAY_BASIC,  Collections.emptyList()\n        )\n    );\n\n    private static final String ROOT = \"Root\";\n\n    public static TreeNode<JsonId, Null> extractSchema(String text) throws ParseException {\n        // com.fasterxml.jackson.databind.ObjectMapper#readTree(String)\n        return extractSchema(JSON.parse(text));\n    }\n\n    /**\n     * Returns a tree data of extracted the json data structure schema\n     *\n     * @param obj the object of use {@link JSON#parse(String)} parsed\n     * @return a tree node of json data schema\n     */\n    public static TreeNode<JsonId, Null> extractSchema(Object obj) throws ParseException {\n        if (!ObjectUtils.isComplexType(obj)) {\n            throw new ParseException(\"The basic type data cannot extract schema: \" + obj, 0);\n        }\n\n        List<JsonId> ids = new LinkedList<>();\n        extractSchema(ids, null, obj, new AtomicInteger(1));\n\n        return ids.isEmpty() ? null : buildTree(ids);\n    }\n\n    public static DataStructure extractData(String original, @Nonnull JsonTree tree) {\n        Object obj;\n        try {\n            obj = JSON.parse(original);\n        } catch (Exception ignored) {\n            ignored.printStackTrace();\n            return new PlainStructure(original);\n        }\n\n        if (!ObjectUtils.isComplexType(obj)) {\n            return new PlainStructure(original);\n        }\n\n        return extractData(obj, tree);\n    }\n\n    /**\n     * Returns a DataStructure object by user specified json columns in tree\n     *\n     * @param object the object of use {@link JSON#parse(String)} parsed\n     * @param tree the json tree\n     * @return a DataStructure object\n     */\n    public static TableStructure extractData(@Nonnull Object object, @Nonnull JsonTree tree) {\n        List<List<Object>> dataset = new LinkedList<>();\n        LinkedHashSet<NodePath<String>> extracted = new LinkedHashSet<>();\n        Map<NodePath<String>, JsonTree> config = tree.toFlatMap();\n        extractData(dataset, tree.getPath(), object, config, extracted);\n\n        Set<String> duplicate = extracted\n                .stream()\n                .map(Collects::getLast)\n                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))\n                .entrySet()\n                .stream()\n                .filter(e -> e.getValue() > 1)\n                .map(Entry::getKey)\n                .collect(Collectors.toSet());\n\n        DataColumn[] columns = new DataColumn[extracted.size()];\n        int i = 0;\n        for (NodePath<String> path : extracted) {\n            JsonTree node = config.get(path);\n            String name = node.getName();\n            if (duplicate.contains(name)) {\n                // prevent repeat name\n                name += \"-\" + String.format(\"%02d\", node.getOrders());\n            }\n            columns[i++] = new DataColumn(name, node.getType(), null);\n        }\n\n        return new TableStructure(\n            columns,\n            dataset.stream().map(List::toArray).collect(Collectors.toList())\n        );\n    }\n\n    // -----------------------------------------------------------------------------------private methods\n    @SuppressWarnings(\"unchecked\")\n    private static void extractSchema(List<JsonId> ids, JsonId parent, Object object, AtomicInteger count) {\n        if (object instanceof Map) { // JSONObject\n            for (Entry<String, Object> entry : ((Map<String, Object>) object).entrySet()) {\n                DataType dataType = detectDataType(entry.getValue());\n                JsonId id = new JsonId(parent, entry.getKey(), dataType, count.getAndIncrement());\n                ids.add(id);\n                if (dataType == null) {\n                    // complex json data type\n                    extractSchema(ids, id, entry.getValue(), count);\n                }\n            }\n        } else if ((object instanceof List) || object.getClass().isArray()) { // JSONArray\n            List<?> list = Collects.toList(object);\n            switch (detectArrayType(list)) {\n                case EMPTY: // 空的数组\n                    ids.add(new JsonId(parent, ARRAY_EMPTY, null, count.getAndIncrement()));\n                    break;\n                case ARRAY: // 二维数组\n                    ids.add(parent = new JsonId(parent, ARRAY_ARRAY, null, count.getAndIncrement()));\n                    buildArrayColumns(findFirstArray((List<List<Object>>) list), ids, parent, count);\n                    break;\n                case OBJECT: // 数组对象\n                    ids.add(parent = new JsonId(parent, ARRAY_OBJECT, null, count.getAndIncrement()));\n                    extractSchema(ids, parent, findFirstObject((List<Map<String, Object>>) list), count); // 继续下钻解析\n                    break;\n                case BASIC: // 基本类型（一行）\n                    ids.add(parent = new JsonId(parent, ARRAY_BASIC, null, count.getAndIncrement()));\n                    buildArrayColumns(list, ids, parent, count);\n                    break;\n                default:\n                    throw new RuntimeException(\"Unknown data type: \" + Jsons.toJson(list)); // cannot happened\n            }\n        } else {\n            // If others json type then to skip\n        }\n    }\n\n    private static TreeNode<JsonId, Null> buildTree(List<JsonId> ids) {\n        if (CollectionUtils.isEmpty(ids)) {\n            return null;\n        }\n\n        TreeNode<JsonId, Null> root = TreeNode.<JsonId, Null> builder(\n            new JsonId(null, ROOT, null, 0)\n        ).build();\n\n        try {\n            root.mount(ids.stream().map(JsonExtractUtils::toNode).collect(Collectors.toList()));\n            return root;\n        } catch (Exception e) {\n            throw new IllegalStateException(\"Parsed json schema occur error: \" + e.getMessage(), e);\n        }\n    }\n\n    private static PlainNode<JsonId, Null> toNode(JsonId id) {\n        return new PlainNode<>(id, id.getParent(), null);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static void extractData(List<List<Object>> dataset, NodePath<String> parent,\n                                    Object object, Map<NodePath<String>, JsonTree> config,\n                                    LinkedHashSet<NodePath<String>> extracted) {\n        JsonTree tree = config.get(parent);\n        if (CollectionUtils.isEmpty(tree.getChildren())) {\n            throw new IllegalStateException(\"Parent \\\"\" + parent + \"\\\" have not children.\");\n        }\n        if (!tree.isChecked()) {\n            return; // parent not check\n        }\n\n        if (object == null) {\n            if (tree.getChildren().size() == 1) {\n                String childName = tree.getChildren().get(0).getName();\n                // if ARRAY_EMPTY then null, else if single map key then Collections.emptyMap()\n                object = NULL_VALUE_MAPPING.getOrDefault(childName, Collections.emptyMap());\n            } else {\n                object = Collections.emptyMap();\n            }\n        }\n\n        if (object instanceof Map) { // JSONObject\n            Map<String, Object> map = (Map<String, Object>) object;\n            List<JsonTree> checkedNodes = new LinkedList<>();\n            for (String name : map.keySet()) {\n                JsonTree node = config.get(new NodePath<>(parent, name));\n                if (node != null && node.isChecked()) {\n                    checkedNodes.add(node);\n                }\n            }\n            // adjust orders\n            checkedNodes.sort(Comparator.comparing(JsonTree::getOrders));\n\n            List<List<Object>> subset = new LinkedList<>();\n            for (JsonTree node : checkedNodes) {\n                Object value = map.get(node.getName());\n                if (node.getType() != null) { // or CollectionUtils.isEmpty(node.getChildren())\n                    // leaf node, extract this value data\n                    extracted.add(node.getPath());\n                    concat(subset, getValue(value, node.getType()));\n                } else {\n                    List<List<Object>> dataset0 = new LinkedList<>();\n                    extractData(dataset0, node.getPath(), value, config, extracted);\n                    concat(subset, dataset0);\n                }\n            }\n\n            append(dataset, subset);\n        } else if ((object instanceof List) || object.getClass().isArray()) { // JSONArray\n            List<?> list = Collects.toList(object);\n            JsonTree node;\n            switch (detectArrayType(list)) {\n                case EMPTY: // 空的数组\n                    // Nothing to do\n                    break;\n                case ARRAY: // 二维数组\n                    node = config.get(new NodePath<>(parent, ARRAY_ARRAY));\n                    if (node != null && node.isChecked()) {\n                        List<Pair<Integer, JsonTree>> checkedNodes = getCheckedChildren(node, config, extracted);\n                        List<List<Object>> subset = new LinkedList<>();\n                        for (List<Object> array : ((List<List<Object>>) list)) {\n                            List<Object> row = new LinkedList<>();\n                            int size = array.size();\n                            for (Pair<Integer, JsonTree> pair : checkedNodes) {\n                                int idx = pair.getLeft();\n                                row.add(idx >= size ? null : getValue(array.get(idx), pair.getRight().getType()));\n                            }\n                            subset.add(row);\n                        }\n                        concat(dataset, subset);\n                    }\n                    break;\n                case OBJECT: // 数组对象\n                    node = config.get(new NodePath<>(parent, ARRAY_OBJECT));\n                    if (node != null && node.isChecked()) {\n                        for (Map<String, Object> map : (List<Map<String, Object>>) list) {\n                            if (map == null) {\n                                map = Collections.emptyMap();\n                            }\n                            extractData(dataset, node.getPath(), map, config, extracted);\n                        }\n                    }\n                    break;\n                case BASIC: // 基本类型（一行多列）\n                    node = config.get(new NodePath<>(parent, ARRAY_BASIC));\n                    if (node != null && node.isChecked()) {\n                        List<Pair<Integer, JsonTree>> checkedNodes = getCheckedChildren(node, config, extracted);\n                        int size = list.size();\n                        List<Object> row = new LinkedList<>();\n                        for (Pair<Integer, JsonTree> pair : checkedNodes) {\n                            int idx = pair.getLeft();\n\n                            // 如果之后数据的长度不够就横向重复\n                            //row.add(getValue(list.get(idx >= size ? size - 1 : idx), pair.getRight().getType()));\n\n                            // 如果之后数据的长度不够就为null\n                            row.add(idx >= size ? null : getValue(list.get(idx), pair.getRight().getType()));\n                        }\n                        concat(dataset, Collections.singletonList(row));\n                    }\n                    break;\n                default:\n                    throw new RuntimeException(\"Unknown data type: \" + Jsons.toJson(list)); // cannot happened\n            }\n        } else {\n            // If others json type then to skip\n        }\n    }\n\n    /**\n     * Detect the array inner emelent type(the first non null value)\n     *\n     * @param list the array\n     * @return a type\n     */\n    private static ArrayType detectArrayType(List<?> list) {\n        if (list.isEmpty()) {\n            return ArrayType.EMPTY;\n        }\n\n        for (Object obj : list) {\n            if (obj == null) {\n                continue;\n            }\n            if (obj instanceof Map) {\n                return ArrayType.OBJECT;\n            } else if (obj instanceof List) {\n                return ArrayType.ARRAY;\n            } else {\n                return ArrayType.BASIC;\n            }\n        }\n\n        // if all elements is null, then determine is basic array type\n        return ArrayType.BASIC;\n    }\n\n    private enum ArrayType {\n        EMPTY, OBJECT, ARRAY, BASIC\n    }\n\n    private static Map<String, Object> findFirstObject(List<Map<String, Object>> list) {\n        for (Map<String, Object> map : list) {\n            if (map != null) {\n                return map;\n            }\n        }\n        throw new RuntimeException(\"Empty [OBJECT]\"); // cannot happened\n    }\n\n    private static List<Object> findFirstArray(List<List<Object>> list) {\n        for (List<Object> array : list) {\n            if (array != null) {\n                return array;\n            }\n        }\n        throw new RuntimeException(\"Empty [ARRAY]\"); // cannot happened\n    }\n\n    private static DataType detectDataType(Object value, DataType defaultType) {\n        DataType dataType = detectDataType(value);\n        return dataType == null ? defaultType : dataType;\n    }\n\n    // 两种特殊类型：数组(array)、对象(object)\n    // 四种基础类型：字符串(string)、数字(number)、布尔型(boolean)、NULL值\n    private static DataType detectDataType(Object value) {\n        if (value == null || value instanceof CharSequence) {\n            // cannot detect if value is null, then determine it's a string type\n            return DataType.STRING;\n        }\n\n        if (value instanceof Boolean) {\n            return DataType.BOOLEAN;\n        }\n\n        if (value instanceof Integer || value instanceof Long || value instanceof BigInteger) {\n            return DataType.INTEGER;\n        }\n\n        if (value instanceof BigDecimal || value instanceof Float || value instanceof Double) {\n            return DataType.DECIMAL;\n        }\n\n        // if not complex type then determine it's a string type\n        return ObjectUtils.isComplexType(value) ? null : DataType.STRING;\n    }\n\n    private static Object getValue(Object value, DataType dataType) {\n        return dataType == DataType.STRING ? Objects.toString(value, null) : value;\n    }\n\n    private static void buildArrayColumns(List<?> list, List<JsonId> ids,\n                                          JsonId parent, AtomicInteger count) {\n        for (Object element : list) {\n            int index = count.getAndIncrement();\n            // 二维数组和基本数组不再下钻解析，如果为复合类型（二维数组），则直接判决其为STRING\n            DataType dataType = detectDataType(element, DataType.STRING);\n            ids.add(new JsonId(parent, String.format(ARRAY_INDEX, index), dataType, index));\n        }\n    }\n\n    private static List<Pair<Integer, JsonTree>> getCheckedChildren(\n            JsonTree node, Map<NodePath<String>, JsonTree> config, LinkedHashSet<NodePath<String>> columns) {\n        int startIndex = node.getOrders() + 1;\n        List<Pair<Integer, JsonTree>> checkedNodes = new LinkedList<>();\n        for (int i = 0, n = node.getChildren().size(); i < n; i++) {\n            String name = String.format(ARRAY_INDEX, startIndex + i);\n            JsonTree child = config.get(new NodePath<>(node.getPath(), name));\n            if (child != null && child.isChecked()) {\n                checkedNodes.add(Pair.of(i, child));\n                columns.add(child.getPath());\n            }\n        }\n        if (checkedNodes.isEmpty()) {\n            throw new IllegalStateException(\n                \"Parent is checked but not checked children: \" + node.getPath().toString()\n            );\n        }\n        return checkedNodes;\n    }\n\n    // ------------------------------------------------------------append table\n    /**\n     * Appends the sub dataset after dataset last row, as new row\n     *\n     * [[1,2], [3,4]]  append  [[\"a\",\"b\"], [\"c\",\"d\"]]  =>  [[1,2], [3,4], [\"a\",\"b\"], [\"c\",\"d\"]]\n     *\n     * @param dataset the dataset\n     * @param subset  the sub dataset\n     */\n    public static void append(List<List<Object>> dataset, List<List<Object>> subset) {\n        if (subset.isEmpty()) {\n            return;\n        }\n\n        if (dataset.isEmpty()) {\n            for (List<Object> row : subset) {\n                dataset.add(new LinkedList<>(row));\n            }\n            return;\n        }\n\n        int maxCol = Math.max(dataset.get(0).size(), subset.get(0).size());\n        completeCol(dataset, maxCol);\n        completeCol(subset, maxCol);\n\n        dataset.addAll(subset);\n    }\n\n    private static void completeCol(List<List<Object>> listArray, int maxCol) {\n        if (listArray.isEmpty() || listArray.get(0).size() >= maxCol) {\n            return;\n        }\n\n        for (List<Object> array : listArray) {\n            Object lastCol = array.get(array.size() - 1);\n            for (int i = maxCol - array.size(); i > 0; i--) {\n                array.add(lastCol);\n            }\n        }\n    }\n\n    // ------------------------------------------------------------join table\n    /**\n     * Concats the sub dataset after dataset last column, as new column\n     *\n     * [[1,2], [3,4]]  concat  \"a\"  =>  [[1,2,\"a\"], [3,4,\"a\"]]\n     *\n     * @param dataset the dataset\n     * @param value   the new column vlaue\n     */\n    public static void concat(List<List<Object>> dataset, Object value) {\n        if (dataset.isEmpty()) {\n            List<Object> row = new LinkedList<>();\n            row.add(value);\n            dataset.add(row);\n        } else {\n            for (List<Object> array : dataset) {\n                array.add(value);\n            }\n        }\n    }\n\n    /**\n     * Concats the sub dataset after dataset last column, as new column\n     *\n     * [[1,2], [3,4]]  concat  [[\"a\",\"b\"], [\"c\",\"d\"]]  =>  [[1,2,\"a\",\"b\"], [3,4,\"c\",\"d\"]]\n     *\n     * @param dataset the dataset\n     * @param subset  the sub dataset\n     */\n    public static void concat(List<List<Object>> dataset, List<List<Object>> subset) {\n        if (subset.isEmpty()) {\n            return;\n        }\n\n        if (dataset.isEmpty()) {\n            dataset.addAll(subset);\n            return;\n        }\n\n        int maxRow = Math.max(dataset.size(), subset.size());\n        completeRow(dataset, maxRow);\n        completeRow(subset, maxRow);\n\n        for (int i = 0; i < maxRow; i++) {\n            dataset.get(i).addAll(subset.get(i));\n        }\n    }\n\n    private static void completeRow(List<List<Object>> listArray, int maxRow) {\n        if (listArray.size() >= maxRow) {\n            return;\n        }\n\n        List<Object> lastRow = listArray.get(listArray.size() - 1);\n        for (int i = maxRow - listArray.size(); i > 0; i--) {\n            listArray.add(new LinkedList<>(lastRow)); // repeat last row\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/json/JsonId.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema.json;\n\nimport cn.ponfee.commons.schema.DataType;\nimport cn.ponfee.commons.tree.NodeId;\n\nimport javax.annotation.Nonnull;\nimport java.util.Objects;\n\n/**\n * The element id of json data structure\n * \n * @author Ponfee\n */\npublic class JsonId extends NodeId<JsonId> {\n\n    private static final long serialVersionUID = -6344204521700761391L;\n\n    private final String   name; // 节点名称\n    private final DataType type; // 数据类型\n    private final int    orders; // 次序\n\n    public JsonId(JsonId parent, @Nonnull String name,\n                  DataType type, int orders) {\n        super(parent);\n        this.name   = Objects.requireNonNull(name);\n        this.type   = type;\n        this.orders = orders;\n    }\n\n    @Override\n    protected boolean equals(JsonId another) {\n        return this.name.equals(another.name);\n    }\n\n    @Override\n    protected int compare(JsonId another) {\n        int a = this.orders - another.orders;\n        return a != 0 ? a : this.name.compareTo(another.name);\n    }\n\n    @Override\n    protected int hash() {\n        return this.name == null ? 0 : this.name.hashCode();\n    }\n\n    @Override\n    public JsonId clone() {\n        return new JsonId(\n            this.parent == null ? null : this.parent.clone(), \n            this.name, this.type, this.orders\n        );\n    }\n\n    // -----------------------------------------------------------------setter\n    public String getName() {\n        return name;\n    }\n\n    public DataType getType() {\n        return type;\n    }\n\n    public int getOrders() {\n        return orders;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/schema/json/JsonTree.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.schema.json;\n\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.model.Null;\nimport cn.ponfee.commons.model.ToJsonString;\nimport cn.ponfee.commons.schema.DataType;\nimport cn.ponfee.commons.tree.NodePath;\nimport cn.ponfee.commons.tree.TreeNode;\nimport cn.ponfee.commons.tree.TreeTrait;\nimport com.alibaba.fastjson.annotation.JSONField;\nimport org.apache.commons.collections4.CollectionUtils;\n\nimport java.io.Serializable;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * Json data tree structure\n * \n * @author Ponfee\n */\npublic class JsonTree extends ToJsonString implements Serializable, Comparable<JsonTree>, TreeTrait<JsonId, Null, JsonTree> {\n\n    private static final long serialVersionUID = 2185766536906561848L;\n\n    // 解决NodePath泛型参数为具体类型时，FastJson反序列化的报错问题\n    @JSONField(deserializeUsing = NodePath.FastjsonDeserializer.class)\n    private NodePath<String>   path; // 路径\n    private String             name; // 节点\n    private int              orders; // 次序\n    private boolean         checked; // 是否选中\n    private DataType           type; // 数据类型\n    private List<JsonTree> children; // 子节点列表\n\n    @Override\n    public int compareTo(JsonTree o) {\n        return this.path.compareTo(o.path);\n    }\n\n    @Override\n    public int hashCode() {\n        return this.path.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (!(obj instanceof JsonTree)) {\n            return false;\n        }\n\n        JsonTree other = (JsonTree) obj;\n        if (!this.path.equals(other.path)) {\n            return false;\n        }\n\n        if (   CollectionUtils.isEmpty(this.children)\n            && CollectionUtils.isEmpty(other.children)\n        ) {\n            return true;\n        }\n\n        if (   CollectionUtils.isEmpty(this.children)\n            || CollectionUtils.isEmpty(other.children)\n            || this.children.size() != other.children.size()\n        ) {\n            return false;\n        }\n\n        try {\n            this.sortByName();\n            other.sortByName();\n\n            for (int i = 0, n = this.children.size(); i < n; i++) {\n                if (!this.children.get(i).equals(other.children.get(i))) {\n                    return false;\n                }\n            }\n\n            return true;\n        } finally {\n            this.sortByOrders();\n            other.sortByOrders();\n        }\n    }\n\n    @Override\n    public String toString() {\n        return Jsons.toJson(this);\n        //return JSON.toJSONString(this, FastjsonPropertyFilter.exclude(\"children\"));\n    }\n\n    @Override\n    public void setChildren(List<JsonTree> children) {\n        if (CollectionUtils.isNotEmpty(children)) {\n            List<String> duplicated = Collects.duplicate(children, JsonTree::getName);\n            if (CollectionUtils.isNotEmpty(duplicated)) {\n                throw new IllegalStateException(\"Duplicated child name \" + duplicated);\n            }\n        }\n\n        this.children = children;\n    }\n\n    // --------------------------------------------------------------------------sort\n    public void sortByOrders() {\n        this.sortChildren(Comparator.comparing(JsonTree::getOrders));\n    }\n\n    public void sortByName() {\n        this.sortChildren(Comparator.comparing(JsonTree::getName));\n    }\n\n    public void sortChildren(Comparator<JsonTree> comparator) {\n        if (CollectionUtils.isNotEmpty(this.children)) {\n            this.children.sort(comparator);\n            for (JsonTree node : this.children) {\n                node.sortChildren(comparator);\n            }\n        }\n    }\n\n    public Map<NodePath<String>, JsonTree> toFlatMap() {\n        Map<NodePath<String>, JsonTree> map = new HashMap<>();\n        this.toFlatMap(map);\n        return map;\n    }\n\n    // --------------------------------------------------------------------------static methods\n    public static JsonTree convert(TreeNode<JsonId, Null> tree) {\n        JsonId id = tree.getNid();\n        JsonTree jt = new JsonTree();\n\n        jt.setName(id.getName());\n        jt.setOrders(id.getOrders());\n        jt.setChecked(false);\n        jt.setType(id.getType());\n        jt.setPath(new NodePath<>(tree.getPath().stream().map(JsonId::getName).collect(Collectors.toList())));\n\n        return jt;\n    }\n\n    public static boolean hasChoose(JsonTree root) {\n        if (root == null) {\n            return false;\n        }\n        checkChoose(root);\n        return root.checked;\n    }\n\n    // --------------------------------------------------------------------------getter/setter\n    public NodePath<String> getPath() {\n        return path;\n    }\n\n    public void setPath(NodePath<String> path) {\n        this.path = path;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getOrders() {\n        return orders;\n    }\n\n    public void setOrders(int orders) {\n        this.orders = orders;\n    }\n\n    public boolean isChecked() {\n        return checked;\n    }\n\n    public void setChecked(boolean checked) {\n        this.checked = checked;\n    }\n\n    @Override\n    public List<JsonTree> getChildren() {\n        return children;\n    }\n\n    public DataType getType() {\n        return type;\n    }\n\n    public void setType(DataType type) {\n        this.type = type;\n    }\n\n    // --------------------------------------------------------------------------private methods\n    private static boolean checkChoose(JsonTree node) {\n        if (CollectionUtils.isEmpty(node.children)) {\n            return node.checked;\n        }\n\n        boolean hasLeafChildChoose = false;\n        for (JsonTree child : node.children) {\n            if (child.checked && !node.checked) {\n                throw new IllegalStateException(\n                    \"Child is checked but parent is unchecked: \" + child.path.toString()\n                );\n            }\n            if (CollectionUtils.isEmpty(child.children)) {\n                // leaf node\n                if (child.checked && JsonExtractUtils.ARRAY_EMPTY.equals(child.name)) {\n                    throw new IllegalStateException(\n                        \"Empty array cannot be checked: \" + child.path.toString()\n                    );\n                }\n                hasLeafChildChoose |= child.checked;\n            } else {\n                hasLeafChildChoose |= checkChoose(child);\n            }\n        }\n\n        if (node.checked && !hasLeafChildChoose) {\n            throw new IllegalStateException(\n                \"Parent is checked but not checked children: \" + node.path.toString()\n            );\n        }\n        return hasLeafChildChoose;\n    }\n\n    private void toFlatMap(Map<NodePath<String>, JsonTree> map) {\n        map.put(this.getPath(), this);\n        if (CollectionUtils.isNotEmpty(this.children)) {\n            for (JsonTree node : this.children) {\n                node.toFlatMap(map);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/ByteArraySerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.io.GzipProcessor;\nimport cn.ponfee.commons.reflect.ClassUtils;\n\n/**\n * 字段串序例化\n * \n * @author Ponfee\n */\npublic class ByteArraySerializer extends Serializer {\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        if (!(obj instanceof byte[])) {\n            throw new SerializationException(\n                \"Object must be byte[].class type, but it's \" + ClassUtils.getClassName(obj.getClass()) + \" type.\"\n            );\n        }\n\n        byte[] bytes = (byte[]) obj;\n        return compress ? GzipProcessor.compress(bytes) : bytes;\n    }\n\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress) {\n        if (clazz != byte[].class) {\n            throw new SerializationException(\n                \"clazz must be byte[].class, but it's \" + ClassUtils.getClassName(clazz) + \".class\"\n            );\n        }\n\n        return (T) (compress ? GzipProcessor.decompress(bytes) : bytes);\n    }\n\n    // -------------------------------------------------------------------\n    public byte[] serialize(byte[] bytes, boolean compress) {\n        if (bytes == null || bytes.length == 0) {\n            return bytes;\n        }\n        return compress ? GzipProcessor.compress(bytes) : bytes;\n    }\n\n    public byte[] deserialize(byte[] bytes, boolean compress) {\n        if (bytes == null || bytes.length == 0) {\n            return bytes;\n        }\n        return compress ? GzipProcessor.decompress(bytes) : bytes;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/ByteArrayTraitSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.collect.ByteArrayTrait;\nimport cn.ponfee.commons.io.GzipProcessor;\nimport cn.ponfee.commons.reflect.ClassUtils;\n\n/**\n * \n * Byte array trait Serializer\n * \n * @author Ponfee\n */\npublic class ByteArrayTraitSerializer extends Serializer {\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        if (!(obj instanceof ByteArrayTrait)) {\n            throw new SerializationException(\n                \"object must be ByteArrayTrait type, but it's \" + ClassUtils.getClassName(obj.getClass()) + \" type\"\n            );\n        }\n\n        return serialize((ByteArrayTrait) obj, compress);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress) {\n        if (!ByteArrayTrait.class.isAssignableFrom(clazz)) {\n            throw new SerializationException(\n                \"clazz must be ByteArrayTrait.class, but it's \" + ClassUtils.getClassName(clazz) + \".class\"\n            );\n        }\n\n        if (compress) {\n            bytes = GzipProcessor.decompress(bytes);\n        }\n        return (T) ofBytes(bytes, (Class<? extends ByteArrayTrait>) clazz);\n    }\n\n    public static <T extends ByteArrayTrait> T ofBytes(byte[] bytes, Class<T> type) {\n        //return clazz.getDeclaredMethod(\"fromByteArray\", byte[].class).invoke(null, bytes);\n        return ClassUtils.newInstance(type, new Class<?>[]{byte[].class}, new Object[]{bytes});\n    }\n\n    // ----------------------------------------------------------------------\n    /**\n     * serialize the byte array of ByteArrayTrait\n     * \n     * @param trait\n     * @param compress\n     * @return\n     */\n    public byte[] serialize(ByteArrayTrait trait, boolean compress) {\n        if (trait == null) {\n            return null;\n        }\n\n        byte[] bytes = trait.toByteArray();\n        return compress ? GzipProcessor.compress(bytes) : bytes;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/FstSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.io.GzipProcessor;\nimport org.apache.commons.lang3.ClassUtils;\nimport org.nustaq.serialization.FSTConfiguration;\n\n/**\n * Fst Serializer\n * \n * @author Ponfee\n */\npublic class FstSerializer extends Serializer {\n\n    // createDefaultConfiguration\n    private static final ThreadLocal<FSTConfiguration> FST_CFG =\n        ThreadLocal.withInitial(FSTConfiguration::createStructConfiguration);\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        byte[] bytes = FST_CFG.get().asByteArray(obj);\n        return compress ? GzipProcessor.compress(bytes) : bytes;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress) {\n        if (compress) {\n            bytes = GzipProcessor.decompress(bytes);\n        }\n\n        T obj = (T) FST_CFG.get().asObject(bytes);\n        if (obj != null && !ClassUtils.isAssignable(obj.getClass(), clazz)) {\n            throw new ClassCastException(\n                ClassUtils.getName(obj.getClass()) + \" can't be cast to \" + ClassUtils.getName(clazz)\n            );\n        }\n        return obj;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/HessianSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.io.Closeables;\nimport cn.ponfee.commons.io.ExtendedGZIPOutputStream;\nimport com.caucho.hessian.io.HessianSerializerInput;\nimport com.caucho.hessian.io.HessianSerializerOutput;\nimport org.apache.commons.lang3.ClassUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\n\n/**\n * hessian序例化\n * \n * @author Ponfee\n */\npublic class HessianSerializer extends Serializer {\n\n    private static final Logger LOG = LoggerFactory.getLogger(HessianSerializer.class);\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        GZIPOutputStream gzout = null;\n        HessianSerializerOutput hessian = null;\n        try {\n            ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE);\n            if (compress) {\n                gzout = new ExtendedGZIPOutputStream(baos);\n                hessian = new HessianSerializerOutput(gzout);\n            } else {\n                hessian = new HessianSerializerOutput(baos);\n            }\n            hessian.writeObject(obj);\n            hessian.close();\n            hessian = null;\n            if (gzout != null) {\n                gzout.close();\n                gzout = null;\n            }\n            return baos.toByteArray();\n        } catch (IOException e) {\n            throw new SerializationException(e);\n        } finally {\n            if (hessian != null) {\n                try {\n                    hessian.close();\n                } catch (IOException e) {\n                    LOG.error(\"close hessian exception\", e);\n                }\n            }\n            Closeables.log(gzout, \"close GZIPOutputStream exception\");\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress) {\n        GZIPInputStream gzin = null;\n        HessianSerializerInput hessian = null;\n        try {\n            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);\n            if (compress) {\n                gzin = new GZIPInputStream(bais);\n                hessian = new HessianSerializerInput(gzin);\n            } else {\n                hessian = new HessianSerializerInput(bais);\n            }\n            T t = (T) hessian.readObject();\n            if (t != null && !ClassUtils.isAssignable(t.getClass(), clazz)) {\n                throw new ClassCastException(\n                    ClassUtils.getName(t.getClass()) + \" can't be cast to \" + ClassUtils.getName(clazz)\n                );\n            }\n            return t;\n        } catch (IOException e) {\n            throw new SerializationException(e);\n        } finally {\n            if (hessian != null) {\n                try {\n                    hessian.close();\n                } catch (Exception e) {\n                    LOG.error(\"close hessian exception\", e);\n                }\n            }\n            Closeables.log(gzin, \"close GZIPInputStream exception\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/JdkSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.io.Closeables;\nimport cn.ponfee.commons.io.ExtendedGZIPOutputStream;\nimport org.apache.commons.lang3.ClassUtils;\n\nimport java.io.*;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\n\n/**\n * <pre>\n * JDK序例化\n *\n * 1、Default serialization process：仅实现了Serializable接口，则JDK会使用默认的序列化进程序列化和反序列化对象\n * Note：针对所有non-transient和non-static成员变量\n * {@code java.io.ObjectOutputStream#defaultWriteObject() }\n * {@code java.io.ObjectInputStream#defaultReadObject() }\n *\n * 2、Customizing the serialization process：不仅实现了Serializable接口还定义了两个方法，则JDK会使用这两个方法定制化的进行序列化和反序列化对象\n * Note: must private access modifier, Subclasses will be inherit this method\n * {@code private void readObject(ObjectInputStream input) }\n * {@code private void writeObject(ObjectOutputSteam out) }\n *\n * 3、java.io.Externalizable：该接口是继承于Serializable，也是自定义实现序列化和反序列化方式的一种方式\n * {@code void writeExternal(ObjectOutput out) }\n * {@code void readExternal(ObjectInput in) }\n * </pre>\n * \n * @author Ponfee\n */\npublic class JdkSerializer extends Serializer {\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        GZIPOutputStream gzout = null;\n        ObjectOutputStream oos = null;\n        try {\n            ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE);\n            if (compress) {\n                gzout = new ExtendedGZIPOutputStream(baos);\n                oos = new ObjectOutputStream(gzout);\n            } else {\n                oos = new ObjectOutputStream(baos);\n            }\n            oos.writeObject(obj);\n            oos.close();\n            oos = null;\n            if (gzout != null) {\n                gzout.close();\n                gzout = null;\n            }\n            return baos.toByteArray();\n        } catch (IOException e) {\n            throw new SerializationException(e);\n        } finally {\n            // 先打开的后关闭，后打开的先关闭\n            // 看依赖关系，如果流a依赖流b，应该先关闭流a，再关闭流b\n            // 处理流a依赖节点流b，应该先关闭处理流a，再关闭节点流b\n            Closeables.log(oos, \"close ObjectOutputStream exception\");\n            Closeables.log(gzout, \"close GZIPOutputStream exception\");\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress) {\n        GZIPInputStream gzin = null;\n        ObjectInputStream ois = null;\n        try {\n            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);\n            if (compress) {\n                gzin = new GZIPInputStream(bais);\n                ois = new ObjectInputStream(gzin);\n            } else {\n                ois = new ObjectInputStream(bais);\n            }\n\n            T t = (T) ois.readObject();\n            if (t != null && !ClassUtils.isAssignable(t.getClass(), clazz)) {\n                throw new ClassCastException(\n                    ClassUtils.getName(t.getClass()) + \" can't be cast to \" + ClassUtils.getName(clazz)\n                );\n            }\n            return t;\n        } catch (IOException | ClassNotFoundException e) {\n            throw new SerializationException(e);\n        } finally {\n            Closeables.log(ois, \"close ObjectInputStream exception\");\n            Closeables.log(gzin, \"close GZIPInputStream exception\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/JsonSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.io.Closeables;\nimport cn.ponfee.commons.io.ExtendedGZIPOutputStream;\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.apache.commons.io.IOUtils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\n\n/**\n * json序例化\n * \n * @author Ponfee\n */\npublic class JsonSerializer extends Serializer {\n\n    /** json object mapper */\n    private static final ObjectMapper MAPPER = new ObjectMapper();\n    static {\n        MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);\n        MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);\n    }\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        GZIPOutputStream gzout = null;\n        try {\n            byte[] data = MAPPER.writeValueAsBytes(obj);\n            if (compress) {\n                ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE);\n                gzout = new ExtendedGZIPOutputStream(baos);\n                gzout.write(data, 0, data.length);\n                gzout.close();\n                gzout = null;\n                data = baos.toByteArray();\n            }\n            return data;\n        } catch (IOException e) {\n            throw new SerializationException(e);\n        } finally {\n            Closeables.log(gzout, \"close GZIPOutputStream exception\");\n        }\n    }\n\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress) {\n        GZIPInputStream gzin = null;\n        try {\n            if (compress) {\n                gzin = new GZIPInputStream(new ByteArrayInputStream(bytes));\n                bytes =  IOUtils.toByteArray(gzin);\n            }\n            return MAPPER.readValue(bytes, clazz);\n        } catch (IOException e) {\n            throw new SerializationException(e);\n        } finally {\n            Closeables.log(gzin, \"close GZIPInputStream exception\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/KryoSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.io.Closeables;\nimport cn.ponfee.commons.io.ExtendedGZIPOutputStream;\nimport cn.ponfee.commons.io.Files;\nimport com.esotericsoftware.kryo.Kryo;\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\nimport com.esotericsoftware.kryo.io.Input;\nimport com.esotericsoftware.kryo.io.Output;\nimport com.esotericsoftware.kryo.util.Pool;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\n\n/**\n * kryo序例化\n * \n * the bean class must include default no-arg constructor\n * \n * @author Ponfee\n */\npublic class KryoSerializer extends Serializer {\n\n    private static final Logger LOG = LoggerFactory.getLogger(KryoSerializer.class);\n    public static final KryoSerializer INSTANCE = new KryoSerializer();\n\n    //private static final ThreadLocal<Kryo> KRYO_HOLDER = ThreadLocal.withInitial(Kryo::new);\n\n    // Pool constructor arguments: thread safe, soft references, maximum capacity\n    private static final Pool<Kryo> KRYO_POOL = new Pool<Kryo>(true, false, 32) {\n        @Override\n        protected Kryo create () {\n            Kryo kryo = new Kryo();\n            // Configure the Kryo instance.\n            kryo.setRegistrationRequired(false);\n            //kryo.register(A.class, B.class);\n            //kryo.register(B.class, new com.esotericsoftware.kryo.serializers.JavaSerializer());\n            //kryo.addDefaultSerializer(A.class, ASerializer.class);\n            return kryo;\n        }\n    };\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        GZIPOutputStream gzout = null;\n        Output output = null;\n        Kryo kryo = null;\n        try {\n            ByteArrayOutputStream baos = new ByteArrayOutputStream(BYTE_SIZE);\n            if (compress) {\n                gzout = new ExtendedGZIPOutputStream(baos);\n                output = new ByteBufferOutput(gzout, Files.BUFF_SIZE);\n            } else {\n                output = new ByteBufferOutput(baos, Files.BUFF_SIZE);\n            }\n            (kryo = obtain()).writeObject(output, obj);\n            output.close();\n            output = null;\n            if (gzout != null) {\n                gzout.close();\n                gzout = null;\n            }\n            return baos.toByteArray();\n        } catch (IOException e) {\n            throw new SerializationException(e);\n        } finally {\n            free(kryo);\n            Closeables.log(output, \"close Output exception\");\n            Closeables.log(gzout, \"close GZIPOutputStream exception\");\n        }\n    }\n\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress) {\n        GZIPInputStream gzin = null;\n        Input input = null;\n        Kryo kryo = null;\n        try {\n            if (compress) {\n                gzin = new GZIPInputStream(new ByteArrayInputStream(bytes));\n                input = new ByteBufferInput(gzin);\n            } else {\n                input = new ByteBufferInput(bytes);\n            }\n            return (kryo = obtain()).readObject(input, clazz);\n        } catch (IOException e) {\n            throw new SerializationException(e);\n        } finally {\n            free(kryo);\n            Closeables.log(input, \"close Input exception\");\n            Closeables.log(gzin, \"close GZIPInputStream exception\");\n        }\n    }\n\n    private Kryo obtain() {\n        return KRYO_POOL.obtain();\n    }\n\n    private void free(Kryo kryo) {\n        if (kryo == null) {\n            return;\n        }\n        try {\n            KRYO_POOL.free(kryo);\n        } catch (Throwable t) {\n            LOG.error(\"release kryo occur error\", t);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/NullSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\n/**\n * The {@code NullSerializer} class is representing unable serializer, \n * it will be throws NullPointerException\n * \n * @author Ponfee\n */\npublic final class NullSerializer extends Serializer {\n\n    public static final NullSerializer SINGLETON = new NullSerializer();\n\n    private NullSerializer() {}\n\n    @Override\n    protected <T> byte[] serialize0(T obj, boolean compress) {\n        throw new NullPointerException();\n    }\n\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress) {\n        throw new NullPointerException();\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/ProtostuffSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.io.GzipProcessor;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.SynchronizedCaches;\nimport io.protostuff.LinkedBuffer;\nimport io.protostuff.ProtostuffIOUtil;\nimport io.protostuff.Schema;\nimport io.protostuff.runtime.RuntimeSchema;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Protostuff Serializer\n * \n * @author Ponfee\n */\npublic class ProtostuffSerializer extends Serializer {\n\n    private static final Map<Class<?>, Schema<?>> SCHEMA_CACHE = new HashMap<>();\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    protected <T> byte[] serialize0(T obj, boolean compress) {\n        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);\n        try {\n            byte[] bytes = ProtostuffIOUtil.toByteArray(\n                obj, getSchema((Class<T>) obj.getClass()), buffer\n            );\n            return compress ? GzipProcessor.compress(bytes) : bytes;\n        } finally {\n            buffer.clear();\n        }\n    }\n\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> type, boolean compress) {\n        if (compress) {\n            bytes = GzipProcessor.decompress(bytes);\n        }\n\n        T message = ObjectUtils.newInstance(type);\n        ProtostuffIOUtil.mergeFrom(bytes, message, getSchema(type));\n        return message;\n    }\n\n    // ------------------------------------------------------------------------private methods\n    @SuppressWarnings(\"unchecked\")\n    private static <T> Schema<T> getSchema(Class<T> type) {\n        return (Schema<T>) SynchronizedCaches.get(type, SCHEMA_CACHE, RuntimeSchema::createFrom);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/SerializationException.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\n/**\n * 序例化异常类\n * \n * @author Ponfee\n */\npublic class SerializationException extends RuntimeException {\n\n    private static final long serialVersionUID = -5285807406910063551L;\n\n    public SerializationException(String msg, Throwable cause) {\n        super(msg, cause);\n    }\n\n    public SerializationException(String msg) {\n        super(msg);\n    }\n\n    public SerializationException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/Serializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\n/**\n * 序例化抽象类\n * \n * Method template pattern\n * \n * @author Ponfee\n */\npublic abstract class Serializer {\n\n    static final int BYTE_SIZE = 512;\n\n    /**\n     * 对象序例化为流数据\n     * \n     * @param obj 对象\n     * @param compress 是否要压缩：true是；false否；\n     * @return 序例化后的流数据\n     */\n    protected abstract <T> byte[] serialize0(T obj, boolean compress);\n\n    /**\n     * 流数据反序例化为对象\n     * \n     * @param bytes 流数据\n     * @param compress 是否被压缩：true是；false否；\n     * @return 反序例化后的对象\n     */\n    protected abstract <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress);\n\n    // ----------------------------------------------------------------------------------\n    public final byte[] serialize(Object obj, boolean compress) {\n        if (obj == null) {\n            return null;\n        }\n        return Serializer.this.serialize0(obj, compress);\n    }\n\n    public final byte[] serialize(Object obj) {\n        return serialize(obj, false);\n    }\n\n    public final <T> T deserialize(byte[] bytes, Class<T> clazz, boolean compress) {\n        if (bytes == null) {\n            return null;\n        }\n        return Serializer.this.deserialize0(bytes, clazz, compress);\n    }\n\n    public final <T> T deserialize(byte[] bytes, Class<T> clazz) {\n        return this.deserialize(bytes, clazz, false);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/StringSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.io.GzipProcessor;\nimport cn.ponfee.commons.reflect.ClassUtils;\n\nimport java.nio.charset.Charset;\n\n/**\n * 字段串序例化\n * \n * @author Ponfee\n */\npublic class StringSerializer extends Serializer {\n\n    private final Charset charset;\n\n    public StringSerializer() {\n        this(Charset.defaultCharset());\n    }\n\n    public StringSerializer(String charset) {\n        this(Charset.forName(charset));\n    }\n\n    public StringSerializer(Charset charset) {\n        if (charset == null) {\n            throw new IllegalArgumentException(\"charset cannot be null\");\n        }\n        this.charset = charset;\n    }\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        if (!(obj instanceof String)) {\n            throw new SerializationException(\n                \"object must be java.lang.String type, but it's \" + ClassUtils.getClassName(obj.getClass()) + \" type\"\n            );\n        }\n\n        return serialize((String) obj, compress);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    protected <T> T deserialize0(byte[] bytes, Class<T> clazz, boolean compress) {\n        if (clazz != String.class) {\n            throw new SerializationException(\n                \"clazz must be java.lang.String.class, but it's \" + ClassUtils.getClassName(clazz) + \".class\"\n            );\n        }\n\n        return (T) deserialize(bytes, compress);\n    }\n\n    // ----------------------------------------------------------------------\n    /**\n     * serialize the byte array of string\n     * \n     * @param str\n     * @param compress\n     * @return\n     */\n    public byte[] serialize(String str, boolean compress) {\n        if (str == null) {\n            return null;\n        }\n\n        byte[] bytes = str.getBytes(charset);\n        return compress ? GzipProcessor.compress(bytes) : bytes;\n    }\n\n    /**\n     * deserialize the byte array to string\n     * \n     * @param bytes\n     * @param compress\n     * @return\n     */\n    public String deserialize(byte[] bytes, boolean compress) {\n        if (bytes == null) {\n            return null;\n        }\n        if (compress) {\n            bytes = GzipProcessor.decompress(bytes);\n        }\n        return new String(bytes, charset);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/ToStringSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.io.GzipProcessor;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.util.ObjectUtils;\n\nimport javax.annotation.Nonnull;\nimport java.lang.reflect.Constructor;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Objects;\n\n/**\n * Object toString Serializer\n * \n * @author Ponfee\n */\npublic class ToStringSerializer extends Serializer {\n\n    private final Charset charset;\n\n    public ToStringSerializer() {\n        this(StandardCharsets.UTF_8);\n    }\n\n    public ToStringSerializer(@Nonnull Charset charset) {\n        this.charset = Objects.requireNonNull(charset);\n    }\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        byte[] bytes = obj.toString().getBytes(charset);\n        return compress ? GzipProcessor.compress(bytes) : bytes;\n    }\n\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> type, boolean compress) {\n        if (compress) {\n            bytes = GzipProcessor.decompress(bytes);\n        }\n\n        Constructor<T> constructor = ClassUtils.getConstructor(type, byte[].class);\n        if (constructor != null) {\n            try {\n                return constructor.newInstance(bytes);\n            } catch (Exception e) {\n                throw new IllegalStateException(e);\n            }\n        } else {\n            return ObjectUtils.cast(new String(bytes, charset), type);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/serial/WrappedSerializer.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.serial;\n\nimport cn.ponfee.commons.base.Symbol;\nimport cn.ponfee.commons.collect.ByteArrayTrait;\nimport cn.ponfee.commons.collect.ByteArrayWrapper;\nimport cn.ponfee.commons.io.GzipProcessor;\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.util.Bytes;\nimport com.google.common.collect.ImmutableMap.Builder;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.EnumUtils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\n/**\n * Wrapped other Serializer\n *\n * @author Ponfee\n */\npublic class WrappedSerializer extends Serializer {\n\n    public static final WrappedSerializer WRAPPED_KRYO_SERIALIZER = new WrappedSerializer(KryoSerializer.INSTANCE);\n\n    public static final WrappedSerializer WRAPPED_TOSTRING_SERIALIZER = new WrappedSerializer(new ToStringSerializer());\n\n    public static final byte BOOL_TRUE_BYTE = (byte) 0xFF;\n    public static final byte BOOL_FALSE_BYTE = 0x00;\n\n    private static final Map<Class<?>, Object> PRIMITIVES = new Builder<Class<?>, Object>()\n        .put(boolean.class, Boolean.FALSE)\n        .put(byte.class,    Numbers.ZERO_BYTE)\n        .put(short.class,  (short) 0)\n        .put(char.class,   Symbol.Char.ZERO)\n        .put(int.class,    Numbers.ZERO_INT)\n        .put(long.class,   0L)\n        .put(float.class,  0.0F)\n        .put(double.class, 0.0D)\n        .build();\n\n    private final Serializer wrapper;\n\n    public WrappedSerializer(Serializer wrapped) {\n        this.wrapper = wrapped;\n    }\n\n    @Override\n    protected byte[] serialize0(Object obj, boolean compress) {\n        byte[] bytes = serialize0(obj);\n        return compress ? GzipProcessor.compress(bytes) : bytes;\n    }\n\n    @Override\n    protected <T> T deserialize0(byte[] bytes, Class<T> type, boolean compress) {\n        if (compress) {\n            bytes = GzipProcessor.decompress(bytes);\n        }\n        return deserialize0(bytes, type);\n    }\n\n    // ---------------------------------------------------------------------------primitive&wrapper type\n    public byte[] serialize(boolean value) {\n        return new byte[]{value ? BOOL_TRUE_BYTE : BOOL_FALSE_BYTE};\n    }\n\n    public byte[] serialize(byte value) {\n        return new byte[]{value};\n    }\n\n    public byte[] serialize(short value) {\n        return Bytes.toBytes(value);\n    }\n\n    public byte[] serialize(char value) {\n        return Bytes.toBytes(value);\n    }\n\n    public byte[] serialize(int value) {\n        return Bytes.toBytes(value);\n    }\n\n    public byte[] serialize(long value) {\n        return Bytes.toBytes(value);\n    }\n\n    public byte[] serialize(float value) {\n        return Bytes.toBytes(value);\n    }\n\n    public byte[] serialize(double value) {\n        return Bytes.toBytes(value);\n    }\n\n    public byte[] serialize(Boolean value) {\n        return value == null ? null : serialize((boolean) value);\n    }\n\n    public byte[] serialize(Byte value) {\n        return value == null ? null : serialize((byte) value);\n    }\n\n    public byte[] serialize(Short value) {\n        return value == null ? null : serialize((short) value);\n    }\n\n    public byte[] serialize(Character value) {\n        return value == null ? null : serialize((char) value);\n    }\n\n    public byte[] serialize(Integer value) {\n        return value == null ? null : serialize((int) value);\n    }\n\n    public byte[] serialize(Long value) {\n        return value == null ? null : serialize((long) value);\n    }\n\n    public byte[] serialize(Float value) {\n        return value == null ? null : serialize((float) value);\n    }\n\n    public byte[] serialize(Double value) {\n        return value == null ? null : serialize((double) value);\n    }\n\n    // ---------------------------------------------------------------------------other type\n    public byte[] serialize(byte[] value) {\n        return value;\n    }\n\n    public byte[] serialize(Byte[] value) {\n        return value == null ? null : ArrayUtils.toPrimitive(value);\n    }\n\n    public byte[] serialize(Date value) {\n        return value == null ? null : Bytes.toBytes(value.getTime());\n    }\n\n    public byte[] serialize(ByteArrayWrapper value) {\n        return value == null ? null : value.getArray();\n    }\n\n    public byte[] serialize(ByteArrayTrait value) {\n        return value == null ? null : value.toByteArray();\n    }\n\n    public byte[] serialize(CharSequence value) {\n        return value == null ? null : Serializers.STRING.toBytes(value.toString());\n    }\n\n    public byte[] serialize(InputStream value) {\n        if (value == null) {\n            return null;\n        }\n        try (InputStream input = value) {\n            return IOUtils.toByteArray(input);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public byte[] serialize(ByteBuffer value) {\n        return value == null ? null : value.array();\n    }\n\n    public byte[] serialize(Enum<?> value) {\n        // Bytes.toBytes(value.ordinal())\n        return value == null ? null : Serializers.STRING.toBytes(value.name());\n    }\n\n    // ---------------------------------------------------------------------------private methods\n    /**\n     * Returns the serialize byte array data for value\n     *\n     * @param value the value\n     * @return a byte array\n     */\n    private byte[] serialize0(Object value) {\n        if (value == null) {\n            return null;\n        }\n\n        Serializers serializer = Serializers.of(value.getClass());\n        if (serializer != null) {\n            return serializer.toBytes(value);\n        }\n\n        if (value instanceof CharSequence) {\n            return Serializers.STRING.toBytes(value.toString());\n        } else if (value instanceof InputStream) {\n            return serialize((InputStream) value);\n        } else if (value instanceof ByteArrayTrait) {\n            return ((ByteArrayTrait) value).toByteArray();\n        } else if (value instanceof ByteBuffer) {\n            return ((ByteBuffer) value).array();\n        } else if (value instanceof Enum) {\n            return serialize((Enum<?>) value);\n        } else {\n            return wrapper.serialize(value);\n        }\n    }\n\n    /**\n     * Returns a object or primitive value from\n     * deserialize the byte array\n     *\n     * @param value the byte array\n     * @param type  the target type\n     * @return a spec type object\n     */\n    @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n    private <T> T deserialize0(byte[] value, Class<T> type) {\n        if (ArrayUtils.isEmpty(value) && type.isPrimitive()) {\n            return (T) PRIMITIVES.get(type); // primitive type use default value\n        }\n\n        if (value == null) {\n            return null;\n        }\n\n        Serializers serializer = Serializers.of(type);\n        if (serializer != null) {\n            return serializer.fromBytes(value);\n        }\n\n        if (CharSequence.class.isAssignableFrom(type)) {\n            return ClassUtils.newInstance(type, new Class<?>[]{String.class}, new Object[]{Serializers.STRING.fromBytes(value)});\n        } else if (InputStream.class.isAssignableFrom(type)) {\n            return (T) new ByteArrayInputStream(value);\n        } else if (ByteArrayTrait.class.isAssignableFrom(type)) {\n            return (T) ByteArrayTraitSerializer.ofBytes(value, (Class<? extends ByteArrayTrait>) type);\n        } else if (ByteBuffer.class.isAssignableFrom(type)) {\n            return (T) ByteBuffer.wrap(value);\n        } else if (type.isEnum()) {\n            //return type.getEnumConstants()[Bytes.toInt(value)];\n            return (T) EnumUtils.getEnumIgnoreCase((Class<Enum>) type, Serializers.STRING.fromBytes(value));\n        } else {\n            return wrapper.deserialize(value, type);\n        }\n    }\n\n    // ---------------------------------------------------------------------------private class\n    @SuppressWarnings(\"unchecked\")\n    private enum Serializers {\n\n        BOOLEAN(boolean.class, Boolean.class) {\n            @Override\n            byte[] to(Object value) {\n                return new byte[]{(boolean) value ? BOOL_TRUE_BYTE : BOOL_FALSE_BYTE};\n            }\n\n            @Override\n            Boolean from(byte[] value) {\n                return value[0] != BOOL_FALSE_BYTE;\n            }\n        },\n\n        BYTE(byte.class, Byte.class) {\n            @Override\n            byte[] to(Object value) {\n                return new byte[]{(byte) value};\n            }\n\n            @Override\n            Byte from(byte[] value) {\n                return value[0];\n            }\n        },\n\n        SHORT(short.class, Short.class) {\n            @Override\n            byte[] to(Object value) {\n                return Bytes.toBytes((short) value);\n            }\n\n            @Override\n            Short from(byte[] value) {\n                return Bytes.toShort(value);\n            }\n        },\n\n        CHAR(char.class, Character.class) {\n            @Override\n            byte[] to(Object value) {\n                return Bytes.toBytes((char) value);\n            }\n\n            @Override\n            Character from(byte[] value) {\n                return Bytes.toChar(value);\n            }\n        },\n\n        INT(int.class, Integer.class) {\n            @Override\n            byte[] to(Object value) {\n                return Bytes.toBytes((int) value);\n            }\n\n            @Override\n            Integer from(byte[] value) {\n                return Bytes.toInt(value);\n            }\n        },\n\n        LONG(long.class, Long.class) {\n            @Override\n            byte[] to(Object value) {\n                return Bytes.toBytes((long) value);\n            }\n\n            @Override\n            Long from(byte[] value) {\n                return Bytes.toLong(value);\n            }\n        },\n\n        FLOAT(float.class, Float.class) {\n            @Override\n            byte[] to(Object value) {\n                return Bytes.toBytes((float) value);\n            }\n\n            @Override\n            Float from(byte[] value) {\n                return Bytes.toFloat(value);\n            }\n        },\n\n        DOUBLE(double.class, Double.class) {\n            @Override\n            byte[] to(Object value) {\n                return Bytes.toBytes((double) value);\n            }\n\n            @Override\n            Double from(byte[] value) {\n                return Bytes.toDouble(value);\n            }\n        },\n\n        PRIMITIVE_BYTES(byte[].class) {\n            @Override\n            byte[] to(Object value) {\n                return (byte[]) value;\n            }\n\n            @Override\n            byte[] from(byte[] value) {\n                return value;\n            }\n        },\n\n        WRAP_BYTES(Byte[].class) {\n            @Override\n            byte[] to(Object value) {\n                return ArrayUtils.toPrimitive((Byte[]) value);\n            }\n\n            @Override\n            Byte[] from(byte[] value) {\n                return ArrayUtils.toObject(value);\n            }\n        },\n\n        STRING(String.class) {\n            @Override\n            byte[] to(Object value) {\n                return ((String) value).getBytes(UTF_8);\n            }\n\n            @Override\n            String from(byte[] value) {\n                return new String(value, UTF_8);\n            }\n        },\n\n        DATE(Date.class) {\n            @Override\n            byte[] to(Object value) {\n                return Bytes.toBytes(((Date) value).getTime());\n            }\n\n            @Override\n            Date from(byte[] value) {\n                return new Date(Bytes.toLong(value));\n            }\n        },\n\n        BYTE_ARRAY_WRAP(ByteArrayWrapper.class) {\n            @Override\n            byte[] to(Object value) {\n                return ((ByteArrayWrapper) value).getArray();\n            }\n\n            @Override\n            ByteArrayWrapper from(byte[] value) {\n                return ByteArrayWrapper.of(value);\n            }\n        };\n\n        Serializers(Class<?>... types) {\n            for (Class<?> type : types) {\n                Hide.MAPPING.put(type, this);\n            }\n        }\n\n        final byte[] toBytes(Object value) {\n            return value == null ? null : to(value);\n        }\n\n        final <T> T fromBytes(byte[] value) {\n            return value == null ? null : from(value);\n        }\n\n        abstract byte[] to(Object value);\n\n        abstract <T> T from(byte[] value);\n\n        static Serializers of(Class<?> targetType) {\n            return Hide.MAPPING.get(targetType);\n        }\n    }\n\n    private static class Hide {\n        private static final Map<Class<?>, Serializers> MAPPING = new HashMap<>();\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/BaseController.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport cn.ponfee.commons.date.JavaUtilDateFormat;\nimport cn.ponfee.commons.date.LocalDateTimeFormat;\nimport cn.ponfee.commons.model.TypedKeyValue;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.bind.WebDataBinder;\nimport org.springframework.web.bind.annotation.InitBinder;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport java.beans.PropertyEditorSupport;\nimport java.text.ParseException;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.Date;\n\n/**\n * Spring web base controller\n *\n * @author Ponfee\n */\npublic abstract class BaseController implements TypedKeyValue<String, String> {\n\n    protected final Logger log = LoggerFactory.getLogger(getClass());\n\n    @InitBinder\n    public void initBinder(WebDataBinder binder) {\n        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {\n            @Override\n            public void setAsText(String text) {\n                try {\n                    super.setValue(JavaUtilDateFormat.DEFAULT.parse(text));\n                } catch (ParseException e) {\n                    throw new IllegalArgumentException(\"Invalid date format: \" + text);\n                }\n            }\n        });\n        binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {\n            @Override\n            public void setAsText(String text) {\n                super.setValue(LocalDateTimeFormat.DEFAULT.parse(text));\n            }\n        });\n        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {\n            @Override\n            public void setAsText(String text) {\n                LocalDateTime dateTime = LocalDateTimeFormat.DEFAULT.parse(text);\n                super.setValue(dateTime == null ? null : dateTime.toLocalDate());\n            }\n        });\n        binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {\n            private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(\"HH:mm:ss\");\n\n            @Override\n            public void setAsText(String text) {\n                super.setValue(LocalTime.parse(text, timeFormatter));\n            }\n        });\n    }\n\n    public static ServletRequestAttributes getRequestAttributes() {\n        return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();\n    }\n\n    public static HttpServletRequest getRequest() {\n        return getRequestAttributes().getRequest();\n    }\n\n    public static HttpServletResponse getResponse() {\n        return getRequestAttributes().getResponse();\n    }\n\n    public static HttpSession getSession() {\n        return getRequest().getSession();\n    }\n\n    @Override\n    public String getValue(String key) {\n        return getRequest().getParameter(key);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/JdbcTemplateWrapper.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport org.springframework.jdbc.core.ConnectionCallback;\nimport org.springframework.jdbc.core.JdbcTemplate;\nimport org.springframework.jdbc.core.RowMapper;\nimport org.springframework.util.Assert;\n\nimport javax.annotation.Nullable;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Wrapped jdbc template.\n *\n * @author Ponfee\n */\npublic class JdbcTemplateWrapper {\n\n    private final JdbcTemplate jdbcTemplate;\n\n    private JdbcTemplateWrapper(JdbcTemplate jdbcTemplate) {\n        this.jdbcTemplate = jdbcTemplate;\n    }\n\n    public static JdbcTemplateWrapper of(JdbcTemplate jdbcTemplate) {\n        return new JdbcTemplateWrapper(jdbcTemplate);\n    }\n\n    public void execute(String sql) {\n        jdbcTemplate.execute(sql);\n    }\n\n    public <T> T execute(ConnectionCallback<T> action) {\n        return jdbcTemplate.execute(action);\n    }\n\n    public int insert(String sql, @Nullable Object... args) {\n        Assert.isTrue(sql.startsWith(\"INSERT \"), () -> \"Invalid DELETE sql: \" + sql);\n        return jdbcTemplate.update(sql, args);\n    }\n\n    public int update(String sql, @Nullable Object... args) {\n        Assert.isTrue(sql.startsWith(\"UPDATE \"), () -> \"Invalid DELETE sql: \" + sql);\n        return jdbcTemplate.update(sql, args);\n    }\n\n    public int delete(String sql, @Nullable Object... args) {\n        Assert.isTrue(sql.startsWith(\"DELETE \"), () -> \"Invalid DELETE sql: \" + sql);\n        return jdbcTemplate.update(sql, args);\n    }\n\n    public <T> List<T> queryForList(String sql, RowMapper<T> rowMapper, @Nullable Object... args) {\n        Assert.isTrue(sql.startsWith(\"SELECT \"), () -> \"Invalid SELECT sql: \" + sql);\n        return jdbcTemplate.queryForStream(sql, rowMapper, args).collect(Collectors.toList());\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/LocalizedMethodArgumentResolver.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport com.google.common.collect.ImmutableSet;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.core.MethodParameter;\nimport org.springframework.core.annotation.AnnotationUtils;\nimport org.springframework.util.Assert;\nimport org.springframework.web.bind.support.WebDataBinderFactory;\nimport org.springframework.web.context.request.NativeWebRequest;\nimport org.springframework.web.method.support.HandlerMethodArgumentResolver;\nimport org.springframework.web.method.support.ModelAndViewContainer;\n\nimport javax.servlet.ServletInputStream;\nimport javax.servlet.http.HttpServletRequest;\nimport java.io.IOException;\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Type;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\nimport static org.springframework.web.bind.annotation.RequestMethod.*;\n\n/**\n * Localized method parameter for spring web {@code org.springframework.stereotype.Controller} methods.\n * <p>Can defined multiple object arguments for {@code org.springframework.web.bind.annotation.RequestMapping} method.\n *\n * @author Ponfee\n */\npublic class LocalizedMethodArgumentResolver implements HandlerMethodArgumentResolver {\n\n    //private final WeakHashMap<NativeWebRequest, Map<String, Object>> resolvedCache = new WeakHashMap<>();\n\n    private static final Set<String> QUERY_PARAM_METHODS = ImmutableSet.of(\n        GET.name(), DELETE.name(), HEAD.name(), OPTIONS.name()\n    );\n\n    private static final String CACHE_ATTRIBUTE_KEY = \"LOCALIZED_METHOD_ARGUMENTS\";\n\n    private static final Class<? extends Annotation> MARKED_ANNOTATION_TYPE = LocalizedMethodArguments.class;\n\n    @Override\n    public boolean supportsParameter(MethodParameter parameter) {\n        if (!(parameter.getExecutable() instanceof Method)) {\n            return false;\n        }\n\n        return isAnnotationPresent(parameter.getMethod(), MARKED_ANNOTATION_TYPE)\n            || isAnnotationPresent(parameter.getDeclaringClass(), MARKED_ANNOTATION_TYPE);\n    }\n\n    @Override\n    public Object resolveArgument(MethodParameter parameter,\n                                  ModelAndViewContainer mavContainer,\n                                  NativeWebRequest webRequest,\n                                  WebDataBinderFactory binderFactory) throws IOException {\n        Method method = Objects.requireNonNull(parameter.getMethod());\n        HttpServletRequest httpServletRequest = Objects.requireNonNull(webRequest.getNativeRequest(HttpServletRequest.class));\n        int parameterIndex = parameter.getParameterIndex();\n        Object[] arguments;\n        if (parameterIndex == 0) {\n            arguments = parseMethodParameters(method, httpServletRequest);\n            if (method.getParameterCount() > 1) {\n                // CACHE_KEY_PREFIX + method.toString()\n                httpServletRequest.setAttribute(CACHE_ATTRIBUTE_KEY, arguments);\n            }\n        } else {\n            arguments = (Object[]) httpServletRequest.getAttribute(CACHE_ATTRIBUTE_KEY);\n        }\n\n        return Collects.get(arguments, parameterIndex);\n    }\n\n    private Object[] parseMethodParameters(Method method, HttpServletRequest request) throws IOException {\n        if (QUERY_PARAM_METHODS.contains(request.getMethod())) {\n            return parseQueryString(method, request.getParameterMap());\n        } else {\n            try (ServletInputStream inputStream = request.getInputStream()) {\n                String body = IOUtils.toString(inputStream, StandardCharsets.UTF_8);\n                if (StringUtils.isEmpty(body)) {\n                    return parseQueryString(method, request.getParameterMap());\n                } else {\n                    return Jsons.parseMethodArgs(body, method);\n                }\n            }\n        }\n    }\n\n    private Object[] parseQueryString(Method method, Map<String, String[]> parameterMap) {\n        int parameterCount = method.getParameterCount();\n        Object[] arguments = new Object[parameterCount];\n        for (int i = 0; i < parameterCount; i++) {\n            String argName = \"args[\" + i + \"]\";\n            String[] array = parameterMap.get(argName);\n            Assert.isTrue(\n                array == null || array.length <= 1,\n                () -> \"Argument cannot be multiple value, name: \" + argName + \", value: \" + Jsons.toJson(array)\n            );\n            String argValue = Collects.get(array, 0);\n            Type argType = method.getGenericParameterTypes()[i];\n            if (argValue == null) {\n                // if basic type then set default value\n                arguments[i] = (argType instanceof Class<?>) ? ObjectUtils.cast(null, (Class<?>) argType) : null;\n            } else {\n                arguments[i] = Jsons.fromJson(argValue, argType);\n            }\n        }\n        return arguments;\n    }\n\n    private static boolean isAnnotationPresent(Method method, Class<? extends Annotation> annotationType) {\n        return AnnotationUtils.findAnnotation(method, annotationType) != null;\n    }\n\n    private static boolean isAnnotationPresent(Class<?> clazz, Class<? extends Annotation> annotationType) {\n        return AnnotationUtils.findAnnotation(clazz, annotationType) != null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/LocalizedMethodArguments.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport java.lang.annotation.*;\n\n/**\n * Localization method arguments annotation definition.\n *\n * @author Ponfee\n */\n@Target({ElementType.METHOD, ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Inherited\n@Documented\npublic @interface LocalizedMethodArguments {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/MarkRpcController.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * Mark this subclass is a spring web controller and with rpc({@code LocalizedMethodArguments}) trait\n *\n * @author Ponfee\n */\n@RestController\n@LocalizedMethodArguments\npublic interface MarkRpcController {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/PageMethodArgumentResolver.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.model.PageHandler;\nimport cn.ponfee.commons.model.PageParameter;\nimport cn.ponfee.commons.reflect.Fields;\nimport com.google.common.collect.ImmutableList;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.core.MethodParameter;\nimport org.springframework.web.bind.support.WebDataBinderFactory;\nimport org.springframework.web.context.request.NativeWebRequest;\nimport org.springframework.web.method.support.HandlerMethodArgumentResolver;\nimport org.springframework.web.method.support.ModelAndViewContainer;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport static cn.ponfee.commons.model.PageHandler.DEFAULT_LIMIT;\nimport static cn.ponfee.commons.model.PageHandler.DEFAULT_PAGE_SIZE;\n\n/**\n * 分页查询方法参数解析，在spring-mvc配置文件中做如下配置\n *  <mvc:annotation-driven>\n *    <mvc:argument-resolvers>\n *      <bean class=\"cn.ponfee.commons.spring.PageMethodArgumentResolver\" />\n *    </mvc:argument-resolvers>\n *  </mvc:annotation-driven>\n * \n * 配置完之后PageMethodArgumentResolver这个spring bean会被注入到RequestMappingHandlerAdapter.argumentResolvers中\n * \n * https://blog.csdn.net/lqzkcx3/article/details/78794636\n * \n * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter\n * @see org.springframework.web.method.support.HandlerMethodArgumentResolverComposite\n * @see org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver\n * @see org.springframework.web.method.annotation.RequestParamMethodArgumentResolver\n * \n * @author Ponfee\n */\npublic class PageMethodArgumentResolver implements HandlerMethodArgumentResolver {\n\n    // pageSize(or limit) has not spec or spec less 1 then use this default value\n    private static final int DEFAULT_SIZE = 20;\n\n    private static final List<String> SIZE_PARAMS = ImmutableList.of(\n        DEFAULT_PAGE_SIZE, DEFAULT_LIMIT\n    );\n\n    @Override\n    public boolean supportsParameter(MethodParameter parameter) {\n        //return parameter.hasParameterAnnotation(PageRequestParam.class);\n        return PageParameter.class == parameter.getParameterType();\n    }\n\n    @Override\n    public PageParameter resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,\n                                         NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {\n        Map<String, String[]> params = webRequest.getParameterMap();\n        PageParameter pp = new PageParameter(params.size(), 1);\n        params.forEach((key, value) -> {\n            if (PageParameter.PAGE_PARAMS.contains(key)) {\n                int value0 = Numbers.toInt(value[0], 0);\n                if (value0 < 1 && SIZE_PARAMS.contains(key)) {\n                    value0 = DEFAULT_SIZE;\n                }\n                Fields.put(pp, key, value0);\n                pp.put(key, value0);\n            } else if (PageParameter.SORT_PARAM.equalsIgnoreCase(key)) {\n                // value：“name ASC, age DESC”\n                String value0 = StringUtils.join(value, ',').trim();\n                Fields.put(pp, PageParameter.SORT_PARAM, value0);\n                pp.put(PageParameter.SORT_PARAM, value0);\n            } else {\n                pp.put(key, value.length == 1 ? value[0].trim() : value);\n            }\n        });\n\n        if (pp.getLimit() > 0) { // use limit query\n            if (pp.getLimit() > PageHandler.MAX_SIZE) {\n                pp.setLimit(PageHandler.MAX_SIZE);\n            }\n            if (pp.getOffset() < 0) {\n                pp.setOffset(0); // start with 0\n            }\n        } else {\n            if (pp.getPageSize() < 1) {\n                pp.setPageSize(DEFAULT_SIZE);\n            } else if (pp.getPageSize() > PageHandler.MAX_SIZE) {\n                pp.setPageSize(PageHandler.MAX_SIZE);\n            }\n            if (pp.getPageNum() < 1) {\n                pp.setPageNum(1); // start with 1\n            }\n        }\n\n        return pp;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/ProxyUtils.java",
    "content": "package cn.ponfee.commons.spring;\n\nimport cn.ponfee.commons.reflect.Fields;\nimport org.springframework.aop.framework.Advised;\nimport org.springframework.aop.framework.AdvisedSupport;\nimport org.springframework.aop.support.AopUtils;\n\n/**\n * Spring proxy utils\n *\n * @author Ponfee\n */\npublic class ProxyUtils {\n\n    /**\n     * Returns the proxy target object\n     *\n     * @param object the object\n     * @return target object\n     * @throws Exception\n     */\n    public static Object getTargetObject(Object object) throws Exception {\n        if (!AopUtils.isAopProxy(object)) {\n            return object;\n        }\n        if (object instanceof Advised) {\n            return ((Advised) object).getTargetSource().getTarget();\n        }\n        if (AopUtils.isJdkDynamicProxy(object)) {\n            return getProxyTargetObject(Fields.get(object, \"h\"));\n        }\n        if (AopUtils.isCglibProxy(object)) {\n            return getProxyTargetObject(Fields.get(object, \"CGLIB$CALLBACK_0\"));\n        }\n        return object;\n    }\n\n    private static Object getProxyTargetObject(Object proxy) throws Exception {\n        AdvisedSupport advisedSupport = (AdvisedSupport) Fields.get(proxy, \"advised\");\n        return advisedSupport.getTargetSource().getTarget();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/RpcController.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * Mark this subclass is a spring web controller and with rpc({@code LocalizedMethodArguments}) trait\n *\n * @author Ponfee\n */\n@RestController\n@LocalizedMethodArguments\npublic interface RpcController {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/SpringContextHolder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.DisposableBean;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.util.Assert;\n\nimport javax.annotation.Resource;\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Function;\n\n/**\n * <pre>\n * ContextLoaderListener的bean factory是DispatcherServlet的parent\n * spring上下文无法访问spring mvc上下文，但spring mvc上下文却能访问spring上下文，使用List<ApplicationContext>解决\n * </pre>\n *\n * spring上下文持有类\n *\n * @author Ponfee\n */\npublic class SpringContextHolder implements ApplicationContextAware, DisposableBean {\n\n    private static final List<ApplicationContext> HOLDER = new ArrayList<>();\n\n    private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);\n\n    @Override\n    public void setApplicationContext(ApplicationContext cxt) throws BeansException {\n        synchronized (SpringContextHolder.class) {\n            if (!HOLDER.contains(cxt)) {\n                HOLDER.add(cxt);\n            }\n            INITIALIZED.set(true);\n        }\n    }\n\n    public static boolean isInitialized() {\n        return INITIALIZED.get();\n    }\n\n    /**\n     * 通过名称获取bean\n     *\n     * @param name\n     * @return\n     */\n    public static Object getBean(String name) {\n        return get(c -> c.getBean(name));\n    }\n\n    /**\n     * 通过类获取bean\n     *\n     * @param clazz\n     * @return\n     */\n    public static <T> T getBean(Class<T> clazz) {\n        return get(c -> c.getBean(clazz));\n    }\n\n    /**\n     * @param name\n     * @param clazz\n     * @return\n     */\n    public static <T> T getBean(String name, Class<T> clazz) {\n        return get(c -> c.getBean(name, clazz));\n    }\n\n    /**\n     * 判断是否含有该名称的Bean\n     *\n     * @param name\n     * @return\n     */\n    public static boolean containsBean(String name) {\n        for (ApplicationContext c : HOLDER) {\n            if (c.containsBean(name)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 判断Bean是否单例\n     *\n     * @param name\n     * @return\n     */\n    public static boolean isSingleton(String name) {\n        BeansException ex = null;\n        for (ApplicationContext c : HOLDER) {\n            try {\n                if (c.isSingleton(name)) {\n                    return true;\n                }\n            } catch (BeansException e) {\n                if (ex == null) {\n                    ex = e;\n                }\n            }\n        }\n        if (ex == null) {\n            return false;\n        } else {\n            throw ex;\n        }\n    }\n\n    /**\n     * 获取Bean的类型\n     *\n     * @param name\n     * @return\n     */\n    public static Class<?> getType(String name) {\n        return get(c -> c.getType(name));\n    }\n\n    /**\n     * 获取bean的别名\n     *\n     * @param name\n     * @return\n     */\n    public static String[] getAliases(String name) {\n        return get(c -> c.getAliases(name)); // 不抛异常\n    }\n\n    /**\n     * Returns a map that conatain spec annotation beans\n     *\n     * @param annotationType the Annotation type\n     * @return a map\n     */\n    public static Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {\n        return get(c -> c.getBeansWithAnnotation(annotationType));\n    }\n\n    // -----------------------------------------------------------------------\n    /**\n     * Injects the field from spring container for object\n     *\n     * @param object the object\n     *\n     * @see #autowire(Object)\n     */\n    public static void inject(Object object) {\n        Assert.state(HOLDER.size() > 0, \"Must be defined SpringContextHolder within spring config file.\");\n\n        for (Field field : ClassUtils.listFields(object.getClass())) {\n            Object fieldValue = null;\n            Class<?> fieldType = GenericUtils.getFieldActualType(object.getClass(), field);\n            Resource resource = field.getAnnotation(Resource.class);\n            if (resource != null) {\n                fieldValue = getBean(StringUtils.isNotBlank(resource.name()) ? resource.name() : field.getName(), fieldType);\n                if (fieldValue == null) {\n                    fieldValue = getBean(fieldType);\n                }\n            } else if (field.isAnnotationPresent(Autowired.class)) {\n                Qualifier qualifier = field.getAnnotation(Qualifier.class);\n                if (qualifier != null && StringUtils.isNotBlank(qualifier.value())) {\n                    fieldValue = getBean(qualifier.value(), fieldType);\n                } else {\n                    fieldValue = getBean(fieldType);\n                }\n            }\n\n            if (fieldType.isInstance(fieldValue)) {\n                Fields.put(object, field, fieldValue);\n            }\n        }\n    }\n\n    /**\n     * Autowire annotated from spring container for object\n     *\n     * @param object the object\n     *\n     * @see #inject(Object)\n     */\n    public static void autowire(Object object) {\n        Assert.state(HOLDER.size() > 0, \"Must be defined SpringContextHolder within spring config file.\");\n\n        for (ApplicationContext context : HOLDER) {\n            context.getAutowireCapableBeanFactory().autowireBean(object);\n        }\n    }\n\n    @Override\n    public void destroy() {\n        /*\n        synchronized (SpringContextHolder.class) {\n            HOLDER.clear();\n        }\n        */\n    }\n\n    private static <T> T get(Function<ApplicationContext, T> finder) throws BeansException {\n        //Assert.state(HOLDER.size() > 0, \"must be defined SpringContextHolder within spring config file.\");\n        BeansException ex = null;\n        T result;\n        for (ApplicationContext c : HOLDER) {\n            try {\n                if ((result = finder.apply(c)) != null) {\n                    return result;\n                }\n            } catch (BeansException e) {\n                if (ex == null) {\n                    ex = e;\n                }\n            }\n        }\n\n        if (ex == null) {\n            return null;\n        } else {\n            throw ex;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/SpringUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport org.apache.commons.io.IOUtils;\nimport org.springframework.core.io.InputStreamResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.util.ResourceUtils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.net.URL;\n\n/**\n * Spring utils\n *\n * @author Ponfee\n */\npublic final class SpringUtils {\n\n    public static Resource getResource(String resourceLocation) throws IOException {\n        // return new DefaultResourceLoader().getResource(resourceLocation);\n        URL url = ResourceUtils.getURL(resourceLocation);\n        byte[] bytes = IOUtils.toByteArray(url);\n        return new InputStreamResource(new ByteArrayInputStream(bytes));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/TransactionUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport org.springframework.transaction.PlatformTransactionManager;\nimport org.springframework.transaction.TransactionDefinition;\nimport org.springframework.transaction.TransactionStatus;\nimport org.springframework.transaction.support.DefaultTransactionDefinition;\nimport org.springframework.transaction.support.TransactionSynchronization;\nimport org.springframework.transaction.support.TransactionSynchronizationManager;\nimport org.springframework.util.Assert;\n\nimport java.util.function.Consumer;\n\nimport static cn.ponfee.commons.exception.Throwables.ThrowingSupplier;\n\n/**\n * Spring transaction utility.\n *\n * @author Ponfee\n */\npublic class TransactionUtils {\n\n    /**\n     * 在事务提交后再执行\n     *\n     * @param action the action code\n     */\n    public static void doAfterTransactionCommit(final Runnable action) {\n        if (TransactionSynchronizationManager.isActualTransactionActive()) {\n            TransactionSynchronization ts = new TransactionSynchronization() {\n                @Override\n                public void afterCommit() {\n                    action.run();\n                }\n            };\n            TransactionSynchronizationManager.registerSynchronization(ts);\n        } else {\n            action.run();\n        }\n    }\n\n    /**\n     * 创建一个新事务，如果当前存在事务，则将这个事务挂起。\n     * <p>内部事务与外部事务相互独立，互不依赖。\n     *\n     * @param txManager the txManager\n     * @param action    the action code\n     * @param log       the exception log\n     * @param <R>       return type\n     * @return do action result\n     */\n    public static <R> R doInRequiresNewTransaction(PlatformTransactionManager txManager,\n                                                   ThrowingSupplier<R, Throwable> action,\n                                                   Consumer<Throwable> log) {\n        return doInPropagationTransaction(txManager, action, log, TransactionDefinition.PROPAGATION_REQUIRES_NEW);\n    }\n\n    /**\n     * 如果当前存在事务则开启一个嵌套事务，如果当前不存在事务则新建一个事务并运行。\n     * <p>内部事务为外部事务的一个子事务。\n     * <p>内部事务的提交/回滚不影响外部事务的提交/回滚\n     * <p>内部事务的提交/回滚最终依赖外部事务的提交/回滚。\n     *\n     * @param txManager the txManager\n     * @param action    the action code\n     * @param log       the exception log\n     * @param <R>       return type\n     * @return do action result\n     */\n    public static <R> R doInNestedTransaction(PlatformTransactionManager txManager,\n                                              ThrowingSupplier<R, Throwable> action,\n                                              Consumer<Throwable> log) {\n        Assert.isTrue(\n            TransactionSynchronizationManager.isActualTransactionActive(),\n            \"Do nested transaction must be in parent transaction.\"\n        );\n        return doInPropagationTransaction(txManager, action, log, TransactionDefinition.PROPAGATION_NESTED);\n    }\n\n    // ----------------------------------------------------------------------private methods\n\n    private static <R> R doInPropagationTransaction(PlatformTransactionManager txManager,\n                                                    ThrowingSupplier<R, Throwable> action,\n                                                    Consumer<Throwable> log,\n                                                    int transactionPropagation) {\n        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();\n        txDefinition.setPropagationBehavior(transactionPropagation);\n        TransactionStatus status = txManager.getTransaction(txDefinition);\n        try {\n            R result = action.get();\n            txManager.commit(status);\n            return result;\n        } catch (Throwable t) {\n            txManager.rollback(status);\n            log.accept(t);\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/TypedMapMethodArgumentResolver.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport cn.ponfee.commons.model.TypedParameter;\nimport org.springframework.core.MethodParameter;\nimport org.springframework.web.bind.support.WebDataBinderFactory;\nimport org.springframework.web.context.request.NativeWebRequest;\nimport org.springframework.web.method.support.HandlerMethodArgumentResolver;\nimport org.springframework.web.method.support.ModelAndViewContainer;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * The common request parameter resolver for map model\n * \n * @author Ponfee\n */\npublic class TypedMapMethodArgumentResolver implements HandlerMethodArgumentResolver {\n\n    @Override\n    public boolean supportsParameter(MethodParameter parameter) {\n        return TypedParameter.class == parameter.getParameterType();\n    }\n\n    @Override\n    public TypedParameter resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,\n                                          NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {\n        Map<String, String[]> params = webRequest.getParameterMap();\n        Map<String, Object> map = new LinkedHashMap<>(params.size(), 1);\n        params.forEach((key, value) -> map.put(key, value.length == 1 ? value[0] : value));\n        return new TypedParameter(map);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/YamlProperties.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport cn.ponfee.commons.model.TypedMap;\nimport cn.ponfee.commons.base.Symbol.Char;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.Strings;\nimport org.springframework.core.io.InputStreamResource;\nimport org.springframework.core.io.Resource;\n\nimport java.io.*;\nimport java.lang.reflect.Field;\nimport java.util.Properties;\n\n/**\n * Yaml properties\n *\n * @author Ponfee\n */\npublic class YamlProperties extends Properties implements TypedMap<Object, Object> {\n    private static final long serialVersionUID = -1599483902442715272L;\n\n    public YamlProperties(File file) throws IOException {\n        try (InputStream inputStream = new FileInputStream(file)) {\n            loadYaml(inputStream);\n        }\n    }\n\n    public YamlProperties(InputStream inputStream) {\n        loadYaml(inputStream);\n    }\n\n    public YamlProperties(byte[] content) {\n        loadYaml(new ByteArrayInputStream(content));\n    }\n\n    public <T> T extract(Class<T> beanType, String prefix) {\n        T bean = ClassUtils.newInstance(beanType);\n        char[] separators = {Char.HYPHEN, Char.DOT};\n        for (Field field : ClassUtils.listFields(beanType)) {\n            for (char separator : separators) {\n                String name = prefix + Strings.toSeparatedName(field.getName(), separator);\n                if (super.containsKey(name)) {\n                    Fields.put(bean, field, ObjectUtils.cast(get(name), field.getType()));\n                    break;\n                }\n            }\n        }\n        return bean;\n    }\n\n    private void loadYaml(InputStream inputStream) {\n        Resource resource = new InputStreamResource(inputStream);\n        super.putAll(YamlPropertySourceFactory.loadYml(resource));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/spring/YamlPropertySourceFactory.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.spring;\n\nimport org.springframework.beans.factory.config.YamlPropertiesFactoryBean;\nimport org.springframework.core.env.PropertiesPropertySource;\nimport org.springframework.core.env.PropertySource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.support.DefaultPropertySourceFactory;\nimport org.springframework.core.io.support.EncodedResource;\n\nimport java.io.IOException;\nimport java.util.Properties;\n\n/**\n * Spring yaml properties source factory,\n * <p>for help use annotation {@link org.springframework.context.annotation.PropertySource}\n *\n * <pre>{@code\n * @PropertySource(value = \"classpath:xxx.yml\", factory = YamlPropertySourceFactory.class)\n * public class DruidConfig {\n *   @Value(\"${datasource.jdbc-url}\")\n *   private String jdbcUrl;\n *\n *   @Value(\"${datasource.username}\")\n *   private String username;\n *\n *   @Value(\"${datasource.password}\")\n *   private String password;\n * }}</pre>\n *\n * @author Ponfee\n */\npublic class YamlPropertySourceFactory extends DefaultPropertySourceFactory {\n\n    @Override\n    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {\n        String sourceName = name != null ? name : resource.getResource().getFilename();\n        if (!resource.getResource().exists()) {\n            return new PropertiesPropertySource(sourceName, new Properties());\n        } else if (sourceName.endsWith(\".yml\") || sourceName.endsWith(\".yaml\")) {\n            //return new YamlPropertySourceLoader().load(sourceName, resource.getResource()).get(0);\n            return new PropertiesPropertySource(sourceName, loadYml(resource.getResource()));\n        } else {\n            return super.createPropertySource(name, resource);\n        }\n    }\n\n    public static Properties loadYml(Resource resource) {\n        YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();\n        factory.setResources(resource);\n        factory.afterPropertiesSet();\n        return factory.getObject();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/BaseNode.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport org.apache.commons.lang3.ObjectUtils;\nimport org.apache.commons.lang3.SerializationUtils;\nimport org.springframework.util.Assert;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * 基于树形结构节点的基类\n *\n * @param <T> the node id type\n * @param <A> the attachment biz object type\n * @author Ponfee\n */\npublic abstract class BaseNode<T extends Serializable & Comparable<T>, A> implements Serializable, Cloneable {\n    private static final long serialVersionUID = -4116799955526185765L;\n\n    // -------------------------------------------------------------------基础信息\n\n    protected final T             nid; // node id\n    protected final T             pid; // parent node id\n    protected final boolean   enabled; // 状态（业务相关）：false无效；true有效；\n    protected final boolean available; // 是否可用（parent.available & this.enabled）\n    protected final A          attach; // 附加信息（与业务相关）\n\n    // -------------------------------------------------------------------以ROOT为根的整棵树\n\n    protected int               level; // 节点层级（以根节点为1开始，往下逐级加1）\n    protected List<T>            path; // 节点路径list<nid>（父节点在前，末尾元素是节点本身的nid）\n    protected int              degree; // 节点的度数（子节点数量，叶子节点的度为0）\n    protected int       leftLeafCount; // 左叶子节点数量（在其左边的所有叶子节点数量：相邻左兄弟节点的左叶子节点个数+该兄弟节点的子节点个数）\n\n    // -------------------------------------------------------------------以当前节点为根的子树\n\n    protected int           treeDepth; // 树的深度（叶子节点的树深度为1）\n    protected int       treeNodeCount; // 树的节点数量\n    protected int       treeMaxDegree; // 树中最大的度数（树中所有节点数目=所有节点度数之和+1）\n    protected int       treeLeafCount; // 树的叶子节点数量（叶子节点为1）\n    protected int       childrenCount; // 子节点个数\n    protected int      siblingOrdinal; // 兄弟节点按顺序排行（从1开始）\n\n    public BaseNode(T nid, T pid, A attach) {\n        this(nid, pid, true, attach);\n    }\n\n    public BaseNode(T nid, T pid, boolean enabled, A attach) {\n        this(nid, pid, enabled, enabled, attach);\n    }\n\n    public BaseNode(T nid, T pid, boolean enabled, boolean available, A attach) {\n        Assert.isTrue(ObjectUtils.isNotEmpty(nid), \"Node id cannot be empty.\");\n        this.nid = nid;\n        this.pid = pid;\n        this.enabled = enabled;\n        this.available = available;\n        this.attach = attach;\n    }\n\n    /**\n     * Deep copy\n     *\n     * @return a copies of node\n     */\n    @Override\n    public BaseNode<T, A> clone() {\n        return SerializationUtils.clone(this);\n    }\n\n    // -----------------------------------------------final field getter\n\n    public T getNid() {\n        return nid;\n    }\n\n    public T getPid() {\n        return pid;\n    }\n\n    public boolean isEnabled() {\n        return enabled;\n    }\n\n    public boolean isAvailable() {\n        return available;\n    }\n\n    public A getAttach() {\n        return attach;\n    }\n\n    // ----------------------------------------------------others field getter\n\n    public int getLevel() {\n        return level;\n    }\n\n    public List<T> getPath() {\n        return path;\n    }\n\n    public int getDegree() {\n        return degree;\n    }\n\n    public int getLeftLeafCount() {\n        return leftLeafCount;\n    }\n\n    public int getTreeDepth() {\n        return treeDepth;\n    }\n\n    public int getTreeNodeCount() {\n        return treeNodeCount;\n    }\n\n    public int getTreeMaxDegree() {\n        return treeMaxDegree;\n    }\n\n    public int getTreeLeafCount() {\n        return treeLeafCount;\n    }\n\n    public int getChildrenCount() {\n        return childrenCount;\n    }\n\n    public int getSiblingOrdinal() {\n        return siblingOrdinal;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/FlatNode.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport org.apache.commons.collections4.CollectionUtils;\n\nimport java.io.Serializable;\nimport java.util.function.Function;\n\n/**\n * 节点扁平结构\n *\n * @param <T> the node id type\n * @param <A> the attachment biz object type\n * @author Ponfee\n */\npublic final class FlatNode<T extends Serializable & Comparable<T>, A> extends BaseNode<T, A> {\n    private static final long serialVersionUID = 5191371614061952661L;\n\n    private final boolean leaf; // 是否叶子节点\n\n    FlatNode(TreeNode<T, A> n) {\n        super(n.nid, n.pid, n.enabled, n.available, n.attach);\n\n        super.level  = n.level;\n        super.degree = n.degree;\n        super.path   = n.path;\n\n        super.leftLeafCount = n.leftLeafCount;\n\n        super.treeDepth      = n.treeDepth;\n        super.treeNodeCount  = n.treeNodeCount;\n        super.treeMaxDegree  = n.treeMaxDegree;\n        super.treeLeafCount  = n.treeLeafCount;\n        super.childrenCount  = n.childrenCount;\n        super.siblingOrdinal = n.siblingOrdinal;\n\n        this.leaf = CollectionUtils.isEmpty(n.getChildren());\n    }\n\n    public <R> R convert(Function<FlatNode<T, A>, R> convertor) {\n        return convertor.apply(this);\n    }\n\n    // ----------------------------------------------getter/setter\n    public boolean isLeaf() {\n        return leaf;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/MapTreeTrait.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport java.io.Serializable;\nimport java.util.LinkedHashMap;\nimport java.util.List;\n\n/**\n * The map for Tree node\n *\n * @param <T> the node id type\n * @param <A> the attachment biz object type\n * @author Ponfee\n */\npublic class MapTreeTrait<T extends Serializable & Comparable<T>, A>\n    extends LinkedHashMap<String, Object> implements TreeTrait<T, A, MapTreeTrait<T, A>> {\n    private static final long serialVersionUID = -5799393887664198242L;\n\n    public static final String DEFAULT_CHILDREN_KEY = \"children\";\n\n    private final String childrenKey;\n\n    public MapTreeTrait() {\n        this(DEFAULT_CHILDREN_KEY);\n    }\n\n    public MapTreeTrait(String childrenKey) {\n        this.childrenKey = childrenKey;\n    }\n\n    @Override\n    public void setChildren(List<MapTreeTrait<T, A>> children) {\n        super.put(childrenKey, children);\n    }\n\n    @Override\n    public List<MapTreeTrait<T, A>> getChildren() {\n        return (List<MapTreeTrait<T, A>>) super.get(childrenKey);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/NodeId.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport cn.ponfee.commons.model.ToJsonString;\nimport org.apache.commons.lang3.builder.HashCodeBuilder;\n\nimport java.io.Serializable;\nimport java.util.Objects;\n\n/**\n * Base node id\n *\n * @author Ponfee\n * @param <T> the NodeId implementation sub class\n */\npublic abstract class NodeId<T extends NodeId<T>> extends ToJsonString implements Comparable<T>, Serializable, Cloneable {\n\n    private static final long serialVersionUID = -9004940918491918780L;\n\n    protected final T parent;\n\n    public NodeId(T parent) {\n        this.parent = parent;\n    }\n\n    @Override @SuppressWarnings(\"unchecked\")\n    public final boolean equals(Object obj) {\n        if (obj == null || this.getClass() != obj.getClass()) {\n            return false;\n        }\n\n        T o = (T) obj;\n        return Objects.equals(this.parent, o.parent) && this.equals(o);\n    }\n\n    @Override\n    public final int compareTo(T another) {\n        if (this.parent == null) {\n            return another.parent == null ? this.compare(another) : -1;\n        }\n        if (another.parent == null) {\n            return 1;\n        }\n\n        int a = this.parent.compareTo(another.parent);\n        return a != 0 ? a : this.compare(another);\n    }\n\n    @Override\n    public final int hashCode() {\n        return new HashCodeBuilder()\n            .append(this.parent)\n            .append(this.hash())\n            .build();\n    }\n\n    protected abstract boolean equals(T another);\n\n    protected abstract int compare(T another);\n\n    protected abstract int hash();\n\n    @Override\n    public abstract T clone();\n\n    public final T getParent() {\n        return parent;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/NodePath.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport cn.ponfee.commons.collect.ImmutableArrayList;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport com.alibaba.fastjson.annotation.JSONType;\nimport com.alibaba.fastjson.parser.DefaultJSONParser;\nimport com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport java.lang.reflect.Type;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Representing immutable node path array\n *\n * @param <T> the node id type\n * @author Ponfee\n */\n// NodePath is extends ArrayList, so must be use mappingTo in fastjson\n// if not do it then deserialized json as a collection type(java.util.ArrayList)\n// hashCode()/equals() extends ImmutableArrayList\n@JSONType(mappingTo = NodePath.FastjsonDeserializeMarker.class) // fastjson\n@JsonDeserialize(using = NodePath.JacksonDeserializer.class)    // jackson\npublic final class NodePath<T extends Serializable & Comparable<T>>\n    extends ImmutableArrayList<T> implements Comparable<NodePath<T>> {\n\n    private static final long serialVersionUID = 9090552044337950223L;\n\n    public NodePath() {\n        // Note: For help deserialization(jackson)\n    }\n\n    @SafeVarargs\n    public NodePath(T... path) {\n        super(path);\n    }\n\n    public NodePath(T[] parent, T child) {\n        super(ArrayUtils.addAll(Objects.requireNonNull(parent), child));\n    }\n\n    public NodePath(List<T> path) {\n        super(path.toArray());\n    }\n\n    public NodePath(List<T> path, T child) {\n        super(ArrayUtils.addAll(path.toArray(), child));\n    }\n\n    public NodePath(NodePath<T> parent) {\n        super(parent.toArray());\n    }\n\n    public NodePath(NodePath<T> parent, T child) {\n        super(parent.join(child));\n    }\n\n    @Override\n    public int compareTo(NodePath<T> o) {\n        int c;\n        for (Iterator<T> a = this.iterator(), b = o.iterator(); a.hasNext() && b.hasNext(); ) {\n            if ((c = a.next().compareTo(b.next())) != 0) {\n                return c;\n            }\n        }\n        return super.size() - o.size();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return (obj instanceof NodePath) && super.equals(obj);\n    }\n\n    @Override\n    public NodePath<T> clone() {\n        return new NodePath<>(this);\n    }\n\n    // -----------------------------------------------------custom fastjson deserialize\n\n    @JSONType(deserializer = FastjsonDeserializer.class)\n    public static class FastjsonDeserializeMarker { }\n\n    /**\n     * <pre> {@code\n     *   public static class IntegerNodePath {\n     *     // 当定义的NodePath字段其泛型参数为具体类型时，必须用JSONField注解，否则报错\n     *     @JSONField(deserializeUsing = FastjsonDeserializer.class)\n     *     private NodePath<Integer> path; // ** NodePath<Integer> **\n     *   }\n     * }</pre>\n     *\n     * @param <T>\n     */\n    public static class FastjsonDeserializer<T extends Serializable & Comparable<T>> implements ObjectDeserializer {\n        @Override\n        @SuppressWarnings(\"unchecked\")\n        public NodePath<T> deserialze(DefaultJSONParser parser, Type type, Object fieldName) {\n            if (GenericUtils.getRawType(type) != NodePath.class) {\n                throw new UnsupportedOperationException(\"Only supported deserialize NodePath, cannot supported: \" + type);\n            }\n            List<T> list = parser.parseArray(GenericUtils.getActualTypeArgument(type, 0));\n            return list.isEmpty() ? null : new NodePath<>(list);\n        }\n\n        @Override\n        public int getFastMatchToken() {\n            return 0 /*JSONToken.RBRACKET*/;\n        }\n    }\n\n    // -----------------------------------------------------custom jackson deserialize\n\n    public static class JacksonDeserializer<T extends Serializable & Comparable<T>> extends JsonDeserializer<NodePath<T>> {\n        @Override\n        @SuppressWarnings(\"unchecked\")\n        public NodePath<T> deserialize(JsonParser p, DeserializationContext ctx) throws IOException {\n            List<T> list = p.readValueAs(List.class);\n            return CollectionUtils.isEmpty(list) ? null : new NodePath<>(list);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/PlainNode.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport java.io.Serializable;\n\n/**\n * Representing plain node\n *\n * @param <T> the node id type\n * @param <A> the attachment biz object type\n * @author Ponfee\n */\npublic final class PlainNode<T extends Serializable & Comparable<T>, A> extends BaseNode<T, A> {\n    private static final long serialVersionUID = -2189191471047483877L;\n\n    public PlainNode(T nid, T pid, A attach) {\n        super(nid, pid, attach);\n    }\n\n    public PlainNode(T nid, T pid, boolean enabled, A attach) {\n        super(nid, pid, enabled, attach);\n    }\n\n    public PlainNode(T nid, T pid, boolean enabled, boolean available, A attach) {\n        super(nid, pid, enabled, available, attach);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/SiblingNodesComparator.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport cn.ponfee.commons.collect.Comparators;\nimport org.apache.commons.collections4.CollectionUtils;\n\nimport java.io.Serializable;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.function.Function;\n\n/**\n * Sibling nodes comparator\n *\n * @author Ponfee\n */\npublic class SiblingNodesComparator<T extends Serializable & Comparable<T>, A> {\n\n    private final Comparator<TreeNode<T, A>> comparator;\n\n    private SiblingNodesComparator(Comparator<TreeNode<T, A>> comparator) {\n        this.comparator = comparator;\n    }\n\n    public static <T extends Serializable & Comparable<T>, A, U extends Comparable<? super U>> SiblingNodesComparator<T, A> comparing(Function<TreeNode<T, A>, U> first) {\n        // default nullsLast and ASC\n        return comparing(first, false, true);\n    }\n\n    public static <T extends Serializable & Comparable<T>, A, U extends Comparable<? super U>> SiblingNodesComparator<T, A> comparing(Function<TreeNode<T, A>, U> first, boolean nullsFirst, boolean asc) {\n        return new SiblingNodesComparator<>(Comparator.comparing(first, comparator(nullsFirst, asc)));\n    }\n\n    public <U extends Comparable<? super U>> SiblingNodesComparator<T, A> thenComparing(Function<TreeNode<T, A>, U> then) {\n        // default nullsLast and ASC\n        return thenComparing(then, false, true);\n    }\n\n    public <U extends Comparable<? super U>> SiblingNodesComparator<T, A> thenComparing(Function<TreeNode<T, A>, U> then, boolean nullsFirst, boolean asc) {\n        return new SiblingNodesComparator<>(comparator.thenComparing(then, comparator(nullsFirst, asc)));\n    }\n\n    public void sort(TreeNode<T, A> root) {\n        sort(root.getChildren());\n    }\n\n    public void sort(List<TreeNode<T, A>> children) {\n        if (CollectionUtils.isNotEmpty(children)) {\n            children.sort(comparator);\n        }\n    }\n\n    public Comparator get() {\n        return comparator;\n    }\n\n    private static <U extends Comparable<? super U>> Comparator<U> comparator(boolean nullsFirst, boolean asc) {\n        Comparator<U> comparator = Comparators.order(asc);\n        return nullsFirst ? Comparator.nullsFirst(comparator) : Comparator.nullsLast(comparator);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/TreeNode.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.tree.print.MultiwayTreePrinter;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Lists;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.ObjectUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport java.util.*;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * <pre>\n * Tree node structure\n *\n * ┌───────────────────────────┐\n * │              0            │\n * │        ┌─────┴─────┐      │\n * │        1           2      │\n * │    ┌───┴───┐   ┌───┴───┐  │\n * │    3       4   5       6  │\n * │  ┌─┴─┐   ┌─┘              │\n * │  7   8   9                │\n * └───────────────────────────┘\n *\n * 上面这棵二叉树中的遍历方式：\n *   DFS前序遍历：0137849256\n *   DFS中序遍历：7381940526\n *   DFS后序遍历：7839415620\n *   BFS广度优先：0123456789\n *   CFS孩子优先：0123478956         (备注：教科书上没有CFS一说，是我为方便说明描述而取名的)\n * </pre>\n *\n * @param <T> the node id type\n * @param <A> the attachment biz object type\n * @author Ponfee\n */\npublic final class TreeNode<T extends Serializable & Comparable<T>, A> extends BaseNode<T, A> {\n    private static final long serialVersionUID = -9081626363752680404L;\n\n    public static final String DEFAULT_ROOT_ID = \"__ROOT__\";\n\n    // 用于比较兄弟节点次序\n    private final Comparator<? super TreeNode<T, A>> siblingNodesComparator;\n\n    // 子节点列表（空列表则表示为叶子节点）\n    private final LinkedList<TreeNode<T, A>> children = new LinkedList<>();\n\n    // 是否构建path\n    private final boolean buildPath;\n\n    /**\n     * Constructs a tree node\n     *\n     * @param nid                    the node id\n     * @param pid                    the parent node id(withhold this pid field value,when use if the other root node mount this node as child)\n     * @param enabled                the node is enabled\n     * @param available              the current node is available(parent.available & this.enabled)\n     * @param attach                 the attachment for biz object\n     * @param siblingNodesComparator the comparator for sort sibling nodes(has the same parent node)\n     * @param buildPath              the if whether build path\n     * @param doMount                the if whether do mount, if is inner new TreeNode then false else true\n     */\n    TreeNode(T nid, T pid, boolean enabled, boolean available, A attach,\n             Comparator<? super TreeNode<T, A>> siblingNodesComparator,\n             boolean buildPath, boolean doMount) {\n        super(nid, pid, enabled, available, attach);\n\n        this.siblingNodesComparator = Objects.requireNonNull(siblingNodesComparator);\n\n        this.buildPath = buildPath;\n\n        if (doMount) {\n            // as root node if new instance at external(TreeNodeBuilder) or of(TreeNode)\n            mount(null);\n        }\n    }\n\n    public static <T extends Serializable & Comparable<T>, A> TreeNodeBuilder<T, A> builder(T nid) {\n        return new TreeNodeBuilder<>(nid);\n    }\n\n    // ------------------------------------------------------mount children nodes\n\n    public <E extends BaseNode<T, A>> TreeNode<T, A> mount(List<E> nodes) {\n        mount(nodes, false);\n        return this;\n    }\n\n    /**\n     * Mount a tree\n     *\n     * @param list         子节点列表\n     * @param ignoreOrphan {@code true}忽略孤儿节点，{@code false}如果有孤儿节点则会抛异常\n     */\n    public <E extends BaseNode<T, A>> TreeNode<T, A> mount(List<E> list, boolean ignoreOrphan) {\n        if (list == null) {\n            list = Collections.emptyList();\n        }\n\n        // 1、预处理\n        List<BaseNode<T, A>> nodes = prepare(list);\n\n        // 2、检查是否存在重复节点\n        List<T> checkDuplicateList = Collects.newLinkedList(super.nid);\n        nodes.forEach(n -> checkDuplicateList.add(n.nid));\n        List<T> duplicated = Collects.duplicate(checkDuplicateList);\n        if (CollectionUtils.isNotEmpty(duplicated)) {\n            throw new IllegalStateException(\"Duplicated nodes: \" + duplicated);\n        }\n\n        // 3、以此节点为根构建节点树\n        super.level = 1;         // root node level is 1\n        super.path = null;       // reset with null\n        super.leftLeafCount = 0; // root node left leaf count is 1\n        super.siblingOrdinal = 1;\n        mount0(null, nodes, ignoreOrphan, super.nid);\n\n        // 4、检查是否存在孤儿节点\n        if (!ignoreOrphan && CollectionUtils.isNotEmpty(nodes)) {\n            String nids = nodes.stream().map(e -> String.valueOf(e.getNid())).collect(Collectors.joining(\",\"));\n            throw new IllegalStateException(\"Invalid orphan nodes: [\" + nids + \"]\");\n        }\n\n        // 5、统计\n        count();\n\n        return this;\n    }\n\n    // -------------------------------------------------------------DFS\n\n    /**\n     * 深度优先搜索DFS(Depth-First Search)：使用前序遍历\n     * <p>Should be invoking after {@link #mount(List)}\n     *\n     * @return a list nodes for DFS tree node\n     */\n    public List<FlatNode<T, A>> flatDFS() {\n        List<FlatNode<T, A>> collect = Lists.newLinkedList();\n        Deque<TreeNode<T, A>> stack = Collects.newLinkedList(this);\n        while (!stack.isEmpty()) {\n            TreeNode<T, A> node = stack.pop();\n            collect.add(new FlatNode<>(node));\n            node.ifChildrenPresent(cs -> {\n                // 反向遍历子节点\n                for (Iterator<TreeNode<T, A>> iter = cs.descendingIterator(); iter.hasNext(); ) {\n                    stack.push(iter.next());\n                }\n            });\n        }\n        return collect;\n    }\n\n    /*// 递归方式DFS\n    public List<FlatNode<T, A>> flatDFS() {\n        List<FlatNode<T, A>> collect = Lists.newLinkedList();\n        dfs(collect);\n        return collect;\n    }\n    private void dfs(List<FlatNode<T, A>> collect) {\n        collect.add(new FlatNode<>(this));\n        forEachChild(child -> child.dfs(collect));\n    }\n    */\n\n    // -------------------------------------------------------------CFS\n\n    /**\n     * 按层级方式展开节点：兄弟节点相邻\n     * <p>子节点优先搜索CFS(Children-First Search)\n     * <p>Should be invoking after {@link #mount(List)}\n     *\n     * Note：为了构建复杂表头，保证左侧的叶子节点必须排在右侧叶子节点前面，此处不能用广度优先搜索策略\n     *\n     * @return a list nodes for CFS tree node\n     */\n    public List<FlatNode<T, A>> flatCFS() {\n        List<FlatNode<T, A>> collect = Collects.newLinkedList(new FlatNode<>(this));\n        Deque<TreeNode<T, A>> stack = Collects.newLinkedList(this);\n        while (!stack.isEmpty()) {\n            TreeNode<T, A> node = stack.pop();\n            node.ifChildrenPresent(cs -> {\n                cs.forEach(child -> collect.add(new FlatNode<>(child)));\n\n                // 反向遍历子节点\n                for (Iterator<TreeNode<T, A>> iter = cs.descendingIterator(); iter.hasNext(); ) {\n                    stack.push(iter.next());\n                }\n            });\n        }\n        return collect;\n    }\n\n    /*// 递归方式CFS\n    public List<FlatNode<T, A>> flatCFS() {\n        List<FlatNode<T, A>> collect = Collects.newLinkedList(new FlatNode<>(this));\n        cfs(collect);\n        return collect;\n    }\n    private void cfs(List<FlatNode<T, A>> collect) {\n        forEachChild(child -> collect.add(new FlatNode<>(child)));\n        forEachChild(child -> child.cfs(collect));\n    }\n    */\n\n    // -------------------------------------------------------------BFS\n\n    /**\n     * 广度优先遍历BFS(Breath-First Search)\n     *\n     * @return a list nodes for BFS tree node\n     */\n    public List<FlatNode<T, A>> flatBFS() {\n        List<FlatNode<T, A>> collect = new LinkedList<>();\n        Queue<TreeNode<T, A>> queue = Collects.newLinkedList(this);\n        while (!queue.isEmpty()) {\n            for (int i = queue.size(); i > 0; i--) {\n                TreeNode<T, A> node = queue.poll();\n                collect.add(new FlatNode<>(node));\n                node.forEachChild(queue::offer);\n            }\n        }\n        return collect;\n    }\n\n    /*// 递归方式BFS\n    public List<FlatNode<T, A>> flatBFS() {\n        List<FlatNode<T, A>> collect = new LinkedList<>();\n        Queue<TreeNode<T, A>> queue = Collects.newLinkedList(this);\n        bfs(queue, collect);\n        return collect;\n    }\n    private void bfs(Queue<TreeNode<T, A>> queue, List<FlatNode<T, A>> collect) {\n        int size = queue.size();\n        if (size == 0) {\n            return;\n        }\n        while (size-- > 0) {\n            TreeNode<T, A> node = queue.poll();\n            collect.add(new FlatNode<>(node));\n            node.forEachChild(queue::offer);\n        }\n        bfs(queue, collect);\n    }\n    */\n\n    // -----------------------------------------------------------children\n\n    public void ifChildrenPresent(Consumer<LinkedList<TreeNode<T, A>>> childrenProcessor) {\n        if (!children.isEmpty()) {\n            childrenProcessor.accept(children);\n        }\n    }\n\n    public void forEachChild(Consumer<TreeNode<T, A>> childProcessor) {\n        if (!children.isEmpty()) {\n            children.forEach(childProcessor);\n        }\n    }\n\n    // -----------------------------------------------------------tree traverse\n\n    /**\n     * Traverses the tree\n     *\n     * @param action the action function\n     */\n    public void traverse(Consumer<TreeNode<T, A>> action) {\n        Deque<TreeNode<T, A>> stack = Collects.newLinkedList(this);\n        while (!stack.isEmpty()) {\n            TreeNode<T, A> node = stack.pop();\n            action.accept(node);\n            node.forEachChild(stack::push);\n        }\n    }\n\n    // -----------------------------------------------------------convert to TreeTrait\n\n    public <E extends TreeTrait<T, A, E>> E convert(Function<TreeNode<T, A>, E> convert) {\n        return convert(convert, true);\n    }\n\n    public <E extends TreeTrait<T, A, E>> E convert(Function<TreeNode<T, A>, E> convert,\n                                                    boolean containsUnavailable) {\n        if (!available && !containsUnavailable) {\n            // not contains unavailable node\n            return null;\n        }\n\n        E root = convert.apply(this);\n        convert(convert, root, containsUnavailable);\n        return root;\n    }\n\n    public String print(Function<TreeNode<T, A>, CharSequence> nodeLabel) throws IOException {\n        StringBuilder builder = new StringBuilder();\n        new MultiwayTreePrinter<>(builder, nodeLabel, TreeNode::getChildren).print(this);\n        return builder.toString();\n    }\n\n    @Override\n    public String toString() {\n        try {\n            return print(e -> String.valueOf(e.getNid()));\n        } catch (IOException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    public LinkedList<TreeNode<T, A>> getChildren() {\n        return children;\n    }\n\n    // -----------------------------------------------------------private methods\n\n    private <E extends BaseNode<T, A>> List<BaseNode<T, A>> prepare(List<E> nodes) {\n        List<BaseNode<T, A>> list = Lists.newLinkedList();\n\n        // nodes list\n        for (BaseNode<T, A> node : nodes) {\n            if (node instanceof TreeNode) {\n                // if tree node, then add all the tree nodes that includes the node's children(recursive)\n                ((TreeNode<T, A>) node).traverse(list::add);\n            } else {\n                list.add(node); // node.clone()\n            }\n        }\n\n        // the root node children\n        ifChildrenPresent(cs -> {\n            cs.forEach(child -> child.traverse(list::add));\n            cs.clear(); // clear the root node children before mount\n        });\n        return list;\n    }\n\n    private <E extends BaseNode<T, A>> void mount0(List<T> parentPath, List<E> nodes,\n                                                   boolean ignoreOrphan, T mountPidIfNull) {\n        // current \"this\" is parent: AbstractNode parent = this;\n\n        // 当前节点路径=父节点路径+当前节点\n        // the \"super\" means defined in super class BaseNode's field, is not parent node\n        super.path = buildPath(parentPath, super.nid);\n\n        // find child nodes for the current node\n        for (Iterator<E> iter = nodes.iterator(); iter.hasNext();) {\n            BaseNode<T, A> node = iter.next();\n\n            if (!ignoreOrphan && ObjectUtils.isEmpty(node.pid)) { // effect condition that pid is null\n                // 不忽略孤儿节点且节点的父节点为空，则其父节点视为根节点（将其挂载到根节点下）\n                Fields.put(node, \"pid\", mountPidIfNull); // pid is final modify\n            }\n\n            if (super.nid.equals(node.pid)) {\n                // found a child node\n                TreeNode<T, A> child = new TreeNode<>(\n                    node.nid,\n                    node.pid,\n                    node.enabled,\n                    super.available && node.enabled, // recompute the child node is available\n                    node.attach,\n                    this.siblingNodesComparator,\n                    this.buildPath,\n                    false\n                );\n\n                child.level = super.level + 1;\n                children.add(child); // 挂载子节点\n\n                iter.remove(); // remove the found child node\n            }\n        }\n\n        ifChildrenPresent(cs -> {\n            // recursion to mount child tree\n            cs.forEach(child -> child.mount0(path, nodes, ignoreOrphan, mountPidIfNull));\n\n            // sort the children list(sibling nodes sort)\n            cs.sort(siblingNodesComparator);\n        });\n        super.degree = children.size();\n    }\n\n    private void count() {\n        if (children.isEmpty()) {\n            // 叶子节点\n            super.treeDepth     = 1;\n            super.treeMaxDegree = 0;\n            super.treeLeafCount = 1;\n            super.treeNodeCount = 1;\n            super.childrenCount = 0;\n            return;\n        }\n\n        // 非叶子节点\n        int maxChildTreeDepth        = 0,\n            maxChildTreeMaxDegree    = 0,\n            sumChildrenTreeLeafCount = 0,\n            sumChildrenTreeNodeCount = 0;\n        TreeNode<T, A> child;\n        for (int i = 0; i < children.size(); i++) {\n            child = children.get(i);\n            child.siblingOrdinal = i + 1;\n\n            // 1、统计左叶子节点数量\n            if (i == 0) {\n                // 是最左子节点：左叶子节点个数=父节点的左叶子节点个数\n                child.leftLeafCount = super.leftLeafCount;\n            } else {\n                // 非最左子节点：左叶子节点个数=相邻左兄弟节点的左叶子节点个数+该兄弟节点的子节点个数\n                TreeNode<T, A> prevSibling = children.get(i - 1);\n                child.leftLeafCount = prevSibling.leftLeafCount + prevSibling.treeLeafCount;\n            }\n\n            // 2、递归\n            child.count();\n\n            // 3、统计子叶子节点数量及整棵树节点的数量\n            maxChildTreeDepth         = Math.max(maxChildTreeDepth, child.treeDepth);\n            maxChildTreeMaxDegree     = Math.max(maxChildTreeMaxDegree, child.degree);\n            sumChildrenTreeLeafCount += child.treeLeafCount;\n            sumChildrenTreeNodeCount += child.treeNodeCount;\n        }\n\n        super.treeDepth     = maxChildTreeDepth + 1;                         // 加上自身节点的层级\n        super.treeMaxDegree = Math.max(maxChildTreeMaxDegree, super.degree); // 树中的最大度数\n        super.treeLeafCount = sumChildrenTreeLeafCount;                      // 子节点的叶子节点之和\n        super.treeNodeCount = sumChildrenTreeNodeCount + 1;                  // 要包含节点本身\n        super.childrenCount = children.size();                               // 子节点数量\n    }\n\n    /**\n     * Returns a immutable list for current node path\n     *\n     * @param parentPath the parent node path\n     * @param nid        the current node id\n     * @return a immutable list appended current node id\n     */\n    private List<T> buildPath(List<T> parentPath, T nid) {\n        if (!buildPath) {\n            return null;\n        }\n\n        // already check duplicated, so cannot happen has circular dependencies state\n        /*\n        if (IterableUtils.matchesAny(parentPath, nid::equals)) {\n            // 节点路径中已经包含了此节点，则视为环状\n            throw new IllegalStateException(\"Node circular dependencies: \" + parentPath + \" -> \" + nid);\n        }\n        */\n\n        int size = parentPath == null ? 1 : parentPath.size() + 1;\n        ImmutableList.Builder<T> builder = ImmutableList.builderWithExpectedSize(size);\n        // root node un-contains null parent\n        if (parentPath != null) {\n            builder.addAll(parentPath);\n        }\n        return builder.add(nid).build();\n    }\n\n    private <E extends TreeTrait<T, A, E>> void convert(Function<TreeNode<T, A>, E> convert,\n                                                        E parent, boolean containsUnavailable) {\n        if (children.isEmpty()) {\n            parent.setChildren(null);\n            return;\n        }\n\n        List<E> list = new LinkedList<>();\n        for (TreeNode<T, A> child : children) {\n            if (child.available || containsUnavailable) { // filter unavailable\n                E node = convert.apply(child);\n                child.convert(convert, node, containsUnavailable);\n                list.add(node);\n            }\n        }\n        parent.setChildren(list);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/TreeNodeBuilder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport java.io.Serializable;\nimport java.util.Comparator;\nimport java.util.Objects;\n\n/**\n * Builds tree node as root node\n *\n * @param <T> the node id type\n * @param <A> the attachment biz object type\n * @author Ponfee\n */\npublic final class TreeNodeBuilder<T extends Serializable & Comparable<T>, A> {\n\n    private final T nid;\n\n    private Comparator<? super TreeNode<T, A>> siblingNodesComparator = Comparator.comparing(TreeNode::getNid);\n    private T       pid       = null;\n    private boolean enabled   = true;\n    private boolean available = true;\n    private A       attach    = null;\n    private boolean buildPath = true;\n\n    TreeNodeBuilder(T nid) {\n        this.nid = Objects.requireNonNull(nid);\n    }\n\n    public TreeNodeBuilder<T, A> pid(T pid) {\n        this.pid = pid;\n        return this;\n    }\n\n    public TreeNodeBuilder<T, A> siblingNodesComparator(Comparator<? super TreeNode<T, A>> comparator) {\n        this.siblingNodesComparator = Objects.requireNonNull(comparator, \"Sibling nodes comparator cannot be null.\");\n        return this;\n    }\n\n    public TreeNodeBuilder<T, A> enabled(boolean enabled) {\n        this.enabled = enabled;\n        return this;\n    }\n\n    public TreeNodeBuilder<T, A> available(boolean available) {\n        this.available = available;\n        return this;\n    }\n\n    public TreeNodeBuilder<T, A> attach(A attach) {\n        this.attach = attach;\n        return this;\n    }\n\n    public TreeNodeBuilder<T, A> buildPath(boolean buildPath) {\n        this.buildPath = buildPath;\n        return this;\n    }\n\n    public TreeNode<T, A> build() {\n        return new TreeNode<>(nid, pid, enabled, available, attach, siblingNodesComparator, buildPath, true);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/TreeTrait.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * The trait for Tree node\n *\n * @param <T> the node id type\n * @param <A> the attachment biz object type\n * @param <E> the TreeTrait type\n * @author Ponfee\n */\npublic interface TreeTrait<T extends Serializable & Comparable<T>, A, E extends TreeTrait<T, A, E>> {\n\n    /**\n     * Sets node list as children\n     *\n     * @param children the children node list\n     */\n    void setChildren(List<E> children);\n\n    /**\n     * Gets children node list\n     *\n     * @return children node list\n     */\n    List<E> getChildren();\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/print/BinaryTreePrinter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree.print;\n\nimport cn.ponfee.commons.io.Files;\nimport com.google.common.base.Strings;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Function;\n\n/**\n * Print binary tree\n *\n * @author Ponfee\n */\npublic class BinaryTreePrinter<T> {\n\n    public enum Branch {\n        RECTANGLE, TRIANGLE\n    }\n\n    private final Appendable output;\n    private final Function<T, String> nodeLabel;\n    private final Function<T, T> leftChild;\n    private final Function<T, T> rightChild;\n\n    /**\n     * 分支是方形还是三角形\n     */\n    private final Branch branch;\n\n    /**\n     * 只有一个子节点时，是否区分左右方向\n     */\n    private final boolean directed;\n\n    /**\n     * 单棵树节点间的空隙\n     */\n    private final int nodeSpace;\n\n    /**\n     * 多棵树时，树间的空隙\n     */\n    private final int treeSpace;\n\n    BinaryTreePrinter(Appendable output,\n                      Function<T, String> nodeLabel,\n                      Function<T, T> leftChild,\n                      Function<T, T> rightChild,\n                      Branch branch,\n                      boolean directed,\n                      int nodeSpace,\n                      int treeSpace) {\n        this.output = output;\n        this.nodeLabel = nodeLabel;\n        this.leftChild = leftChild;\n        this.rightChild = rightChild;\n        this.branch = branch;\n        this.directed = directed;\n        this.nodeSpace = nodeSpace;\n        this.treeSpace = Math.max((treeSpace / 2) * 2 + 1, 3);\n    }\n\n    /**\n     * Prints ascii representation of binary tree.\n     * Parameter nodeSpace is minimum number of spaces between adjacent node labels.\n     * Parameter squareBranches, when set to true, results in branches being printed with ASCII box\n     * drawing characters.\n     */\n    public void print(T root) throws IOException {\n        printTreeLines(buildTreeLines(root));\n    }\n\n    /**\n     * Prints ascii representations of multiple trees across page.\n     * Parameter nodeSpace is minimum number of spaces between adjacent node labels in a tree.\n     * Parameter treeSpace is horizontal distance between trees, as well as number of blank lines\n     * between rows of trees.\n     * Parameter lineWidth is maximum width of output\n     * Parameter squareBranches, when set to true, results in branches being printed with ASCII box\n     * drawing characters.\n     *\n     * @param trees     the multiple tree\n     * @param lineWidth 行的宽度：小于该宽度则多棵树水平排列，否则换行后再来打印下一棵树\n     */\n    public void print(List<T> trees, int lineWidth) throws IOException {\n        List<List<TreeLine>> allTreeLines = new ArrayList<>(trees.size());\n        int[] treeWidths = new int[trees.size()];\n        int[] minLeftOffsets = new int[trees.size()];\n        int[] maxRightOffsets = new int[trees.size()];\n        for (int i = 0; i < trees.size(); i++) {\n            List<TreeLine> treeLines = buildTreeLines(trees.get(i));\n            allTreeLines.add(treeLines);\n            minLeftOffsets[i] = minLeftOffset(treeLines);\n            maxRightOffsets[i] = maxRightOffset(treeLines);\n            treeWidths[i] = maxRightOffsets[i] - minLeftOffsets[i] + 1;\n        }\n\n        String halfTreeSpaceStr = spaces(treeSpace/2);\n        int nextTreeIndex = 0;\n        while (nextTreeIndex < trees.size()) {\n            // print a row of trees starting at nextTreeIndex\n            // first figure range of trees we can print for next row\n            int sumOfWidths = treeWidths[nextTreeIndex];\n            int endTreeIndex = nextTreeIndex + 1;\n            while (endTreeIndex < trees.size() && sumOfWidths + treeSpace + treeWidths[endTreeIndex] < lineWidth) {\n                sumOfWidths += (treeSpace + treeWidths[endTreeIndex]);\n                endTreeIndex++;\n            }\n            endTreeIndex--;\n\n            // find max number of lines for tallest tree\n            int maxLines = allTreeLines.stream().mapToInt(List::size).max().orElse(0);\n\n            // print trees line by line\n            for (int i = 0; i < maxLines; i++) {\n                for (int j = nextTreeIndex; j <= endTreeIndex; j++) {\n                    List<TreeLine> treeLines = allTreeLines.get(j);\n                    if (i >= treeLines.size()) {\n                        output.append(spaces(treeWidths[j]));\n                    } else {\n                        int leftSpaces = -(minLeftOffsets[j] - treeLines.get(i).leftOffset);\n                        int rightSpaces = maxRightOffsets[j] - treeLines.get(i).rightOffset;\n                        output.append(spaces(leftSpaces)).append(treeLines.get(i).line).append(spaces(rightSpaces));\n                    }\n                    if (j < endTreeIndex) {\n                        output.append(halfTreeSpaceStr).append('|').append(halfTreeSpaceStr);\n                    }\n                }\n                output.append(Files.UNIX_LINE_SEPARATOR);\n            }\n\n            nextTreeIndex = endTreeIndex + 1;\n        }\n    }\n\n    private void printTreeLines(List<TreeLine> treeLines) throws IOException {\n        if (treeLines.size() <= 0) {\n            return;\n        }\n        int minLeftOffset = minLeftOffset(treeLines);\n        int maxRightOffset = maxRightOffset(treeLines);\n        for (TreeLine treeLine : treeLines) {\n            output.append(spaces(-(minLeftOffset - treeLine.leftOffset)))\n                  .append(treeLine.line)\n                  .append(spaces(maxRightOffset - treeLine.rightOffset))\n                  .append(Files.UNIX_LINE_SEPARATOR);\n        }\n    }\n\n    private List<TreeLine> buildTreeLines(T root) {\n        if (root == null) {\n            return Collections.emptyList();\n        }\n\n        String rootLabel = nodeLabel.apply(root);\n        List<TreeLine> leftTreeLines = buildTreeLines(leftChild.apply(root));\n        List<TreeLine> rightTreeLines = buildTreeLines(rightChild.apply(root));\n\n        int leftCount = leftTreeLines.size();\n        int rightCount = rightTreeLines.size();\n        int minCount = Math.min(leftCount, rightCount);\n        int maxCount = Math.max(leftCount, rightCount);\n\n        // The left and right subtree print representations have jagged edges, and we essentially we have to\n        // figure out how close together we can bring the left and right roots so that the edges just meet on\n        // some line.  Then we add hspace, and round up to next odd number.\n        int maxRootSpacing = 0;\n        for (int i = 0; i < minCount; i++) {\n            maxRootSpacing = Math.max(maxRootSpacing, leftTreeLines.get(i).rightOffset - rightTreeLines.get(i).leftOffset);\n        }\n        int rootSpacing = maxRootSpacing + nodeSpace;\n        if ((rootSpacing & 0x01) == 0) {\n            rootSpacing++;\n        }\n        // rootSpacing is now the number of spaces between the roots of the two subtrees\n\n        List<TreeLine> allTreeLines = new ArrayList<>();\n\n        // strip ANSI escape codes to get length of rendered string. Fixes wrong padding when labels use ANSI escapes for colored nodes.\n        String renderedRootLabel = rootLabel.replaceAll(\"\\\\e\\\\[[\\\\d;]*[^\\\\d;]\", \"\");\n\n        // add the root and the two branches leading to the subtrees\n        allTreeLines.add(new TreeLine(rootLabel, -(renderedRootLabel.length() - 1) / 2, renderedRootLabel.length() / 2));\n\n        // also calculate offset adjustments for left and right subtrees\n        int leftTreeAdjust = 0;\n        int rightTreeAdjust = 0;\n\n        boolean hasLeftTreeLines = !leftTreeLines.isEmpty();\n        boolean hasRightTreeLines = !rightTreeLines.isEmpty();\n        if (hasLeftTreeLines && hasRightTreeLines) {\n            // there's a left and right subtree\n            if (branch == Branch.RECTANGLE) {\n                int adjust = (rootSpacing / 2) + 1;\n                String horizontal = String.join(\"\", Collections.nCopies(rootSpacing / 2, \"─\"));\n                String branch = \"┌\" + horizontal + \"┴\" + horizontal + \"┐\";\n                allTreeLines.add(new TreeLine(branch, -adjust, adjust));\n                rightTreeAdjust = adjust;\n                leftTreeAdjust = -adjust;\n            } else {\n                if (rootSpacing == 1) {\n                    allTreeLines.add(new TreeLine(\"/ \\\\\", -1, 1));\n                    rightTreeAdjust = 2;\n                    leftTreeAdjust = -2;\n                } else {\n                    for (int i = 1; i < rootSpacing; i += 2) {\n                        String branches = \"/\" + spaces(i) + \"\\\\\";\n                        allTreeLines.add(new TreeLine(branches, -((i + 1) / 2), (i + 1) / 2));\n                    }\n                    rightTreeAdjust = (rootSpacing / 2) + 1;\n                    leftTreeAdjust = -((rootSpacing / 2) + 1);\n                }\n            }\n        } else if (hasLeftTreeLines) {\n            // there's a left subtree only\n            if (branch == Branch.RECTANGLE) {\n                if (directed) {\n                    allTreeLines.add(new TreeLine(\"│\", 0, 0));\n                } else {\n                    allTreeLines.add(new TreeLine(\"┌┘\", -1, 0));\n                    leftTreeAdjust = -1;\n                }\n            } else {\n                allTreeLines.add(new TreeLine(\"/\", -1, -1));\n                leftTreeAdjust = -2;\n            }\n        } else if (hasRightTreeLines) {\n            // there's a right subtree only\n            if (branch == Branch.RECTANGLE) {\n                if (directed) {\n                    allTreeLines.add(new TreeLine(\"│\", 0, 0));\n                } else {\n                    allTreeLines.add(new TreeLine(\"└┐\", 0, 1));\n                    rightTreeAdjust = 1;\n                }\n            } else {\n                allTreeLines.add(new TreeLine(\"\\\\\", 1, 1));\n                rightTreeAdjust = 2;\n            }\n        }\n\n        // now add joined lines of subtrees, with appropriate number of separating spaces, and adjusting offsets\n        for (int i = 0; i < maxCount; i++) {\n            TreeLine left, right;\n            if (i >= leftTreeLines.size()) {\n                // nothing remaining on left subtree\n                right = rightTreeLines.get(i);\n                right.leftOffset += rightTreeAdjust;\n                right.rightOffset += rightTreeAdjust;\n                allTreeLines.add(right);\n            } else if (i >= rightTreeLines.size()) {\n                // nothing remaining on right subtree\n                left = leftTreeLines.get(i);\n                left.leftOffset += leftTreeAdjust;\n                left.rightOffset += leftTreeAdjust;\n                allTreeLines.add(left);\n            } else {\n                left = leftTreeLines.get(i);\n                right = rightTreeLines.get(i);\n                int adjustedRootSpacing = (rootSpacing == 1 ? (branch == Branch.RECTANGLE ? 1 : 3) : rootSpacing);\n                TreeLine combined = new TreeLine(\n                    left.line + spaces(adjustedRootSpacing - left.rightOffset + right.leftOffset) + right.line,\n                    left.leftOffset + leftTreeAdjust,\n                    right.rightOffset + rightTreeAdjust\n                );\n                allTreeLines.add(combined);\n            }\n        }\n        return allTreeLines;\n    }\n\n    private static int minLeftOffset(List<TreeLine> treeLines) {\n        return treeLines.stream().mapToInt(e -> e.leftOffset).min().orElse(0);\n    }\n\n    private static int maxRightOffset(List<TreeLine> treeLines) {\n        return treeLines.stream().mapToInt(e -> e.rightOffset).max().orElse(0);\n    }\n\n    private static String spaces(int n) {\n        return Strings.repeat(\" \", n);\n    }\n\n    private static class TreeLine {\n        final String line;\n        int leftOffset;\n        int rightOffset;\n\n        TreeLine(String line, int leftOffset, int rightOffset) {\n            this.line = line;\n            this.leftOffset = leftOffset;\n            this.rightOffset = rightOffset;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/print/BinaryTreePrinterBuilder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree.print;\n\nimport java.util.function.Function;\n\n/**\n * Binary tree printer builder\n *\n * @author Ponfee\n */\npublic class BinaryTreePrinterBuilder<T> {\n\n    private final Appendable output;\n    private final Function<T, String> nodeLabel;\n    private final Function<T, T> leftChild;\n    private final Function<T, T> rightChild;\n\n    private BinaryTreePrinter.Branch branch = BinaryTreePrinter.Branch.RECTANGLE;\n    private boolean directed = true;\n    private int nodeSpace = 2;\n    private int treeSpace = 5;\n\n    public BinaryTreePrinterBuilder(Function<T, String> nodeLabel,\n                                    Function<T, T> leftChild,\n                                    Function<T, T> rightChild) {\n        this(System.out, nodeLabel, leftChild, rightChild);\n    }\n\n    public BinaryTreePrinterBuilder(Appendable output,\n                                    Function<T, String> nodeLabel,\n                                    Function<T, T> leftChild,\n                                    Function<T, T> rightChild) {\n        this.output = output;\n        this.nodeLabel = nodeLabel;\n        this.leftChild = leftChild;\n        this.rightChild = rightChild;\n    }\n\n    public BinaryTreePrinterBuilder<T> branch(BinaryTreePrinter.Branch branch) {\n        this.branch = branch;\n        return this;\n    }\n\n    public BinaryTreePrinterBuilder<T> directed(boolean directed) {\n        this.directed = directed;\n        return this;\n    }\n\n    public BinaryTreePrinterBuilder<T> nodeSpace(int nodeSpace) {\n        this.nodeSpace = nodeSpace;\n        return this;\n    }\n\n    public BinaryTreePrinterBuilder<T> treeSpace(int treeSpace) {\n        this.treeSpace = treeSpace;\n        return this;\n    }\n\n    public BinaryTreePrinter<T> build() {\n        return new BinaryTreePrinter<>(\n            output, nodeLabel, leftChild, rightChild,\n            branch, directed, nodeSpace, treeSpace\n        );\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/tree/print/MultiwayTreePrinter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.tree.print;\n\nimport cn.ponfee.commons.base.tuple.Tuple4;\nimport cn.ponfee.commons.collect.Collects;\nimport com.google.common.collect.Lists;\n\nimport java.io.IOException;\nimport java.util.Deque;\nimport java.util.List;\nimport java.util.function.Function;\n\n/**\n * Print multiway tree\n *\n * @author Ponfee\n */\npublic final class MultiwayTreePrinter<T> {\n\n    private final Appendable output;\n    private final Function<T, CharSequence> nodeLabel;\n    private final Function<T, List<T>> nodeChildren;\n\n    public MultiwayTreePrinter(Appendable output,\n                               Function<T, CharSequence> nodeLabel,\n                               Function<T, List<T>> nodeChildren) {\n        this.output = output;\n        this.nodeLabel = nodeLabel;\n        this.nodeChildren = nodeChildren;\n    }\n\n    /*// DFS递归方式\n    public void print(T root) throws IOException {\n        print(\"\", \"\", \"\", root);\n    }\n\n    private void print(String prefix, String middle, String suffix, T node) throws IOException {\n        output.append(prefix).append(suffix).append(nodeLabel.apply(node)).append('\\n');\n\n        // print children\n        List<T> children = nodeChildren.apply(node);\n        if (children == null || children.isEmpty()) {\n            return;\n        }\n\n        if (middle.length() > 0) {\n            prefix += middle;\n        }\n\n        int index = children.size();\n        for (T child : children) {\n            if (--index > 0) {\n                print(prefix, \"│   \", \"├── \", child);\n            } else {\n                // last child of parent, space: (char) 0xa0\n                print(prefix, \"    \", \"└── \", child);\n            }\n        }\n    }\n    */\n\n    public void print(T root) throws IOException {\n        Deque<Tuple4<String, String, String, T>> stack = Collects.newLinkedList(Tuple4.of(\"\", \"\", \"\", root));\n        while (!stack.isEmpty()) {\n            Tuple4<String, String, String, T> tuple = stack.pop();\n            output.append(tuple.a).append(tuple.c).append(nodeLabel.apply(tuple.d)).append('\\n');\n\n            List<T> children = nodeChildren.apply(tuple.d);\n            if (children != null && !children.isEmpty()) {\n                String a = tuple.b.isEmpty() ? tuple.a : tuple.a + tuple.b;\n                int index = 0;\n                for (T child : Lists.reverse(children)) {\n                    if (index++ == 0) {\n                        // last child of parent, space: (char) 0xa0\n                        stack.push(Tuple4.of(a, \"    \", \"└── \", child));\n                    } else {\n                        stack.push(Tuple4.of(a, \"│   \", \"├── \", child));\n                    }\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Asserts.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport org.apache.commons.lang3.StringUtils;\n\n/**\n * Extended org.springframework.util.Assert\n * \n * @author Ponfee\n */\npublic class Asserts extends org.springframework.util.Assert {\n\n    public static void notEmpty(String text, String msg) {\n        if (StringUtils.isEmpty(text)) {\n            throw new IllegalArgumentException(msg);\n        }\n    }\n\n    public static void notBlank(String text, String msg) {\n        if (StringUtils.isBlank(text)) {\n            throw new IllegalArgumentException(msg);\n        }\n    }\n\n    public static void minLen(String text, int min, String msg) {\n        if (text != null && text.length() < min) {\n            throw new IllegalArgumentException(msg);\n        }\n    }\n\n    public static void maxLen(String text, int max, String msg) {\n        if (text != null && text.length() > max) {\n            throw new IllegalArgumentException(msg);\n        }\n    }\n\n    public static void range(int value, int min, int max, String msg) {\n        if (value < min || value > max) {\n            throw new IllegalArgumentException(msg);\n        }\n    }\n\n    public static void rangeLen(String text, int min, int max, String msg) {\n        if (text == null) {\n            return;\n        }\n        range(text.length(), min, max, msg);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Base58.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.math.BigInteger;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\n\n/**\n * <pre>\n * https://www.jianshu.com/p/ffc97c4d2306\n * 在计算机系统中数值使用补码来表示和存储\n *  原码（True form）               ：正数的原码为其二进制；负数的原码为对应的正数值在高位补1；\n *  反码（1's complement）：正数的反码与原码相同；负数的反码为其原码除符号位以外各位取反；\n *  补码（2's complement）：正数的补码与原码相同；负数的补码为其反码（最低位）加1；\n * \n * 补码系统的最大优点是可以在加法或减法处理中，不需因为数字的正负而使用不同的计算方式\n * 计算机中只有加法，用两数补码相加，结果仍是补码表示\n * 补码转原码：减1再取反\n * \n * byte b = a byte number;\n * int i = b & 0xff; // 使得i与b的二进制补码一致\n * \n * 0xff is the bit length mask, calc bit length mask can use: \n *      (1&lt;&lt;bits)-1 or -1L^(-1L&lt;&lt;bits)\n * \n * Base58 code：except number 0, uppercase letter I and O, lowercase latter l\n * Reference from internet\n * </pre>\n * \n * @author Ponfee\n */\npublic class Base58 {\n\n    private static final char[] ALPHABET =\n        \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\".toCharArray();\n\n    private static final int LENGTH = ALPHABET.length;\n\n    private static final int[] INDEXES = new int[128];\n    static {\n        Arrays.fill(INDEXES, -1);\n        for (int i = 0; i < LENGTH; i++) {\n            INDEXES[ALPHABET[i]] = i;\n        }\n    }\n\n    /**\n     * Encodes the given bytes as a base58 string (no checksum is appended).\n     * @param data  the bytes to encode\n     * @return the base58-encoded string\n     */\n    public static String encode(byte[] data) {\n        if (data.length == 0) {\n            return \"\";\n        }\n\n        // Duplicate data \n        data = Arrays.copyOfRange(data, 0, data.length);\n\n        // Count leading zeroes.\n        int zeroCount = 0;\n        while (zeroCount < data.length && data[zeroCount] == 0) {\n            ++zeroCount;\n        }\n\n        // The actual encoding.\n        byte[] temp = new byte[data.length << 1];\n        int j = temp.length;\n        for (int startAt = zeroCount; startAt < data.length;) {\n            int mod = divmod58(data, startAt);\n            if (data[startAt] == 0) {\n                ++startAt;\n            }\n            temp[--j] = (byte) ALPHABET[mod];\n        }\n\n        // Strip extra '1' if there are some after decoding.\n        while (j < temp.length && temp[j] == ALPHABET[0]) {\n            ++j;\n        }\n\n        // Add as many leading '1' as there were leading zeros.\n        while (--zeroCount >= 0) {\n            temp[--j] = (byte) ALPHABET[0];\n        }\n\n        return new String(temp, j, temp.length - j, StandardCharsets.US_ASCII);\n    }\n\n    /**\n     * Decodes the given base58 string into the original data bytes.\n     * @param data  the base58-encoded string to decode\n     * @return  the decoded data bytes\n     */\n    public static byte[] decode(String data) {\n        if (data.length() == 0) {\n            return new byte[0];\n        }\n\n        byte[] input58 = new byte[data.length()];\n\n        // Transform the String to a base58 byte sequence  \n        for (int i = 0; i < data.length(); ++i) {\n            char c = data.charAt(i);\n            int digit58 = -1;\n            if (c >= 0 && c < 128) {\n                digit58 = INDEXES[c];\n            }\n            if (digit58 < 0) {\n                throw new IllegalArgumentException(\"Illegal character '\" \n                                                 + c + \"' at [\" + i + \"]\");\n            }\n            input58[i] = (byte) digit58;\n        }\n\n        // Count leading zeroes  \n        int zeroCount = 0;\n        while (zeroCount < input58.length && input58[zeroCount] == 0) {\n            ++zeroCount;\n        }\n\n        // The encoding  \n        byte[] temp = new byte[data.length()];\n        int j = temp.length;\n\n        int startAt = zeroCount;\n        while (startAt < input58.length) {\n            byte mod = divmod256(input58, startAt);\n            if (input58[startAt] == 0) {\n                ++startAt;\n            }\n\n            temp[--j] = mod;\n        }\n\n        // Do no add extra leading zeroes, move j to first non null byte.\n        while (j < temp.length && temp[j] == 0) {\n            ++j;\n        }\n\n        return Arrays.copyOfRange(temp, j - zeroCount, temp.length);\n    }\n\n    /**\n     * base58 decode and construct BigInteger\n     * @param data the base58-encoded string to decode\n     * @return the BigInteger of base58-decoded\n     */\n    public static BigInteger decodeToBigInteger(String data) {\n        return Bytes.toBigInteger(decode(data));\n    }\n\n    /**\n     * Encodes the given bytes as a base58 string (with appended checksum).\n     * @param data  the bytes to encode\n     * @return the base58-encoded string is appended checksum\n     */\n    public static String encodeChecked(byte[] data) {\n        return encode(ArrayUtils.addAll(data, checksum(data)));\n    }\n\n    /**\n     * Decodes the given base58 string into the original data bytes, using the checksum in the\n     * last 4 bytes of the decoded data to verify that the rest are correct. The checksum is\n     * removed from the returned data.\n     * \n     * if the data is not base 58 or the checksum is invalid then throw IllegalArgumentException\n     *\n     * @param data the base58-encoded string to decode (which should include the checksum)\n     */\n    public static byte[] decodeChecked(String data) {\n        byte[] decoded = decode(data);\n        if (decoded.length < 4) {\n            throw new IllegalArgumentException(\"Data too short.\");\n        }\n\n        int bounds = decoded.length - 4;\n        byte[] origin = Arrays.copyOfRange(decoded, 0, bounds);\n        byte[] checksum = Arrays.copyOfRange(decoded, bounds, decoded.length);\n\n        if (!Arrays.equals(checksum, checksum(origin))) {\n            throw new IllegalArgumentException(\"Invalid checksum.\");\n        }\n        return origin;\n    }\n\n    // --------------------------------------------------------------private methods\n    // number -> number / LENGTH, returns number % LENGTH\n    private static int divmod58(byte[] number, int startAt) {\n        int remainder = 0;\n        for (int i = startAt; i < number.length; i++) {\n            // b & 0xFF再转int是为了保持二进制补码的一致性\n            // byte b = -127; 补码：10000001\n            // int  i = -127; 补码：111111111111111111111111 10000001\n            // Integer.toBinaryString(b & 0xFF) --> 10000001\n            // new BigInteger(1, new byte[] { b }).toString(2); // 10000001\n            int digit256 = number[i] & 0xFF;\n            int temp = (remainder << 8) + digit256;\n            number[i] = (byte) (temp / LENGTH);\n            remainder = temp % LENGTH;\n        }\n\n        return remainder;\n    }\n\n    // number -> number / 256, returns number % 256\n    private static byte divmod256(byte[] number58, int startAt) {\n        int remainder = 0;\n        for (int i = startAt; i < number58.length; i++) {\n            int digit58 = number58[i] & 0xFF;\n            int temp = remainder * LENGTH + digit58;\n            number58[i] = (byte) (temp / 256);\n            remainder = temp & 0xFF; // % 256\n        }\n\n        return (byte) remainder;\n    }\n\n    private static byte[] checksum(byte[] data) {\n        // twice sha256\n        byte[] twiceSha256 = DigestUtils.sha256(DigestUtils.sha256(data));\n        return Arrays.copyOfRange(twiceSha256, 0, 4); // take previous 4 bytes\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Base64UrlSafe.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport java.util.Base64;\n\n/**\n * Base64 Url Safe\n * \n * @author Ponfee\n */\npublic class Base64UrlSafe {\n\n    public static String encode(byte[] data) {\n        return Base64.getUrlEncoder().withoutPadding().encodeToString(data);\n    }\n\n    public static byte[] decode(String b64) {\n        return Base64.getUrlDecoder().decode(b64);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Bytes.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.math.Numbers;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.Formatter;\nimport java.util.Objects;\nimport java.util.zip.CRC32;\n\n/**\n * byte[]\n * <pre>\n * 转hex：new BigInteger(1, bytes).toString(16);\n * Padding4位：(4 - (length & 0x03)) & 0x03\n *\n * 左移<<:      该数对应的二进制码整体左移，左边超出的部分舍弃，右边补0\n * 右移>>:      该数对应的二进制码整体右移，左边部分以原有标志位填充，右边超出的部分舍弃\n * 无符号右移>>>: 该数对应的二进制码整体右移，左边部分以0填充，右边超出的部分舍弃\n * </pre>\n *\n * @author Ponfee\n */\npublic final class Bytes {\n\n    private static final char SPACE_CHAR = ' ';\n    private static final char[] HEX_LOWER_CODES = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};\n    private static final char[] HEX_UPPER_CODES = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};\n\n    /**\n     * Dump byte array, like as these\n     * {@link org.apache.commons.io.HexDump#dump(byte[], long, java.io.OutputStream, int)},\n     * {@link sun.misc.HexDumpEncoder#encode(byte[], java.io.OutputStream);}\n     *\n     * @param data   字节数组\n     * @param chunk  每行块数\n     * @param block  每块大小\n     * @return Dump the byte array as hex string\n     */\n    public static String dumpHex(byte[] data, int chunk, int block) {\n        Formatter fmt = new Formatter(), text;\n\n        String lineNumberFormat = \"%0\" + (Integer.toHexString((data.length + 15) / 16).length() + 1) + \"x: \";\n        for (int i, j = 0, wid = block * chunk; j * wid < data.length; j++) {\n            fmt.format(lineNumberFormat, j * wid); // 输出行号：“00000: ”\n\n            text = new Formatter(); // 右边文本\n            for (i = 0; i < wid && (i + j * wid) < data.length; i++) {\n                byte b = data[i + j * wid];\n                fmt.format(\"%02X \", b); // 输出hex：“B1 ”\n                if ((i + 1) % block == 0 || i + 1 == wid) {\n                    fmt.format(\"%s\", SPACE_CHAR); // block与block间加一个空格\n                }\n                if (b > 0x1F && b < 0x7F) {\n                    text.format(\"%c\", b);\n                } else {\n                    text.format(\"%c\", '.'); // 非ascii码则输出“.”\n                }\n            }\n\n            if (i < wid) { // 最后一行空格补全：i为该行的byte数\n                fmt.format(\"%s\", StringUtils.repeat(SPACE_CHAR, (wid - i) * 3)); // 补全byte位\n                for (int k = i + 1; k <= wid; k += block) {\n                    fmt.format(\"%s\", SPACE_CHAR); // 补全block与block间的空格\n                }\n            }\n\n            fmt.format(\"%s\", SPACE_CHAR); // block与text间加一个空格\n            fmt.format(\"%s\", text); // 输出text：“..@.s.UwH...b{.U”\n            fmt.format(\"%s\", \"\\n\"); // 输出换行\n            text.close();\n        }\n\n        fmt.flush();\n        try {\n            return fmt.toString();\n        } finally {\n            fmt.close();\n        }\n    }\n\n    public static String dumpHex(byte[] data) {\n        return dumpHex(data, 2, 8);\n    }\n\n    /**\n     * convert the byte array to binary string\n     * byte:\n     *    -1: 11111111\n     *     0: 00000000\n     *   127: 01111111\n     *  -128: 10000000\n     *\n     * @param array\n     * @return\n     */\n    public static String toBinary(byte... array) {\n        if (array == null || array.length == 0) {\n            return null;\n        }\n\n        StringBuilder builder = new StringBuilder(array.length << 3);\n        String binary;\n        for (byte b : array) {\n            // byte & 0xFF ：byte转int保留bit位\n            // byte | 0x100：对于正数保留八位，保证未尾8位为原byte的bit位，即1xxxxxxxx\n            //               正数会有前缀0，如果不加，转binary string时前面的0会被舍去\n            // 也可以用 “byte + 0x100”或者“leftPad(binaryString, 8, '0')”\n            binary = Integer.toBinaryString((b & 0xFF) | 0x100);\n            builder.append(binary, 1, binary.length());\n        }\n        return builder.toString();\n    }\n\n    // -----------------------------------------------------------------hexEncode/hexDecode\n\n    public static void encodeHex(char[] charArray, int i, byte b) {\n        charArray[  i] = HEX_LOWER_CODES[(0xF0 & b) >>> 4];\n        charArray[++i] = HEX_LOWER_CODES[ 0x0F & b       ];\n    }\n\n    public static String encodeHex(byte b, boolean lowercase) {\n        char[] codes = lowercase ? HEX_LOWER_CODES : HEX_UPPER_CODES;\n        return new String(new char[] {\n            codes[(0xF0 & b) >>> 4], codes[0x0F & b]\n        });\n    }\n\n    public static String encodeHex(byte[] bytes) {\n        return encodeHex(bytes, true);\n    }\n\n    /**\n     * encode the byte array the hex string\n     * @param bytes\n     * @param lowercase\n     * @return\n     */\n    public static String encodeHex(byte[] bytes, boolean lowercase) {\n        //new BigInteger(1, bytes).toString(16);\n        int len = bytes.length;\n        char[] out = new char[len << 1];\n\n        char[] codes = lowercase ? HEX_LOWER_CODES : HEX_UPPER_CODES;\n\n        // one byte -> two char\n        for (int i = 0, j = 0; i < len; i++) {\n            out[j++] = codes[(0xF0 & bytes[i]) >>> 4];\n            out[j++] = codes[ 0x0F & bytes[i]       ];\n        }\n        return new String(out);\n    }\n\n    /**\n     * Decode hex string to byte array\n     *\n     * @param hex the hex string\n     * @return byte array\n     */\n    public static byte[] decodeHex(String hex) {\n        int len = hex.length();\n        if ((len & 0x01) == 1) {\n            throw new IllegalArgumentException(\"Hex string must be twice length.\");\n        }\n\n        byte[] out = new byte[len >> 1];\n        // two char -> one byte\n        for (int i = 0, j = 0; j < len; i++, j += 2) {\n            char c1 = hex.charAt(j), c2 = hex.charAt(j + 1);\n            out[i] = (byte) (Character.digit(c1, 16) << 4 | Character.digit(c2, 16));\n        }\n        return out;\n    }\n\n    // -----------------------------------------------------------------String\n\n    /**\n     * Converts byte array to String\n     *\n     * @param bytes the byte array\n     * @return a string\n     */\n    public static String toString(byte[] bytes) {\n        return bytes == null ? null : new String(bytes);\n    }\n\n    /**\n     * Converts byte array to String\n     *\n     * @param bytes the byte array\n     * @param charset the Charset\n     * @return a string\n     */\n    public static String toString(byte[] bytes, Charset charset) {\n        return bytes == null ? null : new String(bytes, charset);\n    }\n\n    /**\n     * Converts String to byte array\n     *\n     * @param value the string value\n     * @return a byte array\n     */\n    public static byte[] toBytes(String value) {\n        return value == null ? null : value.getBytes();\n    }\n\n    /**\n     * Converts string to byte array\n     *\n     * @param value the string value\n     * @param charset the charset\n     * @return a byte array\n     */\n    public static byte[] toBytes(String value, Charset charset) {\n        return value == null ? null : value.getBytes(charset);\n    }\n\n    // -----------------------------------------------------------------char array\n\n    /**\n     * Converts byte array to char array\n     *\n     * @param bytes the byte array\n     * @return a char array\n     */\n    public static char[] toCharArray(byte[] bytes) {\n        return toCharArray(bytes, StandardCharsets.US_ASCII);\n    }\n\n    /**\n     * Converts byte array to char array\n     *\n     * @param bytes the byte array\n     * @param charset the charset\n     * @return a char array\n     */\n    public static char[] toCharArray(byte[] bytes, Charset charset) {\n        //return new String(bytes, charset).toCharArray();\n        ByteBuffer buffer = ByteBuffer.allocate(bytes.length);\n        buffer.put(bytes);\n        buffer.flip();\n        return charset.decode(buffer).array();\n    }\n\n    /**\n     * Converts char array to byte array\n     *\n     * @param chars the char array\n     * @return a byte array\n     */\n    public static byte[] toBytes(char[] chars) {\n        return toBytes(chars, StandardCharsets.US_ASCII);\n    }\n\n    /**\n     * Converts char array to byte array\n     *\n     * @param chars the char array\n     * @param charset the charset\n     * @return a byte array\n     */\n    public static byte[] toBytes(char[] chars, Charset charset) {\n        //return new String(chars).getBytes(charset);\n        CharBuffer buffer = CharBuffer.allocate(chars.length);\n        buffer.put(chars);\n        buffer.flip();\n        return charset.encode(buffer).array();\n    }\n\n    // -----------------------------------------------------------------short\n\n    public static byte[] toBytes(short value) {\n        return new byte[] {\n            (byte) (value >>> 8), (byte) value\n        };\n        //return ByteBuffer.allocate(Short.BYTES).putShort(value).array();\n    }\n\n    public static short toShort(byte[] bytes) {\n        return toShort(bytes, 0);\n    }\n\n    public static short toShort(byte[] bytes, int fromIdx) {\n        return (short) (\n              (bytes[  fromIdx]       ) << 8\n            | (bytes[++fromIdx] & 0xFF)\n        );\n        //return ByteBuffer.wrap(bytes, fromIdx, Short.BYTES).getShort();\n        //ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);\n        //buffer.put(bytes, fromIdx, Short.BYTES).flip();\n        //return buffer.getShort();\n    }\n\n    // -----------------------------------------------------------------char\n\n    public static byte[] toBytes(char value) {\n        return new byte[] {\n            (byte) (value >>> 8), (byte) value\n        };\n    }\n\n    public static char toChar(byte[] bytes) {\n        return toChar(bytes, 0);\n    }\n\n    public static char toChar(byte[] bytes, int fromIdx) {\n        return (char) (\n            (bytes[  fromIdx]       ) << 8\n          | (bytes[++fromIdx] & 0xFF)\n      );\n    }\n\n    // -----------------------------------------------------------------int\n\n    public static byte[] toBytes(int value) {\n        return new byte[] {\n            (byte) (value >>> 24), (byte) (value >>> 16),\n            (byte) (value >>>  8), (byte) (value       )\n        };\n    }\n\n    public static int toInt(byte[] bytes) {\n        return toInt(bytes, 0);\n    }\n\n    public static int toInt(byte[] bytes, int fromIdx) {\n        return (bytes[  fromIdx]       ) << 24 // 高8位转int后左移24位，刚好剩下原来的8位，故不用&0xFF\n             | (bytes[++fromIdx] & 0xFF) << 16 // 其它转int：若为负数，则是其补码表示，故要&0xFF\n             | (bytes[++fromIdx] & 0xFF) <<  8\n             | (bytes[++fromIdx] & 0xFF);\n    }\n\n    // -----------------------------------------------------------------long\n\n    /**\n     * convert long value to byte array\n     * @param value the long number\n     * @return byte array\n     */\n    public static byte[] toBytes(long value) {\n        return new byte[] {\n            (byte) (value >>> 56), (byte) (value >>> 48),\n            (byte) (value >>> 40), (byte) (value >>> 32),\n            (byte) (value >>> 24), (byte) (value >>> 16),\n            (byte) (value >>>  8), (byte) (value       )\n        };\n    }\n\n    /**\n     * convert byte array to long number\n     * @param bytes  the byte array\n     * @param fromIdx the byte array offset\n     * @return long number\n     */\n    public static long toLong(byte[] bytes, int fromIdx) {\n        return ((long) bytes[  fromIdx]       ) << 56\n             | ((long) bytes[++fromIdx] & 0xFF) << 48\n             | ((long) bytes[++fromIdx] & 0xFF) << 40\n             | ((long) bytes[++fromIdx] & 0xFF) << 32\n             | ((long) bytes[++fromIdx] & 0xFF) << 24\n             | ((long) bytes[++fromIdx] & 0xFF) << 16\n             | ((long) bytes[++fromIdx] & 0xFF) <<  8\n             | ((long) bytes[++fromIdx] & 0xFF);\n    }\n\n    /**\n     * convert byte array to long number\n     * @param bytes the byte array\n     * @return\n     */\n    public static long toLong(byte[] bytes) {\n        return toLong(bytes, 0);\n    }\n\n    /**\n     * Long value to hex string\n     *\n     * @param value the long value\n     * @return String of long value hex string\n     */\n    public static String toHex(long value) {\n        return toHex(value, true);\n    }\n\n    /**\n     * Long value to hex string\n     *\n     * @param value     the long value\n     * @param lowercase {@code true} if lowercase hex string, else uppercase\n     * @return String of long value hex string\n     */\n    public static String toHex(long value, boolean lowercase) {\n        char[] a = lowercase ? HEX_LOWER_CODES : HEX_UPPER_CODES;\n        int mask = 0x0F;\n        return new String(new char[]{\n            a[       (int) (value >>> 60)], a[mask & (int) (value >>> 56)],\n            a[mask & (int) (value >>> 52)], a[mask & (int) (value >>> 48)],\n            a[mask & (int) (value >>> 44)], a[mask & (int) (value >>> 40)],\n            a[mask & (int) (value >>> 36)], a[mask & (int) (value >>> 32)],\n            a[mask & (int) (value >>> 28)], a[mask & (int) (value >>> 24)],\n            a[mask & (int) (value >>> 20)], a[mask & (int) (value >>> 16)],\n            a[mask & (int) (value >>> 12)], a[mask & (int) (value >>>  8)],\n            a[mask & (int) (value >>>  4)], a[mask & (int) (value       )]\n        });\n    }\n\n    // -----------------------------------------------------------------float\n\n    public static byte[] toBytes(float value) {\n        return toBytes(Float.floatToIntBits(value));\n    }\n\n    public static float toFloat(byte[] bytes) {\n        return toFloat(bytes, 0);\n    }\n\n    public static float toFloat(byte[] bytes, int fromIdx) {\n        return Float.intBitsToFloat(toInt(bytes, fromIdx));\n    }\n\n    // -----------------------------------------------------------------double\n\n    public static byte[] toBytes(double value) {\n        return toBytes(Double.doubleToLongBits(value));\n    }\n\n    public static double toDouble(byte[] bytes) {\n        return toDouble(bytes, 0);\n    }\n\n    public static double toDouble(byte[] bytes, int fromIdx) {\n        return Double.longBitsToDouble(toLong(bytes, fromIdx));\n    }\n\n    // ---------------------------------------------------------BigDecimal\n\n    /**\n     * Convert a BigDecimal value to a byte array\n     *\n     * @param val the BigDecimal value\n     * @return the byte array\n     */\n    public static byte[] toBytes(BigDecimal val) {\n        byte[] valueBytes = val.unscaledValue().toByteArray();\n        byte[] result = new byte[valueBytes.length + Integer.BYTES];\n        put(val.scale(), result, 0);\n        System.arraycopy(valueBytes, 0, result, 4, valueBytes.length);\n        return result;\n    }\n\n    /**\n     * Puts int number to byte array\n     *\n     * @param val    the int value\n     * @param bytes  the byte array\n     * @param offset the byte array start offset\n     */\n    public static void put(int val, byte[] bytes, int offset) {\n        for (int i = 3; i >= 0; i--, offset++) {\n            bytes[offset] = (byte) (val >>> (i << 3));\n        }\n    }\n\n    public static void put(long val, byte[] bytes, int offset) {\n        for (int i = 7; i >= 0; i--, offset++) {\n            bytes[offset] = (byte) (val >>> (i << 3));\n        }\n    }\n\n    /**\n     * Converts a byte array to a BigDecimal\n     *\n     * @param bytes\n     * @return the char value\n     */\n    public static BigDecimal toBigDecimal(byte[] bytes) {\n        return toBigDecimal(bytes, 0, bytes.length);\n    }\n\n    /**\n     * Converts a byte array to a BigDecimal value\n     *\n     * @param bytes\n     * @param offset\n     * @param length\n     * @return the char value\n     */\n    public static BigDecimal toBigDecimal(byte[] bytes, int offset, final int length) {\n        if (bytes == null || length < Integer.BYTES + 1 ||\n            (offset + length > bytes.length)) {\n            return null;\n        }\n\n        int scale = toInt(bytes, offset);\n        byte[] tcBytes = new byte[length - Integer.BYTES];\n        System.arraycopy(bytes, offset + Integer.BYTES, tcBytes, 0, length - Integer.BYTES);\n        return new BigDecimal(new BigInteger(tcBytes), scale);\n    }\n\n    // ---------------------------------------------------------BigInteger\n\n    /**\n     * Converts byte array to positive BigInteger\n     *\n     * @param bytes the byte array\n     * @return a positive BigInteger number\n     */\n    public static BigInteger toBigInteger(byte[] bytes) {\n        if (bytes == null || bytes.length == 0) {\n            return BigInteger.ZERO;\n        }\n        return new BigInteger(1, bytes);\n    }\n\n    // ----------------------------------------------------------others\n\n    /**\n     * merge byte arrays\n     * @param first  first byte array of args\n     * @param rest   others byte array\n     * @return a new byte array of them\n     */\n    public static byte[] concat(byte[] first, byte[]... rest) {\n        Objects.requireNonNull(first, \"the first array arg cannot be null\");\n        if (rest == null || rest.length == 0) {\n            return first;\n        }\n\n        int totalLength = first.length;\n        for (byte[] array : rest) {\n            if (array != null) {\n                totalLength += array.length;\n            }\n        }\n\n        byte[] result = Arrays.copyOf(first, totalLength);\n        int offset = first.length;\n        for (byte[] array : rest) {\n            if (array != null) {\n                System.arraycopy(array, 0, result, offset, array.length);\n                offset += array.length;\n            }\n        }\n        return result;\n\n        /*\n        ByteArrayOutputStream baos = new ByteArrayOutputStream(totalLength);\n        baos.write(first, 0, first.length);\n        for (byte[] array : rest) {\n            if (array != null) {\n                baos.write(array, 0, array.length);\n            }\n        }\n        return baos.toByteArray();\n        */\n    }\n\n    public static void tailCopy(byte[] src, int destLen) {\n        tailCopy(src, new byte[destLen]);\n    }\n\n    public static void tailCopy(byte[] src, byte[] dest) {\n        tailCopy(src, 0, src.length, dest, 0, dest.length);\n    }\n\n    /**\n     * copy src to dest\n     * 从尾部开始拷贝src到dest：\n     *   若src数据不足则在dest前面补0\n     *   若src数据有多则舍去src前面的数据\n     *\n     * @param src\n     * @param srcFrom\n     * @param srcLen\n     * @param dest\n     * @param destFrom\n     * @param destLen\n     */\n    public static void tailCopy(byte[] src, int srcFrom, int srcLen,\n                                byte[] dest, int destFrom, int destLen) {\n        tailCopy(src, srcFrom, srcLen, dest, destFrom, destLen, Numbers.ZERO_BYTE);\n    }\n\n    /**\n     * copy src to dest\n     * 从尾部开始拷贝src到dest：\n     *   若src数据不足则在dest前面补heading\n     *   若src数据有多则舍去src前面的数据\n     *\n     * @param src\n     * @param srcFrom\n     * @param srcLen\n     * @param dest\n     * @param destFrom\n     * @param destLen\n     * @param heading\n     */\n    public static void tailCopy(byte[] src, int srcFrom, int srcLen,\n                                byte[] dest, int destFrom, int destLen,\n                                byte heading) {\n        int srcTo = Math.min(src.length, srcFrom + srcLen),\n           destTo = Math.min(dest.length, destFrom + destLen);\n        for (int i = destTo - 1, j = srcTo - 1; i >= destFrom; i--, j--) {\n            dest[i] = (j < srcFrom) ? heading : src[j];\n        }\n    }\n\n    /**\n     * copy src to dest\n     * 从首部开始拷贝src到dest：\n     *   若src数据不足则在dest后面补tailing\n     *   若src数据有多则舍去src后面的数据\n     *\n     * @param src\n     * @param dest\n     */\n    public static void headCopy(byte[] src, byte[] dest) {\n        headCopy(src, 0, src.length, dest, 0, dest.length, Numbers.ZERO_BYTE);\n    }\n\n    public static void headCopy(byte[] src, int srcFrom, int srcLen,\n                                byte[] dest, int destFrom, int destLen,\n                                byte tailing) {\n        int srcTo = Math.min(src.length, srcFrom + srcLen),\n            destTo = Math.min(dest.length, destFrom + destLen);\n        for (int i = destFrom, j = srcFrom; i < destTo; i++, j++) {\n            dest[i] = (j < srcTo) ? src[j] : tailing;\n        }\n    }\n\n    public static long crc32(byte[] bytes) {\n        CRC32 crc32 = new CRC32();\n        crc32.update(bytes);\n        return crc32.getValue();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/CRC16.java",
    "content": "/**\n * Copyright (c) 2013-2019 Nikita Koksharov\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 */\npackage cn.ponfee.commons.util;\n\n/**\n * @author <a href=\"mailto:mpaluch@paluch.biz\">Mark Paluch</a>\n * \n * @see java.util.zip.CRC32#CRC32()\n */\npublic final class CRC16 {\n\n    private static final int[] LOOKUP_TABLE = { \n        0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6,\n        0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273,\n        0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF,\n        0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528,\n        0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695,\n        0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886,\n        0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A,\n        0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF,\n        0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60,\n        0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5,\n        0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59,\n        0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2,\n        0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F,\n        0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8,\n        0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424,\n        0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691,\n        0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A,\n        0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F,\n        0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3,\n        0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64,\n        0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9,\n        0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 \n    };\n\n    private CRC16() {}\n\n    public static int digest(byte[] bytes) {\n        int crc = 0x0000;\n\n        for (byte b : bytes) {\n            crc = (crc << 8) ^ LOOKUP_TABLE[((crc >>> 8) ^ (b & 0xFF)) & 0xFF];\n        }\n        return crc & 0xFFFF;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Captchas.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport javax.imageio.ImageIO;\nimport java.awt.*;\nimport java.awt.geom.AffineTransform;\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\nimport static java.util.concurrent.ThreadLocalRandom.current;\n\n/**\n * 图片验证码生成类\n * \n * @author Ponfee\n */\npublic class Captchas {\n\n    // 使用到Algerian字体，系统里没有的话需要安装字体\n    // 去掉了1,0,i,o几个容易混淆的字符\n    private static final char[] CODES = \"23456789ABCDEFGHJKLMNPQRSTUVWXYZ\".toCharArray();\n\n    private static final Color[] COLOR_SPACES = {\n        Color.RED, Color.GRAY, Color.YELLOW, Color.WHITE,\n        Color.GREEN, Color.CYAN, Color.PINK, Color.BLUE,\n        Color.MAGENTA, Color.ORANGE, Color.LIGHT_GRAY\n    };\n\n    /**\n     * 使用系统默认字符源生成验证码\n     * @param size 验证码长度\n     * @return\n     */\n    public static String random(int size) {\n        return random(size, CODES);\n    }\n\n    /**\n     * 使用指定源生成验证码\n     * @param size 验证码长度\n     * @param sources 验证码字符源\n     * @return\n     */\n    public static String random(int size, char[] sources) {\n        if (sources == null || sources.length == 0) {\n            sources = CODES;\n        }\n        StringBuilder codes = new StringBuilder(size);\n        for (int i = 0, length = sources.length; i < size; i++) {\n            codes.append(sources[SecureRandoms.nextInt(length)]);\n        }\n        return codes.toString();\n    }\n\n    public static void generate(int width, OutputStream out, String code) {\n        generate(width, (int) Math.ceil(width * 0.618D), out, code);\n    }\n\n    /**\n     * 输出指定验证码图片流\n     * @param width\n     * @param height\n     * @param out\n     * @param code\n     */\n    public static void generate(int width, int height, OutputStream out, String code) {\n        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);\n        Graphics2D g2 = image.createGraphics();\n        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\n        Color[] colors = new Color[5];\n        for (int i = 0; i < colors.length; i++) {\n            colors[i] = COLOR_SPACES[current().nextInt(COLOR_SPACES.length)];\n        }\n\n        g2.setColor(Color.GRAY); // 设置边框色\n        g2.fillRect(0, 0, width, height);\n\n        Color c = getRandColor(200, 250);\n        g2.setColor(c); // 设置背景色\n        g2.fillRect(0, 2, width, height - 4);\n\n        // 绘制干扰线\n        g2.setColor(getRandColor(160, 200)); // 设置线条的颜色\n        for (int i = 0; i < 15; i++) {\n            int x = current().nextInt(width - 1);\n            int y = current().nextInt(height - 1);\n            int xl = current().nextInt(6) + 1;\n            int yl = current().nextInt(12) + 1;\n            g2.drawLine(x, y, x + xl + 40, y + yl + 20);\n        }\n\n        // 添加噪点\n        float yawpRate = 0.03f; // 噪声率\n        int area = (int) (yawpRate * width * height);\n        for (int i = 0; i < area; i++) {\n            int x = current().nextInt(width);\n            int y = current().nextInt(height);\n            int rgb = getRandomIntColor();\n            image.setRGB(x, y, rgb);\n        }\n\n        shear(g2, width, height, c); // 使图片扭曲\n\n        g2.setColor(getRandColor(100, 160));\n        int fontSize = height - 14;\n        g2.setFont(new Font(\"Algerian\", Font.ITALIC, fontSize));\n        char[] chars = code.toCharArray();\n        int size = code.length();\n        for (int i = 0; i < size; i++) {\n            AffineTransform affine = new AffineTransform();\n            int signum = (current().nextBoolean() ? 1 : -1);\n            affine.setToRotation(\n                Math.PI / 4.0D * current().nextDouble() * signum,\n                width / size * i + fontSize / 2, \n                height / 2\n            );\n            g2.setTransform(affine);\n            int x = 1, y = 4;\n            g2.drawChars(\n                chars, i, 1, \n                (width - 7) / size * i + x, \n                height / 2 + fontSize / 2 - y\n            );\n        }\n\n        g2.dispose();\n\n        try {\n            ImageIO.write(image, \"JPEG\", out);\n            //JPEGCodec.createJPEGEncoder(out).encode(image);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    //-------------------------private methods\n    private static Color getRandColor(int fc, int bc) {\n        fc = Math.min(fc, 255);\n        bc = Math.min(bc, 255);\n        int r = fc + current().nextInt(bc - fc);\n        int g = fc + current().nextInt(bc - fc);\n        int b = fc + current().nextInt(bc - fc);\n        return new Color(r, g, b);\n    }\n\n    private static int getRandomIntColor() {\n        int color = 0;\n        for (int c : getRandomRgb()) {\n            color = (color << 8) | c;\n        }\n        return color;\n    }\n\n    private static int[] getRandomRgb() {\n        return new int[] {\n            current().nextInt(256),\n            current().nextInt(256),\n            current().nextInt(256)\n        };\n    }\n\n    private static void shear(Graphics g, int w1, int h1, Color color) {\n        shearX(g, w1, h1, color);\n        shearY(g, w1, h1, color);\n    }\n\n    private static void shearX(Graphics g, int w, int h, Color color) {\n        int period = current().nextInt(2),\n             phase = current().nextInt(2);\n        double frames = 2 * Math.PI * phase;\n\n        for (int d, i = 0; i < h; i++) {\n            d = (int) ((period >> 1) * Math.sin((double) i / period + frames));\n            g.copyArea(0, i, w, 1, d, 0);\n            //if (current().nextBoolean()) {\n            g.setColor(color);\n            g.drawLine(d, i, 0, i);\n            g.drawLine(d + w, i, w, i);\n            //}\n        }\n    }\n\n    private static void shearY(Graphics g, int w, int h, Color color) {\n        int period = current().nextInt(40) + 10, // 50\n            phase = 7;\n        double frames = (2 * Math.PI * phase) / 20.0D;\n\n        for (int d, i = 0; i < w; i++) {\n            d = (int) ((period >> 1) * Math.sin((double) i / period + frames));\n            g.copyArea(i, 0, 1, h, 0, d);\n            //if (current().nextBoolean()) {\n            g.setColor(color);\n            g.drawLine(i, d, i, 0);\n            g.drawLine(i, d + h, i, h);\n            //}\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Colors.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport java.awt.Color;\n\n/**\n * color rgb and hex transform\n * \n * @author Ponfee\n */\npublic final class Colors {\n\n    public static Color fromHex(String hex) {\n        if (hex == null || hex.isEmpty()) {\n            return null;\n        }\n        return new Color(Integer.parseInt(hex.substring(1), 16));\n    }\n\n    public static String toHex(Color c) {\n        return '#' + hex(c.getRed()) + hex(c.getGreen()) + hex(c.getBlue());\n    }\n\n    private static String hex(int i) {\n        // StringUtils.leftPad(Integer.toHexString(i), 2, \"0\")\n        return String.format(\"%02x\", i);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/ConsistentHash.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport org.apache.commons.codec.digest.DigestUtils;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.SortedMap;\nimport java.util.TreeMap;\nimport java.util.function.Function;\n\n/**\n * Consistent hashing algorithm.\n *\n * @param <T> the ring node type\n * @author Ponfee\n */\npublic class ConsistentHash<T> {\n\n    /**\n     * Hash String to long value\n     */\n    @FunctionalInterface\n    public interface HashFunction {\n        /**\n         * Returns key's int hash value\n         *\n         * @param key string key\n         * @return int hash value\n         */\n        int hash(String key);\n\n        HashFunction MD5 = key -> {\n            byte[] digest = DigestUtils.md5(key);\n            assert digest.length == 16;\n\n            int hash = 0;\n            for (int i = 15; i >= 3; ) {\n                int h = ((digest[i--] & 0xFF) << 24)\n                      | ((digest[i--] & 0xFF) << 16)\n                      | ((digest[i--] & 0xFF) <<  8)\n                      | ((digest[i--] & 0xFF)      );\n                hash = (i == 11) ? h : (hash ^ h);\n            }\n            return hash;\n        };\n\n        HashFunction FNV = key -> {\n            int p = 16777619;\n            int h = (int) 2166136261L;\n            for (int i = 0; i < key.length(); i++) {\n                h = (h ^ key.charAt(i)) * p;\n            }\n            h += h << 13;\n            h ^= h >> 7;\n            h += h << 3;\n            h ^= h >> 17;\n            h += h << 5;\n            return h;\n        };\n\n        HashFunction CRC16 = key -> cn.ponfee.commons.util.CRC16.digest(key.getBytes(StandardCharsets.UTF_8));\n    }\n\n    /**\n     * Virtual node of the consistent hash ring.\n     */\n    private class VirtualNode {\n        private final T physicalNode;\n        private final String physicalKey;\n        private final String virtualKey;\n\n        private VirtualNode(T physicalNode, int replicaIndex) {\n            this.physicalNode = physicalNode;\n            this.physicalKey = keyMapper.apply(physicalNode);\n            this.virtualKey = \"SHARD-\" + physicalKey + \"-NODE-\" + replicaIndex;\n        }\n\n        private boolean isVirtualNodeOf(T pNode) {\n            return physicalKey.equals(keyMapper.apply(pNode));\n        }\n    }\n\n    private final TreeMap<Integer, VirtualNode> ring = new TreeMap<>();\n    private final Function<T, String> keyMapper;\n    private final HashFunction hashFunction;\n\n    public ConsistentHash(Collection<T> pNodes, int vNodeCount) {\n        this(pNodes, vNodeCount, String::valueOf, HashFunction.MD5);\n    }\n\n    public ConsistentHash(Collection<T> pNodes,\n                          int vNodeCount,\n                          Function<T, String> keyMapper) {\n        this(pNodes, vNodeCount, keyMapper, HashFunction.MD5);\n    }\n\n    /**\n     * @param pNodes       collections of physical nodes\n     * @param vNodeCount   number of virtual nodes\n     * @param keyMapper    physical node mapping to string key function\n     * @param hashFunction hash function to hash node instances\n     */\n    public ConsistentHash(Collection<T> pNodes,\n                          int vNodeCount,\n                          Function<T, String> keyMapper,\n                          HashFunction hashFunction) {\n        if (keyMapper == null) {\n            throw new NullPointerException(\"Key mapper is null.\");\n        }\n        if (hashFunction == null) {\n            throw new NullPointerException(\"Hash function is null.\");\n        }\n        this.keyMapper = keyMapper;\n        this.hashFunction = hashFunction;\n        if (pNodes != null) {\n            for (T pNode : pNodes) {\n                addNode(pNode, vNodeCount);\n            }\n        }\n    }\n\n    /**\n     * Add physic node to the hash ring with some virtual nodes\n     *\n     * @param pNode      physical node\n     * @param vNodeCount the number virtual node of the physical node.\n     */\n    public void addNode(T pNode, int vNodeCount) {\n        if (vNodeCount < 0) {\n            throw new IllegalArgumentException(\"Invalid virtual node counts :\" + vNodeCount);\n        }\n        int existingReplicas = getExistingReplicas(pNode);\n        for (int i = 0; i < vNodeCount; i++) {\n            VirtualNode vNode = new VirtualNode(pNode, i + existingReplicas);\n            ring.put(hashFunction.hash(vNode.virtualKey), vNode);\n        }\n    }\n\n    /**\n     * Remove the physical node from the hash ring\n     *\n     * @param pNode the physical node\n     */\n    public void removeNode(T pNode) {\n        Iterator<Integer> it = ring.keySet().iterator();\n        while (it.hasNext()) {\n            Integer key = it.next();\n            VirtualNode virtualNode = ring.get(key);\n            if (virtualNode.isVirtualNodeOf(pNode)) {\n                it.remove();\n            }\n        }\n    }\n\n    /**\n     * Returns the physical node of counted specified key\n     *\n     * @param key the key to find the nearest physical node\n     * @return routed physical node\n     */\n    public T routeNode(String key) {\n        if (ring.isEmpty()) {\n            return null;\n        }\n\n        SortedMap<Integer, VirtualNode> tailMap = ring.tailMap(hashFunction.hash(key));\n        VirtualNode virtualNode = tailMap.isEmpty() \n                                ? ring.firstEntry().getValue() \n                                : ring.get(tailMap.firstKey());\n        return virtualNode.physicalNode;\n    }\n\n    public int getExistingReplicas(T pNode) {\n        return (int) ring.entrySet()\n                         .stream()\n                         .filter(e -> e.getValue().isVirtualNodeOf(pNode))\n                         .count();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/CurrencyEnum.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport java.util.Currency;\nimport java.util.Map;\n\n/**\n * <b>Currency enum definition.</b>\n *\n * <ul>\n *   <li><a href=\"http://en.wikipedia.org/wiki/ISO_4217\">币种代码(维基百科，需要翻墙)</a></li>\n *   <li><a href=\"https://www.xe.com/symbols/\">币种符号</a></li>\n *   <li><a href=\"https://baike.baidu.com/item/%E4%B8%96%E7%95%8C%E5%90%84%E5%9B%BD%E5%92%8C%E5%9C%B0%E5%8C%BA%E5%90%8D%E7%A7%B0%E4%BB%A3%E7%A0%81/6560023\">世界各国和地区名称代码(GB/T 2659-2000，百度百科)</a></li>\n * </ul>\n *\n * @author Ponfee\n * @see java.util.Currency\n */\npublic enum CurrencyEnum {\n\n    /** 人民币 [¥], previous: {0xffe5}, Same symbol as JPY Japan Yen. */\n    CNY(new char[]{0xa5}),\n\n    /** 美元 [US$] */\n    USD(new char[]{0x55, 0x53, 0x24}),\n\n    /** 港元 [HK$] */\n    HKD(new char[]{0x48, 0x4b, 0x24}),\n\n    /** 台币 [NT$] */\n    TWD(new char[]{0x4e, 0x54, 0x24}),\n\n    /** 欧元 [€] */\n    EUR(new char[]{0x20ac}),\n\n    /** 英镑 [£], previous: {0xffe1} */\n    GBP(new char[]{0xa3}),\n\n    /** 日元 [¥], Same symbol as CNY China Yuan Renminbi. */\n    JPY(new char[]{0xa5}),\n\n    /** 巴西雷亚尔 [R$] */\n    BRL(new char[]{0x52, 0x24}),\n\n    /** 俄罗斯卢布 [₽] */\n    RUB(new char[]{0x20bd}),\n\n    /** 澳元 [AU$] */\n    AUD(new char[]{0x41, 0x55, 0x24}),\n\n    /** 加元 [CA$] */\n    CAD(new char[]{0x43, 0x41, 0x24}),\n\n    /** 印度卢比 [₹], previous: {0x52, 0x73, 0x2e} */\n    INR(new char[]{0x20b9}),\n\n    /** 乌克兰里夫纳 [₴], previous: {0x433, 0x440, 0x43d, 0x2e} */\n    UAH(new char[]{0x20b4}),\n\n    /** 墨西哥比索 [MX$] */\n    MXN(new char[]{0x4d, 0x58, 0x24}),\n\n    /** 瑞士法郎 [CHF] */\n    CHF(new char[]{0x43, 0x48, 0x46}),\n\n    /** 新加坡元 [SG$] */\n    SGD(new char[]{0x53, 0x47, 0x24}),\n\n    /** 波兰兹罗提 [zł] */\n    PLN(new char[]{0x7a, 0x142}),\n\n    /** 瑞典克朗 [kr] */\n    SEK(new char[]{0x6b, 0x72}),\n\n    /** 智利比索 [CL$] */\n    CLP(new char[]{0x43, 0x4c, 0x24}),\n\n    /** 韩元 [₩] */\n    KRW(new char[]{0x20a9}),\n\n    /** 肯尼亚先令 [KSh] */\n    KES(new char[]{0x4b, 0x53, 0x68}),\n\n    /** 澳门元 [MOP] */\n    MOP(new char[]{0x4d, 0x4f, 0x50}),\n\n    /** 印度尼西亚卢比 [Rp] */\n    IDR(new char[]{0x52, 0x70}),\n\n    /** 沙特里亚尔 [﷼] */\n    SAR(new char[]{0xfdfc}),\n\n    /** 保加利亚列弗 [лв] */\n    BGN(new char[]{0x43b, 0x432}),\n\n    /** 罗马尼亚新列伊 [lei] */\n    RON(new char[]{0x6c, 0x65, 0x69}),\n\n    /** 捷克克朗 [Kč] */\n    CZK(new char[]{0x4b, 0x10d}),\n\n    /** 匈牙利福林 [Ft] */\n    HUF(new char[]{0x46, 0x74}),\n\n    /** 越南盾 [₫] */\n    VND(new char[]{0x20ab}),\n\n    /** 马来西亚林吉特 [RM] */\n    MYR(new char[]{0x52, 0x4d}),\n\n    /** 菲律宾比索 [₱] */\n    PHP(new char[]{0x20b1}),\n\n    /** 泰铢 [฿] */\n    THB(new char[]{0xe3f}),\n\n    /** 巴基斯坦卢比 [₨] */\n    PKR(new char[]{0x20a8}),\n\n    /** 挪威克朗 [kr] */\n    NOK(new char[]{0x6b, 0x72}),\n\n    /** 丹麦克朗 [kr] */\n    DKK(new char[]{0x6b, 0x72}),\n\n    /** 阿联酋迪拉姆 [AED] */\n    AED(new char[]{0x41, 0x45, 0x44}),\n\n    /** 阿富汗尼 [؋] */\n    AFN(new char[]{0x60b}),\n\n    /** 阿尔巴尼列克 [Lek] */\n    ALL(new char[]{0x4c, 0x65, 0x6b}),\n\n    /** 亚美尼亚德拉姆 [AMD] */\n    AMD(new char[]{0x41, 0x4d, 0x44}),\n\n    /** 荷兰盾 [ƒ] */\n    ANG(new char[]{0x192}),\n\n    /** 安哥拉宽扎 [AOA] */\n    AOA(new char[]{0x41, 0x4f, 0x41}),\n\n    /** 阿根廷比索 [$] */\n    ARS(new char[]{0x24}),\n\n    /** 阿鲁巴或荷兰盾 [ƒ] */\n    AWG(new char[]{0x192}),\n\n    /** 阿塞拜疆新马纳特 [₼], previous: {0x43c, 0x430, 0x43d} */\n    AZN(new char[]{0x20bc}),\n\n    /** 波斯尼亚可兑换马尔卡 [KM] */\n    BAM(new char[]{0x4b, 0x4d}),\n\n    /** 巴巴多斯元 [$] */\n    BBD(new char[]{0x24}),\n\n    /** 孟加拉国塔卡 [BDT] */\n    BDT(new char[]{0x42, 0x44, 0x54}),\n\n    /** 巴林第纳尔 [BHD] */\n    BHD(new char[]{0x42, 0x48, 0x44}),\n\n    /** 布隆迪法郎 [BIF] */\n    BIF(new char[]{0x42, 0x49, 0x46}),\n\n    /** 百慕大元 [$] */\n    BMD(new char[]{0x24}),\n\n    /** 文莱元 [$] */\n    BND(new char[]{0x24}),\n\n    /** 玻利维亚诺 [$b] */\n    BOB(new char[]{0x24, 0x62}),\n\n    /** 巴哈马元 [$] */\n    BSD(new char[]{0x24}),\n\n    /** 不丹努尔特鲁姆 [BTN] */\n    BTN(new char[]{0x42, 0x54, 0x4e}),\n\n    /** 博茨瓦纳普拉 [P] */\n    BWP(new char[]{0x50}),\n\n    /** 白俄罗斯卢布 [p.] */\n    BYR(new char[]{0x70, 0x2e}),\n\n    /** 伯利兹元 [BZ$] */\n    BZD(new char[]{0x42, 0x5a, 0x24}),\n\n    /** 刚果法郎 [CDF] */\n    CDF(new char[]{0x43, 0x44, 0x46}),\n\n    /** 哥伦比亚比索 [$] */\n    COP(new char[]{0x24}),\n\n    /** 哥斯达黎加科朗 [₡] */\n    CRC(new char[]{0x20a1}),\n\n    /** 古巴可兑换比索 [CUC] */\n    CUC(new char[]{0x43, 0x55, 0x43}),\n\n    /** 古巴比索 [₱] */\n    CUP(new char[]{0x20b1}),\n\n    /** 佛得角埃斯库多 [CVE] */\n    CVE(new char[]{0x43, 0x56, 0x45}),\n\n    /** 吉布提法郎 [DJF] */\n    DJF(new char[]{0x44, 0x4a, 0x46}),\n\n    /** 多米尼加比索 [RD$] */\n    DOP(new char[]{0x52, 0x44, 0x24}),\n\n    /** 阿尔及利亚第纳尔 [DZD] */\n    DZD(new char[]{0x44, 0x5a, 0x44}),\n\n    /** 埃及镑 [£] */\n    EGP(new char[]{0xa3}),\n\n    /** 厄立特里亚纳克法 [ERN] */\n    ERN(new char[]{0x45, 0x52, 0x4e}),\n\n    /** 埃塞俄比亚比尔 [ETB] */\n    ETB(new char[]{0x45, 0x54, 0x42}),\n\n    /** 斐济元 [$] */\n    FJD(new char[]{0x24}),\n\n    /** 福克兰群岛镑 [£] */\n    FKP(new char[]{0xa3}),\n\n    /** 格鲁吉亚拉里 [GEL] */\n    GEL(new char[]{0x47, 0x45, 0x4c}),\n\n    /** 加纳塞地 [¢], previous: {0x47, 0x48, 0x53} */\n    GHS(new char[]{0xa2}),\n\n    /** 直布罗陀镑 [£] */\n    GIP(new char[]{0xa3}),\n\n    /** 冈比亚达拉西 [GMD] */\n    GMD(new char[]{0x47, 0x4d, 0x44}),\n\n    /** 几内亚法郎 [GNF] */\n    GNF(new char[]{0x47, 0x4e, 0x46}),\n\n    /** 危地马拉格查尔 [Q] */\n    GTQ(new char[]{0x51}),\n\n    /** 圭亚那元 [$] */\n    GYD(new char[]{0x24}),\n\n    /** 洪都拉斯伦皮拉 [L] */\n    HNL(new char[]{0x4c}),\n\n    /** 克罗地亚库纳 [kn] */\n    HRK(new char[]{0x6b, 0x6e}),\n\n    /** 海地古德 [HTG] */\n    HTG(new char[]{0x48, 0x54, 0x47}),\n\n    /** 以色列谢克尔 [₪] */\n    ILS(new char[]{0x20aa}),\n\n    /** 伊拉克第纳尔 [IQD] */\n    IQD(new char[]{0x49, 0x51, 0x44}),\n\n    /** 伊朗里亚尔 [﷼] */\n    IRR(new char[]{0xfdfc}),\n\n    /** 冰岛克朗 [kr] */\n    ISK(new char[]{0x6b, 0x72}),\n\n    /** 牙买加元 [J$] */\n    JMD(new char[]{0x4a, 0x24}),\n\n    /** 约旦第纳尔 [JOD] */\n    JOD(new char[]{0x4a, 0x4f, 0x44}),\n\n    /** 吉尔吉斯斯坦索姆 [лв] */\n    KGS(new char[]{0x43b, 0x432}),\n\n    /** 柬埔寨瑞尔 [៛] */\n    KHR(new char[]{0x17db}),\n\n    /** 科摩罗法郎 [KMF] */\n    KMF(new char[]{0x4b, 0x4d, 0x46}),\n\n    /** 朝鲜元 [₩] */\n    KPW(new char[]{0x20a9}),\n\n    /** 科威特第纳尔 [KWD] */\n    KWD(new char[]{0x4b, 0x57, 0x44}),\n\n    /** 开曼元 [$] */\n    KYD(new char[]{0x24}),\n\n    /** 哈萨克斯坦坚戈 [лв] */\n    KZT(new char[]{0x43b, 0x432}),\n\n    /** 老挝基普 [₭] */\n    LAK(new char[]{0x20ad}),\n\n    /** 黎巴嫩镑 [£] */\n    LBP(new char[]{0xa3}),\n\n    /** 斯里兰卡卢比 [₨] */\n    LKR(new char[]{0x20a8}),\n\n    /** 利比里亚元 [$] */\n    LRD(new char[]{0x24}),\n\n    /** 巴索托洛蒂 [LSL] */\n    LSL(new char[]{0x4c, 0x53, 0x4c}),\n\n    /** 利比亚第纳尔 [LYD] */\n    LYD(new char[]{0x4c, 0x59, 0x44}),\n\n    /** 摩洛哥迪拉姆 [MAD] */\n    MAD(new char[]{0x4d, 0x41, 0x44}),\n\n    /** 摩尔多瓦列伊 [MDL] */\n    MDL(new char[]{0x4d, 0x44, 0x4c}),\n\n    /** 马尔加什阿里亚 [MGA] */\n    MGA(new char[]{0x4d, 0x47, 0x41}),\n\n    /** 马其顿第纳尔 [ден] */\n    MKD(new char[]{0x434, 0x435, 0x43d}),\n\n    /** 缅元 [MMK] */\n    MMK(new char[]{0x4d, 0x4d, 0x4b}),\n\n    /** 蒙古图格里克 [₮] */\n    MNT(new char[]{0x20ae}),\n\n    /** 毛里塔尼亚乌吉亚 [MRO] */\n    MRO(new char[]{0x4d, 0x52, 0x4f}),\n\n    /** 毛里塔尼亚卢比 [₨] */\n    MUR(new char[]{0x20a8}),\n\n    /** 马尔代夫拉菲亚 [MVR] */\n    MVR(new char[]{0x4d, 0x56, 0x52}),\n\n    /** 马拉维克瓦查 [MWK] */\n    MWK(new char[]{0x4d, 0x57, 0x4b}),\n\n    /** 莫桑比克梅蒂卡尔 [MT] */\n    MZN(new char[]{0x4d, 0x54}),\n\n    /** 纳米比亚元 [$] */\n    NAD(new char[]{0x24}),\n\n    /** 尼日利亚奈拉 [₦] */\n    NGN(new char[]{0x20a6}),\n\n    /** 尼加拉瓜科多巴 [C$] */\n    NIO(new char[]{0x43, 0x24}),\n\n    /** 尼泊尔卢比 [₨] */\n    NPR(new char[]{0x20a8}),\n\n    /** 新西兰元 [NZ$] */\n    NZD(new char[]{0x4e, 0x5a, 0x24}),\n\n    /** 阿曼里亚尔 [﷼] */\n    OMR(new char[]{0xfdfc}),\n\n    /** 巴拿马巴波亚 [B/.] */\n    PAB(new char[]{0x42, 0x2f, 0x2e}),\n\n    /** 秘鲁新索尔 [S/.] */\n    PEN(new char[]{0x53, 0x2f, 0x2e}),\n\n    /** 巴布亚新几内亚基那 [PGK] */\n    PGK(new char[]{0x50, 0x47, 0x4b}),\n\n    /** 巴拉圭瓜拉尼 [Gs] */\n    PYG(new char[]{0x47, 0x73}),\n\n    /** 卡塔尔里亚尔 [﷼] */\n    QAR(new char[]{0xfdfc}),\n\n    /** 塞尔维亚第纳尔 [Дин.] */\n    RSD(new char[]{0x414, 0x438, 0x43d, 0x2e}),\n\n    /** 卢旺达法郎 [RWF] */\n    RWF(new char[]{0x52, 0x57, 0x46}),\n\n    /** 所罗门群岛元 [$] */\n    SBD(new char[]{0x24}),\n\n    /** 塞舌尔卢比 [₨] */\n    SCR(new char[]{0x20a8}),\n\n    /** 苏丹镑 [SDG] */\n    SDG(new char[]{0x53, 0x44, 0x47}),\n\n    /** 圣赫勒拿镑 [£] */\n    SHP(new char[]{0xa3}),\n\n    /** 塞拉利昂利昂 [SLL] */\n    SLL(new char[]{0x53, 0x4c, 0x4c}),\n\n    /** 索马里先令 [S] */\n    SOS(new char[]{0x53}),\n\n    /** 苏里南元 [$] */\n    SRD(new char[]{0x24}),\n\n    /** 圣多美多布拉 [STD] */\n    STD(new char[]{0x53, 0x54, 0x44}),\n\n    /** 叙利亚镑 [£] */\n    SYP(new char[]{0xa3}),\n\n    /** 斯威士兰里兰吉尼 [SZL] */\n    SZL(new char[]{0x53, 0x5a, 0x4c}),\n\n    /** 塔吉克斯坦索莫尼 [TJS] */\n    TJS(new char[]{0x54, 0x4a, 0x53}),\n\n    /** 土库曼斯坦马纳特 [TMT] */\n    TMT(new char[]{0x54, 0x4d, 0x54}),\n\n    /** 突尼斯第纳尔 [TND] */\n    TND(new char[]{0x54, 0x4e, 0x44}),\n\n    /** 汤加潘加 [TOP] */\n    TOP(new char[]{0x54, 0x4f, 0x50}),\n\n    /** 土耳其里拉 [₺], previous: {0x54, 0x52, 0x59} */\n    TRY(new char[]{0x20ba}),\n\n    /** 特立尼达元 [TT$] */\n    TTD(new char[]{0x54, 0x54, 0x24}),\n\n    /** 坦桑尼亚先令 [TZS] */\n    TZS(new char[]{0x54, 0x5a, 0x53}),\n\n    /** 乌干达先令 [UGX] */\n    UGX(new char[]{0x55, 0x47, 0x58}),\n\n    /** 乌拉圭比索 [$U] */\n    UYU(new char[]{0x24, 0x55}),\n\n    /** 乌兹别克斯坦索姆 [лв] */\n    UZS(new char[]{0x43b, 0x432}),\n\n    /** 委内瑞拉玻利瓦尔 [Bs] */\n    VEF(new char[]{0x42, 0x73}),\n\n    /** 瓦努阿图瓦图 [VUV] */\n    VUV(new char[]{0x56, 0x55, 0x56}),\n\n    /** 萨摩亚塔拉 [WST] */\n    WST(new char[]{0x57, 0x53, 0x54}),\n\n    /** 中非金融合作法郎 [XAF] */\n    XAF(new char[]{0x58, 0x41, 0x46}),\n\n    /** 银（盎司） [XAG] */\n    XAG(new char[]{0x58, 0x41, 0x47}),\n\n    /** 金（盎司） [XAU] */\n    XAU(new char[]{0x58, 0x41, 0x55}),\n\n    /** 东加勒比元 [$] */\n    XCD(new char[]{0x24}),\n\n    /** 国际货币基金组织特别提款权 [XDR] */\n    XDR(new char[]{0x58, 0x44, 0x52}),\n\n    /** CFA 法郎 [XOF] */\n    XOF(new char[]{0x58, 0x4f, 0x46}),\n\n    /** 钯（盎司） [XPD] */\n    XPD(new char[]{0x58, 0x50, 0x44}),\n\n    /** CFP 法郎 [XPF] */\n    XPF(new char[]{0x58, 0x50, 0x46}),\n\n    /** 铂（盎司） [XPT] */\n    XPT(new char[]{0x58, 0x50, 0x54}),\n\n    /** 也门里亚尔 [﷼] */\n    YER(new char[]{0xfdfc}),\n\n    /** 南非兰特 [R] */\n    ZAR(new char[]{0x52}),\n\n    /** Belarus Ruble [Br] */\n    BYN(new char[]{0x42, 0x72}),\n\n    /** El Salvador Colon [$] */\n    SVC(new char[]{0x24}),\n\n    /** Zimbabwe Dollar [Z$] */\n    ZWD(new char[]{0x5a, 0x24}),\n\n    // ----------------------------------------------------------------------------------others\n    /** 安道尔比塞塔 [ADP] */\n    ADP(new char[]{0x41, 0x44, 0x50}),\n\n    /** 奥地利先令 [ATS] */\n    ATS(new char[]{0x41, 0x54, 0x53}),\n\n    /** AYM [AYM] */\n    AYM(new char[]{0x41, 0x59, 0x4d}),\n\n    /** 比利时法郎 [BEF] */\n    BEF(new char[]{0x42, 0x45, 0x46}),\n\n    /** 保加利亚硬列弗 [BGL] */\n    BGL(new char[]{0x42, 0x47, 0x4c}),\n\n    /** 玻利维亚 Mvdol（资金） [BOV] */\n    BOV(new char[]{0x42, 0x4f, 0x56}),\n\n    /** CHE [CHE] */\n    CHE(new char[]{0x43, 0x48, 0x45}),\n\n    /** CHW [CHW] */\n    CHW(new char[]{0x43, 0x48, 0x57}),\n\n    /** 智利 Unidades de Fomento（资金） [CLF] */\n    CLF(new char[]{0x43, 0x4c, 0x46}),\n\n    /** COU [COU] */\n    COU(new char[]{0x43, 0x4f, 0x55}),\n\n    /** 塞浦路斯镑 [CYP] */\n    CYP(new char[]{0x43, 0x59, 0x50}),\n\n    /** 德国马克 [DEM] */\n    DEM(new char[]{0x44, 0x45, 0x4d}),\n\n    /** 爱沙尼亚克朗 [EEK] */\n    EEK(new char[]{0x45, 0x45, 0x4b}),\n\n    /** 西班牙比塞塔 [ESP] */\n    ESP(new char[]{0x45, 0x53, 0x50}),\n\n    /** 芬兰马克 [FIM] */\n    FIM(new char[]{0x46, 0x49, 0x4d}),\n\n    /** 法国法郎 [FRF] */\n    FRF(new char[]{0x46, 0x52, 0x46}),\n\n    /** 加纳塞第 [GHC] */\n    GHC(new char[]{0x47, 0x48, 0x43}),\n\n    /** 希腊德拉克马 [GRD] */\n    GRD(new char[]{0x47, 0x52, 0x44}),\n\n    /** 几内亚比绍比索 [GWP] */\n    GWP(new char[]{0x47, 0x57, 0x50}),\n\n    /** 爱尔兰镑 [IEP] */\n    IEP(new char[]{0x49, 0x45, 0x50}),\n\n    /** 意大利里拉 [ITL] */\n    ITL(new char[]{0x49, 0x54, 0x4c}),\n\n    /** 立陶宛立特 [LTL] */\n    LTL(new char[]{0x4c, 0x54, 0x4c}),\n\n    /** 卢森堡法郎 [LUF] */\n    LUF(new char[]{0x4c, 0x55, 0x46}),\n\n    /** 拉脱维亚拉特 [LVL] */\n    LVL(new char[]{0x4c, 0x56, 0x4c}),\n\n    /** 马达加斯加法郎 [MGF] */\n    MGF(new char[]{0x4d, 0x47, 0x46}),\n\n    /** Mauritanian Ouguiya [MRU] */\n    MRU(new char[]{0x4d, 0x52, 0x55}),\n\n    /** 马耳他里拉 [MTL] */\n    MTL(new char[]{0x4d, 0x54, 0x4c}),\n\n    /** 墨西哥 Unidad de Inversion (UDI)（资金） [MXV] */\n    MXV(new char[]{0x4d, 0x58, 0x56}),\n\n    /** 旧莫桑比克美提卡 [MZM] */\n    MZM(new char[]{0x4d, 0x5a, 0x4d}),\n\n    /** 荷兰盾 [NLG] */\n    NLG(new char[]{0x4e, 0x4c, 0x47}),\n\n    /** 葡萄牙埃斯库多 [PTE] */\n    PTE(new char[]{0x50, 0x54, 0x45}),\n\n    /** 苏丹第纳尔 [SDD] */\n    SDD(new char[]{0x53, 0x44, 0x44}),\n\n    /** 斯洛文尼亚托拉尔 [SIT] */\n    SIT(new char[]{0x53, 0x49, 0x54}),\n\n    /** 斯洛伐克克朗 [SKK] */\n    SKK(new char[]{0x53, 0x4b, 0x4b}),\n\n    /** 苏里南盾 [SRG] */\n    SRG(new char[]{0x53, 0x52, 0x47}),\n\n    /** South Sudanese Pound [SSP] */\n    SSP(new char[]{0x53, 0x53, 0x50}),\n\n    /** São Tomé and Príncipe Dobra [STN] */\n    STN(new char[]{0x53, 0x54, 0x4e}),\n\n    /** 土库曼斯坦马纳特 [TMM] */\n    TMM(new char[]{0x54, 0x4d, 0x4d}),\n\n    /** 帝汶埃斯库多 [TPE] */\n    TPE(new char[]{0x54, 0x50, 0x45}),\n\n    /** 土耳其里拉 [TRL] */\n    TRL(new char[]{0x54, 0x52, 0x4c}),\n\n    /** 美元（次日） [USN] */\n    USN(new char[]{0x55, 0x53, 0x4e}),\n\n    /** 美元（当日） [USS] */\n    USS(new char[]{0x55, 0x53, 0x53}),\n\n    /** UYI [UYI] */\n    UYI(new char[]{0x55, 0x59, 0x49}),\n\n    /** 委内瑞拉博利瓦 [VEB] */\n    VEB(new char[]{0x56, 0x45, 0x42}),\n\n    /** Venezuelan Bolívar Soberano [VED] */\n    //VED(new char[]{0x56, 0x45, 0x44}), // Already invalided\n\n    /** Venezuelan Bolívar Soberano [VES] */\n    VES(new char[]{0x56, 0x45, 0x53}),\n\n    /** 欧洲复合单位 [XBA] */\n    XBA(new char[]{0x58, 0x42, 0x41}),\n\n    /** 欧洲货币联盟 [XBB] */\n    XBB(new char[]{0x58, 0x42, 0x42}),\n\n    /** 欧洲计算单位 (XBC) [XBC] */\n    XBC(new char[]{0x58, 0x42, 0x43}),\n\n    /** 欧洲计算单位 (XBD) [XBD] */\n    XBD(new char[]{0x58, 0x42, 0x44}),\n\n    /** 法国 UIC 法郎 [XFU] */\n    XFU(new char[]{0x58, 0x46, 0x55}),\n\n    /** Sucre [XSU] */\n    XSU(new char[]{0x58, 0x53, 0x55}),\n\n    /** 为测试保留的代码 [XTS] */\n    XTS(new char[]{0x58, 0x54, 0x53}),\n\n    /** ADB Unit of Account [XUA] */\n    XUA(new char[]{0x58, 0x55, 0x41}),\n\n    /** 南斯拉夫偌威第纳尔 [YUM] */\n    YUM(new char[]{0x59, 0x55, 0x4d}),\n\n    /** 赞比亚克瓦查 [ZMK] */\n    ZMK(new char[]{0x5a, 0x4d, 0x4b}),\n\n    /** 货币未知或无效 [XXX] */\n    XXX(new char[]{0x58, 0x58, 0x58}),\n\n    // -------------------------------Invalid currency\n    /** Guernsey Pound [£] */\n    //GGP(new char[]{0xa3}),\n\n    /** Isle of Man Pound [£] */\n    //IMP(new char[]{0xa3}),\n\n    /** Jersey Pound [£] */\n    //JEP(new char[]{0xa3}),\n\n    /** Tuvalu Dollar [$] */\n    //TVD(new char[]{0x24}),\n\n    ;\n\n    private static final Map<String, CurrencyEnum> CURRENCY_CODES = Enums.toMap(CurrencyEnum.class, CurrencyEnum::currencyCode);\n    private static final Map<String, CurrencyEnum> NUMERIC_CODES  = Enums.toMap(CurrencyEnum.class, CurrencyEnum::numericCode);\n\n    /**\n     * 币种代码\n     */\n    private final String currencyCode;\n\n    /**\n     * 币种符号\n     */\n    private final String currencySymbol;\n\n    /**\n     * 世界各国和地区名称数字\n     */\n    private final String numericCode;\n\n    /**\n     * 币种实例对象\n     */\n    private final Currency currency;\n\n    /**\n     * Constructor\n     *\n     * @param currencySymbol 币种符号\n     */\n    CurrencyEnum(char[] currencySymbol) {\n        this.currencyCode   = super.name();\n        this.currencySymbol = new String(currencySymbol);\n        this.currency       = Currency.getInstance(currencyCode);\n        this.numericCode    = String.format(\"%03d\", currency.getNumericCode());\n    }\n\n    /**\n     * @return 币种代码(e.g. CNY)\n     */\n    public String currencyCode() {\n        return currencyCode;\n    }\n\n    /**\n     * @return 世界各国和地区名称数字代码（e.g. 156）\n     */\n    public String numericCode() {\n        return numericCode;\n    }\n\n    /**\n     * @return 币种符号(e.g. ¥)\n     */\n    public String currencySymbol() {\n        return currencySymbol;\n    }\n\n    /**\n     * <pre>\n     *  java.util.Currency.getDisplayName(Locale.CHINA)  -> 人民币\n     *  java.util.Currency.getDisplayName(Locale.US)     -> Chinese Yuan\n     *  java.util.Currency.getSymbol(Locale.CHINA)       -> ￥\n     *  java.util.Currency.getNumericCode()              -> 156\n     * </pre>\n     *\n     * @return {@code java.util.Currency } object instance\n     */\n    public Currency currency() {\n        return currency;\n    }\n\n    // --------------------------------------------------------------of methods\n\n    /**\n     * Gets CurrencyEnum by currency code\n     *\n     * @param currencyCode the currency code\n     * @return CurrencyEnum\n     */\n    public static CurrencyEnum ofCurrencyCode(String currencyCode) {\n        return CURRENCY_CODES.get(currencyCode);\n    }\n\n    /**\n     * Gets CurrencyEnum by numeric code\n     *\n     * @param numericCode the numeric code\n     * @return CurrencyEnum\n     */\n    public static CurrencyEnum ofNumericCode(String numericCode) {\n        return NUMERIC_CODES.get(numericCode);\n    }\n\n    /**\n     * Gets CurrencyEnum by currency\n     *\n     * @param currency the currency\n     * @return CurrencyEnum\n     */\n    public static CurrencyEnum ofCurrency(Currency currency) {\n        return CURRENCY_CODES.get(currency.getCurrencyCode());\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Enums.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.commons.lang3.EnumUtils;\n\nimport java.util.Map;\nimport java.util.function.Function;\n\n/**\n * Enum utility\n * \n * @author Ponfee\n */\npublic class Enums {\n\n    /**\n     * Gets the {@code Map} of enums by name.\n     *\n     * @param enumType the enum type\n     * @param <E>      map key mapper\n     * @return the immutable map of enum to map enums, never null\n     * @see EnumUtils#getEnumMap(Class)\n     */\n    public static <E extends Enum<E>> Map<String, E> toMap(Class<E> enumType) {\n        return toMap(enumType, Enum::name);\n    }\n\n    /**\n     * Returns {@code Map} of enum\n     *\n     * @param enumType  the enum type\n     * @param keyMapper map key mapper\n     * @param <K>       then map key type\n     * @param <E>       the enum type\n     * @return the immutable map of enum to map enums, never null\n     */\n    public static <K, E extends Enum<E>> Map<K, E> toMap(Class<E> enumType, Function<E, K> keyMapper) {\n        E[] enumConstants = enumType.getEnumConstants();\n        ImmutableMap.Builder<K, E> mapping = ImmutableMap.builderWithExpectedSize(enumConstants.length << 1);\n        for (final E e: enumConstants) {\n            mapping.put(keyMapper.apply(e), e);\n        }\n        return mapping.build();\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/ExtendMethodHandles.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.exception.Throwables.ThrowingSupplier;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\n\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodHandles.Lookup;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Method;\nimport java.util.function.Function;\n\n/**\n * <pre>\n * jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link Lookup}\n * 在调用方法 {@link Lookup#findSpecial(Class, String, java.lang.invoke.MethodType, Class)}\n * 和{@link Lookup#unreflectSpecial(Method, Class)}\n * 获取父类方法句柄{@link MethodHandle}时\n * 可能出现权限不够, 抛出如下异常, 所以通过反射创建{@link Lookup}解决该问题.\n *\n *  java.lang.IllegalAccessException: no private access for invokespecial: interface com.example.demo.methodhandle.UserService, from com.example.demo.methodhandle.UserServiceInvoke\n *  at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850)\n *  at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1572)\n *\n * 而jdk11中直接调用{@link MethodHandles#lookup()}获取到的{@link Lookup},也只能对接口类型才会权限获取方法的方法句柄{@link MethodHandle}.\n * 如果是普通类型Class,需要使用jdk9开始提供的 MethodHandles#privateLookupIn(java.lang.Class, java.lang.invoke.MethodHandles.Lookup)方法.\n * </pre>\n *\n * <a href=\"https://blog.csdn.net/u013202238/article/details/108687086\">参考文章</a>\n *\n * @author Ponfee\n */\npublic class ExtendMethodHandles {\n\n    public static final Function<Class<?>, Lookup> METHOD_LOOKUP = getMethodLookup();\n\n    /**\n     * Jdk9中的MethodHandles.lookup()方法返回的Lookup对象，有权限访问specialCaller != lookupClass()的类\n     * 但是只能适用于接口，java.lang.invoke.MethodHandles.Lookup#checkSpecialCaller(Class)\n     */\n    public static MethodHandle getSpecialMethodHandle(Method parentMethod) {\n        Class<?> declaringClass = parentMethod.getDeclaringClass();\n        Lookup lookup = METHOD_LOOKUP.apply(declaringClass);\n        try {\n            return lookup.in(declaringClass).unreflectSpecial(parentMethod, declaringClass);\n        } catch (IllegalAccessException e) {\n            return ExceptionUtils.rethrow(e);\n        }\n    }\n\n    private static Function<Class<?>, Lookup> getMethodLookup() {\n        // 先查询jdk9开始提供的“java.lang.invoke.MethodHandles.privateLookupIn”方法\n        Method jdk9PrivateLookupInMethod = ClassUtils.getMethod(MethodHandles.class, \"privateLookupIn\", Class.class, Lookup.class);\n        if (jdk9PrivateLookupInMethod != null) {\n            return callerClass -> ThrowingSupplier.doChecked(() -> (Lookup) jdk9PrivateLookupInMethod.invoke(MethodHandles.class, callerClass, MethodHandles.lookup()));\n        }\n\n        // 查询jdk8版本：这种方式也适用于jdk9及以上的版本，但优先上面的可以避免jdk9反射警告\n        Constructor<Lookup> jdk8LookupConstructor = ClassUtils.getConstructor(Lookup.class, Class.class, int.class);\n        if (jdk8LookupConstructor == null) {\n            // 未找到则可能是jdk8以下版本\n            throw new UnsupportedOperationException(\"Not found method 'private java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)'.\");\n        }\n\n        jdk8LookupConstructor.setAccessible(true);\n        int modifiers = Lookup.PRIVATE | Lookup.PROTECTED | Lookup.PACKAGE | Lookup.PUBLIC;\n        return callerClass -> ThrowingSupplier.doChecked(() -> jdk8LookupConstructor.newInstance(callerClass, modifiers));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/FailRetryTemplate.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.function.Supplier;\n\n/**\n * Fail retry template(template method pattern)\n *\n * @author Ponfee\n */\npublic class FailRetryTemplate {\n\n    private static final Logger LOG = LoggerFactory.getLogger(FailRetryTemplate.class);\n\n    public static <T> T execute(Supplier<T> normal, Supplier<String> message) throws Exception {\n        return execute(normal, normal, 5, message);\n    }\n\n    public static <T> T execute(Supplier<T> normal, Supplier<T> fallback,\n                                int failRetryCount, Supplier<String> message) throws Exception {\n        int i = 0;\n        Exception ex;\n        String logMsg = null;\n        do {\n            try {\n                if (i == 0) {\n                    return normal.get();\n                } else {\n                    return fallback.get();\n                }\n            } catch (Exception e) {\n                ex = e;\n                if (i < failRetryCount) {\n                    // not the last loop\n                    if (logMsg == null) {\n                        logMsg = UuidUtils.uuid32() + \" - \" + message.get();\n                    }\n                    int count = i + 1;\n                    LOG.error(\"Execute failed, will retrying - \" + count + \" - \" + logMsg, e);\n                    Thread.sleep(5000L * count);\n                }\n            }\n        } while (++i <= failRetryCount);\n\n        throw ex;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Holder.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport java.util.Objects;\nimport java.util.function.*;\n\n/**\n * 变量持有，用于lambda方法体内\n * <p>non-thread-safe\n *\n * @param <T> the type T\n * @author Ponfee\n */\npublic final class Holder<T> {\n\n    private T value;\n\n    private Holder(T value) {\n        this.value = value;\n    }\n\n    public static <T> Holder<T> empty() {\n        return new Holder<>(null);\n    }\n\n    public static <T> Holder<T> of(T value) {\n        return new Holder<>(value);\n    }\n\n    /**\n     * Returns the holder value whether null\n     *\n     * @return a boolean, if {@code true} then the value is null\n     */\n    public boolean isEmpty() {\n        return value == null;\n    }\n\n    /**\n     * Sets a newly value and return former value\n     *\n     * @param value the newly value\n     * @return then former value\n     */\n    public T set(T value) {\n        T former = this.value;\n        this.value = value;\n        return former;\n    }\n\n    /**\n     * Sets a new value if former value is null\n     *\n     * @param value the new value\n     */\n    public void setIfAbsent(T value) {\n        if (this.value == null) {\n            this.value = value;\n        }\n    }\n\n    /**\n     * Replaces value if former value is not null,\n     * and return former value\n     *\n     * @param value the newly value\n     * @return then former value\n     */\n    public T setIfPresent(T value) {\n        T former = this.value;\n        if (this.value != null) {\n            this.value = value;\n        }\n        return former;\n    }\n\n    public T setIfMatches(T value, Predicate<T> predicate) {\n        T former = this.value;\n        if (predicate.test(this.value)) {\n            this.value = value;\n        }\n        return former;\n    }\n\n    public T setIfMatches(T value, BiPredicate<T, T> predicate) {\n        T former = this.value;\n        if (predicate.test(this.value, value)) {\n            this.value = value;\n        }\n        return former;\n    }\n\n    public void ifPresent(Consumer<? super T> consumer) {\n        if (value != null) {\n            consumer.accept(value);\n        }\n    }\n\n    public <U> Holder<U> map(Function<? super T, ? extends U> mapper) {\n        Objects.requireNonNull(mapper);\n        return isEmpty() ? empty() : of(mapper.apply(value));\n    }\n\n    public Holder<T> filter(Predicate<? super T> predicate) {\n        Objects.requireNonNull(predicate);\n        return (isEmpty() || predicate.test(value)) ? this : empty();\n    }\n\n    public T get() {\n        return value;\n    }\n\n    public T orElse(T other) {\n        return value != null ? value : other;\n    }\n\n    public T orElseGet(Supplier<T> other) {\n        return value != null ? value : other.get();\n    }\n\n    public <E extends Throwable> T orElseThrow(Supplier<? extends E> exceptionSupplier) throws E {\n        if (value != null) {\n            return value;\n        } else {\n            throw exceptionSupplier.get();\n        }\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj) {\n            return true;\n        }\n\n        return (obj instanceof Holder)\n            && Objects.equals(value, ((Holder<?>) obj).value);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hashCode(value);\n    }\n\n    @Override\n    public String toString() {\n        return value != null ? String.format(\"Holder(%s)\", value) : \"Holder.empty\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/IdcardResolver.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.date.Dates;\nimport com.google.common.collect.ImmutableMap;\n\nimport java.util.*;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.regex.Pattern;\n\nimport static java.util.Calendar.YEAR;\n\n/**\n * 身份证解析及生成\n * \n * http://www.mca.gov.cn/article/sj/xzqh/2018/\n * \n * @author Ponfee\n */\npublic class IdcardResolver {\n\n    private static final long ORIGINAL = Dates.toDate(\"1950-01-01 00:00:00\").getTime();\n\n    /**\n     * 证件类型\n     */\n    public enum CertType {\n        FIRST, SECOND, HONGKONG, MACAO, TAIWAN, PASSPORT\n    }\n\n    /**\n     * 性别：M男；F女；N未知；\n     */\n    public enum Sex {\n        M, F, N\n    }\n\n    private String idcard; // idCard\n    private boolean isValid = false; // 是否有效\n    private CertType type; // 类型\n    private String province; // 省份\n    private String district; // 市县\n    private Date birthday; // 生日\n    private Integer age; // 年龄\n    private Sex sex; // 性别\n\n    /**\n     * 随机身份证号码生成\n     * @return\n     */\n    public static String generate() {\n        Random random = ThreadLocalRandom.current();\n        StringBuilder builder = new StringBuilder(18);\n        builder.append(AREA_CODE_LIST.get(random.nextInt(AREA_CODE_LIST.size()))); // 行政区号：6位\n        builder.append(Dates.format(Dates.random(ORIGINAL, System.currentTimeMillis()), \"yyyyMMdd\")); // 生日：8位\n        // 当地派出所在该日期的出生顺序号：3位，其中17位（倒数第二位）男为单数，女为双数\n        builder.append(String.format(\"%03d\", random.nextInt(1000)));\n        builder.append(genPowerSum(builder.toString().toCharArray())); // 校验码：1位\n        return builder.toString();\n    }\n\n    public IdcardResolver(String idcard) {\n        if (idcard == null) {\n            return;\n        }\n\n        this.idcard = idcard.trim().toUpperCase();\n        this.isValid = this.resolve(this.idcard);\n    }\n\n    /**\n     * 解析\n     * @return\n     */\n    private boolean resolve(String idcard) {\n        return isSecond(idcard) || isFirst(idcard) || isPassport(idcard) || isOther(idcard);\n    }\n\n    /**\n     * 验证15位身份编码是否合法\n     * @param idCard 身份编码\n     * @return 是否合法\n     */\n    private static final Pattern FIRST = Pattern.compile(\"^[0-9]{15}$\");\n    private boolean isFirst(String idcard) {\n        if (!FIRST.matcher(idcard).matches()) {\n            return false;\n        }\n\n        // 转成18位\n        idcard = idcard.substring(0, 6) + \"19\" + idcard.substring(6);\n        idcard += genPowerSum(idcard.toCharArray());\n        if (isSecond(idcard)) {\n            this.type = CertType.FIRST;\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * 验证18位身份编码是否合法\n     * @param idCard 身份编码\n     * @return 是否合法\n     */\n    private static final Pattern SECOND = Pattern.compile(\"^[0-9]{17}[0-9X]$\");\n    private boolean isSecond(String idcard) {\n        if (!SECOND.matcher(idcard).matches()) {\n            return false;\n        }\n\n        // 城市验证\n        this.province = CITY_CODES.get(idcard.substring(0, 2));\n        if (this.province == null) {\n            return false;\n        }\n\n        this.district = DISTRICT_CODE_MAPPING.get(Integer.parseInt(idcard.substring(0, 6)));\n        //if (this.district == null) return false; // 有些县升为地级市，code改变了\n\n        // 校验码验证\n        String prefix17 = idcard.substring(0, 17);\n        String checkCode = genPowerSum(prefix17.toCharArray());\n        if (!checkCode.equals(idcard.substring(17))) {\n            return false;\n        }\n\n        // 生日验证\n        if (!verifyBirthday(idcard.substring(6, 14))) {\n            return false;\n        }\n\n        // 性别提取\n        if ((Character.getNumericValue(idcard.charAt(16)) & 0x01) == 1) {\n            this.sex = Sex.M;\n        } else {\n            this.sex = Sex.F;\n        }\n\n        this.type = CertType.SECOND;\n        return true;\n    }\n\n    /**\n     * 护照验证\n     * @param idcard\n     * @return\n     */\n    private static final Pattern PASSPORT_REGEX = Pattern.compile(\n        \"^1[45][0-9]{7}|G[0-9]{8}|P[0-9]{7}|S[0-9]{7,8}|D[0-9]+$\"\n    );\n    private boolean isPassport(String idcard) {\n        if (PASSPORT_REGEX.matcher(idcard).matches()) {\n            this.type = CertType.PASSPORT;\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * 验证10位身份编码是否合法\n     * @param idCard 身份编码\n     * @return 身份证信息数组\n     */\n    private static final Pattern HONGKONG = Pattern.compile(\"^[A-Z]{1,2}[0-9]{6}\\\\(?[0-9A]\\\\)?$\");\n    private static final Pattern MACO = Pattern.compile(\"^([157])[0-9]{6}\\\\(?[0-9A-Z]\\\\)?$\");\n    private static final Pattern TAIWAN = Pattern.compile(\"^[a-zA-Z][0-9]{9}$\");\n    private boolean isOther(String idcard) {\n        idcard = idcard.replaceAll(\"[(|)]\", \"\");\n\n        if (HONGKONG.matcher(idcard).matches()) { // 香港\n            this.type = CertType.HONGKONG;\n            this.sex = Sex.N;\n            return validateHKCard(idcard);\n        } else if (MACO.matcher(idcard).matches()) { // 澳门\n            this.type = CertType.MACAO;\n            this.sex = Sex.N;\n            return true;\n        } else if (TAIWAN.matcher(idcard).matches()) { // 台湾\n            this.type = CertType.TAIWAN;\n            String sex = idcard.substring(1, 2);\n            if (\"1\".equals(sex)) {\n                this.sex = Sex.M;\n            } else if (\"2\".equals(sex)) {\n                this.sex = Sex.F;\n            } else {\n                this.sex = Sex.N;\n            }\n            return validateTWCard(idcard);\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * 验证台湾身份证号码\n     * @param idCard 身份证号码\n     * @return 验证码是否符合\n     */\n    private boolean validateTWCard(String idCard) {\n        String start = idCard.substring(0, 1);\n        String mid = idCard.substring(1, 9);\n        String end = idCard.substring(9, 10);\n        int iStart = TW_FIRST_CODE.get(start);\n        int sum = iStart / 10 + (iStart % 10) * 9;\n        int iteration = 8;\n        for (char c : mid.toCharArray()) {\n            sum = sum + (int) c * iteration--;\n        }\n        return (10 - sum % 10) % 10 == Integer.parseInt(end);\n    }\n\n    /**\n     * <pre>\n     *   验证香港身份证号码(存在bug，部份特殊身份证无法校验)\n     *   身份证前2位为英文字符，如果只出现一个英文字符则表示第一位是空格，对应数字58 \n     *   前2位英文字符A-Z分别对应数字10-35 最后一位校验码为0-9的数字加上字符\"A\"，\"A\"代表10\n     *   将身份证号码全部转换为数字，分别对应乘9-1相加的总和，整除11则证件号码有效\n     * </pre>\n     * \n     * @param idCard 身份证号码\n     * @return 验证码是否符合\n     */\n    private boolean validateHKCard(String idCard) {\n        String card = idCard.replaceAll(\"[\\\\(|\\\\)]\", \"\");\n        int sum;\n        if (card.length() == 9) {\n            sum = ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 9\n                + ((int) card.substring(1, 2).toUpperCase().toCharArray()[0] - 55) * 8;\n            card = card.substring(1, 9);\n        } else {\n            sum = 522 + ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 8;\n        }\n        String mid = card.substring(1, 7);\n        String end = card.substring(7, 8);\n        int iteration = 7;\n        for (char c : mid.toCharArray()) {\n            sum = sum + (int) c * iteration--;\n        }\n\n        if (\"A\".equalsIgnoreCase(end)) {\n            sum = sum + 10;\n        } else {\n            sum = sum + Integer.parseInt(end);\n        }\n\n        return sum % 11 == 0;\n    }\n\n    /**\n     * <pre>\n     * 18位身份证验证\n     * 根据〖中华人民共和国国家标准 GB 11643-1999〗中有关公民身份号码的规定，公民身份号码是特征组合码，\n     * 由十七位数字本体码和一位数字校验码组成。\n     *\n     * 排列顺序从左至右依次为：六位数字地址码，八位数字出生日期码，三位数字顺序码和一位数字校验码。\n     * 第十八位数字(校验码)的计算方法为：\n     * 1.将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为：7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2\n     * 2.将这17位数字和系数相乘的结果相加。\n     * 3.用加出来和除以11，看余数是多少？\n     * 4.余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2。\n     * 5.通过上面得知如果余数是2，就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10，身份证的最后一位号码就是2。\n     * </pre>\n     */\n    private static String genPowerSum(char[] chars) {\n        int[] n = new int[chars.length];\n        int result = 0;\n        for (int i = 0; i < n.length; i++) {\n            n[i] = Character.getNumericValue(chars[i]);\n        }\n        for (int i = 0; i < n.length; i++) {\n            result += POWER[i] * n[i];\n        }\n        return JUXTAPOSE[result % 11];\n    }\n\n    /**\n     * 验证生日是否有效\n     * @param date\n     * @return\n     */\n    private boolean verifyBirthday(String date) {\n        try {\n            this.birthday = Dates.toDate(date, \"yyyyMMdd\");\n            if (this.birthday.after(new Date())) {\n                return false;\n            }\n            Calendar calendar = Calendar.getInstance();\n            calendar.setTime(this.birthday);\n            this.age = Calendar.getInstance().get(YEAR) - calendar.get(YEAR);\n            return true;\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    public String getIdcard() {\n        return idcard;\n    }\n\n    public boolean isValid() {\n        return isValid;\n    }\n\n    public CertType getType() {\n        return type;\n    }\n\n    public String getProvince() {\n        return province;\n    }\n\n    public String getDistrict() {\n        return district;\n    }\n\n    public Date getBirthday() {\n        return birthday;\n    }\n\n    public Sex getSex() {\n        return sex;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    @Override\n    public String toString() {\n        return new StringBuilder(\"IdcardResolver {idcard=\")\n            .append(idcard)\n            .append(\", isValid=\").append(isValid)\n            .append(\", type=\").append(type)\n            .append(\", province=\").append(province)\n            .append(\", district=\").append(district)\n            .append(\", birthday=\").append(birthday)\n            .append(\", age=\").append(age)\n            .append(\", sex=\").append(sex)\n            .append(\"}\").toString();\n    }\n\n    /** 每位加权因子 */\n    private static final int[] POWER = {\n        7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 \n    };\n\n    /** 校验和对照表 */\n    private static final String[] JUXTAPOSE = { \n        \"1\", \"0\", \"X\", \"9\", \"8\", \"7\", \"6\", \"5\", \"4\", \"3\", \"2\" \n    };\n\n    /** 大陆城市 */\n    private static final Map<String, String> CITY_CODES = \n      new ImmutableMap.Builder<String, String>()\n        .put(\"11\", \"北京\")\n        .put(\"12\", \"天津\")\n        .put(\"13\", \"河北\")\n        .put(\"14\", \"山西\")\n        .put(\"15\", \"内蒙古\")\n        .put(\"21\", \"辽宁\")\n        .put(\"22\", \"吉林\")\n        .put(\"23\", \"黑龙江\")\n        .put(\"31\", \"上海\")\n        .put(\"32\", \"江苏\")\n        .put(\"33\", \"浙江\")\n        .put(\"34\", \"安徽\")\n        .put(\"35\", \"福建\")\n        .put(\"36\", \"江西\")\n        .put(\"37\", \"山东\")\n        .put(\"41\", \"河南\")\n        .put(\"42\", \"湖北\")\n        .put(\"43\", \"湖南\")\n        .put(\"44\", \"广东\")\n        .put(\"45\", \"广西\")\n        .put(\"46\", \"海南\")\n        .put(\"50\", \"重庆\")\n        .put(\"51\", \"四川\")\n        .put(\"52\", \"贵州\")\n        .put(\"53\", \"云南\")\n        .put(\"54\", \"西藏\")\n        .put(\"61\", \"陕西\")\n        .put(\"62\", \"甘肃\")\n        .put(\"63\", \"青海\")\n        .put(\"64\", \"宁夏\")\n        .put(\"65\", \"新疆\")\n        .put(\"71\", \"台湾\")\n        .put(\"81\", \"香港\")\n        .put(\"82\", \"澳门\")\n        .put(\"91\", \"国外\")\n        .build();\n\n    /** 台湾身份首字母对应数字 */\n    private static final Map<String, Integer> TW_FIRST_CODE = \n        new ImmutableMap.Builder<String, Integer>()\n        .put(\"A\", 10)\n        .put(\"B\", 11)\n        .put(\"C\", 12)\n        .put(\"D\", 13)\n        .put(\"E\", 14)\n        .put(\"F\", 15)\n        .put(\"G\", 16)\n        .put(\"H\", 17)\n        .put(\"J\", 18)\n        .put(\"K\", 19)\n        .put(\"L\", 20)\n        .put(\"M\", 21)\n        .put(\"N\", 22)\n        .put(\"P\", 23)\n        .put(\"Q\", 24)\n        .put(\"R\", 25)\n        .put(\"S\", 26)\n        .put(\"T\", 27)\n        .put(\"U\", 28)\n        .put(\"V\", 29)\n        .put(\"X\", 30)\n        .put(\"Y\", 31)\n        .put(\"W\", 32)\n        .put(\"Z\", 33)\n        .put(\"I\", 34)\n        .put(\"O\", 35)\n        .build();\n\n    public static final Map<Integer, String> DISTRICT_CODE_MAPPING;\n    private static final List<Integer> AREA_CODE_LIST;\n    static {\n        ImmutableMap.Builder<Integer, String> b = new ImmutableMap.Builder<>();\n        b.put(110000, \"北京市\");\n        b.put(110101, \"东城区\");\n        b.put(110102, \"西城区\");\n        b.put(110105, \"朝阳区\");\n        b.put(110106, \"丰台区\");\n        b.put(110107, \"石景山区\");\n        b.put(110108, \"海淀区\");\n        b.put(110109, \"门头沟区\");\n        b.put(110111, \"房山区\");\n        b.put(110112, \"通州区\");\n        b.put(110113, \"顺义区\");\n        b.put(110114, \"昌平区\");\n        b.put(110115, \"大兴区\");\n        b.put(110116, \"怀柔区\");\n        b.put(110117, \"平谷区\");\n        b.put(110118, \"密云区\");\n        b.put(110119, \"延庆区\");\n        b.put(120000, \"天津市\");\n        b.put(120101, \"和平区\");\n        b.put(120102, \"河东区\");\n        b.put(120103, \"河西区\");\n        b.put(120104, \"南开区\");\n        b.put(120105, \"河北区\");\n        b.put(120106, \"红桥区\");\n        b.put(120110, \"东丽区\");\n        b.put(120111, \"西青区\");\n        b.put(120112, \"津南区\");\n        b.put(120113, \"北辰区\");\n        b.put(120114, \"武清区\");\n        b.put(120115, \"宝坻区\");\n        b.put(120116, \"滨海新区\");\n        b.put(120117, \"宁河区\");\n        b.put(120118, \"静海区\");\n        b.put(120119, \"蓟州区\");\n        b.put(130000, \"河北省\");\n        b.put(130100, \"石家庄市\");\n        b.put(130102, \"长安区\");\n        b.put(130104, \"桥西区\");\n        b.put(130105, \"新华区\");\n        b.put(130107, \"井陉矿区\");\n        b.put(130108, \"裕华区\");\n        b.put(130109, \"藁城区\");\n        b.put(130110, \"鹿泉区\");\n        b.put(130111, \"栾城区\");\n        b.put(130121, \"井陉县\");\n        b.put(130123, \"正定县\");\n        b.put(130125, \"行唐县\");\n        b.put(130126, \"灵寿县\");\n        b.put(130127, \"高邑县\");\n        b.put(130128, \"深泽县\");\n        b.put(130129, \"赞皇县\");\n        b.put(130130, \"无极县\");\n        b.put(130131, \"平山县\");\n        b.put(130132, \"元氏县\");\n        b.put(130133, \"赵县\");\n        b.put(130181, \"辛集市\");\n        b.put(130183, \"晋州市\");\n        b.put(130184, \"新乐市\");\n        b.put(130200, \"唐山市\");\n        b.put(130202, \"路南区\");\n        b.put(130203, \"路北区\");\n        b.put(130204, \"古冶区\");\n        b.put(130205, \"开平区\");\n        b.put(130207, \"丰南区\");\n        b.put(130208, \"丰润区\");\n        b.put(130209, \"曹妃甸区\");\n        b.put(130223, \"滦县\");\n        b.put(130224, \"滦南县\");\n        b.put(130225, \"乐亭县\");\n        b.put(130227, \"迁西县\");\n        b.put(130229, \"玉田县\");\n        b.put(130281, \"遵化市\");\n        b.put(130283, \"迁安市\");\n        b.put(130300, \"秦皇岛市\");\n        b.put(130302, \"海港区\");\n        b.put(130303, \"山海关区\");\n        b.put(130304, \"北戴河区\");\n        b.put(130306, \"抚宁区\");\n        b.put(130321, \"青龙满族自治县\");\n        b.put(130322, \"昌黎县\");\n        b.put(130324, \"卢龙县\");\n        b.put(130400, \"邯郸市\");\n        b.put(130402, \"邯山区\");\n        b.put(130403, \"丛台区\");\n        b.put(130404, \"复兴区\");\n        b.put(130406, \"峰峰矿区\");\n        b.put(130407, \"肥乡区\");\n        b.put(130408, \"永年区\");\n        b.put(130423, \"临漳县\");\n        b.put(130424, \"成安县\");\n        b.put(130425, \"大名县\");\n        b.put(130426, \"涉县\");\n        b.put(130427, \"磁县\");\n        b.put(130430, \"邱县\");\n        b.put(130431, \"鸡泽县\");\n        b.put(130432, \"广平县\");\n        b.put(130433, \"馆陶县\");\n        b.put(130434, \"魏县\");\n        b.put(130435, \"曲周县\");\n        b.put(130481, \"武安市\");\n        b.put(130500, \"邢台市\");\n        b.put(130502, \"桥东区\");\n        b.put(130503, \"桥西区\");\n        b.put(130521, \"邢台县\");\n        b.put(130522, \"临城县\");\n        b.put(130523, \"内丘县\");\n        b.put(130524, \"柏乡县\");\n        b.put(130525, \"隆尧县\");\n        b.put(130526, \"任县\");\n        b.put(130527, \"南和县\");\n        b.put(130528, \"宁晋县\");\n        b.put(130529, \"巨鹿县\");\n        b.put(130530, \"新河县\");\n        b.put(130531, \"广宗县\");\n        b.put(130532, \"平乡县\");\n        b.put(130533, \"威县\");\n        b.put(130534, \"清河县\");\n        b.put(130535, \"临西县\");\n        b.put(130581, \"南宫市\");\n        b.put(130582, \"沙河市\");\n        b.put(130600, \"保定市\");\n        b.put(130602, \"竞秀区\");\n        b.put(130606, \"莲池区\");\n        b.put(130607, \"满城区\");\n        b.put(130608, \"清苑区\");\n        b.put(130609, \"徐水区\");\n        b.put(130623, \"涞水县\");\n        b.put(130624, \"阜平县\");\n        b.put(130626, \"定兴县\");\n        b.put(130627, \"唐县\");\n        b.put(130628, \"高阳县\");\n        b.put(130629, \"容城县\");\n        b.put(130630, \"涞源县\");\n        b.put(130631, \"望都县\");\n        b.put(130632, \"安新县\");\n        b.put(130633, \"易县\");\n        b.put(130634, \"曲阳县\");\n        b.put(130635, \"蠡县\");\n        b.put(130636, \"顺平县\");\n        b.put(130637, \"博野县\");\n        b.put(130638, \"雄县\");\n        b.put(130681, \"涿州市\");\n        b.put(130682, \"定州市\");\n        b.put(130683, \"安国市\");\n        b.put(130684, \"高碑店市\");\n        b.put(130700, \"张家口市\");\n        b.put(130702, \"桥东区\");\n        b.put(130703, \"桥西区\");\n        b.put(130705, \"宣化区\");\n        b.put(130706, \"下花园区\");\n        b.put(130708, \"万全区\");\n        b.put(130709, \"崇礼区\");\n        b.put(130722, \"张北县\");\n        b.put(130723, \"康保县\");\n        b.put(130724, \"沽源县\");\n        b.put(130725, \"尚义县\");\n        b.put(130726, \"蔚县\");\n        b.put(130727, \"阳原县\");\n        b.put(130728, \"怀安县\");\n        b.put(130730, \"怀来县\");\n        b.put(130731, \"涿鹿县\");\n        b.put(130732, \"赤城县\");\n        b.put(130800, \"承德市\");\n        b.put(130802, \"双桥区\");\n        b.put(130803, \"双滦区\");\n        b.put(130804, \"鹰手营子矿区\");\n        b.put(130821, \"承德县\");\n        b.put(130822, \"兴隆县\");\n        b.put(130824, \"滦平县\");\n        b.put(130825, \"隆化县\");\n        b.put(130826, \"丰宁满族自治县\");\n        b.put(130827, \"宽城满族自治县\");\n        b.put(130828, \"围场满族蒙古族自治县\");\n        b.put(130881, \"平泉市\");\n        b.put(130900, \"沧州市\");\n        b.put(130902, \"新华区\");\n        b.put(130903, \"运河区\");\n        b.put(130921, \"沧县\");\n        b.put(130922, \"青县\");\n        b.put(130923, \"东光县\");\n        b.put(130924, \"海兴县\");\n        b.put(130925, \"盐山县\");\n        b.put(130926, \"肃宁县\");\n        b.put(130927, \"南皮县\");\n        b.put(130928, \"吴桥县\");\n        b.put(130929, \"献县\");\n        b.put(130930, \"孟村回族自治县\");\n        b.put(130981, \"泊头市\");\n        b.put(130982, \"任丘市\");\n        b.put(130983, \"黄骅市\");\n        b.put(130984, \"河间市\");\n        b.put(131000, \"廊坊市\");\n        b.put(131002, \"安次区\");\n        b.put(131003, \"广阳区\");\n        b.put(131022, \"固安县\");\n        b.put(131023, \"永清县\");\n        b.put(131024, \"香河县\");\n        b.put(131025, \"大城县\");\n        b.put(131026, \"文安县\");\n        b.put(131028, \"大厂回族自治县\");\n        b.put(131081, \"霸州市\");\n        b.put(131082, \"三河市\");\n        b.put(131100, \"衡水市\");\n        b.put(131102, \"桃城区\");\n        b.put(131103, \"冀州区\");\n        b.put(131121, \"枣强县\");\n        b.put(131122, \"武邑县\");\n        b.put(131123, \"武强县\");\n        b.put(131124, \"饶阳县\");\n        b.put(131125, \"安平县\");\n        b.put(131126, \"故城县\");\n        b.put(131127, \"景县\");\n        b.put(131128, \"阜城县\");\n        b.put(131182, \"深州市\");\n        b.put(140000, \"山西省\");\n        b.put(140100, \"太原市\");\n        b.put(140105, \"小店区\");\n        b.put(140106, \"迎泽区\");\n        b.put(140107, \"杏花岭区\");\n        b.put(140108, \"尖草坪区\");\n        b.put(140109, \"万柏林区\");\n        b.put(140110, \"晋源区\");\n        b.put(140121, \"清徐县\");\n        b.put(140122, \"阳曲县\");\n        b.put(140123, \"娄烦县\");\n        b.put(140181, \"古交市\");\n        b.put(140200, \"大同市\");\n        b.put(140212, \"新荣区\");\n        b.put(140213, \"平城区\");\n        b.put(140214, \"云冈区\");\n        b.put(140215, \"云州区\");\n        b.put(140221, \"阳高县\");\n        b.put(140222, \"天镇县\");\n        b.put(140223, \"广灵县\");\n        b.put(140224, \"灵丘县\");\n        b.put(140225, \"浑源县\");\n        b.put(140226, \"左云县\");\n        b.put(140300, \"阳泉市\");\n        b.put(140302, \"城区\");\n        b.put(140303, \"矿区\");\n        b.put(140311, \"郊区\");\n        b.put(140321, \"平定县\");\n        b.put(140322, \"盂县\");\n        b.put(140400, \"长治市\");\n        b.put(140402, \"城区\");\n        b.put(140411, \"郊区\");\n        b.put(140421, \"长治县\");\n        b.put(140423, \"襄垣县\");\n        b.put(140424, \"屯留县\");\n        b.put(140425, \"平顺县\");\n        b.put(140426, \"黎城县\");\n        b.put(140427, \"壶关县\");\n        b.put(140428, \"长子县\");\n        b.put(140429, \"武乡县\");\n        b.put(140430, \"沁县\");\n        b.put(140431, \"沁源县\");\n        b.put(140481, \"潞城市\");\n        b.put(140500, \"晋城市\");\n        b.put(140502, \"城区\");\n        b.put(140521, \"沁水县\");\n        b.put(140522, \"阳城县\");\n        b.put(140524, \"陵川县\");\n        b.put(140525, \"泽州县\");\n        b.put(140581, \"高平市\");\n        b.put(140600, \"朔州市\");\n        b.put(140602, \"朔城区\");\n        b.put(140603, \"平鲁区\");\n        b.put(140621, \"山阴县\");\n        b.put(140622, \"应县\");\n        b.put(140623, \"右玉县\");\n        b.put(140681, \"怀仁市\");\n        b.put(140700, \"晋中市\");\n        b.put(140702, \"榆次区\");\n        b.put(140721, \"榆社县\");\n        b.put(140722, \"左权县\");\n        b.put(140723, \"和顺县\");\n        b.put(140724, \"昔阳县\");\n        b.put(140725, \"寿阳县\");\n        b.put(140726, \"太谷县\");\n        b.put(140727, \"祁县\");\n        b.put(140728, \"平遥县\");\n        b.put(140729, \"灵石县\");\n        b.put(140781, \"介休市\");\n        b.put(140800, \"运城市\");\n        b.put(140802, \"盐湖区\");\n        b.put(140821, \"临猗县\");\n        b.put(140822, \"万荣县\");\n        b.put(140823, \"闻喜县\");\n        b.put(140824, \"稷山县\");\n        b.put(140825, \"新绛县\");\n        b.put(140826, \"绛县\");\n        b.put(140827, \"垣曲县\");\n        b.put(140828, \"夏县\");\n        b.put(140829, \"平陆县\");\n        b.put(140830, \"芮城县\");\n        b.put(140881, \"永济市\");\n        b.put(140882, \"河津市\");\n        b.put(140900, \"忻州市\");\n        b.put(140902, \"忻府区\");\n        b.put(140921, \"定襄县\");\n        b.put(140922, \"五台县\");\n        b.put(140923, \"代县\");\n        b.put(140924, \"繁峙县\");\n        b.put(140925, \"宁武县\");\n        b.put(140926, \"静乐县\");\n        b.put(140927, \"神池县\");\n        b.put(140928, \"五寨县\");\n        b.put(140929, \"岢岚县\");\n        b.put(140930, \"河曲县\");\n        b.put(140931, \"保德县\");\n        b.put(140932, \"偏关县\");\n        b.put(140981, \"原平市\");\n        b.put(141000, \"临汾市\");\n        b.put(141002, \"尧都区\");\n        b.put(141021, \"曲沃县\");\n        b.put(141022, \"翼城县\");\n        b.put(141023, \"襄汾县\");\n        b.put(141024, \"洪洞县\");\n        b.put(141025, \"古县\");\n        b.put(141026, \"安泽县\");\n        b.put(141027, \"浮山县\");\n        b.put(141028, \"吉县\");\n        b.put(141029, \"乡宁县\");\n        b.put(141030, \"大宁县\");\n        b.put(141031, \"隰县\");\n        b.put(141032, \"永和县\");\n        b.put(141033, \"蒲县\");\n        b.put(141034, \"汾西县\");\n        b.put(141081, \"侯马市\");\n        b.put(141082, \"霍州市\");\n        b.put(141100, \"吕梁市\");\n        b.put(141102, \"离石区\");\n        b.put(141121, \"文水县\");\n        b.put(141122, \"交城县\");\n        b.put(141123, \"兴县\");\n        b.put(141124, \"临县\");\n        b.put(141125, \"柳林县\");\n        b.put(141126, \"石楼县\");\n        b.put(141127, \"岚县\");\n        b.put(141128, \"方山县\");\n        b.put(141129, \"中阳县\");\n        b.put(141130, \"交口县\");\n        b.put(141181, \"孝义市\");\n        b.put(141182, \"汾阳市\");\n        b.put(150000, \"内蒙古自治区\");\n        b.put(150100, \"呼和浩特市\");\n        b.put(150102, \"新城区\");\n        b.put(150103, \"回民区\");\n        b.put(150104, \"玉泉区\");\n        b.put(150105, \"赛罕区\");\n        b.put(150121, \"土默特左旗\");\n        b.put(150122, \"托克托县\");\n        b.put(150123, \"和林格尔县\");\n        b.put(150124, \"清水河县\");\n        b.put(150125, \"武川县\");\n        b.put(150200, \"包头市\");\n        b.put(150202, \"东河区\");\n        b.put(150203, \"昆都仑区\");\n        b.put(150204, \"青山区\");\n        b.put(150205, \"石拐区\");\n        b.put(150206, \"白云鄂博矿区\");\n        b.put(150207, \"九原区\");\n        b.put(150221, \"土默特右旗\");\n        b.put(150222, \"固阳县\");\n        b.put(150223, \"达尔罕茂明安联合旗\");\n        b.put(150300, \"乌海市\");\n        b.put(150302, \"海勃湾区\");\n        b.put(150303, \"海南区\");\n        b.put(150304, \"乌达区\");\n        b.put(150400, \"赤峰市\");\n        b.put(150402, \"红山区\");\n        b.put(150403, \"元宝山区\");\n        b.put(150404, \"松山区\");\n        b.put(150421, \"阿鲁科尔沁旗\");\n        b.put(150422, \"巴林左旗\");\n        b.put(150423, \"巴林右旗\");\n        b.put(150424, \"林西县\");\n        b.put(150425, \"克什克腾旗\");\n        b.put(150426, \"翁牛特旗\");\n        b.put(150428, \"喀喇沁旗\");\n        b.put(150429, \"宁城县\");\n        b.put(150430, \"敖汉旗\");\n        b.put(150500, \"通辽市\");\n        b.put(150502, \"科尔沁区\");\n        b.put(150521, \"科尔沁左翼中旗\");\n        b.put(150522, \"科尔沁左翼后旗\");\n        b.put(150523, \"开鲁县\");\n        b.put(150524, \"库伦旗\");\n        b.put(150525, \"奈曼旗\");\n        b.put(150526, \"扎鲁特旗\");\n        b.put(150581, \"霍林郭勒市\");\n        b.put(150600, \"鄂尔多斯市\");\n        b.put(150602, \"东胜区\");\n        b.put(150603, \"康巴什区\");\n        b.put(150621, \"达拉特旗\");\n        b.put(150622, \"准格尔旗\");\n        b.put(150623, \"鄂托克前旗\");\n        b.put(150624, \"鄂托克旗\");\n        b.put(150625, \"杭锦旗\");\n        b.put(150626, \"乌审旗\");\n        b.put(150627, \"伊金霍洛旗\");\n        b.put(150700, \"呼伦贝尔市\");\n        b.put(150702, \"海拉尔区\");\n        b.put(150703, \"扎赉诺尔区\");\n        b.put(150721, \"阿荣旗\");\n        b.put(150722, \"莫力达瓦达斡尔族自治旗\");\n        b.put(150723, \"鄂伦春自治旗\");\n        b.put(150724, \"鄂温克族自治旗\");\n        b.put(150725, \"陈巴尔虎旗\");\n        b.put(150726, \"新巴尔虎左旗\");\n        b.put(150727, \"新巴尔虎右旗\");\n        b.put(150781, \"满洲里市\");\n        b.put(150782, \"牙克石市\");\n        b.put(150783, \"扎兰屯市\");\n        b.put(150784, \"额尔古纳市\");\n        b.put(150785, \"根河市\");\n        b.put(150800, \"巴彦淖尔市\");\n        b.put(150802, \"临河区\");\n        b.put(150821, \"五原县\");\n        b.put(150822, \"磴口县\");\n        b.put(150823, \"乌拉特前旗\");\n        b.put(150824, \"乌拉特中旗\");\n        b.put(150825, \"乌拉特后旗\");\n        b.put(150826, \"杭锦后旗\");\n        b.put(150900, \"乌兰察布市\");\n        b.put(150902, \"集宁区\");\n        b.put(150921, \"卓资县\");\n        b.put(150922, \"化德县\");\n        b.put(150923, \"商都县\");\n        b.put(150924, \"兴和县\");\n        b.put(150925, \"凉城县\");\n        b.put(150926, \"察哈尔右翼前旗\");\n        b.put(150927, \"察哈尔右翼中旗\");\n        b.put(150928, \"察哈尔右翼后旗\");\n        b.put(150929, \"四子王旗\");\n        b.put(150981, \"丰镇市\");\n        b.put(152200, \"兴安盟\");\n        b.put(152201, \"乌兰浩特市\");\n        b.put(152202, \"阿尔山市\");\n        b.put(152221, \"科尔沁右翼前旗\");\n        b.put(152222, \"科尔沁右翼中旗\");\n        b.put(152223, \"扎赉特旗\");\n        b.put(152224, \"突泉县\");\n        b.put(152500, \"锡林郭勒盟\");\n        b.put(152501, \"二连浩特市\");\n        b.put(152502, \"锡林浩特市\");\n        b.put(152522, \"阿巴嘎旗\");\n        b.put(152523, \"苏尼特左旗\");\n        b.put(152524, \"苏尼特右旗\");\n        b.put(152525, \"东乌珠穆沁旗\");\n        b.put(152526, \"西乌珠穆沁旗\");\n        b.put(152527, \"太仆寺旗\");\n        b.put(152528, \"镶黄旗\");\n        b.put(152529, \"正镶白旗\");\n        b.put(152530, \"正蓝旗\");\n        b.put(152531, \"多伦县\");\n        b.put(152900, \"阿拉善盟\");\n        b.put(152921, \"阿拉善左旗\");\n        b.put(152922, \"阿拉善右旗\");\n        b.put(152923, \"额济纳旗\");\n        b.put(210000, \"辽宁省\");\n        b.put(210100, \"沈阳市\");\n        b.put(210102, \"和平区\");\n        b.put(210103, \"沈河区\");\n        b.put(210104, \"大东区\");\n        b.put(210105, \"皇姑区\");\n        b.put(210106, \"铁西区\");\n        b.put(210111, \"苏家屯区\");\n        b.put(210112, \"浑南区\");\n        b.put(210113, \"沈北新区\");\n        b.put(210114, \"于洪区\");\n        b.put(210115, \"辽中区\");\n        b.put(210123, \"康平县\");\n        b.put(210124, \"法库县\");\n        b.put(210181, \"新民市\");\n        b.put(210200, \"大连市\");\n        b.put(210202, \"中山区\");\n        b.put(210203, \"西岗区\");\n        b.put(210204, \"沙河口区\");\n        b.put(210211, \"甘井子区\");\n        b.put(210212, \"旅顺口区\");\n        b.put(210213, \"金州区\");\n        b.put(210214, \"普兰店区\");\n        b.put(210224, \"长海县\");\n        b.put(210281, \"瓦房店市\");\n        b.put(210283, \"庄河市\");\n        b.put(210300, \"鞍山市\");\n        b.put(210302, \"铁东区\");\n        b.put(210303, \"铁西区\");\n        b.put(210304, \"立山区\");\n        b.put(210311, \"千山区\");\n        b.put(210321, \"台安县\");\n        b.put(210323, \"岫岩满族自治县\");\n        b.put(210381, \"海城市\");\n        b.put(210400, \"抚顺市\");\n        b.put(210402, \"新抚区\");\n        b.put(210403, \"东洲区\");\n        b.put(210404, \"望花区\");\n        b.put(210411, \"顺城区\");\n        b.put(210421, \"抚顺县\");\n        b.put(210422, \"新宾满族自治县\");\n        b.put(210423, \"清原满族自治县\");\n        b.put(210500, \"本溪市\");\n        b.put(210502, \"平山区\");\n        b.put(210503, \"溪湖区\");\n        b.put(210504, \"明山区\");\n        b.put(210505, \"南芬区\");\n        b.put(210521, \"本溪满族自治县\");\n        b.put(210522, \"桓仁满族自治县\");\n        b.put(210600, \"丹东市\");\n        b.put(210602, \"元宝区\");\n        b.put(210603, \"振兴区\");\n        b.put(210604, \"振安区\");\n        b.put(210624, \"宽甸满族自治县\");\n        b.put(210681, \"东港市\");\n        b.put(210682, \"凤城市\");\n        b.put(210700, \"锦州市\");\n        b.put(210702, \"古塔区\");\n        b.put(210703, \"凌河区\");\n        b.put(210711, \"太和区\");\n        b.put(210726, \"黑山县\");\n        b.put(210727, \"义县\");\n        b.put(210781, \"凌海市\");\n        b.put(210782, \"北镇市\");\n        b.put(210800, \"营口市\");\n        b.put(210802, \"站前区\");\n        b.put(210803, \"西市区\");\n        b.put(210804, \"鲅鱼圈区\");\n        b.put(210811, \"老边区\");\n        b.put(210881, \"盖州市\");\n        b.put(210882, \"大石桥市\");\n        b.put(210900, \"阜新市\");\n        b.put(210902, \"海州区\");\n        b.put(210903, \"新邱区\");\n        b.put(210904, \"太平区\");\n        b.put(210905, \"清河门区\");\n        b.put(210911, \"细河区\");\n        b.put(210921, \"阜新蒙古族自治县\");\n        b.put(210922, \"彰武县\");\n        b.put(211000, \"辽阳市\");\n        b.put(211002, \"白塔区\");\n        b.put(211003, \"文圣区\");\n        b.put(211004, \"宏伟区\");\n        b.put(211005, \"弓长岭区\");\n        b.put(211011, \"太子河区\");\n        b.put(211021, \"辽阳县\");\n        b.put(211081, \"灯塔市\");\n        b.put(211100, \"盘锦市\");\n        b.put(211102, \"双台子区\");\n        b.put(211103, \"兴隆台区\");\n        b.put(211104, \"大洼区\");\n        b.put(211122, \"盘山县\");\n        b.put(211200, \"铁岭市\");\n        b.put(211202, \"银州区\");\n        b.put(211204, \"清河区\");\n        b.put(211221, \"铁岭县\");\n        b.put(211223, \"西丰县\");\n        b.put(211224, \"昌图县\");\n        b.put(211281, \"调兵山市\");\n        b.put(211282, \"开原市\");\n        b.put(211300, \"朝阳市\");\n        b.put(211302, \"双塔区\");\n        b.put(211303, \"龙城区\");\n        b.put(211321, \"朝阳县\");\n        b.put(211322, \"建平县\");\n        b.put(211324, \"喀喇沁左翼蒙古族自治县\");\n        b.put(211381, \"北票市\");\n        b.put(211382, \"凌源市\");\n        b.put(211400, \"葫芦岛市\");\n        b.put(211402, \"连山区\");\n        b.put(211403, \"龙港区\");\n        b.put(211404, \"南票区\");\n        b.put(211421, \"绥中县\");\n        b.put(211422, \"建昌县\");\n        b.put(211481, \"兴城市\");\n        b.put(220000, \"吉林省\");\n        b.put(220100, \"长春市\");\n        b.put(220102, \"南关区\");\n        b.put(220103, \"宽城区\");\n        b.put(220104, \"朝阳区\");\n        b.put(220105, \"二道区\");\n        b.put(220106, \"绿园区\");\n        b.put(220112, \"双阳区\");\n        b.put(220113, \"九台区\");\n        b.put(220122, \"农安县\");\n        b.put(220182, \"榆树市\");\n        b.put(220183, \"德惠市\");\n        b.put(220200, \"吉林市\");\n        b.put(220202, \"昌邑区\");\n        b.put(220203, \"龙潭区\");\n        b.put(220204, \"船营区\");\n        b.put(220211, \"丰满区\");\n        b.put(220221, \"永吉县\");\n        b.put(220281, \"蛟河市\");\n        b.put(220282, \"桦甸市\");\n        b.put(220283, \"舒兰市\");\n        b.put(220284, \"磐石市\");\n        b.put(220300, \"四平市\");\n        b.put(220302, \"铁西区\");\n        b.put(220303, \"铁东区\");\n        b.put(220322, \"梨树县\");\n        b.put(220323, \"伊通满族自治县\");\n        b.put(220381, \"公主岭市\");\n        b.put(220382, \"双辽市\");\n        b.put(220400, \"辽源市\");\n        b.put(220402, \"龙山区\");\n        b.put(220403, \"西安区\");\n        b.put(220421, \"东丰县\");\n        b.put(220422, \"东辽县\");\n        b.put(220500, \"通化市\");\n        b.put(220502, \"东昌区\");\n        b.put(220503, \"二道江区\");\n        b.put(220521, \"通化县\");\n        b.put(220523, \"辉南县\");\n        b.put(220524, \"柳河县\");\n        b.put(220581, \"梅河口市\");\n        b.put(220582, \"集安市\");\n        b.put(220600, \"白山市\");\n        b.put(220602, \"浑江区\");\n        b.put(220605, \"江源区\");\n        b.put(220621, \"抚松县\");\n        b.put(220622, \"靖宇县\");\n        b.put(220623, \"长白朝鲜族自治县\");\n        b.put(220681, \"临江市\");\n        b.put(220700, \"松原市\");\n        b.put(220702, \"宁江区\");\n        b.put(220721, \"前郭尔罗斯蒙古族自治县\");\n        b.put(220722, \"长岭县\");\n        b.put(220723, \"乾安县\");\n        b.put(220781, \"扶余市\");\n        b.put(220800, \"白城市\");\n        b.put(220802, \"洮北区\");\n        b.put(220821, \"镇赉县\");\n        b.put(220822, \"通榆县\");\n        b.put(220881, \"洮南市\");\n        b.put(220882, \"大安市\");\n        b.put(222400, \"延边朝鲜族自治州\");\n        b.put(222401, \"延吉市\");\n        b.put(222402, \"图们市\");\n        b.put(222403, \"敦化市\");\n        b.put(222404, \"珲春市\");\n        b.put(222405, \"龙井市\");\n        b.put(222406, \"和龙市\");\n        b.put(222424, \"汪清县\");\n        b.put(222426, \"安图县\");\n        b.put(230000, \"黑龙江省\");\n        b.put(230100, \"哈尔滨市\");\n        b.put(230102, \"道里区\");\n        b.put(230103, \"南岗区\");\n        b.put(230104, \"道外区\");\n        b.put(230108, \"平房区\");\n        b.put(230109, \"松北区\");\n        b.put(230110, \"香坊区\");\n        b.put(230111, \"呼兰区\");\n        b.put(230112, \"阿城区\");\n        b.put(230113, \"双城区\");\n        b.put(230123, \"依兰县\");\n        b.put(230124, \"方正县\");\n        b.put(230125, \"宾县\");\n        b.put(230126, \"巴彦县\");\n        b.put(230127, \"木兰县\");\n        b.put(230128, \"通河县\");\n        b.put(230129, \"延寿县\");\n        b.put(230183, \"尚志市\");\n        b.put(230184, \"五常市\");\n        b.put(230200, \"齐齐哈尔市\");\n        b.put(230202, \"龙沙区\");\n        b.put(230203, \"建华区\");\n        b.put(230204, \"铁锋区\");\n        b.put(230205, \"昂昂溪区\");\n        b.put(230206, \"富拉尔基区\");\n        b.put(230207, \"碾子山区\");\n        b.put(230208, \"梅里斯达斡尔族区\");\n        b.put(230221, \"龙江县\");\n        b.put(230223, \"依安县\");\n        b.put(230224, \"泰来县\");\n        b.put(230225, \"甘南县\");\n        b.put(230227, \"富裕县\");\n        b.put(230229, \"克山县\");\n        b.put(230230, \"克东县\");\n        b.put(230231, \"拜泉县\");\n        b.put(230281, \"讷河市\");\n        b.put(230300, \"鸡西市\");\n        b.put(230302, \"鸡冠区\");\n        b.put(230303, \"恒山区\");\n        b.put(230304, \"滴道区\");\n        b.put(230305, \"梨树区\");\n        b.put(230306, \"城子河区\");\n        b.put(230307, \"麻山区\");\n        b.put(230321, \"鸡东县\");\n        b.put(230381, \"虎林市\");\n        b.put(230382, \"密山市\");\n        b.put(230400, \"鹤岗市\");\n        b.put(230402, \"向阳区\");\n        b.put(230403, \"工农区\");\n        b.put(230404, \"南山区\");\n        b.put(230405, \"兴安区\");\n        b.put(230406, \"东山区\");\n        b.put(230407, \"兴山区\");\n        b.put(230421, \"萝北县\");\n        b.put(230422, \"绥滨县\");\n        b.put(230500, \"双鸭山市\");\n        b.put(230502, \"尖山区\");\n        b.put(230503, \"岭东区\");\n        b.put(230505, \"四方台区\");\n        b.put(230506, \"宝山区\");\n        b.put(230521, \"集贤县\");\n        b.put(230522, \"友谊县\");\n        b.put(230523, \"宝清县\");\n        b.put(230524, \"饶河县\");\n        b.put(230600, \"大庆市\");\n        b.put(230602, \"萨尔图区\");\n        b.put(230603, \"龙凤区\");\n        b.put(230604, \"让胡路区\");\n        b.put(230605, \"红岗区\");\n        b.put(230606, \"大同区\");\n        b.put(230621, \"肇州县\");\n        b.put(230622, \"肇源县\");\n        b.put(230623, \"林甸县\");\n        b.put(230624, \"杜尔伯特蒙古族自治县\");\n        b.put(230700, \"伊春市\");\n        b.put(230702, \"伊春区\");\n        b.put(230703, \"南岔区\");\n        b.put(230704, \"友好区\");\n        b.put(230705, \"西林区\");\n        b.put(230706, \"翠峦区\");\n        b.put(230707, \"新青区\");\n        b.put(230708, \"美溪区\");\n        b.put(230709, \"金山屯区\");\n        b.put(230710, \"五营区\");\n        b.put(230711, \"乌马河区\");\n        b.put(230712, \"汤旺河区\");\n        b.put(230713, \"带岭区\");\n        b.put(230714, \"乌伊岭区\");\n        b.put(230715, \"红星区\");\n        b.put(230716, \"上甘岭区\");\n        b.put(230722, \"嘉荫县\");\n        b.put(230781, \"铁力市\");\n        b.put(230800, \"佳木斯市\");\n        b.put(230803, \"向阳区\");\n        b.put(230804, \"前进区\");\n        b.put(230805, \"东风区\");\n        b.put(230811, \"郊区\");\n        b.put(230822, \"桦南县\");\n        b.put(230826, \"桦川县\");\n        b.put(230828, \"汤原县\");\n        b.put(230881, \"同江市\");\n        b.put(230882, \"富锦市\");\n        b.put(230883, \"抚远市\");\n        b.put(230900, \"七台河市\");\n        b.put(230902, \"新兴区\");\n        b.put(230903, \"桃山区\");\n        b.put(230904, \"茄子河区\");\n        b.put(230921, \"勃利县\");\n        b.put(231000, \"牡丹江市\");\n        b.put(231002, \"东安区\");\n        b.put(231003, \"阳明区\");\n        b.put(231004, \"爱民区\");\n        b.put(231005, \"西安区\");\n        b.put(231025, \"林口县\");\n        b.put(231081, \"绥芬河市\");\n        b.put(231083, \"海林市\");\n        b.put(231084, \"宁安市\");\n        b.put(231085, \"穆棱市\");\n        b.put(231086, \"东宁市\");\n        b.put(231100, \"黑河市\");\n        b.put(231102, \"爱辉区\");\n        b.put(231121, \"嫩江县\");\n        b.put(231123, \"逊克县\");\n        b.put(231124, \"孙吴县\");\n        b.put(231181, \"北安市\");\n        b.put(231182, \"五大连池市\");\n        b.put(231200, \"绥化市\");\n        b.put(231202, \"北林区\");\n        b.put(231221, \"望奎县\");\n        b.put(231222, \"兰西县\");\n        b.put(231223, \"青冈县\");\n        b.put(231224, \"庆安县\");\n        b.put(231225, \"明水县\");\n        b.put(231226, \"绥棱县\");\n        b.put(231281, \"安达市\");\n        b.put(231282, \"肇东市\");\n        b.put(231283, \"海伦市\");\n        b.put(232700, \"大兴安岭地区\");\n        b.put(232701, \"漠河市\");\n        b.put(232721, \"呼玛县\");\n        b.put(232722, \"塔河县\");\n        b.put(310000, \"上海市\");\n        b.put(310101, \"黄浦区\");\n        b.put(310104, \"徐汇区\");\n        b.put(310105, \"长宁区\");\n        b.put(310106, \"静安区\");\n        b.put(310107, \"普陀区\");\n        b.put(310109, \"虹口区\");\n        b.put(310110, \"杨浦区\");\n        b.put(310112, \"闵行区\");\n        b.put(310113, \"宝山区\");\n        b.put(310114, \"嘉定区\");\n        b.put(310115, \"浦东新区\");\n        b.put(310116, \"金山区\");\n        b.put(310117, \"松江区\");\n        b.put(310118, \"青浦区\");\n        b.put(310120, \"奉贤区\");\n        b.put(310151, \"崇明区\");\n        b.put(320000, \"江苏省\");\n        b.put(320100, \"南京市\");\n        b.put(320102, \"玄武区\");\n        b.put(320104, \"秦淮区\");\n        b.put(320105, \"建邺区\");\n        b.put(320106, \"鼓楼区\");\n        b.put(320111, \"浦口区\");\n        b.put(320113, \"栖霞区\");\n        b.put(320114, \"雨花台区\");\n        b.put(320115, \"江宁区\");\n        b.put(320116, \"六合区\");\n        b.put(320117, \"溧水区\");\n        b.put(320118, \"高淳区\");\n        b.put(320200, \"无锡市\");\n        b.put(320205, \"锡山区\");\n        b.put(320206, \"惠山区\");\n        b.put(320211, \"滨湖区\");\n        b.put(320213, \"梁溪区\");\n        b.put(320214, \"新吴区\");\n        b.put(320281, \"江阴市\");\n        b.put(320282, \"宜兴市\");\n        b.put(320300, \"徐州市\");\n        b.put(320302, \"鼓楼区\");\n        b.put(320303, \"云龙区\");\n        b.put(320305, \"贾汪区\");\n        b.put(320311, \"泉山区\");\n        b.put(320312, \"铜山区\");\n        b.put(320321, \"丰县\");\n        b.put(320322, \"沛县\");\n        b.put(320324, \"睢宁县\");\n        b.put(320381, \"新沂市\");\n        b.put(320382, \"邳州市\");\n        b.put(320400, \"常州市\");\n        b.put(320402, \"天宁区\");\n        b.put(320404, \"钟楼区\");\n        b.put(320411, \"新北区\");\n        b.put(320412, \"武进区\");\n        b.put(320413, \"金坛区\");\n        b.put(320481, \"溧阳市\");\n        b.put(320500, \"苏州市\");\n        b.put(320505, \"虎丘区\");\n        b.put(320506, \"吴中区\");\n        b.put(320507, \"相城区\");\n        b.put(320508, \"姑苏区\");\n        b.put(320509, \"吴江区\");\n        b.put(320581, \"常熟市\");\n        b.put(320582, \"张家港市\");\n        b.put(320583, \"昆山市\");\n        b.put(320585, \"太仓市\");\n        b.put(320600, \"南通市\");\n        b.put(320602, \"崇川区\");\n        b.put(320611, \"港闸区\");\n        b.put(320612, \"通州区\");\n        b.put(320623, \"如东县\");\n        b.put(320681, \"启东市\");\n        b.put(320682, \"如皋市\");\n        b.put(320684, \"海门市\");\n        b.put(320685, \"海安市\");\n        b.put(320700, \"连云港市\");\n        b.put(320703, \"连云区\");\n        b.put(320706, \"海州区\");\n        b.put(320707, \"赣榆区\");\n        b.put(320722, \"东海县\");\n        b.put(320723, \"灌云县\");\n        b.put(320724, \"灌南县\");\n        b.put(320800, \"淮安市\");\n        b.put(320803, \"淮安区\");\n        b.put(320804, \"淮阴区\");\n        b.put(320812, \"清江浦区\");\n        b.put(320813, \"洪泽区\");\n        b.put(320826, \"涟水县\");\n        b.put(320830, \"盱眙县\");\n        b.put(320831, \"金湖县\");\n        b.put(320900, \"盐城市\");\n        b.put(320902, \"亭湖区\");\n        b.put(320903, \"盐都区\");\n        b.put(320904, \"大丰区\");\n        b.put(320921, \"响水县\");\n        b.put(320922, \"滨海县\");\n        b.put(320923, \"阜宁县\");\n        b.put(320924, \"射阳县\");\n        b.put(320925, \"建湖县\");\n        b.put(320981, \"东台市\");\n        b.put(321000, \"扬州市\");\n        b.put(321002, \"广陵区\");\n        b.put(321003, \"邗江区\");\n        b.put(321012, \"江都区\");\n        b.put(321023, \"宝应县\");\n        b.put(321081, \"仪征市\");\n        b.put(321084, \"高邮市\");\n        b.put(321100, \"镇江市\");\n        b.put(321102, \"京口区\");\n        b.put(321111, \"润州区\");\n        b.put(321112, \"丹徒区\");\n        b.put(321181, \"丹阳市\");\n        b.put(321182, \"扬中市\");\n        b.put(321183, \"句容市\");\n        b.put(321200, \"泰州市\");\n        b.put(321202, \"海陵区\");\n        b.put(321203, \"高港区\");\n        b.put(321204, \"姜堰区\");\n        b.put(321281, \"兴化市\");\n        b.put(321282, \"靖江市\");\n        b.put(321283, \"泰兴市\");\n        b.put(321300, \"宿迁市\");\n        b.put(321302, \"宿城区\");\n        b.put(321311, \"宿豫区\");\n        b.put(321322, \"沭阳县\");\n        b.put(321323, \"泗阳县\");\n        b.put(321324, \"泗洪县\");\n        b.put(330000, \"浙江省\");\n        b.put(330100, \"杭州市\");\n        b.put(330102, \"上城区\");\n        b.put(330103, \"下城区\");\n        b.put(330104, \"江干区\");\n        b.put(330105, \"拱墅区\");\n        b.put(330106, \"西湖区\");\n        b.put(330108, \"滨江区\");\n        b.put(330109, \"萧山区\");\n        b.put(330110, \"余杭区\");\n        b.put(330111, \"富阳区\");\n        b.put(330112, \"临安区\");\n        b.put(330122, \"桐庐县\");\n        b.put(330127, \"淳安县\");\n        b.put(330182, \"建德市\");\n        b.put(330200, \"宁波市\");\n        b.put(330203, \"海曙区\");\n        b.put(330205, \"江北区\");\n        b.put(330206, \"北仑区\");\n        b.put(330211, \"镇海区\");\n        b.put(330212, \"鄞州区\");\n        b.put(330213, \"奉化区\");\n        b.put(330225, \"象山县\");\n        b.put(330226, \"宁海县\");\n        b.put(330281, \"余姚市\");\n        b.put(330282, \"慈溪市\");\n        b.put(330300, \"温州市\");\n        b.put(330302, \"鹿城区\");\n        b.put(330303, \"龙湾区\");\n        b.put(330304, \"瓯海区\");\n        b.put(330305, \"洞头区\");\n        b.put(330324, \"永嘉县\");\n        b.put(330326, \"平阳县\");\n        b.put(330327, \"苍南县\");\n        b.put(330328, \"文成县\");\n        b.put(330329, \"泰顺县\");\n        b.put(330381, \"瑞安市\");\n        b.put(330382, \"乐清市\");\n        b.put(330400, \"嘉兴市\");\n        b.put(330402, \"南湖区\");\n        b.put(330411, \"秀洲区\");\n        b.put(330421, \"嘉善县\");\n        b.put(330424, \"海盐县\");\n        b.put(330481, \"海宁市\");\n        b.put(330482, \"平湖市\");\n        b.put(330483, \"桐乡市\");\n        b.put(330500, \"湖州市\");\n        b.put(330502, \"吴兴区\");\n        b.put(330503, \"南浔区\");\n        b.put(330521, \"德清县\");\n        b.put(330522, \"长兴县\");\n        b.put(330523, \"安吉县\");\n        b.put(330600, \"绍兴市\");\n        b.put(330602, \"越城区\");\n        b.put(330603, \"柯桥区\");\n        b.put(330604, \"上虞区\");\n        b.put(330624, \"新昌县\");\n        b.put(330681, \"诸暨市\");\n        b.put(330683, \"嵊州市\");\n        b.put(330700, \"金华市\");\n        b.put(330702, \"婺城区\");\n        b.put(330703, \"金东区\");\n        b.put(330723, \"武义县\");\n        b.put(330726, \"浦江县\");\n        b.put(330727, \"磐安县\");\n        b.put(330781, \"兰溪市\");\n        b.put(330782, \"义乌市\");\n        b.put(330783, \"东阳市\");\n        b.put(330784, \"永康市\");\n        b.put(330800, \"衢州市\");\n        b.put(330802, \"柯城区\");\n        b.put(330803, \"衢江区\");\n        b.put(330822, \"常山县\");\n        b.put(330824, \"开化县\");\n        b.put(330825, \"龙游县\");\n        b.put(330881, \"江山市\");\n        b.put(330900, \"舟山市\");\n        b.put(330902, \"定海区\");\n        b.put(330903, \"普陀区\");\n        b.put(330921, \"岱山县\");\n        b.put(330922, \"嵊泗县\");\n        b.put(331000, \"台州市\");\n        b.put(331002, \"椒江区\");\n        b.put(331003, \"黄岩区\");\n        b.put(331004, \"路桥区\");\n        b.put(331022, \"三门县\");\n        b.put(331023, \"天台县\");\n        b.put(331024, \"仙居县\");\n        b.put(331081, \"温岭市\");\n        b.put(331082, \"临海市\");\n        b.put(331083, \"玉环市\");\n        b.put(331100, \"丽水市\");\n        b.put(331102, \"莲都区\");\n        b.put(331121, \"青田县\");\n        b.put(331122, \"缙云县\");\n        b.put(331123, \"遂昌县\");\n        b.put(331124, \"松阳县\");\n        b.put(331125, \"云和县\");\n        b.put(331126, \"庆元县\");\n        b.put(331127, \"景宁畲族自治县\");\n        b.put(331181, \"龙泉市\");\n        b.put(340000, \"安徽省\");\n        b.put(340100, \"合肥市\");\n        b.put(340102, \"瑶海区\");\n        b.put(340103, \"庐阳区\");\n        b.put(340104, \"蜀山区\");\n        b.put(340111, \"包河区\");\n        b.put(340121, \"长丰县\");\n        b.put(340122, \"肥东县\");\n        b.put(340123, \"肥西县\");\n        b.put(340124, \"庐江县\");\n        b.put(340181, \"巢湖市\");\n        b.put(340200, \"芜湖市\");\n        b.put(340202, \"镜湖区\");\n        b.put(340203, \"弋江区\");\n        b.put(340207, \"鸠江区\");\n        b.put(340208, \"三山区\");\n        b.put(340221, \"芜湖县\");\n        b.put(340222, \"繁昌县\");\n        b.put(340223, \"南陵县\");\n        b.put(340225, \"无为县\");\n        b.put(340300, \"蚌埠市\");\n        b.put(340302, \"龙子湖区\");\n        b.put(340303, \"蚌山区\");\n        b.put(340304, \"禹会区\");\n        b.put(340311, \"淮上区\");\n        b.put(340321, \"怀远县\");\n        b.put(340322, \"五河县\");\n        b.put(340323, \"固镇县\");\n        b.put(340400, \"淮南市\");\n        b.put(340402, \"大通区\");\n        b.put(340403, \"田家庵区\");\n        b.put(340404, \"谢家集区\");\n        b.put(340405, \"八公山区\");\n        b.put(340406, \"潘集区\");\n        b.put(340421, \"凤台县\");\n        b.put(340422, \"寿县\");\n        b.put(340500, \"马鞍山市\");\n        b.put(340503, \"花山区\");\n        b.put(340504, \"雨山区\");\n        b.put(340506, \"博望区\");\n        b.put(340521, \"当涂县\");\n        b.put(340522, \"含山县\");\n        b.put(340523, \"和县\");\n        b.put(340600, \"淮北市\");\n        b.put(340602, \"杜集区\");\n        b.put(340603, \"相山区\");\n        b.put(340604, \"烈山区\");\n        b.put(340621, \"濉溪县\");\n        b.put(340700, \"铜陵市\");\n        b.put(340705, \"铜官区\");\n        b.put(340706, \"义安区\");\n        b.put(340711, \"郊区\");\n        b.put(340722, \"枞阳县\");\n        b.put(340800, \"安庆市\");\n        b.put(340802, \"迎江区\");\n        b.put(340803, \"大观区\");\n        b.put(340811, \"宜秀区\");\n        b.put(340822, \"怀宁县\");\n        b.put(340824, \"潜山县\");\n        b.put(340825, \"太湖县\");\n        b.put(340826, \"宿松县\");\n        b.put(340827, \"望江县\");\n        b.put(340828, \"岳西县\");\n        b.put(340881, \"桐城市\");\n        b.put(341000, \"黄山市\");\n        b.put(341002, \"屯溪区\");\n        b.put(341003, \"黄山区\");\n        b.put(341004, \"徽州区\");\n        b.put(341021, \"歙县\");\n        b.put(341022, \"休宁县\");\n        b.put(341023, \"黟县\");\n        b.put(341024, \"祁门县\");\n        b.put(341100, \"滁州市\");\n        b.put(341102, \"琅琊区\");\n        b.put(341103, \"南谯区\");\n        b.put(341122, \"来安县\");\n        b.put(341124, \"全椒县\");\n        b.put(341125, \"定远县\");\n        b.put(341126, \"凤阳县\");\n        b.put(341181, \"天长市\");\n        b.put(341182, \"明光市\");\n        b.put(341200, \"阜阳市\");\n        b.put(341202, \"颍州区\");\n        b.put(341203, \"颍东区\");\n        b.put(341204, \"颍泉区\");\n        b.put(341221, \"临泉县\");\n        b.put(341222, \"太和县\");\n        b.put(341225, \"阜南县\");\n        b.put(341226, \"颍上县\");\n        b.put(341282, \"界首市\");\n        b.put(341300, \"宿州市\");\n        b.put(341302, \"埇桥区\");\n        b.put(341321, \"砀山县\");\n        b.put(341322, \"萧县\");\n        b.put(341323, \"灵璧县\");\n        b.put(341324, \"泗县\");\n        b.put(341500, \"六安市\");\n        b.put(341502, \"金安区\");\n        b.put(341503, \"裕安区\");\n        b.put(341504, \"叶集区\");\n        b.put(341522, \"霍邱县\");\n        b.put(341523, \"舒城县\");\n        b.put(341524, \"金寨县\");\n        b.put(341525, \"霍山县\");\n        b.put(341600, \"亳州市\");\n        b.put(341602, \"谯城区\");\n        b.put(341621, \"涡阳县\");\n        b.put(341622, \"蒙城县\");\n        b.put(341623, \"利辛县\");\n        b.put(341700, \"池州市\");\n        b.put(341702, \"贵池区\");\n        b.put(341721, \"东至县\");\n        b.put(341722, \"石台县\");\n        b.put(341723, \"青阳县\");\n        b.put(341800, \"宣城市\");\n        b.put(341802, \"宣州区\");\n        b.put(341821, \"郎溪县\");\n        b.put(341822, \"广德县\");\n        b.put(341823, \"泾县\");\n        b.put(341824, \"绩溪县\");\n        b.put(341825, \"旌德县\");\n        b.put(341881, \"宁国市\");\n        b.put(350000, \"福建省\");\n        b.put(350100, \"福州市\");\n        b.put(350102, \"鼓楼区\");\n        b.put(350103, \"台江区\");\n        b.put(350104, \"仓山区\");\n        b.put(350105, \"马尾区\");\n        b.put(350111, \"晋安区\");\n        b.put(350112, \"长乐区\");\n        b.put(350121, \"闽侯县\");\n        b.put(350122, \"连江县\");\n        b.put(350123, \"罗源县\");\n        b.put(350124, \"闽清县\");\n        b.put(350125, \"永泰县\");\n        b.put(350128, \"平潭县\");\n        b.put(350181, \"福清市\");\n        b.put(350200, \"厦门市\");\n        b.put(350203, \"思明区\");\n        b.put(350205, \"海沧区\");\n        b.put(350206, \"湖里区\");\n        b.put(350211, \"集美区\");\n        b.put(350212, \"同安区\");\n        b.put(350213, \"翔安区\");\n        b.put(350300, \"莆田市\");\n        b.put(350302, \"城厢区\");\n        b.put(350303, \"涵江区\");\n        b.put(350304, \"荔城区\");\n        b.put(350305, \"秀屿区\");\n        b.put(350322, \"仙游县\");\n        b.put(350400, \"三明市\");\n        b.put(350402, \"梅列区\");\n        b.put(350403, \"三元区\");\n        b.put(350421, \"明溪县\");\n        b.put(350423, \"清流县\");\n        b.put(350424, \"宁化县\");\n        b.put(350425, \"大田县\");\n        b.put(350426, \"尤溪县\");\n        b.put(350427, \"沙县\");\n        b.put(350428, \"将乐县\");\n        b.put(350429, \"泰宁县\");\n        b.put(350430, \"建宁县\");\n        b.put(350481, \"永安市\");\n        b.put(350500, \"泉州市\");\n        b.put(350502, \"鲤城区\");\n        b.put(350503, \"丰泽区\");\n        b.put(350504, \"洛江区\");\n        b.put(350505, \"泉港区\");\n        b.put(350521, \"惠安县\");\n        b.put(350524, \"安溪县\");\n        b.put(350525, \"永春县\");\n        b.put(350526, \"德化县\");\n        b.put(350527, \"金门县\");\n        b.put(350581, \"石狮市\");\n        b.put(350582, \"晋江市\");\n        b.put(350583, \"南安市\");\n        b.put(350600, \"漳州市\");\n        b.put(350602, \"芗城区\");\n        b.put(350603, \"龙文区\");\n        b.put(350622, \"云霄县\");\n        b.put(350623, \"漳浦县\");\n        b.put(350624, \"诏安县\");\n        b.put(350625, \"长泰县\");\n        b.put(350626, \"东山县\");\n        b.put(350627, \"南靖县\");\n        b.put(350628, \"平和县\");\n        b.put(350629, \"华安县\");\n        b.put(350681, \"龙海市\");\n        b.put(350700, \"南平市\");\n        b.put(350702, \"延平区\");\n        b.put(350703, \"建阳区\");\n        b.put(350721, \"顺昌县\");\n        b.put(350722, \"浦城县\");\n        b.put(350723, \"光泽县\");\n        b.put(350724, \"松溪县\");\n        b.put(350725, \"政和县\");\n        b.put(350781, \"邵武市\");\n        b.put(350782, \"武夷山市\");\n        b.put(350783, \"建瓯市\");\n        b.put(350800, \"龙岩市\");\n        b.put(350802, \"新罗区\");\n        b.put(350803, \"永定区\");\n        b.put(350821, \"长汀县\");\n        b.put(350823, \"上杭县\");\n        b.put(350824, \"武平县\");\n        b.put(350825, \"连城县\");\n        b.put(350881, \"漳平市\");\n        b.put(350900, \"宁德市\");\n        b.put(350902, \"蕉城区\");\n        b.put(350921, \"霞浦县\");\n        b.put(350922, \"古田县\");\n        b.put(350923, \"屏南县\");\n        b.put(350924, \"寿宁县\");\n        b.put(350925, \"周宁县\");\n        b.put(350926, \"柘荣县\");\n        b.put(350981, \"福安市\");\n        b.put(350982, \"福鼎市\");\n        b.put(360000, \"江西省\");\n        b.put(360100, \"南昌市\");\n        b.put(360102, \"东湖区\");\n        b.put(360103, \"西湖区\");\n        b.put(360104, \"青云谱区\");\n        b.put(360105, \"湾里区\");\n        b.put(360111, \"青山湖区\");\n        b.put(360112, \"新建区\");\n        b.put(360121, \"南昌县\");\n        b.put(360123, \"安义县\");\n        b.put(360124, \"进贤县\");\n        b.put(360200, \"景德镇市\");\n        b.put(360202, \"昌江区\");\n        b.put(360203, \"珠山区\");\n        b.put(360222, \"浮梁县\");\n        b.put(360281, \"乐平市\");\n        b.put(360300, \"萍乡市\");\n        b.put(360302, \"安源区\");\n        b.put(360313, \"湘东区\");\n        b.put(360321, \"莲花县\");\n        b.put(360322, \"上栗县\");\n        b.put(360323, \"芦溪县\");\n        b.put(360400, \"九江市\");\n        b.put(360402, \"濂溪区\");\n        b.put(360403, \"浔阳区\");\n        b.put(360404, \"柴桑区\");\n        b.put(360423, \"武宁县\");\n        b.put(360424, \"修水县\");\n        b.put(360425, \"永修县\");\n        b.put(360426, \"德安县\");\n        b.put(360428, \"都昌县\");\n        b.put(360429, \"湖口县\");\n        b.put(360430, \"彭泽县\");\n        b.put(360481, \"瑞昌市\");\n        b.put(360482, \"共青城市\");\n        b.put(360483, \"庐山市\");\n        b.put(360500, \"新余市\");\n        b.put(360502, \"渝水区\");\n        b.put(360521, \"分宜县\");\n        b.put(360600, \"鹰潭市\");\n        b.put(360602, \"月湖区\");\n        b.put(360603, \"余江区\");\n        b.put(360681, \"贵溪市\");\n        b.put(360700, \"赣州市\");\n        b.put(360702, \"章贡区\");\n        b.put(360703, \"南康区\");\n        b.put(360704, \"赣县区\");\n        b.put(360722, \"信丰县\");\n        b.put(360723, \"大余县\");\n        b.put(360724, \"上犹县\");\n        b.put(360725, \"崇义县\");\n        b.put(360726, \"安远县\");\n        b.put(360727, \"龙南县\");\n        b.put(360728, \"定南县\");\n        b.put(360729, \"全南县\");\n        b.put(360730, \"宁都县\");\n        b.put(360731, \"于都县\");\n        b.put(360732, \"兴国县\");\n        b.put(360733, \"会昌县\");\n        b.put(360734, \"寻乌县\");\n        b.put(360735, \"石城县\");\n        b.put(360781, \"瑞金市\");\n        b.put(360800, \"吉安市\");\n        b.put(360802, \"吉州区\");\n        b.put(360803, \"青原区\");\n        b.put(360821, \"吉安县\");\n        b.put(360822, \"吉水县\");\n        b.put(360823, \"峡江县\");\n        b.put(360824, \"新干县\");\n        b.put(360825, \"永丰县\");\n        b.put(360826, \"泰和县\");\n        b.put(360827, \"遂川县\");\n        b.put(360828, \"万安县\");\n        b.put(360829, \"安福县\");\n        b.put(360830, \"永新县\");\n        b.put(360881, \"井冈山市\");\n        b.put(360900, \"宜春市\");\n        b.put(360902, \"袁州区\");\n        b.put(360921, \"奉新县\");\n        b.put(360922, \"万载县\");\n        b.put(360923, \"上高县\");\n        b.put(360924, \"宜丰县\");\n        b.put(360925, \"靖安县\");\n        b.put(360926, \"铜鼓县\");\n        b.put(360981, \"丰城市\");\n        b.put(360982, \"樟树市\");\n        b.put(360983, \"高安市\");\n        b.put(361000, \"抚州市\");\n        b.put(361002, \"临川区\");\n        b.put(361003, \"东乡区\");\n        b.put(361021, \"南城县\");\n        b.put(361022, \"黎川县\");\n        b.put(361023, \"南丰县\");\n        b.put(361024, \"崇仁县\");\n        b.put(361025, \"乐安县\");\n        b.put(361026, \"宜黄县\");\n        b.put(361027, \"金溪县\");\n        b.put(361028, \"资溪县\");\n        b.put(361030, \"广昌县\");\n        b.put(361100, \"上饶市\");\n        b.put(361102, \"信州区\");\n        b.put(361103, \"广丰区\");\n        b.put(361121, \"上饶县\");\n        b.put(361123, \"玉山县\");\n        b.put(361124, \"铅山县\");\n        b.put(361125, \"横峰县\");\n        b.put(361126, \"弋阳县\");\n        b.put(361127, \"余干县\");\n        b.put(361128, \"鄱阳县\");\n        b.put(361129, \"万年县\");\n        b.put(361130, \"婺源县\");\n        b.put(361181, \"德兴市\");\n        b.put(370000, \"山东省\");\n        b.put(370100, \"济南市\");\n        b.put(370102, \"历下区\");\n        b.put(370103, \"市中区\");\n        b.put(370104, \"槐荫区\");\n        b.put(370105, \"天桥区\");\n        b.put(370112, \"历城区\");\n        b.put(370113, \"长清区\");\n        b.put(370114, \"章丘区\");\n        b.put(370124, \"平阴县\");\n        b.put(370125, \"济阳县\");\n        b.put(370126, \"商河县\");\n        b.put(370200, \"青岛市\");\n        b.put(370202, \"市南区\");\n        b.put(370203, \"市北区\");\n        b.put(370211, \"黄岛区\");\n        b.put(370212, \"崂山区\");\n        b.put(370213, \"李沧区\");\n        b.put(370214, \"城阳区\");\n        b.put(370215, \"即墨区\");\n        b.put(370281, \"胶州市\");\n        b.put(370283, \"平度市\");\n        b.put(370285, \"莱西市\");\n        b.put(370300, \"淄博市\");\n        b.put(370302, \"淄川区\");\n        b.put(370303, \"张店区\");\n        b.put(370304, \"博山区\");\n        b.put(370305, \"临淄区\");\n        b.put(370306, \"周村区\");\n        b.put(370321, \"桓台县\");\n        b.put(370322, \"高青县\");\n        b.put(370323, \"沂源县\");\n        b.put(370400, \"枣庄市\");\n        b.put(370402, \"市中区\");\n        b.put(370403, \"薛城区\");\n        b.put(370404, \"峄城区\");\n        b.put(370405, \"台儿庄区\");\n        b.put(370406, \"山亭区\");\n        b.put(370481, \"滕州市\");\n        b.put(370500, \"东营市\");\n        b.put(370502, \"东营区\");\n        b.put(370503, \"河口区\");\n        b.put(370505, \"垦利区\");\n        b.put(370522, \"利津县\");\n        b.put(370523, \"广饶县\");\n        b.put(370600, \"烟台市\");\n        b.put(370602, \"芝罘区\");\n        b.put(370611, \"福山区\");\n        b.put(370612, \"牟平区\");\n        b.put(370613, \"莱山区\");\n        b.put(370634, \"长岛县\");\n        b.put(370681, \"龙口市\");\n        b.put(370682, \"莱阳市\");\n        b.put(370683, \"莱州市\");\n        b.put(370684, \"蓬莱市\");\n        b.put(370685, \"招远市\");\n        b.put(370686, \"栖霞市\");\n        b.put(370687, \"海阳市\");\n        b.put(370700, \"潍坊市\");\n        b.put(370702, \"潍城区\");\n        b.put(370703, \"寒亭区\");\n        b.put(370704, \"坊子区\");\n        b.put(370705, \"奎文区\");\n        b.put(370724, \"临朐县\");\n        b.put(370725, \"昌乐县\");\n        b.put(370781, \"青州市\");\n        b.put(370782, \"诸城市\");\n        b.put(370783, \"寿光市\");\n        b.put(370784, \"安丘市\");\n        b.put(370785, \"高密市\");\n        b.put(370786, \"昌邑市\");\n        b.put(370800, \"济宁市\");\n        b.put(370811, \"任城区\");\n        b.put(370812, \"兖州区\");\n        b.put(370826, \"微山县\");\n        b.put(370827, \"鱼台县\");\n        b.put(370828, \"金乡县\");\n        b.put(370829, \"嘉祥县\");\n        b.put(370830, \"汶上县\");\n        b.put(370831, \"泗水县\");\n        b.put(370832, \"梁山县\");\n        b.put(370881, \"曲阜市\");\n        b.put(370883, \"邹城市\");\n        b.put(370900, \"泰安市\");\n        b.put(370902, \"泰山区\");\n        b.put(370911, \"岱岳区\");\n        b.put(370921, \"宁阳县\");\n        b.put(370923, \"东平县\");\n        b.put(370982, \"新泰市\");\n        b.put(370983, \"肥城市\");\n        b.put(371000, \"威海市\");\n        b.put(371002, \"环翠区\");\n        b.put(371003, \"文登区\");\n        b.put(371082, \"荣成市\");\n        b.put(371083, \"乳山市\");\n        b.put(371100, \"日照市\");\n        b.put(371102, \"东港区\");\n        b.put(371103, \"岚山区\");\n        b.put(371121, \"五莲县\");\n        b.put(371122, \"莒县\");\n        b.put(371200, \"莱芜市\");\n        b.put(371202, \"莱城区\");\n        b.put(371203, \"钢城区\");\n        b.put(371300, \"临沂市\");\n        b.put(371302, \"兰山区\");\n        b.put(371311, \"罗庄区\");\n        b.put(371312, \"河东区\");\n        b.put(371321, \"沂南县\");\n        b.put(371322, \"郯城县\");\n        b.put(371323, \"沂水县\");\n        b.put(371324, \"兰陵县\");\n        b.put(371325, \"费县\");\n        b.put(371326, \"平邑县\");\n        b.put(371327, \"莒南县\");\n        b.put(371328, \"蒙阴县\");\n        b.put(371329, \"临沭县\");\n        b.put(371400, \"德州市\");\n        b.put(371402, \"德城区\");\n        b.put(371403, \"陵城区\");\n        b.put(371422, \"宁津县\");\n        b.put(371423, \"庆云县\");\n        b.put(371424, \"临邑县\");\n        b.put(371425, \"齐河县\");\n        b.put(371426, \"平原县\");\n        b.put(371427, \"夏津县\");\n        b.put(371428, \"武城县\");\n        b.put(371481, \"乐陵市\");\n        b.put(371482, \"禹城市\");\n        b.put(371500, \"聊城市\");\n        b.put(371502, \"东昌府区\");\n        b.put(371521, \"阳谷县\");\n        b.put(371522, \"莘县\");\n        b.put(371523, \"茌平县\");\n        b.put(371524, \"东阿县\");\n        b.put(371525, \"冠县\");\n        b.put(371526, \"高唐县\");\n        b.put(371581, \"临清市\");\n        b.put(371600, \"滨州市\");\n        b.put(371602, \"滨城区\");\n        b.put(371603, \"沾化区\");\n        b.put(371621, \"惠民县\");\n        b.put(371622, \"阳信县\");\n        b.put(371623, \"无棣县\");\n        b.put(371625, \"博兴县\");\n        b.put(371626, \"邹平县\");\n        b.put(371700, \"菏泽市\");\n        b.put(371702, \"牡丹区\");\n        b.put(371703, \"定陶区\");\n        b.put(371721, \"曹县\");\n        b.put(371722, \"单县\");\n        b.put(371723, \"成武县\");\n        b.put(371724, \"巨野县\");\n        b.put(371725, \"郓城县\");\n        b.put(371726, \"鄄城县\");\n        b.put(371728, \"东明县\");\n        b.put(410000, \"河南省\");\n        b.put(410100, \"郑州市\");\n        b.put(410102, \"中原区\");\n        b.put(410103, \"二七区\");\n        b.put(410104, \"管城回族区\");\n        b.put(410105, \"金水区\");\n        b.put(410106, \"上街区\");\n        b.put(410108, \"惠济区\");\n        b.put(410122, \"中牟县\");\n        b.put(410181, \"巩义市\");\n        b.put(410182, \"荥阳市\");\n        b.put(410183, \"新密市\");\n        b.put(410184, \"新郑市\");\n        b.put(410185, \"登封市\");\n        b.put(410200, \"开封市\");\n        b.put(410202, \"龙亭区\");\n        b.put(410203, \"顺河回族区\");\n        b.put(410204, \"鼓楼区\");\n        b.put(410205, \"禹王台区\");\n        b.put(410212, \"祥符区\");\n        b.put(410221, \"杞县\");\n        b.put(410222, \"通许县\");\n        b.put(410223, \"尉氏县\");\n        b.put(410225, \"兰考县\");\n        b.put(410300, \"洛阳市\");\n        b.put(410302, \"老城区\");\n        b.put(410303, \"西工区\");\n        b.put(410304, \"瀍河回族区\");\n        b.put(410305, \"涧西区\");\n        b.put(410306, \"吉利区\");\n        b.put(410311, \"洛龙区\");\n        b.put(410322, \"孟津县\");\n        b.put(410323, \"新安县\");\n        b.put(410324, \"栾川县\");\n        b.put(410325, \"嵩县\");\n        b.put(410326, \"汝阳县\");\n        b.put(410327, \"宜阳县\");\n        b.put(410328, \"洛宁县\");\n        b.put(410329, \"伊川县\");\n        b.put(410381, \"偃师市\");\n        b.put(410400, \"平顶山市\");\n        b.put(410402, \"新华区\");\n        b.put(410403, \"卫东区\");\n        b.put(410404, \"石龙区\");\n        b.put(410411, \"湛河区\");\n        b.put(410421, \"宝丰县\");\n        b.put(410422, \"叶县\");\n        b.put(410423, \"鲁山县\");\n        b.put(410425, \"郏县\");\n        b.put(410481, \"舞钢市\");\n        b.put(410482, \"汝州市\");\n        b.put(410500, \"安阳市\");\n        b.put(410502, \"文峰区\");\n        b.put(410503, \"北关区\");\n        b.put(410505, \"殷都区\");\n        b.put(410506, \"龙安区\");\n        b.put(410522, \"安阳县\");\n        b.put(410523, \"汤阴县\");\n        b.put(410526, \"滑县\");\n        b.put(410527, \"内黄县\");\n        b.put(410581, \"林州市\");\n        b.put(410600, \"鹤壁市\");\n        b.put(410602, \"鹤山区\");\n        b.put(410603, \"山城区\");\n        b.put(410611, \"淇滨区\");\n        b.put(410621, \"浚县\");\n        b.put(410622, \"淇县\");\n        b.put(410700, \"新乡市\");\n        b.put(410702, \"红旗区\");\n        b.put(410703, \"卫滨区\");\n        b.put(410704, \"凤泉区\");\n        b.put(410711, \"牧野区\");\n        b.put(410721, \"新乡县\");\n        b.put(410724, \"获嘉县\");\n        b.put(410725, \"原阳县\");\n        b.put(410726, \"延津县\");\n        b.put(410727, \"封丘县\");\n        b.put(410728, \"长垣县\");\n        b.put(410781, \"卫辉市\");\n        b.put(410782, \"辉县市\");\n        b.put(410800, \"焦作市\");\n        b.put(410802, \"解放区\");\n        b.put(410803, \"中站区\");\n        b.put(410804, \"马村区\");\n        b.put(410811, \"山阳区\");\n        b.put(410821, \"修武县\");\n        b.put(410822, \"博爱县\");\n        b.put(410823, \"武陟县\");\n        b.put(410825, \"温县\");\n        b.put(410882, \"沁阳市\");\n        b.put(410883, \"孟州市\");\n        b.put(410900, \"濮阳市\");\n        b.put(410902, \"华龙区\");\n        b.put(410922, \"清丰县\");\n        b.put(410923, \"南乐县\");\n        b.put(410926, \"范县\");\n        b.put(410927, \"台前县\");\n        b.put(410928, \"濮阳县\");\n        b.put(411000, \"许昌市\");\n        b.put(411002, \"魏都区\");\n        b.put(411003, \"建安区\");\n        b.put(411024, \"鄢陵县\");\n        b.put(411025, \"襄城县\");\n        b.put(411081, \"禹州市\");\n        b.put(411082, \"长葛市\");\n        b.put(411100, \"漯河市\");\n        b.put(411102, \"源汇区\");\n        b.put(411103, \"郾城区\");\n        b.put(411104, \"召陵区\");\n        b.put(411121, \"舞阳县\");\n        b.put(411122, \"临颍县\");\n        b.put(411200, \"三门峡市\");\n        b.put(411202, \"湖滨区\");\n        b.put(411203, \"陕州区\");\n        b.put(411221, \"渑池县\");\n        b.put(411224, \"卢氏县\");\n        b.put(411281, \"义马市\");\n        b.put(411282, \"灵宝市\");\n        b.put(411300, \"南阳市\");\n        b.put(411302, \"宛城区\");\n        b.put(411303, \"卧龙区\");\n        b.put(411321, \"南召县\");\n        b.put(411322, \"方城县\");\n        b.put(411323, \"西峡县\");\n        b.put(411324, \"镇平县\");\n        b.put(411325, \"内乡县\");\n        b.put(411326, \"淅川县\");\n        b.put(411327, \"社旗县\");\n        b.put(411328, \"唐河县\");\n        b.put(411329, \"新野县\");\n        b.put(411330, \"桐柏县\");\n        b.put(411381, \"邓州市\");\n        b.put(411400, \"商丘市\");\n        b.put(411402, \"梁园区\");\n        b.put(411403, \"睢阳区\");\n        b.put(411421, \"民权县\");\n        b.put(411422, \"睢县\");\n        b.put(411423, \"宁陵县\");\n        b.put(411424, \"柘城县\");\n        b.put(411425, \"虞城县\");\n        b.put(411426, \"夏邑县\");\n        b.put(411481, \"永城市\");\n        b.put(411500, \"信阳市\");\n        b.put(411502, \"浉河区\");\n        b.put(411503, \"平桥区\");\n        b.put(411521, \"罗山县\");\n        b.put(411522, \"光山县\");\n        b.put(411523, \"新县\");\n        b.put(411524, \"商城县\");\n        b.put(411525, \"固始县\");\n        b.put(411526, \"潢川县\");\n        b.put(411527, \"淮滨县\");\n        b.put(411528, \"息县\");\n        b.put(411600, \"周口市\");\n        b.put(411602, \"川汇区\");\n        b.put(411621, \"扶沟县\");\n        b.put(411622, \"西华县\");\n        b.put(411623, \"商水县\");\n        b.put(411624, \"沈丘县\");\n        b.put(411625, \"郸城县\");\n        b.put(411626, \"淮阳县\");\n        b.put(411627, \"太康县\");\n        b.put(411628, \"鹿邑县\");\n        b.put(411681, \"项城市\");\n        b.put(411700, \"驻马店市\");\n        b.put(411702, \"驿城区\");\n        b.put(411721, \"西平县\");\n        b.put(411722, \"上蔡县\");\n        b.put(411723, \"平舆县\");\n        b.put(411724, \"正阳县\");\n        b.put(411725, \"确山县\");\n        b.put(411726, \"泌阳县\");\n        b.put(411727, \"汝南县\");\n        b.put(411728, \"遂平县\");\n        b.put(411729, \"新蔡县\");\n        b.put(419001, \"济源市\");\n        b.put(420000, \"湖北省\");\n        b.put(420100, \"武汉市\");\n        b.put(420102, \"江岸区\");\n        b.put(420103, \"江汉区\");\n        b.put(420104, \"硚口区\");\n        b.put(420105, \"汉阳区\");\n        b.put(420106, \"武昌区\");\n        b.put(420107, \"青山区\");\n        b.put(420111, \"洪山区\");\n        b.put(420112, \"东西湖区\");\n        b.put(420113, \"汉南区\");\n        b.put(420114, \"蔡甸区\");\n        b.put(420115, \"江夏区\");\n        b.put(420116, \"黄陂区\");\n        b.put(420117, \"新洲区\");\n        b.put(420200, \"黄石市\");\n        b.put(420202, \"黄石港区\");\n        b.put(420203, \"西塞山区\");\n        b.put(420204, \"下陆区\");\n        b.put(420205, \"铁山区\");\n        b.put(420222, \"阳新县\");\n        b.put(420281, \"大冶市\");\n        b.put(420300, \"十堰市\");\n        b.put(420302, \"茅箭区\");\n        b.put(420303, \"张湾区\");\n        b.put(420304, \"郧阳区\");\n        b.put(420322, \"郧西县\");\n        b.put(420323, \"竹山县\");\n        b.put(420324, \"竹溪县\");\n        b.put(420325, \"房县\");\n        b.put(420381, \"丹江口市\");\n        b.put(420500, \"宜昌市\");\n        b.put(420502, \"西陵区\");\n        b.put(420503, \"伍家岗区\");\n        b.put(420504, \"点军区\");\n        b.put(420505, \"猇亭区\");\n        b.put(420506, \"夷陵区\");\n        b.put(420525, \"远安县\");\n        b.put(420526, \"兴山县\");\n        b.put(420527, \"秭归县\");\n        b.put(420528, \"长阳土家族自治县\");\n        b.put(420529, \"五峰土家族自治县\");\n        b.put(420581, \"宜都市\");\n        b.put(420582, \"当阳市\");\n        b.put(420583, \"枝江市\");\n        b.put(420600, \"襄阳市\");\n        b.put(420602, \"襄城区\");\n        b.put(420606, \"樊城区\");\n        b.put(420607, \"襄州区\");\n        b.put(420624, \"南漳县\");\n        b.put(420625, \"谷城县\");\n        b.put(420626, \"保康县\");\n        b.put(420682, \"老河口市\");\n        b.put(420683, \"枣阳市\");\n        b.put(420684, \"宜城市\");\n        b.put(420700, \"鄂州市\");\n        b.put(420702, \"梁子湖区\");\n        b.put(420703, \"华容区\");\n        b.put(420704, \"鄂城区\");\n        b.put(420800, \"荆门市\");\n        b.put(420802, \"东宝区\");\n        b.put(420804, \"掇刀区\");\n        b.put(420822, \"沙洋县\");\n        b.put(420881, \"钟祥市\");\n        b.put(420882, \"京山市\");\n        b.put(420900, \"孝感市\");\n        b.put(420902, \"孝南区\");\n        b.put(420921, \"孝昌县\");\n        b.put(420922, \"大悟县\");\n        b.put(420923, \"云梦县\");\n        b.put(420981, \"应城市\");\n        b.put(420982, \"安陆市\");\n        b.put(420984, \"汉川市\");\n        b.put(421000, \"荆州市\");\n        b.put(421002, \"沙市区\");\n        b.put(421003, \"荆州区\");\n        b.put(421022, \"公安县\");\n        b.put(421023, \"监利县\");\n        b.put(421024, \"江陵县\");\n        b.put(421081, \"石首市\");\n        b.put(421083, \"洪湖市\");\n        b.put(421087, \"松滋市\");\n        b.put(421100, \"黄冈市\");\n        b.put(421102, \"黄州区\");\n        b.put(421121, \"团风县\");\n        b.put(421122, \"红安县\");\n        b.put(421123, \"罗田县\");\n        b.put(421124, \"英山县\");\n        b.put(421125, \"浠水县\");\n        b.put(421126, \"蕲春县\");\n        b.put(421127, \"黄梅县\");\n        b.put(421181, \"麻城市\");\n        b.put(421182, \"武穴市\");\n        b.put(421200, \"咸宁市\");\n        b.put(421202, \"咸安区\");\n        b.put(421221, \"嘉鱼县\");\n        b.put(421222, \"通城县\");\n        b.put(421223, \"崇阳县\");\n        b.put(421224, \"通山县\");\n        b.put(421281, \"赤壁市\");\n        b.put(421300, \"随州市\");\n        b.put(421303, \"曾都区\");\n        b.put(421321, \"随县\");\n        b.put(421381, \"广水市\");\n        b.put(422800, \"恩施土家族苗族自治州\");\n        b.put(422801, \"恩施市\");\n        b.put(422802, \"利川市\");\n        b.put(422822, \"建始县\");\n        b.put(422823, \"巴东县\");\n        b.put(422825, \"宣恩县\");\n        b.put(422826, \"咸丰县\");\n        b.put(422827, \"来凤县\");\n        b.put(422828, \"鹤峰县\");\n        b.put(429004, \"仙桃市\");\n        b.put(429005, \"潜江市\");\n        b.put(429006, \"天门市\");\n        b.put(429021, \"神农架林区\");\n        b.put(430000, \"湖南省\");\n        b.put(430100, \"长沙市\");\n        b.put(430102, \"芙蓉区\");\n        b.put(430103, \"天心区\");\n        b.put(430104, \"岳麓区\");\n        b.put(430105, \"开福区\");\n        b.put(430111, \"雨花区\");\n        b.put(430112, \"望城区\");\n        b.put(430121, \"长沙县\");\n        b.put(430181, \"浏阳市\");\n        b.put(430182, \"宁乡市\");\n        b.put(430200, \"株洲市\");\n        b.put(430202, \"荷塘区\");\n        b.put(430203, \"芦淞区\");\n        b.put(430204, \"石峰区\");\n        b.put(430211, \"天元区\");\n        b.put(430212, \"渌口区\");\n        b.put(430223, \"攸县\");\n        b.put(430224, \"茶陵县\");\n        b.put(430225, \"炎陵县\");\n        b.put(430281, \"醴陵市\");\n        b.put(430300, \"湘潭市\");\n        b.put(430302, \"雨湖区\");\n        b.put(430304, \"岳塘区\");\n        b.put(430321, \"湘潭县\");\n        b.put(430381, \"湘乡市\");\n        b.put(430382, \"韶山市\");\n        b.put(430400, \"衡阳市\");\n        b.put(430405, \"珠晖区\");\n        b.put(430406, \"雁峰区\");\n        b.put(430407, \"石鼓区\");\n        b.put(430408, \"蒸湘区\");\n        b.put(430412, \"南岳区\");\n        b.put(430421, \"衡阳县\");\n        b.put(430422, \"衡南县\");\n        b.put(430423, \"衡山县\");\n        b.put(430424, \"衡东县\");\n        b.put(430426, \"祁东县\");\n        b.put(430481, \"耒阳市\");\n        b.put(430482, \"常宁市\");\n        b.put(430500, \"邵阳市\");\n        b.put(430502, \"双清区\");\n        b.put(430503, \"大祥区\");\n        b.put(430511, \"北塔区\");\n        b.put(430521, \"邵东县\");\n        b.put(430522, \"新邵县\");\n        b.put(430523, \"邵阳县\");\n        b.put(430524, \"隆回县\");\n        b.put(430525, \"洞口县\");\n        b.put(430527, \"绥宁县\");\n        b.put(430528, \"新宁县\");\n        b.put(430529, \"城步苗族自治县\");\n        b.put(430581, \"武冈市\");\n        b.put(430600, \"岳阳市\");\n        b.put(430602, \"岳阳楼区\");\n        b.put(430603, \"云溪区\");\n        b.put(430611, \"君山区\");\n        b.put(430621, \"岳阳县\");\n        b.put(430623, \"华容县\");\n        b.put(430624, \"湘阴县\");\n        b.put(430626, \"平江县\");\n        b.put(430681, \"汨罗市\");\n        b.put(430682, \"临湘市\");\n        b.put(430700, \"常德市\");\n        b.put(430702, \"武陵区\");\n        b.put(430703, \"鼎城区\");\n        b.put(430721, \"安乡县\");\n        b.put(430722, \"汉寿县\");\n        b.put(430723, \"澧县\");\n        b.put(430724, \"临澧县\");\n        b.put(430725, \"桃源县\");\n        b.put(430726, \"石门县\");\n        b.put(430781, \"津市市\");\n        b.put(430800, \"张家界市\");\n        b.put(430802, \"永定区\");\n        b.put(430811, \"武陵源区\");\n        b.put(430821, \"慈利县\");\n        b.put(430822, \"桑植县\");\n        b.put(430900, \"益阳市\");\n        b.put(430902, \"资阳区\");\n        b.put(430903, \"赫山区\");\n        b.put(430921, \"南县\");\n        b.put(430922, \"桃江县\");\n        b.put(430923, \"安化县\");\n        b.put(430981, \"沅江市\");\n        b.put(431000, \"郴州市\");\n        b.put(431002, \"北湖区\");\n        b.put(431003, \"苏仙区\");\n        b.put(431021, \"桂阳县\");\n        b.put(431022, \"宜章县\");\n        b.put(431023, \"永兴县\");\n        b.put(431024, \"嘉禾县\");\n        b.put(431025, \"临武县\");\n        b.put(431026, \"汝城县\");\n        b.put(431027, \"桂东县\");\n        b.put(431028, \"安仁县\");\n        b.put(431081, \"资兴市\");\n        b.put(431100, \"永州市\");\n        b.put(431102, \"零陵区\");\n        b.put(431103, \"冷水滩区\");\n        b.put(431121, \"祁阳县\");\n        b.put(431122, \"东安县\");\n        b.put(431123, \"双牌县\");\n        b.put(431124, \"道县\");\n        b.put(431125, \"江永县\");\n        b.put(431126, \"宁远县\");\n        b.put(431127, \"蓝山县\");\n        b.put(431128, \"新田县\");\n        b.put(431129, \"江华瑶族自治县\");\n        b.put(431200, \"怀化市\");\n        b.put(431202, \"鹤城区\");\n        b.put(431221, \"中方县\");\n        b.put(431222, \"沅陵县\");\n        b.put(431223, \"辰溪县\");\n        b.put(431224, \"溆浦县\");\n        b.put(431225, \"会同县\");\n        b.put(431226, \"麻阳苗族自治县\");\n        b.put(431227, \"新晃侗族自治县\");\n        b.put(431228, \"芷江侗族自治县\");\n        b.put(431229, \"靖州苗族侗族自治县\");\n        b.put(431230, \"通道侗族自治县\");\n        b.put(431281, \"洪江市\");\n        b.put(431300, \"娄底市\");\n        b.put(431302, \"娄星区\");\n        b.put(431321, \"双峰县\");\n        b.put(431322, \"新化县\");\n        b.put(431381, \"冷水江市\");\n        b.put(431382, \"涟源市\");\n        b.put(433100, \"湘西土家族苗族自治州\");\n        b.put(433101, \"吉首市\");\n        b.put(433122, \"泸溪县\");\n        b.put(433123, \"凤凰县\");\n        b.put(433124, \"花垣县\");\n        b.put(433125, \"保靖县\");\n        b.put(433126, \"古丈县\");\n        b.put(433127, \"永顺县\");\n        b.put(433130, \"龙山县\");\n        b.put(440000, \"广东省\");\n        b.put(440100, \"广州市\");\n        b.put(440103, \"荔湾区\");\n        b.put(440104, \"越秀区\");\n        b.put(440105, \"海珠区\");\n        b.put(440106, \"天河区\");\n        b.put(440111, \"白云区\");\n        b.put(440112, \"黄埔区\");\n        b.put(440113, \"番禺区\");\n        b.put(440114, \"花都区\");\n        b.put(440115, \"南沙区\");\n        b.put(440117, \"从化区\");\n        b.put(440118, \"增城区\");\n        b.put(440200, \"韶关市\");\n        b.put(440203, \"武江区\");\n        b.put(440204, \"浈江区\");\n        b.put(440205, \"曲江区\");\n        b.put(440222, \"始兴县\");\n        b.put(440224, \"仁化县\");\n        b.put(440229, \"翁源县\");\n        b.put(440232, \"乳源瑶族自治县\");\n        b.put(440233, \"新丰县\");\n        b.put(440281, \"乐昌市\");\n        b.put(440282, \"南雄市\");\n        b.put(440300, \"深圳市\");\n        b.put(440303, \"罗湖区\");\n        b.put(440304, \"福田区\");\n        b.put(440305, \"南山区\");\n        b.put(440306, \"宝安区\");\n        b.put(440307, \"龙岗区\");\n        b.put(440308, \"盐田区\");\n        b.put(440309, \"龙华区\");\n        b.put(440310, \"坪山区\");\n        b.put(440311, \"光明区\");\n        b.put(440400, \"珠海市\");\n        b.put(440402, \"香洲区\");\n        b.put(440403, \"斗门区\");\n        b.put(440404, \"金湾区\");\n        b.put(440500, \"汕头市\");\n        b.put(440507, \"龙湖区\");\n        b.put(440511, \"金平区\");\n        b.put(440512, \"濠江区\");\n        b.put(440513, \"潮阳区\");\n        b.put(440514, \"潮南区\");\n        b.put(440515, \"澄海区\");\n        b.put(440523, \"南澳县\");\n        b.put(440600, \"佛山市\");\n        b.put(440604, \"禅城区\");\n        b.put(440605, \"南海区\");\n        b.put(440606, \"顺德区\");\n        b.put(440607, \"三水区\");\n        b.put(440608, \"高明区\");\n        b.put(440700, \"江门市\");\n        b.put(440703, \"蓬江区\");\n        b.put(440704, \"江海区\");\n        b.put(440705, \"新会区\");\n        b.put(440781, \"台山市\");\n        b.put(440783, \"开平市\");\n        b.put(440784, \"鹤山市\");\n        b.put(440785, \"恩平市\");\n        b.put(440800, \"湛江市\");\n        b.put(440802, \"赤坎区\");\n        b.put(440803, \"霞山区\");\n        b.put(440804, \"坡头区\");\n        b.put(440811, \"麻章区\");\n        b.put(440823, \"遂溪县\");\n        b.put(440825, \"徐闻县\");\n        b.put(440881, \"廉江市\");\n        b.put(440882, \"雷州市\");\n        b.put(440883, \"吴川市\");\n        b.put(440900, \"茂名市\");\n        b.put(440902, \"茂南区\");\n        b.put(440904, \"电白区\");\n        b.put(440981, \"高州市\");\n        b.put(440982, \"化州市\");\n        b.put(440983, \"信宜市\");\n        b.put(441200, \"肇庆市\");\n        b.put(441202, \"端州区\");\n        b.put(441203, \"鼎湖区\");\n        b.put(441204, \"高要区\");\n        b.put(441223, \"广宁县\");\n        b.put(441224, \"怀集县\");\n        b.put(441225, \"封开县\");\n        b.put(441226, \"德庆县\");\n        b.put(441284, \"四会市\");\n        b.put(441300, \"惠州市\");\n        b.put(441302, \"惠城区\");\n        b.put(441303, \"惠阳区\");\n        b.put(441322, \"博罗县\");\n        b.put(441323, \"惠东县\");\n        b.put(441324, \"龙门县\");\n        b.put(441400, \"梅州市\");\n        b.put(441402, \"梅江区\");\n        b.put(441403, \"梅县区\");\n        b.put(441422, \"大埔县\");\n        b.put(441423, \"丰顺县\");\n        b.put(441424, \"五华县\");\n        b.put(441426, \"平远县\");\n        b.put(441427, \"蕉岭县\");\n        b.put(441481, \"兴宁市\");\n        b.put(441500, \"汕尾市\");\n        b.put(441502, \"城区\");\n        b.put(441521, \"海丰县\");\n        b.put(441523, \"陆河县\");\n        b.put(441581, \"陆丰市\");\n        b.put(441600, \"河源市\");\n        b.put(441602, \"源城区\");\n        b.put(441621, \"紫金县\");\n        b.put(441622, \"龙川县\");\n        b.put(441623, \"连平县\");\n        b.put(441624, \"和平县\");\n        b.put(441625, \"东源县\");\n        b.put(441700, \"阳江市\");\n        b.put(441702, \"江城区\");\n        b.put(441704, \"阳东区\");\n        b.put(441721, \"阳西县\");\n        b.put(441781, \"阳春市\");\n        b.put(441800, \"清远市\");\n        b.put(441802, \"清城区\");\n        b.put(441803, \"清新区\");\n        b.put(441821, \"佛冈县\");\n        b.put(441823, \"阳山县\");\n        b.put(441825, \"连山壮族瑶族自治县\");\n        b.put(441826, \"连南瑶族自治县\");\n        b.put(441881, \"英德市\");\n        b.put(441882, \"连州市\");\n        b.put(441900, \"东莞市\");\n        b.put(442000, \"中山市\");\n        b.put(445100, \"潮州市\");\n        b.put(445102, \"湘桥区\");\n        b.put(445103, \"潮安区\");\n        b.put(445122, \"饶平县\");\n        b.put(445200, \"揭阳市\");\n        b.put(445202, \"榕城区\");\n        b.put(445203, \"揭东区\");\n        b.put(445222, \"揭西县\");\n        b.put(445224, \"惠来县\");\n        b.put(445281, \"普宁市\");\n        b.put(445300, \"云浮市\");\n        b.put(445302, \"云城区\");\n        b.put(445303, \"云安区\");\n        b.put(445321, \"新兴县\");\n        b.put(445322, \"郁南县\");\n        b.put(445381, \"罗定市\");\n        b.put(450000, \"广西壮族自治区\");\n        b.put(450100, \"南宁市\");\n        b.put(450102, \"兴宁区\");\n        b.put(450103, \"青秀区\");\n        b.put(450105, \"江南区\");\n        b.put(450107, \"西乡塘区\");\n        b.put(450108, \"良庆区\");\n        b.put(450109, \"邕宁区\");\n        b.put(450110, \"武鸣区\");\n        b.put(450123, \"隆安县\");\n        b.put(450124, \"马山县\");\n        b.put(450125, \"上林县\");\n        b.put(450126, \"宾阳县\");\n        b.put(450127, \"横县\");\n        b.put(450200, \"柳州市\");\n        b.put(450202, \"城中区\");\n        b.put(450203, \"鱼峰区\");\n        b.put(450204, \"柳南区\");\n        b.put(450205, \"柳北区\");\n        b.put(450206, \"柳江区\");\n        b.put(450222, \"柳城县\");\n        b.put(450223, \"鹿寨县\");\n        b.put(450224, \"融安县\");\n        b.put(450225, \"融水苗族自治县\");\n        b.put(450226, \"三江侗族自治县\");\n        b.put(450300, \"桂林市\");\n        b.put(450302, \"秀峰区\");\n        b.put(450303, \"叠彩区\");\n        b.put(450304, \"象山区\");\n        b.put(450305, \"七星区\");\n        b.put(450311, \"雁山区\");\n        b.put(450312, \"临桂区\");\n        b.put(450321, \"阳朔县\");\n        b.put(450323, \"灵川县\");\n        b.put(450324, \"全州县\");\n        b.put(450325, \"兴安县\");\n        b.put(450326, \"永福县\");\n        b.put(450327, \"灌阳县\");\n        b.put(450328, \"龙胜各族自治县\");\n        b.put(450329, \"资源县\");\n        b.put(450330, \"平乐县\");\n        b.put(450381, \"荔浦市\");\n        b.put(450332, \"恭城瑶族自治县\");\n        b.put(450400, \"梧州市\");\n        b.put(450403, \"万秀区\");\n        b.put(450405, \"长洲区\");\n        b.put(450406, \"龙圩区\");\n        b.put(450421, \"苍梧县\");\n        b.put(450422, \"藤县\");\n        b.put(450423, \"蒙山县\");\n        b.put(450481, \"岑溪市\");\n        b.put(450500, \"北海市\");\n        b.put(450502, \"海城区\");\n        b.put(450503, \"银海区\");\n        b.put(450512, \"铁山港区\");\n        b.put(450521, \"合浦县\");\n        b.put(450600, \"防城港市\");\n        b.put(450602, \"港口区\");\n        b.put(450603, \"防城区\");\n        b.put(450621, \"上思县\");\n        b.put(450681, \"东兴市\");\n        b.put(450700, \"钦州市\");\n        b.put(450702, \"钦南区\");\n        b.put(450703, \"钦北区\");\n        b.put(450721, \"灵山县\");\n        b.put(450722, \"浦北县\");\n        b.put(450800, \"贵港市\");\n        b.put(450802, \"港北区\");\n        b.put(450803, \"港南区\");\n        b.put(450804, \"覃塘区\");\n        b.put(450821, \"平南县\");\n        b.put(450881, \"桂平市\");\n        b.put(450900, \"玉林市\");\n        b.put(450902, \"玉州区\");\n        b.put(450903, \"福绵区\");\n        b.put(450921, \"容县\");\n        b.put(450922, \"陆川县\");\n        b.put(450923, \"博白县\");\n        b.put(450924, \"兴业县\");\n        b.put(450981, \"北流市\");\n        b.put(451000, \"百色市\");\n        b.put(451002, \"右江区\");\n        b.put(451021, \"田阳县\");\n        b.put(451022, \"田东县\");\n        b.put(451023, \"平果县\");\n        b.put(451024, \"德保县\");\n        b.put(451026, \"那坡县\");\n        b.put(451027, \"凌云县\");\n        b.put(451028, \"乐业县\");\n        b.put(451029, \"田林县\");\n        b.put(451030, \"西林县\");\n        b.put(451031, \"隆林各族自治县\");\n        b.put(451081, \"靖西市\");\n        b.put(451100, \"贺州市\");\n        b.put(451102, \"八步区\");\n        b.put(451103, \"平桂区\");\n        b.put(451121, \"昭平县\");\n        b.put(451122, \"钟山县\");\n        b.put(451123, \"富川瑶族自治县\");\n        b.put(451200, \"河池市\");\n        b.put(451202, \"金城江区\");\n        b.put(451203, \"宜州区\");\n        b.put(451221, \"南丹县\");\n        b.put(451222, \"天峨县\");\n        b.put(451223, \"凤山县\");\n        b.put(451224, \"东兰县\");\n        b.put(451225, \"罗城仫佬族自治县\");\n        b.put(451226, \"环江毛南族自治县\");\n        b.put(451227, \"巴马瑶族自治县\");\n        b.put(451228, \"都安瑶族自治县\");\n        b.put(451229, \"大化瑶族自治县\");\n        b.put(451300, \"来宾市\");\n        b.put(451302, \"兴宾区\");\n        b.put(451321, \"忻城县\");\n        b.put(451322, \"象州县\");\n        b.put(451323, \"武宣县\");\n        b.put(451324, \"金秀瑶族自治县\");\n        b.put(451381, \"合山市\");\n        b.put(451400, \"崇左市\");\n        b.put(451402, \"江州区\");\n        b.put(451421, \"扶绥县\");\n        b.put(451422, \"宁明县\");\n        b.put(451423, \"龙州县\");\n        b.put(451424, \"大新县\");\n        b.put(451425, \"天等县\");\n        b.put(451481, \"凭祥市\");\n        b.put(460000, \"海南省\");\n        b.put(460100, \"海口市\");\n        b.put(460105, \"秀英区\");\n        b.put(460106, \"龙华区\");\n        b.put(460107, \"琼山区\");\n        b.put(460108, \"美兰区\");\n        b.put(460200, \"三亚市\");\n        b.put(460202, \"海棠区\");\n        b.put(460203, \"吉阳区\");\n        b.put(460204, \"天涯区\");\n        b.put(460205, \"崖州区\");\n        b.put(460300, \"三沙市\");\n        b.put(460400, \"儋州市\");\n        b.put(469001, \"五指山市\");\n        b.put(469002, \"琼海市\");\n        b.put(469005, \"文昌市\");\n        b.put(469006, \"万宁市\");\n        b.put(469007, \"东方市\");\n        b.put(469021, \"定安县\");\n        b.put(469022, \"屯昌县\");\n        b.put(469023, \"澄迈县\");\n        b.put(469024, \"临高县\");\n        b.put(469025, \"白沙黎族自治县\");\n        b.put(469026, \"昌江黎族自治县\");\n        b.put(469027, \"乐东黎族自治县\");\n        b.put(469028, \"陵水黎族自治县\");\n        b.put(469029, \"保亭黎族苗族自治县\");\n        b.put(469030, \"琼中黎族苗族自治县\");\n        b.put(500000, \"重庆市\");\n        b.put(500101, \"万州区\");\n        b.put(500102, \"涪陵区\");\n        b.put(500103, \"渝中区\");\n        b.put(500104, \"大渡口区\");\n        b.put(500105, \"江北区\");\n        b.put(500106, \"沙坪坝区\");\n        b.put(500107, \"九龙坡区\");\n        b.put(500108, \"南岸区\");\n        b.put(500109, \"北碚区\");\n        b.put(500110, \"綦江区\");\n        b.put(500111, \"大足区\");\n        b.put(500112, \"渝北区\");\n        b.put(500113, \"巴南区\");\n        b.put(500114, \"黔江区\");\n        b.put(500115, \"长寿区\");\n        b.put(500116, \"江津区\");\n        b.put(500117, \"合川区\");\n        b.put(500118, \"永川区\");\n        b.put(500119, \"南川区\");\n        b.put(500120, \"璧山区\");\n        b.put(500151, \"铜梁区\");\n        b.put(500152, \"潼南区\");\n        b.put(500153, \"荣昌区\");\n        b.put(500154, \"开州区\");\n        b.put(500155, \"梁平区\");\n        b.put(500156, \"武隆区\");\n        b.put(500229, \"城口县\");\n        b.put(500230, \"丰都县\");\n        b.put(500231, \"垫江县\");\n        b.put(500233, \"忠县\");\n        b.put(500235, \"云阳县\");\n        b.put(500236, \"奉节县\");\n        b.put(500237, \"巫山县\");\n        b.put(500238, \"巫溪县\");\n        b.put(500240, \"石柱土家族自治县\");\n        b.put(500241, \"秀山土家族苗族自治县\");\n        b.put(500242, \"酉阳土家族苗族自治县\");\n        b.put(500243, \"彭水苗族土家族自治县\");\n        b.put(510000, \"四川省\");\n        b.put(510100, \"成都市\");\n        b.put(510104, \"锦江区\");\n        b.put(510105, \"青羊区\");\n        b.put(510106, \"金牛区\");\n        b.put(510107, \"武侯区\");\n        b.put(510108, \"成华区\");\n        b.put(510112, \"龙泉驿区\");\n        b.put(510113, \"青白江区\");\n        b.put(510114, \"新都区\");\n        b.put(510115, \"温江区\");\n        b.put(510116, \"双流区\");\n        b.put(510117, \"郫都区\");\n        b.put(510121, \"金堂县\");\n        b.put(510129, \"大邑县\");\n        b.put(510131, \"蒲江县\");\n        b.put(510132, \"新津县\");\n        b.put(510181, \"都江堰市\");\n        b.put(510182, \"彭州市\");\n        b.put(510183, \"邛崃市\");\n        b.put(510184, \"崇州市\");\n        b.put(510185, \"简阳市\");\n        b.put(510300, \"自贡市\");\n        b.put(510302, \"自流井区\");\n        b.put(510303, \"贡井区\");\n        b.put(510304, \"大安区\");\n        b.put(510311, \"沿滩区\");\n        b.put(510321, \"荣县\");\n        b.put(510322, \"富顺县\");\n        b.put(510400, \"攀枝花市\");\n        b.put(510402, \"东区\");\n        b.put(510403, \"西区\");\n        b.put(510411, \"仁和区\");\n        b.put(510421, \"米易县\");\n        b.put(510422, \"盐边县\");\n        b.put(510500, \"泸州市\");\n        b.put(510502, \"江阳区\");\n        b.put(510503, \"纳溪区\");\n        b.put(510504, \"龙马潭区\");\n        b.put(510521, \"泸县\");\n        b.put(510522, \"合江县\");\n        b.put(510524, \"叙永县\");\n        b.put(510525, \"古蔺县\");\n        b.put(510600, \"德阳市\");\n        b.put(510603, \"旌阳区\");\n        b.put(510604, \"罗江区\");\n        b.put(510623, \"中江县\");\n        b.put(510681, \"广汉市\");\n        b.put(510682, \"什邡市\");\n        b.put(510683, \"绵竹市\");\n        b.put(510700, \"绵阳市\");\n        b.put(510703, \"涪城区\");\n        b.put(510704, \"游仙区\");\n        b.put(510705, \"安州区\");\n        b.put(510722, \"三台县\");\n        b.put(510723, \"盐亭县\");\n        b.put(510725, \"梓潼县\");\n        b.put(510726, \"北川羌族自治县\");\n        b.put(510727, \"平武县\");\n        b.put(510781, \"江油市\");\n        b.put(510800, \"广元市\");\n        b.put(510802, \"利州区\");\n        b.put(510811, \"昭化区\");\n        b.put(510812, \"朝天区\");\n        b.put(510821, \"旺苍县\");\n        b.put(510822, \"青川县\");\n        b.put(510823, \"剑阁县\");\n        b.put(510824, \"苍溪县\");\n        b.put(510900, \"遂宁市\");\n        b.put(510903, \"船山区\");\n        b.put(510904, \"安居区\");\n        b.put(510921, \"蓬溪县\");\n        b.put(510922, \"射洪县\");\n        b.put(510923, \"大英县\");\n        b.put(511000, \"内江市\");\n        b.put(511002, \"市中区\");\n        b.put(511011, \"东兴区\");\n        b.put(511024, \"威远县\");\n        b.put(511025, \"资中县\");\n        b.put(511083, \"隆昌市\");\n        b.put(511100, \"乐山市\");\n        b.put(511102, \"市中区\");\n        b.put(511111, \"沙湾区\");\n        b.put(511112, \"五通桥区\");\n        b.put(511113, \"金口河区\");\n        b.put(511123, \"犍为县\");\n        b.put(511124, \"井研县\");\n        b.put(511126, \"夹江县\");\n        b.put(511129, \"沐川县\");\n        b.put(511132, \"峨边彝族自治县\");\n        b.put(511133, \"马边彝族自治县\");\n        b.put(511181, \"峨眉山市\");\n        b.put(511300, \"南充市\");\n        b.put(511302, \"顺庆区\");\n        b.put(511303, \"高坪区\");\n        b.put(511304, \"嘉陵区\");\n        b.put(511321, \"南部县\");\n        b.put(511322, \"营山县\");\n        b.put(511323, \"蓬安县\");\n        b.put(511324, \"仪陇县\");\n        b.put(511325, \"西充县\");\n        b.put(511381, \"阆中市\");\n        b.put(511400, \"眉山市\");\n        b.put(511402, \"东坡区\");\n        b.put(511403, \"彭山区\");\n        b.put(511421, \"仁寿县\");\n        b.put(511423, \"洪雅县\");\n        b.put(511424, \"丹棱县\");\n        b.put(511425, \"青神县\");\n        b.put(511500, \"宜宾市\");\n        b.put(511502, \"翠屏区\");\n        b.put(511503, \"南溪区\");\n        b.put(511521, \"宜宾县\");\n        b.put(511523, \"江安县\");\n        b.put(511524, \"长宁县\");\n        b.put(511525, \"高县\");\n        b.put(511526, \"珙县\");\n        b.put(511527, \"筠连县\");\n        b.put(511528, \"兴文县\");\n        b.put(511529, \"屏山县\");\n        b.put(511600, \"广安市\");\n        b.put(511602, \"广安区\");\n        b.put(511603, \"前锋区\");\n        b.put(511621, \"岳池县\");\n        b.put(511622, \"武胜县\");\n        b.put(511623, \"邻水县\");\n        b.put(511681, \"华蓥市\");\n        b.put(511700, \"达州市\");\n        b.put(511702, \"通川区\");\n        b.put(511703, \"达川区\");\n        b.put(511722, \"宣汉县\");\n        b.put(511723, \"开江县\");\n        b.put(511724, \"大竹县\");\n        b.put(511725, \"渠县\");\n        b.put(511781, \"万源市\");\n        b.put(511800, \"雅安市\");\n        b.put(511802, \"雨城区\");\n        b.put(511803, \"名山区\");\n        b.put(511822, \"荥经县\");\n        b.put(511823, \"汉源县\");\n        b.put(511824, \"石棉县\");\n        b.put(511825, \"天全县\");\n        b.put(511826, \"芦山县\");\n        b.put(511827, \"宝兴县\");\n        b.put(511900, \"巴中市\");\n        b.put(511902, \"巴州区\");\n        b.put(511903, \"恩阳区\");\n        b.put(511921, \"通江县\");\n        b.put(511922, \"南江县\");\n        b.put(511923, \"平昌县\");\n        b.put(512000, \"资阳市\");\n        b.put(512002, \"雁江区\");\n        b.put(512021, \"安岳县\");\n        b.put(512022, \"乐至县\");\n        b.put(513200, \"阿坝藏族羌族自治州\");\n        b.put(513201, \"马尔康市\");\n        b.put(513221, \"汶川县\");\n        b.put(513222, \"理县\");\n        b.put(513223, \"茂县\");\n        b.put(513224, \"松潘县\");\n        b.put(513225, \"九寨沟县\");\n        b.put(513226, \"金川县\");\n        b.put(513227, \"小金县\");\n        b.put(513228, \"黑水县\");\n        b.put(513230, \"壤塘县\");\n        b.put(513231, \"阿坝县\");\n        b.put(513232, \"若尔盖县\");\n        b.put(513233, \"红原县\");\n        b.put(513300, \"甘孜藏族自治州\");\n        b.put(513301, \"康定市\");\n        b.put(513322, \"泸定县\");\n        b.put(513323, \"丹巴县\");\n        b.put(513324, \"九龙县\");\n        b.put(513325, \"雅江县\");\n        b.put(513326, \"道孚县\");\n        b.put(513327, \"炉霍县\");\n        b.put(513328, \"甘孜县\");\n        b.put(513329, \"新龙县\");\n        b.put(513330, \"德格县\");\n        b.put(513331, \"白玉县\");\n        b.put(513332, \"石渠县\");\n        b.put(513333, \"色达县\");\n        b.put(513334, \"理塘县\");\n        b.put(513335, \"巴塘县\");\n        b.put(513336, \"乡城县\");\n        b.put(513337, \"稻城县\");\n        b.put(513338, \"得荣县\");\n        b.put(513400, \"凉山彝族自治州\");\n        b.put(513401, \"西昌市\");\n        b.put(513422, \"木里藏族自治县\");\n        b.put(513423, \"盐源县\");\n        b.put(513424, \"德昌县\");\n        b.put(513425, \"会理县\");\n        b.put(513426, \"会东县\");\n        b.put(513427, \"宁南县\");\n        b.put(513428, \"普格县\");\n        b.put(513429, \"布拖县\");\n        b.put(513430, \"金阳县\");\n        b.put(513431, \"昭觉县\");\n        b.put(513432, \"喜德县\");\n        b.put(513433, \"冕宁县\");\n        b.put(513434, \"越西县\");\n        b.put(513435, \"甘洛县\");\n        b.put(513436, \"美姑县\");\n        b.put(513437, \"雷波县\");\n        b.put(520000, \"贵州省\");\n        b.put(520100, \"贵阳市\");\n        b.put(520102, \"南明区\");\n        b.put(520103, \"云岩区\");\n        b.put(520111, \"花溪区\");\n        b.put(520112, \"乌当区\");\n        b.put(520113, \"白云区\");\n        b.put(520115, \"观山湖区\");\n        b.put(520121, \"开阳县\");\n        b.put(520122, \"息烽县\");\n        b.put(520123, \"修文县\");\n        b.put(520181, \"清镇市\");\n        b.put(520200, \"六盘水市\");\n        b.put(520201, \"钟山区\");\n        b.put(520203, \"六枝特区\");\n        b.put(520221, \"水城县\");\n        b.put(520281, \"盘州市\");\n        b.put(520300, \"遵义市\");\n        b.put(520302, \"红花岗区\");\n        b.put(520303, \"汇川区\");\n        b.put(520304, \"播州区\");\n        b.put(520322, \"桐梓县\");\n        b.put(520323, \"绥阳县\");\n        b.put(520324, \"正安县\");\n        b.put(520325, \"道真仡佬族苗族自治县\");\n        b.put(520326, \"务川仡佬族苗族自治县\");\n        b.put(520327, \"凤冈县\");\n        b.put(520328, \"湄潭县\");\n        b.put(520329, \"余庆县\");\n        b.put(520330, \"习水县\");\n        b.put(520381, \"赤水市\");\n        b.put(520382, \"仁怀市\");\n        b.put(520400, \"安顺市\");\n        b.put(520402, \"西秀区\");\n        b.put(520403, \"平坝区\");\n        b.put(520422, \"普定县\");\n        b.put(520423, \"镇宁布依族苗族自治县\");\n        b.put(520424, \"关岭布依族苗族自治县\");\n        b.put(520425, \"紫云苗族布依族自治县\");\n        b.put(520500, \"毕节市\");\n        b.put(520502, \"七星关区\");\n        b.put(520521, \"大方县\");\n        b.put(520522, \"黔西县\");\n        b.put(520523, \"金沙县\");\n        b.put(520524, \"织金县\");\n        b.put(520525, \"纳雍县\");\n        b.put(520526, \"威宁彝族回族苗族自治县\");\n        b.put(520527, \"赫章县\");\n        b.put(520600, \"铜仁市\");\n        b.put(520602, \"碧江区\");\n        b.put(520603, \"万山区\");\n        b.put(520621, \"江口县\");\n        b.put(520622, \"玉屏侗族自治县\");\n        b.put(520623, \"石阡县\");\n        b.put(520624, \"思南县\");\n        b.put(520625, \"印江土家族苗族自治县\");\n        b.put(520626, \"德江县\");\n        b.put(520627, \"沿河土家族自治县\");\n        b.put(520628, \"松桃苗族自治县\");\n        b.put(522300, \"黔西南布依族苗族自治州\");\n        b.put(522301, \"兴义市\");\n        b.put(522322, \"兴仁县\");\n        b.put(522323, \"普安县\");\n        b.put(522324, \"晴隆县\");\n        b.put(522325, \"贞丰县\");\n        b.put(522326, \"望谟县\");\n        b.put(522327, \"册亨县\");\n        b.put(522328, \"安龙县\");\n        b.put(522600, \"黔东南苗族侗族自治州\");\n        b.put(522601, \"凯里市\");\n        b.put(522622, \"黄平县\");\n        b.put(522623, \"施秉县\");\n        b.put(522624, \"三穗县\");\n        b.put(522625, \"镇远县\");\n        b.put(522626, \"岑巩县\");\n        b.put(522627, \"天柱县\");\n        b.put(522628, \"锦屏县\");\n        b.put(522629, \"剑河县\");\n        b.put(522630, \"台江县\");\n        b.put(522631, \"黎平县\");\n        b.put(522632, \"榕江县\");\n        b.put(522633, \"从江县\");\n        b.put(522634, \"雷山县\");\n        b.put(522635, \"麻江县\");\n        b.put(522636, \"丹寨县\");\n        b.put(522700, \"黔南布依族苗族自治州\");\n        b.put(522701, \"都匀市\");\n        b.put(522702, \"福泉市\");\n        b.put(522722, \"荔波县\");\n        b.put(522723, \"贵定县\");\n        b.put(522725, \"瓮安县\");\n        b.put(522726, \"独山县\");\n        b.put(522727, \"平塘县\");\n        b.put(522728, \"罗甸县\");\n        b.put(522729, \"长顺县\");\n        b.put(522730, \"龙里县\");\n        b.put(522731, \"惠水县\");\n        b.put(522732, \"三都水族自治县\");\n        b.put(530000, \"云南省\");\n        b.put(530100, \"昆明市\");\n        b.put(530102, \"五华区\");\n        b.put(530103, \"盘龙区\");\n        b.put(530111, \"官渡区\");\n        b.put(530112, \"西山区\");\n        b.put(530113, \"东川区\");\n        b.put(530114, \"呈贡区\");\n        b.put(530115, \"晋宁区\");\n        b.put(530124, \"富民县\");\n        b.put(530125, \"宜良县\");\n        b.put(530126, \"石林彝族自治县\");\n        b.put(530127, \"嵩明县\");\n        b.put(530128, \"禄劝彝族苗族自治县\");\n        b.put(530129, \"寻甸回族彝族自治县\");\n        b.put(530181, \"安宁市\");\n        b.put(530300, \"曲靖市\");\n        b.put(530302, \"麒麟区\");\n        b.put(530303, \"沾益区\");\n        b.put(530304, \"马龙区\");\n        b.put(530322, \"陆良县\");\n        b.put(530323, \"师宗县\");\n        b.put(530324, \"罗平县\");\n        b.put(530325, \"富源县\");\n        b.put(530326, \"会泽县\");\n        b.put(530381, \"宣威市\");\n        b.put(530400, \"玉溪市\");\n        b.put(530402, \"红塔区\");\n        b.put(530403, \"江川区\");\n        b.put(530422, \"澄江县\");\n        b.put(530423, \"通海县\");\n        b.put(530424, \"华宁县\");\n        b.put(530425, \"易门县\");\n        b.put(530426, \"峨山彝族自治县\");\n        b.put(530427, \"新平彝族傣族自治县\");\n        b.put(530428, \"元江哈尼族彝族傣族自治县\");\n        b.put(530500, \"保山市\");\n        b.put(530502, \"隆阳区\");\n        b.put(530521, \"施甸县\");\n        b.put(530523, \"龙陵县\");\n        b.put(530524, \"昌宁县\");\n        b.put(530581, \"腾冲市\");\n        b.put(530600, \"昭通市\");\n        b.put(530602, \"昭阳区\");\n        b.put(530621, \"鲁甸县\");\n        b.put(530622, \"巧家县\");\n        b.put(530623, \"盐津县\");\n        b.put(530624, \"大关县\");\n        b.put(530625, \"永善县\");\n        b.put(530626, \"绥江县\");\n        b.put(530627, \"镇雄县\");\n        b.put(530628, \"彝良县\");\n        b.put(530629, \"威信县\");\n        b.put(530681, \"水富市\");\n        b.put(530700, \"丽江市\");\n        b.put(530702, \"古城区\");\n        b.put(530721, \"玉龙纳西族自治县\");\n        b.put(530722, \"永胜县\");\n        b.put(530723, \"华坪县\");\n        b.put(530724, \"宁蒗彝族自治县\");\n        b.put(530800, \"普洱市\");\n        b.put(530802, \"思茅区\");\n        b.put(530821, \"宁洱哈尼族彝族自治县\");\n        b.put(530822, \"墨江哈尼族自治县\");\n        b.put(530823, \"景东彝族自治县\");\n        b.put(530824, \"景谷傣族彝族自治县\");\n        b.put(530825, \"镇沅彝族哈尼族拉祜族自治县\");\n        b.put(530826, \"江城哈尼族彝族自治县\");\n        b.put(530827, \"孟连傣族拉祜族佤族自治县\");\n        b.put(530828, \"澜沧拉祜族自治县\");\n        b.put(530829, \"西盟佤族自治县\");\n        b.put(530900, \"临沧市\");\n        b.put(530902, \"临翔区\");\n        b.put(530921, \"凤庆县\");\n        b.put(530922, \"云县\");\n        b.put(530923, \"永德县\");\n        b.put(530924, \"镇康县\");\n        b.put(530925, \"双江拉祜族佤族布朗族傣族自治县\");\n        b.put(530926, \"耿马傣族佤族自治县\");\n        b.put(530927, \"沧源佤族自治县\");\n        b.put(532300, \"楚雄彝族自治州\");\n        b.put(532301, \"楚雄市\");\n        b.put(532322, \"双柏县\");\n        b.put(532323, \"牟定县\");\n        b.put(532324, \"南华县\");\n        b.put(532325, \"姚安县\");\n        b.put(532326, \"大姚县\");\n        b.put(532327, \"永仁县\");\n        b.put(532328, \"元谋县\");\n        b.put(532329, \"武定县\");\n        b.put(532331, \"禄丰县\");\n        b.put(532500, \"红河哈尼族彝族自治州\");\n        b.put(532501, \"个旧市\");\n        b.put(532502, \"开远市\");\n        b.put(532503, \"蒙自市\");\n        b.put(532504, \"弥勒市\");\n        b.put(532523, \"屏边苗族自治县\");\n        b.put(532524, \"建水县\");\n        b.put(532525, \"石屏县\");\n        b.put(532527, \"泸西县\");\n        b.put(532528, \"元阳县\");\n        b.put(532529, \"红河县\");\n        b.put(532530, \"金平苗族瑶族傣族自治县\");\n        b.put(532531, \"绿春县\");\n        b.put(532532, \"河口瑶族自治县\");\n        b.put(532600, \"文山壮族苗族自治州\");\n        b.put(532601, \"文山市\");\n        b.put(532622, \"砚山县\");\n        b.put(532623, \"西畴县\");\n        b.put(532624, \"麻栗坡县\");\n        b.put(532625, \"马关县\");\n        b.put(532626, \"丘北县\");\n        b.put(532627, \"广南县\");\n        b.put(532628, \"富宁县\");\n        b.put(532800, \"西双版纳傣族自治州\");\n        b.put(532801, \"景洪市\");\n        b.put(532822, \"勐海县\");\n        b.put(532823, \"勐腊县\");\n        b.put(532900, \"大理白族自治州\");\n        b.put(532901, \"大理市\");\n        b.put(532922, \"漾濞彝族自治县\");\n        b.put(532923, \"祥云县\");\n        b.put(532924, \"宾川县\");\n        b.put(532925, \"弥渡县\");\n        b.put(532926, \"南涧彝族自治县\");\n        b.put(532927, \"巍山彝族回族自治县\");\n        b.put(532928, \"永平县\");\n        b.put(532929, \"云龙县\");\n        b.put(532930, \"洱源县\");\n        b.put(532931, \"剑川县\");\n        b.put(532932, \"鹤庆县\");\n        b.put(533100, \"德宏傣族景颇族自治州\");\n        b.put(533102, \"瑞丽市\");\n        b.put(533103, \"芒市\");\n        b.put(533122, \"梁河县\");\n        b.put(533123, \"盈江县\");\n        b.put(533124, \"陇川县\");\n        b.put(533300, \"怒江傈僳族自治州\");\n        b.put(533301, \"泸水市\");\n        b.put(533323, \"福贡县\");\n        b.put(533324, \"贡山独龙族怒族自治县\");\n        b.put(533325, \"兰坪白族普米族自治县\");\n        b.put(533400, \"迪庆藏族自治州\");\n        b.put(533401, \"香格里拉市\");\n        b.put(533422, \"德钦县\");\n        b.put(533423, \"维西傈僳族自治县\");\n        b.put(540000, \"西藏自治区\");\n        b.put(540100, \"拉萨市\");\n        b.put(540102, \"城关区\");\n        b.put(540103, \"堆龙德庆区\");\n        b.put(540104, \"达孜区\");\n        b.put(540121, \"林周县\");\n        b.put(540122, \"当雄县\");\n        b.put(540123, \"尼木县\");\n        b.put(540124, \"曲水县\");\n        b.put(540127, \"墨竹工卡县\");\n        b.put(540200, \"日喀则市\");\n        b.put(540202, \"桑珠孜区\");\n        b.put(540221, \"南木林县\");\n        b.put(540222, \"江孜县\");\n        b.put(540223, \"定日县\");\n        b.put(540224, \"萨迦县\");\n        b.put(540225, \"拉孜县\");\n        b.put(540226, \"昂仁县\");\n        b.put(540227, \"谢通门县\");\n        b.put(540228, \"白朗县\");\n        b.put(540229, \"仁布县\");\n        b.put(540230, \"康马县\");\n        b.put(540231, \"定结县\");\n        b.put(540232, \"仲巴县\");\n        b.put(540233, \"亚东县\");\n        b.put(540234, \"吉隆县\");\n        b.put(540235, \"聂拉木县\");\n        b.put(540236, \"萨嘎县\");\n        b.put(540237, \"岗巴县\");\n        b.put(540300, \"昌都市\");\n        b.put(540302, \"卡若区\");\n        b.put(540321, \"江达县\");\n        b.put(540322, \"贡觉县\");\n        b.put(540323, \"类乌齐县\");\n        b.put(540324, \"丁青县\");\n        b.put(540325, \"察雅县\");\n        b.put(540326, \"八宿县\");\n        b.put(540327, \"左贡县\");\n        b.put(540328, \"芒康县\");\n        b.put(540329, \"洛隆县\");\n        b.put(540330, \"边坝县\");\n        b.put(540400, \"林芝市\");\n        b.put(540402, \"巴宜区\");\n        b.put(540421, \"工布江达县\");\n        b.put(540422, \"米林县\");\n        b.put(540423, \"墨脱县\");\n        b.put(540424, \"波密县\");\n        b.put(540425, \"察隅县\");\n        b.put(540426, \"朗县\");\n        b.put(540500, \"山南市\");\n        b.put(540502, \"乃东区\");\n        b.put(540521, \"扎囊县\");\n        b.put(540522, \"贡嘎县\");\n        b.put(540523, \"桑日县\");\n        b.put(540524, \"琼结县\");\n        b.put(540525, \"曲松县\");\n        b.put(540526, \"措美县\");\n        b.put(540527, \"洛扎县\");\n        b.put(540528, \"加查县\");\n        b.put(540529, \"隆子县\");\n        b.put(540530, \"错那县\");\n        b.put(540531, \"浪卡子县\");\n        b.put(540600, \"那曲市\");\n        b.put(540602, \"色尼区\");\n        b.put(540621, \"嘉黎县\");\n        b.put(540622, \"比如县\");\n        b.put(540623, \"聂荣县\");\n        b.put(540624, \"安多县\");\n        b.put(540625, \"申扎县\");\n        b.put(540626, \"索县\");\n        b.put(540627, \"班戈县\");\n        b.put(540628, \"巴青县\");\n        b.put(540629, \"尼玛县\");\n        b.put(540630, \"双湖县\");\n        b.put(542500, \"阿里地区\");\n        b.put(542521, \"普兰县\");\n        b.put(542522, \"札达县\");\n        b.put(542523, \"噶尔县\");\n        b.put(542524, \"日土县\");\n        b.put(542525, \"革吉县\");\n        b.put(542526, \"改则县\");\n        b.put(542527, \"措勤县\");\n        b.put(610000, \"陕西省\");\n        b.put(610100, \"西安市\");\n        b.put(610102, \"新城区\");\n        b.put(610103, \"碑林区\");\n        b.put(610104, \"莲湖区\");\n        b.put(610111, \"灞桥区\");\n        b.put(610112, \"未央区\");\n        b.put(610113, \"雁塔区\");\n        b.put(610114, \"阎良区\");\n        b.put(610115, \"临潼区\");\n        b.put(610116, \"长安区\");\n        b.put(610117, \"高陵区\");\n        b.put(610118, \"鄠邑区\");\n        b.put(610122, \"蓝田县\");\n        b.put(610124, \"周至县\");\n        b.put(610200, \"铜川市\");\n        b.put(610202, \"王益区\");\n        b.put(610203, \"印台区\");\n        b.put(610204, \"耀州区\");\n        b.put(610222, \"宜君县\");\n        b.put(610300, \"宝鸡市\");\n        b.put(610302, \"渭滨区\");\n        b.put(610303, \"金台区\");\n        b.put(610304, \"陈仓区\");\n        b.put(610322, \"凤翔县\");\n        b.put(610323, \"岐山县\");\n        b.put(610324, \"扶风县\");\n        b.put(610326, \"眉县\");\n        b.put(610327, \"陇县\");\n        b.put(610328, \"千阳县\");\n        b.put(610329, \"麟游县\");\n        b.put(610330, \"凤县\");\n        b.put(610331, \"太白县\");\n        b.put(610400, \"咸阳市\");\n        b.put(610402, \"秦都区\");\n        b.put(610403, \"杨陵区\");\n        b.put(610404, \"渭城区\");\n        b.put(610422, \"三原县\");\n        b.put(610423, \"泾阳县\");\n        b.put(610424, \"乾县\");\n        b.put(610425, \"礼泉县\");\n        b.put(610426, \"永寿县\");\n        b.put(610428, \"长武县\");\n        b.put(610429, \"旬邑县\");\n        b.put(610430, \"淳化县\");\n        b.put(610431, \"武功县\");\n        b.put(610481, \"兴平市\");\n        b.put(610482, \"彬州市\");\n        b.put(610500, \"渭南市\");\n        b.put(610502, \"临渭区\");\n        b.put(610503, \"华州区\");\n        b.put(610522, \"潼关县\");\n        b.put(610523, \"大荔县\");\n        b.put(610524, \"合阳县\");\n        b.put(610525, \"澄城县\");\n        b.put(610526, \"蒲城县\");\n        b.put(610527, \"白水县\");\n        b.put(610528, \"富平县\");\n        b.put(610581, \"韩城市\");\n        b.put(610582, \"华阴市\");\n        b.put(610600, \"延安市\");\n        b.put(610602, \"宝塔区\");\n        b.put(610603, \"安塞区\");\n        b.put(610621, \"延长县\");\n        b.put(610622, \"延川县\");\n        b.put(610623, \"子长县\");\n        b.put(610625, \"志丹县\");\n        b.put(610626, \"吴起县\");\n        b.put(610627, \"甘泉县\");\n        b.put(610628, \"富县\");\n        b.put(610629, \"洛川县\");\n        b.put(610630, \"宜川县\");\n        b.put(610631, \"黄龙县\");\n        b.put(610632, \"黄陵县\");\n        b.put(610700, \"汉中市\");\n        b.put(610702, \"汉台区\");\n        b.put(610703, \"南郑区\");\n        b.put(610722, \"城固县\");\n        b.put(610723, \"洋县\");\n        b.put(610724, \"西乡县\");\n        b.put(610725, \"勉县\");\n        b.put(610726, \"宁强县\");\n        b.put(610727, \"略阳县\");\n        b.put(610728, \"镇巴县\");\n        b.put(610729, \"留坝县\");\n        b.put(610730, \"佛坪县\");\n        b.put(610800, \"榆林市\");\n        b.put(610802, \"榆阳区\");\n        b.put(610803, \"横山区\");\n        b.put(610822, \"府谷县\");\n        b.put(610824, \"靖边县\");\n        b.put(610825, \"定边县\");\n        b.put(610826, \"绥德县\");\n        b.put(610827, \"米脂县\");\n        b.put(610828, \"佳县\");\n        b.put(610829, \"吴堡县\");\n        b.put(610830, \"清涧县\");\n        b.put(610831, \"子洲县\");\n        b.put(610881, \"神木市\");\n        b.put(610900, \"安康市\");\n        b.put(610902, \"汉滨区\");\n        b.put(610921, \"汉阴县\");\n        b.put(610922, \"石泉县\");\n        b.put(610923, \"宁陕县\");\n        b.put(610924, \"紫阳县\");\n        b.put(610925, \"岚皋县\");\n        b.put(610926, \"平利县\");\n        b.put(610927, \"镇坪县\");\n        b.put(610928, \"旬阳县\");\n        b.put(610929, \"白河县\");\n        b.put(611000, \"商洛市\");\n        b.put(611002, \"商州区\");\n        b.put(611021, \"洛南县\");\n        b.put(611022, \"丹凤县\");\n        b.put(611023, \"商南县\");\n        b.put(611024, \"山阳县\");\n        b.put(611025, \"镇安县\");\n        b.put(611026, \"柞水县\");\n        b.put(620000, \"甘肃省\");\n        b.put(620100, \"兰州市\");\n        b.put(620102, \"城关区\");\n        b.put(620103, \"七里河区\");\n        b.put(620104, \"西固区\");\n        b.put(620105, \"安宁区\");\n        b.put(620111, \"红古区\");\n        b.put(620121, \"永登县\");\n        b.put(620122, \"皋兰县\");\n        b.put(620123, \"榆中县\");\n        b.put(620200, \"嘉峪关市\");\n        b.put(620300, \"金昌市\");\n        b.put(620302, \"金川区\");\n        b.put(620321, \"永昌县\");\n        b.put(620400, \"白银市\");\n        b.put(620402, \"白银区\");\n        b.put(620403, \"平川区\");\n        b.put(620421, \"靖远县\");\n        b.put(620422, \"会宁县\");\n        b.put(620423, \"景泰县\");\n        b.put(620500, \"天水市\");\n        b.put(620502, \"秦州区\");\n        b.put(620503, \"麦积区\");\n        b.put(620521, \"清水县\");\n        b.put(620522, \"秦安县\");\n        b.put(620523, \"甘谷县\");\n        b.put(620524, \"武山县\");\n        b.put(620525, \"张家川回族自治县\");\n        b.put(620600, \"武威市\");\n        b.put(620602, \"凉州区\");\n        b.put(620621, \"民勤县\");\n        b.put(620622, \"古浪县\");\n        b.put(620623, \"天祝藏族自治县\");\n        b.put(620700, \"张掖市\");\n        b.put(620702, \"甘州区\");\n        b.put(620721, \"肃南裕固族自治县\");\n        b.put(620722, \"民乐县\");\n        b.put(620723, \"临泽县\");\n        b.put(620724, \"高台县\");\n        b.put(620725, \"山丹县\");\n        b.put(620800, \"平凉市\");\n        b.put(620802, \"崆峒区\");\n        b.put(620821, \"泾川县\");\n        b.put(620822, \"灵台县\");\n        b.put(620823, \"崇信县\");\n        b.put(620825, \"庄浪县\");\n        b.put(620826, \"静宁县\");\n        b.put(620881, \"华亭市\");\n        b.put(620900, \"酒泉市\");\n        b.put(620902, \"肃州区\");\n        b.put(620921, \"金塔县\");\n        b.put(620922, \"瓜州县\");\n        b.put(620923, \"肃北蒙古族自治县\");\n        b.put(620924, \"阿克塞哈萨克族自治县\");\n        b.put(620981, \"玉门市\");\n        b.put(620982, \"敦煌市\");\n        b.put(621000, \"庆阳市\");\n        b.put(621002, \"西峰区\");\n        b.put(621021, \"庆城县\");\n        b.put(621022, \"环县\");\n        b.put(621023, \"华池县\");\n        b.put(621024, \"合水县\");\n        b.put(621025, \"正宁县\");\n        b.put(621026, \"宁县\");\n        b.put(621027, \"镇原县\");\n        b.put(621100, \"定西市\");\n        b.put(621102, \"安定区\");\n        b.put(621121, \"通渭县\");\n        b.put(621122, \"陇西县\");\n        b.put(621123, \"渭源县\");\n        b.put(621124, \"临洮县\");\n        b.put(621125, \"漳县\");\n        b.put(621126, \"岷县\");\n        b.put(621200, \"陇南市\");\n        b.put(621202, \"武都区\");\n        b.put(621221, \"成县\");\n        b.put(621222, \"文县\");\n        b.put(621223, \"宕昌县\");\n        b.put(621224, \"康县\");\n        b.put(621225, \"西和县\");\n        b.put(621226, \"礼县\");\n        b.put(621227, \"徽县\");\n        b.put(621228, \"两当县\");\n        b.put(622900, \"临夏回族自治州\");\n        b.put(622901, \"临夏市\");\n        b.put(622921, \"临夏县\");\n        b.put(622922, \"康乐县\");\n        b.put(622923, \"永靖县\");\n        b.put(622924, \"广河县\");\n        b.put(622925, \"和政县\");\n        b.put(622926, \"东乡族自治县\");\n        b.put(622927, \"积石山保安族东乡族撒拉族自治县\");\n        b.put(623000, \"甘南藏族自治州\");\n        b.put(623001, \"合作市\");\n        b.put(623021, \"临潭县\");\n        b.put(623022, \"卓尼县\");\n        b.put(623023, \"舟曲县\");\n        b.put(623024, \"迭部县\");\n        b.put(623025, \"玛曲县\");\n        b.put(623026, \"碌曲县\");\n        b.put(623027, \"夏河县\");\n        b.put(630000, \"青海省\");\n        b.put(630100, \"西宁市\");\n        b.put(630102, \"城东区\");\n        b.put(630103, \"城中区\");\n        b.put(630104, \"城西区\");\n        b.put(630105, \"城北区\");\n        b.put(630121, \"大通回族土族自治县\");\n        b.put(630122, \"湟中县\");\n        b.put(630123, \"湟源县\");\n        b.put(630200, \"海东市\");\n        b.put(630202, \"乐都区\");\n        b.put(630203, \"平安区\");\n        b.put(630222, \"民和回族土族自治县\");\n        b.put(630223, \"互助土族自治县\");\n        b.put(630224, \"化隆回族自治县\");\n        b.put(630225, \"循化撒拉族自治县\");\n        b.put(632200, \"海北藏族自治州\");\n        b.put(632221, \"门源回族自治县\");\n        b.put(632222, \"祁连县\");\n        b.put(632223, \"海晏县\");\n        b.put(632224, \"刚察县\");\n        b.put(632300, \"黄南藏族自治州\");\n        b.put(632321, \"同仁县\");\n        b.put(632322, \"尖扎县\");\n        b.put(632323, \"泽库县\");\n        b.put(632324, \"河南蒙古族自治县\");\n        b.put(632500, \"海南藏族自治州\");\n        b.put(632521, \"共和县\");\n        b.put(632522, \"同德县\");\n        b.put(632523, \"贵德县\");\n        b.put(632524, \"兴海县\");\n        b.put(632525, \"贵南县\");\n        b.put(632600, \"果洛藏族自治州\");\n        b.put(632621, \"玛沁县\");\n        b.put(632622, \"班玛县\");\n        b.put(632623, \"甘德县\");\n        b.put(632624, \"达日县\");\n        b.put(632625, \"久治县\");\n        b.put(632626, \"玛多县\");\n        b.put(632700, \"玉树藏族自治州\");\n        b.put(632701, \"玉树市\");\n        b.put(632722, \"杂多县\");\n        b.put(632723, \"称多县\");\n        b.put(632724, \"治多县\");\n        b.put(632725, \"囊谦县\");\n        b.put(632726, \"曲麻莱县\");\n        b.put(632800, \"海西蒙古族藏族自治州\");\n        b.put(632801, \"格尔木市\");\n        b.put(632802, \"德令哈市\");\n        b.put(632803, \"茫崖市\");\n        b.put(632821, \"乌兰县\");\n        b.put(632822, \"都兰县\");\n        b.put(632823, \"天峻县\");\n        b.put(640000, \"宁夏回族自治区\");\n        b.put(640100, \"银川市\");\n        b.put(640104, \"兴庆区\");\n        b.put(640105, \"西夏区\");\n        b.put(640106, \"金凤区\");\n        b.put(640121, \"永宁县\");\n        b.put(640122, \"贺兰县\");\n        b.put(640181, \"灵武市\");\n        b.put(640200, \"石嘴山市\");\n        b.put(640202, \"大武口区\");\n        b.put(640205, \"惠农区\");\n        b.put(640221, \"平罗县\");\n        b.put(640300, \"吴忠市\");\n        b.put(640302, \"利通区\");\n        b.put(640303, \"红寺堡区\");\n        b.put(640323, \"盐池县\");\n        b.put(640324, \"同心县\");\n        b.put(640381, \"青铜峡市\");\n        b.put(640400, \"固原市\");\n        b.put(640402, \"原州区\");\n        b.put(640422, \"西吉县\");\n        b.put(640423, \"隆德县\");\n        b.put(640424, \"泾源县\");\n        b.put(640425, \"彭阳县\");\n        b.put(640500, \"中卫市\");\n        b.put(640502, \"沙坡头区\");\n        b.put(640521, \"中宁县\");\n        b.put(640522, \"海原县\");\n        b.put(650000, \"新疆维吾尔自治区\");\n        b.put(650100, \"乌鲁木齐市\");\n        b.put(650102, \"天山区\");\n        b.put(650103, \"沙依巴克区\");\n        b.put(650104, \"新市区\");\n        b.put(650105, \"水磨沟区\");\n        b.put(650106, \"头屯河区\");\n        b.put(650107, \"达坂城区\");\n        b.put(650109, \"米东区\");\n        b.put(650121, \"乌鲁木齐县\");\n        b.put(650200, \"克拉玛依市\");\n        b.put(650202, \"独山子区\");\n        b.put(650203, \"克拉玛依区\");\n        b.put(650204, \"白碱滩区\");\n        b.put(650205, \"乌尔禾区\");\n        b.put(650400, \"吐鲁番市\");\n        b.put(650402, \"高昌区\");\n        b.put(650421, \"鄯善县\");\n        b.put(650422, \"托克逊县\");\n        b.put(650500, \"哈密市\");\n        b.put(650502, \"伊州区\");\n        b.put(650521, \"巴里坤哈萨克自治县\");\n        b.put(650522, \"伊吾县\");\n        b.put(652300, \"昌吉回族自治州\");\n        b.put(652301, \"昌吉市\");\n        b.put(652302, \"阜康市\");\n        b.put(652323, \"呼图壁县\");\n        b.put(652324, \"玛纳斯县\");\n        b.put(652325, \"奇台县\");\n        b.put(652327, \"吉木萨尔县\");\n        b.put(652328, \"木垒哈萨克自治县\");\n        b.put(652700, \"博尔塔拉蒙古自治州\");\n        b.put(652701, \"博乐市\");\n        b.put(652702, \"阿拉山口市\");\n        b.put(652722, \"精河县\");\n        b.put(652723, \"温泉县\");\n        b.put(652800, \"巴音郭楞蒙古自治州\");\n        b.put(652801, \"库尔勒市\");\n        b.put(652822, \"轮台县\");\n        b.put(652823, \"尉犁县\");\n        b.put(652824, \"若羌县\");\n        b.put(652825, \"且末县\");\n        b.put(652826, \"焉耆回族自治县\");\n        b.put(652827, \"和静县\");\n        b.put(652828, \"和硕县\");\n        b.put(652829, \"博湖县\");\n        b.put(652900, \"阿克苏地区\");\n        b.put(652901, \"阿克苏市\");\n        b.put(652922, \"温宿县\");\n        b.put(652923, \"库车县\");\n        b.put(652924, \"沙雅县\");\n        b.put(652925, \"新和县\");\n        b.put(652926, \"拜城县\");\n        b.put(652927, \"乌什县\");\n        b.put(652928, \"阿瓦提县\");\n        b.put(652929, \"柯坪县\");\n        b.put(653000, \"克孜勒苏柯尔克孜自治州\");\n        b.put(653001, \"阿图什市\");\n        b.put(653022, \"阿克陶县\");\n        b.put(653023, \"阿合奇县\");\n        b.put(653024, \"乌恰县\");\n        b.put(653100, \"喀什地区\");\n        b.put(653101, \"喀什市\");\n        b.put(653121, \"疏附县\");\n        b.put(653122, \"疏勒县\");\n        b.put(653123, \"英吉沙县\");\n        b.put(653124, \"泽普县\");\n        b.put(653125, \"莎车县\");\n        b.put(653126, \"叶城县\");\n        b.put(653127, \"麦盖提县\");\n        b.put(653128, \"岳普湖县\");\n        b.put(653129, \"伽师县\");\n        b.put(653130, \"巴楚县\");\n        b.put(653131, \"塔什库尔干塔吉克自治县\");\n        b.put(653200, \"和田地区\");\n        b.put(653201, \"和田市\");\n        b.put(653221, \"和田县\");\n        b.put(653222, \"墨玉县\");\n        b.put(653223, \"皮山县\");\n        b.put(653224, \"洛浦县\");\n        b.put(653225, \"策勒县\");\n        b.put(653226, \"于田县\");\n        b.put(653227, \"民丰县\");\n        b.put(654000, \"伊犁哈萨克自治州\");\n        b.put(654002, \"伊宁市\");\n        b.put(654003, \"奎屯市\");\n        b.put(654004, \"霍尔果斯市\");\n        b.put(654021, \"伊宁县\");\n        b.put(654022, \"察布查尔锡伯自治县\");\n        b.put(654023, \"霍城县\");\n        b.put(654024, \"巩留县\");\n        b.put(654025, \"新源县\");\n        b.put(654026, \"昭苏县\");\n        b.put(654027, \"特克斯县\");\n        b.put(654028, \"尼勒克县\");\n        b.put(654200, \"塔城地区\");\n        b.put(654201, \"塔城市\");\n        b.put(654202, \"乌苏市\");\n        b.put(654221, \"额敏县\");\n        b.put(654223, \"沙湾县\");\n        b.put(654224, \"托里县\");\n        b.put(654225, \"裕民县\");\n        b.put(654226, \"和布克赛尔蒙古自治县\");\n        b.put(654300, \"阿勒泰地区\");\n        b.put(654301, \"阿勒泰市\");\n        b.put(654321, \"布尔津县\");\n        b.put(654322, \"富蕴县\");\n        b.put(654323, \"福海县\");\n        b.put(654324, \"哈巴河县\");\n        b.put(654325, \"青河县\");\n        b.put(654326, \"吉木乃县\");\n        b.put(659001, \"石河子市\");\n        b.put(659002, \"阿拉尔市\");\n        b.put(659003, \"图木舒克市\");\n        b.put(659004, \"五家渠市\");\n        b.put(659005, \"北屯市\");\n        b.put(659006, \"铁门关市\");\n        b.put(659007, \"双河市\");\n        b.put(659008, \"可克达拉市\");\n        b.put(659009, \"昆玉市\");\n        b.put(710000, \"台湾省\");\n        b.put(810000, \"香港特别行政区\");\n        b.put(820000, \"澳门特别行政区\");\n\n        DISTRICT_CODE_MAPPING = b.build();\n        AREA_CODE_LIST = new ArrayList<>(DISTRICT_CODE_MAPPING.keySet());\n    }\n\n    /* 香港身份首字母对应数字 */\n    /*private static final Map<String, Integer> HK_FIRST_CODE = \n        new ImmutableMap.Builder<String, Integer>()\n        .put(\"A\", 1)\n        .put(\"B\", 2)\n        .put(\"C\", 3)\n        .put(\"R\", 18)\n        .put(\"U\", 21)\n        .put(\"Z\", 26)\n        .put(\"X\", 24)\n        .put(\"W\", 23)\n        .put(\"O\", 15)\n        .put(\"N\", 14)\n        .build();*/\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/ImageUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.io.Closeables;\n\nimport javax.imageio.ImageIO;\nimport javax.imageio.ImageReader;\nimport javax.imageio.stream.MemoryCacheImageInputStream;\nimport javax.swing.ImageIcon;\nimport java.awt.Graphics2D;\nimport java.awt.image.BufferedImage;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n/**\n * 图片工具类\n * \n * @author Ponfee\n */\npublic class ImageUtils {\n\n    /**\n     * 获取图片大小\n     * @param input\n     * @return [width, height]\n     */\n    public static int[] getImageSize(InputStream input) {\n        try {\n            BufferedImage image = ImageIO.read(input);\n            return new int[] { image.getWidth(), image.getHeight() };\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        } finally {\n            Closeables.console(input);\n        }\n    }\n\n    /**\n     * 横向合并图片\n     * @param format\n     * @param imgs\n     * @return\n     */\n    public static byte[] mergeHorizontal(String format, InputStream... imgs) {\n        int width = 0, height = 0;\n        try {\n            List<BufferedImage> list = new ArrayList<>();\n            for (InputStream img : imgs) {\n                BufferedImage i = ImageIO.read(img);\n                width += i.getWidth();// 图片宽度\n                height = Math.max(height, i.getHeight());\n                list.add(i);\n            }\n\n            BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);\n            width = 0;\n            for (BufferedImage i : list) {\n                int[] array = new int[i.getWidth() * i.getHeight()];// 从图片中读取RGB\n                array = i.getRGB(0, 0, i.getWidth(), i.getHeight(), array, 0, i.getWidth());\n                result.setRGB(width, 0, i.getWidth(), i.getHeight(), array, 0, i.getWidth());// 设置左半部分的RGB\n                width += i.getWidth();// 图片宽度\n                i.flush();\n            }\n            ByteArrayOutputStream out = new ByteArrayOutputStream();\n            ImageIO.write(result, format, out);\n            out.flush();\n            return out.toByteArray();\n        } catch (IOException e) {\n            throw new RuntimeException(\"图片合并失败\", e);\n        }\n    }\n\n    /**\n     * 纵向合并图片\n     * @param format  png,jpeg,gif\n     * @param imgs\n     * @return\n     */\n    public static byte[] mergeVertical(String format, InputStream... imgs) {\n        try {\n            int width = 0, height = 0;\n            List<BufferedImage> list = new ArrayList<>();\n            for (InputStream img : imgs) {\n                BufferedImage i = ImageIO.read(img);\n                height += i.getHeight();// 图片宽度\n                width = Math.max(width, i.getWidth());\n                list.add(i);\n            }\n\n            BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);\n            height = 0;\n            for (BufferedImage i : list) {\n                int[] array = new int[i.getWidth() * i.getHeight()];// 从图片中读取RGB\n                array = i.getRGB(0, 0, i.getWidth(), i.getHeight(), array, 0, i.getWidth());\n                result.setRGB(0, height, i.getWidth(), i.getHeight(), array, 0, i.getWidth());// 设置左半部分的RGB\n                height += i.getHeight();// 图片宽度\n                i.flush();\n            }\n            ByteArrayOutputStream out = new ByteArrayOutputStream();\n            ImageIO.write(result, format, out);\n            out.flush();\n            return out.toByteArray();\n        } catch (IOException e) {\n            throw new RuntimeException(\"图片合并失败\", e);\n        }\n    }\n\n    /**\n     * <pre>\n     * 图片透明处理\n     *     白    rgb:-1-->255,255,255\n     *     红    rgb:-65536-->255,0,0\n     *   透明    rgb:0-->0,0,0\n     *     红    rgb:-922812416-->255,0,0\n     *     黑    rgb:-16777216-->0,0,0\n     *     黑    rgb:-939524096-->0,0,0\n     *  </pre>\n     * @param image\n     * @return\n     */\n    public static byte[] transparent(InputStream image, int refer, int normal) {\n        try {\n            ImageIcon icon = new ImageIcon(ImageIO.read(image));\n            BufferedImage img = new BufferedImage(\n                icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_4BYTE_ABGR\n            );\n            Graphics2D g2D = (Graphics2D) img.getGraphics();\n            g2D.drawImage(icon.getImage(), 0, 0, icon.getImageObserver());\n            for (int alpha, rgb, j, i = img.getMinX(); i < img.getWidth(); i++) {\n                for (j = img.getMinY(); j < img.getHeight(); j++) {\n                    rgb = img.getRGB(i, j);\n                    if (rgb != 0) { // 0为透明\n                        if (compare(rgb, refer)) {\n                            alpha = 0; // -1为白色：255 255 255\n                        } else {\n                            alpha = normal; // 默认设置半透明\n                        }\n                        rgb = (alpha << 24) | (rgb & 0x00ffffff); // 计算rgb\n                        img.setRGB(i, j, rgb); // 重新设置rgb\n                    }\n                }\n            }\n            //g2D.drawImage(bufferedImage, 0, 0, imageIcon.getImageObserver());\n            ByteArrayOutputStream bos = new ByteArrayOutputStream();\n            ImageIO.write(img, \"png\", bos);\n            return bos.toByteArray();\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 图片类型\n     * @param img\n     * @return\n     * @throws IOException\n     */\n    public static String[] getImageType(InputStream img) throws IOException {\n        List<String> types = new ArrayList<>();\n        try (MemoryCacheImageInputStream m = new MemoryCacheImageInputStream(img)) {\n            for (Iterator<ImageReader> i = ImageIO.getImageReaders(m); i.hasNext(); ) {\n                types.add(i.next().getFormatName());\n            }\n            return types.isEmpty() ? null : types.toArray(new String[0]);\n        }\n    }\n\n    private static boolean compare(int color, int colorRange) {\n        int r = (color & 0xff0000) >> 16;\n        int g = (color & 0x00ff00) >> 8;\n        int b = (color & 0x0000ff);\n        return (r >= colorRange && g >= colorRange && b >= colorRange);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/LazyLoader.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport org.springframework.cglib.proxy.Enhancer;\nimport org.springframework.cglib.proxy.InvocationHandler;\n\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\n/**\n * Lazy loader\n *\n * @author Ponfee\n */\npublic class LazyLoader<T> implements Supplier<T> {\n\n    private final Supplier<T> loader;\n\n    private Optional<T> holder;\n\n    private LazyLoader(Supplier<T> loader) {\n        this.loader = Objects.requireNonNull(loader);\n    }\n\n    public static <T> LazyLoader<T> of(Supplier<T> loader) {\n        return new LazyLoader<>(loader);\n    }\n\n    public static <T, R extends T> R of(Class<T> type, Supplier<R> loader) {\n        return of(type, of(loader));\n    }\n\n    public static <T, A> LazyLoader<T> of(Function<A, T> loader, A arg) {\n        return new LazyLoader<>(() -> loader.apply(arg));\n    }\n\n    public static <T, A, R extends T> R of(Class<T> type, Function<A, R> loader, A arg) {\n        return of(type, of(loader, arg));\n    }\n\n    @Override\n    public T get() {\n        return holder().get();\n    }\n\n    public void orElse(T defaultValue) {\n        holder().orElse(defaultValue);\n    }\n\n    public void orElseGet(Supplier<? extends T> other) {\n        holder().orElseGet(other);\n    }\n\n    public void ifPresent(Consumer<? super T> consumer) {\n        holder().ifPresent(consumer);\n    }\n\n    // ------------------------------------------------------------------------private methods\n    private Optional<T> holder() {\n        if (holder == null) {\n            holder = Optional.ofNullable(loader.get());\n        }\n        return holder;\n    }\n\n    private static <T, R extends T> R of(Class<T> type, final LazyLoader<R> lazyLoader) {\n        Enhancer enhancer = new Enhancer();\n        enhancer.setSuperclass(type);\n        enhancer.setUseCache(true);\n        enhancer.setInterceptDuringConstruction(false);\n        //enhancer.setCallback(org.springframework.cglib.proxy.Proxy.getInvocationHandler(proxy)); // occur error\n        //enhancer.setCallback((org.springframework.cglib.proxy.MethodInterceptor) (beanProxy, method, args, methodProxy) -> method.invoke(lazyLoader.get(), args));\n        enhancer.setCallback((InvocationHandler) (beanProxy, method, args) -> method.invoke(lazyLoader.get(), args));\n        return (R) enhancer.create();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/MavenProjects.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.io.Files;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * <pre>\n *  maven标准的项目文件工具类\n *  only use in test case\n *\n *  new File(\"src/test/resources/test.txt\");\n *  new File(\"src/test/java/test/test1.java\");\n *  new File(\"src/main/resources/log4j2.xml\");\n *  new File(\"src/main/java/code/ponfee/commons/util/Asserts.java\");\n * </pre>\n *\n * @author Ponfee\n */\npublic class MavenProjects {\n\n    private static final String EXCLUSION_STRING = \"[\\r\\n]\"; // \"\\r|\\n|\\\\s+\"\n\n    public static String getProjectBaseDir() {\n        String path = Thread.currentThread().getContextClassLoader().getResource(\"\").getFile();\n        return Strings.cleanPath(new File(path).getParentFile().getParentFile().getPath());\n    }\n\n    // --------------------------------------------------------------------------------------java\n    public static File getMainJavaFile(Class<?> clazz) {\n        return new File(getMainJavaPath(\"\") + clazz.getCanonicalName().replace('.', '/') + \".java\");\n    }\n\n    public static byte[] getMainJavaFileAsBytes(Class<?> clazz) {\n        return Files.toByteArray(MavenProjects.getMainJavaFile(clazz));\n    }\n\n    public static String getMainJavaFileAsString(Class<?> clazz) {\n        try {\n            return Files.toString(MavenProjects.getMainJavaFile(clazz)).replaceAll(EXCLUSION_STRING, \"\");\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static File getTestJavaFile(Class<?> clazz) {\n        return new File(getTestJavaPath(\"\") + clazz.getCanonicalName().replace('.', '/') + \".java\");\n    }\n\n    public static byte[] getTestJavaFileAsBytes(Class<?> clazz) {\n        return Files.toByteArray(MavenProjects.getTestJavaFile(clazz));\n    }\n\n    public static String getTestJavaFileAsString(Class<?> clazz) {\n        try {\n            return Files.toString(MavenProjects.getTestJavaFile(clazz)).replaceAll(EXCLUSION_STRING, \"\");\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static String getMainJavaPath(String basePackage) {\n        return getProjectBaseDir() + \"/src/main/java/\" + basePackage.replace('.', '/');\n    }\n\n    public static String getMainJavaPath(String basePackage, String filename) {\n        return getMainJavaPath(basePackage) + \"/\" + filename;\n    }\n\n    public static String getTestJavaPath(String basePackage) {\n        return getProjectBaseDir() + \"/src/test/java/\" + basePackage.replace('.', '/');\n    }\n\n    public static String getTestJavaPath(String basePackage, String filename) {\n        return getTestJavaPath(basePackage) + \"/\" + filename;\n    }\n\n    // --------------------------------------------------------------------------------------scala\n    public static File getMainScalaFile(Class<?> clazz) {\n        return new File(getMainScalaPath(\"\") + clazz.getCanonicalName().replace('.', '/') + \".scala\");\n    }\n\n    public static byte[] getMainScalaFileAsBytes(Class<?> clazz) {\n        return Files.toByteArray(MavenProjects.getMainScalaFile(clazz));\n    }\n\n    public static String getMainScalaFileAsString(Class<?> clazz) {\n        try {\n            return Files.toString(MavenProjects.getMainScalaFile(clazz)).replaceAll(EXCLUSION_STRING, \"\");\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static File getTestScalaFile(Class<?> clazz) {\n        return new File(getTestScalaPath(\"\") + clazz.getCanonicalName().replace('.', '/') + \".scala\");\n    }\n\n    public static byte[] getTestScalaFileAsBytes(Class<?> clazz) {\n        return Files.toByteArray(MavenProjects.getTestScalaFile(clazz));\n    }\n\n    public static String getTestScalaFileAsString(Class<?> clazz) {\n        try {\n            return Files.toString(MavenProjects.getTestScalaFile(clazz)).replaceAll(EXCLUSION_STRING, \"\");\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static String getMainScalaPath(String basePackage) {\n        return getProjectBaseDir() + \"/src/main/scala/\" + basePackage.replace('.', '/');\n    }\n\n    public static String getMainScalaPath(String basePackage, String filename) {\n        return getMainScalaPath(basePackage) + \"/\" + filename;\n    }\n\n    public static String getTestScalaPath(String basePackage) {\n        return getProjectBaseDir() + \"/src/test/scala/\" + basePackage.replace('.', '/');\n    }\n\n    public static String getTestScalaPath(String basePackage, String filename) {\n        return getTestScalaPath(basePackage) + \"/\" + filename;\n    }\n\n    // --------------------------------------------------------------------------------------resources\n    public static String getMainResourcesPath() {\n        return getProjectBaseDir() + \"/src/main/resources/\";\n    }\n\n    public static String getMainResourcesPath(String followPath) {\n        return getMainResourcesPath() + followPath;\n    }\n\n    public static String getTestResourcesPath() {\n        return getProjectBaseDir() + \"/src/test/resources/\";\n    }\n\n    public static String getTestResourcesPath(String followPath) {\n        return getTestResourcesPath() + followPath;\n    }\n\n    // --------------------------------------------------------------------------------------webapp\n    public static String getWebAppPath() {\n        return getProjectBaseDir() + \"/src/main/webapp/\";\n    }\n\n    public static String getWebAppPath(String webappPath) {\n        return getWebAppPath() + webappPath;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/MessageFormats.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.collect.Maps;\n\nimport java.text.MessageFormat;\nimport java.util.*;\nimport java.util.Map.Entry;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * 消息格式化\n * \n * @author Ponfee\n */\npublic final class MessageFormats {\n\n    private static final String PREFIX = \"#\\\\{(\\\\s|\\\\t)*\";\n    private static final String SUFFIX = \"(\\\\s|\\\\t)*\\\\}\";\n    private static final Pattern PATTERN = Pattern.compile(PREFIX + \"(\\\\w+)\" + SUFFIX);\n\n    public static String format(String text, Map<String, Object> args) {\n        List<String> arguments = new ArrayList<>(args.size());\n        int i = 0;\n        for (Entry<String, Object> entry : args.entrySet()) {\n            text = text.replaceAll(PREFIX + entry.getKey() + SUFFIX, \"{\" + i++ + \"}\");\n            // toString reason：MessageFormat.format(\"{0}\", 10000) -> 10,000\n            arguments.add(Objects.toString(entry.getValue(), \"\"));\n        }\n        return MessageFormat.format(text, arguments.toArray());\n    }\n\n    public static String format(String text, Object... args) {\n        Map<String, Object> map = new HashMap<>(args.length << 1);\n        Matcher matcher = PATTERN.matcher(text);\n        for (int n = args.length, i = 0; i < n && matcher.find(); i++) {\n            map.put(matcher.group(2), args[i]);\n        }\n        return format(text, map);\n    }\n\n    public static String formatPair(String text, Object... args) {\n        return format(text, Maps.toMap(args));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Money.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.collect.Comparators;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport com.alibaba.fastjson.JSONObject;\nimport com.alibaba.fastjson.annotation.JSONType;\nimport com.alibaba.fastjson.parser.DefaultJSONParser;\nimport com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;\nimport com.alibaba.fastjson.serializer.JSONSerializer;\nimport com.alibaba.fastjson.serializer.ObjectSerializer;\nimport com.alibaba.fastjson.serializer.SerializeWriter;\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize;\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.node.BaseJsonNode;\nimport com.fasterxml.jackson.databind.node.NullNode;\nimport org.apache.commons.lang3.builder.HashCodeBuilder;\n\nimport java.io.IOException;\nimport java.io.Serializable;\nimport java.lang.reflect.Type;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.util.Currency;\nimport java.util.stream.LongStream;\n\n/**\n * Money definition based on {@link Long} numeric for minor currency unit representation.\n *\n * @author Ponfee\n */\n@JSONType(serializer = Money.Fastjson.class, deserializer = Money.Fastjson.class) // fastjson\n@JsonSerialize(using = Money.JacksonSerializer.class)     // jackson\n@JsonDeserialize(using = Money.JacksonDeserializer.class) // jackson\npublic class Money implements Serializable, Comparable<Money>, Cloneable {\n    private static final long serialVersionUID = 7743331479636754564L;\n\n    public static final String FIELD_NAME_CURRENCY = \"currency\";\n\n    public static final String FIELD_NAME_NUMBER = \"number\";\n\n    /**\n     * scaling mode, default<code>RoundingMode.HALF_EVEN</code>\n     * <p>银行家舍入法: 四舍六入，当小数为0.5时，则取最近的偶数\n     */\n    private static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN;\n\n    /**\n     * Current unit scaling factor\n     */\n    private static final int[] FACTORS = {1, 10, 100, 1000, 10000, 100000};\n\n    /**\n     * the currency\n     */\n    private final Currency currency;\n\n    /**\n     * the number\n     */\n    private long number;\n\n    /**\n     * Creates a MultiCurrencyMoney with majorUnitNumber, minorUnitNumber and currency\n     *\n     * @param currency        the currency\n     * @param majorUnitNumber the majorUnitNumber\n     * @param minorUnitNumber the minorUnitNumber\n     */\n    public Money(Currency currency, long majorUnitNumber, int minorUnitNumber) {\n        if (currency == null) {\n            throw new IllegalArgumentException(\"Currency cannot null.\");\n        }\n        // check minorUnitNumber whether overflow\n        int factor = getFactor();\n        if (minorUnitNumber >= factor) {\n            throw new RuntimeException(\"Minor[\" + minorUnitNumber + \"] must less than factor[\" + factor + \"].\");\n        }\n\n        this.currency = currency;\n        this.number = majorUnitNumber * factor + minorUnitNumber;\n    }\n\n    public Money(Currency currency, long number) {\n        if (currency == null) {\n            throw new IllegalArgumentException(\"Currency cannot null.\");\n        }\n        this.currency = currency;\n        this.number = number;\n    }\n\n    // -------------------------------------------------------------------------------------of methods\n    public static Money of(Currency currency, long number) {\n        return new Money(currency, number);\n    }\n\n    public static Money of(CurrencyEnum currencyEnum, long number) {\n        return new Money(currencyEnum.currency(), number);\n    }\n\n    public static Money of(String currencyCode, long number) {\n        return new Money(Currency.getInstance(currencyCode), number);\n    }\n\n    public Money ofMajor(CurrencyEnum currencyEnum, String majorUnitNumber, RoundingMode roundingMode) {\n        return ofMajor(currencyEnum.currency(), new BigDecimal(majorUnitNumber), roundingMode);\n    }\n\n    public Money ofMajor(String currencyCode, String majorUnitNumber, RoundingMode roundingMode) {\n        return ofMajor(Currency.getInstance(currencyCode), new BigDecimal(majorUnitNumber), roundingMode);\n    }\n\n    public Money ofMajor(Currency currency, String majorUnitNumber, RoundingMode roundingMode) {\n        return ofMajor(currency, new BigDecimal(majorUnitNumber), roundingMode);\n    }\n\n    public static Money ofMajor(CurrencyEnum currencyEnum, BigDecimal majorUnitNumber, RoundingMode roundingMode) {\n        return ofMajor(currencyEnum.currency(), majorUnitNumber, roundingMode);\n    }\n\n    public static Money ofMajor(String currencyCode, BigDecimal majorUnitNumber, RoundingMode roundingMode) {\n        return ofMajor(Currency.getInstance(currencyCode), majorUnitNumber, roundingMode);\n    }\n\n    /**\n     * Creates a new Money instance with the specified currency, major number and rounding mode.\n     *\n     * @param currency        the currency\n     * @param majorUnitNumber the major unit number\n     * @param roundingMode    the rounding mode\n     */\n    public static Money ofMajor(Currency currency, BigDecimal majorUnitNumber, RoundingMode roundingMode) {\n        long number = rounding(majorUnitNumber.movePointRight(currency.getDefaultFractionDigits()), roundingMode);\n        return new Money(currency, number);\n    }\n\n    /**\n     * Obtains an instance of {@code Money} representing zero at a specific currency.\n     * <p>\n     * For example, {@code zero(USD)} creates the instance {@code USD 0.00}.\n     *\n     * @param currency the currency, not null\n     * @return the instance representing zero, never null\n     */\n    public static Money zero(Currency currency) {\n        return new Money(currency, 0);\n    }\n\n    public static Money zero(CurrencyEnum currencyEnum) {\n        return zero(currencyEnum.currency());\n    }\n\n    public static Money zero(String currencyCode) {\n        return zero(Currency.getInstance(currencyCode));\n    }\n\n    // -------------------------------------------------------------------------------------getter/setter methods\n\n    /**\n     * Returns currency\n     *\n     * @return {@code java.util.Currency} object\n     */\n    public Currency getCurrency() {\n        return currency;\n    }\n\n    public long getNumber() {\n        return number;\n    }\n\n    /**\n     * Returns currency code\n     *\n     * @return currency code\n     */\n    public final String getCurrencyCode() {\n        return currency.getCurrencyCode();\n    }\n\n    /**\n     * Set number\n     *\n     * @param number the number\n     */\n    public void setNumber(long number) {\n        this.number = number;\n    }\n\n    /**\n     * Returns currency scaling factor\n     *\n     * @return scaling factor\n     */\n    public final int getFactor() {\n        return FACTORS[currency.getDefaultFractionDigits()];\n    }\n\n    // -------------------------------------------------------------------------------------to methods\n\n    /**\n     * Returns BigDecimal of major number string\n     *\n     * @return major number BigDecimal\n     */\n    public BigDecimal toMajorNumber() {\n        return BigDecimal.valueOf(number, currency.getDefaultFractionDigits());\n    }\n\n    /**\n     * Returns major number string\n     *\n     * @return major number string(e.g. USD$1.23 -> 1.23)\n     */\n    public String toMajorString() {\n        return toMajorNumber().toString();\n    }\n\n    // -------------------------------------------------------------------------------------基本对象方法\n\n    /**\n     * Returns the specified object is the same currency and has same amount\n     *\n     * @param other the other object\n     * @return {@code true} if equals\n     * @see java.lang.Object#equals(java.lang.Object)\n     */\n    @Override\n    public boolean equals(Object other) {\n        return (other instanceof Money) && equals((Money) other);\n    }\n\n    /**\n     * Returns boolean of equals other money object\n     *\n     * @param other the other money\n     * @return {@code true} if equals\n     */\n    public boolean equals(Money other) {\n        if (other == null) {\n            return false;\n        }\n        return currency.equals(other.currency) && (number == other.number);\n    }\n\n    /**\n     * Return this object's hash code\n     *\n     * @return int value of hash code\n     * @see java.lang.Object#hashCode()\n     */\n    @Override\n    public int hashCode() {\n        return new HashCodeBuilder().append(currency).append(number).toHashCode();\n    }\n\n    /**\n     * Returns this object's clone\n     *\n     * @see java.lang.Object#clone()\n     */\n    @Override\n    public Money clone() {\n        return new Money(currency, number);\n    }\n\n    /**\n     * Returns the string representation of this Money.\n     *\n     * @return money as string(e.g. $1.23)\n     */\n    @Override\n    public String toString() {\n        return CurrencyEnum.ofCurrency(currency).currencySymbol() + toMajorString();\n    }\n\n    /**\n     * Compares with other money object.\n     *\n     * @param other the other money.\n     * @return -1: less, 0: equals, 1: greater\n     */\n    @Override\n    public int compareTo(Money other) {\n        assertSameCurrency(other);\n        return Long.compare(number, other.number);\n    }\n\n    /**\n     * Compares is greater than other money.\n     *\n     * @param other the other money.\n     * @return {@code true} if greater than other\n     */\n    public boolean greaterThan(Money other) {\n        return compareTo(other) > Comparators.EQ;\n    }\n\n    // -------------------------------------------------------------------------------------货币算术\n\n    /**\n     * Returns new Money object of the two money addition\n     *\n     * @param other the other money.\n     * @return new Money object of the two money addition\n     */\n    public Money add(Money other) {\n        assertSameCurrency(other);\n        return create(number + other.number);\n    }\n\n    /**\n     * This money addition other money\n     *\n     * @param other the other money\n     * @return the caller money object(chain program)\n     */\n    public Money addTo(Money other) {\n        assertSameCurrency(other);\n        this.number += other.number;\n        return this;\n    }\n\n    /**\n     * Returns new Money object of the two money subtraction\n     *\n     * @param other the other money.\n     * @return new Money object of the two money subtraction\n     */\n    public Money subtract(Money other) {\n        assertSameCurrency(other);\n        return create(number - other.number);\n    }\n\n    /**\n     * This money subtraction other money\n     *\n     * @param other the other money\n     * @return the caller money object(chain program)\n     */\n    public Money subtractFrom(Money other) {\n        assertSameCurrency(other);\n        this.number -= other.number;\n        return this;\n    }\n\n    /**\n     * Returns new Money object of this money multiply value\n     *\n     * @param val the value\n     * @return new Money object of multiply result\n     */\n    public Money multiply(long val) {\n        return create(number * val);\n    }\n\n    /**\n     * This money multiply value factor\n     *\n     * @param val the value\n     * @return the caller money object(chain program)\n     */\n    public Money multiplyBy(long val) {\n        this.number *= val;\n        return this;\n    }\n\n    /**\n     * Returns new Money object of this money multiply value\n     *\n     * @param val the value\n     * @return new Money object of multiply result\n     */\n    public Money multiply(BigDecimal val) {\n        return multiply(val, DEFAULT_ROUNDING_MODE);\n    }\n\n    /**\n     * This money multiply value factor\n     *\n     * @param val the value\n     * @return the caller money object(chain program)\n     */\n    public Money multiplyBy(BigDecimal val) {\n        return multiplyBy(val, DEFAULT_ROUNDING_MODE);\n    }\n\n    /**\n     * Returns new Money object of this money multiply value\n     *\n     * @param val          the value\n     * @param roundingMode the rounding mode\n     * @return new Money object of multiply result\n     */\n    public Money multiply(BigDecimal val, RoundingMode roundingMode) {\n        return create(rounding(BigDecimal.valueOf(number).multiply(val), roundingMode));\n    }\n\n    /**\n     * This money multiply value factor\n     *\n     * @param val          the value\n     * @param roundingMode the rounding mode\n     * @return the caller money object(chain program)\n     */\n    public Money multiplyBy(BigDecimal val, RoundingMode roundingMode) {\n        this.number = rounding(BigDecimal.valueOf(number).multiply(val), roundingMode);\n        return this;\n    }\n\n    public Money divide(BigDecimal val) {\n        return divide(val, DEFAULT_ROUNDING_MODE);\n    }\n\n    public Money divide(BigDecimal val, RoundingMode roundingMode) {\n        return create(BigDecimal.valueOf(number).divide(val, roundingMode).longValue());\n    }\n\n    public Money divideBy(BigDecimal val) {\n        return divideBy(val, DEFAULT_ROUNDING_MODE);\n    }\n\n    public Money divideBy(BigDecimal val, RoundingMode roundingMode) {\n        this.number = BigDecimal.valueOf(number).divide(val, roundingMode).longValue();\n        return this;\n    }\n\n    // -------------------------------------------------------------------------------------slice\n\n    /**\n     * Average slice this money to segment part\n     *\n     * @param segment the segment\n     * @return Money array\n     */\n    public Money[] slice(int segment) {\n        Money[] results = new Money[segment];\n\n        long low = number / segment, high = low + 1;\n        int remainder = (int) number % segment;\n\n        for (int i = 0; i < remainder; i++) {\n            results[i] = create(high);\n        }\n\n        for (int i = remainder; i < segment; i++) {\n            results[i] = create(low);\n        }\n\n        return results;\n    }\n\n    /**\n     * Slice this money with specified ratios\n     *\n     * @param ratios the ratio\n     * @return Money array\n     */\n    public Money[] slice(long[] ratios) {\n        Money[] results = new Money[ratios.length];\n\n        long total = LongStream.of(ratios).sum();\n        long remainder = number;\n        for (int i = 0; i < results.length; i++) {\n            results[i] = create((number * ratios[i]) / total);\n            remainder -= results[i].number;\n        }\n\n        for (int i = 0; i < remainder; i++) {\n            results[i].number++;\n        }\n\n        return results;\n    }\n\n    // -------------------------------------------------------------------------------------private methods\n\n    private void assertSameCurrency(Money other) {\n        if (!currency.equals(other.currency)) {\n            throw new IllegalArgumentException(\"Money math currency mismatch.\");\n        }\n    }\n\n    private Money create(long number) {\n        return new Money(currency, number);\n    }\n\n    /**\n     * Returns BigDecimal to long value with specified rounding mode.\n     *\n     * @param val          the BigDecimal value\n     * @param roundingMode the rounding mode\n     * @return long value\n     */\n    private static long rounding(BigDecimal val, RoundingMode roundingMode) {\n        return val.setScale(0, roundingMode).longValue();\n    }\n\n    // -------------------------------------------------------------------------------------custom fastjson deserialize\n\n    /**\n     * Custom deserialize Money based fastjson.\n     */\n    public static class Fastjson implements ObjectSerializer, ObjectDeserializer {\n        @Override\n        public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {\n            SerializeWriter writer = serializer.getWriter();\n            if (object == null) {\n                serializer.writeNull();\n            } else {\n                Money money = (Money) object;\n                writer.write(\"{\\\"\" + FIELD_NAME_CURRENCY + \"\\\":\\\"\");\n                writer.write(money.getCurrency().getCurrencyCode());\n                writer.write(\"\\\",\\\"\" + FIELD_NAME_NUMBER + \"\\\":\");\n                writer.writeLong(money.getNumber());\n                writer.write(\"}\");\n            }\n        }\n\n        @Override\n        public Money deserialze(DefaultJSONParser parser, Type type, Object fieldName) {\n            if (GenericUtils.getRawType(type) != Money.class) {\n                throw new UnsupportedOperationException(\"Cannot supported deserialize type: \" + type);\n            }\n            JSONObject jsonObject = parser.parseObject();\n            String currencyCode = jsonObject.getString(FIELD_NAME_CURRENCY);\n            long number = jsonObject.getLongValue(FIELD_NAME_NUMBER);\n            return new Money(CurrencyEnum.ofCurrencyCode(currencyCode).currency(), number);\n        }\n\n        @Override\n        public int getFastMatchToken() {\n            return 0 /*JSONToken.RBRACKET*/;\n        }\n    }\n\n    // -------------------------------------------------------------------------------------custom jackson serializer & deserialize\n\n    /**\n     * Custom serialize Money based jackson.\n     */\n    public static class JacksonSerializer extends JsonSerializer<Money> {\n        @Override\n        public void serialize(Money money, JsonGenerator generator, SerializerProvider serializerProvider) throws IOException {\n            if (money == null) {\n                generator.writeNull();\n            } else {\n                generator.writeStartObject();\n                generator.writeStringField(FIELD_NAME_CURRENCY, money.getCurrencyCode());\n                generator.writeNumberField(FIELD_NAME_NUMBER, money.getNumber());\n                generator.writeEndObject();\n            }\n        }\n    }\n\n    /**\n     * Custom deserialize Money based jackson.\n     */\n    public static class JacksonDeserializer extends JsonDeserializer<Money> {\n        @Override\n        public Money deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {\n            BaseJsonNode jsonNode = parser.readValueAsTree();\n            if (jsonNode == null || jsonNode instanceof NullNode) {\n                return null;\n            }\n            String currencyCode = jsonNode.required(FIELD_NAME_CURRENCY).textValue();\n            long number = jsonNode.required(FIELD_NAME_NUMBER).longValue();\n            return new Money(CurrencyEnum.ofCurrencyCode(currencyCode).currency(), number);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Networks.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.net.*;\nimport java.util.Enumeration;\n\n/**\n * 网络工具类\n * <pre>\n *  isAnyLocalAddress  通配符地址        IPv4的通配符地址是0.0.0.0\n *  isLoopbackAddress  回环地址          IPv4的的范围是127.0.0.0 ~ 127.255.255.255    IPv6的是0:0:0:0:0:0:0:1,也可以简写成::1\n *  isLinkLocalAddress 本地连接地址       IPv4的的范围是169.254.0.0 ~ 169.254.255.255  IPv6的前12位是FE8，其他的位可以是任意取值\n *  isSiteLocalAddress 地区本地地址       IPv4的分为三段:10.0.0.0 ~ 10.255.255.255等   IPv6的地区本地地址的前12位是FEC，其他的位可以是任意取值\n *  isMulticastAddress 广播地址          IPv4的范围是224.0.0.0 ~ 239.255.255.255     IPv6的第一个字节是FF，其他的字节可以是任意值\n *  isMCGlobal         全球范围的广播地址\n *  isMCLinkLocal      子网广播地址\n *  isMCNodeLocal      本地接口广播地址\n *  isMCOrgLocal       组织范围的广播地址\n *  isMCSiteLocal      站点范围的广播地址\n * </pre>\n *\n * @author Ponfee\n */\npublic final class Networks {\n\n    private static final Logger LOG = LoggerFactory.getLogger(Networks.class);\n\n    private static final String LOCALHOST_IP = \"127.0.0.1\";\n    private static final String LOCALHOST_NAME = \"localhost\";\n    private static final String EMPTY_IP = \"0.0.0.0\";\n\n    /**\n     * the max ip value\n     * <p>toLong(\"255.255.255.255\")\n     */\n    public static final long MAX_IP_VALUE = (1L << 32) - 1;\n\n    /**\n     * 掩码\n     */\n    private static final long[] MASK = {0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF};\n\n    /**\n     * local ip\n     */\n    public static final String HOST_IP = getHostIp();\n\n    /**\n     * getMachineNetworkFlag 获取机器的MAC或者IP，优先获取MAC\n     *\n     * @param ia InetAddress\n     * @return mac or ip\n     */\n    public static String getMacOrIp(InetAddress ia) {\n        if (ia == null) {\n            try {\n                ia = InetAddress.getLocalHost();\n            } catch (UnknownHostException e) {\n                throw new RuntimeException(e);\n            }\n        }\n        String mac = getMacAddress(ia);\n        return StringUtils.isBlank(mac) ? getIpAddress(ia) : mac;\n    }\n\n    /**\n     * 获取指定地址的mac地址，不指定默认取本机的mac地址\n     *\n     * @param ia InetAddress\n     * @return mac or ip\n     */\n    public static String getMacAddress(InetAddress ia) {\n        byte[] mac;\n        try {\n            if (ia == null) {\n                ia = InetAddress.getLocalHost();\n            }\n            mac = NetworkInterface.getByInetAddress(ia).getHardwareAddress();\n        } catch (SocketException | UnknownHostException e) {\n            throw new RuntimeException(e);\n        }\n\n        if (mac == null) {\n            return \"\";\n        }\n\n        StringBuilder sb = new StringBuilder(17);\n        for (int i = 0; i < mac.length; i++) {\n            if (i != 0) {\n                sb.append(\"-\");\n            }\n            sb.append(Bytes.toHex(mac[i], false));\n        }\n        return sb.toString();\n    }\n\n    public static String getHostIp() {\n        InetAddress address = getHostAddress();\n        return address == null ? LOCALHOST_IP : address.getHostAddress();\n    }\n\n    public static String getHostName() {\n        InetAddress address = getHostAddress();\n        return address == null ? LOCALHOST_NAME : address.getHostName();\n    }\n\n\n    /**\n     * Check the port is available\n     *\n     * @param port 待测试端口\n     * @return if @{code true} is available, else unavailable\n     */\n    public static boolean isAvailablePort(int port) {\n        try (ServerSocket ss = new ServerSocket(port)) {\n            return true;\n        } catch (IOException ignored) {\n            return false;\n        }\n    }\n\n    /**\n     * Returns this server available port\n     *\n     * @param startPort\n     * @return if -1 then not find available port\n     * else returns available port\n     */\n    public static int findAvailablePort(int startPort) {\n        if (startPort < 0 || startPort > 65535) {\n            return -1;\n        }\n\n        for (int port = startPort; port <= 65535; port++) {\n            if (isAvailablePort(port)) {\n                return port;\n            }\n        }\n\n        for (int port = startPort - 1; port >= 0; port--) {\n            if (isAvailablePort(port)) {\n                return port;\n            }\n        }\n\n        return -1;\n    }\n\n    /**\n     * Convert ipv4 to long，max value is 4294967295\n     *\n     * @param ip the ip address\n     * @return\n     */\n    public static long toLong(String ip) {\n        if (!RegexUtils.isIpv4(ip)) {\n            throw new IllegalArgumentException(\"invalid ip address[\" + ip + \"]\");\n        }\n        String[] ipNums = ip.split(\"\\\\.\", 4);\n        return (Long.parseLong(ipNums[0]) << 24)\n             + (Long.parseLong(ipNums[1]) << 16)\n             + (Long.parseLong(ipNums[2]) <<  8)\n             + (Long.parseLong(ipNums[3])      );\n    }\n\n    /**\n     * Convert long value to ipv4 address string\n     *\n     * @param ip\n     * @return\n     */\n    public static String fromLong(long ip) {\n        return new StringBuilder(15)\n            .append((ip & MASK[0]) >> 24).append('.')\n            .append((ip & MASK[1]) >> 16).append('.')\n            .append((ip & MASK[2]) >>  8).append('.')\n            .append((ip & MASK[3])      ).toString();\n    }\n\n    /**\n     * 获取指定地址的ip地址，不指定默认取本机的ip地址\n     *\n     * @param ia InetAddress\n     * @return mac or ip\n     */\n    private static String getIpAddress(InetAddress ia) {\n        return ia.getHostAddress();\n    }\n\n    private static InetAddress getHostAddress() {\n        InetAddress localAddress = null;\n        try {\n            localAddress = InetAddress.getLocalHost();\n            if (isValidHostAddress(localAddress)) {\n                return localAddress;\n            }\n        } catch (Exception e) {\n            LOG.warn(\"Failed to get local host address: {} \", e.getMessage());\n        }\n\n        try {\n            Enumeration<NetworkInterface> inters = NetworkInterface.getNetworkInterfaces();\n            if (inters == null) {\n                return localAddress;\n            }\n            while (inters.hasMoreElements()) {\n                try {\n                    Enumeration<InetAddress> addresses = inters.nextElement().getInetAddresses();\n                    while (addresses.hasMoreElements()) {\n                        try {\n                            InetAddress address = addresses.nextElement();\n                            if (isValidHostAddress(address)) {\n                                return address;\n                            }\n                        } catch (Exception e) {\n                            LOG.warn(\"Failed to get host address: {}\", e.getMessage());\n                        }\n                    }\n                } catch (Exception e) {\n                    LOG.warn(\"Failed to get network address: {}\", e.getMessage());\n                }\n            }\n        } catch (Exception e) {\n            LOG.warn(\"Failed to get network interface: {}\", e.getMessage());\n        }\n\n        LOG.warn(\"Could not get host ip address, will use 127.0.0.1 instead.\");\n        return localAddress;\n    }\n\n    /**\n     * Returns the host address is valid\n     *\n     * @param address\n     * @return if {@code true} is valid, else invalid\n     */\n    private static boolean isValidHostAddress(InetAddress address) {\n        if (address == null || address.isLoopbackAddress()) {\n            return false;\n        }\n        String ip = address.getHostAddress();\n        return ip != null\n            && !EMPTY_IP.equals(ip)\n            && !LOCALHOST_IP.equals(ip)\n            && RegexUtils.isIpv4(ip);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/ObjectUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.base.PrimitiveTypes;\nimport cn.ponfee.commons.date.JavaUtilDateFormat;\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.reflect.Fields;\nimport com.google.common.base.Preconditions;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.EnumUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.builder.ToStringStyle;\n\nimport java.lang.reflect.Array;\nimport java.text.ParseException;\nimport java.util.*;\n\nimport static cn.ponfee.commons.collect.Comparators.*;\nimport static org.apache.commons.lang3.builder.ToStringBuilder.reflectionToString;\n\n/**\n * Object utilities\n *\n * @author Ponfee\n */\npublic final class ObjectUtils {\n\n    private static final char[] URL_SAFE_BASE64_CODES =\n        \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\".toCharArray();\n\n    /**\n     * Returns object toString\n     *\n     * @param obj the target object\n     * @return the string of object\n     */\n    public static String toString(Object obj) {\n        return toString(obj, \"null\");\n    }\n\n    public static String toString(Object obj, String defaultStr) {\n        return (obj == null)\n               ? defaultStr\n               : reflectionToString(obj, ToStringStyle.JSON_STYLE);\n    }\n\n    /**\n     * Compare two object numerically\n     *\n     * @param a the object a\n     * @param b the object b\n     * @return 0(a==b), 1(a>b), -1(a<b)\n     */\n    public static int compare(Object a, Object b) {\n        if (a == b) {\n            return EQ;\n        }\n        if (a == null) {\n            // null last\n            return GT;\n        }\n        if (b == null) {\n            // null last\n            return LT;\n        }\n\n        if ((a instanceof Comparable) && (b instanceof Comparable)) {\n            if (a.getClass().isInstance(b)) {\n                // a class is super class\n                return ((Comparable) a).compareTo(b);\n            } else if (b.getClass().isInstance(a)) {\n                // b class is super class\n                return ((Comparable) b).compareTo(a);\n            }\n        }\n\n        // Fields.addressOf\n        int res = Integer.compare(System.identityHashCode(a.getClass()), System.identityHashCode(b.getClass()));\n        return res != EQ ? res : Integer.compare(System.identityHashCode(a), System.identityHashCode(b));\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> Class<T> typeOf(T obj) {\n        return obj != null ? (Class<T>) obj.getClass() : null;\n    }\n\n    /**\n     * Returns <tt>true</tt> if this object value is complex json type(Object or Array)\n     *\n     * @param value the value\n     * @return {@code true} if the value is complex json type\n     */\n    public static boolean isComplexType(Object value) {\n        if (value == null) {\n            return false;\n        }\n\n        return value instanceof Map        // Object\n            || value instanceof List       // List\n            || value.getClass().isArray(); // Array\n    }\n\n    /**\n     * 判断对象是否为空\n     *\n     * @param o the object\n     * @return {@code true} is empty\n     */\n    public static boolean isEmpty(Object o) {\n        if (o == null) {\n            return true;\n        }\n        if (o instanceof CharSequence) {\n            return ((CharSequence) o).length() == 0;\n        }\n        if (o instanceof Collection) {\n            return ((Collection<?>) o).isEmpty();\n        }\n        if (o.getClass().isArray()) {\n            return Array.getLength(o) == 0;\n        }\n        if (o instanceof Map) {\n            return ((Map<?, ?>) o).isEmpty();\n        }\n        if (o instanceof Dictionary) {\n            return ((Dictionary<?, ?>) o).isEmpty();\n        }\n        return false;\n    }\n\n    /**\n     * Gets the target's name value\n     *\n     * @param obj  the object\n     * @param name the field name\n     * @return a value\n     */\n    public static Object getValue(Object obj, String name) {\n        if (obj == null) {\n            return null;\n        }\n        if (obj instanceof Map) {\n            return ((Map<?, ?>) obj).get(name);\n        }\n        if (obj instanceof Dictionary) {\n            return ((Dictionary<?, ?>) obj).get(name);\n        }\n        return Fields.get(obj, name);\n    }\n\n    /**\n     * Returns target type value from origin value cast\n     *\n     * @param value source object\n     * @param type  target object type\n     * @return target type object\n     *\n     * @see com.alibaba.fastjson.util.TypeUtils#castToInt(Object)\n     */\n    @SuppressWarnings({ \"unchecked\", \"rawtypes\" })\n    public static <T> T cast(Object value, Class<T> type) {\n        if (type.isInstance(value)) {\n            return (T) value;\n        }\n\n        PrimitiveOrWrapperConvertors convertor = PrimitiveOrWrapperConvertors.of(type);\n        if (convertor != null) {\n            return convertor.to(value);\n        }\n\n        if (value == null) {\n            return null;\n        }\n\n        if (type.isEnum()) {\n            return (value instanceof Number)\n                 ? type.getEnumConstants()[((Number) value).intValue()]\n                 : (T) EnumUtils.getEnumIgnoreCase((Class<Enum>) type, value.toString());\n        }\n\n        if (Date.class == type) {\n            if (value instanceof Number) {\n                return (T) new Date(((Number) value).longValue());\n            }\n            String text = value.toString();\n            if (StringUtils.isNumeric(text) && !RegexUtils.isDatePattern(text)) {\n                return (T) new Date(Numbers.toLong(text));\n            }\n            try {\n                return (T) JavaUtilDateFormat.DEFAULT.parse(text);\n            } catch (ParseException e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n        return ClassUtils.newInstance(type, new Object[]{value.toString()});\n    }\n\n    /**\n     * 获取堆栈信息\n     *\n     * @param deepPath the deep path\n     * @return stack trace\n     */\n    public static String getStackTrace(int deepPath) {\n        // 获取当前方法\n        // Method currentMethod = new Object() {}.getClass().getEnclosingMethod();\n        StackTraceElement[] traces = Thread.currentThread().getStackTrace();\n        if (traces.length <= deepPath) {\n            return \"warning: out of stack trace.\";\n        }\n        return traces[deepPath].toString();\n    }\n\n    public static String getStackTrace() {\n        return buildStackTrace(Thread.currentThread().getStackTrace());\n    }\n\n    public static String getStackTrace(Thread thread) {\n        return buildStackTrace(thread.getStackTrace());\n    }\n\n    private static String buildStackTrace(StackTraceElement[] traces) {\n        StringBuilder builder = new StringBuilder();\n        for (int i = 2, n = traces.length; i < n; i++) {\n            builder.append(\"--\\t\").append(traces[i].toString()).append(\"\\n\");\n        }\n        return builder.toString();\n    }\n\n    /**\n     * Copies source fields value to target fields\n     *\n     * @param source the source\n     * @param target the target\n     * @param fields the fields of String array\n     */\n    public static <T> void copy(T source, T target, String... fields) {\n        Preconditions.checkState(ArrayUtils.isNotEmpty(fields));\n        for (String field : fields) {\n            Fields.put(target, field, Fields.get(source, field));\n        }\n    }\n\n    /**\n     * Returns a new instance of copy from spec fields\n     *\n     * @param source the source\n     * @param fields the fields\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T copyOf(T source, String... fields) {\n        Preconditions.checkState(ArrayUtils.isNotEmpty(fields));\n        T target = (T) newInstance(source.getClass());\n        copy(source, target, fields);\n        return target;\n    }\n\n    /**\n     * Returns a new instance of type\n     *\n     * @param type the type class\n     * @return a new instance\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T newInstance(Class<T> type) {\n        if (Map.class == type) {\n            return (T) new HashMap<>();\n        }\n        if (Set.class == type) {\n            return (T) new HashSet<>();\n        }\n        if (Collection.class == type || List.class == type) {\n            return (T) new ArrayList<>();\n        }\n        if (Dictionary.class == type) {\n            return (T) new Hashtable<>();\n        }\n        if (type.isPrimitive() || PrimitiveTypes.isWrapperType(type)) {\n            Class<?> wrapper = PrimitiveTypes.ofPrimitiveOrWrapper(type).wrapper();\n            // new Boolean(\"0\") -> false\n            return (T) ClassUtils.newInstance(wrapper, new Class<?>[]{String.class}, new Object[]{\"0\"});\n        }\n        return ClassUtils.newInstance(type);\n    }\n\n    /**\n     * Returns the type is not a bean type\n     *\n     * @param type the type class\n     * @return {@code true} assert is not a bean type\n     */\n    public static boolean isNotBeanType(Class<?> type) {\n        return null == type\n            || Object.class == type\n            || type.isArray()\n            || PrimitiveTypes.ofPrimitiveOrWrapper(type) != null\n            || CharSequence.class.isAssignableFrom(type)\n            || Map.class.isAssignableFrom(type)\n            || Dictionary.class.isAssignableFrom(type)\n            || Enumeration.class.isAssignableFrom(type)\n            || Iterable.class.isAssignableFrom(type)\n            || Iterator.class.isAssignableFrom(type);\n    }\n\n    // -------------------------------------------------------------------------- private class\n    private enum PrimitiveOrWrapperConvertors {\n\n        BOOLEAN(boolean.class) {\n            @Override\n            public Boolean to(Object value) {\n                return Numbers.toBoolean(value);\n            }\n        },\n\n        WRAP_BOOLEAN(Boolean.class) {\n            @Override\n            public Boolean to(Object value) {\n                return Numbers.toWrapBoolean(value);\n            }\n        },\n\n        BYTE(byte.class) {\n            @Override\n            public Byte to(Object value) {\n                return Numbers.toByte(value);\n            }\n        },\n\n        WRAP_BYTE(Byte.class) {\n            @Override\n            public Byte to(Object value) {\n                return Numbers.toWrapByte(value);\n            }\n        },\n\n        SHORT(short.class) {\n            @Override\n            public Short to(Object value) {\n                return Numbers.toShort(value);\n            }\n        },\n\n        WRAP_SHORT(Short.class) {\n            @Override\n            public Short to(Object value) {\n                return Numbers.toWrapShort(value);\n            }\n        },\n\n        CHAR(char.class) {\n            @Override\n            public Character to(Object value) {\n                return Numbers.toChar(value);\n            }\n        },\n\n        WRAP_CHAR(Character.class) {\n            @Override\n            public Character to(Object value) {\n                return Numbers.toWrapChar(value);\n            }\n        },\n\n        INT(int.class) {\n            @Override\n            public Integer to(Object value) {\n                return Numbers.toInt(value);\n            }\n        },\n\n        WRAP_INT(Integer.class) {\n            @Override\n            public Integer to(Object value) {\n                return Numbers.toWrapInt(value);\n            }\n        },\n\n        LONG(long.class) {\n            @Override\n            public Long to(Object value) {\n                return Numbers.toLong(value);\n            }\n        },\n\n        WRAP_LONG(Long.class) {\n            @Override\n            public Long to(Object value) {\n                return Numbers.toWrapLong(value);\n            }\n        },\n\n        FLOAT(float.class) {\n            @Override\n            public Float to(Object value) {\n                return Numbers.toFloat(value);\n            }\n        },\n\n        WRAP_FLOAT(Float.class) {\n            @Override\n            public Float to(Object value) {\n                return Numbers.toWrapFloat(value);\n            }\n        },\n\n        DOUBLE(double.class) {\n            @Override\n            public Double to(Object value) {\n                return Numbers.toDouble(value);\n            }\n        },\n\n        WRAP_DOUBLE(Double.class) {\n            @Override\n            public Double to(Object value) {\n                return Numbers.toWrapDouble(value);\n            }\n        };\n\n        private static final Map<Class<?>, PrimitiveOrWrapperConvertors> MAPPING =\n                Enums.toMap(PrimitiveOrWrapperConvertors.class, PrimitiveOrWrapperConvertors::type);\n\n        private final Class<?> type;\n\n        PrimitiveOrWrapperConvertors(Class<?> type) {\n            this.type = type;\n        }\n\n        abstract <T> T to(Object value);\n\n        public Class<?> type() {\n            return this.type;\n        }\n\n        static PrimitiveOrWrapperConvertors of(Class<?> targetType) {\n            return MAPPING.get(targetType);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/PropertiesUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.reflect.Fields;\n\nimport java.lang.reflect.Field;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Properties;\n\n/**\n * Properties Utility\n *\n * @author Ponfee\n */\npublic final class PropertiesUtils {\n\n    public static Properties filterProperties(Properties props, String keyPrefix) {\n        Properties properties = new Properties();\n        int prefixLen = keyPrefix.length();\n        props.forEach((k, v) -> {\n            String key = k.toString();\n            if (key.startsWith(keyPrefix)) {\n                properties.put(key.substring(prefixLen), v);\n            }\n        });\n        return properties;\n    }\n\n    public static <T> T extract(Properties props, Class<T> beanType, String prefix, char... separators) {\n        T bean = ClassUtils.newInstance(beanType);\n        List<Field> fields = Objects.requireNonNull(ClassUtils.listFields(beanType));\n        for (Field field : fields) {\n            for (char separator : separators) {\n                String name = prefix + Strings.toSeparatedName(field.getName(), separator);\n                if (props.containsKey(name)) {\n                    Fields.put(bean, field, ObjectUtils.cast(props.get(name), field.getType()));\n                    break;\n                }\n            }\n        }\n        return bean;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/RegexUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport com.google.common.cache.CacheBuilder;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * 正则工具类\n * http://blog.csdn.net/carechere/article/details/52315728\n * \n * @author Ponfee\n */\npublic final class RegexUtils {\n\n    /**\n     * Username regexp\n     */\n    private static final Pattern PATTERN_USERNAME = Pattern.compile(\"^[0-9A-Za-z_\\\\-]{4,20}$\");\n\n    private static final String SYMBOL = \"@#!%&_\\\\.\\\\?\\\\-\\\\$\\\\^\\\\*\";\n\n    /**\n     * Password regexp\n     */\n    private static final Pattern PATTERN_PASSWORD = Pattern.compile(\"^((?=.*\\\\d)(?=.*[A-Za-z])|(?=.*\\\\d)(?=.*[\" + SYMBOL + \"])|(?=.*[A-Za-z])(?=.*[\" + SYMBOL + \"]))[\\\\dA-Za-z\" + SYMBOL + \"]{8,20}$\");\n\n    /**\n     * Mobile phone regexp\n     */\n    private static final Pattern PATTERN_MOBILE = Pattern.compile(\"^\\\\s*(((\\\\+)?86)|(\\\\((\\\\+)?86\\\\)))?1\\\\d{10}\\\\s*$\");\n\n    /**\n     * 中国电信号码格式验证 手机段： 133,153,180,181,189,177,1700,173,199\n     **/\n    private static final Pattern CHINA_TELECOM_PATTERN = Pattern.compile(\"(^1(33|53|77|73|99|8[019])\\\\d{8}$)|(^1700\\\\d{7}$)\");\n\n    /**\n     * 中国联通号码格式验证 手机段：130,131,132,155,156,185,186,145,176,1709\n     **/\n    private static final Pattern CHINA_UNICOM_PATTERN = Pattern.compile(\"(^1(3[0-2]|4[5]|5[56]|7[6]|8[56])\\\\d{8}$)|(^1709\\\\d{7}$)\");\n\n    /**\n     * 中国移动号码格式验证\n     * 手机段：134,135,136,137,138,139,150,151,152,157,158,159,182,183,184,187,188,147,178,1705\n     **/\n    private static final Pattern CHINA_MOBILE_PATTERN = Pattern.compile(\"(^1(3[4-9]|4[7]|5[0-27-9]|7[8]|8[2-478])\\\\d{8}$)|(^1705\\\\d{7}$)\");\n\n    /**\n     * Email regexp\n     */\n    private static final Pattern PATTERN_EMAIL = Pattern.compile(\"^\\\\w+((-\\\\w+)|(\\\\.\\\\w+))*\\\\@[A-Za-z0-9]+((\\\\.|-)[A-Za-z0-9]+)*\\\\.[A-Za-z0-9]+$\");\n\n    /**\n     * IP V4 pattern\n     */\n    private static final Pattern PATTERN_IPV4 = Pattern.compile(\"(?:(?:2[0-4][0-9]\\\\.)|(?:25[0-5]\\\\.)|(?:1[0-9][0-9]\\\\.)|(?:[1-9][0-9]\\\\.)|(?:[0-9]\\\\.)){3}(?:(?:2[0-4][0-9])|(?:25[0-5])|(?:1[0-9][0-9])|(?:[1-9][0-9])|(?:[0-9]))\");\n\n    /**\n     * IP V4 pattern\n     */\n    private static final Pattern PATTERN_IPV6 = Pattern.compile(\"^([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,5}((:[0-9a-fA-F]{1,4}){1,2}|:)|([0-9a-fA-F]{1,4}:){1,4}((:[0-9a-fA-F]{1,4}){1,3}|:)|([0-9a-fA-F]{1,4}:){1,3}((:[0-9a-fA-F]{1,4}){1,4}|:)|([0-9a-fA-F]{1,4}:){1,2}((:[0-9a-fA-F]{1,4}){1,5}|:)|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6}|:)|:((:[0-9a-fA-F]{1,4}){1,7}|:)\");\n\n    /**\n     * yyyyMMdd(HHmmss(SSS))\n     */\n    private static final Pattern PATTERN_DATE = Pattern.compile(\"^([1-9]\\\\d{3}((0[1-9]|1[012])(0[1-9]|1\\\\d|2[0-8])|(0[13456789]|1[012])(29|30)|(0[13578]|1[02])31)|(([2-9]\\\\d)(0[48]|[2468][048]|[13579][26])|(([2468][048]|[3579][26])00))0229)(([0-1][0-9]|2[0-3])([0-5][0-9])([0-5][0-9])(\\\\d{3})?)?$\");\n\n    private static final LoadingCache<String, Pattern> PATTERNS = CacheBuilder.newBuilder().softValues().build(\n        new CacheLoader<String, Pattern>() {\n            @Override\n            public Pattern load(String pattern) {\n                return Pattern.compile(pattern/*, Pattern.CASE_INSENSITIVE*/);\n            }\n        }\n    );\n\n    /**\n     * Finds the first match string from originalStr use regex\n     * \n     * @param originalStr the origin str\n     * @param regex       the regex\n     * @return the first match string\n     */\n    public static String findFirst(String originalStr, String regex) {\n        return findGroup(originalStr, regex, 0);\n    }\n\n    public static String findGroup(String originalStr, String regex, int group) {\n        if (originalStr == null || regex == null) {\n            return StringUtils.EMPTY;\n        }\n\n        try {\n            Matcher matcher = PATTERNS.get(regex).matcher(originalStr);\n            return matcher.find() ? matcher.group(group) : StringUtils.EMPTY;\n        } catch (ExecutionException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static boolean matches(String originalStr, String regex) {\n        if (originalStr == null || regex == null) {\n            return false;\n        }\n\n        try {\n            return PATTERNS.get(regex).matcher(originalStr).matches();\n        } catch (ExecutionException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * check is china mobile phone\n     * @param text\n     * @return {@code true} is mobile phone\n     */\n    public static boolean isMobilePhone(String text) {\n        return text != null && PATTERN_MOBILE.matcher(text).matches();\n    }\n\n    /**\n     * 校验是否邮箱地址\n     * @param text\n     * @return {@code true} is email address\n     */\n    public static boolean isEmail(String text) {\n        return text != null && PATTERN_EMAIL.matcher(text).matches();\n    }\n\n    /**\n     * 校验是否ipv4地址\n     *\n     * @param text\n     * @return {@code true} is ipv4 address\n     */\n    public static boolean isIpv4(String text) {\n        return text != null && PATTERN_IPV4.matcher(text).matches();\n    }\n\n    /**\n     * 校验是否ipv6地址\n     *\n     * @param text\n     * @return {@code true} is ipv6 address\n     */\n    public static boolean isIpv6(String text) {\n        return text != null && PATTERN_IPV6.matcher(text).matches();\n    }\n\n    /**\n     * 校验是否是有效的用户名\n     * 数据库用户名字段最好不要区分大小写\n     * @param text\n     * @return {@code true} is valid user name\n     */\n    public static boolean isValidUserName(String text) {\n        return text != null && PATTERN_USERNAME.matcher(text).matches();\n    }\n\n\n    /**\n     * 校验是否是有效的密码：\n     *   > 8-20位\n     *   > 必须包含字母、数字、符号中至少2种（可选的符号包括：@#!%&_.?-$^*）\n     *   > 其它模式：^(?=.*\\\\d)(?=.*[A-Z])(?=.*[a-z])[\\\\dA-Za-z@#!%&_\\\\.\\\\?\\\\-\\\\$\\\\^\\\\*]{8,20}$\n     *            ：^(?=.*\\\\d)(?=.*[A-Za-z])[\\\\dA-Za-z@#!%&_\\\\.\\\\?\\\\-\\\\$\\\\^\\\\*]{8,20}$\n     *\n     * isValidPassword(\"12131111\") // false: 只有数字\n     * isValidPassword(\"@#.@#.$^\") // false: 只有字符\n     * isValidPassword(\"aaaaaaaa\") // false: 只有字母\n     * isValidPassword(\"121311@1\") // true: 数字字符\n     * isValidPassword(\"121311A1\") // true: 数字字母\n     * isValidPassword(\"aaaaaa.a\") // true: 字母字符\n     * @param text\n     * @return {@code true} is valid password\n     */\n    public static boolean isValidPassword(String text) {\n        return text != null && PATTERN_PASSWORD.matcher(text).matches();\n    }\n\n    /**\n     * Validates the text whether date pattern\n     * \n     * @param text the string\n     * @return if returns {@code true} then is a valid date pattern\n     */\n    public static boolean isDatePattern(String text) {\n        return text != null\n            && PATTERN_DATE.matcher(text).matches(); \n    }\n\n\n    /**\n     * 获取移动号码运营商类型\n     * \n     * @param mobilePhone the mobile phone\n     * @return 0未知；1移动；2联通；3电信；\n     */\n    public static int getPhoneCarrier(String mobilePhone) {\n        if (StringUtils.isBlank(mobilePhone)) {\n            return 0;\n        } else if (CHINA_MOBILE_PATTERN.matcher(mobilePhone).matches()) {\n            return 1;\n        } else if (CHINA_UNICOM_PATTERN.matcher(mobilePhone).matches()) {\n            return 2;\n        } else if (CHINA_TELECOM_PATTERN.matcher(mobilePhone).matches()) {\n            return 3;\n        } else {\n            return 0;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/SecureRandoms.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.reflect.Fields;\n\nimport java.math.BigInteger;\nimport java.nio.ByteBuffer;\nimport java.security.SecureRandom;\nimport java.util.UUID;\n\n/**\n * 安全随机数生成工具类\n *\n * @author Ponfee\n */\npublic final class SecureRandoms {\n\n    /**\n     * SHA1PRNG\n     */\n    private static final SecureRandom SECURE_RANDOM;\n    static {\n        ByteBuffer buffer = ByteBuffer.allocate(56);\n        buffer.putLong(System.currentTimeMillis());\n        buffer.putLong(System.nanoTime());\n        buffer.putLong(Thread.currentThread().getId());\n        buffer.putLong(Fields.addressOf(SecureRandoms.class));\n        buffer.putLong(Fields.addressOf(buffer));\n        UUID uuid = UUID.randomUUID();\n        buffer.putLong(uuid.getMostSignificantBits());\n        buffer.putLong(uuid.getLeastSignificantBits());\n        buffer.flip();\n        SECURE_RANDOM = new SecureRandom(new SecureRandom(buffer.array()).generateSeed(20));\n    }\n\n    /**\n     * random byte[] array by SecureRandom\n     * @param numOfByte\n     * @return\n     */\n    public static byte[] nextBytes(int numOfByte) {\n        byte[] bytes = new byte[numOfByte];\n        SECURE_RANDOM.nextBytes(bytes);\n        return bytes;\n    }\n\n    /**\n     * returns a pseudo random int, between 0 and bound\n     * @param bound\n     * @return\n     */\n    public static int nextInt(int bound) {\n        return SECURE_RANDOM.nextInt(bound);\n    }\n\n    public static int nextInt() {\n        return SECURE_RANDOM.nextInt();\n    }\n\n    public static long nextLong() {\n        return SECURE_RANDOM.nextLong();\n    }\n\n    public static float nextFloat() {\n        return SECURE_RANDOM.nextFloat();\n    }\n\n    public static double nextDouble() {\n        return SECURE_RANDOM.nextDouble();\n    }\n\n    public static boolean nextBoolean() {\n        return SECURE_RANDOM.nextBoolean();\n    }\n\n    /**\n     * Returns a pseudo random BigInteger specified bit length\n     *\n     * @param bitLen specified bit length\n     * @return a pseudo random BigInteger\n     */\n    public static BigInteger random(int bitLen) {\n        BigInteger rnd;\n        do {\n            rnd = new BigInteger(bitLen, SECURE_RANDOM);\n        } while (rnd.bitLength() != bitLen);\n        return rnd;\n    }\n\n    /**\n     * Returns a pseudo random BigInteger, the bit length\n     * equals mod's bit length - 1\n     *\n     * @param mod the modulo of maximum bounds\n     * @return a pseudo random BigInteger\n     */\n    public static BigInteger random(BigInteger mod) {\n        return random(mod.bitLength() - 1);\n    }\n\n    public static byte[] generateSeed(int numBytes) {\n        return SECURE_RANDOM.generateSeed(numBytes);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Snowflake.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.math.Maths;\n\n/**\n * <pre>\n * 基于snowflake算法的ID生成器\n *\n * BINARY(Long.MAX_VALUE         )=0111111111111111111111111111111111111111111111111111111111111111\n * BINARY(2039-09-07 23:47:35.551)=0000000000000000000000011111111111111111111111111111111111111111\n * \n * 0 | 0000000000 0000000000 0000000000 0000000000 0 | 00000 | 00000 | 0000000000 00\n * - | ------------------timestamp------------------ | -did- | -wid- | -----seq-----\n * \n * 00 ~ 00：1位未使用（实际上也是作为long的符号位）\n * 01 ~ 41：41位为毫秒级时间（能到“2039-09-07 23:47:35.551”，41位bit的最大Long值，超过会溢出）\n * 42 ~ 46：5位datacenterId\n * 47 ~ 51：5位workerId（并不算标识符，实际是为线程标识），\n * 52 ~ 63：12位该毫秒内的当前毫秒内的计数\n *\n * 毫秒内序列 （由datacenter和机器ID作区分），并且效率较高。经测试，\n * snowflake每秒能够产生26万ID左右，完全满足需要。\n *\n * 计算掩码的三种方式：\n *   a：(1 << bits) - 1\n *   b：-1L ^ (-1L << bits)\n *   c：Long.MAX_VALUE >>> (63 - bits)\n * </pre>\n *\n * @author Ponfee\n */\npublic final class Snowflake {\n\n    // Long.toBinaryString(Long.MAX_VALUE).length()\n    private static final int SIZE = Long.SIZE - 1; // 63位（除去最开头的一个符号位）\n    private static final long TWEPOCH = 1514736000000L; // 起始基准时间点(2018-01-01)\n\n    private final int datacenterId; // 数据中心ID\n    private final int workerId;     // 工作机器ID\n\n    private final int workerIdShift;\n    private final int datacenterIdShift;\n    private final int timestampShift;\n\n    private final long sequenceMask;\n    private final long timestampMask;\n\n    private long lastTimestamp = -1L;\n    private long sequence      = 0L;\n\n    public Snowflake(int workerId, int datacenterId,\n                     int sequenceBits, int workerIdBits,\n                     int datacenterIdBits) {\n        long maxWorkerId = Maths.bitsMask(workerIdBits);\n        if (workerId > maxWorkerId || workerId < 0) {\n            throw new IllegalArgumentException(\n                String.format(\"worker Id can't be greater than %d or less than 0\", maxWorkerId)\n            );\n        }\n\n        long maxDatacenterId = Maths.bitsMask(datacenterIdBits);\n        if (datacenterId > maxDatacenterId || datacenterId < 0) {\n            throw new IllegalArgumentException(\n                String.format(\"datacenter Id can't be greater than %d or less than 0\", maxDatacenterId)\n            );\n        }\n\n        this.workerIdShift     = sequenceBits;\n        this.datacenterIdShift = sequenceBits + workerIdBits;\n        this.timestampShift    = sequenceBits + workerIdBits + datacenterIdBits;\n\n        this.sequenceMask      = Maths.bitsMask(sequenceBits);\n        this.timestampMask     = Maths.bitsMask(SIZE - this.timestampShift);\n\n        this.workerId          = workerId;\n        this.datacenterId      = datacenterId;\n    }\n\n    /**\n     * sequenceBits: 12 bit, value range of 0 ~ 4095(111111111111)\n     * workerIdBits:  5 bit, value range of 0 ~   31(11111)\n     * datacenterIdBits: 5 bit, value range of 0 ~ 31(11111)\n     * \n     * workerIdShift: sequenceBits，左移12位(seq12位)\n     * datacenterIdShift: sequenceBits+workerIdBits，即左移17位(wid5位+seq12位)\n     * timestampShift: sequenceBits+workerIdBits+datacenterIdBits，\n     *                 即左移22位(did5位+wid5位+seq12位)\n     * timestampMask: (1L<<(MAX_SIZE-timestampShift))-1 = (1L<<41)-1\n     * \n     * @param workerId\n     * @param datacenterId\n     */\n    public Snowflake(int workerId, int datacenterId) {\n        this(workerId, datacenterId, 12, 5, 5);\n    }\n\n    /**\n     * no datacenterId\n     * max sequence count: 16384\n     * max work count    : 32\n     * max time at       : 2527-06-23 14:20:44.415\n     * \n     * @param workerId\n     */\n    public Snowflake(int workerId) {\n        this(workerId, 0, 14, 5, 0);\n    }\n\n    public synchronized long nextId() {\n        long timestamp = timeGen();\n        if (timestamp < this.lastTimestamp) {\n            // 时间戳只能单调递增\n            throw new RuntimeException(\n                String.format(\"Clock moved backwards. Refusing to generate id for %d milliseconds\", this.lastTimestamp - timestamp)\n            );\n        }\n        if (this.lastTimestamp == timestamp) {\n            // sequence递增\n            this.sequence = (this.sequence + 1) & this.sequenceMask;\n            if (this.sequence == 0) {\n                // 当前毫秒的sequence已用完，需要循环等待获取下一毫秒\n                timestamp = untilNextMillis(this.lastTimestamp);\n                this.lastTimestamp = timestamp;\n            }\n        } else {\n            // 上一毫秒的sequence未超用，当前毫秒第一次使用\n            this.sequence = 0L;\n            this.lastTimestamp = timestamp;\n        }\n\n        return (((timestamp - TWEPOCH) << this.timestampShift) & this.timestampMask)\n             | (this.datacenterId << this.datacenterIdShift)\n             | (this.workerId << this.workerIdShift)\n             | this.sequence;\n    }\n\n    /**\n     * 获取下一个时间戳毫秒，一直循环直到获取到为止\n     * \n     * @param lastTimestamp the lastTimestamp\n     * @return\n     */\n    private long untilNextMillis(long lastTimestamp) {\n        long timestamp;\n        do {\n            timestamp = timeGen();\n        } while (timestamp <= lastTimestamp);\n        return timestamp;\n    }\n\n    private long timeGen() {\n        return System.currentTimeMillis();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/SqlUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Sql utility\n * \n * @author Ponfee\n */\npublic final class SqlUtils {\n\n    public static String trim(String sql) {\n        if (StringUtils.isEmpty(sql)) {\n            return sql;\n        }\n\n        //sql = sql.replaceAll(\"\\\\s{2,}\", \" \");\n        int start = 0, end = (sql.length() - 1), n = end;\n\n        while (start < n && isBlank(sql.charAt(start))) {\n            start++;\n        }\n\n        if (start == n) {\n            return \"\";\n        }\n\n        while (end > start && isBlankOrSemicolon(sql.charAt(end))) {\n            end--;\n        }\n\n        return start == end ? \"\" : sql.substring(start, end + 1);\n    }\n\n    // --------------------------------------------------------------limit mysql\n    private static final Pattern LIMIT_MYSQL = Pattern.compile(\n        \"^(.+)(\\\\s+(?i)LIMIT\\\\s+(\\\\d+)(\\\\s*,\\\\s*(\\\\d+))?\\\\s*)$\"/*, Pattern.CASE_INSENSITIVE*/\n    );\n    public static String limitMysql(String sql, int limit) {\n        String outermostSql = outermostSql(sql);\n        Matcher matcher = LIMIT_MYSQL.matcher(outermostSql);\n        if (!matcher.matches()) {\n            return sql + \" LIMIT \" + limit;\n        }\n\n        // (LIMIT a) OR (LIMIT a, b)\n        String a = matcher.group(3), b = matcher.group(5);\n        if (Integer.parseInt(Optional.ofNullable(b).orElse(a)) <= limit) {\n            return sql;\n        }\n\n        String replace = b == null ? Integer.toString(limit) : a + \",\" + limit;\n\n        return sql.substring(0, sql.length() - outermostSql.length()) \n             + matcher.group(1) + \" LIMIT \" + replace;\n    }\n\n    // --------------------------------------------------------------limit pgsql\n    private static final Pattern LIMIT_PGSQL = Pattern.compile(\n       \"^(.+)(?i)(\\\\s+LIMIT\\\\s+(\\\\d+)(\\\\s+OFFSET\\\\s+(\\\\d+))?\\\\s*)$\"\n    );\n    public static String limitPgsql(String sql, int limit) {\n        String outermostSql = outermostSql(sql);\n        Matcher matcher = LIMIT_PGSQL.matcher(outermostSql);\n        if (!matcher.matches()) {\n            return sql + \" LIMIT \" + limit;\n        }\n\n        // (LIMIT a) OR (LIMIT a OFFSET b)\n        String a = matcher.group(3), b = matcher.group(5);\n        if (Integer.parseInt(a) <= limit) {\n            return sql;\n        }\n\n        String limitStr = limit + (b == null ? \"\" : \" OFFSET \" + b);\n\n        return sql.substring(0, sql.length() - outermostSql.length()) \n             + matcher.group(1) + \" LIMIT \" + limitStr;\n    }\n\n    // --------------------------------------------------------------limit oracle\n    // Oracle不支持ROWNUM>(=)number语法：SELECT * FROM t_table_name WHERE ROWNUM>2 AND ROWNUM<=4\n    // 因为第一条数据行号为1，不符合>2的条件所以第一行被去掉，之前的第二行又变为新的第一行，如此下去到最后一条数据也查不出来\n    // 所以此处正则表达式不考虑“ROWNUM>(=)number”的情况（因为正常oracle sql不会这么写）\n    private static final Pattern LIMIT_ORACLE = Pattern.compile(\n        \"^(.+)(\\\\s+(?i)ROWNUM\\\\s*<=?\\\\s*(\\\\d+))(\\\\s+(?i)AND\\\\s+.+|\\\\s*)$\"\n    );\n    public static String limitOracle(String sql, int limit) {\n        String outermostSql = outermostSql(sql);\n        Matcher matcher = LIMIT_ORACLE.matcher(outermostSql);\n        if (!matcher.matches()) {\n            return sql + completeWhere(outermostSql) + \" ROWNUM<\" + limit;\n        }\n\n        // ROWNUM<limit\n        String a = matcher.group(3);\n        if (Integer.parseInt(a) <= limit) {\n            return sql;\n        }\n\n        String mid = matcher.group(1), tail = matcher.group(4);\n        return sql.substring(0, sql.length() - outermostSql.length()) \n             + mid + completeWhere(mid) + \" ROWNUM<\" + limit + Strings.ifEmpty(tail, \"\");\n    }\n\n    // --------------------------------------------------------------limit microsoft sql(SQL Server)\n    private static final Pattern LIMIT_SQLSERVER = Pattern.compile(\n        // .+? / X*?：非贪婪模式/懒汉模式；\n        // (?i)：忽略大小写；\n        // (?s)：所在位置右侧的表达式开启单行模式；\n        // (?m)：所在位置右侧的表达式开启多行模式；\n        \"^(.+?)(\\\\s+(?i)TOP\\\\s+(\\\\d+)\\\\s+)(.+)$\"\n    );\n    private static final Pattern SELECT_SQLSERVER = Pattern.compile(\n        \"^(\\\\s*(?i)SELECT\\\\s)(.+)$\"\n    );\n    public static String limitMssql(String sql, int limit) {\n        Matcher matcher = LIMIT_SQLSERVER.matcher(sql);\n        if (!matcher.matches()) {\n            matcher = SELECT_SQLSERVER.matcher(sql);\n            if (!matcher.matches()) {\n                throw new IllegalArgumentException(\"Invalid select sql: \" + sql);\n            }\n            return matcher.group(1) + \"TOP \" + limit + \" \" + matcher.group(2);\n        }\n\n        String top = matcher.group(3);\n        if (Integer.parseInt(top) <= limit) {\n            return sql;\n        }\n\n        return matcher.group(1) + \" TOP \" + limit + \" \" + matcher.group(4);\n    }\n\n    // --------------------------------------------------------------limit hive\n    private static final Pattern LIMIT_HIVE = Pattern.compile(\n        \"^(.+)(\\\\s+(?i)LIMIT\\\\s+(\\\\d+)?\\\\s*)$\"/*, Pattern.CASE_INSENSITIVE*/\n    );\n    public static String limitHive(String sql, int limit) {\n        String outermostSql = outermostSql(sql);\n        Matcher matcher = LIMIT_HIVE.matcher(outermostSql);\n        if (!matcher.matches()) {\n            return sql + \" LIMIT \" + limit;\n        }\n\n        String a = matcher.group(3);\n        if (Integer.parseInt(a) <= limit) {\n            return sql;\n        }\n\n\n        return sql.substring(0, sql.length() - outermostSql.length()) \n             + matcher.group(1) + \" LIMIT \" + limit;\n    }\n\n    // --------------------------------------------------------------private methods\n    private static boolean isBlank(char ch) {\n        return ch == ' ' || ch == '\\t' || ch == '\\r' || ch == '\\n';\n    }\n\n    private static boolean isBlankOrSemicolon(char ch) {\n        return isBlank(ch) || ch == ';';\n    }\n\n    private static String outermostSql(String sql) {\n        if (StringUtils.isEmpty(sql)) {\n            return \"\";\n        }\n\n        int pos = sql.lastIndexOf(')'); // extract outermost sql string(include \")\")\n        return (pos == -1) ? sql : sql.substring(pos);\n    }\n\n    private static String completeWhere(String outermost) {\n        outermost = outermost.trim().toUpperCase();\n        if (outermost.endsWith(\" WHERE\") || outermost.endsWith(\" AND\")) {\n            return \"\";\n        }\n\n        return outermost.contains(\" WHERE \") ? \" AND \" : \" WHERE \";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Strings.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.math.Numbers;\nimport com.google.common.base.CaseFormat;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\n/**\n * 字符串工具类\n *\n * @author Ponfee\n */\npublic class Strings {\n\n    private static final char[] REGEX_SPECIALS = { '\\\\', '$', '(', ')', '*', '+', '.', '[', ']', '?', '^', '{', '}', '|' };\n\n    public static String join(Collection<?> coll) {\n        return join(coll, \",\", String::valueOf, \"\", \"\");\n    }\n\n    public static String join(Collection<?> coll, String delimiter) {\n        return join(coll, delimiter, String::valueOf, \"\", \"\");\n    }\n\n    /**\n     * Convert to hexadecimal string array\n     *\n     * @param text the text\n     * @return hexadecimal string array\n     */\n    public static String[] hexadecimal(String text) {\n        // Integer.toString(text.charAt(i), 16)\n        return IntStream.range(0, text.length())\n                        .mapToObj(i -> \"0x\" + Integer.toHexString(text.charAt(i)))\n                        .toArray(String[]::new);\n    }\n\n    /**\n     * 集合拼接为字符串<p>\n     * join(Arrays.asList(\"a\",\"b\",\"c\"), \",\", \"(\", \")\") -> (a),(b),(c)\n     *\n     * @param coll      集合对象\n     * @param delimiter 分隔符\n     * @param mapper    对象转String\n     * @param open      每个元素添加的前缀\n     * @param close     每个元素添加的后缀\n     * @return a String with joined\n     *\n     * @see org.apache.commons.collections4.IteratorUtils#toString(Iterator, org.apache.commons.collections4.Transformer, String, String, String)\n     * @see org.apache.commons.collections4.IterableUtils#toString(Iterable, org.apache.commons.collections4.Transformer, String, String, String)\n     *\n     * @see java.lang.String#join(CharSequence, CharSequence...)\n     * @see java.util.stream.Collectors#joining(CharSequence, CharSequence, CharSequence)\n     * @see org.apache.commons.lang3.StringUtils#join(List, String, int, int)\n     * @see com.google.common.base.Joiner#join(Object, Object, Object...)\n     * @see java.util.StringJoiner#StringJoiner(CharSequence, CharSequence, CharSequence)\n     */\n    public static <T> String join(Collection<T> coll, String delimiter, Function<T, String> mapper, String open, String close) {\n        if (coll == null) {\n            return null;\n        }\n        if (coll.isEmpty()) {\n            return \"\";\n        }\n\n        StringBuilder builder = new StringBuilder(128);\n        for (T o : coll) {\n            builder.append(open).append(mapper.apply(o)).append(close).append(delimiter);\n        }\n        builder.setLength(builder.length() - delimiter.length());\n        return builder.toString();\n    }\n\n    /**\n     * Parse main method args, such as: [name1=value,name2=value2,...]\n     *\n     * @param args the args\n     * @return a map object params\n     */\n    public static Map<String, String> fromArgs(String[] args) {\n        if (args == null) {\n            return null;\n        }\n        return Arrays.stream(args)\n                     .filter(s -> s != null && s.contains(\"=\"))\n                     .map(s -> s.split(\"=\", 2))\n                     .collect(Collectors.toMap(p -> p[0], p -> p[1], (v1, v2) -> v1));\n    }\n\n    public static String mask(String text, String regex, String replacement) {\n        if (text == null) {\n            return null;\n        }\n        return text.replaceAll(regex, replacement);\n    }\n\n    public static String mask(String text, int start, int len) {\n        return mask(text, start, len, '*');\n    }\n\n    /**\n     * 遮掩（如手机号中间4位加*）\n     * @param text    需要处理的字符串\n     * @param start   开始位置\n     * @param len     要处理的字数长度\n     * @param maskChar 替换的字符\n     * @return\n     */\n    public static String mask(String text, int start, int len, char maskChar) {\n        int length;\n        if (len < 1 || StringUtils.isEmpty(text)\n            || (length = text.length()) < start) {\n            return text;\n        }\n        if (start < 0) {\n            start = 0;\n        }\n        if (length < start + len) {\n            len = length - start;\n        }\n        int end = length - start - len;\n        String regex = \"(\\\\w{\" + start + \"})\\\\w{\" + len + \"}(\\\\w{\" + end + \"})\";\n        return mask(text, regex, \"$1\" + StringUtils.repeat(maskChar, len) + \"$2\");\n    }\n\n    /**\n     * Count str occur on text.\n     *\n     * @param text the text\n     * @param str  the string\n     * @return number of occur count\n     */\n    public static int count(String text, String str) {\n        int count = 0;\n        for (int len = str.length(), index=-len; (index = text.indexOf(str, index + len)) != -1; ) {\n            count++;\n        }\n        return count;\n    }\n\n    /**\n     * 字符串分片\n     * slice(\"abcdefghijklmn\", 5)  ->  [\"abc\",\"def\",\"ghi\",\"jkl\",\"mn\"]\n     * @param str\n     * @param segment\n     * @return\n     */\n    public static String[] slice(String str, int segment) {\n        int[] array = Numbers.slice(str.length(), segment);\n        String[] result = new String[array.length];\n        for (int j = 0, i = 0; i < array.length; i++) {\n            result[i] = str.substring(j, (j += array[i]));\n        }\n        return result;\n    }\n\n    /**\n     * <pre>\n     * '?' Matches any single character.\n     * '*' Matches any sequence of characters (including the empty sequence).\n     *\n     * isMatch(\"aa\",\"a\")       = false\n     * isMatch(\"aa\",\"aa\")      = true\n     * isMatch(\"aaa\",\"aa\")     = false\n     * isMatch(\"aa\", \"*\")      = true\n     * isMatch(\"aa\", \"a*\")     = true\n     * isMatch(\"ab\", \"?*\")     = true\n     * isMatch(\"aab\", \"c*a*b\") = false\n     * </pre>\n     *\n     * @param s the text\n     * @param p the wildcard pattern\n     * @return {@code true} if the string match pattern\n     */\n    public static boolean isMatch(String s, String p) {\n        // 状态 dp[i][j] : 表示 s 的前 i 个字符和 p 的前 j 个字符是否匹配 (true 的话表示匹配)\n        // 状态转移方程：\n        //      1. 当 s[i] == p[j]，或者 p[j] == ? 那么 dp[i][j] = dp[i - 1][j - 1];\n        //      2. 当 p[j] == * 那么 dp[i][j] = dp[i][j - 1] || dp[i - 1][j]    其中：\n        //      dp[i][j - 1] 表示 * 代表的是空字符，例如 ab, ab*\n        //      dp[i - 1][j] 表示 * 代表的是非空字符，例如 abcd, ab*\n        // 初始化：\n        //      1. dp[0][0] 表示什么都没有，其值为 true\n        //      2. 第一行 dp[0][j]，换句话说，s 为空，与 p 匹配，所以只要 p 开始为 * 才为 true\n        //      3. 第一列 dp[i][0]，当然全部为 false\n        int m = s.length(), n = p.length();\n        boolean[][] dp = new boolean[m + 1][n + 1];\n        dp[0][0] = true;\n        for (int i = 1; i <= n; ++i) {\n            if (p.charAt(i - 1) == '*') {\n                dp[0][i] = true;\n            } else {\n                break;\n            }\n        }\n        for (int i = 1; i <= m; ++i) {\n            for (int j = 1; j <= n; ++j) {\n                if (p.charAt(j - 1) == '*') {\n                    dp[i][j] = dp[i][j - 1] || dp[i - 1][j];\n                } else if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {\n                    dp[i][j] = dp[i - 1][j - 1];\n                }\n            }\n        }\n        return dp[m][n];\n    }\n\n    /**\n     * Returns a safe file system path that forbid access parent dir\n     *\n     * @param path the path\n     * @return a safe path\n     */\n    public static String safePath(String path) {\n        if (path == null) {\n            return null;\n        }\n        return cleanPath(path).replace(\"../\", \"\");\n    }\n\n    /**\n     * 文件路径规范化，如“path/..”内部的点号\n     * 注意：windows的文件分隔符“\\”会替换为“/”\n     *\n     * @param path 文件路径\n     * @return 规范的文件路径\n     */\n    public static String cleanPath(String path) {\n        if (path == null) {\n            return null;\n        }\n\n        String pathToUse = StringUtils.replace(path, Files.WINDOWS_FOLDER_SEPARATOR, Files.UNIX_FOLDER_SEPARATOR);\n\n        // Strip prefix from path to analyze, to not treat it as part of the\n        // first path element. This is necessary to correctly parse paths like\n        // \"file:core/../core/io/Resource.class\", where the \"..\" should just\n        // strip the first \"core\" directory while keeping the \"file:\" prefix.\n        int prefixIndex = pathToUse.indexOf(\":\");\n        String prefix = \"\";\n        if (prefixIndex != -1) {\n            prefix = pathToUse.substring(0, prefixIndex + 1);\n            if (prefix.contains(\"/\")) {\n                prefix = \"\";\n            } else {\n                pathToUse = pathToUse.substring(prefixIndex + 1);\n            }\n        }\n        if (pathToUse.startsWith(Files.UNIX_FOLDER_SEPARATOR)) {\n            prefix = prefix + Files.UNIX_FOLDER_SEPARATOR;\n            pathToUse = pathToUse.substring(1);\n        }\n\n        String[] pathArray = StringUtils.split(pathToUse, Files.UNIX_FOLDER_SEPARATOR);\n        List<String> pathElements = new LinkedList<>();\n        int tops = 0;\n\n        for (int i = pathArray.length - 1; i >= 0; i--) {\n            String element = pathArray[i];\n            if (Files.CURRENT_PATH.equals(element)) {\n                // Points to current directory - drop it.\n            } else if (Files.TOP_PATH.equals(element)) {\n                // Registering top path found.\n                tops++;\n            } else {\n                if (tops > 0) {\n                    // Merging path element with element corresponding to top path.\n                    tops--;\n                } else {\n                    // Normal path element found.\n                    pathElements.add(0, element);\n                }\n            }\n        }\n\n        // Remaining top paths need to be retained.\n        for (int i = 0; i < tops; i++) {\n            pathElements.add(0, Files.TOP_PATH);\n        }\n\n        return prefix + String.join(Files.UNIX_FOLDER_SEPARATOR, pathElements);\n    }\n\n    /**\n     * 驼峰转为带分隔符名字，如驼峰转换为下划线：CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, camelCaseName);\n     *\n     * @param camelcaseName the camelcase name\n     * @param separator     the separator\n     * @return with separator name\n     * @see CaseFormat#to(CaseFormat, String)\n     */\n    public static String toSeparatedName(String camelcaseName, char separator) {\n        if (StringUtils.isEmpty(camelcaseName)) {\n            return camelcaseName;\n        }\n\n        StringBuilder result = new StringBuilder(camelcaseName.length() << 1);\n        result.append(Character.toLowerCase(camelcaseName.charAt(0)));\n        for (int i = 1, len = camelcaseName.length(); i < len; i++) {\n            char ch = camelcaseName.charAt(i);\n            if (Character.isUpperCase(ch)) {\n                result.append(separator).append(Character.toLowerCase(ch));\n            } else {\n                result.append(ch);\n            }\n        }\n        return result.toString();\n    }\n\n    /**\n     * 带分隔符名字转驼峰，如下划线转换为驼峰：CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, underscoreName);\n     * 1  LOWER_HYPHEN       连字符的变量命名规范如lower-hyphen\n     * 2  LOWER_UNDERSCORE   c++变量命名规范如lower_underscore\n     * 3  LOWER_CAMEL        java变量命名规范如lowerCamel\n     * 4  UPPER_CAMEL        java和c++类的命名规范如UpperCamel\n     * 5  UPPER_UNDERSCORE   java和c++常量的命名规范如UPPER_UNDERSCORE\n     *\n     * @param separatedName the separated name\n     * @param separator     the separator\n     * @return camelcase name\n     * @see CaseFormat#to(CaseFormat, String)\n     */\n    public static String toCamelcaseName(String separatedName, char separator) {\n        if (StringUtils.isEmpty(separatedName)) {\n            return separatedName;\n        }\n\n        StringBuilder result = new StringBuilder(separatedName.length());\n        for (int i = 0, len = separatedName.length(); i < len; i++) {\n            char ch = separatedName.charAt(i);\n            if (separator == ch) {\n                if (++i < len) {\n                    result.append(Character.toUpperCase(separatedName.charAt(i)));\n                }\n            } else {\n                result.append(ch);\n            }\n        }\n        return result.toString();\n    }\n\n    /**\n     * 如果为空则设置默认\n     *\n     * @param str\n     * @param defaultStr\n     * @return\n     */\n    public static String ifEmpty(String str, String defaultStr) {\n        return StringUtils.isEmpty(str) ? defaultStr : str;\n    }\n\n    public static String ifBlank(String str, String defaultStr) {\n        return StringUtils.isBlank(str) ? defaultStr : str;\n    }\n\n    // ---------------------------------------------------------------------------escape\n    /**\n     * <pre>\n     * Escapes the characters in a <code>String</code> to be suitable to pass to\n     * an SQL query.\n     *\n     * For example,\n     *  statement.executeQuery(\"SELECT * FROM MOVIES WHERE TITLE='\" +\n     *  StringEscapeUtils.escapeSql(\"McHale's Navy\") +  \"'\");\n     *\n     * At present, this method only turns single-quotes into doubled single-quotes\n     * (<code>\"McHale's Navy\"</code> => <code>\"McHale''s Navy\"</code>). It does not\n     * handle the cases of percent (%) or underscore (_) for use in LIKE clauses.\n     *\n     * see http://www.jguru.com/faq/view.jsp?EID=8881\n     * </pre>\n     *\n     * @param str  the string to escape, may be null\n     * @return a new String, escaped for SQL, <code>null</code> if null string input\n     */\n    public static String escapeSql(String str) {\n        if (str == null) {\n            return null;\n        }\n        return StringUtils.replace(str, \"'\", \"''\");\n    }\n\n    /**\n     * Escape the regex characters: $()*+.[]?\\^{},|\n     *\n     * @param text the text string\n     * @return a new String, escaped for regex\n     */\n    public static String escapeRegex(String text) {\n        if (StringUtils.isBlank(text)) {\n            return text;\n        }\n\n        StringBuilder escaped = new StringBuilder(text.length() + 8);\n        char c;\n        for (int i = 0, n = text.length(); i < n; i++) {\n            c = text.charAt(i);\n            if (ArrayUtils.contains(REGEX_SPECIALS, c)) {\n                escaped.append('\\\\');\n            }\n            escaped.append(c);\n        }\n        return escaped.toString();\n    }\n\n    public static boolean containsAny(String str, List<String> searches) {\n        if (StringUtils.isEmpty(str) || CollectionUtils.isEmpty(searches)) {\n            return false;\n        }\n        for (String search : searches) {\n            if (StringUtils.contains(str, search)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    // ---------------------------------------------------------------------------csv split\n    /**\n    * Parse a CSV string using {@link #csvSplit(List,String, int, int)}\n    * use in {@link cn.ponfee.commons.web.WebContext.WebContextFilter)\n    *\n    * @param s The string to parse\n    * @return An array of parsed values.\n    */\n    public static String[] csvSplit(String s) {\n        if (s == null) {\n            return null;\n        }\n        return csvSplit(s, 0, s.length());\n    }\n\n    /**\n     * Parse a CSV string using {@link #csvSplit(List, String, int, int)}\n     *\n     * @param s The string to parse\n     * @param off The offset into the string to start parsing\n     * @param len The len in characters to parse\n     * @return An array of parsed values.\n     */\n    public static String[] csvSplit(String s, int off, int len) {\n        if (s == null) {\n            return null;\n        }\n        if (off < 0 || len < 0 || off > s.length()) {\n            throw new IllegalArgumentException();\n        }\n\n        List<String> list = new ArrayList<>();\n        csvSplit(list, s, off, len);\n        return list.toArray(new String[0]);\n    }\n\n    private enum CsvSplitState {\n        PRE_DATA, QUOTE, SLOSH, DATA, WHITE, POST_DATA\n    }\n\n    /** Split a quoted comma separated string to a list\n     * <p>Handle <a href=\"https://www.ietf.org/rfc/rfc4180.txt\">rfc4180</a>-like\n     * CSV strings, with the exceptions:<ul>\n     * <li>quoted values may contain double quotes escaped with back-slash\n     * <li>Non-quoted values are trimmed of leading trailing white space\n     * <li>trailing commas are ignored\n     * <li>double commas result in a empty string value\n     * </ul>\n     * @param list The Collection to split to (or null to get a new list)\n     * @param s The string to parse\n     * @param off The offset into the string to start parsing\n     * @param len The len in characters to parse\n     * @return list containing the parsed list values\n     */\n    public static List<String> csvSplit(List<String> list, String s, int off, int len) {\n        if (list == null) {\n            list = new ArrayList<>();\n        }\n        CsvSplitState state = CsvSplitState.PRE_DATA;\n        StringBuilder out = new StringBuilder();\n        int last = -1;\n        while (len > 0) {\n            char ch = s.charAt(off++);\n            len--;\n\n            switch (state) {\n                case PRE_DATA:\n                    if (Character.isWhitespace(ch)) {\n                        continue;\n                    }\n\n                    if ('\"' == ch) {\n                        state = CsvSplitState.QUOTE;\n                        continue;\n                    }\n\n                    if (',' == ch) {\n                        list.add(\"\");\n                        continue;\n                    }\n\n                    state = CsvSplitState.DATA;\n                    out.append(ch);\n                    continue;\n\n                case DATA:\n                    if (Character.isWhitespace(ch)) {\n                        last = out.length();\n                        out.append(ch);\n                        state = CsvSplitState.WHITE;\n                        continue;\n                    }\n\n                    if (',' == ch) {\n                        list.add(out.toString());\n                        out.setLength(0);\n                        state = CsvSplitState.PRE_DATA;\n                        continue;\n                    }\n\n                    out.append(ch);\n                    continue;\n\n                case WHITE:\n                    if (Character.isWhitespace(ch)) {\n                        out.append(ch);\n                        continue;\n                    }\n\n                    if (',' == ch) {\n                        out.setLength(last);\n                        list.add(out.toString());\n                        out.setLength(0);\n                        state = CsvSplitState.PRE_DATA;\n                        continue;\n                    }\n\n                    state = CsvSplitState.DATA;\n                    out.append(ch);\n                    last = -1;\n                    continue;\n\n                case QUOTE:\n                    if ('\\\\' == ch) {\n                        state = CsvSplitState.SLOSH;\n                        continue;\n                    }\n                    if ('\"' == ch) {\n                        list.add(out.toString());\n                        out.setLength(0);\n                        state = CsvSplitState.POST_DATA;\n                        continue;\n                    }\n                    out.append(ch);\n                    continue;\n\n                case SLOSH:\n                    out.append(ch);\n                    state = CsvSplitState.QUOTE;\n                    continue;\n\n                case POST_DATA:\n                    if (',' == ch) {\n                        state = CsvSplitState.PRE_DATA;\n                        continue;\n                    }\n                    continue;\n                default:\n                    throw new UnsupportedOperationException(\"Unsupported state \" + state);\n            }\n        }\n\n        switch (state) {\n            case PRE_DATA:\n            case POST_DATA:\n                break;\n            case DATA:\n            case QUOTE:\n            case SLOSH:\n                list.add(out.toString());\n                break;\n            case WHITE:\n                out.setLength(last);\n                list.add(out.toString());\n                break;\n            default:\n                throw new UnsupportedOperationException(\"Unsupported state \" + state);\n        }\n\n        return list;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/SynchronizedCaches.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\n/**\n * Synchronized cache\n *\n * @author Ponfee\n */\npublic final class SynchronizedCaches {\n\n    public static <K, V> V get(K key, Map<K, V> cache, Function<K, V> mapper) {\n        V val = cache.get(key);\n        if (val != null) {\n            return val;\n        }\n        synchronized (cache) {\n            if ((val = cache.get(key)) == null) {\n                if ((val = mapper.apply(key)) != null) {\n                    cache.put(key, val);\n                }\n            }\n        }\n        return val;\n    }\n\n    public static <K, V> V get(K key, Map<K, V> cache, Supplier<V> supplier) {\n        V val = cache.get(key);\n        if (val != null) {\n            return val;\n        }\n        synchronized (cache) {\n            if ((val = cache.get(key)) == null) {\n                if ((val = supplier.get()) != null) {\n                    cache.put(key, val);\n                }\n            }\n        }\n        return val;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/TimingWheel.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.PriorityBlockingQueue;\n\n/**\n * Timing wheel structure.\n *\n * @author Ponfee\n */\npublic class TimingWheel<T extends TimingWheel.Timing<T>> implements java.io.Serializable {\n    private static final long serialVersionUID = -3950831738037257527L;\n\n    /**\n     * Millis number of per second.\n     */\n    private static final int MILLIS_PER_SECOND = 1000;\n\n    /**\n     * Second number of per minute.\n     */\n    private static final int SECONDS_PER_MINUTE = 60;\n\n    /**\n     * Millis number of per minute.\n     */\n    private static final int MILLIS_PER_MINUTE = MILLIS_PER_SECOND * SECONDS_PER_MINUTE;\n\n    private final TimingQueue<T>[] ringBuffer;\n\n    public TimingWheel() {\n        this(11);\n    }\n\n    public TimingWheel(int priorityQueueInitialCapacity) {\n        TimingQueue<T>[] array = (TimingQueue<T>[]) new TimingQueue[SECONDS_PER_MINUTE];\n        // initialize 0 ~ 59 slots\n        for (int i = 0; i < SECONDS_PER_MINUTE; i++) {\n            array[i] = new TimingQueue<>(priorityQueueInitialCapacity);\n        }\n        this.ringBuffer = array;\n    }\n\n    /**\n     * Verifies the timing data\n     *\n     * @param timing the timing data\n     * @return if {@code true} verify success\n     */\n    protected boolean verify(T timing) {\n        return true;\n    }\n\n    public final boolean offer(T timing) {\n        // 毫秒在[000 ~ 900]放入下一个刻度\n        // 毫秒在[901 ~ 999]放入下两个刻度\n        return offer(timing, System.currentTimeMillis() + 1099);\n    }\n\n    /**\n     * Puts to timing wheel.\n     *\n     * @param timing          the timing data\n     * @param leastTimeMillis the least time millis\n     * @return if {@code true} put success\n     */\n    public final boolean offer(T timing, long leastTimeMillis) {\n        if (!verify(timing)) {\n            return false;\n        }\n\n        // 如果小于leastTimeMillis，则放入leastTimeMillis所在的槽位\n        long slotTimeMillis = Math.max(timing.timing(), leastTimeMillis);\n        int ringSecond = secondOfMinute(slotTimeMillis);\n        return ringBuffer[ringSecond].offer(timing);\n    }\n\n    public final List<T> poll() {\n        return poll(System.currentTimeMillis());\n    }\n\n    /**\n     * Gets from timing wheel.\n     *\n     * @param latestTimeMillis the latest time millis\n     * @return list of Timing\n     */\n    public final List<T> poll(long latestTimeMillis) {\n        List<T> ringTrigger = new ArrayList<>();\n        int ringSecond = secondOfMinute(latestTimeMillis);\n        long maximumTiming = (latestTimeMillis / MILLIS_PER_SECOND) * MILLIS_PER_SECOND + 999;\n        // process current and previous tick timingQueue\n        for (int i = 0; i < 2; i++) {\n            TimingQueue<T> ringTick = ringBuffer[(ringSecond - i + SECONDS_PER_MINUTE) % SECONDS_PER_MINUTE];\n            T first;\n            while ((first = ringTick.peek()) != null && first.timing() <= maximumTiming) {\n                first = ringTick.poll();\n\n                // if run in single thread, there code block unnecessary\n                if (first == null) {\n                    break;\n                }\n                if (first.timing() > maximumTiming) {\n                    ringTick.offer(first);\n                    break;\n                }\n\n                ringTrigger.add(first);\n            }\n        }\n        return ringTrigger;\n    }\n\n    private static int secondOfMinute(long timeMillis) {\n        return (int) ((timeMillis % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND);\n    }\n\n    /**\n     * Timing of TimingWheel elements\n     */\n    public interface Timing<T extends Timing<T>> extends Comparable<T> {\n        /**\n         * Returns millis timestamp\n         *\n         * @return millis timestamp\n         */\n        long timing();\n\n        /**\n         * Provides default compare\n         *\n         * @param other the other\n         * @return the value 0 if this == other; a value less than 0 if this < other; and a value greater than 0 if this > other\n         */\n        @Override\n        default int compareTo(T other) {\n            return Long.compare(this.timing(), other.timing());\n        }\n    }\n\n    /**\n     * Timing queue\n     *\n     * @param <T> element type\n     */\n    public static class TimingQueue<T extends Timing<T>> extends PriorityBlockingQueue<T> {\n\n        public TimingQueue() {\n            super();\n        }\n\n        public TimingQueue(int initialCapacity) {\n            super(initialCapacity);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/URLCodes.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.io.Files;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\n\n/**\n * URL encode/decode utility class.\n *\n * @author Ponfee\n */\npublic final class URLCodes {\n\n    public static String encodeURI(String url) {\n        return encodeURI(url, Files.UTF_8);\n    }\n\n    /**\n     * <pre>\n     * 相当于javascript中的encodeURI\n     * 不会被此方法编码的字符：! @ # $& * ( ) = : / ; ? + '\n     * encodeURI(\"http://www.oschina.net/search?scope=bbs&q=C语言\", \"UTF-8\") -> http://www.oschina.net/search?scope=bbs&q=C%E8%AF%AD%E8%A8%80\n     * </pre>\n     *\n     * @param url     the url string\n     * @param charset the charset\n     * @return encoded url string\n     */\n    public static String encodeURI(String url, String charset) {\n        if (url == null) {\n            return null;\n        }\n        StringBuilder builder = new StringBuilder(url.length() * 3 / 2);\n        byte[] b;\n        for (int n = url.length(), i = 0; i < n; i++) {\n            char c = url.charAt(i);\n            if (c >= 0 && c <= 255) {\n                builder.append(c);\n            } else {\n                try {\n                    b = Character.toString(c).getBytes(charset);\n                } catch (Exception ex) {\n                    b = new byte[0];\n                }\n                for (int k, j = 0; j < b.length; j++) {\n                    k = b[j];\n                    if (k < 0) {\n                        k += 256;\n                    }\n                    builder.append('%').append(Integer.toHexString(k).toUpperCase());\n                }\n            }\n        }\n        return builder.toString();\n    }\n\n    public static String decodeURI(String url) {\n        return decodeURI(url, Files.UTF_8);\n    }\n\n    /**\n     * 相当于javascript的decodeURI\n     *\n     * @param url     the url string\n     * @param charset the charset\n     * @return decoded url string\n     */\n    public static String decodeURI(String url, String charset) {\n        if (url == null) {\n            return null;\n        }\n        try {\n            return URLDecoder.decode(url, charset);\n        } catch (UnsupportedEncodingException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    // ------------------------------------------------------------------------------encode/decode uri component\n    public static String encodeURIComponent(String url) {\n        return encodeURIComponent(url, Files.UTF_8);\n    }\n\n    /**\n     * <pre>\n     * 相当于javascript中的encodeURIComponent\n     * 不会被此方法编码的字符：! * ( )\n     * encodeURIComponent(\"http://www.oschina.net/search?scope=bbs&q=C语言\", \"UTF-8\") -> http%3A%2F%2Fwww.oschina.net%2Fsearch%3Fscope%3Dbbs%26q%3DC%E8%AF%AD%E8%A8%80\n     * </pre>\n     *\n     * @param url     the uri string\n     * @param charset the charset\n     * @return encoded uri component string\n     */\n    public static String encodeURIComponent(String url, String charset) {\n        if (url == null) {\n            return null;\n        }\n        try {\n            return URLEncoder.encode(url, charset);\n        } catch (UnsupportedEncodingException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    public static String decodeURIComponent(String url) {\n        return decodeURI(url, Files.UTF_8);\n    }\n\n    /**\n     * 相当于javascript中的decodeURIComponent\n     *\n     * @param url     the url string\n     * @param charset the charset\n     * @return decoded uri component string\n     */\n    public static String decodeURIComponent(String url, String charset) {\n        return decodeURI(url, charset);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/UuidUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport java.util.Base64;\nimport java.util.UUID;\n\n/**\n * UUID utility\n *\n * @author Ponfee\n */\npublic final class UuidUtils {\n\n    /**\n     * Returns 16 length byte array uuid\n     *\n     * @return 16 length uuid byte array\n     */\n    public static byte[] uuid() {\n        UUID uuid = UUID.randomUUID();\n        byte[] value = new byte[16];\n        Bytes.put(uuid.getMostSignificantBits(), value, 0);\n        Bytes.put(uuid.getLeastSignificantBits(), value, 8);\n        return value;\n    }\n\n    /**\n     * Returns 32 length string uuid, use hex encoding\n     *\n     * @return 32 length uuid string\n     */\n    public static String uuid32() {\n        UUID uuid = UUID.randomUUID();\n        return Bytes.toHex(uuid.getMostSignificantBits(),  true)\n             + Bytes.toHex(uuid.getLeastSignificantBits(), true);\n    }\n\n    /**\n     * Returns 22 length string uuid, use base64 url encoding and without padding\n     *\n     * @return 22 length uuid string\n     */\n    public static String uuid22() {\n        return Base64.getUrlEncoder().withoutPadding().encodeToString(uuid());\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/Wechats.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.http.Http;\nimport cn.ponfee.commons.http.HttpParams;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport cn.ponfee.commons.json.Jsons;\n\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * 微信工具类：https://www.cnblogs.com/txw1958/p/weixin76-user-info.html\n * OAuth2.0：https://www.jianshu.com/p/6392420faf99\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"unchecked\")\npublic class Wechats {\n\n    // -------------------------------------------------------构建微信授权地址\n    public static String buildAuthorizeUrl(String appid, String redirect, String state) {\n        return buildAuthorizeUrl(appid, Files.UTF_8, redirect, state);\n    }\n\n    public static String buildAuthorizeUrl(String appid, String charset,\n                                           String redirect, String state) {\n        return buildAuthorizeUrl(appid, charset, redirect, state, \"snsapi_base\");\n    }\n\n    /**\n     * 构建授权地址\n     * \n     * @param appid the appid\n     * \n     * @param charset the charset for params encoding\n     *\n     * @param redirect the service url\n     * \n     * @param state 在发送state之后，可以把state保存到Session以便用于后续回调时的比较。\n     *              这样做的目的是防止应用接受任意伪造的授权码（CSRF）。\n     * \n     * @param scope snsapi_base    ：不弹出授权页面，直接跳转，只能获取用户openid<p>\n     *              snsapi_userinfo：弹出授权页面，可通过openid拿到昵称、性别、所在地。并且，即使在未关注的情况下，只要用户授权，也能获取其信息<p>\n     *              snsapi_login   ：登录\n     *                               \n     * @return a url of wechat auth\n     */\n    public static String buildAuthorizeUrl(String appid, String charset,\n                                           String redirect, String state, String scope) {\n        Map<String, String> params = new LinkedHashMap<>();\n        params.put(\"appid\", appid);\n        params.put(\"redirect_uri\", redirect);\n        params.put(\"response_type\", \"code\");\n        params.put(\"scope\", scope);\n        params.put(\"state\", state);\n        return HttpParams.buildUrlPath(\"https://open.weixin.qq.com/connect/oauth2/authorize\", charset, params) + \"#wechat_redirect\";\n    }\n\n    // -------------------------------------------------------通过授权地址的回调参数code换取网页授权access_token和openId\n    /**\n     * <pre>\n     * 1、构建的微信授权地址返回到客户端\n     * 2、用户客户端访问此微信授权地址进行登录授权\n     * 3、微信会让用户客户端重定向到redirect(应用的回调地址)并附带code参数\n     * 4、应用通过code换取网页授权access_token和openId\n     *\n     * scope=snsapi_userinfo\n     *  {\n     *    \"access_token\":\"OezXcEiiBSKSxW0eow\",\n     *    \"expires_in\":7200,\n     *    \"refresh_token\":\"OezXcqDQy52232WDXB3Msuzq1A\",\n     *    \"openid\":\"oLVPpjqs9BhvzwPj5A-vTYAX3GLc\",\n     *    \"scope\":\"snsapi_userinfo,\"\n     *  }\n     *  \n     * scope=snsapi_base\n     *  {\n     *    \"access_token\": \"OezXcEiiBSKSxW0eoylIeAsR0GmYd1awCffdHgb4fhS_KKf2CotGj2cBNUKQQvj-oJ9VmO-0Z-_izfnSAX_s0aqDsYkW4s8W5dLZ4iyNj5Y6vey3dgDtFki5C8r6D0E6mSVxxtb8BjLMhb-mCyT_Yg\",\n     *    \"expires_in\": 7200,\n     *    \"refresh_token\": \"OezXcEiiBSKSxW0eoylIeAsR0GmYd1awCffdHgb4fhS_KKf2CotGj2cBNUKQQvj-oJ9VmO-0Z-_izfnSAX_s0aqDsYkW4s8W5dLZ4iyNj5YBkF0ZUH1Ew8Iqea6x_itq13sYDqP1D7ieaDy9u2AHHw\",\n     *    \"openid\": \"oLVPpjqs9BhvzwPj5A-vTYAX3GLc\",\n     *    \"scope\": \"snsapi_base\"\n     *  }\n     * </pre>\n     * \n     * 获取微信openID及授权access_token\n     * \n     * \n     * password模式：https://api.oauth2server.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID\n     * \n     * @param appid the appid\n     * @param secret the secret\n     * @param code   the aut url callback result data, \n     *               {@link #buildAuthorizeUrl(String, String, String, String, String)}\n     * \n     * @return wechat oauth info\n     */\n    public static Map<String, Object> getOAuth2(String appid, String secret, String code) {\n        Map<String, String> params = new LinkedHashMap<>();\n        params.put(\"appid\", appid);\n        params.put(\"secret\", secret);\n        params.put(\"code\", code);\n        params.put(\"grant_type\", \"authorization_code\");\n        Map<String, Object> result = Http.post(\"https://api.weixin.qq.com/sns/oauth2/access_token\")\n                                         .data(params).request(Map.class);\n\n        checkError(result);\n        return result;\n    }\n\n    /**\n     * Refresh the access token by refresh_token\n     * \n     * @param appid the appid\n     * @param refreshToken the refresh_token\n     * @return refresed a new access_token by refresh_token\n     */\n    public static Map<String, Object> refreshAccessToken(String appid, String refreshToken) {\n        Map<String, String> params = new LinkedHashMap<>();\n        params.put(\"grant_type\", \"refresh_token\");\n        params.put(\"appid\", appid);\n        params.put(\"refresh_token\", refreshToken);\n        Map<String, Object> result = Http.post(\"https://api.weixin.qq.com/sns/oauth2/refresh_token\")\n                                         .data(params).request(Map.class);\n\n        checkError(result);\n        return result;\n    }\n\n    /**\n     * <pre>\n     *  {\n     *    \"openid\":\"oLVPpjqs9BhvzwPj5A-vTYAX3GLc\",\n     *    \"nickname\":\"方倍\",\n     *    \"sex\":1,\n     *    \"language\":\"zh_CN\",\n     *    \"city\":\"Shenzhen\",\n     *    \"province\":\"Guangdong\",\n     *    \"country\":\"CN\",\n     *    \"headimgurl\":\"http://wx.qlogo.cn/mmopen/utpBBg18/0\",\n     *    \"privilege\":[]\n     *  }\n     * </pre>\n     * \n     * 通过OAuth2.0方式弹出授权页面获得用户基本信息（因scope=snsapi_userinfo会弹出授权页面）\n     * \n     * @param accessToken  the access token, {@link #getOAuth2(String, String, String)}\n     * @param openid the openid, {@link #getOAuth2(String, String, String)}\n     * \n     * @return wechat user info\n     * \n     * @see Wechats#buildAuthorizeUrl(String, String, String, String, String) set scope=snsapi_userinfo\n     */\n    public static Map<String, Object> getUserInfoByOAuth2(String accessToken, String openid) {\n        Map<String, String> params = new LinkedHashMap<>();\n        params.put(\"access_token\", accessToken);\n        params.put(\"openid\", openid);\n        params.put(\"lang\", \"zh_CN\"); // this param can be unset\n        Map<String, Object> result = Http.post(\"https://api.weixin.qq.com/sns/userinfo\")\n                                         .data(params).request(Map.class);\n\n        checkError(result);\n        return result;\n    }\n\n    // -------------------------------------------------------获取全局access_token\n    /**\n     * Gets global access token\n     * \n     * @param appid\n     * @param secret\n     * \n     * @return {access_token=token, expires_in=7200}\n     */\n    public static String getAccessToken(String appid, String secret) {\n        Map<String, String> params = new LinkedHashMap<>();\n        params.put(\"grant_type\", \"client_credential\");\n        params.put(\"appid\", appid);\n        params.put(\"secret\", secret);\n        Map<String, Object> result = Http.post(\"https://api.weixin.qq.com/cgi-bin/token\")\n                                         .data(params).request(Map.class);\n\n        checkError(result);\n        return (String) result.get(\"access_token\");\n    }\n\n    // -------------------------------------------------------通过全局access token获取用户信息\n    /**\n     * <pre>\n     *  1、用户关注以及回复消息的时候，均可以获得用户的OpenID\n     *   <xml>\n     *     <ToUserName><![CDATA[gh_b629c48b653e]]></ToUserName>\n     *     <FromUserName><![CDATA[ollB4jv7LA3tydjviJp5V9qTU_kA]]></FromUserName>\n     *     <CreateTime>1372307736</CreateTime>\n     *     <MsgType><![CDATA[event]]></MsgType>\n     *     <Event><![CDATA[subscribe]]></Event>\n     *     <EventKey><![CDATA[]]></EventKey>\n     *   </xml>\n     *  其中的FromUserName就是OpenID\n     * \n     *  2、返回数据格式\n     *  {\n     *    \"subscribe\": 1,\n     *    \"openid\": \"oLVPpjqs2BhvzwPj5A-vTYAX4GLc\",\n     *    \"nickname\": \"nickname\",\n     *    \"sex\": 1,\n     *    \"language\": \"zh_CN\",\n     *    \"unionid\": \"unionid\" // 只有在用户将公众号绑定到微信开放平台帐号后，才会出现该字段\n     *    \"city\": \"深圳\",\n     *    \"province\": \"广东\",\n     *    \"country\": \"中国\",\n     *    \"headimgurl\": \"http://wx.qlogo.cn/mmopen/JcDicrZBlREhnNXZRudod9PmibRkIs5K2f1tUQ7lFjC63pYHaXGxNDgMzjGDEuvzYZbFOqtUXaxSdoZG6iane5ko9H30krIbzGv/0\",\n     *    \"subscribe_time\": 1386160805 // 用户关注时间，为时间戳。如果用户曾多次关注，则取最后关注时间\n     *  }\n     * </pre>\n     * \n     * 通过全局Access Token获取用户基本信息\n     * \n     * @param accessToken  the global access token, {@link #getAccessToken(String, String)}\n     * @param openid the openid, 用户关注以及回复消息时可获取此openid\n     * \n     * @return wechat user info\n     */\n    public static Map<String, Object> getUserInfoByGlobal(String accessToken, String openid) {\n        Map<String, String> params = new LinkedHashMap<>();\n        params.put(\"access_token\", accessToken);\n        params.put(\"openid\", openid);\n        Map<String, Object> result = Http.post(\"https://api.weixin.qq.com/cgi-bin/user/info\")\n                                         .data(params).request(Map.class);\n\n        checkError(result);\n        return result;\n    }\n\n    // -------------------------------------------------------获取api_ticket\n    public static String getJsapiTicket(String accessToken) {\n        return getTicket(\"jsapi\", accessToken);\n    }\n\n    /**\n     * Gets jsapi ticket\n     * \n     * @param type        wx_card 卡券；jsapi js接口票据；\n     * @param accessToken the global accessToken, {@link #getAccessToken(String, String)}\n     * \n     * @return {errcode=0, errmsg=ok, ticket=ticket, expires_in=7200}\n     */\n    public static String getTicket(String type, String accessToken) {\n        Map<String, String> params = new LinkedHashMap<>();\n        params.put(\"access_token\", accessToken);\n        params.put(\"type\", type);\n        Map<String, Object> result = Http.post(\"https://api.weixin.qq.com/cgi-bin/ticket/getticket\")\n                                         .data(params).request(Map.class);\n\n        checkError(result);\n        return (String) result.get(\"ticket\");\n    }\n\n    /**\n     * Returns the share url link to the wechat friend moments\n     * \n     * @param jsapiTicket the jsapi ticket, {@link #getJsapiTicket(String)}\n     * @param appid       the wechat appid\n     * @param url         the url link\n     * @return share url data with signurate give to client use\n     */\n    public static Map<String, String> shareUrl(String jsapiTicket, String appid, String url) {\n        Map<String, String> map = new HashMap<>();\n        map.put(\"jsapi_ticket\", jsapiTicket);\n        map.put(\"noncestr\", UuidUtils.uuid22());\n        map.put(\"timestamp\", Long.toString(System.currentTimeMillis() / 1000));\n        map.put(\"url\", !url.contains(\"#\") ? url : url.substring(0, url.indexOf(\"#\")));\n\n        // generate sigin data\n        map.put(\"signature\", DigestUtils.sha1Hex(HttpParams.buildSigning(map)));\n        map.put(\"appid\", appid);\n        return map;\n    }\n\n    // -------------------------------------------------------private methods\n    private static void checkError(Map<String, ?> result) {\n        Object errcode = result.get(\"errcode\");\n        if (errcode != null && !\"0\".equals(errcode.toString())) {\n            throw new RuntimeException(\"Wechat server response error:\" + Jsons.toJson(result));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/util/ZipUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.io.Files;\nimport net.lingala.zip4j.ZipFile;\nimport net.lingala.zip4j.exception.ZipException;\nimport net.lingala.zip4j.model.FileHeader;\nimport net.lingala.zip4j.model.ZipParameters;\nimport net.lingala.zip4j.model.enums.AesKeyStrength;\nimport net.lingala.zip4j.model.enums.CompressionLevel;\nimport net.lingala.zip4j.model.enums.CompressionMethod;\nimport net.lingala.zip4j.model.enums.EncryptionMethod;\nimport org.apache.commons.io.FilenameUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * zip utility based zip4j\n * \n * @see <a href=\"http://commons.apache.org/proper/commons-compress\">apache commons-compress</a>\n * \n * @author Ponfee\n */\npublic class ZipUtils {\n\n    private static final String SUFFIX = \".zip\";\n\n    // -----------------------------------压缩-----------------------------------\n    /**\n     * 压缩指定文件到当前文件夹，压缩后的文件名为：待压缩文件名+.zip\n     * @param src 要压缩的指定文件\n     * @return 最终的压缩文件存放的绝对路径\n     */\n    public static String zip(String src) throws ZipException {\n        File srcFile = new File(src);\n        if (!srcFile.exists()) {\n            throw new ZipException(\"source file not found: \" + src);\n        }\n\n        String dest = srcFile.getParent() + File.separator;\n        if (srcFile.isFile()) {\n            dest += FilenameUtils.getBaseName(srcFile.getName()); // 文件名去除后缀\n        } else {\n            dest += srcFile.getName(); // 以目录名作为文件名\n        }\n\n        return zip(src, dest + SUFFIX);\n    }\n\n    /**\n     * 压缩文件到指定路径\n     * @param src 待压缩的文件\n     * @param dest 压缩文件存放路径\n     * @return 最终的压缩文件存放的绝对路径\n     */\n    public static String zip(String src, String dest) throws ZipException {\n        return zip(src, dest, null);\n    }\n\n    /**\n     * 使用给定密码压缩文件到指定路径\n     * @param src 要压缩的文件\n     * @param dest 压缩文件存放路径\n     * @param passwd 压缩使用的密码\n     * @return 最终的压缩文件存放的绝对路径\n     */\n    public static String zip(String src, String dest, String passwd)\n        throws ZipException {\n        return zip(src, dest, true, passwd, null);\n    }\n\n    /**\n     * 压缩文件到指定路径\n     * @param src        待压缩的文件名或文件夹路径名\n     * @param dest       压缩文件存放路径\n     * @param recursion  是否递归压缩（只对待压缩文件为文件夹时有效）：true是；false否；\n     * @param passwd     压缩使用的密码\n     * @param comment    注释信息\n     * @return 最终的压缩文件存放的绝对路径\n     */\n    public static String zip(String src, String dest, boolean recursion,\n                             String passwd, String comment) throws ZipException {\n        // validate source file\n        File srcFile = new File(src);\n        if (!srcFile.exists()) {\n            throw new ZipException(\"source file not found: \" + src);\n        }\n\n        // validate dest file\n        if (StringUtils.isEmpty(dest)) {\n            throw new ZipException(\"dest file cannot be null\");\n        }\n        File destFile = new File(dest);\n        if (destFile.exists()) {\n            throw new ZipException(\"dest file exists: \" + dest);\n        }\n        Files.mkdir(destFile.getParent()); // 创建父路径（如果不存在）\n\n        // create zip parameters\n        ZipParameters parameters = new ZipParameters();\n        parameters.setCompressionMethod(CompressionMethod.DEFLATE); // 压缩方式\n        parameters.setCompressionLevel(CompressionLevel.NORMAL); // 压缩级别\n        if (!StringUtils.isEmpty(passwd)) {\n            parameters.setEncryptFiles(true);\n            parameters.setEncryptionMethod(EncryptionMethod.AES); // 加密方式\n            parameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);\n            //parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD); // 加密方式\n        }\n        ZipFile zipFile = new ZipFile(destFile, toCharArray(passwd));\n\n        // 开始压缩\n        if (srcFile.isFile()) { // 压缩文件\n            zipFile.addFile(srcFile, parameters);\n        } else { // 压缩目录\n            File[] files = srcFile.listFiles();\n            if (files == null || files.length == 0) {\n                return null;\n            }\n            for (File file : files) {\n                if (file.isFile()) {\n                    zipFile.addFile(file, parameters);\n                } else if (recursion) { // 递归压缩目录\n                    zipFile.addFolder(file, parameters);\n                }\n            }\n        }\n\n        if (comment != null) {\n            zipFile.setComment(comment);\n        }\n        return destFile.getAbsolutePath();\n    }\n\n    // -----------------------------------解压缩-----------------------------------\n    /**\n     * 解压缩文件到当前目录\n     * @param zipFile 压缩文件\n     * @return 解压后文件数组\n     * @throws ZipException\n     */\n    public static String unzip(String zipFile) throws ZipException {\n        if (StringUtils.isBlank(zipFile)) {\n            throw new ZipException(\"zip file cannot be null\");\n        }\n\n        String lowercasePath = zipFile.toLowerCase();\n        if (lowercasePath.endsWith(SUFFIX)) {\n            String dest = zipFile.substring(0, lowercasePath.indexOf(SUFFIX));\n            unzip(zipFile, dest);\n            return dest;\n        } else {\n            throw new ZipException(\"the zip file name must be end with .zip\");\n        }\n    }\n\n    /**\n     * 解压缩文件到指定目录\n     * @param zipFile 指定的压缩文件\n     * @param dest 解压缩存放的目录\n     * @return  解压后文件数组\n     * @throws ZipException 压缩文件有损坏或者解压缩失败抛出\n     */\n    public static File[] unzip(String zipFile, String dest) throws ZipException {\n        return unzip(zipFile, dest, null);\n    }\n\n    /**\n     * 使用给定密码解压指定的压缩文件到指定目录<p>\n     * 如果指定目录不存在，可以自动创建，不合法的路径将导致异常被抛出\n     * @param zipFile 指定的压缩文件\n     * @param dest 解压目录\n     * @param passwd 压缩文件的密码\n     * @return 解压后文件数组\n     * @throws ZipException 压缩文件有损坏或者解压缩失败抛出\n     */\n    public static File[] unzip(String zipFile, String dest, String passwd) throws ZipException {\n        return unzip(new File(zipFile), dest, passwd);\n    }\n\n    /**\n     * 使用给定密码解压指定的压缩文件到指定目录<p>\n     * 如果指定目录不存在,可以自动创建,不合法的路径将导致异常被抛出\n     * @param zipFile 指定的压缩文件\n     * @param dest 解压目录\n     * @param passwd 压缩文件的密码\n     * @return  解压后文件数组\n     * @throws ZipException 压缩文件有损坏或者解压缩失败抛出\n     */\n    public static File[] unzip(File zipFile, String dest, String passwd) throws ZipException {\n        // validate zip file\n        if (!zipFile.exists()) {\n            throw new ZipException(\"zip file not found: \" + zipFile.getAbsolutePath());\n        }\n\n        ZipFile zFile = new ZipFile(zipFile, toCharArray(passwd));\n        if (!zFile.isValidZipFile()) {\n            throw new ZipException(\"invalid zip file.\");\n        }\n        if (zFile.isEncrypted() && StringUtils.isEmpty(passwd)) {\n            throw new ZipException(\"passwd can't be null\");\n        }\n\n        // validate dest file path\n        if (StringUtils.isEmpty(dest)) {\n            throw new ZipException(\"dest file path can't be null\");\n        } else if (new File(dest).exists()) {\n            throw new ZipException(\"dest file is exists: \" + dest);\n        } else {\n            Files.mkdir(dest); // 校验并创建解压缩存放目录\n        }\n\n        // unpack zip file\n        zFile.extractAll(dest);\n        List<File> fileEntries = new ArrayList<>();\n        for (FileHeader fileHeader : zFile.getFileHeaders()) {\n            if (!fileHeader.isDirectory()) {\n                fileEntries.add(new File(dest, fileHeader.getFileName()));\n            }\n        }\n        return fileEntries.toArray(new File[0]);\n    }\n\n    private static char[] toCharArray(String str) {\n        return StringUtils.isEmpty(str) ? null : str.toCharArray();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/web/AbstractWebExceptionHandler.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.web;\n\nimport cn.ponfee.commons.exception.BaseUncheckedException;\nimport cn.ponfee.commons.exception.UnauthorizedException;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.model.ResultCode;\nimport com.google.common.base.Throwables;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.TypeMismatchException;\nimport org.springframework.http.converter.HttpMessageNotReadableException;\nimport org.springframework.validation.BindException;\nimport org.springframework.validation.ObjectError;\nimport org.springframework.web.HttpMediaTypeNotSupportedException;\nimport org.springframework.web.HttpRequestMethodNotSupportedException;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.ServletRequestBindingException;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.validation.ConstraintViolation;\nimport javax.validation.ConstraintViolationException;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Spring mvc global exception handler for web application\n * \n * <code>\n *   `@ControllerAdvice\n *   public class WebExceptionHandler extends AbstractWebExceptionHandler {}\n * </code\n * \n * 有@RequestBody：类型转换出错时，会抛HttpMessageNotReadableException\n * 无@RequestBody：类型转换出错时，会抛BindException（假如方法中有BindingResult参数，则错误信息会收集到此参数中，正常进入到业务方法）\n * \n * @author Ponfee\n */\npublic abstract class AbstractWebExceptionHandler {\n\n    public static final int UNAUTHORIZED     = ResultCode.UNAUTHORIZED.getCode();\n    public static final int BAD_REQUEST      = ResultCode.BAD_REQUEST.getCode();\n    public static final int NOT_ALLOWED      = ResultCode.NOT_ALLOWED.getCode();\n    public static final int UNSUPPORT_MEDIA  = ResultCode.UNSUPPORT_MEDIA.getCode();\n    public static final int SERVER_ERROR     = ResultCode.SERVER_ERROR.getCode();\n\n    protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractWebExceptionHandler.class);\n\n    private final String unauthorizedPage;\n    private final String serverErrorPage;\n    private final String defaultErrorMsg;\n\n    public AbstractWebExceptionHandler() {\n        this(\"/page/401.html\", \"/page/500.html\", \"Server error.\");\n    }\n\n    public AbstractWebExceptionHandler(String unauthorizedPage, \n                                       String serverErrorPage, \n                                       String defaultErrorMsg) {\n        this.unauthorizedPage = unauthorizedPage;\n        this.serverErrorPage = serverErrorPage;\n        this.defaultErrorMsg = defaultErrorMsg;\n    }\n\n    /**\n     * 401 - Unauthorized\n     */\n    @ExceptionHandler(UnauthorizedException.class)\n    //@ResponseBody @ResponseStatus(HttpStatus.UNAUTHORIZED)\n    public void handle(HttpServletRequest req, HttpServletResponse resp, UnauthorizedException e) {\n        LOGGER.debug(\"Unauthorized\", e);\n        handle(req, resp, unauthorizedPage, e.getCode(), e.getMessage());\n    }\n\n    /**\n     * 400 - Bind error: jsr 303\n     * \n     * Controller方法中无BindingResult参数\n     * public Result testValidate1(@Valid Article article) {}\n     * \n     * 注：\n     *  1、@org.springframework.validation.annotation.Validated可代替@Valid\n     *  2、类型转换失败（如前端传错误的日期格式）也会抛BindException\n     */\n    @ExceptionHandler(BindException.class)\n    //@ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST)\n    public void handle(HttpServletRequest req, HttpServletResponse resp, BindException e) {\n        LOGGER.debug(\"Bind failed\", e);\n        handle(req, resp, e.getAllErrors());\n    }\n\n    /**\n     * 400 - Method argument not valid: jsr 303\n     * \n     * 含@RequestBody注解（HttpMessageConverter application/json）\n     * \n     * Controller方法中无BindingResult参数\n     * public Result testValidate2(@RequestBody `@Valid Article article) {}\n     * \n     * 注：\n     *  1、@org.springframework.validation.annotation.Validated可代替@Valid\n     *  2、类型转换失败（如前端传错误的日期格式）会抛HttpMessageNotReadableException，不会抛MethodArgumentNotValidException（即不会进入此方法）\n     */\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    //@ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST)\n    public void handleMethod(HttpServletRequest req, HttpServletResponse resp, \n                             MethodArgumentNotValidException e) {\n        LOGGER.debug(\"Method argument not valid\", e);\n        handle(req, resp, e.getBindingResult().getAllErrors());\n    }\n\n    /**\n     * 400 - Constraint violation: jsr 303\n     * \n     * 1、加配置：\n     *   <bean id=\"MethodValidationPostProcessor\" class=\"org.springframework.validation.beanvalidation.MethodValidationPostProcessor\">\n     *     <property name=\"validator\"><bean class=\"cn.ponfee.commons.constrain.FastFailValidatorFactoryBean\" /></property>\n     *   </bean>\n     * 2、必须在Controller类中注解@org.springframework.validation.annotation.Validated\n     * 3、public `@NotNull Result testValidate3(@Range(min = 1, max = 9, message = \"年级只能从1-9\") @RequestParam(name = \"grade\", required = true) int grade) {}\n     * 4、若是接口实现类，则参数约束注解只能放在接口方法参数上（返回值可放在实现类方法上）\n     */\n    @ExceptionHandler(ConstraintViolationException.class)\n    //@ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST)\n    public void handle(HttpServletRequest req, HttpServletResponse resp, \n                       ConstraintViolationException e) {\n        LOGGER.debug(\"Constraint violation\", e);\n        String message = e.getConstraintViolations()\n                          .stream()\n                          .map(ConstraintViolation::getMessage)\n                          .collect(Collectors.joining(\",\", \"[\", \"]\"));\n        handle(req, resp, BAD_REQUEST, message);\n    }\n\n    /**\n     * 400 - Bad Request\n     */\n    @ExceptionHandler({\n        IllegalArgumentException.class, IllegalStateException.class, \n        TypeMismatchException.class, HttpMessageNotReadableException.class,\n        ServletRequestBindingException.class, \n    })\n    //@ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST)\n    public void handle(HttpServletRequest req, HttpServletResponse resp, Exception e) {\n        LOGGER.debug(\"Bad request\", e);\n        handle(req, resp, BAD_REQUEST, e.getMessage());\n    }\n\n    /**\n     * 405 - Method Not Allowed\n     */\n    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)\n    //@ResponseBody @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)\n    public void handle(HttpServletRequest req, HttpServletResponse resp, \n                       HttpRequestMethodNotSupportedException e) {\n        LOGGER.debug(\"Request method not supported\", e);\n        handle(req, resp, NOT_ALLOWED, e.getMessage());\n    }\n\n    /**\n     * 415 - Unsupported Media Type\n     */\n    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)\n    //@ResponseBody @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)\n    public void handle(HttpServletRequest req, HttpServletResponse resp, \n                       HttpMediaTypeNotSupportedException e) {\n        LOGGER.debug(\"Media type not supported\", e);\n        handle(req, resp, UNSUPPORT_MEDIA, e.getMessage());\n    }\n\n    /*\n    @ExceptionHandler(org.apache.commons.fileupload.FileUploadException.class)\n    //@ResponseBody @ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)\n    public void handle(HttpServletRequest req, HttpServletResponse resp, org.apache.commons.fileupload.FileUploadException e) {\n        LOGGER.debug(\"File upload fail.\", e);\n        handle(req, resp, HttpStatus.PAYLOAD_TOO_LARGE.value(), e.getMessage());\n    }\n    */\n\n    /**\n     * 500 - Biz operate failure\n     */\n    @ExceptionHandler(BaseUncheckedException.class)\n    //@ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    public void handle(HttpServletRequest req, HttpServletResponse resp, BaseUncheckedException e) {\n        LOGGER.debug(\"Biz operate failure\", e);\n        handle(req, resp, e.getCode(), e.getMessage());\n    }\n\n    /**\n     * 500 - Internal Server Error\n     */\n    @ExceptionHandler(Throwable.class)\n    //@ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    public void handle(HttpServletRequest req, HttpServletResponse resp, Throwable t) {\n        LOGGER.error(\"Server error\", t);\n        String message = LOGGER.isDebugEnabled() \n                       ? Throwables.getStackTraceAsString(t) \n                       : this.defaultErrorMsg;\n        handle(req, resp, this.serverErrorPage, SERVER_ERROR, message);\n    }\n\n    // -----------------------------------------------------------------------protected methods\n    protected void handle(HttpServletRequest req, HttpServletResponse resp,\n                          String page, int code, String message) {\n        if (page == null || LOGGER.isDebugEnabled() || WebUtils.isAjax(req)) {\n            // resp.setStatus(code); HttpStatus.valueOf(code);\n            WebUtils.respJson(resp, Result.failure(code, message));\n        } else {\n            handErrorPage(req, resp, page);\n        }\n    }\n\n    protected void handErrorPage(HttpServletRequest req, HttpServletResponse resp, String page) {\n        try {\n            req.getRequestDispatcher(page).forward(req, resp);\n            //resp.sendRedirect(resp.encodeRedirectURL(WebUtils.getContextPath(req) + page));\n        } catch (IOException | ServletException e) {\n            LOGGER.error(\"Forward page occur error.\", e);\n        }\n    }\n\n    // -----------------------------------------------------------------------private methods\n    private void handle(HttpServletRequest req, HttpServletResponse resp,\n                        List<ObjectError> errors) {\n        String message = errors.stream()\n                               .map(ObjectError::getDefaultMessage)\n                               .collect(Collectors.joining(\",\", \"[\", \"]\"));\n        handle(req, resp, BAD_REQUEST, message);\n    }\n\n    private void handle(HttpServletRequest req, HttpServletResponse resp,\n                        int code, String message) {\n        handle(req, resp, null, code, message);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/web/DevicePlatform.java",
    "content": "/*\n * Copyright 2002-2014 the original author or authors.\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 */\n\npackage cn.ponfee.commons.web;\n\n/**\n * Enumeration for the platform of device that has been resolved\n * \n * @author Onur Kagan Ozcan\n * \n * Modify from org.springframework.mobile.device.DevicePlatform\n * @see org.springframework.mobile.device.DevicePlatform\n */\npublic enum DevicePlatform {\n\n    /**\n     * Represents an apple platform\n     */\n    IOS,\n\n    /**\n     * Represents an android platform\n     */\n    ANDROID,\n\n    /**\n     * Represents unknown platform\n     */\n    UNKNOWN\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/web/DeviceType.java",
    "content": "/*\n * Copyright 2010-2014 the original author or authors.\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 */\n\npackage cn.ponfee.commons.web;\n\n/**\n * Enumeration for the type of device that has been resolved\n * \n * @author Roy Clarkson\n * \n * Modify from org.springframework.mobile.device.DeviceType\n * @see org.springframework.mobile.device.DeviceType\n */\npublic enum DeviceType {\n\n    /**\n     * Represents a normal device. i.e. a browser on a desktop or laptop computer\n     */\n    NORMAL,\n\n    /**\n     * Represents a mobile device, such as an iPhone\n     */\n    MOBILE,\n\n    /**\n     * Represents a tablet device, such as an iPad\n     */\n    TABLET\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/web/GlobalExceptionHandler.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n//package cn.ponfee.commons.web;\n//\n//import java.io.IOException;\n//import java.io.PrintWriter;\n//\n//import javax.servlet.ServletConfig;\n//import javax.servlet.ServletException;\n//import javax.servlet.http.HttpServlet;\n//import javax.servlet.http.HttpServletRequest;\n//import javax.servlet.http.HttpServletResponse;\n//\n//import org.apache.commons.lang3.StringUtils;\n//import org.slf4j.Logger;\n//import org.slf4j.LoggerFactory;\n//\n//import com.google.common.collect.ImmutableMap;\n//\n//import cn.ponfee.commons.exception.BasicException;\n//import cn.ponfee.commons.http.HttpParams;\n//import cn.ponfee.commons.io.Files;\n//import cn.ponfee.commons.json.Jsons;\n//import cn.ponfee.commons.model.Result;\n//import cn.ponfee.commons.model.ResultCode;\n//import cn.ponfee.commons.web.WebUtils;\n//\n///**\n// * 全局异常处理<p>\n// * https://www.runoob.com/servlet/servlet-exception-handling.html\n// * \n// * <pre>{@code \n// * \n// *   <servlet>\n// *     <servlet-name>globalExceptionHandler</servlet-name>\n// *     <servlet-class>cn.ponfee.web.framework.web.GlobalExceptionHandler</servlet-class>\n// *     <init-param>\n// *       <param-name>handlerType</param-name>\n// *       <param-value>application/json</param-value>\n// *     </init-param>\n// *   </servlet>\n// *   <servlet-mapping>\n// *     <servlet-name>globalExceptionHandler</servlet-name>\n// *     <url-pattern>/globalExceptionHandler</url-pattern>\n// *   </servlet-mapping>\n// *   <error-page>\n// *     <error-code>404</error-code>\n// *     <location>/globalExceptionHandler</location>\n// *   </error-page>\n// *   <error-page>\n// *     <exception-type>java.lang.Throwable</exception-type>\n// *     <location>/globalExceptionHandler</location>\n// *   </error-page>\n// *   <error-page>\n// *     <error-code>403</error-code>\n// *     <location>/globalExceptionHandler</location>\n// *   </error-page>\n// * \n// * }</pre>\n// * \n// * @author Ponfee\n// */\n///*@WebServlet(\n//    name = \"cn.ponfee.web.framework.web.GlobalExceptionHandler\",\n//    urlPatterns = {\"/globalExceptionHandler\"},\n//    initParams = {\n//        @WebInitParam(name=\"handlerType\", value=\"application/json\")\n//    },\n//    asyncSupported=true\n//)*/\n//public class GlobalExceptionHandler extends HttpServlet {\n//\n//    private static final long serialVersionUID = 6067653829035388068L;\n//    private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);\n//    private static final String DEFAULT_HANDLER_TYPE = \"application/json\";\n//    public static final String ERROR_MSG = \"系统异常，请与管理员联系\";\n//\n//    private String handlerType;\n//    private String errorPage;\n//\n//    @Override\n//    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {\n//        this.doPost(req, resp);\n//    }\n//\n//    @Override\n//    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {\n//        Throwable throwable = (Throwable) req.getAttribute(\"javax.servlet.error.exception\");\n//        Integer statusCode = (Integer) req.getAttribute(\"javax.servlet.error.status_code\");\n//        String servletName = (String) req.getAttribute(\"javax.servlet.error.servlet_name\");\n//        String requestUri = (String) req.getAttribute(\"javax.servlet.error.request_uri\");\n//        //Class<?> message = (Class<?>) req.getAttribute(\"javax.servlet.error.message\");\n//        //Class<?> type = (Class<?>) req.getAttribute(\"javax.servlet.error.exception_type\");\n//        logger.info(\"{}-{}-{}-{}\", throwable, statusCode, servletName, requestUri);\n//\n//        try {\n//            if (throwable != null) {\n//                if (throwable instanceof BasicException \n//                    || throwable instanceof IllegalArgumentException) {\n//                    logger.info(\"\", throwable);\n//                } else {\n//                    logger.error(\"\", throwable);\n//                }\n//                throw throwable;\n//            } else {\n//                throw new WebException(ResultCode.NOT_FOUND.getCode(), \"file not found\");\n//            }\n//        } catch (Throwable e) {\n//            String errorMsg = (e instanceof BasicException) || logger.isInfoEnabled()\n//                              ? e.getMessage() : ERROR_MSG;\n//            errorMsg = StringUtils.isBlank(errorMsg) ?  ERROR_MSG : errorMsg;\n//            switch (handlerType) {\n//                case \"application/json\":\n//                case \"text/html\":\n//                case \"text/plain\":\n//                    int code = (e instanceof BasicException) \n//                               ? ((BasicException) e).getCode() \n//                               : ResultCode.SERVER_ERROR.getCode();\n//                    resp.setContentType(handlerType + \";charset=\" + Files.UTF_8);\n//                    try (PrintWriter writer = resp.getWriter()) {\n//                        writer.print(Jsons.toJson(Result.failure(code, errorMsg)));\n//                    } catch (IOException ex) {\n//                        logger.error(\"\", ex);\n//                    }\n//                    break;\n//                default:\n//                    if (errorPage != null) {\n//                        String context = WebUtils.getContextPath(req);\n//                        String url = HttpParams.buildUrlPath(context + errorPage, Files.UTF_8, \n//                                                             ImmutableMap.of(\"msg\", errorMsg));\n//                        try {\n//                            resp.sendRedirect(resp.encodeRedirectURL(url));\n//                        } catch (IOException ex) {\n//                            logger.error(\"response send redirect occur error\", ex);\n//                        }\n//                    } else {\n//                        resp.setStatus(ResultCode.SERVER_ERROR.getCode());\n//                        try (PrintWriter writer = resp.getWriter()) {\n//                            writer.append(errorMsg);\n//                        } catch (IOException ex) {\n//                            logger.error(\"\", ex);\n//                        }\n//                    }\n//                    break;\n//            }\n//        }\n//    }\n//\n//    @Override\n//    public void init(ServletConfig config) throws ServletException {\n//        super.init(config);\n//        handlerType = config.getInitParameter(\"handlerType\");\n//        if (StringUtils.isBlank(handlerType)) {\n//            handlerType = DEFAULT_HANDLER_TYPE;\n//        }\n//        handlerType = handlerType.toLowerCase();\n//\n//        errorPage = config.getInitParameter(\"errorPage\");\n//        if (StringUtils.isBlank(errorPage)) {\n//            errorPage = null;\n//        }\n//    }\n//\n//}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/web/GlobalExceptionResolver.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n//package cn.ponfee.commons.web;\n//\n//import java.io.IOException;\n//import java.io.PrintWriter;\n//import java.util.Collections;\n//import java.util.Enumeration;\n//import java.util.HashMap;\n//import java.util.Map;\n//import java.util.Properties;\n//\n//import javax.servlet.http.HttpServletRequest;\n//import javax.servlet.http.HttpServletResponse;\n//\n//import org.apache.commons.lang3.StringUtils;\n//import org.springframework.web.servlet.ModelAndView;\n//import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;\n//import org.springframework.web.util.WebUtils;\n//\n//import cn.ponfee.commons.exception.BasicException;\n//import cn.ponfee.commons.exception.Throwables;\n//import cn.ponfee.commons.json.Jsons;\n//import cn.ponfee.commons.model.Result;\n//import cn.ponfee.commons.model.ResultCode;\n//\n///**\n// * Spring mvc 全局异常处理方式\n// * 更改自：org.springframework.web.servlet.handler.SimpleMappingExceptionResolver\n// * \n// * @author Ponfee\n// */\n//public class GlobalExceptionResolver extends AbstractHandlerExceptionResolver {\n//\n//    public static final String ERROR_MSG = \"系统异常，请与管理员联系\";\n//\n//    /** The default name of the exception attribute: \"exception\". */\n//    private Properties exceptionMappings;\n//\n//    private Class<?>[] excludedExceptions;\n//\n//    private String defaultErrorView;\n//\n//    private Integer defaultStatusCode;\n//\n//    private Map<String, Integer> statusCodes = new HashMap<>();\n//\n//    private String exceptionAttribute = \"exception\";\n//\n//    private String stackTraceAttribute = \"stackTrace\";\n//\n//    private String resolverType = \"modeView\";\n//\n//    //private static Logger logger = LoggerFactory.getLogger(CespHandlerExceptionResolver.class);\n//\n//    /**\n//     * Set the mappings between exception class names and error view names. The exception class name can be a substring, with no wildcard support at present. A\n//     * value of \"ServletException\" would match {@code javax.servlet.ServletException} and subclasses, for example.\n//     * <p>\n//     * <b>NB:</b> Consider carefully how specific the pattern is, and whether to include package information (which isn't mandatory). For example, \"Exception\"\n//     * will match nearly anything, and will probably hide other rules. \"java.lang.Exception\" would be correct if \"Exception\" was meant to define a rule for all\n//     * checked exceptions. With more unusual exception names such as \"BaseBusinessException\" there's no need to use a FQN.\n//     * @param mappings exception patterns (can also be fully qualified class names) as keys, and error view names as values\n//     */\n//    public void setExceptionMappings(Properties mappings) {\n//        this.exceptionMappings = mappings;\n//    }\n//\n//    /**\n//     * Set one or more exceptions to be excluded from the exception mappings. Excluded exceptions are checked first and if one of them equals the actual\n//     * exception, the exception will remain unresolved.\n//     * @param excludedExceptions one or more excluded exception types\n//     */\n//    public void setExcludedExceptions(Class<?>... excludedExceptions) {\n//        this.excludedExceptions = excludedExceptions;\n//    }\n//\n//    /**\n//     * Set the name of the default error view. This view will be returned if no specific mapping was found.\n//     * <p>\n//     * Default is none.\n//     */\n//    public void setDefaultErrorView(String defaultErrorView) {\n//        this.defaultErrorView = defaultErrorView;\n//    }\n//\n//    /**\n//     * Set the HTTP status code that this exception resolver will apply for a given resolved error view. Keys are view names; values are status codes.\n//     * <p>\n//     * Note that this error code will only get applied in case of a top-level request. It will not be set for an include request, since the HTTP status cannot\n//     * be modified from within an include.\n//     * <p>\n//     * If not specified, the default status code will be applied.\n//     * @see #setDefaultStatusCode(int)\n//     */\n//    public void setStatusCodes(Properties statusCodes) {\n//        for (Enumeration<?> enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements();) {\n//            String viewName = (String) enumeration.nextElement();\n//            Integer statusCode = new Integer(statusCodes.getProperty(viewName));\n//            this.statusCodes.put(viewName, statusCode);\n//        }\n//    }\n//\n//    /**\n//     * An alternative to {@link #setStatusCodes(Properties)} for use with Java-based configuration.\n//     */\n//    public void addStatusCode(String viewName, int statusCode) {\n//        this.statusCodes.put(viewName, statusCode);\n//    }\n//\n//    /**\n//     * Returns the HTTP status codes provided via {@link #setStatusCodes(Properties)}. Keys are view names; values are status codes.\n//     */\n//    public Map<String, Integer> getStatusCodesAsMap() {\n//        return Collections.unmodifiableMap(statusCodes);\n//    }\n//\n//    /**\n//     * Set the default HTTP status code that this exception resolver will apply if it resolves an error view and if there is no status code mapping defined.\n//     * <p>\n//     * Note that this error code will only get applied in case of a top-level request. It will not be set for an include request, since the HTTP status cannot\n//     * be modified from within an include.\n//     * <p>\n//     * If not specified, no status code will be applied, either leaving this to the controller or view, or keeping the servlet engine's default of 200 (OK).\n//     * @param defaultStatusCode HTTP status code value, for example 500 ({@link HttpServletResponse#SC_INTERNAL_SERVER_ERROR}) or 404 (\n//     *        {@link HttpServletResponse#SC_NOT_FOUND})\n//     * @see #setStatusCodes(Properties)\n//     */\n//    public void setDefaultStatusCode(int defaultStatusCode) {\n//        this.defaultStatusCode = defaultStatusCode;\n//    }\n//\n//    /**\n//     * Set the name of the model attribute as which the exception should be exposed. Default is \"exception\".\n//     * <p>\n//     * This can be either set to a different attribute name or to {@code null} for not exposing an exception attribute at all.\n//     */\n//    public void setExceptionAttribute(String exceptionAttribute) {\n//        this.exceptionAttribute = exceptionAttribute;\n//    }\n//\n//    public void setStackTraceAttribute(String stackTraceAttribute) {\n//        this.stackTraceAttribute = stackTraceAttribute;\n//    }\n//\n//    public void setResolverType(String resolverType) {\n//        this.resolverType = resolverType;\n//    }\n//\n//    /**\n//     * Actually resolve the given exception that got thrown during on handler execution, returning a ModelAndView that represents a specific error page if\n//     * appropriate.\n//     * <p>\n//     * May be overridden in subclasses, in order to apply specific exception checks. Note that this template method will be invoked <i>after</i> checking\n//     * whether this resolved applies (\"mappedHandlers\" etc), so an implementation may simply proceed with its actual exception handling.\n//     * @param request current HTTP request\n//     * @param response current HTTP response\n//     * @param handler the executed handler, or {@code null} if none chosen at the time of the exception (for example, if multipart resolution failed)\n//     * @param ex the exception that got thrown during handler execution\n//     * @return a corresponding ModelAndView to forward to, or {@code null} for default processing\n//     */\n//    @Override\n//    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {\n//        if (ex instanceof BasicException || ex instanceof IllegalArgumentException) {\n//            logger.info(\"\", ex);\n//        } else {\n//            logger.error(\"\", ex);\n//        }\n//\n//        switch (resolverType) {\n//            case \"application/json\":\n//            case \"text/html\":\n//            case \"text/plain\":\n//                int code = (ex instanceof BasicException) \n//                           ? ((BasicException) ex).getCode() \n//                           : ResultCode.SERVER_ERROR.getCode();\n//                String msg = (ex instanceof BasicException) || logger.isInfoEnabled()\n//                             ? ex.getMessage() : ERROR_MSG;\n//                msg = StringUtils.isBlank(msg) ? ERROR_MSG : msg;\n//                response.setContentType(resolverType + \";charset=UTF-8\");\n//                try (PrintWriter writer = response.getWriter()) {\n//                    writer.print(Jsons.toJson(Result.failure(code, msg)));\n//                } catch (IOException e) {\n//                    logger.error(\"response write occur error\", e);\n//                }\n//                return null;\n//            default:\n//                // Expose ModelAndView for chosen error view.\n//                String viewName = determineViewName(ex, request);\n//                ModelAndView mv = null;\n//                if (viewName != null) {\n//                    // Apply HTTP status code for error views, if specified.\n//                    // Only apply it if we're processing a top-level request.\n//                    Integer statusCode = determineStatusCode(request, viewName);\n//                    if (statusCode != null) {\n//                        applyStatusCodeIfPossible(request, response, statusCode);\n//                    }\n//\n//                    mv = new ModelAndView(viewName);\n//                    if (stackTraceAttribute != null) {\n//                        mv.addObject(stackTraceAttribute, Throwables.getStackTrace(ex));\n//                    }\n//                    if (exceptionAttribute != null) {\n//                        mv.addObject(exceptionAttribute, ex);\n//                    }\n//                }\n//                return mv;\n//        }\n//    }\n//\n//    /**\n//     * Determine the view name for the given exception, first checking against the {@link #setExcludedExceptions(Class[]) \"excludedExecptions\"}, then searching\n//     * the {@link #setExceptionMappings \"exceptionMappings\"}, and finally using the {@link #setDefaultErrorView \"defaultErrorView\"} as a fallback.\n//     * @param ex the exception that got thrown during handler execution\n//     * @param request current HTTP request (useful for obtaining metadata)\n//     * @return the resolved view name, or {@code null} if excluded or none found\n//     */\n//    protected String determineViewName(Exception ex, HttpServletRequest request) {\n//        String viewName = null;\n//        if (this.excludedExceptions != null) {\n//            for (Class<?> excludedEx : this.excludedExceptions) {\n//                if (excludedEx.equals(ex.getClass())) {\n//                    return null;\n//                }\n//            }\n//        }\n//        // Check for specific exception mappings.\n//        if (this.exceptionMappings != null) {\n//            viewName = findMatchingViewName(this.exceptionMappings, ex);\n//        }\n//        // Return default error view else, if defined.\n//        if (viewName == null && this.defaultErrorView != null) {\n//            logger.debug(\"Resolving to default view '\" + this.defaultErrorView + \"' for exception of type [\" + ex.getClass().getName() + \"]\");\n//            viewName = this.defaultErrorView;\n//        }\n//        return viewName;\n//    }\n//\n//    /**\n//     * Find a matching view name in the given exception mappings.\n//     * @param exceptionMappings mappings between exception class names and error view names\n//     * @param ex the exception that got thrown during handler execution\n//     * @return the view name, or {@code null} if none found\n//     * @see #setExceptionMappings\n//     */\n//    protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {\n//        String viewName = null;\n//        String dominantMapping = null;\n//        int deepest = Integer.MAX_VALUE;\n//        for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {\n//            String exceptionMapping = (String) names.nextElement();\n//            int depth = getDepth(exceptionMapping, ex);\n//            if (depth >= 0 && (depth < deepest || (depth == deepest && dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) {\n//                deepest = depth;\n//                dominantMapping = exceptionMapping;\n//                viewName = exceptionMappings.getProperty(exceptionMapping);\n//            }\n//        }\n//        if (viewName != null) {\n//            logger.debug(\"Resolving to view '\" + viewName + \"' for exception of type [\" + ex.getClass().getName() + \"], based on exception mapping [\"\n//                + dominantMapping + \"]\");\n//        }\n//        return viewName;\n//    }\n//\n//    /**\n//     * Return the depth to the superclass matching.\n//     * <p>\n//     * 0 means ex matches exactly. Returns -1 if there's no match. Otherwise, returns depth. Lowest depth wins.\n//     */\n//    protected int getDepth(String exceptionMapping, Exception ex) {\n//        return getDepth(exceptionMapping, ex.getClass(), 0);\n//    }\n//\n//    private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {\n//        if (exceptionClass.getName().contains(exceptionMapping)) {\n//            // Found it!\n//            return depth;\n//        }\n//        // If we've gone as far as we can go and haven't found it...\n//        if (exceptionClass.equals(Throwable.class)) {\n//            return -1;\n//        }\n//        return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);\n//    }\n//\n//    /**\n//     * Determine the HTTP status code to apply for the given error view.\n//     * <p>\n//     * The default implementation returns the status code for the given view name (specified through the {@link #setStatusCodes(Properties) statusCodes}\n//     * property), or falls back to the {@link #setDefaultStatusCode defaultStatusCode} if there is no match.\n//     * <p>\n//     * Override this in a custom subclass to customize this behavior.\n//     * @param request current HTTP request\n//     * @param viewName the name of the error view\n//     * @return the HTTP status code to use, or {@code null} for the servlet container's default (200 in case of a standard error view)\n//     * @see #setDefaultStatusCode\n//     * @see #applyStatusCodeIfPossible\n//     */\n//    protected Integer determineStatusCode(HttpServletRequest request, String viewName) {\n//        if (this.statusCodes.containsKey(viewName)) {\n//            return this.statusCodes.get(viewName);\n//        }\n//        return this.defaultStatusCode;\n//    }\n//\n//    /**\n//     * Apply the specified HTTP status code to the given response, if possible (that is, if not executing within an include request).\n//     * @param request current HTTP request\n//     * @param response current HTTP response\n//     * @param statusCode the status code to apply\n//     * @see #determineStatusCode\n//     * @see #setDefaultStatusCode\n//     * @see HttpServletResponse#setStatus\n//     */\n//    protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) {\n//        if (!WebUtils.isIncludeRequest(request)) {\n//            logger.debug(\"Applying HTTP status code \" + statusCode);\n//            response.setStatus(statusCode);\n//            request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);\n//        }\n//    }\n//}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/web/LiteDevice.java",
    "content": "/*\n * Copyright 2010-2014 the original author or authors.\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 */\n\npackage cn.ponfee.commons.web;\n\n/**\n * A lightweight Device implementation suitable for use as support code.\n * Typically used to hold the output of a device resolution invocation.\n * \n * @author Keith Donald\n * @author Roy Clarkson\n * @author Scott Rossillo\n * @author Onur Kagan Ozcan\n * \n * Modify from org.springframework.mobile.device.LiteDevice\n * @see org.springframework.mobile.device.LiteDevice\n */\npublic class LiteDevice {\n\n    public static final LiteDevice NORMAL_INSTANCE = new LiteDevice(DeviceType.NORMAL, DevicePlatform.UNKNOWN);\n    public static final LiteDevice MOBILE_INSTANCE = new LiteDevice(DeviceType.MOBILE, DevicePlatform.UNKNOWN);\n    public static final LiteDevice TABLET_INSTANCE = new LiteDevice(DeviceType.TABLET, DevicePlatform.UNKNOWN);\n\n    public boolean isNormal() {\n        return this.deviceType == DeviceType.NORMAL;\n    }\n\n    public boolean isMobile() {\n        return this.deviceType == DeviceType.MOBILE;\n    }\n\n    public boolean isTablet() {\n        return this.deviceType == DeviceType.TABLET;\n    }\n\n    public DevicePlatform getDevicePlatform() {\n        return this.devicePlatform;\n    }\n\n    public DeviceType getDeviceType() {\n        return this.deviceType;\n    }\n\n    public static LiteDevice from(DeviceType deviceType, DevicePlatform devicePlatform) {\n        return new LiteDevice(deviceType, devicePlatform);\n    }\n\n    @Override\n    public String toString() {\n        return new StringBuilder().append(\"[LiteDevice type=\")\n             .append(this.deviceType).append(\"]\").toString();\n    }\n\n    private final DeviceType deviceType;\n\n    private final DevicePlatform devicePlatform;\n\n    /**\n     * Creates a LiteDevice with DevicePlatform.\n     */\n    private LiteDevice(DeviceType deviceType, DevicePlatform devicePlatform) {\n        this.deviceType = deviceType;\n        this.devicePlatform = devicePlatform;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/web/LiteDeviceResolver.java",
    "content": "/*\n * Copyright 2010-2014 the original author or authors.\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 */\n\npackage cn.ponfee.commons.web;\n\nimport org.apache.commons.collections4.CollectionUtils;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.util.ArrayList;\nimport java.util.Enumeration;\nimport java.util.List;\n\nimport static com.google.common.collect.ImmutableList.of;\n\n/**\n * A \"lightweight\" device resolver algorithm based on Wordpress's Mobile pack. Detects the\n * presence of a mobile device and works for a large percentage of mobile browsers. Does\n * not perform any device capability mapping, if you need that consider WURFL.\n * \n * The code is based primarily on a list of approximately 90 well-known mobile browser UA\n * string snippets, with a couple of special cases for Opera Mini, the W3C default\n * delivery context and certain other Windows browsers. The code also looks to see if the\n * browser advertises WAP capabilities as a hint.\n * \n * Tablet resolution is also performed based on known tablet browser UA strings. Android\n * tablets are detected based on <a href=\n * \"http://googlewebmastercentral.blogspot.com/2011/03/mo-better-to-also-detect-mobile-user.html\"\n * >Google's recommendations</a>.\n * \n * @author Keith Donald\n * @author Roy Clarkson\n * @author Scott Rossillo\n * @author Yuri Mednikov\n * @author Onur Kagan Ozcan\n * \n * Modify from org.springframework.mobile.device.LiteDeviceResolver\n * @see org.springframework.mobile.device.LiteDeviceResolver\n */\npublic class LiteDeviceResolver {\n\n    private final List<String> normalUserAgentKeywords = new ArrayList<>();\n\n    public LiteDeviceResolver() {\n        this(null);\n    }\n\n    public LiteDeviceResolver(List<String> normalUserAgentKeywords) {\n        if (CollectionUtils.isNotEmpty(normalUserAgentKeywords)) {\n            this.normalUserAgentKeywords.addAll(normalUserAgentKeywords);\n        }\n    }\n\n    public LiteDevice resolveDevice(HttpServletRequest request) {\n        String userAgent = request.getHeader(\"User-Agent\");\n        // UserAgent keyword detection of Normal devices\n        if (userAgent != null) {\n            userAgent = userAgent.toLowerCase();\n            for (String keyword : normalUserAgentKeywords) {\n                if (userAgent.contains(keyword)) {\n                    return resolveFallback(request);\n                }\n            }\n        }\n        // UserAgent keyword detection of Tablet devices\n        if (userAgent != null) {\n            userAgent = userAgent.toLowerCase();\n            // Android special case\n            if (userAgent.contains(\"android\") && !userAgent.contains(\"mobile\")) {\n                return resolveWithPlatform(DeviceType.TABLET, DevicePlatform.ANDROID);\n            }\n            // Apple special case\n            if (userAgent.contains(\"ipad\")) {\n                return resolveWithPlatform(DeviceType.TABLET, DevicePlatform.IOS);\n            }\n            // Kindle Fire special case\n            if (userAgent.contains(\"silk\") && !userAgent.contains(\"mobile\")) {\n                return resolveWithPlatform(DeviceType.TABLET, DevicePlatform.UNKNOWN);\n            }\n            for (String keyword : KNOWN_TABLET_USER_AGENT_KEYWORDS) {\n                if (userAgent.contains(keyword)) {\n                    return resolveWithPlatform(DeviceType.TABLET, DevicePlatform.UNKNOWN);\n                }\n            }\n        }\n        // UAProf detection\n        if (request.getHeader(\"x-wap-profile\") != null || request.getHeader(\"Profile\") != null) {\n            if (userAgent != null) {\n                // Android special case\n                if (userAgent.contains(\"android\")) {\n                    return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.ANDROID);\n                }\n                // Apple special case\n                if (userAgent.contains(\"iphone\") || userAgent.contains(\"ipod\") || userAgent.contains(\"ipad\")) {\n                    return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.IOS);\n                }\n            }\n            return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN);\n        }\n        // User-Agent prefix detection\n        if (userAgent != null && userAgent.length() >= 4) {\n            String prefix = userAgent.substring(0, 4).toLowerCase();\n            if (KNOWN_MOBILE_USER_AGENT_PREFIXES.contains(prefix)) {\n                return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN);\n            }\n        }\n        // Accept-header based detection\n        String accept = request.getHeader(\"Accept\");\n        if (accept != null && accept.contains(\"wap\")) {\n            return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN);\n        }\n        // UserAgent keyword detection for Mobile devices\n        if (userAgent != null) {\n            // Android special case\n            if (userAgent.contains(\"android\")) {\n                return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.ANDROID);\n            }\n            // Apple special case\n            if (userAgent.contains(\"iphone\") || userAgent.contains(\"ipod\") || userAgent.contains(\"ipad\")) {\n                return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.IOS);\n            }\n            for (String keyword : KNOWN_MOBILE_USER_AGENT_KEYWORDS) {\n                if (userAgent.contains(keyword)) {\n                    return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN);\n                }\n            }\n        }\n        // OperaMini special case\n        @SuppressWarnings(\"rawtypes\") Enumeration headers = request.getHeaderNames();\n        while (headers.hasMoreElements()) {\n            String header = (String) headers.nextElement();\n            if (header.contains(\"OperaMini\")) {\n                /*return LiteDevice.MOBILE_INSTANCE;*/\n                return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.UNKNOWN);\n            }\n        }\n        return resolveFallback(request);\n    }\n\n    // subclassing hooks\n\n    /**\n     * Wrapper method for allow subclassing platform based resolution\n     */\n    protected LiteDevice resolveWithPlatform(DeviceType deviceType, DevicePlatform devicePlatform) {\n        return LiteDevice.from(deviceType, devicePlatform);\n    }\n\n    /**\n     * Fallback called if no mobile device is matched by this resolver. The default\n     * implementation of this method returns a \"normal\" {@link Device} that is neither\n     * mobile or a tablet. Subclasses may override to try additional mobile or tablet\n     * device matching before falling back to a \"normal\" device.\n     */\n    protected LiteDevice resolveFallback(HttpServletRequest request) {\n        return LiteDevice.NORMAL_INSTANCE;\n    }\n\n    // internal helpers\n    private static final List<String> KNOWN_MOBILE_USER_AGENT_PREFIXES = of(\n        \"w3c \", \"w3c-\", \"acs-\", \"alav\", \"alca\", \"amoi\", \"audi\", \"avan\", \"benq\",\n        \"bird\", \"blac\", \"blaz\", \"brew\", \"cell\", \"cldc\", \"cmd-\", \"dang\", \"doco\",\n        \"eric\", \"hipt\", \"htc_\", \"inno\", \"ipaq\", \"ipod\", \"jigs\", \"kddi\", \"keji\",\n        \"leno\", \"lg-c\", \"lg-d\", \"lg-g\", \"lge-\", \"lg/u\", \"maui\", \"maxo\", \"midp\",\n        \"mits\", \"mmef\", \"mobi\", \"mot-\", \"moto\", \"mwbp\", \"nec-\", \"newt\", \"noki\",\n        \"palm\", \"pana\", \"pant\", \"phil\", \"play\", \"port\", \"prox\", \"qwap\", \"sage\",\n        \"sams\", \"sany\", \"sch-\", \"sec-\", \"send\", \"seri\", \"sgh-\", \"shar\", \"sie-\",\n        \"siem\", \"smal\", \"smar\", \"sony\", \"sph-\", \"symb\", \"t-mo\", \"teli\", \"tim-\",\n        \"tosh\", \"tsm-\", \"upg1\", \"upsi\", \"vk-v\", \"voda\", \"wap-\", \"wapa\", \"wapi\",\n        \"wapp\", \"wapr\", \"webc\", \"winw\", \"winw\", \"xda \", \"xda-\" \n    );\n\n    private static final List<String> KNOWN_MOBILE_USER_AGENT_KEYWORDS = of(\n        \"blackberry\", \"webos\", \"ipod\", \"lge vx\", \"midp\", \"maemo\", \"mmp\", \"mobile\",\n        \"netfront\", \"hiptop\", \"nintendo DS\", \"novarra\", \"openweb\", \"opera mobi\",\n        \"opera mini\", \"palm\", \"psp\", \"phone\", \"smartphone\", \"symbian\", \"up.browser\",\n        \"up.link\", \"wap\", \"windows ce\" \n    );\n\n    private static final List<String> KNOWN_TABLET_USER_AGENT_KEYWORDS = of(\n        \"ipad\", \"playbook\", \"hp-tablet\", \"kindle\" \n    );\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/web/WebContext.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.web;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.util.RegexUtils;\nimport cn.ponfee.commons.util.Strings;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.servlet.*;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.*;\n\n/**\n * 本地线程级的web上下文持有类\n * \n * https://github.com/alibaba/transmittable-thread-local\n * \n * @author Ponfee\n */\npublic final class WebContext {\n\n    /** HTTP请求与响应 */\n    private static final ThreadLocal<HttpServletRequest> REQUEST = new ThreadLocal<>();\n    private static final ThreadLocal<HttpServletResponse> RESPONSE = new ThreadLocal<>();\n\n    /** 用于非用户访问请求：程序内部反射调用controller方法 */\n    private static final ThreadLocal<Map<String, String[]>> INJECTED_PARAMS =\n        ThreadLocal.withInitial(HashMap::new);\n\n    // -----------------------getter\n    public static HttpServletRequest getRequest() {\n        //<listener><listener-class>org.springframework.web.context.request.RequestContextListener</listener-class></listener>\n        //return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();\n        return REQUEST.get();\n    }\n\n    public static HttpServletResponse getResponse() {\n        //return ((ServletWebRequest)RequestContextHolder.getRequestAttributes()).getResponse();\n        return RESPONSE.get();\n    }\n\n    /**\n     * 设置参数\n     * @param name\n     * @param value\n     */\n    public static void setParameter(String name, String value) {\n        String[] values = INJECTED_PARAMS.get().get(name);\n        if (values == null || values.length == 0) {\n            values = new String[] { value };\n        } else {\n            values = ArrayUtils.add(values, value);\n        }\n        INJECTED_PARAMS.get().put(name, values);\n    }\n\n    public static void clearParameter() {\n        INJECTED_PARAMS.remove();\n    }\n\n    /**\n     * 获取参数\n     * @param name\n     * @return\n     */\n    public static String getParameter(String name) {\n        HttpServletRequest request = getRequest();\n        if (null != request) {\n            return request.getParameter(name);\n        } else {\n            String[] values = INJECTED_PARAMS.get().get(name); // INJECTED_PARAMS.get().remove(name)\n            return (values == null || values.length == 0) ? null : values[0];\n        }\n    }\n\n    /**\n     * 获取参数\n     * @param name\n     * @return\n     */\n    public static String[] getParameterValues(String name) {\n        HttpServletRequest request = getRequest();\n        if (null != request) {\n            return request.getParameterValues(name);\n        } else {\n            return INJECTED_PARAMS.get().get(name);\n        }\n    }\n\n    public String getText() {\n        return getText(Files.DEFAULT_CHARSET_NAME);\n    }\n\n    /**\n     * Gets the text string from request input stream\n     * \n     * @param charset the string encoding\n     * @return string\n     */\n    public String getText(String charset) {\n        return WebUtils.getText(getRequest(), charset);\n    }\n\n    /**\n     * 获取客户端IP\n     * @return\n     */\n    public static String getClientIp() {\n        return WebUtils.getClientIp(getRequest());\n    }\n\n    /**\n     * 获取客户端设备类型\n     * @return\n     */\n    public static LiteDevice getClientDevice() {\n        return WebUtils.getClientDevice(getRequest());\n    }\n\n    // --------------------------setter/remover\n    private static void setRequest(HttpServletRequest req) {\n        REQUEST.set(req);\n    }\n\n    private static void setResponse(HttpServletResponse resp) {\n        RESPONSE.set(resp);\n    }\n\n    private static void removeRequest() {\n        REQUEST.remove();\n    }\n\n    private static void removeResponse() {\n        RESPONSE.remove();\n    }\n\n    /**\n     * Implementation of the\n     * <a href=\"http://www.w3.org/TR/cors/\">cross-origin resource sharing</a>.\n     * <p>\n     * A typical example is to use this filter to allow cross-domain\n     * <a href=\"http://cometd.org\">cometd</a> communication using the standard\n     * long polling transport instead of the JSONP transport (that is less\n     * efficient and less reactive to failures).\n     * <p>\n     * This filter allows the following configuration parameters:\n     * <dl>\n     * <dt>allowedOrigins</dt>\n     * <dd>a comma separated list of origins that are\n     * allowed to access the resources. Default value is <b>*</b>, meaning all\n     * origins.    Note that using wild cards can result in security problems\n     * for requests identifying hosts that do not exist. \n     * <p>\n     * If an allowed origin contains one or more * characters (for example\n     * http://*.domain.com), then \"*\" characters are converted to \".*\", \".\"\n     * characters are escaped to \"\\.\" and the resulting allowed origin\n     * interpreted as a regular expression.\n     * <p>\n     * Allowed origins can therefore be more complex expressions such as\n     * https?://*.domain.[a-z]{3} that matches http or https, multiple subdomains\n     * and any 3 letter top-level domain (.com, .net, .org, etc.).</dd>\n     * \n     * <dt>allowedTimingOrigins</dt>\n     * <dd>a comma separated list of origins that are\n     * allowed to time the resource. Default value is the empty string, meaning\n     * no origins.\n     * <p>\n     * The check whether the timing header is set, will be performed only if\n     * the user gets general access to the resource using the <b>allowedOrigins</b>.\n     *\n     * <dt>allowedMethods</dt>\n     * <dd>a comma separated list of HTTP methods that\n     * are allowed to be used when accessing the resources. Default value is\n     * <b>GET,POST,HEAD</b></dd>\n     * \n     * \n     * <dt>allowedHeaders</dt>\n     * <dd>a comma separated list of HTTP headers that\n     * are allowed to be specified when accessing the resources. Default value\n     * is <b>X-Requested-With,Content-Type,Accept,Origin</b>. If the value is a single \"*\",\n     * this means that any headers will be accepted.</dd>\n     * \n     * <dt>preflightMaxAge</dt>\n     * <dd>the number of seconds that preflight requests\n     * can be cached by the client. Default value is <b>1800</b> seconds, or 30\n     * minutes</dd>\n     * \n     * <dt>allowCredentials</dt>\n     * <dd>a boolean indicating if the resource allows\n     * requests with credentials. Default value is <b>true</b></dd>\n     * \n     * <dt>exposedHeaders</dt>\n     * <dd>a comma separated list of HTTP headers that\n     * are allowed to be exposed on the client. Default value is the\n     * <b>empty list</b></dd>\n     * \n     * <dt>chainPreflight</dt>\n     * <dd>if true preflight requests are chained to their\n     * target resource for normal handling (as an OPTION request).  Otherwise the\n     * filter will response to the preflight. Default is <b>true</b>.</dd>\n     * \n     * </dl>\n     * A typical configuration could be:\n     * \n     * \n     * \n     * -------------------------------------------------------------------------------\n     * Plan A:\n     * <pre>\n     *  〈filter>\n     *    〈filter-name>00000-web-context-filter</filter-name>\n     *    〈filter-class>cn.ponfee.commons.web.WebContext$WebContextFilter</filter-class>\n     *    〈init-param>\n     *      <param-name>allowedOrigins</param-name>\n     *      〈param-value>http://localhost:8080,http://127.0.0.1:8080</param-value>  \n     *    〈/init-param>\n     *    〈init-param>\n     *      〈param-name>allowedMethods</param-name>\n     *      〈param-value>GET,POST,HEAD</param-value>\n     *    〈/init-param>\n     *    〈init-param>\n     *      〈param-name>allowedHeaders</param-name>\n     *      〈param-value>X-Requested-With,Content-Type,Accept,Origin</param-value>\n     *    〈/init-param>\n     *  〈/filter>\n     *  〈filter-mapping>\n     *    〈filter-name>00000-web-context-filter</filter-name>\n     *    〈url-pattern>/*</url-pattern>\n     *  〈/filter-mapping>\n     * </pre>\n     * \n     * Reference from org.eclipse.jetty.servlets.CrossOriginFilter\n     * @see org.eclipse.jetty.servlets.CrossOriginFilter\n     * \n     * \n     * Plan B: Spring mvc xml config\n     *  <mvc:cors>\n     *    <mvc:mapping path=\"/**\" \n     *      allowed-origins=\"http://localhost:8080,http://127.0.0.1:8080\" \n     *      allowed-methods=\"GET,POST,PUT,DELETE,HEAD,OPTIONS\" \n     *      allowed-headers=\"X-Requested-With,Content-Type,Accept,Origin,LastModified,Authorization\" \n     *      exposed-headers=\"Set-Cookie\" allow-credentials=\"true\" />\n     *    <mvc:mapping path=\"/api/**\" allowed-origins=\"http://domain1.com,http://domain2.com\" />\n     *  </mvc:cors>\n     * \n     * \n     * 过滤器：根据filterName的属性值的首字母排序的顺序执行\n     */\n    /*@WebFilter(\n       filterName = \"0000.cn.ponfee.commons.web.WebContext$WebContextFilter\",\n       dispatcherTypes = {\n           DispatcherType.REQUEST,\n           DispatcherType.FORWARD,\n           DispatcherType.INCLUDE,\n           DispatcherType.ASYNC,\n           DispatcherType.ERROR\n        }, \n        urlPatterns = { \"/*\" }, \n        initParams = { \n            @WebInitParam(name = \"allowedOrigins\", value = \"http://localhost:8080,http://127.0.0.1:8080\"), \n            @WebInitParam(name = \"allowedMethods\", value = \"GET,POST,HEAD\") \n        },\n        asyncSupported = true // 支持异步Servlet\n    )*/\n    public static class WebContextFilter implements Filter {\n\n        private static final Logger LOG = LoggerFactory.getLogger(WebContextFilter.class);\n\n        // -------------------------------------------------------------Request headers\n        private static final String ORIGIN_HEADER = \"Origin\";\n        public static final String ACCESS_CONTROL_REQUEST_METHOD_HEADER = \"Access-Control-Request-Method\";\n        public static final String ACCESS_CONTROL_REQUEST_HEADERS_HEADER = \"Access-Control-Request-Headers\";\n\n        // -------------------------------------------------------------Response headers\n        public static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = \"Access-Control-Allow-Origin\";\n        public static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = \"Access-Control-Allow-Methods\";\n        public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = \"Access-Control-Allow-Headers\";\n        public static final String ACCESS_CONTROL_MAX_AGE_HEADER = \"Access-Control-Max-Age\";\n        public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = \"Access-Control-Allow-Credentials\";\n        public static final String ACCESS_CONTROL_EXPOSE_HEADERS_HEADER = \"Access-Control-Expose-Headers\";\n        public static final String TIMING_ALLOW_ORIGIN_HEADER = \"Timing-Allow-Origin\";\n\n        // -------------------------------------------------------------Implementation constants\n        public static final String ALLOWED_ORIGINS_PARAM = \"allowedOrigins\";\n        public static final String ALLOWED_TIMING_ORIGINS_PARAM = \"allowedTimingOrigins\";\n        public static final String ALLOWED_METHODS_PARAM = \"allowedMethods\";\n        public static final String ALLOWED_HEADERS_PARAM = \"allowedHeaders\";\n        public static final String PREFLIGHT_MAX_AGE_PARAM = \"preflightMaxAge\";\n        public static final String ALLOW_CREDENTIALS_PARAM = \"allowCredentials\";\n        public static final String EXPOSED_HEADERS_PARAM = \"exposedHeaders\";\n        public static final String CHAIN_PREFLIGHT_PARAM = \"chainPreflight\";\n        private static final String ANY_ORIGIN = \"*\";\n        private static final String DEFAULT_ALLOWED_ORIGINS = \"*\";\n        private static final String DEFAULT_ALLOWED_TIMING_ORIGINS = \"\";\n        private static final List<String> SIMPLE_HTTP_METHODS = Arrays.asList(\"GET\", \"POST\", \"HEAD\");\n        private static final List<String> DEFAULT_ALLOWED_METHODS = Arrays.asList(\"GET\", \"POST\", \"HEAD\");\n        private static final List<String> DEFAULT_ALLOWED_HEADERS = Arrays.asList(\"X-Requested-With\", \"Content-Type\", \"Accept\", \"Origin\");\n\n        private boolean corsEnable;\n        private boolean anyOriginAllowed;\n        private boolean anyTimingOriginAllowed;\n        private boolean anyHeadersAllowed;\n        private final List<String> allowedOrigins       = new ArrayList<>();\n        private final List<String> allowedTimingOrigins = new ArrayList<>();\n        private final List<String> allowedMethods       = new ArrayList<>();\n        private final List<String> allowedHeaders       = new ArrayList<>();\n        private final List<String> exposedHeaders       = new ArrayList<>();\n        private int preflightMaxAge;\n        private boolean allowCredentials;\n        private boolean chainPreflight;\n\n        @Override\n        public void init(FilterConfig config) {\n            String allowedOriginsConfig = config.getInitParameter(ALLOWED_ORIGINS_PARAM);\n            String allowedTimingOriginsConfig = config.getInitParameter(ALLOWED_TIMING_ORIGINS_PARAM);\n\n            corsEnable = Boolean.parseBoolean(Strings.ifBlank(config.getInitParameter(\"cors\"), \"true\")); // default enable cors\n            anyOriginAllowed = generateAllowedOrigins(allowedOrigins, allowedOriginsConfig, DEFAULT_ALLOWED_ORIGINS);\n            anyTimingOriginAllowed = generateAllowedOrigins(allowedTimingOrigins, allowedTimingOriginsConfig, DEFAULT_ALLOWED_TIMING_ORIGINS);\n\n            String allowedMethodsConfig = config.getInitParameter(ALLOWED_METHODS_PARAM);\n            if (allowedMethodsConfig == null) {\n                allowedMethods.addAll(DEFAULT_ALLOWED_METHODS);\n            } else {\n                allowedMethods.addAll(Arrays.asList(Strings.csvSplit(allowedMethodsConfig)));\n            }\n\n            String allowedHeadersConfig = config.getInitParameter(ALLOWED_HEADERS_PARAM);\n            if (allowedHeadersConfig == null) {\n                allowedHeaders.addAll(DEFAULT_ALLOWED_HEADERS);\n            } else if (\"*\".equals(allowedHeadersConfig)) {\n                anyHeadersAllowed = true;\n            } else {\n                allowedHeaders.addAll(Arrays.asList(Strings.csvSplit(allowedHeadersConfig)));\n            }\n\n            String preflightMaxAgeConfig = config.getInitParameter(PREFLIGHT_MAX_AGE_PARAM);\n            if (preflightMaxAgeConfig == null) {\n                preflightMaxAgeConfig = \"1800\"; // Default is 30 minutes\n            }\n            try {\n                preflightMaxAge = Integer.parseInt(preflightMaxAgeConfig);\n            } catch (NumberFormatException x) {\n                LOG.info(\n                    \"Cross-origin filter, could not parse '{}' parameter as integer: {}\", \n                    PREFLIGHT_MAX_AGE_PARAM, preflightMaxAgeConfig\n                );\n            }\n\n            String allowedCredentialsConfig = config.getInitParameter(ALLOW_CREDENTIALS_PARAM);\n            if (allowedCredentialsConfig == null) {\n                allowedCredentialsConfig = \"true\";\n            }\n            allowCredentials = Boolean.parseBoolean(allowedCredentialsConfig);\n\n            String exposedHeadersConfig = config.getInitParameter(EXPOSED_HEADERS_PARAM);\n            if (exposedHeadersConfig == null) {\n                exposedHeadersConfig = \"\";\n            }\n            exposedHeaders.addAll(Arrays.asList(Strings.csvSplit(exposedHeadersConfig)));\n\n            String chainPreflightConfig = config.getInitParameter(CHAIN_PREFLIGHT_PARAM);\n            if (chainPreflightConfig == null) {\n                chainPreflightConfig = \"true\";\n            }\n            chainPreflight = Boolean.parseBoolean(chainPreflightConfig);\n\n            if (LOG.isDebugEnabled()) {\n                LOG.debug(\n                    \"Cross-origin filter configuration: {}={}, {}={}, {}={}, {}={}, {}={}, {}={}, {}={}, {}={}\",\n                    ALLOWED_ORIGINS_PARAM,        allowedOriginsConfig,\n                    ALLOWED_TIMING_ORIGINS_PARAM, allowedTimingOriginsConfig,\n                    ALLOWED_METHODS_PARAM,        allowedMethodsConfig,\n                    ALLOWED_HEADERS_PARAM,        allowedHeadersConfig,\n                    PREFLIGHT_MAX_AGE_PARAM,      preflightMaxAgeConfig,\n                    ALLOW_CREDENTIALS_PARAM,      allowedCredentialsConfig,\n                    EXPOSED_HEADERS_PARAM,        exposedHeadersConfig,\n                    CHAIN_PREFLIGHT_PARAM,        chainPreflightConfig\n                );\n            }\n        }\n\n        @Override\n        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) \n            throws IOException, ServletException {\n            HttpServletRequest request   = (HttpServletRequest) req;\n            HttpServletResponse response = (HttpServletResponse) resp;\n\n            try {\n                //WebUtils.cors(request, response);\n                if (this.cros(request, response, chain)) {\n                    WebContext.setRequest(request);\n                    WebContext.setResponse(response);\n                    chain.doFilter(request, response);\n                }\n            } finally {\n                WebContext.removeRequest();\n                WebContext.removeResponse();\n            }\n        }\n\n        @Override\n        public void destroy() {\n            anyOriginAllowed = false;\n            allowedOrigins.clear();\n            allowedMethods.clear();\n            allowedHeaders.clear();\n            preflightMaxAge = 0;\n            allowCredentials = false;\n        }\n\n        protected boolean isEnabled(HttpServletRequest request) {\n            // WebSocket clients such as Chrome 5 implement a version of the WebSocket\n            // protocol that does not accept extra response headers on the upgrade response\n            for (Enumeration<String> elm = request.getHeaders(\"Connection\"); elm.hasMoreElements();) {\n                String connection = elm.nextElement();\n                if (\"Upgrade\".equalsIgnoreCase(connection)) {\n                    for (Enumeration<String> upg = request.getHeaders(\"Upgrade\"); upg.hasMoreElements();) {\n                        String upgrade = upg.nextElement();\n                        if (\"WebSocket\".equalsIgnoreCase(upgrade)) {\n                            return false;\n                        }\n                    }\n                }\n            }\n            return true;\n        }\n\n        // ----------------------------------------------------------------------------------private methods\n        private boolean generateAllowedOrigins(List<String> allowedOriginStore,\n                                               String allowedOriginsConfig, String defaultOrigin) {\n            if (allowedOriginsConfig == null) {\n                allowedOriginsConfig = defaultOrigin;\n            }\n            String[] allowedOrigins = Strings.csvSplit(allowedOriginsConfig);\n            for (String allowedOrigin : allowedOrigins) {\n                if (allowedOrigin.length() > 0) {\n                    if (ANY_ORIGIN.equals(allowedOrigin)) {\n                        allowedOriginStore.clear();\n                        return true;\n                    } else {\n                        allowedOriginStore.add(allowedOrigin);\n                    }\n                }\n            }\n            return false;\n        }\n\n        private boolean cros(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException {\n            String origin = request.getHeader(ORIGIN_HEADER);\n            // Is it a cross origin request ?\n            if (origin == null || !corsEnable || !isEnabled(request)) {\n                return true;\n            }\n\n            if (!anyOriginAllowed && !originMatches(allowedOrigins, origin)) {\n                LOG.debug(\n                     \"Cross-origin request to {} with origin {} does not match allowed origins {}\", \n                     request.getRequestURI(), origin, allowedOrigins\n                 );\n                response.sendError(HttpServletResponse.SC_FORBIDDEN, \"Invalid CORS request.\");\n                return false;\n            }\n\n            if (isSimpleRequest(request)) {\n                LOG.debug(\"Cross-origin request to {} is a simple cross-origin request\", request.getRequestURI());\n                handleSimpleResponse(request, response, origin);\n            } else if (isPreflightRequest(request)) {\n                LOG.debug(\"Cross-origin request to {} is a preflight cross-origin request\", request.getRequestURI());\n                handlePreflightResponse(request, response, origin);\n                if (chainPreflight) {\n                    LOG.debug(\"Preflight cross-origin request to {} forwarded to application\", request.getRequestURI());\n                } else {\n                    return false;\n                }\n            } else {\n                LOG.debug(\"Cross-origin request to {} is a non-simple cross-origin request\", request.getRequestURI());\n                handleSimpleResponse(request, response, origin);\n            }\n\n            if (anyTimingOriginAllowed || originMatches(allowedTimingOrigins, origin)) {\n                response.setHeader(TIMING_ALLOW_ORIGIN_HEADER, origin);\n            } else {\n                LOG.debug(\n                    \"Cross-origin request to {} with origin {} does not match allowed timing origins {}\", \n                    request.getRequestURI(), origin, allowedTimingOrigins\n                );\n            }\n\n            return true;\n        }\n\n        private boolean originMatches(List<String> allowedOrigins, String originList) {\n            if (originList.trim().length() == 0) {\n                return false;\n            }\n\n            String[] origins = originList.split(\" \");\n            for (String origin : origins) {\n                if (origin.trim().length() == 0) {\n                    continue;\n                }\n\n                for (String allowedOrigin : allowedOrigins) {\n                    if (allowedOrigin.contains(\"*\")) {\n                        // we want to be greedy here to match multiple subdomains, thus we use .*\n                        String regex = allowedOrigin.replace(\".\", \"\\\\.\").replace(\"*\", \".*\");\n                        if (RegexUtils.matches(origin, regex)) {\n                            return true;\n                        }\n                    } else if (allowedOrigin.equals(origin)) {\n                        return true;\n                    }\n                }\n            }\n            return false;\n        }\n\n        private boolean isSimpleRequest(HttpServletRequest request) {\n            String method = request.getMethod();\n            if (SIMPLE_HTTP_METHODS.contains(method)) {\n                // TODO: implement better detection of simple headers\n                // The specification says that for a request to be simple, custom request headers must be simple.\n                // Here for simplicity I just check if there is a Access-Control-Request-Method header,\n                // which is required for preflight requests\n                return request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null;\n            }\n            return false;\n        }\n\n        private boolean isPreflightRequest(HttpServletRequest request) {\n            String method = request.getMethod();\n            if (!\"OPTIONS\".equalsIgnoreCase(method)) {\n                return false;\n            }\n            return request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) != null;\n        }\n\n        private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse response, String origin) {\n            response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);\n            //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation\n            if (!anyOriginAllowed) {\n                response.addHeader(\"Vary\", ORIGIN_HEADER);\n            }\n            if (allowCredentials) {\n                response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, \"true\");\n            }\n            if (!exposedHeaders.isEmpty()) {\n                response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS_HEADER, commify(exposedHeaders));\n            }\n        }\n\n        private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse response, String origin) {\n            boolean methodAllowed = isMethodAllowed(request);\n\n            if (!methodAllowed) {\n                return;\n            }\n            List<String> headersRequested = getAccessControlRequestHeaders(request);\n            boolean headersAllowed = areHeadersAllowed(headersRequested);\n            if (!headersAllowed) {\n                return;\n            }\n            response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);\n            //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation\n            if (!anyOriginAllowed) {\n                response.addHeader(\"Vary\", ORIGIN_HEADER);\n            }\n            if (allowCredentials) {\n                response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, \"true\");\n            }\n            if (preflightMaxAge > 0) {\n                response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge));\n            }\n            response.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, commify(allowedMethods));\n            if (anyHeadersAllowed) {\n                response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(headersRequested));\n            } else {\n                response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(allowedHeaders));\n            }\n        }\n\n        private boolean isMethodAllowed(HttpServletRequest request) {\n            String accessControlRequestMethod = request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER);\n            LOG.debug(\"{} is {}\", ACCESS_CONTROL_REQUEST_METHOD_HEADER, accessControlRequestMethod);\n            boolean result = false;\n            if (accessControlRequestMethod != null) {\n                result = allowedMethods.contains(accessControlRequestMethod);\n            }\n            LOG.debug(\n                \"Method {} is{} among allowed methods {}\", \n                accessControlRequestMethod, result ? \"\" : \" not\", allowedMethods\n            );\n            return result;\n        }\n\n        private List<String> getAccessControlRequestHeaders(HttpServletRequest request) {\n            String accessControlRequestHeaders = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS_HEADER);\n            LOG.debug(\"{} is {}\", ACCESS_CONTROL_REQUEST_HEADERS_HEADER, accessControlRequestHeaders);\n            if (accessControlRequestHeaders == null) {\n                return Collections.emptyList();\n            }\n\n            List<String> requestedHeaders = new ArrayList<>();\n            String[] headers = Strings.csvSplit(accessControlRequestHeaders);\n            for (String header : headers) {\n                String h = header.trim();\n                if (h.length() > 0) {\n                    requestedHeaders.add(h);\n                }\n            }\n            return requestedHeaders;\n        }\n\n        private boolean areHeadersAllowed(List<String> requestedHeaders) {\n            if (anyHeadersAllowed) {\n                LOG.debug(\"Any header is allowed\");\n                return true;\n            }\n\n            boolean result = true;\n            for (String requestedHeader : requestedHeaders) {\n                boolean headerAllowed = false;\n                for (String allowedHeader : allowedHeaders) {\n                    if (requestedHeader.equalsIgnoreCase(allowedHeader.trim())) {\n                        headerAllowed = true;\n                        break;\n                    }\n                }\n                if (!headerAllowed) {\n                    result = false;\n                    break;\n                }\n            }\n            LOG.debug(\n                \"Headers {} are{} among allowed headers {}\", \n                requestedHeaders, result ? \"\" : \" not\", allowedHeaders\n            );\n            return result;\n        }\n\n        private String commify(List<String> strings) {\n            StringBuilder builder = new StringBuilder();\n            for (int i = 0; i < strings.size(); ++i) {\n                if (i > 0) {\n                    builder.append(\",\");\n                }\n                builder.append(strings.get(i));\n            }\n            return builder.toString();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/web/WebUtils.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.web;\n\nimport cn.ponfee.commons.http.ContentType;\nimport cn.ponfee.commons.io.ByteOrderMarks;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.io.GzipProcessor;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.util.Networks;\nimport cn.ponfee.commons.util.URLCodes;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.servlet.ServletContext;\nimport javax.servlet.http.Cookie;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.TreeMap;\nimport java.util.regex.Pattern;\n\n/**\n * web工具类\n * \n * @author Ponfee\n */\npublic final class WebUtils {\n\n    private final static Pattern PATTERN_SUFFIX = Pattern.compile(\"\\\\S*[?]\\\\S*\");\n    public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = \"javax.servlet.include.context_path\";\n\n    /*private static final Pattern PATTERN_MOBILE = Pattern.compile(\n        \"\\\\b(ip(hone|od)|android|opera m(ob|in)i|windows (phone|ce)|blackberry|s(ymbian|eries60|amsung)\"\n      + \"|p(laybook|alm|rofile/midp|laystation portable)|nokia|fennec|htc[-_]|mobile|up.browser\"\n      + \"|[1-4][0-9]{2}x[1-4][0-9]{2})\\\\b\", Pattern.CASE_INSENSITIVE);\n\n    private static final Pattern PATTERN_IPAD = Pattern.compile(\n        \"\\\\b(ipad|tablet|(Nexus 7)|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\\\b\", \n        Pattern.CASE_INSENSITIVE\n    );*/\n\n    /** authorization */\n    public static final String AUTH_HEADER = \"X-Auth-Token\";\n    public static final String AUTH_COOKIE = \"auth_token\";\n    public static final String AUTH_PARAME = \"authToken\";\n\n    public static final String COOKIE_ROOT_PATH = \"/\";\n\n    /**\n     * Gets the http servlet request parameters\n     * the parameter value is {@link java.lang.String} type\n     * if is array parameter, then the value is based-join on \",\" as String\n     * \n     * @param request\n     * @return Map<String, String>, the array param value use \",\" to join\n     */\n    public static Map<String, String> getParams(HttpServletRequest request) {\n        Map<String, String> params = new TreeMap<>();\n        for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {\n            params.put(entry.getKey(), StringUtils.join(entry.getValue(), \",\"));\n        }\n        return params;\n    }\n\n    public static String getText(HttpServletRequest request) {\n        return getText(request, Files.UTF_8);\n    }\n\n    /**\n     * get the text string from request input stream\n     * @param request the HttpServletRequest\n     * @param charset the string encoding\n     * @return string\n     */\n    public static String getText(HttpServletRequest request, String charset) {\n        try (InputStream input = request.getInputStream()) {\n            return IOUtils.toString(input, charset);\n        } catch (Exception e) {\n            throw new RuntimeException(\"read request input stream error\", e);\n        }\n    }\n\n    private static final List<String> LOCAL_IPS = Arrays.asList(\n        \"127.0.0.1\", \"0:0:0:0:0:0:0:1\", \"::1\"\n    );\n    /**\n     * 获取客户端ip\n     * @param req\n     * @return\n     */\n    public static String getClientIp(HttpServletRequest req) {\n        boolean invalid;\n        String ip = req.getHeader(\"x-forwarded-for\");\n        if (invalid = isInvalidIp(ip)) {\n            ip = req.getHeader(\"Proxy-Client-IP\");\n        }\n        if (invalid && (invalid = isInvalidIp(ip))) {\n            ip = req.getHeader(\"WL-Proxy-Client-IP\");\n        }\n        if (invalid && (invalid = isInvalidIp(ip))) {\n            ip = req.getHeader(\"HTTP_CLIENT_IP\");\n        }\n        if (invalid && (invalid = isInvalidIp(ip))) {\n            ip = req.getHeader(\"HTTP_X_FORWARDED_FOR\");\n        }\n        if (invalid && (invalid = isInvalidIp(ip))) {\n            ip = req.getHeader(\"X-Real-IP\");\n        }\n        if (invalid && (invalid = isInvalidIp(ip))) {\n            ip = req.getRemoteAddr();\n        }\n\n        if (ip != null && ip.indexOf(\",\") > 0) {\n            // 对于通过多个代理的情况，第一个ip为客户端真实ip，多个ip按照','分割\n            ip = ip.substring(0, ip.indexOf(\",\"));\n        }\n\n        if (LOCAL_IPS.contains(ip)) {\n            ip = Networks.HOST_IP; // 如果是本机ip\n        }\n        return ip;\n    }\n\n    /**\n     * 获取客户端设备类型\n     * @return\n     */\n    public static LiteDevice getClientDevice(HttpServletRequest req) {\n        return new LiteDeviceResolver().resolveDevice(req);\n        /*String userAgent = Objects.toString(userAgent(req), \"\");\n        if (PATTERN_MOBILE.matcher(userAgent).find()) {\n            return DeviceType.MOBILE;\n        } else if (PATTERN_IPAD.matcher(userAgent).find()) {\n            return DeviceType.TABLET;\n        } else {\n            return DeviceType.NORMAL;\n        }*/\n    }\n\n    /**\n     * 判断是否ajax请求\n     * @param req\n     * @return\n     */\n    public static boolean isAjax(HttpServletRequest req) {\n        return \"XMLHttpRequest\".equals(req.getHeader(\"X-Requested-With\"));\n    }\n\n    /**\n     * Returns the web browser user-agent\n     * \n     * @param req the HttpServletRequest\n     * @return web browser user-agent\n     */\n    public static String userAgent(HttpServletRequest req) {\n        return req.getHeader(\"User-Agent\");\n    }\n\n    /**\n     * 响应数据到请求客户端\n     * @param resp\n     * @param contentType\n     * @param text\n     * @param charset\n     */\n    public static void response(HttpServletResponse resp, \n                                ContentType contentType, \n                                String text, String charset) {\n        resp.setContentType(contentType.value() + \";charset=\" + charset);\n        resp.setCharacterEncoding(charset);\n        try (PrintWriter writer = resp.getWriter()) {\n            writer.write(text);\n        } catch (IOException e) {\n            // cannot happened\n            throw new RuntimeException(\"response \" + contentType + \" occur error\", e);\n        }\n    }\n\n    /**\n     * 响应json数据\n     * @param resp\n     * @param data\n     */\n    public static void respJson(HttpServletResponse resp, Object data) {\n        respJson(resp, data, Files.UTF_8);\n    }\n\n    public static void respJson(HttpServletResponse resp, Object data, String charset) {\n        response(resp, ContentType.APPLICATION_JSON, toJson(data), charset);\n    }\n\n    public static void respJsonp(HttpServletResponse response, \n                                 String callback, Object data) {\n        respJsonp(response, callback, data, Files.UTF_8);\n    }\n\n    /**\n     * 响应jsonp数据\n     * @param resp\n     * @param callback\n     * @param data\n     * @param charset\n     */\n    public static void respJsonp(HttpServletResponse resp, String callback, \n                                 Object data, String charset) {\n        respJson(resp, callback + \"(\" + toJson(data) + \");\", charset);\n    }\n\n    public static void download(HttpServletResponse resp,\n                                byte[] data, String filename) {\n        download(resp, data, filename, Files.UTF_8, false, false);\n    }\n\n    /**\n    * Response as a stream attachment\n    * \n    * @param resp     the HttpServletResponse\n    * @param data     the resp byte array data\n    * @param filename the resp attachment filename\n    * @param charset  the attachment filename encoding\n    * @param isGzip   {@code true} to use gzip compress\n    * @param withBom  {@code true} with bom header\n    */\n    public static void download(HttpServletResponse resp, byte[] data,\n                                String filename, String charset,\n                                boolean isGzip, boolean withBom) {\n        download(resp, new ByteArrayInputStream(data), filename, charset, isGzip, withBom);\n    }\n\n    /**\n     * response to input stream\n     * @param resp     the HttpServletResponse\n     * @param input    the input stream\n     * @param filename the resp attachment filename\n     */\n    public static void download(HttpServletResponse resp, \n                                InputStream input, String filename) {\n        download(resp, input, filename, Files.UTF_8, false, false);\n    }\n\n    /**\n     * Response as a stream attachment\n     * \n     * @param resp     the HttpServletResponse\n     * @param input    the input stream\n     * @param filename the resp attachment filename\n     * @param charset  the attachment filename encoding\n     * @param isGzip   {@code true} to use gzip compress\n     * @param withBom  {@code true} with bom header\n     */\n    public static void download(HttpServletResponse resp, InputStream input, \n                                String filename, String charset, \n                                boolean isGzip, boolean withBom) {\n        try (InputStream in = input; \n             OutputStream out = resp.getOutputStream()\n        ) {\n            addStreamHeader(resp, filename, charset);\n\n            byte[] bom = withBom ? ByteOrderMarks.get(Charset.forName(charset)) : null;\n            if (isGzip) {\n                resp.setHeader(\"Content-Encoding\", \"gzip\");\n                if (bom != null) {\n                    GzipProcessor.compress(\n                        new SequenceInputStream(new ByteArrayInputStream(bom), in), out\n                    );\n                } else {\n                    GzipProcessor.compress(in, out);\n                }\n            } else {\n                if (bom != null) {\n                    out.write(bom);\n                }\n                IOUtils.copyLarge(in, out);\n            }\n        } catch (IOException e) {\n            // cannot happened\n            throw new RuntimeException(\"response input stream occur error\", e);\n        }\n    }\n\n    /**\n     * 响应数据流，如图片数据\n     * \n     * response(resp, image_byte_array, ContentType.IMAGE_JPEG.value(), false);\n     * \n     * @param resp the HttpServletResponse\n     * @param data the byte array data\n     * @param contentType the content-type\n     * @param isGzip whether to encode gzip\n     */\n    public static void response(HttpServletResponse resp, byte[] data,\n                                ContentType contentType, boolean isGzip) {\n        response(resp, new ByteArrayInputStream(data), contentType, isGzip);\n    }\n\n    /**\n     * 响应数据流，如图片数据\n     * \n     * response(resp, image_file_input_stream, ContentType.IMAGE_JPEG.value(), false);\n     * \n     * @param resp  the HttpServletResponse\n     * @param input the input stream\n     * @param contentType the content-type\n     * @param isGzip whether to encode gzip\n     */\n    public static void response(HttpServletResponse resp, InputStream input,\n                                ContentType contentType, boolean isGzip) {\n        try (OutputStream output = resp.getOutputStream()) {\n            resp.setContentType(contentType.value());\n            if (isGzip) {\n                resp.setHeader(\"Content-Encoding\", \"gzip\");\n                GzipProcessor.compress(input, output);\n            } else {\n                IOUtils.copy(input, output);\n            }\n        } catch (IOException e) {\n            // cannot happened\n            throw new RuntimeException(\"response byte array data occur error\", e);\n        }\n    }\n\n    /**\n     * ross-Origin Resource Sharing\n     * \n     * @param req  the HttpServletRequest\n     * @param resp the HttpServletResponse\n     */\n    public static void cors(HttpServletRequest req, HttpServletResponse resp) {\n        String origin = req.getHeader(\"Origin\");\n        origin = StringUtils.isEmpty(origin) ? \"*\" : origin;\n        resp.setHeader(\"Access-Control-Allow-Origin\", origin);\n\n        String headers = req.getHeader(\"Access-Control-Allow-Headers\");\n        headers = StringUtils.isEmpty(headers) \n                  ? \"Origin,No-Cache,X-Requested-With,If-Modified-Since,Pragma,\"\n                  + \"Expires,Last-Modified,Cache-Control,Content-Type,X-E4M-With\" \n                  : headers;\n        resp.setHeader(\"Access-Control-Allow-Headers\", headers);\n\n        resp.setHeader(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,DELETE,HEAD,OPTIONS\");\n        resp.setHeader(\"Access-Control-Max-Age\", \"0\");\n        resp.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n        resp.setHeader(\"XDomainRequestAllowed\", \"1\");\n    }\n\n    /**\n     * 获取请求地址后缀名\n     * @param req\n     * @return\n     */\n    public static String getUrlSuffix(HttpServletRequest req) {\n        String url = req.getRequestURI();\n        if (!url.contains(\".\")) {\n            return null;\n        }\n\n        String[] pathInfos = url.split(\"/\");\n        String endUrl = pathInfos[pathInfos.length - 1];\n        if (PATTERN_SUFFIX.matcher(url).find()) {\n            String[] spEndUrl = endUrl.split(\"\\\\?\");\n            return spEndUrl[0].split(\"\\\\.\")[1];\n        } else {\n            return endUrl.split(\"\\\\.\")[1];\n        }\n    }\n\n    public static String xssReplace(String text) {\n        return StringUtils.replaceEach(\n            text, \n            new String[] { \"<\", \">\", \"%3c\", \"%3e\" }, \n            new String[] { \"&lt;\", \"&gt;\", \"&lt;\", \"&gt;\" }\n        );\n    }\n\n    /**\n     * get cookie value\n     * @param req\n     * @param name\n     * @return\n     */\n    public static String getCookie(HttpServletRequest req, String name) {\n        Cookie[] cookies = req.getCookies();\n        if (cookies == null) {\n            return null;\n        }\n\n        for (Cookie cookie : cookies) {\n            if (name.equals(cookie.getName())) {\n                return cookie.getValue();\n            }\n        }\n        return null;\n    }\n\n    public static void delCookie(HttpServletRequest req, HttpServletResponse resp, String name) {\n        delCookie(req, resp, COOKIE_ROOT_PATH, name);\n    }\n\n    /**\n     * Delete the cookie by spec name\n     * \n     * @param req  the HttpServletRequest\n     * @param resp the HttpServletResponse\n     * @param path the cookie path\n     * @param name the cookie name\n     */\n    public static void delCookie(HttpServletRequest req, \n                                 HttpServletResponse resp, \n                                 String path, String name) {\n        Cookie[] cookies = req.getCookies();\n        if (cookies == null) {\n            return;\n        }\n\n        for (Cookie cookie : cookies) {\n            if (name.equals(cookie.getName())) {\n                cookie.setPath(path); \n                cookie.setMaxAge(0);\n                cookie.setValue(\"\");\n                resp.addCookie(cookie);\n                return;\n            }\n        }\n    }\n\n    /**\n     * 设置cookie\n     * @param response\n     * @param name\n     * @param value\n     */\n    public static void addCookie(HttpServletResponse response, \n                                 String name, String value) {\n        addCookie(response, name, value, COOKIE_ROOT_PATH, 24 * 60 * 60);\n    }\n\n    /**\n     * 设置cookie\n     * @param resp\n     * @param name\n     * @param value\n     * @param path\n     * @param maxAge\n     */\n    public static void addCookie(HttpServletResponse resp, String name, \n                                 String value, String path, int maxAge) {\n        resp.addCookie(createCookie(name, value, path, maxAge));\n    }\n\n    /**\n     * Creates a cookie\n     * \n     * @param name\n     * @param value\n     * @param path\n     * @param maxAge\n     * @return an object of cookie\n     */\n    public static Cookie createCookie(String name, String value, \n                                      String path, int maxAge) {\n        Cookie cookie = new Cookie(name, value);\n        cookie.setPath(path);\n        cookie.setMaxAge(maxAge);\n        //cookie.setHttpOnly(true);\n        return cookie;\n    }\n\n    /**\n     * 会话跟踪\n     */\n    public static void setSessionTrace(HttpServletResponse response, String token) {\n        int maxAge = (token == null) ? 0 : 86400;\n        //result.setAuthToken(token); // to response body\n        WebUtils.addCookie(response, AUTH_COOKIE, token, COOKIE_ROOT_PATH, maxAge); // to cookie\n        response.addHeader(AUTH_HEADER, token); // to header\n    }\n\n    /**\n     * 会话跟踪\n     */\n    public static String getSessionTrace(HttpServletRequest request) {\n        String authToken = request.getParameter(AUTH_PARAME); // from param\n        if (authToken != null) {\n            return authToken;\n        }\n\n        authToken = WebUtils.getCookie(request, AUTH_COOKIE); // from cooike\n        if (authToken != null) {\n            return authToken;\n        }\n\n        return request.getHeader(AUTH_HEADER); // from header;\n    }\n\n    public static String getContextPath(ServletContext context) {\n        String contextPath = normalize(URLCodes.decodeURI(context.getContextPath()));\n        if (\"/\".equals(contextPath)) {\n            contextPath = \"\";\n        }\n        return contextPath;\n    }\n\n    /**\n     * Return the context path for the given request, detecting an include request\n     * URL if called within a RequestDispatcher include.\n     * <p>As the value returned by <code>request.getContextPath()</code> is <i>not</i>\n     * decoded by the servlet container, this method will decode it.\n     *\n     * @param request current HTTP request\n     * @return the context path\n     */\n    public static String getContextPath(HttpServletRequest request) {\n        //String contextPath = request.getContextPath();\n        //return StringUtils.isEmpty(contextPath) ? \"/\" : contextPath;\n\n        String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE);\n        if (contextPath == null) {\n            contextPath = request.getContextPath();\n        }\n\n        String encoding = request.getCharacterEncoding();\n        contextPath = (encoding == null) \n                    ? URLCodes.decodeURI(contextPath) \n                    : URLCodes.decodeURI(contextPath, encoding);\n\n        contextPath = normalize(contextPath);\n        if (\"/\".equals(contextPath)) {\n            contextPath = \"\"; // the normalize method will return a \"/\" and includes on Jetty, will also be a \"/\".\n        }\n        return contextPath;\n    }\n\n    /**\n     * Normalize a relative URI path that may have relative values (\"/./\",\n     * \"/../\", and so on ) it it.  <strong>WARNING</strong> - This method is\n     * useful only for normalizing application-generated paths.  It does not\n     * try to perform security checks for malicious input.\n     * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in\n     * Tomcat trunk, r939305\n     *\n     * @param path Relative path to be normalized\n     * @return normalized path\n     */\n    public static String normalize(String path) {\n        return normalize(path, true);\n    }\n\n    /**\n     * Normalize a relative URI path that may have relative values (\"/./\",\n     * \"/../\", and so on ) it it.  <strong>WARNING</strong> - This method is\n     * useful only for normalizing application-generated paths.  It does not\n     * try to perform security checks for malicious input.\n     * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in\n     * Tomcat trunk, r939305\n     *\n     * @param path             Relative path to be normalized\n     * @param replaceBackSlash Should '\\\\' be replaced with '/'\n     * @return normalized path\n     */\n    public static String normalize(String path, boolean replaceBackSlash) {\n        if (path == null) {\n            return null;\n        }\n\n        // Create a place for the normalized path\n        String normalized = path;\n\n        if (replaceBackSlash && normalized.indexOf('\\\\') >= 0) {\n            normalized = normalized.replace('\\\\', '/');\n        }\n\n        if (\"/.\".equals(normalized)) {\n            return \"/\";\n        }\n\n        // Add a leading \"/\" if necessary\n        if (!normalized.startsWith(\"/\")) {\n            normalized = \"/\" + normalized;\n        }\n\n        // Resolve occurrences of \"//\" in the normalized path\n        while (true) {\n            int index = normalized.indexOf(\"//\");\n            if (index < 0) {\n                break;\n            }\n            normalized = normalized.substring(0, index) + normalized.substring(index + 1);\n        }\n\n        // Resolve occurrences of \"/./\" in the normalized path\n        while (true) {\n            int index = normalized.indexOf(\"/./\");\n            if (index < 0) {\n                break;\n            }\n            normalized = normalized.substring(0, index) + normalized.substring(index + 2);\n        }\n\n        // Resolve occurrences of \"/../\" in the normalized path\n        while (true) {\n            int index = normalized.indexOf(\"/../\");\n            if (index < 0) {\n                break;\n            }\n            if (index == 0) {\n                // Trying to go outside our context\n                return (null);\n            }\n            int index2 = normalized.lastIndexOf('/', index - 1);\n            normalized = normalized.substring(0, index2) + normalized.substring(index + 3);\n        }\n\n        // Return the normalized path that we have completed\n        return (normalized);\n    }\n\n    // --------------------------------------------------------------------private methods\n    /**\n     * to json string\n     * @param data\n     * @return\n     */\n    private static String toJson(Object data) {\n        return (data instanceof CharSequence)\n               ? data.toString()\n               : Jsons.toJson(data);\n    }\n\n    private static void addStreamHeader(HttpServletResponse resp, String filename, String charset) {\n        filename = URLCodes.encodeURIComponent(filename, charset); // others web browse\n        //filename = new String(filename.getBytes(charset), ISO_8859_1); // firefox web browse\n\n        resp.setContentType(ContentType.APPLICATION_OCTET_STREAM.value());\n\n        //resp.setHeader(\"Content-Length\", Long.toString(length));\n\n        resp.setHeader(\"Content-Disposition\", \"attachment; filename=\\\"\" + filename + \"\\\"\");\n        //resp.setHeader(\"Content-Disposition\", \"form-data; name=\\\"attachment\\\"; filename=\\\"\" + filename + \"\\\"\");\n\n        //resp.setHeader(\"Set-Cookie\", \"fileDownload=true; path=/\"); // JQuery $.fileDownload plugin\n\n        resp.setCharacterEncoding(charset);\n    }\n\n    private static boolean isInvalidIp(String ip) {\n        return StringUtils.isBlank(ip) || \"unknown\".equalsIgnoreCase(ip);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/JAXWS.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws;\n\nimport javax.xml.namespace.QName;\nimport javax.xml.ws.Endpoint;\nimport javax.xml.ws.Service;\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\n/**\n * jax-ws工具类\n * \n * 错误：java.lang.NoSuchMethodError: javax.wsdl.xml.WSDLReader.readWSDL\n *     (Ljavax/wsdl/xml/WSDLLocator;Lorg/w3c/dom/Element;)Ljavax/wsdl/Definition\n * \n * 原因：两个依赖的jar包有冲突（axis-wsdl4j-1.5.1.jar与wsdl4j-1.6.3.jar冲突）\n *     [axis:axis:1.4]依赖[axis:axis-wsdl4j:1.5.1]               axis-wsdl4j-1.5.1.jar\n *     [org.apache.cxf:cxf-api:2.7.15]依赖[wsdl4j:wsdl4j:1.6.3]  wsdl4j-1.6.3.jar\n * \n * 解决：排除依赖axis:axis-wsdl4j\n *     <dependency>\n *         <groupId>axis</groupId>\n *         <artifactId>axis</artifactId>\n *         <version>1.4</version>\n *       <exclusions>\n *         <exclusion>\n *           <groupId>axis</groupId>\n *           <artifactId>axis-wsdl4j</artifactId>\n *         </exclusion>\n *       </exclusions>\n *     </dependency>\n * \n * @author Ponfee\n */\npublic class JAXWS {\n\n    /**\n     * Returns a JAX-WS client\n     * \n     * @param clazz         the webservice interface, as use {@code WebService} annotation\n     * @param address       the wsdl url like as http://ip:port/ws/webserviceName?wsdl\n     * @param namespaceURI  the targetNamespace of element <b>&lt;wsdl:definitions&gt;</b> attribute\n     * @param localPart     the name of element <b>&lt;wsdl:definitions&gt;</b> attribute\n     * @return client object can calls rpc\n     */\n    public static <T> T client(Class<T> clazz, String address, \n                               String namespaceURI, String localPart) {\n        return client(clazz, address, new QName(namespaceURI, localPart));\n    }\n\n    public static <T> T client(Class<T> clazz, String address, QName qname) {\n        try {\n            // clazz为接口类\n            return Service.create(new URL(address), qname).getPort(clazz);\n        } catch (MalformedURLException e) {\n            // cannot happened\n            throw new IllegalArgumentException(\"Invalid url: \" + address, e);\n        }\n    }\n\n    /**\n     * Server publish the webservice\n     * \n     * @param address\n     * @param implementor  the webservice interface implements class instance\n     */\n    public static void publish(String address, Object implementor) {\n        Endpoint.publish(address, implementor);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ListMapAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.ws.adapter.model.MapEntry;\nimport cn.ponfee.commons.ws.adapter.model.MapItem;\nimport cn.ponfee.commons.ws.adapter.model.MapItemArray;\nimport com.google.common.collect.Lists;\nimport com.google.common.collect.Maps;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\n/**\n * List<Map<K,V>转换器\n * @param <K>\n * @param <V>\n * \n * @author Ponfee\n */\n//@XmlSeeAlso({ Object[][].class }) 在@WebService注解的接口中加上此注解\n@SuppressWarnings(\"unchecked\")\npublic abstract class ListMapAdapter<K, V> extends XmlAdapter<MapItemArray, List<Map<K, V>>> {\n\n    protected final Class<K> ktype;\n    protected final Class<V> vtype;\n\n    protected ListMapAdapter() {\n        ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0);\n        vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1);\n    }\n\n    @Override\n    public List<Map<K, V>> unmarshal(MapItemArray v) {\n        if (v == null) {\n            return null;\n        } else if (v.getItems() == null) {\n            return Lists.newArrayList();\n        }\n\n        List<Map<K, V>> list = new ArrayList<>();\n        for (MapItem items : v.getItems()) {\n            if (items == null) {\n                continue;\n            }\n            Map<K, V> map = Maps.newLinkedHashMap();\n            for (MapEntry<K, V> item : items.getItem()) {\n                map.put(item.getKey(), item.getValue());\n            }\n            list.add(map);\n        }\n        return list;\n    }\n\n    @Override\n    public MapItemArray marshal(List<Map<K, V>> v) {\n        if (v == null) {\n            return null;\n        }\n\n        MapItem[] items = new MapItem[v.size()];\n        int i = 0;\n        for (Map<K, V> map : v) {\n            if (map == null) {\n                continue;\n            }\n            MapEntry<K, V>[] item = new MapEntry[map.size()];\n            int j = 0;\n            for (Entry<K, V> entry : map.entrySet()) {\n                item[j++] = new MapEntry<>(entry);\n            }\n            items[i++] = new MapItem(item);\n        }\n        return new MapItemArray(items);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ListMapNormalAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * List<Map<String,Object>转换器\n * \n * @author Ponfee\n */\npublic class ListMapNormalAdapter extends ListMapAdapter<String, Object> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/MapAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.ws.adapter.model.MapEntry;\nimport com.google.common.collect.Maps;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\nimport java.util.Map;\n\n/**\n * Map<K,V>转换器\n * @param <K>\n * @param <V>\n * \n * @author Ponfee\n */\n@SuppressWarnings({ \"unchecked\", \"rawtypes\" })\npublic abstract class MapAdapter<K, V> extends XmlAdapter<MapEntry[], Map<K, V>> {\n\n    protected final Class<K> ktype;\n    protected final Class<V> vtype;\n\n    protected MapAdapter() {\n        ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0);\n        vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1);\n    }\n\n    @Override\n    public MapEntry<K, V>[] marshal(Map<K, V> map) {\n        if (map == null) {\n            return null;\n        }\n\n        MapEntry<K, V>[] entries = new MapEntry[map.size()];\n        int i = 0;\n        for (Map.Entry<K, V> entry : map.entrySet()) {\n            entries[i++] = new MapEntry<>(entry);\n        }\n        return entries;\n    }\n\n    @Override\n    public Map<K, V> unmarshal(MapEntry[] entries) {\n        if (entries == null) {\n            return null;\n        }\n\n        Map<K, V> map = Maps.newLinkedHashMap();\n        for (MapEntry<K, V> e : entries) {\n            map.put(e.getKey(), e.getValue());\n        }\n        return map;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/MapNormalAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * Map<String,Object>转换器\n * \n * @author Ponfee\n */\npublic class MapNormalAdapter extends MapAdapter<String, Object> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/MarshalJsonAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.reflect.ClassUtils;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\n\n/**\n * MarshalJsonResult -> MarshalJsonXml\n * \n * `@XmlJavaTypeAdapter(MarshalJsonAdapter.class)\n * \n * @author Ponfee\n * \n * @param <T>\n */\npublic class MarshalJsonAdapter<T> extends XmlAdapter<MarshalJsonXml, Object> {\n\n    @Override\n    public MarshalJsonXml marshal(Object v) {\n        if (v == null) {\n            return null;\n        }\n\n        return new MarshalJsonXml(ClassUtils.getClassName(v.getClass()), Jsons.toJson(v));\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Object unmarshal(MarshalJsonXml v) throws Exception {\n        if (v == null) {\n            return null;\n        }\n\n        Class<?> type = Class.forName(v.getType());\n        if (!MarshalJsonResult.class.isAssignableFrom(type)) {\n            return Jsons.fromJson(v.getData(), type);\n        } else {\n            // must has default no args construct\n            return ((Class<MarshalJsonResult>) type).newInstance().fromJson(v.getData());\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/MarshalJsonResult.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.json.Jsons;\n\n/**\n * Market a bean type defined\n * \n * @author Ponfee\n */\npublic interface MarshalJsonResult/*<E>*/ {\n\n    default /*E*/ MarshalJsonResult fromJson(String json) {\n        return Jsons.fromJson(json, this.getClass());\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/MarshalJsonXml.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport java.io.Serializable;\n\n/**\n * Wrapped for a bean marshal to xml\n * \n * @author Ponfee\n */\npublic class MarshalJsonXml implements Serializable {\n\n    private static final long serialVersionUID = 4570006014299314019L;\n\n    private String type;\n    private String data;\n\n    public MarshalJsonXml() {}\n\n    public MarshalJsonXml(String type, String data) {\n        super();\n        this.type = type;\n        this.data = data;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getData() {\n        return data;\n    }\n\n    public void setData(String data) {\n        this.data = data;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultDataJsonAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\n\n/**\n * Result<data> -> Result<json>\n * @param <T>\n * \n * @author Ponfee\n */\npublic abstract class ResultDataJsonAdapter<T> extends XmlAdapter<Result<String>, Result<T>> {\n\n    protected final Class<T> type;\n\n    protected ResultDataJsonAdapter() {\n        type = GenericUtils.getActualTypeArgument(this.getClass());\n    }\n\n    @Override\n    public Result<T> unmarshal(Result<String> v) {\n        if (StringUtils.isEmpty(v.getData())) {\n            return v.from(null);\n        }\n\n        return v.from(Jsons.fromJson(v.getData(), type));\n    }\n\n    @Override\n    public Result<String> marshal(Result<T> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        }\n\n        return v.from(Jsons.toJson(v.getData()));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultDataJsonPageAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.model.Page;\n\n/**\n * Result<Page<?>> -> Result<json>\n * \n * @author Ponfee\n */\npublic class ResultDataJsonPageAdapter extends ResultDataJsonAdapter<Page<?>> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultListAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.ws.adapter.model.ArrayItem;\nimport com.google.common.collect.Lists;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\nimport java.lang.reflect.Array;\nimport java.util.List;\n\n/**\n * Result<List<T>>转换器\n * @param <T>\n * \n * @author Ponfee\n */\n//ParameterizedTypeImpl cannot be cast to TypeVariable\n//abstract class ResultListAdapter<T> extends XmlAdapter<Result<Item<T>[]>, Result<List<T>>> {\npublic abstract class ResultListAdapter<T> extends XmlAdapter<Result<ArrayItem<T>>, Result<List<T>>> {\n\n    protected final Class<T> type;\n\n    protected ResultListAdapter() {\n        type = GenericUtils.getActualTypeArgument(this.getClass());\n    }\n\n    @Override\n    public Result<List<T>> unmarshal(Result<ArrayItem<T>> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        } else if (v.getData().getItem() == null) {\n            return v.from(Lists.newArrayList());\n        }\n\n        List<T> list = Lists.newArrayList(v.getData().getItem());\n        return v.from(list);\n    }\n\n    @Override @SuppressWarnings(\"unchecked\")\n    public Result<ArrayItem<T>> marshal(Result<List<T>> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        }\n\n        T[] array = v.getData().toArray((T[]) Array.newInstance(type, v.getData().size()));\n        return v.from(new ArrayItem<>(array));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultListMapAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.ws.adapter.model.MapEntry;\nimport cn.ponfee.commons.ws.adapter.model.MapItem;\nimport cn.ponfee.commons.ws.adapter.model.MapItemArray;\nimport com.google.common.collect.Lists;\nimport com.google.common.collect.Maps;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\n/**\n * Result<List<Map<K,V>>转换器\n * @param <K>\n * @param <V>\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"unchecked\")\npublic abstract class ResultListMapAdapter<K, V> extends XmlAdapter<Result<MapItemArray>, Result<List<Map<K, V>>>> {\n\n    protected final Class<K> ktype;\n    protected final Class<V> vtype;\n\n    protected ResultListMapAdapter() {\n        ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0);\n        vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1);\n    }\n\n    @Override\n    public Result<List<Map<K, V>>> unmarshal(Result<MapItemArray> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        } else if (v.getData().getItems() == null) {\n            return v.from(Lists.newArrayList());\n        }\n\n        List<Map<K, V>> list = new ArrayList<>();\n        for (MapItem items : v.getData().getItems()) {\n            if (items == null) {\n                continue;\n            }\n            Map<K, V> map = Maps.newLinkedHashMap();\n            for (MapEntry<K, V> item : items.getItem()) {\n                if (item == null) {\n                    continue;\n                }\n                map.put(item.getKey(), item.getValue());\n            }\n            list.add(map);\n        }\n        return v.from(list);\n    }\n\n    @Override\n    public Result<MapItemArray> marshal(Result<List<Map<K, V>>> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        }\n\n        MapItem[] items = new MapItem[v.getData().size()];\n        int i = 0;\n        for (Map<K, V> map : v.getData()) {\n            if (map == null) {\n                continue;\n            }\n            MapEntry<K, V>[] item = new MapEntry[map.size()];\n            int j = 0;\n            for (Entry<K, V> entry : map.entrySet()) {\n                item[j++] = new MapEntry<>(entry);\n            }\n            items[i++] = new MapItem(item);\n        }\n        return v.from(new MapItemArray(items));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultListMapNormalAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * Result<List<Map<String,Object>>转换器\n * \n * @author Ponfee\n */\npublic class ResultListMapNormalAdapter extends ResultListMapAdapter<String, Object> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultListObjectAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * Result<List<Object>转换器\n * \n * @author Ponfee\n */\npublic class ResultListObjectAdapter extends ResultListAdapter<Object> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultListObjectArrayAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * Result<List<String>转换器\n * \n * @author Ponfee\n */\npublic class ResultListObjectArrayAdapter extends ResultListAdapter<Object[]> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultListStringAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * Result<List<String>转换器\n * \n * @author Ponfee\n */\npublic class ResultListStringAdapter extends ResultListAdapter<String> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultMapAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.ws.adapter.model.MapEntry;\nimport cn.ponfee.commons.ws.adapter.model.MapItem;\nimport com.google.common.collect.Maps;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\nimport java.util.Map;\n\n/**\n * Result<Map<K,V>>转换器\n * @param <K>\n * @param <V>\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"unchecked\")\npublic abstract class ResultMapAdapter<K, V> extends XmlAdapter<Result<MapItem>, Result<Map<K, V>>> {\n\n    protected final Class<K> ktype;\n    protected final Class<V> vtype;\n\n    protected ResultMapAdapter() {\n        ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0);\n        vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1);\n    }\n\n    @Override\n    public Result<Map<K, V>> unmarshal(Result<MapItem> v) {\n        if (v.getData() == null || v.getData().getItem() == null) {\n            return v.from(null);\n        }\n\n        Map<K, V> map = Maps.newLinkedHashMap();\n        for (MapEntry<K, V> e : v.getData().getItem()) {\n            map.put(e.getKey(), e.getValue());\n        }\n        return v.from(map);\n    }\n\n    @Override\n    public Result<MapItem> marshal(Result<Map<K, V>> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        }\n\n        MapEntry<K, V>[] entries = new MapEntry[v.getData().size()];\n        int i = 0;\n        for (Map.Entry<K, V> entry : v.getData().entrySet()) {\n            entries[i++] = new MapEntry<>(entry);\n        }\n        return v.from(new MapItem(entries));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultMapNormalAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * Result<Map<String, Object>>转换器\n * \n * @author Ponfee\n */\npublic class ResultMapNormalAdapter extends ResultMapAdapter<String, Object> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultPageAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.model.Page;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.ws.adapter.model.TransitPage;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\n\n/**\n * Result<Page<T>>转换器\n * @param <T>\n * \n * @see org.springframework.data.domain.jaxb.PageAdapter\n * \n * @author Ponfee\n */\npublic abstract class ResultPageAdapter<T> extends XmlAdapter<Result<TransitPage<T>>, Result<Page<T>>> {\n\n    protected final Class<T> type;\n\n    protected ResultPageAdapter() {\n        type = GenericUtils.getActualTypeArgument(this.getClass());\n    }\n\n    @Override\n    public Result<Page<T>> unmarshal(Result<TransitPage<T>> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        } else if (   v.getData().getRows() == null \n                   || v.getData().getRows().getItem() == null) {\n            return v.from(new Page<>());\n        }\n\n        return v.from(TransitPage.recover(v.getData()));\n    }\n\n    @Override\n    public Result<TransitPage<T>> marshal(Result<Page<T>> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        } else if (v.getData().getRows() == null) {\n            return v.from(new TransitPage<>());\n        }\n\n        return v.from(TransitPage.transform(v.getData(), type));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultPageMapAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.model.Page;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.ws.adapter.model.MapEntry;\nimport cn.ponfee.commons.ws.adapter.model.MapItem;\nimport org.apache.commons.collections4.CollectionUtils;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * Result<Page<Map<K, V>>>转换器\n * @param <K>\n * @param <V>\n * \n * @see org.springframework.data.domain.jaxb.PageAdapter\n * \n * @author Ponfee\n */\npublic abstract class ResultPageMapAdapter<K, V> \n    extends XmlAdapter<Result<Page<MapItem>>, Result<Page<Map<K, V>>>> {\n\n    protected final Class<K> ktype;\n    protected final Class<V> vtype;\n\n    protected ResultPageMapAdapter() {\n        ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0);\n        vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1);\n    }\n\n    @Override\n    public Result<Page<Map<K, V>>> unmarshal(Result<Page<MapItem>> v) {\n        if (v.getData() == null || CollectionUtils.isEmpty(v.getData().getRows())) {\n            return (Result<Page<Map<K, V>>>) ((Result<?>) v);\n        }\n\n        return v.from(v.getData().map(items -> {\n            if (items == null) {\n                return null;\n            }\n            return Arrays.stream(items.getItem())\n                         .collect(Collectors.toMap(MapEntry<K, V>::getKey, MapEntry<K, V>::getValue));\n        }));\n    }\n\n    @Override\n    public Result<Page<MapItem>> marshal(Result<Page<Map<K, V>>> v) {\n        if (v.getData() == null || CollectionUtils.isEmpty(v.getData().getRows())) {\n            return (Result<Page<MapItem>>) ((Result<?>) v);\n        }\n\n        return v.from(v.getData().map(e -> {\n            if (e == null) {\n                return null;\n            }\n            return new MapItem(e.entrySet().stream().map(MapEntry::new).toArray(MapEntry[]::new));\n        }));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultPageMapAdapter.java.bak",
    "content": "package cn.ponfee.commons.ws.adapter;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\n\nimport com.google.common.collect.Lists;\nimport com.google.common.collect.Maps;\n\nimport cn.ponfee.commons.model.Page;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.ws.adapter.model.MapEntry;\nimport cn.ponfee.commons.ws.adapter.model.MapItem;\nimport cn.ponfee.commons.ws.adapter.model.TransitPage;\n\n/**\n * Result<Page<Map<K, V>>>转换器\n * @param <K>\n * @param <V>\n * \n * @see org.springframework.data.domain.jaxb.PageAdapter\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"unchecked\")\npublic abstract class ResultPageMapAdapter<K, V> \n    extends XmlAdapter<Result<TransitPage<MapItem>>, Result<Page<Map<K, V>>>> {\n\n    protected final Class<K> ktype;\n    protected final Class<V> vtype;\n\n    protected ResultPageMapAdapter() {\n        ktype = GenericUtils.getActualTypeArgument(this.getClass(), 0);\n        vtype = GenericUtils.getActualTypeArgument(this.getClass(), 1);\n    }\n\n    @Override\n    public Result<Page<Map<K, V>>> unmarshal(Result<TransitPage<MapItem>> v) {\n        if (v.getData() == null) {\n            return v.copy();\n        } else if (v.getData().getRows() == null \n                || v.getData().getRows().getItem() == null) {\n            return v.copy(new Page<>());\n        }\n\n        return v.copy(TransitPage.recover(v.getData()).transform(items -> {\n            if (items == null) {\n                return null;\n            }\n            Map<K, V> map = Maps.newLinkedHashMap();\n            for (MapEntry<K, V> item : items.getItem()) {\n                map.put(item.getKey(), item.getValue());\n            }\n            return map;\n        }));\n    }\n\n    @Override\n    public Result<TransitPage<MapItem>> marshal(Result<Page<Map<K, V>>> v) {\n        if (v.getData() == null || v.getData().getRows() == null) {\n            return v.copy(null);\n        }\n\n        List<MapItem> list = Lists.newArrayList();\n        Page<Map<K, V>> page = v.getData();\n        for (Map<K, V> map : page.getRows()) {\n            if (map == null) {\n                continue;\n            }\n            MapEntry<K, V>[] item = new MapEntry[map.size()];\n            int index = 0;\n            for (Map.Entry<K, V> entry : map.entrySet()) {\n                item[index++] = new MapEntry<>(entry);\n            }\n            list.add(new MapItem(item));\n        }\n\n        return v.copy(TransitPage.transform(page, list.toArray(new MapItem[list.size()])));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultPageMapNormalAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * Result<Page<Map<String, Object>>>转换器\n * \n * @author Ponfee\n */\npublic class ResultPageMapNormalAdapter extends ResultPageMapAdapter<String, Object> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultPageObjectAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * ResultPageAdapter<Object>转换器\n * \n * @author Ponfee\n */\npublic class ResultPageObjectAdapter extends ResultPageAdapter<Object> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultPageObjectArrayAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * ResultPageAdapter<Object[]>转换器\n * \n * @author Ponfee\n */\npublic class ResultPageObjectArrayAdapter extends ResultPageAdapter<Object[]> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultSetAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.ws.adapter.model.ArrayItem;\nimport com.google.common.collect.Sets;\n\nimport javax.xml.bind.annotation.adapters.XmlAdapter;\nimport java.lang.reflect.Array;\nimport java.util.Set;\n\n/**\n * Result<Set<T>>转换器\n * @param <T>\n * \n * @author Ponfee\n */\npublic abstract class ResultSetAdapter<T> extends XmlAdapter<Result<ArrayItem<T>>, Result<Set<T>>> {\n\n    protected final Class<T> type;\n\n    protected ResultSetAdapter() {\n        type = GenericUtils.getActualTypeArgument(this.getClass());\n    }\n\n    @Override\n    public Result<Set<T>> unmarshal(Result<ArrayItem<T>> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        } else if (v.getData().getItem() == null) {\n            return v.from(Sets.newHashSet());\n        }\n\n        Set<T> set = Sets.newHashSet(v.getData().getItem());\n        return v.from(set);\n    }\n\n    @Override @SuppressWarnings(\"unchecked\")\n    public Result<ArrayItem<T>> marshal(Result<Set<T>> v) {\n        if (v.getData() == null) {\n            return v.from(null);\n        }\n\n        T[] array = v.getData().toArray((T[]) Array.newInstance(type, v.getData().size()));\n        return v.from(new ArrayItem<>(array));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/ResultSetStringAdapter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter;\n\n/**\n * Result<Set<String>转换器\n * \n * @author Ponfee\n */\npublic class ResultSetStringAdapter extends ResultSetAdapter<String> {\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/model/ArrayItem.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter.model;\n\n/**\n * 封装数组对象\n * \n * @author Ponfee\n * @param <T>\n */\npublic class ArrayItem<T> {\n    private T[] item;\n\n    public ArrayItem() {}\n\n    public ArrayItem(T[] item) {\n        this.item = item;\n    }\n\n    public T[] getItem() {\n        return item;\n    }\n\n    public void setItem(T[] item) {\n        this.item = item;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/model/MapEntry.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter.model;\n\nimport java.util.Map;\n\n/**\n * 对应Map.Entry数据\n * \n * @author Ponfee\n * @param <K>\n * @param <V>\n */\npublic class MapEntry<K, V> {\n    private K key;\n    private V value;\n\n    public MapEntry() {\n        this(null, null);\n    }\n\n    public MapEntry(Map.Entry<K, V> entry) {\n        this(entry.getKey(), entry.getValue());\n    }\n\n    public MapEntry(K key, V value) {\n        super();\n        this.key = key;\n        this.value = value;\n    }\n\n    public K getKey() {\n        return key;\n    }\n\n    public void setKey(K key) {\n        this.key = key;\n    }\n\n    public V getValue() {\n        return value;\n    }\n\n    public void setValue(V value) {\n        this.value = value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/model/MapItem.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter.model;\n\n/**\n * cannot with generic like as <code>MapItem<K,V></code>\n * ParameterizedTypeImpl cannot be cast to TypeVariable\n * \n * @author Ponfee\n */\n@SuppressWarnings(\"rawtypes\")\npublic class MapItem {\n    private MapEntry[] item;\n\n    public MapItem() {}\n\n    public MapItem(MapEntry[] item) {\n        this.item = item;\n    }\n\n    public MapEntry[] getItem() {\n        return item;\n    }\n\n    public void setItem(MapEntry[] item) {\n        this.item = item;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/model/MapItemArray.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter.model;\n\n/**\n * 封装MapItem数组\n * \n * @author Ponfee\n */\npublic class MapItemArray {\n    private MapItem[] items;\n\n    public MapItemArray() {}\n\n    public MapItemArray(MapItem[] items) {\n        this.items = items;\n    }\n\n    public MapItem[] getItems() {\n        return items;\n    }\n\n    public void setItems(MapItem[] items) {\n        this.items = items;\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/model/TransitPage.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.ws.adapter.model;\n\nimport cn.ponfee.commons.model.Page;\nimport com.google.common.collect.Lists;\n\nimport java.lang.reflect.Array;\nimport java.util.List;\n\n/**\n * Page转换\n * \n * @author Ponfee\n * @param <T>\n */\npublic class TransitPage<T> {\n\n    private ArrayItem<T> rows;\n    private int pageNum; // 当前页\n    private int pageSize; // 每页的数量\n    private int size; // 当前页的数量\n    private long startRow; // 当前页面第一个元素在数据库中的行号\n    private long endRow; // 当前页面最后一个元素在数据库中的行号\n    private long total; // 总记录数\n    private int pages; // 总页数\n    private int prePage; // 前一页\n    private int nextPage; // 下一页\n    private boolean isFirstPage = false; // 是否为第一页\n    private boolean isLastPage = false; // 是否为最后一页\n    private boolean hasPreviousPage = false; // 是否有前一页\n    private boolean hasNextPage = false; // 是否有下一页\n    private int navigatePages; // 导航页码数\n    private int[] navigatePageNums; // 所有导航页号\n    private int navigateFirstPage; // 导航条上的第一页\n    private int navigateLastPage; // 导航条上的最后一页\n\n    public ArrayItem<T> getRows() {\n        return rows;\n    }\n\n    public void setRows(ArrayItem<T> rows) {\n        this.rows = rows;\n    }\n\n    public int getPageNum() {\n        return pageNum;\n    }\n\n    public void setPageNum(int pageNum) {\n        this.pageNum = pageNum;\n    }\n\n    public int getPageSize() {\n        return pageSize;\n    }\n\n    public void setPageSize(int pageSize) {\n        this.pageSize = pageSize;\n    }\n\n    public int getSize() {\n        return size;\n    }\n\n    public void setSize(int size) {\n        this.size = size;\n    }\n\n    public long getStartRow() {\n        return startRow;\n    }\n\n    public void setStartRow(long startRow) {\n        this.startRow = startRow;\n    }\n\n    public long getEndRow() {\n        return endRow;\n    }\n\n    public void setEndRow(long endRow) {\n        this.endRow = endRow;\n    }\n\n    public long getTotal() {\n        return total;\n    }\n\n    public void setTotal(long total) {\n        this.total = total;\n    }\n\n    public int getPages() {\n        return pages;\n    }\n\n    public void setPages(int pages) {\n        this.pages = pages;\n    }\n\n    public int getPrePage() {\n        return prePage;\n    }\n\n    public void setPrePage(int prePage) {\n        this.prePage = prePage;\n    }\n\n    public int getNextPage() {\n        return nextPage;\n    }\n\n    public void setNextPage(int nextPage) {\n        this.nextPage = nextPage;\n    }\n\n    public boolean isFirstPage() {\n        return isFirstPage;\n    }\n\n    public void setFirstPage(boolean isFirstPage) {\n        this.isFirstPage = isFirstPage;\n    }\n\n    public boolean isLastPage() {\n        return isLastPage;\n    }\n\n    public void setLastPage(boolean isLastPage) {\n        this.isLastPage = isLastPage;\n    }\n\n    public boolean isHasPreviousPage() {\n        return hasPreviousPage;\n    }\n\n    public void setHasPreviousPage(boolean hasPreviousPage) {\n        this.hasPreviousPage = hasPreviousPage;\n    }\n\n    public boolean isHasNextPage() {\n        return hasNextPage;\n    }\n\n    public void setHasNextPage(boolean hasNextPage) {\n        this.hasNextPage = hasNextPage;\n    }\n\n    public int getNavigatePages() {\n        return navigatePages;\n    }\n\n    public void setNavigatePages(int navigatePages) {\n        this.navigatePages = navigatePages;\n    }\n\n    public int[] getNavigatePageNums() {\n        return navigatePageNums;\n    }\n\n    public void setNavigatePageNums(int[] navigatePageNums) {\n        this.navigatePageNums = navigatePageNums;\n    }\n\n    public int getNavigateFirstPage() {\n        return navigateFirstPage;\n    }\n\n    public void setNavigateFirstPage(int navigateFirstPage) {\n        this.navigateFirstPage = navigateFirstPage;\n    }\n\n    public int getNavigateLastPage() {\n        return navigateLastPage;\n    }\n\n    public void setNavigateLastPage(int navigateLastPage) {\n        this.navigateLastPage = navigateLastPage;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> TransitPage<T> transform(Page<T> page, Class<?> type) {\n        T[] array = (T[]) Array.newInstance(type, page.getRows().size());\n        return transform(page, page.getRows().toArray(array));\n    }\n\n    public static <T> TransitPage<T> transform(Page<?> page, T[] t) {\n        TransitPage<T> transit = new TransitPage<>();\n        transit.setRows(new ArrayItem<>(t));\n        copy(transit, page);\n        return transit;\n    }\n\n    public static <T> Page<T> recover(TransitPage<T> transit) {\n        Page<T> page = new Page<>();\n        List<T> list = Lists.newArrayList(transit.getRows().getItem());\n        page.setRows(list);\n        page.setPageNum(transit.getPageNum());\n        page.setPageSize(transit.getPageSize());\n        page.setSize(transit.getSize());\n        page.setStartRow(transit.getStartRow());\n        page.setEndRow(transit.getEndRow());\n        page.setTotal(transit.getTotal());\n        page.setPages(transit.getPages());\n        page.setPrePage(transit.getPrePage());\n        page.setNextPage(transit.getNextPage());\n        page.setFirstPage(transit.isFirstPage());\n        page.setLastPage(transit.isLastPage());\n        page.setHasPreviousPage(transit.isHasPreviousPage());\n        page.setHasNextPage(transit.isHasNextPage());\n        page.setNavigatePages(transit.getNavigatePages());\n        page.setNavigatePageNums(transit.getNavigatePageNums());\n        page.setNavigateFirstPage(transit.getNavigateFirstPage());\n        page.setNavigateLastPage(transit.getNavigateLastPage());\n        return page;\n    }\n\n    private static void copy(TransitPage<?> transit, Page<?> page) {\n        transit.setPageNum(page.getPageNum());\n        transit.setPageSize(page.getPageSize());\n        transit.setSize(page.getSize());\n        transit.setStartRow(page.getStartRow());\n        transit.setEndRow(page.getEndRow());\n        transit.setTotal(page.getTotal());\n        transit.setPages(page.getPages());\n        transit.setPrePage(page.getPrePage());\n        transit.setNextPage(page.getNextPage());\n        transit.setFirstPage(page.getFirstPage());\n        transit.setLastPage(page.getLastPage());\n        transit.setHasPreviousPage(page.getHasPreviousPage());\n        transit.setHasNextPage(page.getHasNextPage());\n        transit.setNavigatePages(page.getNavigatePages());\n        transit.setNavigatePageNums(page.getNavigatePageNums());\n        transit.setNavigateFirstPage(page.getNavigateFirstPage());\n        transit.setNavigateLastPage(page.getNavigateLastPage());\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/model/package-info.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n/**\n * model类\n *\n * @author Ponfee\n */\npackage cn.ponfee.commons.ws.adapter.model;\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/ws/adapter/package-info.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n/**\n * <pre>\n * 解决WebService输入与输出数据无法转换问题\n *  1.当形参或返回值是String、基本数据类型时，CXF可以处理\n *  2.当形参或返回值是JavaBean式的复合类型、List集合、数组时，CXF可以处理\n *  3.当形参或返回值是一些如Map、非Javabean等复合类型时，CXF无法处理\n *\n *  若还无法转换，可在接口类（interface）上加注解：@XmlSeeAlso({ String[].class, Object[].class, Object[][].class, SomeBean[].class })\n *  \n *  `@XmlJavaTypeAdapter(MarshalJsonAdapter.class)\n *  \n * </pre>\n * \n * @author Ponfee\n */\npackage cn.ponfee.commons.ws.adapter;\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/xml/SimpleXmlHandler.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.xml;\n\nimport org.dom4j.Attribute;\nimport org.dom4j.DocumentException;\nimport org.dom4j.Element;\nimport org.dom4j.io.SAXReader;\nimport org.dom4j.io.SAXValidator;\nimport org.dom4j.util.XMLErrorHandler;\nimport org.xml.sax.SAXException;\n\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParser;\nimport javax.xml.parsers.SAXParserFactory;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.*;\n\n/**\n * xml工具类\n * \n * @author Ponfee\n */\npublic class SimpleXmlHandler {\n\n    private static final int MAX_ERROR_SIZE = 500;\n    /**\n     * <pre>\n     * 待解析XML文件 格式必须符合如下规范：\n     *   1.最多三级，每级的node名称自定义，一级节点为根节点，不能包含属性，如：encryptors； \n     *   2.二级节点支持节点属性，属性将被视作子节点，二级节点如：encryptor；\n     *   3.三级节点不能包含属性，三级节点如：encryptorId；\n     *   4.CDATA必须包含在节点中，不能单独出现；\n     *\n     *  <span>xml文件：</span>\n     *  &lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n     *  &lt;encryptors xmlns=\"http://cn.ponfee/encryptor\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n     *              xsi:schemaLocation=\"http://cn.ponfee/encryptor encryptor.xsd\"&gt;\n     *    &lt;encryptor&gt;\n     *      &lt;!-- require --&gt;\n     *      &lt;encryptorId&gt;1&lt;/encryptorId&gt;\n     *      &lt;!--元素：require；规则：以（classpath:或classpath*:或file:或context:开头，默认以classpath开头 ）--&gt;\n     *      &lt;keyStore&gt;&lt;![CDATA[classpath:META-INF/encrypt/encryptors/1.pfx]]&gt;&lt;/keyStore&gt;\n     *      &lt;!-- require --&gt;\n     *      &lt;storePass&gt;1234&lt;/storePass&gt;\n     *      &lt;!-- require --&gt;\n     *      &lt;keyPass&gt;1234&lt;/keyPass&gt;\n     *      &lt;!-- optional[可选]，值：[pfx|jks] --&gt;\n     *      &lt;storeType&gt;pfx&lt;/storeType&gt;\n     *      &lt;!-- implied[可选]，不填默认选第1个密钥对 --&gt;\n     *      &lt;alias&gt;45e4ea70a3589e96cd670c8e5c8c7be5_28766470-a40c-4c9e-b312-d7a5618db23b&lt;/alias&gt;\n     *    &lt;/encryptor&gt;\n     *    ...\n     *    &lt;encryptor&gt;...&lt;/encryptor&gt;\n     *  &lt;/encryptors&gt;\n     *\n     *  <span>xsd文件：</span>\n     *  &lt;?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?&gt;\n     *  &lt;schema xmlns=\"http://www.w3.org/2001/XMLSchema\"\n     *          xmlns:tns=\"http://cn.ponfee/encryptor\"\n     *          attributeFormDefault=\"unqualified\"\n     *          elementFormDefault=\"qualified\"\n     *          targetNamespace=\"http://cn.ponfee/encryptor\"&gt;\n     *    &lt;element name=\"encryptors\"&gt;\n     *      &lt;complexType&gt;\n     *        &lt;sequence&gt;\n     *          &lt;element maxOccurs=\"unbounded\" name=\"encryptor\" type=\"tns:encryptorType\"/&gt;\n     *        &lt;/sequence&gt;\n     *      &lt;/complexType&gt;\n     *    &lt;/element&gt;\n     *    &lt;complexType name=\"encryptorType\"&gt;\n     *      &lt;sequence&gt;\n     *        &lt;element name=\"encryptorId\" type=\"string\"/&gt;\n     *        &lt;element name=\"keyStore\" type=\"tns:resourceType\"/&gt;\n     *        &lt;element name=\"storePass\" type=\"string\"/&gt;\n     *        &lt;element name=\"keyPass\" type=\"string\"/&gt;\n     *        &lt;element minOccurs=\"0\" name=\"storeType\" type=\"tns:storeTypeType\"/&gt;\n     *        &lt;element minOccurs=\"0\" name=\"alias\" type=\"string\"/&gt;\n     *      &lt;/sequence&gt;\n     *    &lt;/complexType&gt;\n     *    &lt;simpleType name=\"storeTypeType\"&gt;\n     *      &lt;restriction base=\"string\"&gt;\n     *        &lt;enumeration value=\"jks\"/&gt;\n     *        &lt;enumeration value=\"pfx\"/&gt;\n     *      &lt;/restriction&gt;\n     *    &lt;/simpleType&gt;\n     *    &lt;simpleType name=\"resourceType\"&gt;\n     *      &lt;restriction base=\"string\"&gt;\n     *        &lt;pattern value=\"(classpath:|classpath\\*:|file:(([c-zC-Z]:)(/|\\\\\\\\)){0,1}|context:){0,1}[^:\\?\\|\\*]*\" /&gt;\n     *      &lt;/restriction&gt;\n     *    &lt;/simpleType&gt;\n     *  &lt;/schema&gt;\n     *\n     * </pre>\n     */\n    public static List<Map<String, String>> parse(InputStream input) {\n        try (InputStream xml = input) {\n            List<Map<String, String>> results = new LinkedList<>();\n            Element root = new SAXReader().read(xml).getRootElement();\n            for (Iterator<Element> seconds = root.elementIterator(); seconds.hasNext(); ) {\n                Element second = seconds.next();\n                Map<String, String> element = new HashMap<>();\n                for (Attribute attr : second.attributes()) {\n                    element.put(attr.getName(), attr.getValue()); // 添加二级节点属性\n                }\n                for (Iterator<Element> thirds = second.elementIterator(); thirds.hasNext(); ) {\n                    Element third = thirds.next(); // 添加三级节点\n                    element.put(third.getName().trim(), third.getText().trim());\n                }\n                results.add(element);\n            }\n            return results;\n        } catch (DocumentException e) {\n            throw new IllegalArgumentException(\"Invalid xml data.\", e);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static List<Map<String, String>> parse(String xml) {\n        return parse(new ByteArrayInputStream(xml.getBytes()));\n    }\n\n    /**\n     * 通过Schema验证xml文件\n     */\n    public static void validate(InputStream xmlIn, InputStream xsdIn) {\n        try (InputStream xml = xmlIn; InputStream xsd = xsdIn) {\n            SAXParserFactory factory = SAXParserFactory.newInstance();\n            factory.setValidating(true);\n            factory.setNamespaceAware(true);\n            SAXParser parser = factory.newSAXParser();\n            parser.setProperty(JAXPConstants.JAXP_SCHEMA_LANGUAGE, JAXPConstants.W3C_XML_SCHEMA);\n            //parser.setProperty(JAXPConstants.JAXP_SCHEMA_SOURCE, \"file:\" + xsdPath);\n            parser.setProperty(JAXPConstants.JAXP_SCHEMA_SOURCE, xsd);\n            if (!parser.isValidating()) {\n                throw new IllegalStateException(\"Invalid xsd definition.\");\n            }\n\n            XMLErrorHandler errorHandler = new XMLErrorHandler();\n            SAXValidator validator = new SAXValidator(parser.getXMLReader());\n            validator.setErrorHandler(errorHandler);\n            validator.validate(new SAXReader().read(xml)); // 校验\n\n            if (errorHandler.getErrors().hasContent()) { // 校验失败\n                // 校验失败则打印错误信息\n                StringBuilder errors = new StringBuilder(128);\n                Set<String> exists = new HashSet<>();\n                for (Element e : errorHandler.getErrors().elements()) {\n                    String position = e.attributeValue(\"line\") + \"#\" + e.attributeValue(\"column\");\n                    if (!exists.add(position)) {\n                        continue;\n                    }\n                    errors.append(position).append(\":\").append(e.getTextTrim()).append(\"\\n\");\n                    if (errors.length() > MAX_ERROR_SIZE) {\n                        break; // break output error\n                    }\n                }\n\n                if (errors.length() > MAX_ERROR_SIZE) {\n                    errors.setLength(MAX_ERROR_SIZE - 3);\n                    errors.append(\"...\");\n                }\n                throw new IllegalStateException(errors.toString());\n            }\n        } catch (ParserConfigurationException | SAXException | DocumentException e) {\n            throw new IllegalStateException(\"Invalid xml data.\", e);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static void validate(String xml, String xsd) {\n        validate(new ByteArrayInputStream(xml.getBytes()), new ByteArrayInputStream(xsd.getBytes()));\n    }\n\n    private static final class JAXPConstants {\n        static final String JAXP_SCHEMA_LANGUAGE = \"http://java.sun.com/xml/jaxp/properties/schemaLanguage\";\n        static final String W3C_XML_SCHEMA = \"http://www.w3.org/2001/XMLSchema\";\n        static final String JAXP_SCHEMA_SOURCE = \"http://java.sun.com/xml/jaxp/properties/schemaSource\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/xml/XmlException.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.xml;\n\n/**\n * xml文件异常\n * \n * @author Ponfee\n */\npublic class XmlException extends RuntimeException {\n    private static final long serialVersionUID = 1112070147872432069L;\n\n    public XmlException() {\n        super();\n    }\n\n    public XmlException(String message) {\n        super(message);\n    }\n\n    public XmlException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public XmlException(Throwable cause) {\n        super(cause);\n    }\n\n    protected XmlException(String message, Throwable cause, \n                           boolean enableSuppression, boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/xml/XmlMap.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.xml;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * xml和map相互转换工具\n * \n * @author Ponfee\n */\npublic final class XmlMap extends LinkedHashMap<String, String> {\n\n    private static final long serialVersionUID = 2775335692799838871L;\n\n    private String root;\n\n    public XmlMap(Map<String, String> map) {\n        this(map, \"xml\");\n    }\n\n    public XmlMap(Map<String, String> map, String root) {\n        this.root = root;\n        super.putAll(map);\n    }\n\n    public XmlMap(String xml) {\n        Map<String, String> map;\n        if (StringUtils.isEmpty(xml)) {\n            map = Collections.emptyMap();\n        } else {\n            map = read(XmlReader.create(xml));\n        }\n        super.putAll(map);\n    }\n\n    public XmlMap(XmlReader reader) {\n        super.putAll(read(reader));\n    }\n\n    /**\n     * 返回Map\n     * @return\n     */\n    public Map<String, String> toMap() {\n        return this;\n    }\n\n    /**\n     * 返回Xml\n     * @return\n     */\n    public String toXml() {\n        XmlWriter writers = XmlWriter.create();\n        for (Map.Entry<String, String> param : this.entrySet()) {\n            if (!StringUtils.isEmpty(param.getValue())) {\n                writers.element(param.getKey(), param.getValue());\n            }\n        }\n        return writers.build(this.root);\n    }\n\n    /**\n     * XML为Map(仅支持2级)\n     * @param reader xmlReader\n     * @return Map对象\n     */\n    private Map<String, String> read(XmlReader reader) {\n        this.root = reader.getRoot();\n        Node rootNode = reader.getNode(this.root);\n        NodeList children;\n        if (rootNode == null || (children = rootNode.getChildNodes()).getLength() == 0) {\n            return Collections.emptyMap();\n        }\n\n        Map<String, String> data = new HashMap<>(children.getLength());\n        Node n;\n        for (int i = 0; i < children.getLength(); i++) {\n            n = children.item(i);\n            if (Node.TEXT_NODE != n.getNodeType()) {\n                data.put(n.getNodeName(), n.getTextContent());\n            }\n        }\n        return data;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/xml/XmlReader.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.xml;\n\nimport cn.ponfee.commons.io.Closeables;\nimport org.apache.commons.lang3.StringUtils;\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\n\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.xpath.XPath;\nimport javax.xml.xpath.XPathExpressionException;\nimport javax.xml.xpath.XPathFactory;\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\n\n/**\n * xml读取\n * \n * @author Ponfee\n */\npublic final class XmlReader {\n\n    private static final DocumentBuilderFactory FACTORY = DocumentBuilderFactory.newInstance();\n    static {\n        FACTORY.setExpandEntityReferences(false); // XXE漏洞\n    }\n\n    private Document document;\n    private String root;\n    private XPath xpath;\n\n    private XmlReader() {}\n\n    public static XmlReader create(String xml) {\n        if (StringUtils.isEmpty(xml)) {\n            throw new IllegalArgumentException(\"xml can't be empty.\");\n        }\n        //xml = xml.replaceAll(\"(\\\\r|\\\\n)\", \"\");\n        return create(new ByteArrayInputStream(xml.getBytes()));\n    }\n\n    public static XmlReader create(InputStream inputStream) {\n        try {\n            XmlReader readers = new XmlReader();\n            readers.document = FACTORY.newDocumentBuilder().parse(inputStream);\n            readers.root = readers.document.getFirstChild().getNodeName();\n            readers.xpath = XPathFactory.newInstance().newXPath();\n            return readers;\n        } catch (Exception e) {\n            throw new XmlException(\"Xmls create fail\", e);\n        } finally {\n            Closeables.console(inputStream);\n        }\n    }\n\n    /**\n     * 获取根节点名称\n     * @return\n     */\n    public String getRoot() {\n        return this.root;\n    }\n\n    /**\n     * 通过xpath取值\n     * @param xpathExp 表达式\n     * @return\n     */\n    public String evaluate(String xpathExp) {\n        try {\n            return this.xpath.evaluate(xpathExp, document);\n        } catch (XPathExpressionException e) {\n            throw new RuntimeException(\"xpath evaluate error\", e);\n        }\n    }\n\n    /**\n     * 获取节点\n     * @param tagName\n     * @return\n     */\n    public Node getNode(String tagName) {\n        NodeList nodes = document.getElementsByTagName(tagName);\n        if (nodes.getLength() <= 0) {\n            return null;\n        } else {\n            return nodes.item(0);\n        }\n    }\n\n    /**\n     * 获取节点列表\n     * @param tagName\n     * @return\n     */\n    public NodeList getNodes(String tagName) {\n        NodeList nodes = document.getElementsByTagName(tagName);\n        if (nodes.getLength() <= 0) {\n            return null;\n        } else {\n            return nodes;\n        }\n    }\n\n    /**\n     * 获取某个节点的文本内容，若有多个该节点，只会返回第一个\n     * @param tagName 标签名\n     * @return 文本内容，或NULL\n     */\n    public String getNodeText(String tagName) {\n        Node node = getNode(tagName);\n        return node == null ? null : node.getTextContent();\n    }\n\n    /**\n     * 获取某个节点的Integer，若有多个该节点，只会返回第一个\n     * @param tagName 标签名\n     * @return Integer值，或NULL\n     */\n    public Integer getNodeInt(String tagName) {\n        String nodeContent = getNodeText(tagName);\n        return nodeContent == null ? null : Integer.valueOf(nodeContent);\n    }\n\n    /**\n     * 获取某个节点的Long值，若有多个该节点，只会返回第一个\n     * @param tagName 标签名\n     * @return Long值，或NULL\n     */\n    public Long getNodeLong(String tagName) {\n        String nodeContent = getNodeText(tagName);\n        return nodeContent == null ? null : Long.valueOf(nodeContent);\n    }\n\n    /**\n     * 获取某个节点的Float，若有多个该节点，只会返回第一个\n     * @param tagName 标签名\n     * @return Float值，或NULL\n     */\n    public Float getNodeFloat(String tagName) {\n        String nodeContent = getNodeText(tagName);\n        return nodeContent == null ? null : Float.valueOf(nodeContent);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/cn/ponfee/commons/xml/XmlWriter.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.xml;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * xml构建\n * \n * @author Ponfee\n */\npublic final class XmlWriter {\n    private static final String XML_DECLARATION = \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\";\n    private final List<E<?>> elements = new ArrayList<>();\n\n    private XmlWriter() {}\n\n    public static XmlWriter create() {\n        return new XmlWriter();\n    }\n\n    public XmlWriter element(String name, String text) {\n        elements.add(new TextE(name, text));\n        return this;\n    }\n\n    public XmlWriter element(String name, Number number) {\n        elements.add(new NumberE(name, number));\n        return this;\n    }\n\n    public XmlWriter element(String parentName, String childName, String childText) {\n        return element(parentName, new TextE(childName, childText));\n    }\n\n    public XmlWriter element(String parentName, String childName, Number childNumber) {\n        return element(parentName, new NumberE(childName, childNumber));\n    }\n\n    /**\n     * 构建包含多个子元素的元素\n     * @param parentName 父元素名\n     * @param childPairs\n     * @return this\n     */\n    public XmlWriter element(String parentName, Object... childPairs) {\n        return element(parentName, newElement(childPairs));\n    }\n\n    public XmlWriter element(String parentName, E<?> child) {\n        return element(parentName, Collections.singletonList(child));\n    }\n\n    public XmlWriter element(String parentName, List<E<?>> children) {\n        elements.add(new NodeE(parentName, children));\n        return this;\n    }\n\n    public String build() {\n        return build(\"xml\");\n    }\n\n    public String build(String root) {\n        StringBuilder xml = new StringBuilder(XML_DECLARATION)\n                         .append(\"<\").append(root).append(\">\");\n        for (E<?> e : elements) {\n            xml.append(e.render());\n        }\n        return xml.append(\"</\").append(root).append(\">\").toString();\n    }\n\n    /**\n     * 创建多个元素的节点列表\n     * @param childPairs childName1, childValue1, childName2, childValu2, ...，长度必须为2的倍数\n     * @return\n     */\n    public static List<E<?>> newElement(Object... childPairs) {\n        if ((childPairs.length & 0x01) == 1) {\n            throw new XmlException(\"args Object array must be pair\");\n        }\n\n        List<E<?>> nodes = new ArrayList<>();\n        for (int i = 0; i < childPairs.length; i = i + 2) {\n            nodes.add(newElement((String) childPairs[i], childPairs[i + 1]));\n        }\n        return nodes;\n    }\n\n    /**\n     * 创建元素\n     * @param name  元素名\n     * @param value 元素值\n     * @return\n     */\n    public static E<?> newElement(String name, Object value) {\n        if (value instanceof Number) {\n            return new NumberE(name, (Number) value);\n        } else if (value instanceof E<?>) {\n            return new NodeE(name, Collections.singletonList((NodeE) value));\n        } else {\n            return new TextE(name, Objects.toString(value, null));\n        }\n    }\n\n    /**\n     * 元素抽象类\n     * @param <T>\n     */\n    public static abstract class E<T> {\n        protected final String name;\n        protected final T value;\n\n        public E(String name, T value) {\n            if (name == null) {\n                throw new IllegalArgumentException(\"element name cannot be null.\");\n            }\n            this.name = name;\n            this.value = value;\n        }\n\n        private String render() {\n            StringBuilder content = new StringBuilder(\"<\").append(name).append(\">\");\n            if (value != null) {\n                content.append(value());\n            }\n            return content.append(\"</\").append(name).append(\">\").toString();\n        }\n\n        protected abstract String value();\n    }\n\n    /**\n     * 文本元素类\n     */\n    public static class TextE extends E<String> {\n        public TextE(String name, String content) {\n            super(name, content);\n        }\n\n        @Override\n        protected String value() {\n            return new StringBuilder(\"<![CDATA[\").append(value).append(\"]]>\").toString();\n        }\n    }\n\n    /**\n     * 数值元素类\n     */\n    public static class NumberE extends E<Number> {\n        public NumberE(String name, Number value) {\n            super(name, value);\n        }\n\n        @Override\n        protected String value() {\n            return value.toString();\n        }\n    }\n\n    /**\n     * 节点元素类\n     */\n    public static class NodeE extends E<List<E<?>>> {\n        public NodeE(String name, List<E<?>> nodes) {\n            super(name, nodes);\n        }\n\n        @Override\n        protected String value() {\n            StringBuilder content = new StringBuilder();\n            for (E<?> e : value) {\n                if (e != null) {\n                    content.append(e.render());\n                }\n            }\n            return content.toString();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/resources/log4j2.xml.template",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--日志级别以及优先级排序： OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->\n<!-- Log4j 2.x 配置文件：每30秒自动检查和应用配置文件的更新；log4j-core-2.11.0.jar!/Log4j-config.xsd -->\n<Configuration status=\"WARN\" monitorInterval=\"30\" strict=\"true\" name=\"commons-core-log\">\n\n  <properties>\n    <!-- 系统属性变量：System.getProperty(\"log.home\")，${variable_name:-default_value} -->\n    <property name=\"LOG_HOME\">${log.home:-../logs}</property>\n    <!-- <property name=\"LOG_HOME\">${log.home}</property> 可由maven配置变量 -->\n    <property name=\"LOG_FILE_NAME\">commons-core</property>\n  </properties>\n\n  <Appenders>\n    <!-- 输出到控制台 -->\n    <Console name=\"Console\" target=\"SYSTEM_OUT\">\n      <PatternLayout pattern=\"%-4r %-5p [%t] %37c %3x - %m%n\" />\n    </Console>\n\n    <!-- 输出到文件，按天或者超过100MB分割 -->\n    <RollingFile name=\"RollingFile\" fileName=\"${LOG_HOME}/${LOG_FILE_NAME}.log\"\n      filePattern=\"${LOG_HOME}/$${date:yyyy-MM}/${LOG_FILE_NAME}-%d{yyyy-MM-dd}-%i.log.gz\">\n\n      <!--控制台只输出level及以上级别的信息（onMatch），其他的直接拒绝（onMismatch）-->\n      <!-- <ThresholdFilter level=\"info\" onMatch=\"ACCEPT\" onMismatch=\"DENY\" /> -->\n\n      <PatternLayout pattern=\"[%-5p] %d{yyyy-MM-dd HH:mm:ss} %l%n%m%n\" />\n      <!-- <PatternLayout pattern=\"###|||%d{yyyy-MM-dd HH:mm:ss.SSS}|||%level|||%X{TRACE_ID:--}|||%thread|||%logger{0}--->%msg%n\" /> -->\n      <Policies>\n        <!-- 启动时就重新滚动日志 -->\n        <!-- <OnStartupTriggeringPolicy /> -->\n\n        <!-- 如果启用此配置，则日志会按文件名生成新压缩文件，即如果filePattern配置的日期格式为 %d{yyyy-MM-dd HH}，\n        `则每小时生成一个压缩文件，如果filePattern配置的日期格式为 %d{yyyy-MM-dd} ，则天生成一个压缩文件 -->\n        <!-- modulate=true用来调整时间，interval属性用来指定多久滚动一次，默认是1 hour，\n        `比如现在是早上3am，interval=4，那么第一次滚动是在4am，接着是8am，而不是7am -->\n        <!-- <TimeBasedTriggeringPolicy interval=\"1\" modulate=\"true\" /> -->\n        <TimeBasedTriggeringPolicy />\n\n        <SizeBasedTriggeringPolicy size=\"100 MB\" />\n      </Policies>\n      <!-- DefaultRolloverStrategy属性如不设置，则默认为最多同一文件夹下7个文件，这里设置了100 -->\n      <DefaultRolloverStrategy max=\"100\" />\n    </RollingFile>\n  </Appenders>\n\n  <Loggers>\n    <!-- \n      Mybatis: <setting name=\"logImpl\" value=\"SLF4J\" />\n               <setting name=\"logPrefix\" value=\"dao.\" />\n      Instead by log4jdbc: jdbc.sqltiming\n    <Logger name=\"dao\" additivity=\"false\" level=\"DEBUG\" >\n        <AppenderRef ref=\"Console\"/>\n    </Logger> -->\n    <!-- maven gav: com.googlecode.log4jdbc:log4jdbc:1.2\n         default.jdbc.driverClass=net.sf.log4jdbc.DriverSpy \n         net.sf.log4jdbc.Slf4jSpyLogDelegator\n    -->\n    <Logger name=\"jdbc.audit\" additivity=\"false\" level=\"OFF\">\n      <AppenderRef ref=\"Console\" />\n    </Logger>\n    <Logger name=\"jdbc.resultset\" additivity=\"false\" level=\"OFF\">\n      <AppenderRef ref=\"Console\" />\n    </Logger>\n    <Logger name=\"jdbc.connection\" additivity=\"false\" level=\"OFF\">\n      <AppenderRef ref=\"Console\" />\n    </Logger>\n    <Logger name=\"jdbc.sqlonly\" additivity=\"false\" level=\"OFF\">\n      <AppenderRef ref=\"Console\" />\n    </Logger>\n    <Logger name=\"log4jdbc.debug\" additivity=\"false\" level=\"OFF\">\n      <AppenderRef ref=\"Console\" />\n    </Logger>\n    <Logger name=\"jdbc.sqltiming\" additivity=\"false\" level=\"DEBUG\">\n      <AppenderRef ref=\"Console\" />\n    </Logger>\n\n    <!-- additivity指定是否同时输出日志到父类的appender，缺省为true -->\n    <Logger name=\"cn.ponfee.commons\" level=\"WARN\" additivity=\"false\">\n      <AppenderRef ref=\"Console\" />\n      <AppenderRef ref=\"RollingFile\" />\n    </Logger>\n\n    <!-- 当根据日志名字获取不到指定的日志器时就使用Root作为默认的日志器 -->\n    <Root level=\"WARN\">\n      <AppenderRef ref=\"Console\" />\n      <AppenderRef ref=\"RollingFile\" />\n    </Root>\n  </Loggers>\n\n</Configuration>\n"
  },
  {
    "path": "src/main/resources/mybatis-conf.xml.template",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-config.dtd\">\n\n<!-- https://mybatis.org/mybatis-3/zh/configuration.html -->\n<configuration>\n    <settings>\n        <!-- 指定MyBatis所用日志的具体实现，未指定时将自动查找。 -->\n        <setting name=\"logImpl\" value=\"SLF4J\" />\n\n        <!-- \n            dao.中的字符“.”是必须的，log4j、log4j2、logback，log4j2.xml中配置：\n            <Logger name=\"dao\" level=\"DEBUG\" additivity=\"false\"><AppenderRef ref=\"Console\"/></Logger>\n         -->\n        <setting name=\"logPrefix\" value=\"dao.\" />\n\n        <!-- 使全局的映射器启用或禁用缓存。 -->\n        <setting name=\"cacheEnabled\" value=\"false\" />\n\n        <!-- 全局启用或禁用延迟加载。当禁用时，所有关联对象都会即时加载。 -->\n        <setting name=\"lazyLoadingEnabled\" value=\"true\" />\n\n        <!-- 返回NULL显示字段 -->\n        <setting name=\"callSettersOnNulls\" value=\"true\" />\n\n        <!-- 下划线转驼峰 -->\n        <setting name=\"mapUnderscoreToCamelCase\" value=\"true\" />\n    </settings>\n\n    <!-- \n        https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md\n        1、helperDialect：分页插件会自动检测当前的数据库链接，自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时，可以使用下面的缩写值：\n            <p>oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby\n            <p>特别注意：使用 SqlServer2012 数据库时，需要手动指定为 sqlserver2012，否则会使用 SqlServer2005 的方式进行分页。\n            <p>你也可以实现 AbstractHelperDialect，然后配置该属性为实现类的全限定名称即可使用自定义的实现方法。\n        2、offsetAsPageNum：默认值为 false，该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时，会将 RowBounds 中的 offset 参数当成 pageNum 使用，可以用页码和页面大小两个参数进行分页。\n        3、rowBoundsWithCount：默认值为false，该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时，使用 RowBounds 分页会进行 count 查询。\n        4、pageSizeZero：默认值为 false，当该参数设置为 true 时，如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果（相当于没有执行分页查询，但是返回结果仍然是 Page 类型）。\n        5、reasonable：分页合理化参数，默认值为false。当该参数设置为 true 时，pageNum<=0 时会查询第一页， pageNum>pages（超过总数时），会查询最后一页。默认false 时，直接根据参数进行查询。\n        6、params：为了支持startPage(Object params)方法，增加了该参数来配置参数映射，用于从对象中根据属性名取值， 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable，不配置映射的用默认值，\n           ,默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。\n        7、supportMethodsArguments：支持通过 Mapper 接口参数来传递分页参数，默认值false，分页插件会从查询方法的参数值中，自动根据上面 params 配置的字段中取值，查找到合适的值时就会自动分页。 \n           ,使用方法可以参考测试代码中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和 ArgumentsObjTest。\n        8、autoRuntimeDialect：默认值为 false。设置为 true 时，允许在运行时根据多数据源自动识别对应方言的分页 （不支持自动选择sqlserver2012，只能使用sqlserver），用法和注意事项参考下面的场景五。\n        9、closeConn：默认值为 true。当使用运行时动态数据源或没有设置 helperDialect 属性自动获取数据库类型时，会自动获取一个数据库连接， 通过该属性来设置是否关闭获取的这个连接，默认true关闭，\n           ,设置为 false 后，不会关闭获取的连接，这个参数的设置要根据自己选择的数据源来决定。\n     -->\n    <plugins>\n        <!-- com.github.pagehelper为PageHelper类所在包名 -->\n        <plugin interceptor=\"com.github.pagehelper.PageInterceptor\">\n            <property name=\"helperDialect\" value=\"mysql\" />\n\n            <!-- 该参数默认为false -->\n            <!-- 设置为true时，会将RowBounds第一个参数offset当成pageNum页码使用 -->\n            <!-- 和startPage中的pageNum效果一样 -->\n            <!-- <property name=\"offsetAsPageNum\" value=\"true\" /> -->\n\n            <!-- 该参数默认为false -->\n            <!-- 设置为true时，使用RowBounds分页会进行count查询 -->\n            <property name=\"rowBoundsWithCount\" value=\"true\" />\n\n            <!-- 默认值为 false，当该参数设置为 true 时，如果 pageSize=0或RowBounds.limit=0就会查询出全部的结果 -->\n            <!-- （相当于没有执行分页查询，但是返回结果仍然是 Page 类型） -->\n            <property name=\"pageSizeZero\" value=\"true\" />\n\n            <!-- 3.3.0版本可用 - 分页参数合理化，默认false禁用 -->\n            <!-- 启用合理化时，如果pageNum<1会查询第一页，如果pageNum>pages会查询最后一页 -->\n            <!-- 禁用合理化时，如果pageNum<1或pageNum>pages会返回空数据 -->\n            <property name=\"reasonable\" value=\"true\" />\n\n            <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->\n            <!-- 增加了一个`params`参数来配置参数映射，用于从Map或ServletRequest中取值 -->\n            <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->\n            <!-- 不理解该含义的前提下，不要随便复制该配置 -->\n            <property name=\"params\" value=\"pageNum=pageHelperStart;pageSize=pageHelperRows;\" />\n\n            <!-- 支持通过Mapper接口参数来传递分页参数 -->\n            <property name=\"supportMethodsArguments\" value=\"true\" />\n\n            <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->\n            <property name=\"returnPageInfo\" value=\"none\" />\n        </plugin>\n    </plugins>\n\n</configuration>\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/Options.java",
    "content": "package cn.ponfee.commons;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport com.google.common.collect.ImmutableMap;\n\n/**\n * Options template code\n * \n * @author Ponfee\n */\npublic class Options {\n\n    public static final Type<Boolean> BOOLEAN = new Type<>();\n    public static final Type<Integer> INTEGER = new Type<>();\n    public static final Type<Long> LONG = new Type<>();\n    public static final Type<Double> DOUBLE = new Type<>();\n    public static final Type<Float> FLOAT = new Type<>();\n    public static final Type<String> STRING = new Type<>();\n\n    private final Map<String, Object> options = new HashMap<>();\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T option(Type<T> key) {\n        return (T) options.get(MAPPER.get(key));\n    }\n\n    public <T> void option(Type<T> key, T value) {\n        options.put(MAPPER.get(key), value);\n    }\n\n    public static class Type<T> {\n        private Type() {}\n    }\n\n    private static final Map<Type<?>, String> MAPPER;\n    static {\n        ImmutableMap.Builder<Type<?>, String> builder = ImmutableMap.builder();\n        try {\n            for (Field field : Options.class.getDeclaredFields()) {\n                int m = field.getModifiers();\n                Object value;\n                if (   Modifier.isPublic(m) && Modifier.isStatic(m) && Modifier.isFinal(m) \n                    && Type.class.isInstance(value = field.get(null))\n                ) {\n                    builder.put((Type<?>) value, field.getName());\n                }\n            }\n        } catch (Exception e) {\n            // cannot happend\n            throw new AssertionError(e);\n        }\n        MAPPER = builder.build();\n    }\n\n}\n\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/SpringBaseTest.java",
    "content": "package cn.ponfee.commons;\n\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.runner.RunWith;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.spring.SpringContextHolder;\n\n/**\n * 测试基类\n * @author Ponfee\n * @param <T>\n */\n@RunWith(SpringRunner.class) // SpringJUnit4ClassRunner.class\n@ContextConfiguration(locations = { \"classpath:spring-context.xml\" })\npublic abstract class SpringBaseTest<T> {\n    private static final Class<?>[] EXCLUDE_CLASSES = {Void.class, Object.class};\n\n    private T bean;\n    private final String beanName;\n\n    public SpringBaseTest() {\n        this(null);\n    }\n\n    public SpringBaseTest(String beanName) {\n        this.beanName = beanName;\n    }\n\n    protected final T getBean() {\n        return bean;\n    }\n\n    @Before\n    public final void setUp() {\n        Class<T> type = GenericUtils.getActualTypeArgument(getClass(), 0);\n        if (!ArrayUtils.contains(EXCLUDE_CLASSES, type)) {\n            bean = StringUtils.isBlank(beanName)\n                    ? SpringContextHolder.getBean(type)\n                    : SpringContextHolder.getBean(beanName, type);\n        }\n        initialize();\n    }\n\n    @After\n    public final void tearDown() {\n        destroy();\n    }\n\n    protected void initialize() {\n        // do no thing\n    }\n\n    protected void destroy() {\n        // do no thing\n    }\n\n    public static void consoleJson(Object obj) {\n        try {\n            Thread.sleep(100);\n            System.err.println(Jsons.toJson(obj));\n            Thread.sleep(100);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static void console(Object obj) {\n        try {\n            Thread.sleep(100);\n            System.err.println(obj);\n            Thread.sleep(100);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/SpringBootTest.java",
    "content": "//package cn.ponfee.commons;\n//\n//import org.junit.After;\n//import org.junit.Before;\n//import org.junit.runner.RunWith;\n//import org.springframework.boot.test.context.SpringBootTest;\n//import org.springframework.test.context.junit4.SpringRunner;\n//\n//import cn.ponfee.commons.json.Jsons;\n//import cn.ponfee.commons.reflect.GenericUtils;\n//import cn.ponfee.commons.spring.SpringContextHolder;\n//\n///**\n// * 测试基类\n// *\n// * @param <T>\n// * @author Ponfee\n// */\n//@RunWith(SpringRunner.class)\n//@SpringBootTest\n//public abstract class SpringBootBaseTest<T> {\n//    private static final Class<?>[] EXCLUDE_CLASSES = {Void.class, Object.class};\n//\n//    private T bean;\n//    private final String beanName;\n//\n//    public SpringBootBaseTest() {\n//        this(null);\n//    }\n//\n//    public SpringBootBaseTest(String beanName) {\n//        this.beanName = beanName;\n//    }\n//\n//    protected final T getBean() {\n//        return bean;\n//    }\n//\n//    @Before\n//    public final void setUp() {\n//        Class<T> type = GenericUtils.getActualTypeArgument(getClass(), 0);\n//        if (!ArrayUtils.contains(EXCLUDE_CLASSES, type)) {\n//            bean = StringUtils.isBlank(beanName)\n//                 ? SpringContextHolder.getBean(type)\n//                 : SpringContextHolder.getBean(beanName, type);\n//        }\n//        initiate();\n//    }\n//\n//    @After\n//    public final void tearDown() {\n//        destory();\n//    }\n//\n//    protected void initiate() {\n//        // do no thing\n//    }\n//\n//    protected void destory() {\n//        // do no thing\n//    }\n//\n//    public static void consoleJson(Object obj) {\n//        try {\n//            Thread.sleep(100);\n//            System.err.println(Jsons.toJson(obj));\n//            Thread.sleep(100);\n//        } catch (InterruptedException e) {\n//            e.printStackTrace();\n//        }\n//    }\n//\n//    public static void console(Object obj) {\n//        try {\n//            Thread.sleep(100);\n//            System.err.println(obj);\n//            Thread.sleep(100);\n//        } catch (InterruptedException e) {\n//            e.printStackTrace();\n//        }\n//    }\n//}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/WebServiceCxfTest.java",
    "content": "//package cn.ponfee.commons;\n//\n//import javax.xml.ws.Endpoint;\n//\n//import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;\n//import org.junit.After;\n//import org.junit.Before;\n//import org.junit.runner.RunWith;\n//import org.springframework.test.context.ContextConfiguration;\n//import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;\n//\n//import cn.ponfee.commons.json.Jsons;\n//import cn.ponfee.commons.reflect.GenericUtils;\n//import cn.ponfee.commons.util.Networks;\n//import cn.ponfee.commons.util.ObjectUtils;\n//import cn.ponfee.commons.spring.SpringContextHolder;\n//\n//@RunWith(SpringJUnit4ClassRunner.class)\n//@ContextConfiguration(locations = { \"classpath:spring/application-config.xml\" })\n//public abstract class WebServiceCxfTest<T> {\n//\n//    private final Class<T> clazz;\n//    private final String addressUrl;\n//\n//    private volatile boolean isPublished = false;\n//    private T client;\n//\n//    protected WebServiceCxfTest() {\n//        this(\"http://localhost:\" + Networks.findAvailablePort(8000) + \"/testws/\" + ObjectUtils.uuid32());\n//    }\n//\n//    protected WebServiceCxfTest(String url) {\n//        clazz = GenericUtils.getActualTypeArgument(this.getClass());\n//        addressUrl = url;\n//    }\n//\n//    protected final T client() {\n//        return client;\n//    }\n//\n//    @Before\n//    public final synchronized void setUp() {\n//        if (isPublished) {\n//            return;\n//        }\n//\n//        // 发布web service\n//        Endpoint.publish(addressUrl, SpringContextHolder.getBean(clazz));\n//        isPublished = true;\n//\n//        // 创建客户端\n//        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();\n//        factory.setServiceClass(clazz);\n//        factory.setAddress(addressUrl);\n//        client = (T) factory.create();\n//\n//        initiate();\n//    }\n//\n//    @After\n//    public final void tearDown() {\n//        destory();\n//    }\n//\n//    protected void initiate() {\n//        // do no thing\n//    }\n//\n//    protected void destory() {\n//        // do no thing\n//    }\n//\n//    public static void consoleJson(Object obj) {\n//        try {\n//            Thread.sleep(100);\n//            System.err.println(Jsons.toJson(obj));\n//            Thread.sleep(100);\n//        } catch (InterruptedException e) {\n//            e.printStackTrace();\n//        }\n//    }\n//\n//    public static void console(Object obj) {\n//        try {\n//            Thread.sleep(100);\n//            System.err.println(obj);\n//            Thread.sleep(100);\n//        } catch (InterruptedException e) {\n//            e.printStackTrace();\n//        }\n//    }\n//\n//}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/WebServiceJaxTest.java",
    "content": "package cn.ponfee.commons;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport cn.ponfee.commons.util.UuidUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.runner.RunWith;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.util.Networks;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.spring.SpringContextHolder;\nimport cn.ponfee.commons.ws.JAXWS;\n\n/**\n * addressUrl: http://localhost:8888/testws/address\n * \n * Endpoint.publish(addressUrl, new WebServiceImpl());\n * \n * wsdl-url: http://localhost:8888/testws/address?wsdl\n * \n * xmlns:ns1=\"http://service.ws.ponfee.cn/\" name=\"TestService\" targetNamespace=\"http://impl.service.ws.ponfee.cn/\">\n * \n * namespaceURI: targetNamespace=\"http://impl.service.ws.ponfee.cn/\"\n *    localPart: name=\"TestService\"\n * \n * @author Ponfee\n * @param <T>\n */\n@RunWith(SpringRunner.class)\n@ContextConfiguration(locations = { \"classpath:spring-context.xml\" })\npublic abstract class WebServiceJaxTest<T> {\n\n    private static final Set<String> PUBLISHED = new HashSet<>();\n\n    private T client;\n    private final String addressUrl;\n    private final String namespaceURI; // targetNamespace=\"http://impl.service.ws.ponfee.cn/\"\n    private final String localPart;    // name=\"TestService\"\n\n    protected WebServiceJaxTest(String namespaceURI, String localPart) {\n        this(\"http://localhost:\" + Networks.findAvailablePort(8000) + \"/testws/\" + UuidUtils.uuid32(), namespaceURI, localPart);\n    }\n\n    protected WebServiceJaxTest(String url, String namespaceURI, String localPart) {\n        this.addressUrl = url;\n        this.namespaceURI = namespaceURI;\n        this.localPart = localPart;\n    }\n\n    protected final T client() {\n        return client;\n    }\n\n    @Before\n    public final void setUp() {\n        Class<T> clazz = GenericUtils.getActualTypeArgument(this.getClass());\n        synchronized (WebServiceJaxTest.class) {\n            int pos = StringUtils.ordinalIndexOf(addressUrl, \"/\", 3);\n            if (pos == -1) {\n                pos = addressUrl.length();\n            }\n            String prefixUrl = addressUrl.substring(0, pos); // http://domain:port\n            if (PUBLISHED.add(prefixUrl)) {\n                // 发布web service\n                JAXWS.publish(addressUrl, SpringContextHolder.getBean(clazz));\n            } else {\n                System.err.println(\"The web service: \" + prefixUrl + \" are already published.\");\n            }\n        }\n\n        // 创建客户端\n        client = JAXWS.client(clazz, addressUrl + \"?wsdl\", namespaceURI, localPart);\n\n        initiate();\n    }\n\n    @After\n    public final void tearDown() {\n        destory();\n    }\n\n    protected void initiate() {\n        // do no thing\n    }\n\n    protected void destory() {\n        // do no thing\n    }\n\n    public static void consoleJson(Object obj) {\n        try {\n            Thread.sleep(100);\n            System.err.println(Jsons.toJson(obj));\n            Thread.sleep(100);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static void console(Object obj) {\n        try {\n            Thread.sleep(100);\n            System.err.println(obj);\n            Thread.sleep(100);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/base/MethodInvokerTest.java",
    "content": "package cn.ponfee.commons.base;\n\nimport com.alibaba.druid.pool.DruidDataSource;\n\nimport cn.ponfee.commons.util.ObjectUtils;\n\npublic class MethodInvokerTest {\n\n    public static void main(String[] args) {\n        DruidDataSource ds = new DruidDataSource();\n        try {\n            Releasable.release(ds);\n        } catch (Exception e) {\n        }\n        try {\n            Initializable.init(ds);\n        } catch (Exception e) {\n        }\n        \n        System.out.println(\"\\n\\n\\n\\n==============================\");\n        try {\n            System.out.println(Releasable.class.getMethod(\"release\"));\n        } catch (Exception e) {\n        }\n        \n        try {\n            System.out.println(ObjectUtils.class.getMethod(\"uuid\"));\n        } catch (Exception e) {\n        }\n        \n        try {\n            System.out.println(MethodInvokerTest.class.getMethod(\"toStr\"));\n        } catch (Exception e) {\n        }\n        \n        try {\n            System.out.println(MethodInvokerTest.class.getMethod(\"toString\"));\n        } catch (Exception e) {\n        }\n    }\n    \n    protected String toStr() {\n        return \"\";\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/base/TupleTest.java",
    "content": "package cn.ponfee.commons.base;\n\nimport cn.ponfee.commons.base.tuple.*;\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class TupleTest {\n\n    @Test\n    public void test() {\n        Assert.assertEquals(new Tuple0(), new Tuple0());\n        Assert.assertEquals(Tuple1.of(1), Tuple1.of(1));\n        Assert.assertEquals(Tuple2.of(1, 2), Tuple2.of(1, 2));\n        Assert.assertTrue(Tuple2.of(1, 2).equals(1, 2));\n\n        StringBuilder builder = new StringBuilder();\n        for (Object e : Tuple4.of(1, 2, 3, 4)) {\n            builder.append(e);\n        }\n        Assert.assertEquals(builder.toString(), \"1234\");\n\n        Assert.assertEquals(Tuple3.of(1, 2, 3).join(\", \", String::valueOf, \"(\", \")\"), \"(1, 2, 3)\");\n\n        Assert.assertEquals(\"()\", Tuple0.of().toString());\n        Assert.assertEquals(\"(1)\", Tuple1.of(1).toString());\n        Assert.assertEquals(\"(1, 2)\", Tuple2.of(1, 2).toString());\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/boolm/GuavaBloomFilterTest.java",
    "content": "package cn.ponfee.commons.boolm;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport com.google.common.hash.BloomFilter;\nimport com.google.common.hash.Funnels;\n\npublic class GuavaBloomFilterTest {\n\n    public static void main(String[] args) {\n        BloomFilter<Integer> filter = BloomFilter.create(\n            //(from, into) -> into.putString(from, Charsets.UTF_8),\n            Funnels.integerFunnel(),\n            1024 * 1024 * 32, \n            0.000000001d\n        );\n        \n        /*filter.test(\"234\");\n        filter.put(\"abc\");\n        filter.mightContain(\"123\");\n        filter.isCompatible(\"abc\");*/\n\n        int size = 1000000;\n        for (int i = 0; i < size; i++) {\n            filter.put(i);\n        }\n\n        for (int i = 0; i < size; i++) {\n            if (!filter.mightContain(i)) {\n                System.out.println(\"有坏人逃脱了\");\n            }\n        }\n\n        List<Integer> list = new ArrayList<>(1000);\n        for (int i = size + 10000; i < size + 20000; i++) {\n            if (filter.mightContain(i)) {\n                list.add(i);\n            }\n        }\n        System.out.println(\"有误伤的数量：\" + list.size());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/boolm/JdkBloomFilter.java",
    "content": "package cn.ponfee.commons.boolm;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.BitSet;\n\nimport cn.ponfee.commons.jce.digest.DigestUtils;\n\npublic class JdkBloomFilter implements VisitedFrontier {\n\n    private static final int DEFAULT_SIZE = 2 << 24;\n    private static final int[] seeds = new int[] { 7, 11, 13, 19, 23, 31, 37, 61 };\n    private BitSet bits = new BitSet(DEFAULT_SIZE);//二进制列表32M\n    private Hash[] func = new Hash[seeds.length]; //8个哈希函数\n\n    private static int size = 0;//保存已经插入的元素个数\n\n    public JdkBloomFilter() {\n        for (int i = 0; i < seeds.length; i++)\n            func[i] = new Hash(DEFAULT_SIZE, seeds[i]);\n    }\n\n    @Override\n    public void put(URL url) {\n        // TODO Auto-generated method stub\n        if (url != null) put(url.toString());\n    }\n\n    @Override\n    public void put(String value) {\n        // TODO Auto-generated method stub\n        size++;\n        for (Hash h : func)//映射位置true\n            bits.set(h.getHash(caculateUrl(value)), true);\n    }\n\n    @Override\n    public boolean contains(URL url) {\n        // TODO Auto-generated method stub\n        return contains(url.toString());\n    }\n\n    @Override\n    public boolean contains(String value) {\n        // TODO Auto-generated method stub\n        if (value == null) return false;\n\n        boolean ret = true;\n        for (Hash h : func)//检测每一个映射到的bit位是否为true\n            ret &= bits.get(h.getHash(caculateUrl(value)));\n        return ret;\n    }\n\n    public static class Hash {\n        private int cap;//保证映射范围在BitSet内\n        private int seed;\n\n        public Hash(int cap, int seed) {\n            this.cap = cap;\n            this.seed = seed;\n        }\n\n        public int getHash(String value) {\n            int result = 0;\n            for (int i = 0; i < value.length(); i++) {//每一位加权相加\n                result = seed * result + value.charAt(i);\n            }\n            return (cap - 1) & result;\n        }\n    }\n\n    private String caculateUrl(String url) {\n        //将没一个url都映射为128个字节的十六进制数，因为有些url相似度很高\n        return DigestUtils.md5Hex(url);\n    }\n\n    public int size() {\n        // TODO Auto-generated method stub\n        return size;\n    }\n\n    public static void main(String[] args) throws MalformedURLException {\n        System.out.println(33554430 >> 6); // % 64\n        System.out.println(((33554430 - 1) >> 6) + 1); // % 64\n        String value = new String(\"http://www.baidu.com\");\n        JdkBloomFilter filter = new JdkBloomFilter();\n        System.out.println(filter.contains(value));\n        filter.put(value);\n        System.out.println(filter.contains(value));\n\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/boolm/RedisBloomFilterTest.java",
    "content": "package cn.ponfee.commons.boolm;\n\nimport java.security.DigestException;\nimport java.security.MessageDigest;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.jce.DigestAlgorithms;\nimport cn.ponfee.commons.jce.digest.DigestUtils;\n\npublic class RedisBloomFilterTest {\n\n    private static final DigestAlgorithms DIGEST = DigestAlgorithms.MD5;\n\n    @Test\n    public void test1() throws DigestException {\n        MessageDigest md = DigestUtils.getMessageDigest(DIGEST, null);\n        md.update(\"123\".getBytes());\n        md.update(\"abc\".getBytes());\n        System.out.println(Hex.encodeHexString(md.digest()));\n\n        md = DigestUtils.getMessageDigest(DIGEST, null);\n        md.update(\"123\".getBytes());\n        System.out.println(Hex.encodeHexString(md.digest(\"abc\".getBytes())));\n\n        md = DigestUtils.getMessageDigest(DIGEST, null);\n        md.update(\"123\".getBytes());\n        md.update(\"abc\".getBytes());\n        byte[] output = new byte[DIGEST.byteSize()];\n        md.digest(output, 0, output.length);\n        System.out.println(Hex.encodeHexString(output));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/boolm/VisitedFrontier.java",
    "content": "package cn.ponfee.commons.boolm;\n\nimport java.net.URL;\n\npublic interface VisitedFrontier {\n    public void put(URL url);\n    public void put(String value);\n    \n    public boolean contains(URL url);\n    public boolean contains(String value);\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/cache/Cache.java",
    "content": "package cn.ponfee.commons.cache;\n\nimport cn.ponfee.commons.base.Releasable;\nimport cn.ponfee.commons.base.TimestampProvider;\nimport cn.ponfee.commons.cache.RemovalNotification.RemovalReason;\nimport cn.ponfee.commons.concurrent.ThreadPoolExecutors;\nimport cn.ponfee.commons.concurrent.ThreadPoolTestUtils;\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport cn.ponfee.commons.util.Base64UrlSafe;\nimport com.google.common.base.Preconditions;\n\nimport java.util.*;\nimport java.util.Map.Entry;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * 缓存类\n * \n * @author Ponfee\n * @param <K>\n * @param <V>\n */\npublic class Cache<K, V> {\n\n    public static final long KEEPALIVE_FOREVER = 0; // 为0表示不失效\n\n    private final boolean caseSensitiveKey; // 是否忽略大小写（只针对String）\n    private final boolean compressKey; // 是否压缩key（只针对String）\n    private final long keepAliveInMillis; // 默认的数据保存的时间\n    private final Map<K, CacheValue<V>> container = new ConcurrentHashMap<>(); // 缓存容器\n\n    private volatile boolean isDestroy = false; // 是否被销毁\n    private final Lock lock = new ReentrantLock(); // 定时清理加锁\n    private final ScheduledExecutorService scheduler;\n    private final RemovalListener<K, V> removalListener;\n    private TimestampProvider timestampProvider = TimestampProvider.CURRENT;\n\n    Cache(boolean caseSensitiveKey, boolean compressKey, long keepAliveInMillis, \n          int autoReleaseInSeconds, ScheduledExecutorService scheduler, \n          RemovalListener<K, V> removalListener) {\n        Preconditions.checkArgument(keepAliveInMillis >= 0);\n        Preconditions.checkArgument(autoReleaseInSeconds >= 0);\n\n        this.caseSensitiveKey = caseSensitiveKey;\n        this.compressKey = compressKey;\n        this.keepAliveInMillis = keepAliveInMillis;\n        this.removalListener = removalListener;\n        this.scheduler = scheduler;\n\n        if (autoReleaseInSeconds > 0) {\n            if (scheduler == null) {\n                scheduler = ThreadPoolTestUtils.CALLER_RUN_SCHEDULER;\n            }\n\n            // 定时清理\n            scheduler.scheduleAtFixedRate(() -> {\n                // none exception to throw, so can not wrap try catch\n                if (!lock.tryLock()) {\n                    return;\n                }\n\n                long now = now();\n                try {\n                    //container.entrySet().removeIf(x -> x.getValue().isExpire(now));\n                    for (Iterator<Entry<K, CacheValue<V>>> iter = container.entrySet().iterator(); iter.hasNext();) {\n                        Entry<K, CacheValue<V>> entry = iter.next();\n                        CacheValue<V> cacheValue = entry.getValue();\n                        if (cacheValue.isExpire(now)) {\n                            iter.remove();\n                            onRemoval(entry.getKey(), cacheValue, RemovalReason.EXPIRED);\n                        }\n                    }\n                } finally {\n                    lock.unlock();\n                }\n            }, autoReleaseInSeconds, autoReleaseInSeconds, TimeUnit.SECONDS);\n        }\n    }\n\n    public boolean isCaseSensitiveKey() {\n        return caseSensitiveKey;\n    }\n\n    public boolean isCompressKey() {\n        return compressKey;\n    }\n\n    public long getKeepAliveInMillis() {\n        return keepAliveInMillis;\n    }\n\n    public RemovalListener<K, V> getRemovalListener() {\n        return removalListener;\n    }\n\n    public ScheduledExecutorService getScheduler() {\n        return scheduler;\n    }\n\n    public TimestampProvider getTimestampProvider() {\n        return timestampProvider;\n    }\n\n    public void setTimestampProvider(TimestampProvider timestampProvider) {\n        this.timestampProvider = timestampProvider;\n    }\n\n    private long now() {\n        return timestampProvider.get();\n    }\n\n    // ---------------------------------------------------------------cache value\n    public void put(K key) {\n        put(key, null);\n    }\n\n    public void put(K key, V value) {\n        long expireTimeMillis;\n        if (keepAliveInMillis > 0) {\n            expireTimeMillis = now() + keepAliveInMillis;\n        } else {\n            expireTimeMillis = KEEPALIVE_FOREVER;\n        }\n\n        put(key, value, expireTimeMillis);\n    }\n\n    public void putWithAliveInMillis(K key, V value, int aliveInMillis) {\n        Preconditions.checkArgument(aliveInMillis > 0);\n\n        put(key, value, now() + aliveInMillis);\n    }\n\n    public void putWithNull(K key, long expireTimeMillis) {\n        put(key, null, expireTimeMillis);\n    }\n\n    public void put(K key, V value, long expireTimeMillis) {\n        Preconditions.checkState(!isDestroy);\n\n        if (expireTimeMillis < KEEPALIVE_FOREVER) {\n            expireTimeMillis = KEEPALIVE_FOREVER;\n        }\n\n        if (expireTimeMillis == KEEPALIVE_FOREVER || expireTimeMillis > now()) {\n            CacheValue<V> newly = new CacheValue<>(value, expireTimeMillis);\n            CacheValue<V> former = container.put(getEffectiveKey(key), newly);\n            onRemoval(key, former, RemovalReason.REPLACED);\n        }\n    }\n\n    /**\n     * 获取\n     * @param key\n     * @return\n     */\n    public V get(K key) {\n        if (isDestroy) {\n            return null;\n        }\n\n        key = getEffectiveKey(key);\n        CacheValue<V> cacheValue = container.get(key);\n        if (cacheValue == null) {\n            return null;\n        } else if (cacheValue.isExpire(now())) {\n            container.remove(key);\n            onRemoval(key, cacheValue, RemovalReason.EXPIRED);\n            return null;\n        } else {\n            return cacheValue.getValue();\n        }\n    }\n\n    /**\n     * Remove key value and return the value if exists\n     * \n     * @param key the key\n     */\n    public V remove(K key) {\n        if (isDestroy) {\n            return null;\n        }\n\n        CacheValue<V> cacheValue = container.remove(getEffectiveKey(key));\n        if (cacheValue == null) {\n            return null;\n        } else if (cacheValue.isExpire(now())) {\n            onRemoval(key, cacheValue, RemovalReason.EXPIRED);\n            return cacheValue.getValue();\n        } else {\n            onRemoval(key, cacheValue, RemovalReason.EVICTED);\n            return null;\n        }\n    }\n\n    /**\n     * @param key\n     * @return\n     */\n    public boolean containsKey(K key) {\n        if (isDestroy) {\n            return false;\n        }\n\n        key = getEffectiveKey(key);\n        CacheValue<V> cacheValue = container.get(key);\n        if (cacheValue == null) {\n            return false;\n        } else if (cacheValue.isExpire(now())) {\n            container.remove(key);\n            onRemoval(key, cacheValue, RemovalReason.EXPIRED);\n            return false;\n        } else {\n            return true;\n        }\n    }\n\n    /**\n     * 是否包含指定的value\n     * @param value\n     * @return\n     */\n    public boolean containsValue(V value) {\n        if (isDestroy) {\n            return false;\n        }\n\n        CacheValue<V> cacheValue;\n        for (Iterator<Entry<K, CacheValue<V>>> i = container.entrySet().iterator(); i.hasNext();) {\n            Entry<K, CacheValue<V>> entry = i.next();\n            cacheValue = entry.getValue();\n            if (cacheValue.isAlive(now())) {\n                if (value == null) {\n                    if (cacheValue.getValue() == null) {\n                        return true;\n                    }\n                } else if (value.equals(cacheValue.getValue())) {\n                    return true;\n                }\n            } else {\n                i.remove();\n                onRemoval(entry.getKey(), cacheValue, RemovalReason.EXPIRED);\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Gets for value collection\n     * \n     * @return  the collection of values\n     */\n    public Collection<V> values() {\n        if (isDestroy) {\n            return Collections.emptyList();\n        }\n\n        Collection<V> values = new ArrayList<>();\n        CacheValue<V> value;\n        for (Iterator<Entry<K, CacheValue<V>>> i = container.entrySet().iterator(); i.hasNext();) {\n            Entry<K, CacheValue<V>> entry = i.next();\n            value = entry.getValue();\n            if (value.isAlive(now())) {\n                values.add(value.getValue());\n            } else {\n                i.remove();\n                onRemoval(entry.getKey(), value, RemovalReason.EXPIRED);\n            }\n        }\n        return values;\n    }\n\n    /**\n     * get size of the cache keys\n     * @return\n     */\n    public int size() {\n        return container.size();\n    }\n\n    /**\n     * check is empty\n     * @return\n     */\n    public boolean isEmpty() {\n        return container.isEmpty();\n    }\n\n    /**\n     * clear all\n     */\n    public void clear() {\n        Preconditions.checkState(!isDestroy);\n\n        container.clear();\n    }\n\n    /**\n     * destory the cache self\n     */\n    public void destroy() {\n        isDestroy = true;\n        if (scheduler != null) {\n            ThreadPoolExecutors.shutdown(scheduler);\n        }\n        container.clear();\n    }\n\n    public boolean isDestroy() {\n        return isDestroy;\n    }\n\n    /**\n     * get effective key\n     * \n     * @param key\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    private K getEffectiveKey(K key) {\n        if (key instanceof CharSequence) {\n            String k = key.toString();\n            if (!caseSensitiveKey) {\n                k = k.toLowerCase(); // 不区分大小写（转小写）\n            }\n            if (compressKey) {\n                k = Base64UrlSafe.encode(DigestUtils.sha1(k)); // 压缩key\n            }\n            key = (K) k;\n        }\n        return key;\n    }\n\n    /**\n     * Removing a value\n     * \n     * @param key the key\n     * @param cacheValue the CacheValue\n     * @param removalReason the removalReason\n     */\n    private void onRemoval(K key, CacheValue<V> cacheValue, RemovalReason removalReason) {\n        V value;\n        if (cacheValue == null || (value = cacheValue.getValue()) == null) {\n            return;\n        }\n\n        try {\n            Releasable.release(value);\n        } catch (Exception ignored) {\n            ignored.printStackTrace();\n        }\n\n        if (this.removalListener != null) {\n            removalListener.onRemoval(new RemovalNotification<>(key, value, removalReason));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/cache/CacheBuilder.java",
    "content": "package cn.ponfee.commons.cache;\n\nimport java.util.concurrent.ScheduledExecutorService;\n\n/**\n * 缓存构建类\n * \n * @author Ponfee\n */\npublic final class CacheBuilder<K, V> {\n    private CacheBuilder() {}\n\n    private boolean caseSensitiveKey = true; // （默认）区分大小写\n    private boolean compressKey = false; // （默认）不压缩key\n    private int autoReleaseInSeconds = 0; // （默认0为不清除）清除无效key的的定时时间间隔\n    private long keepaliveInMillis = 0; // key保留时间，0表示无限制\n    private ScheduledExecutorService executor; // 定时执行器\n    private RemovalListener<K, V> removalListener; // 删除监听器\n\n    public CacheBuilder<K, V>  caseSensitiveKey(boolean caseSensitiveKey) {\n        this.caseSensitiveKey = caseSensitiveKey;\n        return this;\n    }\n\n    public CacheBuilder<K, V>  compressKey(boolean compressKey) {\n        this.compressKey = compressKey;\n        return this;\n    }\n\n    public CacheBuilder<K, V>  autoReleaseInSeconds(int autoReleaseInSeconds) {\n        this.autoReleaseInSeconds = autoReleaseInSeconds;\n        return this;\n    }\n\n    public CacheBuilder<K, V>  keepaliveInMillis(long keepaliveInMillis) {\n        this.keepaliveInMillis = keepaliveInMillis;\n        return this;\n    }\n\n    public CacheBuilder<K, V>  scheduledExecutor(ScheduledExecutorService executor) {\n        this.executor = executor;\n        return this;\n    }\n\n    public CacheBuilder<K, V>  removalListener(RemovalListener<K, V> removalListener) {\n        this.removalListener = removalListener;\n        return this;\n    }\n\n    public Cache<K, V> build() {\n        return new Cache<>(caseSensitiveKey, compressKey, keepaliveInMillis, \n                           autoReleaseInSeconds, executor, removalListener);\n    }\n\n    public static <K, V> CacheBuilder<K, V> newBuilder() {\n        return new CacheBuilder<>();\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/cache/CacheValue.java",
    "content": "package cn.ponfee.commons.cache;\n\n/**\n * 缓存值\n * \n * @author Ponfee\n * @param <T>\n */\nclass CacheValue<T> implements java.io.Serializable {\n\n    private static final long serialVersionUID = 4266458031910874821L;\n\n    private final long expireTimeMillis; // 失效时间\n    private final T value; // 值\n\n    CacheValue(T value, long expireTimeMillis) {\n        this.value = value;\n        this.expireTimeMillis = expireTimeMillis;\n    }\n\n    boolean isAlive(long refTimeMillis) {\n        return Cache.KEEPALIVE_FOREVER == expireTimeMillis\n            || expireTimeMillis > refTimeMillis;\n    }\n\n    boolean isExpire(long refTimeMillis) {\n        return !isAlive(refTimeMillis);\n    }\n\n    T getValue() {\n        return value;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/cache/RemovalListener.java",
    "content": "package cn.ponfee.commons.cache;\n\n/**\n * Removal listener\n * \n * @author Ponfee\n * @param <K> cache key\n * @param <V> cache value\n */\n@FunctionalInterface\npublic interface RemovalListener<K, V> /*extends Consumer<RemovalNotification<K, V>>*/ {\n    void onRemoval(RemovalNotification<K, V> notification);\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/cache/RemovalNotification.java",
    "content": "package cn.ponfee.commons.cache;\n\n/**\n * Removal notification\n * \n * @author Ponfee\n * @param <K>\n * @param <V>\n */\npublic class RemovalNotification<K, V> {\n\n    public enum RemovalReason {\n        REPLACED, // value was replaced by the user\n        EVICTED, // manually removed by the user\n        EXPIRED // expiration timestamp has passed\n    }\n\n    private final K key;\n    private final V value;\n    private final RemovalReason removalReason;\n\n    public RemovalNotification(K key, V value, RemovalReason removalReason) {\n        this.key = key;\n        this.value = value;\n        this.removalReason = removalReason;\n    }\n\n    public K getKey() {\n        return key;\n    }\n\n    public V getValue() {\n        return value;\n    }\n\n    public RemovalReason getRemovalReason() {\n        return removalReason;\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/collects/AbstractArrayList.java",
    "content": "package cn.ponfee.commons.collects;\n\nimport com.google.common.base.Preconditions;\n\nimport java.io.Serializable;\nimport java.util.AbstractList;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.ListIterator;\nimport java.util.RandomAccess;\n\n/**\n * The primitive array of abstract list\n * \n * primitive array convert to list\n * \n * @author Ponfee\n * @param <E>\n */\npublic abstract class AbstractArrayList<E> extends AbstractList<E>\n    implements RandomAccess, Serializable {\n\n    private static final long serialVersionUID = -964514644899401684L;\n    static final int INDEX_NOT_FOUND = -1;\n\n    protected final int start;\n    protected final int end;\n    protected final int size;\n\n    public AbstractArrayList(int start, int end) {\n        Preconditions.checkArgument(start >= 0 && start <= end);\n        this.start = start;\n        this.end = end;\n        this.size = end - start;\n    }\n\n    @Override\n    public final int size() {\n        return size;\n    }\n\n    @Override\n    public final boolean isEmpty() {\n        return size == 0;\n    }\n\n    @Override\n    public final boolean contains(Object target) {\n        return indexOf(target) != INDEX_NOT_FOUND;\n    }\n\n    @Override\n    public final Iterator<E> iterator() {\n        return new ArrayIterator(0);\n    }\n\n    @Override\n    public final ListIterator<E> listIterator(int index) {\n        return super.listIterator(index);\n    }\n\n    private class ArrayIterator implements Iterator<E> {\n        int cursor;\n\n        ArrayIterator(int cursor) {\n            this.cursor = cursor;\n        }\n\n        @Override\n        public boolean hasNext() {\n            return cursor != size;\n        }\n\n        @Override\n        public E next() {\n            return get(cursor++);\n        }\n    }\n\n    // --------------------------------------------------------deprecated methods\n    @Override @Deprecated\n    public final boolean add(E e) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public final void add(int index, E element) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public final E remove(int index) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public final void clear() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public final boolean addAll(int index, Collection<? extends E> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    protected final void removeRange(int fromIndex, int toIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public final boolean remove(Object o) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public final boolean addAll(Collection<? extends E> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public final boolean removeAll(Collection<?> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override @Deprecated\n    public final boolean retainAll(Collection<?> c) {\n        throw new UnsupportedOperationException();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/collects/ByteArrayList.java",
    "content": "package cn.ponfee.commons.collects;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static com.google.common.base.Preconditions.checkElementIndex;\nimport static com.google.common.base.Preconditions.checkPositionIndexes;\n\n/**\n * The primitive byte array to list\n * \n * Error  : Arrays.asList(new byte[] {4,5,6,7})\n * Correct: new ByteArrayList(new byte[] {4,5,6,7})\n * \n * IntStream.of(new int[] { 1, 2, 3, 4 }).boxed().collect(Collectors.toList())\n * \n * @author Ponfee\n */\npublic class ByteArrayList extends AbstractArrayList<Byte> {\n\n    private static final long serialVersionUID = 8638428453599555032L;\n\n    private final byte[] array;\n\n    public ByteArrayList(byte... array) {\n        this(array, 0, array.length);\n    }\n\n    public ByteArrayList(byte[] array, int start, int end) {\n        super(start, end);\n        this.array = Objects.requireNonNull(array);\n    }\n\n    @Override\n    public Byte get(int index) {\n        checkElementIndex(index, size);\n        return array[start + index];\n    }\n\n    @Override\n    public int indexOf(Object target) {\n        return (target instanceof Byte) ? indexOf((byte) target) : INDEX_NOT_FOUND;\n    }\n\n    @Override\n    public int lastIndexOf(Object target) {\n        return (target instanceof Byte) ? lastIndexOf((byte) target) : INDEX_NOT_FOUND;\n    }\n\n    @Override\n    public Byte set(int index, Byte element) {\n        checkElementIndex(index, size);\n        byte oldValue = array[start + index];\n        array[start + index] = Objects.requireNonNull(element);\n        return oldValue;\n    }\n\n    @Override\n    public List<Byte> subList(int fromIndex, int toIndex) {\n        checkPositionIndexes(fromIndex, toIndex, size);\n        if (fromIndex == toIndex) {\n            return Collections.emptyList();\n        }\n        return new ByteArrayList(array, start + fromIndex, start + toIndex);\n    }\n\n    @Override\n    public boolean equals(Object object) {\n        if (object == this) {\n            return true;\n        }\n        if (object instanceof ByteArrayList) {\n            ByteArrayList that = (ByteArrayList) object;\n            if (this.size != that.size) {\n                return false;\n            }\n            for (int i = 0; i < size; i++) {\n                if (this.array[this.start + i] != that.array[that.start + i]) {\n                    return false;\n                }\n            }\n            return true;\n        }\n        return super.equals(object);\n    }\n\n    @Override\n    public int hashCode() {\n        int result = 1;\n        for (int i = start; i < end; i++) {\n            result = 31 * result + (int) array[i];\n        }\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return Arrays.toString(array);\n    }\n\n    public byte[] getArray() {\n        return Arrays.copyOfRange(array, start, end);\n    }\n\n    // ----------------------------------------------------------others methods\n    public int indexOf(byte target) {\n        for (int i = 0; i < size; i++) {\n            if (array[i + start] == target) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public int lastIndexOf(byte target) {\n        for (int i = size - 1; i >= 0; i--) {\n            if (array[i + start] == target) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/collects/ByteArrayListTest.java",
    "content": "package cn.ponfee.commons.collects;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport org.junit.Test;\n\nimport com.google.common.collect.ImmutableMap;\n\nimport cn.ponfee.commons.collect.ArrayHashKey;\nimport cn.ponfee.commons.model.Result;\n\n/**\n * \n * \n * @author Ponfee\n */\npublic class ByteArrayListTest {\n\n    @Test\n    public void test1() {\n        ByteArrayList list = new ByteArrayList(new byte[] {1,2,3,4,5,6,5,8,9}, 3, 7);\n        System.out.println(list.get(0));\n        System.out.println(list.isEmpty());\n        System.out.println(list.size());\n        System.out.println();\n        list.iterator().forEachRemaining(System.out::print);\n        System.out.println();\n        list.listIterator().forEachRemaining(System.out::print);\n        System.out.println();\n        list.listIterator(2).forEachRemaining(System.out::print);\n        System.out.println();\n        \n        System.out.println(\"============================\");\n        System.out.println(list.contains((byte)1));\n        System.out.println(list.contains((byte)5));\n        System.out.println(list.contains(\"x\"));\n\n        System.out.println(\"============================\");\n        System.out.println(list.indexOf((byte) 1));\n        System.out.println(list.indexOf((byte) 5));\n        System.out.println(list.indexOf(\"x\"));\n        \n        System.out.println(\"============================\");\n        System.out.println(list.lastIndexOf((byte) 1));\n        System.out.println(list.lastIndexOf((byte) 5));\n        System.out.println(list.lastIndexOf(\"x\"));\n        \n        System.out.println(\"============================\");\n        list.set(3, (byte)7);\n        System.out.println(list);\n        System.out.println(list.subList(0, 2));\n        System.out.println(list.equals(new ByteArrayList(new byte[] {4,5,6,7})));\n        System.out.println(list.equals(new ByteArrayList(new byte[] {4,5})));\n        System.out.println(list.hashCode());\n\n        System.out.println(Arrays.asList(new byte[] {4,5,6,7}));\n        System.out.println(new ByteArrayList(new byte[] {4,5,6,7}));\n    }\n    \n    @Test\n    public void test2() {\n        ByteArrayList list = new ByteArrayList(new byte[] {1,2,3,4,5,6,5,8,9});\n        System.out.println(list.containsAll(Arrays.asList((byte) 1, (byte) 2, (byte) 3, (byte) 4)));\n        System.out.println(Arrays.toString(list.toArray(new Byte[] {})));\n        System.out.println(IntStream.of(new int[] { 1, 2, 3, 4 }).boxed().collect(Collectors.toList()));\n    }\n\n    @Test\n    public void test3() throws Exception {\n        Method method1 = Result.class.getDeclaredMethod(\"from\", Object.class);\n        Field field1 = Result.class.getDeclaredField(\"code\");\n\n        Map<ArrayHashKey, Boolean> map = ImmutableMap.of(ArrayHashKey.of(method1, field1), true);\n\n        Method method2 = Result.class.getDeclaredMethod(\"from\", Object.class);\n        Field field2 = Result.class.getDeclaredField(\"code\");\n\n        System.out.println(method1 == method2);\n        System.out.println(field1 == field2);\n        \n        System.out.println(map.get(ArrayHashKey.of(method2, field2)));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/collects/DoubleListViewerTest.java",
    "content": "package cn.ponfee.commons.collects;\n\nimport cn.ponfee.commons.collect.DoubleListViewer;\nimport com.google.common.base.Joiner;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * @author Ponfee\n */\npublic class DoubleListViewerTest {\n\n    @Test\n    public void test1() {\n        List<Integer> list1 = new ArrayList<>();\n        list1.add(1);\n        list1.add(2);\n        list1.add(3);\n\n        List<Integer> list2 = new ArrayList<>();\n        list2.add(3);\n        list2.add(4);\n        list2.add(5);\n\n        DoubleListViewer viewer = new DoubleListViewer(Arrays.asList(list1, list2));\n\n        Assert.assertEquals(viewer.get(0), 1);\n        Assert.assertEquals(viewer.get(5), 5);\n        Assert.assertEquals(viewer.indexOf(1), 0);\n        Assert.assertEquals(viewer.indexOf(10), -1);\n        Assert.assertEquals(\"[1, 2, 3, 3, 4, 5]\", viewer.toString());\n        Assert.assertEquals(\"1,2,3,3,4,5\", Joiner.on(\",\").join(viewer));\n        Assert.assertThrows(IndexOutOfBoundsException.class, () -> viewer.get(6));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/collects/ImmutableArrayList1.java",
    "content": "package cn.ponfee.commons.collects;\n\nimport javax.validation.constraints.NotEmpty;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.ListIterator;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\nimport java.util.function.UnaryOperator;\n\n/**\n * Representing immutable ArrayList\n *\n * @param <T> the element type\n * @author Ponfee\n */\npublic abstract class ImmutableArrayList1<T> extends ArrayList<T> {\n    private static final long serialVersionUID = -3263696598948169517L;\n\n    @SuppressWarnings(\"unchecked\")\n    public ImmutableArrayList1(@NotEmpty T... array) {\n        super(array.length);\n        for (T e : array) {\n            super.add(e);\n        }\n    }\n\n    public ImmutableArrayList1(@NotEmpty T[] array, T last) {\n        super(array.length + 1);\n        for (T e : array) {\n            super.add(e);\n        }\n        super.add(last);\n    }\n\n    public ImmutableArrayList1(@NotEmpty List<T> list) {\n        super(list.size());\n        for (T e : list) {\n            super.add(e);\n        }\n    }\n\n    public ImmutableArrayList1(@NotEmpty List<T> list, T last) {\n        super(list.size() + 1);\n        for (T e : list) {\n            super.add(e);\n        }\n        super.add(last);\n    }\n\n    // --------------------------------------------------------------------------override list methods\n    @Override\n    public final List<T> subList(int fromIndex, int toIndex) {\n        return Collections.unmodifiableList(super.subList(fromIndex, toIndex));\n    }\n\n    @Override\n    public final Iterator<T> iterator() {\n        return listIterator(0);\n    }\n\n    @Override\n    public final ListIterator<T> listIterator() {\n        return listIterator(0);\n    }\n\n    @Override\n    public final ListIterator<T> listIterator(int index) {\n        final ListIterator<T> iter = super.listIterator(index);\n        return new ListIterator<T>() {\n            public boolean hasNext()     {return iter.hasNext();}\n            public T next()              {return iter.next();}\n            public boolean hasPrevious() {return iter.hasPrevious();}\n            public T previous()          {return iter.previous();}\n            public int nextIndex()       {return iter.nextIndex();}\n            public int previousIndex()   {return iter.previousIndex();}\n\n            public void remove() { throw new UnsupportedOperationException(); }\n            public void set(T e) { throw new UnsupportedOperationException(); }\n            public void add(T e) { throw new UnsupportedOperationException(); }\n\n            @Override\n            public void forEachRemaining(Consumer<? super T> action) { iter.forEachRemaining(action); }\n        };\n    }\n\n    // --------------------------------------------------------------------------unsupported operation\n    @Override @Deprecated\n    public final boolean add(T e) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final boolean addAll(Collection<? extends T> c) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final T set(int index, T element) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final void add(int index, T element) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final T remove(int index) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final boolean remove(Object o) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final void clear() { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final boolean addAll(int index, Collection<? extends T> c) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    protected final void removeRange(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final boolean removeIf(Predicate<? super T> filter) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final void replaceAll(UnaryOperator<T> operator) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final void sort(Comparator<? super T> c) { throw new UnsupportedOperationException(); }\n\n    @Override @Deprecated\n    public final void trimToSize() { throw new UnsupportedOperationException(); }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/collects/ImmutableArrayListTest.java",
    "content": "package cn.ponfee.commons.collects;\n\nimport cn.ponfee.commons.collect.ImmutableArrayList;\nimport cn.ponfee.commons.json.Jsons;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.collections4.IteratorUtils;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.Arrays;\n\npublic class ImmutableArrayListTest {\n\n    @Test\n    public void test1() {\n        ImmutableArrayList<Object> list = ImmutableArrayList.of();\n        Assert.assertEquals(\"[]\", list.toString());\n        Assert.assertEquals(\"[]\", list.subList(0, 0).toString());\n    }\n\n    @Test\n    public void test2() {\n        ImmutableArrayList<Integer> list = ImmutableArrayList.of(1, 2, 3, 4, 5);\n        Assert.assertEquals(\"[1, 2, 3, 4, 5]\", list.toString());\n        Assert.assertEquals(\"[]\", list.subList(0, 0).toString());\n        Assert.assertTrue(CollectionUtils.isEqualCollection(list.subList(2, 4), Arrays.asList(1, 2, 3, 4, 5).subList(2, 4)));\n\n        Assert.assertTrue(CollectionUtils.isEqualCollection(IteratorUtils.toList(list.subList(2, 4).iterator()), IteratorUtils.toList(Arrays.asList(1, 2, 3, 4, 5).subList(2, 4).iterator())));\n        Assert.assertTrue(CollectionUtils.isEqualCollection(IteratorUtils.toList(list.subList(2, 4).listIterator()), IteratorUtils.toList(Arrays.asList(1, 2, 3, 4, 5).subList(2, 4).listIterator())));\n        Assert.assertTrue(CollectionUtils.isEqualCollection(IteratorUtils.toList(list.subList(2, 4).listIterator(1)), IteratorUtils.toList(Arrays.asList(1, 2, 3, 4, 5).subList(2, 4).listIterator(1))));\n        Assert.assertTrue(list.subList(2, 4).contains(3));\n        Assert.assertFalse(list.subList(2, 4).contains(9));\n        Assert.assertEquals(list.subList(2, 4).indexOf(1), -1);\n        Assert.assertEquals(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).indexOf(1), 0);\n        Assert.assertEquals(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).lastIndexOf(1), 3);\n        Assert.assertTrue(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).containsAll(Arrays.asList(1, 3, 5)));\n        Assert.assertFalse(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).containsAll(Arrays.asList(1, 3, 5, 6)));\n        Assert.assertEquals(Jsons.toJson(Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray(new Integer[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0})), Jsons.toJson(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray(new Integer[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0})));\n        Assert.assertEquals(Jsons.toJson(Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray(new Integer[]{})), Jsons.toJson(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray(new Integer[]{})));\n        Assert.assertEquals(Jsons.toJson(Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray()), Jsons.toJson(ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).toArray()));\n        Assert.assertEquals(Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).hashCode(), ImmutableArrayList.of(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).hashCode());\n\n        Assert.assertTrue(CollectionUtils.isEqualCollection(Arrays.asList(0, 1, 2), ImmutableArrayList.of(0, 1).concat(2)));\n\n        StringBuilder builder = new StringBuilder();\n        Arrays.asList(0, 1, 2, 3, 1, 5, 1, 6).subList(1, 6).spliterator().trySplit().forEachRemaining(builder::append);\n        Assert.assertEquals(\"12315\", builder.toString());\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/collects/IntArrayList.java",
    "content": "package cn.ponfee.commons.collects;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static com.google.common.base.Preconditions.checkElementIndex;\nimport static com.google.common.base.Preconditions.checkPositionIndexes;\n\n/**\n * The primitive int array to list\n * \n * @author Ponfee\n */\npublic class IntArrayList extends AbstractArrayList<Integer> {\n\n    private static final long serialVersionUID = -1601389928083241185L;\n\n    private final int[] array;\n\n    public IntArrayList(int... array) {\n        this(array, 0, array.length);\n    }\n\n    public IntArrayList(int[] array, int start, int end) {\n        super(start, end);\n        this.array = Objects.requireNonNull(array);\n    }\n\n    @Override\n    public Integer get(int index) {\n        checkElementIndex(index, size);\n        return array[start + index];\n    }\n\n    @Override\n    public int indexOf(Object target) {\n        return (target instanceof Integer) ? indexOf((int) target) : INDEX_NOT_FOUND;\n    }\n\n    @Override\n    public int lastIndexOf(Object target) {\n        return (target instanceof Integer) ? lastIndexOf((int) target) : INDEX_NOT_FOUND;\n    }\n\n    @Override\n    public Integer set(int index, Integer element) {\n        checkElementIndex(index, size);\n        int oldValue = array[start + index];\n        array[start + index] = Objects.requireNonNull(element);\n        return oldValue;\n    }\n\n    @Override\n    public List<Integer> subList(int fromIndex, int toIndex) {\n        checkPositionIndexes(fromIndex, toIndex, size);\n        if (fromIndex == toIndex) {\n            return Collections.emptyList();\n        }\n        return new IntArrayList(array, start + fromIndex, start + toIndex);\n    }\n\n    @Override\n    public boolean equals(Object object) {\n        if (object == this) {\n            return true;\n        }\n        if (object instanceof IntArrayList) {\n            IntArrayList that = (IntArrayList) object;\n            if (this.size != that.size) {\n                return false;\n            }\n            for (int i = 0; i < size; i++) {\n                if (this.array[this.start + i] != that.array[that.start + i]) {\n                    return false;\n                }\n            }\n            return true;\n        }\n        return super.equals(object);\n    }\n\n    @Override\n    public int hashCode() {\n        int result = 1;\n        for (int i = start; i < end; i++) {\n            result = 31 * result + array[i];\n        }\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return Arrays.toString(array);\n    }\n\n    public int[] getArray() {\n        return Arrays.copyOfRange(array, start, end);\n    }\n\n    // ----------------------------------------------------------others methods\n    public int indexOf(int target) {\n        for (int i = 0; i < size; i++) {\n            if (array[i + start] == target) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public int lastIndexOf(int target) {\n        for (int i = size - 1; i >= 0; i--) {\n            if (array[i + start] == target) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/collects/ListTest.java",
    "content": "package cn.ponfee.commons.collects;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport cn.ponfee.commons.collect.ImmutableArrayList;\nimport cn.ponfee.commons.collect.Maps;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.collect.Collects;\n\n/**\n * @author Ponfee\n */\npublic class ListTest {\n\n    @Test\n    public void test1() {\n        List<Integer> list1 = new ArrayList<>();\n        list1.add(1);\n        list1.add(2);\n        list1.add(3);\n\n        List<Integer> list2 = new ArrayList<>();\n        list2.add(3);\n        list2.add(4);\n        list2.add(5);\n\n        System.out.println(\"====求交集===\");\n        System.out.println(Collects.intersect(list1, list2));\n\n        System.out.println(\"====求差集===\");\n        System.out.println(Collects.different(list1, list2));\n\n        System.out.println(\"====求并集===\");\n        System.out.println(Collects.union(list1, list2));\n\n        System.out.println();\n        System.out.println(list1);\n        System.out.println(list2);\n\n\n        Map<String, Object> map1 = Maps.toMap(\"a\", 1, \"b\", 2);\n        Map<String, Object> map2 = Maps.toMap(\"c\", 3, \"b\", 2);\n        System.out.println(Collects.different(map1, map2));\n    }\n\n    @Test\n    public void test2() {\n        ImmutableArrayList.of(); // Object[0]\n        ImmutableArrayList.of((String) null); // Object[] { null }\n        //ImmutableList.of((String[]) null); // null\n\n        List<String> list1 = new ArrayList<>();\n        list1.add(\"s\");\n        System.out.println(list1.toArray().getClass()); // class [Ljava.lang.Object;\n\n        List<String> list2 = ImmutableArrayList.of(\"s\");\n        System.out.println(list2.toArray().getClass()); // class [Ljava.lang.String;\n\n        System.out.println(ArrayUtils.addAll(new String[0], \"s\").getClass()); // class [Ljava.lang.String;\n        System.out.println(ArrayUtils.addAll(new Object[0], \"s\").getClass()); // class [Ljava.lang.Object;\n        System.out.println(Arrays.toString(ArrayUtils.addAll(new String[0], \"s\"))); // [s]\n\n        ImmutableArrayList.of(1, 2, 3);\n        ImmutableArrayList.of(\"a\");\n\n    }\n\n    @Test\n    public void test3() {\n        ImmutableArrayList.of(new String[]{\"a\"}, \"b\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/collects/LongArrayList.java",
    "content": "package cn.ponfee.commons.collects;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static com.google.common.base.Preconditions.checkElementIndex;\nimport static com.google.common.base.Preconditions.checkPositionIndexes;\n\n/**\n * The primitive long array to list\n * \n * @author Ponfee\n */\npublic class LongArrayList extends AbstractArrayList<Long> {\n\n    private static final long serialVersionUID = 1648346699558392015L;\n\n    private final long[] array;\n\n    public LongArrayList(long... array) {\n        this(array, 0, array.length);\n    }\n\n    public LongArrayList(long[] array, int start, int end) {\n        super(start, end);\n        this.array = Objects.requireNonNull(array);\n    }\n\n    @Override\n    public Long get(int index) {\n        checkElementIndex(index, size);\n        return array[start + index];\n    }\n\n    @Override\n    public int indexOf(Object target) {\n        return (target instanceof Long) ? indexOf((long) target) : INDEX_NOT_FOUND;\n    }\n\n    @Override\n    public int lastIndexOf(Object target) {\n        return (target instanceof Long) ? lastIndexOf((long) target) : INDEX_NOT_FOUND;\n    }\n\n    @Override\n    public Long set(int index, Long element) {\n        checkElementIndex(index, size);\n        long oldValue = array[start + index];\n        array[start + index] = Objects.requireNonNull(element);\n        return oldValue;\n    }\n\n    @Override\n    public List<Long> subList(int fromIndex, int toIndex) {\n        checkPositionIndexes(fromIndex, toIndex, size);\n        if (fromIndex == toIndex) {\n            return Collections.emptyList();\n        }\n        return new LongArrayList(array, start + fromIndex, start + toIndex);\n    }\n\n    @Override\n    public boolean equals(Object object) {\n        if (object == this) {\n            return true;\n        }\n        if (object instanceof LongArrayList) {\n            LongArrayList that = (LongArrayList) object;\n            if (this.size != that.size) {\n                return false;\n            }\n            for (int i = 0; i < size; i++) {\n                if (this.array[this.start + i] != that.array[that.start + i]) {\n                    return false;\n                }\n            }\n            return true;\n        }\n        return super.equals(object);\n    }\n\n    @Override\n    public int hashCode() {\n        int result = 1;\n        for (int i = start; i < end; i++) {\n            result = 31 * result + (int) array[i];\n        }\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return Arrays.toString(array);\n    }\n\n    public long[] getArray() {\n        return Arrays.copyOfRange(array, start, end);\n    }\n\n    // ----------------------------------------------------------others methods\n    public int indexOf(long target) {\n        for (int i = 0; i < size; i++) {\n            if (array[i + start] == target) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n    public int lastIndexOf(long target) {\n        for (int i = size - 1; i >= 0; i--) {\n            if (array[i + start] == target) {\n                return i;\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/concurrent/ThreadPoolTest.java",
    "content": "package cn.ponfee.commons.concurrent;\n\nimport java.util.Date;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.IntStream;\n\nimport com.google.common.base.Stopwatch;\n\nimport cn.ponfee.commons.date.Dates;\n\npublic class ThreadPoolTest {\n\n    public static void main(String[] args) throws Exception {\n        System.out.println(\"main-thread: \" + Thread.currentThread().getName());\n        //BLOCK_PRODUCER();\n        //CALLER_RUN();\n\n        deadlock(32, () -> { // XXX 33以上会出现死循环\n            /*try {\n                Thread.sleep(1000);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }*/\n            //System.out.println(\"=============thread: \"+ Thread.currentThread().getName());\n        }, 2);\n    }\n\n    private static void deadlock(int threadNumber, Runnable command, int execSeconds) {\n        Stopwatch watch = Stopwatch.createStarted();\n        AtomicBoolean flag = new AtomicBoolean(true);\n\n        CompletableFuture<?>[] futures = IntStream.range(0, threadNumber).mapToObj(\n            x -> CompletableFuture.runAsync(() -> {\n                while (flag.get() && !Thread.currentThread().isInterrupted()) {\n                    command.run();\n                }\n            }, ThreadPoolTestUtils.INFINITY_QUEUE_EXECUTOR) // CALLER_RUN_EXECUTOR：caller run will be dead lock\n       ).toArray(CompletableFuture[]::new);\n\n        System.err.println(\"************************************************\");\n        try {\n            Thread.sleep(execSeconds * 1000L); // parent thread sleep\n            flag.set(false);\n            CompletableFuture.allOf(futures).join();\n        } catch (InterruptedException e) {\n            flag.set(false);\n            throw new RuntimeException(e);\n        } finally {\n            System.out.println(\"multi thread exec async duration: {}\" + watch.stop());\n            System.exit(0);\n        }\n    }\n\n    private static class Comsumer implements Runnable {\n        private final int num;\n\n        public Comsumer(int num) {\n            this.num = num;\n        }\n\n        @Override\n        public void run() {\n            try {\n                Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(6000));\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n            System.out.println(\"consume:=======\" + Dates.format(new Date()) + \"=========\" + num + \", thread:\" + Thread.currentThread().getName());\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/concurrent/ThreadPoolTestUtils.java",
    "content": "package cn.ponfee.commons.concurrent;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.*;\n\n/**\n * Thread pool executor utility\n *\n * https://blog.csdn.net/Holmofy/article/details/73237153\n * https://blog.csdn.net/holmofy/article/details/77411854\n *\n * @author Ponfee\n */\npublic final class ThreadPoolTestUtils {\n    public static final int MAX_CAP = 0x7FFF; // max #workers - 1\n\n    // ----------------------------------------------------------build-in scheduler/executor\n    public static final ScheduledExecutorService CALLER_RUN_SCHEDULER =\n        new DelegatedScheduledExecutorService(\"caller-run-scheduler\", ThreadPoolExecutors.CALLER_RUNS);\n\n    public static final ExecutorService INFINITY_QUEUE_EXECUTOR =\n        new DelegatedExecutorService(\"infinity-queue-executor\", Integer.MAX_VALUE, ThreadPoolExecutors.CALLER_BLOCKS);\n\n    static {\n        Runtime.getRuntime().addShutdownHook(new Thread(() -> {\n            ThreadPoolExecutors.shutdown(((AbstractDelegatedExecutorService) CALLER_RUN_SCHEDULER).delegate);\n            ThreadPoolExecutors.shutdown(((AbstractDelegatedExecutorService) INFINITY_QUEUE_EXECUTOR).delegate);\n        }));\n    }\n\n\n    private static class DelegatedScheduledExecutorService\n            extends AbstractDelegatedExecutorService implements ScheduledExecutorService {\n\n        private DelegatedScheduledExecutorService(String threadName, RejectedExecutionHandler handler) {\n            super(newScheduledExecutorService(threadName, handler));\n        }\n\n        private static ScheduledExecutorService newScheduledExecutorService(String threadName,\n                                                                            RejectedExecutionHandler handler) {\n            // maximumPoolSize=Integer.MAX_VALUE, DelayedWorkQueue, keepAliveTime=0\n            ScheduledThreadPoolExecutor delegate = new ScheduledThreadPoolExecutor(\n                    1, NamedThreadFactory.builder().prefix(threadName).build(), handler\n            );\n\n            //delegate.allowCoreThreadTimeOut(true); // Error: Core threads must have nonzero keep alive times\n            return delegate;\n        }\n\n        @Override\n        public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {\n            return ((ScheduledExecutorService) delegate).schedule(command, delay, unit);\n        }\n\n        @Override\n        public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {\n            return ((ScheduledExecutorService) delegate).schedule(callable, delay, unit);\n        }\n\n        @Override\n        public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,\n                                                      long period, TimeUnit unit) {\n            return ((ScheduledExecutorService) delegate).scheduleAtFixedRate(\n                    command, initialDelay, period, unit\n            );\n        }\n\n        @Override\n        public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,\n                                                         long delay, TimeUnit unit) {\n            return ((ScheduledExecutorService) delegate).scheduleWithFixedDelay(\n                    command, initialDelay, delay, unit\n            );\n        }\n    }\n\n    private static class DelegatedExecutorService extends AbstractDelegatedExecutorService {\n\n        private DelegatedExecutorService(String threadName, int queueCapacity,\n                                         RejectedExecutionHandler handler) {\n            super(newExecutorService(threadName, queueCapacity, handler));\n        }\n\n        private static ExecutorService newExecutorService(String threadName, int queueCapacity,\n                                                          RejectedExecutionHandler handler) {\n            int corePoolSize = Math.max(Runtime.getRuntime().availableProcessors(), 1);\n            int maximumPoolSize = Math.min(corePoolSize << 3, MAX_CAP);\n            corePoolSize = Math.min(corePoolSize << 2, maximumPoolSize);\n\n            BlockingQueue<Runnable> workQueue;\n            if (queueCapacity > 0) {\n                workQueue = new LinkedBlockingQueue<>(queueCapacity);\n            } else {\n                workQueue = new SynchronousQueue<>();\n            }\n\n            // create ThreadPoolExecutor instance\n            ThreadPoolExecutor delegate = new ThreadPoolExecutor(\n                    corePoolSize, maximumPoolSize, 120, TimeUnit.SECONDS,\n                    workQueue, NamedThreadFactory.builder().prefix(threadName).build(), handler\n            );\n            delegate.allowCoreThreadTimeOut(true); // 设置允许核心线程超时关闭\n            return delegate;\n        }\n    }\n\n    private static class AbstractDelegatedExecutorService implements ExecutorService {\n\n        final ExecutorService delegate;\n\n        private AbstractDelegatedExecutorService(ExecutorService delegate) {\n            this.delegate = delegate;\n        }\n\n        @Override @Deprecated\n        public void shutdown() {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override @Deprecated\n        public List<Runnable> shutdownNow() {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override @Deprecated\n        public boolean awaitTermination(long timeout, TimeUnit unit) {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override\n        public boolean isShutdown() {\n            return this.delegate.isShutdown();\n        }\n\n        @Override\n        public boolean isTerminated() {\n            return this.delegate.isTerminated();\n        }\n\n        @Override\n        public <T> Future<T> submit(Callable<T> task) {\n            return delegate.submit(task);\n        }\n\n        @Override\n        public <T> Future<T> submit(Runnable task, T result) {\n            return delegate.submit(task, result);\n        }\n\n        @Override\n        public Future<?> submit(Runnable task) {\n            return delegate.submit(task);\n        }\n\n        @Override\n        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)\n                throws InterruptedException {\n            return delegate.invokeAll(tasks);\n        }\n\n        @Override\n        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,\n                                             long timeout, TimeUnit unit)\n                throws InterruptedException {\n            return delegate.invokeAll(tasks, timeout, unit);\n        }\n\n        @Override\n        public <T> T invokeAny(Collection<? extends Callable<T>> tasks)\n                throws InterruptedException, ExecutionException {\n            return delegate.invokeAny(tasks);\n        }\n\n        @Override\n        public <T> T invokeAny(Collection<? extends Callable<T>> tasks,\n                               long timeout, TimeUnit unit)\n                throws InterruptedException, ExecutionException, TimeoutException {\n            return delegate.invokeAny(tasks, timeout, unit);\n        }\n\n        @Override\n        public void execute(Runnable command) {\n            delegate.execute(command);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/dag/DAGExpressionParserTest.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.dag;\n\nimport cn.ponfee.commons.base.tuple.Tuple2;\nimport cn.ponfee.commons.tree.TreeNode;\nimport cn.ponfee.commons.tree.print.MultiwayTreePrinter;\nimport com.google.common.graph.EndpointPair;\nimport com.google.common.graph.Graph;\nimport static org.junit.Assert.*;\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\n\nimport static java.util.Arrays.asList;\nimport static org.apache.commons.collections4.CollectionUtils.isEqualCollection;\n\n/**\n * DAGParserTest\n *\n * @author Ponfee\n */\npublic class DAGExpressionParserTest {\n\n    private static final MultiwayTreePrinter<TreeNode<DAGExpressionParser.TreeNodeId, Object>> TREE_PRINTER =\n        new MultiwayTreePrinter<>(System.out, e -> e.getNid().toString(), TreeNode::getChildren);\n\n    @Test\n    public void testProcess() {\n        assertEquals(\"((A)->(((B)->(C)->(D)),((A)->(F)))->((G),(H),(X))->(J))\", DAGExpressionParser.completeParenthesis(\"(A->((B->C->D),(A->F))->(G,H,X)->J)\"));\n        assertEquals(\"(A),(B)->((C)->(D)),(E)->(F)\", DAGExpressionParser.completeParenthesis(\"A,B->(C->D),(E)->F\"));\n        assertEquals(\"(A),(B)->((C)->(D)),(E)->(F)\", DAGExpressionParser.completeParenthesis(\"A,B->(C->D),E->F\"));\n    }\n\n    @Test\n    public void testPartition() {\n        assertTrue(isEqualCollection(asList(Tuple2.of(0, 1), Tuple2.of(7, 1)), DAGExpressionParser.group(\"(A -> B)\")));\n\n        assertTrue(isEqualCollection(\n            asList(Tuple2.of(0, 1), Tuple2.of(4, 2), Tuple2.of(12, 2), Tuple2.of(14, 2), Tuple2.of(19, 2), Tuple2.of(22, 2), Tuple2.of(26, 2), Tuple2.of(30, 1)),\n            DAGExpressionParser.group(\"(A->(B->C->D),(E->F)->(G,H)->J)\")\n        ));\n    }\n\n    @Test\n    public void testValidate() {\n        assertTrue(DAGExpressionParser.checkParenthesis(\"(A->(B->C->D),(E->F)->(G,H)->J)\"));\n        assertTrue(DAGExpressionParser.checkParenthesis(\"afdsafd\"));\n        assertFalse(DAGExpressionParser.checkParenthesis(\"((A->(B->C->D),(E->F)->(G,H)->J)\"));\n        assertFalse(DAGExpressionParser.checkParenthesis(\")A->(B->C->D),(E->F)->)G,H(->J(\"));\n        assertFalse(DAGExpressionParser.checkParenthesis(\")(\"));\n        assertFalse(DAGExpressionParser.checkParenthesis(\"()(\"));\n        assertFalse(DAGExpressionParser.checkParenthesis(\"())\"));\n        assertFalse(DAGExpressionParser.checkParenthesis(\"(()\"));\n        assertFalse(DAGExpressionParser.checkParenthesis(\")()\"));\n    }\n\n    @Test\n    public void testBuildTree() throws IOException {\n        List<Tuple2<Integer, Integer>> partitions = DAGExpressionParser.group(\"(A->(B->C->D),(A->F)->(G,H,X)->J)\");\n        TreeNode<DAGExpressionParser.TreeNodeId, Object> root = DAGExpressionParser.buildTree(partitions);\n        assertEquals(root.getChildrenCount(), 3);\n        System.out.println(\"------------------\");\n        TREE_PRINTER.print(root);\n\n        partitions = DAGExpressionParser.group(\"((A->((B->C->D),(E->F))->(G,H)->J))\");\n        root = DAGExpressionParser.buildTree(partitions);\n        assertEquals(root.getChildrenCount(), 1);\n        System.out.println(\"\\n------------------\");\n        TREE_PRINTER.print(root);\n\n        partitions = DAGExpressionParser.group(\"(A->((B->C->D),(E->F))->(G,H)->J)\");\n        root = DAGExpressionParser.buildTree(partitions);\n        assertEquals(root.getChildrenCount(), 2);\n        System.out.println(\"\\n------------------\");\n        TREE_PRINTER.print(root);\n    }\n\n    @Test\n    public void testSameExpression() {\n        String text = \"(A->((B->C->D),(A->F))->(G,H,X)->J)\";\n        assertSameExpression(text, text);\n        assertSameExpression(\"(A->((B->C->D),(A->F))->(G,H,X)->J) \", \" A->((B->C->D),(A->F))->(G,H,X)->J\");\n        assertSameExpression(\"(A->(B->C->D),(A->F)->(G,H,X)->J)\", \"A->((B->C->D),(A->F))->(G,H,X)->J\");\n        assertSameExpression(\"(A->((B->C->D),(A->F))->G,H,X->J)\", \" A->((B->C->D),(A->F))->(G,H,X)->J\");\n        assertSameExpression(\"(A->(B->C->D),(A->F)->G,H,X->J)\", \"A->((B->C->D),(A->F))->(G,H,X)->J\");\n    }\n\n    @Test\n    public void testEdgesEquals() {\n        assertEdgesEquals(\n            \"(A)->((B),(C))->(E),(F->G)->(H)\",\n            \"[<0:0:Start -> 1:1:A>, <1:1:A -> 1:1:B>, <1:1:A -> 1:1:C>, <1:1:B -> 1:1:E>, <1:1:B -> 1:1:F>, <1:1:E -> 1:1:H>, <1:1:H -> 0:0:End>, <1:1:F -> 1:1:G>, <1:1:G -> 1:1:H>, <1:1:C -> 1:1:E>, <1:1:C -> 1:1:F>]\"\n        );\n        assertEdgesEquals(\n            \"(A->((B->C->D),(A->F))->(G,H,X)->J);(A->Y)\",\n            \"[<0:0:Start -> 1:1:A>, <0:0:Start -> 2:3:A>, <1:1:A -> 1:1:B>, <1:1:A -> 1:2:A>, <1:1:B -> 1:1:C>, <1:1:C -> 1:1:D>, <1:1:D -> 1:1:G>, <1:1:D -> 1:1:H>, <1:1:D -> 1:1:X>, <1:1:G -> 1:1:J>, <1:1:J -> 0:0:End>, <1:1:H -> 1:1:J>, <1:1:X -> 1:1:J>, <1:2:A -> 1:1:F>, <1:1:F -> 1:1:G>, <1:1:F -> 1:1:H>, <1:1:F -> 1:1:X>, <2:3:A -> 2:1:Y>, <2:1:Y -> 0:0:End>]\"\n        );\n        assertEdgesEquals(\n            \"(A,B)->(C->D),(A->E),(B->F)->G\",\n            \"[<0:0:Start -> 1:1:A>, <0:0:Start -> 1:2:B>, <1:1:A -> 1:1:C>, <1:1:A -> 1:2:A>, <1:1:A -> 1:1:B>, <1:1:C -> 1:1:D>, <1:1:D -> 1:1:G>, <1:1:G -> 0:0:End>, <1:2:A -> 1:1:E>, <1:1:E -> 1:1:G>, <1:1:B -> 1:1:F>, <1:1:F -> 1:1:G>, <1:2:B -> 1:1:C>, <1:2:B -> 1:2:A>, <1:2:B -> 1:1:B>]\"\n        );\n        assertEdgesEquals(\n            \"A,B->C,D,C\",\n            \"[<0:0:Start -> 1:1:A>, <0:0:Start -> 1:1:B>, <1:1:A -> 1:1:C>, <1:1:A -> 1:1:D>, <1:1:A -> 1:2:C>, <1:1:C -> 0:0:End>, <1:1:D -> 0:0:End>, <1:2:C -> 0:0:End>, <1:1:B -> 1:1:C>, <1:1:B -> 1:1:D>, <1:1:B -> 1:2:C>]\"\n        );\n        assertEdgesEquals(\n            \"A,B->(C->D),E->F\",\n            \"[<0:0:Start -> 1:1:A>, <0:0:Start -> 1:1:B>, <1:1:A -> 1:1:C>, <1:1:A -> 1:1:E>, <1:1:C -> 1:1:D>, <1:1:D -> 1:1:F>, <1:1:F -> 0:0:End>, <1:1:E -> 1:1:F>, <1:1:B -> 1:1:C>, <1:1:B -> 1:1:E>]\"\n        );\n        assertEdgesEquals(\n            \"A,B->(C->D),(E)->F\",\n            \"[<0:0:Start -> 1:1:A>, <0:0:Start -> 1:1:B>, <1:1:A -> 1:1:C>, <1:1:A -> 1:1:E>, <1:1:C -> 1:1:D>, <1:1:D -> 1:1:F>, <1:1:F -> 0:0:End>, <1:1:E -> 1:1:F>, <1:1:B -> 1:1:C>, <1:1:B -> 1:1:E>]\"\n        );\n        assertEdgesEquals(\n            \"A->B;A->B\",\n            \"[<0:0:Start -> 1:1:A>, <0:0:Start -> 2:2:A>, <1:1:A -> 1:1:B>, <1:1:B -> 0:0:End>, <2:2:A -> 2:2:B>, <2:2:B -> 0:0:End>]\"\n        );\n    }\n\n    @Test\n    public void testGraph() {\n        String expression = \"(A->((B->C->D),(A->F))->(G,H,X)->J);(A->Y)\";\n        Graph<DAGNode> graph = new DAGExpressionParser(expression).parse();\n        assertEquals(\"[1:1:A, 2:3:A]\", graph.successors(DAGNode.START).toString());\n        assertTrue(graph.predecessors(DAGNode.START).isEmpty());\n\n        assertEquals(\"[1:1:J, 2:1:Y]\", graph.predecessors(DAGNode.END).toString());\n        assertTrue(graph.successors(DAGNode.END).isEmpty());\n\n        assertEquals(\"[1:1:B, 1:2:A]\", graph.successors(DAGNode.of(1, 1, \"A\")).toString());\n\n        //graph.adjacentNodes();\n        //graph.incidentEdges();\n    }\n\n    @Test\n    public void testGraphSequence() {\n        String expression = \"A -> B,C -> E,(F->G) -> H\";\n        Graph<DAGNode> graph = new DAGExpressionParser(expression).parse();\n        for (EndpointPair<DAGNode> edge : graph.edges()) {\n            System.out.println(edge.source() + \" -> \" + edge.target());\n        }\n\n        Set<DAGNode> predecessors = graph.predecessors(DAGNode.START);\n        assertTrue(predecessors instanceof Set);\n        assertTrue(predecessors.isEmpty());\n\n        Set<DAGNode> successors = graph.successors(DAGNode.END);\n        assertTrue(successors instanceof Set);\n        assertTrue(successors.isEmpty());\n    }\n\n    @Test\n    public void testDAGNode() {\n        assertTrue(DAGNode.fromString(DAGNode.START.toString()) == DAGNode.START);\n        assertTrue(DAGNode.fromString(DAGNode.END.toString()) == DAGNode.END);\n        assertEquals(DAGNode.fromString(\"1:1:test\").toString(), \"1:1:test\");\n        assertEquals(DAGNode.fromString(\"1:1:test:ANY\").getName(), \"test:ANY\");\n        assertEquals(DAGNode.fromString(\"1:1:test:ALL\").getName(), \"test:ALL\");\n        assertEquals(DAGNode.fromString(\"1:1:test:ALL\").toString(), \"1:1:test:ALL\");\n    }\n\n    // ------------------------------------------------------------------------\n\n    private static void assertSameExpression(String text1, String text2) {\n        System.out.println(\"\\n\\n------\\n\\n\");\n        assertEquals(new DAGExpressionParser(text1).parse(), new DAGExpressionParser(text2).parse());\n    }\n\n    private static void assertEdgesEquals(String expression, String edges) {\n        System.out.println(\"\\n\\n------\\n\\n\");\n        System.out.println(expression);\n        Graph<DAGNode> graph = new DAGExpressionParser(expression).parse();\n        assertEquals(edges, graph.edges().toString());\n        System.out.println(expression + \" graph result: \" + graph);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/data/ExtendedDruidPasswordCallback.java",
    "content": "package cn.ponfee.commons.data;\n\nimport java.util.Properties;\n\nimport com.alibaba.druid.filter.config.ConfigTools;\nimport com.alibaba.druid.util.DruidPasswordCallback;\n\n/**\n * Druid数据源数据库密码解密\n * \n * @author Ponfee\n */\npublic class ExtendedDruidPasswordCallback extends DruidPasswordCallback {\n\n    private static final long serialVersionUID = -4596359636208162436L;\n\n    @Override\n    public void setProperties(Properties properties) {\n        super.setProperties(properties);\n        super.setPassword(decrypt((String) properties.get(\"password\")).toCharArray());\n        //super.setUrl(decrypt((String) properties.get(\"url\")));\n    }\n\n    private String decrypt(String data) {\n        try {\n            return ConfigTools.decrypt(\"public-key-text\", data);\n        } catch (Exception e) {\n            return data;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/data/JSONExtractUtilsTest.java",
    "content": "package cn.ponfee.commons.data;\n\nimport cn.ponfee.commons.schema.DataStructure;\nimport cn.ponfee.commons.schema.json.JsonExtractUtils;\nimport cn.ponfee.commons.schema.json.JsonId;\nimport cn.ponfee.commons.schema.json.JsonTree;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.model.Null;\nimport cn.ponfee.commons.tree.TreeNode;\nimport cn.ponfee.commons.util.Asserts;\nimport com.alibaba.fastjson.JSON;\nimport org.junit.Test;\n\nimport java.text.ParseException;\n\npublic class JSONExtractUtilsTest {\n\n    @Test\n    public void testFormat() {\n        System.out.println(String.format(\"%02d\", 1));\n        System.out.println(String.format(\"%01d\", 10));\n        System.out.println(String.format(\"%01d\", 1));\n        System.out.println(String.format(\"%02d\", 10));\n        System.out.println(String.format(\"|% 3d|\", 1));\n        System.out.println(String.format(\"|% 3d|\", 10));\n    }\n\n    @Test\n    public void parseJson() {\n        System.out.println(JSON.parse(\"123\").getClass()); // class java.lang.Integer\n        //System.out.println(JSON.parseObject(\"123\").getClass()); // 【error】\n\n        System.out.println(JSON.parse(\"[1,2,3]\").getClass()); // class com.alibaba.fastjson.JSONArray\n        //System.out.println(JSON.parseObject(\"[1,2,3]\").getClass()); // 【error】\n\n        System.out.println(JSON.parse(\"{}\").getClass()); // class com.alibaba.fastjson.JSONObject\n        System.out.println(JSON.parseObject(\"{}\").getClass()); // class com.alibaba.fastjson.JSONObject\n    }\n\n    // -------------------------------------------------------\n    @Test\n    public void extractObjectArray() throws ParseException {\n        String text = \"[{\\\"name\\\":\\\"Alice\\\",\\\"age\\\":18,\\\"gender\\\":\\\"F\\\"},{\\\"name\\\":\\\"Bob\\\",\\\"age\\\":20,\\\"gender\\\":\\\"M\\\"},{\\\"name\\\":\\\"Tom\\\",\\\"age\\\":30,\\\"gender\\\":\\\"M\\\"}]\";\n        System.out.println(\"Origin data: \" + text);\n\n        TreeNode<JsonId, Null> treeNode = JsonExtractUtils.extractSchema(text);\n        JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert);\n\n        String schemaText = JSON.toJSONString(schemaObj);\n        System.out.println(\"Extracted Schema: \" + schemaText);\n\n        // 拷贝上面的schemaText进行更改部分checked为true（即选中某些想要的数据列）\n        schemaText = \"{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":false,\\\"name\\\":\\\"gender\\\",\\\"orders\\\":2,\\\"path\\\":[\\\"Root\\\",\\\"[{}]\\\",\\\"gender\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"name\\\",\\\"orders\\\":3,\\\"path\\\":[\\\"Root\\\",\\\"[{}]\\\",\\\"name\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"age\\\",\\\"orders\\\":4,\\\"path\\\":[\\\"Root\\\",\\\"[{}]\\\",\\\"age\\\"],\\\"type\\\":\\\"INTEGER\\\"}],\\\"name\\\":\\\"[{}]\\\",\\\"orders\\\":1,\\\"path\\\":[\\\"Root\\\",\\\"[{}]\\\"]}],\\\"name\\\":\\\"Root\\\",\\\"orders\\\":0,\\\"path\\\":[\\\"Root\\\"]}\";\n        System.out.println(\"After selected Some Column: \" + schemaText);\n\n        schemaObj = Jsons.fromJson(schemaText, JsonTree.class);\n        Asserts.isTrue(schemaObj.equals(JSON.parseObject(schemaText, JsonTree.class)), \"two json not equals\");\n        if (!JsonTree.hasChoose(schemaObj)) {\n            throw new IllegalStateException(\"Not choose\");\n        }\n        DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj);\n        System.out.println(\"Table data: \" + JSON.toJSONString(extractedData));\n    }\n\n    @Test\n    public void extractDoubleArray() throws ParseException {\n        String text = \"[[\\\"a1\\\",\\\"b1\\\",11,21],[\\\"a2\\\",\\\"b2\\\",12,22],[\\\"a3\\\",\\\"b3\\\",13,23],[\\\"a4\\\",\\\"b4\\\",14,24]]\";\n        System.out.println(\"Origin data: \" + text);\n\n        TreeNode<JsonId, Null> treeNode = JsonExtractUtils.extractSchema(text);\n        JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert);\n\n        String schemaText = JSON.toJSONString(schemaObj);\n        System.out.println(\"Extracted Schema: \" + schemaText);\n\n        // 拷贝上面的schemaText进行更改部分checked为true（即选中某些想要的数据列）\n        schemaText = \"{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"[02]\\\",\\\"orders\\\":2,\\\"path\\\":[\\\"Root\\\",\\\"[[]]\\\",\\\"[02]\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":false,\\\"name\\\":\\\"[03]\\\",\\\"orders\\\":3,\\\"path\\\":[\\\"Root\\\",\\\"[[]]\\\",\\\"[03]\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"[04]\\\",\\\"orders\\\":4,\\\"path\\\":[\\\"Root\\\",\\\"[[]]\\\",\\\"[04]\\\"],\\\"type\\\":\\\"INTEGER\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"[05]\\\",\\\"orders\\\":5,\\\"path\\\":[\\\"Root\\\",\\\"[[]]\\\",\\\"[05]\\\"],\\\"type\\\":\\\"INTEGER\\\"}],\\\"name\\\":\\\"[[]]\\\",\\\"orders\\\":1,\\\"path\\\":[\\\"Root\\\",\\\"[[]]\\\"]}],\\\"name\\\":\\\"Root\\\",\\\"orders\\\":0,\\\"path\\\":[\\\"Root\\\"]}\";\n        System.out.println(\"After selected Some Column: \" + schemaText);\n\n        schemaObj = Jsons.fromJson(schemaText, JsonTree.class);\n        if (!JsonTree.hasChoose(schemaObj)) {\n            throw new IllegalStateException(\"Not choose\");\n        }\n        DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj);\n        System.out.println(\"Table data: \" + JSON.toJSONString(extractedData));\n    }\n\n    @Test\n    public void extractBasicArray() throws ParseException {\n        String text = \"[\\\"a\\\",\\\"b\\\",1,2]\";\n        System.out.println(\"Origin data: \" + text);\n\n        TreeNode<JsonId, Null> treeNode = JsonExtractUtils.extractSchema(text);\n        JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert);\n\n        String schemaText = JSON.toJSONString(schemaObj);\n        System.out.println(\"Extracted Schema: \" + schemaText);\n\n        // 拷贝上面的schemaText进行更改部分checked为true（即选中某些想要的数据列）\n        schemaText = \"{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"[02]\\\",\\\"orders\\\":2,\\\"path\\\":[\\\"Root\\\",\\\"[()]\\\",\\\"[02]\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":false,\\\"name\\\":\\\"[03]\\\",\\\"orders\\\":3,\\\"path\\\":[\\\"Root\\\",\\\"[()]\\\",\\\"[03]\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":false,\\\"name\\\":\\\"[04]\\\",\\\"orders\\\":4,\\\"path\\\":[\\\"Root\\\",\\\"[()]\\\",\\\"[04]\\\"],\\\"type\\\":\\\"INTEGER\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"[05]\\\",\\\"orders\\\":5,\\\"path\\\":[\\\"Root\\\",\\\"[()]\\\",\\\"[05]\\\"],\\\"type\\\":\\\"INTEGER\\\"}],\\\"name\\\":\\\"[()]\\\",\\\"orders\\\":1,\\\"path\\\":[\\\"Root\\\",\\\"[()]\\\"]}],\\\"name\\\":\\\"Root\\\",\\\"orders\\\":0,\\\"path\\\":[\\\"Root\\\"]}\";\n        System.out.println(\"After selected Some Column: \" + schemaText);\n\n        schemaObj = Jsons.fromJson(schemaText, JsonTree.class);\n        if (!JsonTree.hasChoose(schemaObj)) {\n            throw new IllegalStateException(\"Not choose\");\n        }\n        DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj);\n        System.out.println(\"Table data: \" + JSON.toJSONString(extractedData));\n    }\n\n    @Test\n    public void extractObject() throws ParseException {\n        String text = \"{\\\"name\\\":\\\"Alice\\\",\\\"age\\\":18,\\\"gender\\\":\\\"F\\\"}\";\n        System.out.println(\"Origin data: \" + text);\n\n        TreeNode<JsonId, Null> treeNode = JsonExtractUtils.extractSchema(text);\n        JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert);\n\n        String schemaText = JSON.toJSONString(schemaObj);\n        System.out.println(\"Extracted Schema: \" + schemaText);\n\n        // 拷贝上面的schemaText进行更改部分checked为true（即选中某些想要的数据列）\n        schemaText = \"{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"gender\\\",\\\"orders\\\":1,\\\"path\\\":[\\\"Root\\\",\\\"gender\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":false,\\\"name\\\":\\\"name\\\",\\\"orders\\\":2,\\\"path\\\":[\\\"Root\\\",\\\"name\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"age\\\",\\\"orders\\\":3,\\\"path\\\":[\\\"Root\\\",\\\"age\\\"],\\\"type\\\":\\\"INTEGER\\\"}],\\\"name\\\":\\\"Root\\\",\\\"orders\\\":0,\\\"path\\\":[\\\"Root\\\"]}\";\n        System.out.println(\"After selected Some Column: \" + schemaText);\n\n        schemaObj = Jsons.fromJson(schemaText, JsonTree.class);\n        if (!JsonTree.hasChoose(schemaObj)) {\n            throw new IllegalStateException(\"Not choose\");\n        }\n        DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj);\n        System.out.println(\"Table data: \" + JSON.toJSONString(extractedData));\n    }\n\n    @Test\n    public void extractNormalData() throws ParseException {\n        String text = \"{\\\"code\\\":200,\\\"msg\\\":\\\"成功\\\",\\\"success\\\":true,\\\"data\\\":[{\\\"name\\\":\\\"Alice\\\",\\\"age\\\":18,\\\"gender\\\":\\\"F\\\"},{\\\"name\\\":\\\"Bob\\\",\\\"age\\\":20,\\\"gender\\\":\\\"M\\\"},{\\\"name\\\":\\\"Tom\\\",\\\"age\\\":30,\\\"gender\\\":\\\"M\\\"}]}\";\n        System.out.println(\"Origin data: \" + text);\n\n        TreeNode<JsonId, Null> treeNode = JsonExtractUtils.extractSchema(text);\n        JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert);\n\n        String schemaText = JSON.toJSONString(schemaObj);\n        System.out.println(\"Extracted Schema: \" + schemaText);\n\n        // 拷贝上面的schemaText进行更改部分checked为true（即选中某些想要的数据列）\n        schemaText = \"{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":false,\\\"name\\\":\\\"msg\\\",\\\"orders\\\":1,\\\"path\\\":[\\\"Root\\\",\\\"msg\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":false,\\\"name\\\":\\\"code\\\",\\\"orders\\\":2,\\\"path\\\":[\\\"Root\\\",\\\"code\\\"],\\\"type\\\":\\\"INTEGER\\\"},{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"gender\\\",\\\"orders\\\":5,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"[{}]\\\",\\\"gender\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"name\\\",\\\"orders\\\":6,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"[{}]\\\",\\\"name\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"age\\\",\\\"orders\\\":7,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"[{}]\\\",\\\"age\\\"],\\\"type\\\":\\\"INTEGER\\\"}],\\\"name\\\":\\\"[{}]\\\",\\\"orders\\\":4,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"[{}]\\\"]}],\\\"name\\\":\\\"data\\\",\\\"orders\\\":3,\\\"path\\\":[\\\"Root\\\",\\\"data\\\"]},{\\\"checked\\\":false,\\\"name\\\":\\\"success\\\",\\\"orders\\\":8,\\\"path\\\":[\\\"Root\\\",\\\"success\\\"],\\\"type\\\":\\\"BOOLEAN\\\"}],\\\"name\\\":\\\"Root\\\",\\\"orders\\\":0,\\\"path\\\":[\\\"Root\\\"]}\";\n        System.out.println(\"After selected Some Column: \" + schemaText);\n\n        schemaObj = Jsons.fromJson(schemaText, JsonTree.class);\n        if (!JsonTree.hasChoose(schemaObj)) {\n            throw new IllegalStateException(\"Not choose\");\n        }\n        DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj);\n        System.out.println(\"Table data: \" + JSON.toJSONString(extractedData));\n    }\n\n    @Test\n    public void extractNormalData2() throws ParseException {\n        String text = \"{\\\"code\\\":200,\\\"msg\\\":\\\"成功\\\",\\\"success\\\":true,\\\"data\\\":[{\\\"name\\\":\\\"Alice\\\",\\\"age\\\":18,\\\"gender\\\":\\\"F\\\"},{\\\"name\\\":\\\"Bob\\\",\\\"age\\\":20,\\\"gender\\\":\\\"M\\\"},{\\\"name\\\":\\\"Tom\\\",\\\"age\\\":30,\\\"gender\\\":\\\"M\\\"}]}\";\n        System.out.println(\"Origin data: \" + text);\n\n        TreeNode<JsonId, Null> treeNode = JsonExtractUtils.extractSchema(text);\n        JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert);\n\n        String schemaText = JSON.toJSONString(schemaObj);\n        System.out.println(\"Extracted Schema: \" + schemaText);\n\n        // 拷贝上面的schemaText进行更改部分checked为true（即选中某些想要的数据列）\n        schemaText = \"{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"msg\\\",\\\"orders\\\":1,\\\"path\\\":[\\\"Root\\\",\\\"msg\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"code\\\",\\\"orders\\\":2,\\\"path\\\":[\\\"Root\\\",\\\"code\\\"],\\\"type\\\":\\\"INTEGER\\\"},{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"gender\\\",\\\"orders\\\":5,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"[{}]\\\",\\\"gender\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":false,\\\"name\\\":\\\"name\\\",\\\"orders\\\":6,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"[{}]\\\",\\\"name\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"age\\\",\\\"orders\\\":7,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"[{}]\\\",\\\"age\\\"],\\\"type\\\":\\\"INTEGER\\\"}],\\\"name\\\":\\\"[{}]\\\",\\\"orders\\\":4,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"[{}]\\\"]}],\\\"name\\\":\\\"data\\\",\\\"orders\\\":3,\\\"path\\\":[\\\"Root\\\",\\\"data\\\"]},{\\\"checked\\\":false,\\\"name\\\":\\\"success\\\",\\\"orders\\\":8,\\\"path\\\":[\\\"Root\\\",\\\"success\\\"],\\\"type\\\":\\\"BOOLEAN\\\"}],\\\"name\\\":\\\"Root\\\",\\\"orders\\\":0,\\\"path\\\":[\\\"Root\\\"]}\";\n        System.out.println(\"After selected Some Column: \" + schemaText);\n\n        schemaObj = Jsons.fromJson(schemaText, JsonTree.class);\n        if (!JsonTree.hasChoose(schemaObj)) {\n            throw new IllegalStateException(\"Not choose\");\n        }\n        DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj);\n        System.out.println(\"Table data: \" + JSON.toJSONString(extractedData));\n    }\n\n    @Test\n    public void extractComplexData() throws ParseException {\n        String text = \"{\\\"code\\\":200,\\\"msg\\\":\\\"成功\\\",\\\"success\\\":true,\\\"data\\\":{\\\"friends\\\":[{\\\"name\\\":\\\"Alice\\\",\\\"age\\\":18,\\\"gender\\\":\\\"F\\\",\\\"values\\\":[\\\"a\\\",\\\"b\\\",1,2]},{\\\"name\\\":\\\"Bob\\\",\\\"age\\\":20,\\\"gender\\\":\\\"M\\\",\\\"values\\\":[\\\"c\\\",\\\"d\\\",4]},{\\\"name\\\":\\\"Tom\\\",\\\"age\\\":30,\\\"gender\\\":\\\"M\\\",\\\"values\\\":[\\\"e\\\",\\\"f\\\"]}],\\\"darray\\\":[[\\\"a1\\\",\\\"b1\\\",11,21],[\\\"a2\\\",\\\"b2\\\",12,22],[\\\"a3\\\",\\\"b3\\\",13,23],[\\\"a4\\\",\\\"b4\\\",14,24]]}}\";\n        System.out.println(\"Origin data: \" + text);\n\n        TreeNode<JsonId, Null> treeNode = JsonExtractUtils.extractSchema(text);\n        JsonTree schemaObj = (treeNode == null) ? null : treeNode.convert(JsonTree::convert);\n\n        String schemaText = JSON.toJSONString(schemaObj);\n        System.out.println(\"Extracted Schema: \" + schemaText);\n\n        // 拷贝上面的schemaText进行更改部分checked为true（即选中某些想要的数据列）\n        schemaText = \"{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"msg\\\",\\\"orders\\\":1,\\\"path\\\":[\\\"Root\\\",\\\"msg\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"code\\\",\\\"orders\\\":2,\\\"path\\\":[\\\"Root\\\",\\\"code\\\"],\\\"type\\\":\\\"INTEGER\\\"},{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"[06]\\\",\\\"orders\\\":6,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"darray\\\",\\\"[[]]\\\",\\\"[06]\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"[07]\\\",\\\"orders\\\":7,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"darray\\\",\\\"[[]]\\\",\\\"[07]\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"[08]\\\",\\\"orders\\\":8,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"darray\\\",\\\"[[]]\\\",\\\"[08]\\\"],\\\"type\\\":\\\"INTEGER\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"[09]\\\",\\\"orders\\\":9,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"darray\\\",\\\"[[]]\\\",\\\"[09]\\\"],\\\"type\\\":\\\"INTEGER\\\"}],\\\"name\\\":\\\"[[]]\\\",\\\"orders\\\":5,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"darray\\\",\\\"[[]]\\\"]}],\\\"name\\\":\\\"darray\\\",\\\"orders\\\":4,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"darray\\\"]},{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"gender\\\",\\\"orders\\\":12,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\",\\\"gender\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"children\\\":[{\\\"checked\\\":true,\\\"name\\\":\\\"[15]\\\",\\\"orders\\\":15,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\",\\\"values\\\",\\\"[()]\\\",\\\"[15]\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"[16]\\\",\\\"orders\\\":16,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\",\\\"values\\\",\\\"[()]\\\",\\\"[16]\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"[17]\\\",\\\"orders\\\":17,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\",\\\"values\\\",\\\"[()]\\\",\\\"[17]\\\"],\\\"type\\\":\\\"INTEGER\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"[18]\\\",\\\"orders\\\":18,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\",\\\"values\\\",\\\"[()]\\\",\\\"[18]\\\"],\\\"type\\\":\\\"INTEGER\\\"}],\\\"name\\\":\\\"[()]\\\",\\\"orders\\\":14,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\",\\\"values\\\",\\\"[()]\\\"]}],\\\"name\\\":\\\"values\\\",\\\"orders\\\":13,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\",\\\"values\\\"]},{\\\"checked\\\":true,\\\"name\\\":\\\"name\\\",\\\"orders\\\":19,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\",\\\"name\\\"],\\\"type\\\":\\\"STRING\\\"},{\\\"checked\\\":true,\\\"name\\\":\\\"age\\\",\\\"orders\\\":20,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\",\\\"age\\\"],\\\"type\\\":\\\"INTEGER\\\"}],\\\"name\\\":\\\"[{}]\\\",\\\"orders\\\":11,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\",\\\"[{}]\\\"]}],\\\"name\\\":\\\"friends\\\",\\\"orders\\\":10,\\\"path\\\":[\\\"Root\\\",\\\"data\\\",\\\"friends\\\"]}],\\\"name\\\":\\\"data\\\",\\\"orders\\\":3,\\\"path\\\":[\\\"Root\\\",\\\"data\\\"]},{\\\"checked\\\":true,\\\"name\\\":\\\"success\\\",\\\"orders\\\":21,\\\"path\\\":[\\\"Root\\\",\\\"success\\\"],\\\"type\\\":\\\"BOOLEAN\\\"}],\\\"name\\\":\\\"Root\\\",\\\"orders\\\":0,\\\"path\\\":[\\\"Root\\\"]}\";\n        System.out.println(\"After selected Some Column: \" + schemaText);\n\n        schemaObj = Jsons.fromJson(schemaText, JsonTree.class);\n        if (!JsonTree.hasChoose(schemaObj)) {\n            throw new IllegalStateException(\"Not choose\");\n        }\n        DataStructure extractedData = JsonExtractUtils.extractData(text, schemaObj);\n        System.out.println(\"Table data: \" + JSON.toJSONString(extractedData));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/date/DateFormatTest.java",
    "content": "package cn.ponfee.commons.date;\n\nimport com.google.common.collect.Lists;\nimport org.apache.commons.lang3.time.DateUtils;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.text.ParseException;\nimport java.time.*;\nimport java.time.format.DateTimeFormatter;\nimport java.time.format.DateTimeFormatterBuilder;\nimport java.time.temporal.ChronoField;\nimport java.util.Date;\nimport java.util.Locale;\n\npublic class DateFormatTest {\n\n    @Test\n    public void test1() {\n        LocalDate date = LocalDate.of(2014, 3, 18);\n        String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);\n        String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);\n\n        LocalDate date1 = LocalDate.parse(\"20140318\", DateTimeFormatter.BASIC_ISO_DATE);\n        LocalDate date2 = LocalDate.parse(\"2014-03-18\", DateTimeFormatter.ISO_LOCAL_DATE);\n\n\n        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\"dd/MM/yyyy\");\n        date1 = LocalDate.of(2014, 3, 18);\n        String formattedDate = date1.format(formatter);\n        date2 = LocalDate.parse(formattedDate, formatter);\n\n        DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()\n            .appendText(ChronoField.DAY_OF_MONTH)\n            .appendLiteral(\". \")\n            .appendText(ChronoField.MONTH_OF_YEAR)\n            .appendLiteral(\" \")\n            .appendText(ChronoField.YEAR)\n            .parseCaseInsensitive()\n            .toFormatter(Locale.ITALIAN);\n\n        ZoneId romeZone = ZoneId.of(\"Europe/Rome\");\n        date = LocalDate.of(2014, Month.MARCH, 18);\n        ZonedDateTime zdt1 = date.atStartOfDay(romeZone);\n        LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);\n        ZonedDateTime zdt2 = dateTime.atZone(romeZone);\n        Instant instant = Instant.now();\n        ZonedDateTime zdt3 = instant.atZone(romeZone);\n        System.out.println(zdt3);\n    }\n\n    @Test\n    public void test2() {\n        LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);\n        Instant instantFromDateTime = dateTime.toInstant(ZoneOffset.UTC);\n        System.out.println(instantFromDateTime);\n    }\n\n    @Test\n    public void test3() {\n        System.out.println(Runtime.getRuntime().availableProcessors());\n        System.out.println(Lists.newArrayList(1,2,3).stream().reduce(10, Integer::sum));\n        System.out.println(Lists.newArrayList(1,2,3).stream().reduce(Integer::sum));\n    }\n\n    @Test\n    public void test4() throws ParseException {\n        Date zero = JavaUtilDateFormat.PATTERN_41.parse(Dates.ZERO_DATETIME);\n        Assert.assertEquals(-62170185600000L, zero.getTime());\n        Assert.assertEquals(zero, JavaUtilDateFormat.DEFAULT.parse(Dates.ZERO_DATETIME));\n        Assert.assertEquals(zero, DateUtils.parseDate(Dates.ZERO_DATETIME, Dates.DATETIME_PATTERN));\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/date/DatePeriodCalculatorTest.java",
    "content": "package cn.ponfee.commons.date;\n\nimport org.apache.commons.lang3.time.FastDateFormat;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.Calendar;\nimport java.util.Date;\n\n/**\n * 周期计算\n *\n * @author Ponfee\n */\npublic class DatePeriodCalculatorTest {\n\n    private static final FastDateFormat FORMAT = FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss.SSS\");\n\n    @Test\n    public void test1()  {\n        test(Dates.toDate(\"2020-02-26 00:00:00\", \"yyyy-MM-dd HH:mm:ss\"));\n        System.out.println(\"------------------------\\n\");\n        test(Dates.toDate(\"2021-02-26 00:00:00\", \"yyyy-MM-dd HH:mm:ss\"));\n    }\n\n    private static void test(Date startDate )  {\n        int step = 2;\n        int next = 1;\n        Date target = startDate;\n        String except, actual;\n\n        except = calc(DatePeriodCalculator.Periods.DAILY, startDate, target, step, next);\n        actual = DatePeriods.DAILY.next(startDate, target, step, next).toString();\n        System.out.println(actual);\n        Assert.assertEquals(except, actual);\n\n        except = calc(DatePeriodCalculator.Periods.WEEKLY, startDate, target, step, next);\n        actual = DatePeriods.WEEKLY.next(startDate, target, step, next).toString();\n        System.out.println(actual);\n        Assert.assertEquals(except, actual);\n\n        except = calc(DatePeriodCalculator.Periods.MONTHLY, startDate, target, step, next);\n        actual = DatePeriods.MONTHLY.next(startDate, target, step, next).toString();\n        System.out.println(actual);\n        Assert.assertEquals(except, actual);\n\n        except = calc(DatePeriodCalculator.Periods.QUARTERLY, startDate, target, step, next);\n        actual = DatePeriods.QUARTERLY.next(startDate, target, step, next).toString();\n        System.out.println(actual);\n        Assert.assertEquals(except, actual);\n\n        except = calc(DatePeriodCalculator.Periods.HALF_YEARLY, startDate, target, step, next);\n        actual = DatePeriods.SEMIANNUAL.next(startDate, target, step, next).toString();\n        System.out.println(actual);\n        Assert.assertEquals(except, actual);\n\n        except = calc(DatePeriodCalculator.Periods.YEARLY, startDate, target, step, next);\n        actual = DatePeriods.ANNUAL.next(startDate, target, step, next).toString();\n        System.out.println(actual);\n        Assert.assertEquals(except, actual);\n    }\n\n    private static class DatePeriodCalculator {\n        private final Date starting; // 最开始的周期（起点）时间\n        private final Date target; // 待计算时间\n        private final Periods period; // 周期类型\n\n        public DatePeriodCalculator(Date starting, Date target, Periods period) {\n            this.starting = starting;\n            this.target = target;\n            this.period = period;\n        }\n\n        /**\n         * @param quantity 周期数量\n         * @param next     目标周期的下next个周期\n         * @return\n         */\n        public Date[] calculate(int quantity, int next) {\n            if (quantity < 1) {\n                throw new IllegalArgumentException(\"quantity must be positive number\");\n            }\n            if (starting.after(target)) {\n                throw new IllegalArgumentException(\"starting cannot after target date\");\n            }\n\n            Calendar c1 = Calendar.getInstance();\n            Calendar c2 = Calendar.getInstance();\n            c1.setTime(starting);\n            c2.setTime(target);\n            Date startDate = null;\n            int cycleNum, year;\n            Calendar tmp;\n            float days;\n            switch (period) {\n                case WEEKLY:\n                    quantity *= 7;\n                case DAILY:\n                    days = c2.get(Calendar.DAY_OF_YEAR) - c1.get(Calendar.DAY_OF_YEAR); // 间隔天数\n                    year = c2.get(Calendar.YEAR);\n                    tmp = (Calendar) c1.clone();\n                    while (tmp.get(Calendar.YEAR) != year) {\n                        days += tmp.getActualMaximum(Calendar.DAY_OF_YEAR);// 得到当年的实际天数\n                        tmp.add(Calendar.YEAR, 1);\n                    }\n                    cycleNum = (int) Math.floor(days / quantity) + next; // 上一个周期\n                    c1.add(Calendar.DAY_OF_YEAR, cycleNum * quantity);\n                    startDate = c1.getTime();\n                    c1.add(Calendar.DAY_OF_YEAR, quantity);\n                    break;\n                case QUARTERLY: // 季度\n                case HALF_YEARLY: // 半年度\n                case YEARLY: // 年度\n                case MONTHLY: // 月\n                    switch (period) {\n                        case QUARTERLY: // 季度\n                            quantity *= 3;\n                            break;\n                        case HALF_YEARLY: // 半年度\n                            quantity *= 6;\n                            break;\n                        case YEARLY:\n                            quantity *= 12; // 年度\n                            break;\n                        default: // MONTHLY\n                    }\n                    // 间隔月数\n                    int intervalMonth = (c2.get(Calendar.YEAR) - c1.get(Calendar.YEAR)) * 12 + c2.get(Calendar.MONTH) - c1.get(Calendar.MONTH);\n                    cycleNum = (int) Math.floor(intervalMonth / quantity);\n                    tmp = (Calendar) c1.clone();\n                    // 跨月问题，当前时间仍属于该周期内，则应减一个周期数，如：(2012-01-15 ~ 2012-02-14，当前时间为2012-02-14，则当前时间属于该周期，而不是下一周期)\n                    tmp.add(Calendar.MONTH, cycleNum * quantity);\n                    if (tmp.after(c2)) {\n                        cycleNum -= 1;\n                    }\n                    cycleNum += next; // 上一个周期\n                    c1.add(Calendar.MONTH, cycleNum * quantity);\n                    startDate = c1.getTime(); // 本周期开始时间\n                    c1.add(Calendar.MONTH, quantity); // 本周期结束时间\n                    break;\n                default:\n                    throw new IllegalArgumentException(\"invalid period type\");\n            }\n            c1.add(Calendar.MILLISECOND, -1);\n            return new Date[]{startDate, c1.getTime()};\n        }\n\n        public Date[] calculate(int next) {\n            return calculate(1, next);\n        }\n\n        private enum Periods {\n            DAILY(\"每日\"), //\n            WEEKLY(\"每周\"), //\n            MONTHLY(\"每月\"), //\n            QUARTERLY(\"每季度\"), //\n            HALF_YEARLY(\"每半年\"), //\n            YEARLY(\"每年\");\n\n            private final String desc;\n\n            Periods(String desc) {\n                this.desc = desc;\n            }\n        }\n    }\n\n    private static String calc(DatePeriodCalculator.Periods p, Date start, Date target, int step, int next) {\n        Date[] dates = new DatePeriodCalculator(start, target, p).calculate(step, next);\n        return FORMAT.format(dates[0]) + \" ~ \" + FORMAT.format(dates[1]);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/date/DatePeriodsTest.java",
    "content": "package cn.ponfee.commons.date;\n\nimport cn.ponfee.commons.util.Bytes;\nimport org.apache.commons.codec.DecoderException;\nimport org.apache.commons.codec.binary.Hex;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.Date;\n\npublic class DatePeriodsTest {\n\n    @Test\n    public void test2() {\n        String format = \"yyyy-MM-dd HH:mm:ss.SSS\";\n        Date origin = Dates.toDate(\"2018-10-21 12:23:32.000\", format);\n        Date target = Dates.toDate(\"2018-10-29 12:23:32.000\", format);\n\n        Assert.assertEquals(\"2018-10-29 14:23:32.000 ~ 2018-10-29 16:23:31.999\", DatePeriods.HOURLY.next(origin, target, 2, 1).toString());\n        Assert.assertEquals(\"2018-10-29 16:23:32.000 ~ 2018-10-29 18:23:31.999\", DatePeriods.HOURLY.next(origin, Dates.toDate(\"2018-10-29 14:23:32.000\", format), 2, 1).toString());\n        Assert.assertEquals(\"2018-10-29 14:23:32.000 ~ 2018-10-29 16:23:31.999\", DatePeriods.HOURLY.next(Dates.toDate(\"2018-10-29 14:23:32.000\", format), 2, 0).toString());\n        Assert.assertEquals(\"2018-10-29 14:23:32.000 ~ 2018-10-29 16:23:31.999\", DatePeriods.HOURLY.next(origin, Dates.plusMillis(Dates.toDate(\"2018-10-29 14:23:32.000\", format),-1), 2, 1).toString());\n        Assert.assertEquals(\"2018-10-29 16:23:32.000 ~ 2018-10-29 18:23:31.999\", DatePeriods.HOURLY.next(origin, Dates.plusMillis(Dates.toDate(\"2018-10-29 14:23:32.000\", format),1), 2, 1).toString());\n        Assert.assertEquals(\"2018-11-07 10:23:32.000 ~ 2018-11-07 17:23:31.999\", DatePeriods.HOURLY.next(origin, target, 7, 31).toString());\n        Assert.assertEquals(\"2019-06-02 12:23:32.000 ~ 2019-06-09 12:23:31.999\", DatePeriods.DAILY.next(origin, target, 7, 31).toString());\n        Assert.assertEquals(\"2022-12-18 12:23:32.000 ~ 2023-02-05 12:23:31.999\", DatePeriods.WEEKLY.next(origin, target, 7, 31).toString());\n        Assert.assertEquals(\"2036-11-21 12:23:32.000 ~ 2037-06-21 12:23:31.999\", DatePeriods.MONTHLY.next(origin, target, 7, 31).toString());\n        Assert.assertEquals(\"2073-01-21 12:23:32.000 ~ 2074-10-21 12:23:31.999\", DatePeriods.QUARTERLY.next(origin, target, 7, 31).toString());\n        Assert.assertEquals(\"2127-04-21 12:23:32.000 ~ 2130-10-21 12:23:31.999\", DatePeriods.SEMIANNUAL.next(origin, target, 7, 31).toString());\n        Assert.assertEquals(\"2235-10-21 12:23:32.000 ~ 2242-10-21 12:23:31.999\", DatePeriods.ANNUAL.next(origin, target, 7, 31).toString());\n    }\n\n    @Test\n    public void test3() {\n        String format = \"yyyy-MM-dd HH:mm:ss.SSS\";\n        Date origin = Dates.toDate(\"2017-10-21 12:23:32.000\", format);\n        Date begin = Dates.toDate(\"2018-10-21 11:23:32.000\", format);\n        int step = 3, next = 1;\n        System.out.println(DatePeriods.HOURLY.next(origin, begin, step, 0));\n        System.out.println();\n        DatePeriods.Segment interval = DatePeriods.HOURLY.next(begin, step, next);\n        System.out.println(interval);\n        int i = 4;\n        while (i-- > 0) {\n            begin = interval.begin();\n            interval = DatePeriods.HOURLY.next(begin, step, next);\n            System.out.println(interval);\n        }\n    }\n\n    @Test\n    public void testDateMax() {\n        Date a = Dates.toDate(\"2020-10-12 12:34:23\");\n        Date b = Dates.toDate(\"2020-10-12 12:34:26\");\n\n        Assert.assertEquals(null, Dates.max(null, null));\n        Assert.assertEquals(null, Dates.min(null, null));\n\n        Assert.assertEquals(a, Dates.max(a, null));\n        Assert.assertEquals(a, Dates.max(null, a));\n\n        Assert.assertEquals(a, Dates.min(a, null));\n        Assert.assertEquals(a, Dates.min(null, a));\n\n        Assert.assertEquals(b, Dates.max(a, b));\n        Assert.assertEquals(b, Dates.max(b, a));\n\n        Assert.assertEquals(a, Dates.min(a, b));\n        Assert.assertEquals(a, Dates.min(b, a));\n    }\n\n    @Test\n    public void testPeriods() {\n        String format = \"yyyy-MM-dd HH:mm:ss.SSS\";\n        Date origin = Dates.toDate(\"2018-10-21 06:23:32.000\", format);\n        Date begin = Dates.toDate(\"2018-10-21 11:54:12.000\", format);\n        int step = 3;\n        DatePeriods.Segment segment = DatePeriods.HOURLY.next(origin, begin, step, 0);\n        Assert.assertEquals(\"2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999\", segment.toString());\n\n        segment = DatePeriods.HOURLY.next(segment.begin(), segment.begin(), step, 0);\n        Assert.assertEquals(\"2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999\", segment.toString());\n\n        segment = DatePeriods.HOURLY.next(segment.begin(), step, 0);\n        Assert.assertEquals(\"2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999\", segment.toString());\n\n        segment = DatePeriods.HOURLY.next(segment.begin(), step, 1);\n        Assert.assertEquals(\"2018-10-21 12:23:32.000 ~ 2018-10-21 15:23:31.999\", segment.toString());\n\n        segment = DatePeriods.HOURLY.next(segment.begin(), step, 1);\n        Assert.assertEquals(\"2018-10-21 15:23:32.000 ~ 2018-10-21 18:23:31.999\", segment.toString());\n    }\n\n    @Test\n    public void test() throws DecoderException {\n        String hex = \"0cb703fbc86a41b0\";\n        byte[] bytes = Hex.decodeHex(hex.toCharArray());\n        long number = Bytes.toLong(bytes);\n        System.out.println(number);\n        System.out.println(Long.toHexString(number));\n        Assert.assertEquals(\"cb703fbc86a41b0\", Long.toHexString(number));\n        Assert.assertEquals(\"0cb703fbc86a41b0\", Bytes.encodeHex(Bytes.toBytes(number)));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/date/DatesTest.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.date;\n\nimport cn.ponfee.commons.json.Jsons;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport lombok.Data;\nimport org.apache.commons.lang3.time.DateFormatUtils;\nimport org.apache.commons.lang3.time.DateUtils;\nimport org.apache.commons.lang3.time.FastDateFormat;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Comparator;\nimport java.util.Date;\nimport java.util.Locale;\n\nimport static cn.ponfee.commons.date.Dates.*;\n\n/**\n * Dates test\n *\n * @author Ponfee\n */\npublic class DatesTest {\n\n    public static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance(Dates.DATETIME_PATTERN);\n\n    static int round = 1_000_000;\n\n    @Test\n    public void test() {\n        String str = \"2023-01-03 15:23:45.321\";\n        Assert.assertTrue(isValidDate(str));\n        Assert.assertTrue(isValidDate(str, DATETIME_PATTERN));\n        Assert.assertFalse(isValidDate(\"2020-xx-00 00:00:00\", DATETIME_PATTERN));\n\n        Assert.assertTrue(isZeroDate(toDate(ZERO_DATETIME)));\n        Assert.assertTrue(isZeroDate(toDate(ZERO_DATETIME, DATETIME_PATTERN)));\n        Assert.assertEquals(19, now(DATETIME_PATTERN).length());\n        Assert.assertEquals(str, format(toDate(str), DATEFULL_PATTERN));\n        Assert.assertEquals(str, format(toDate(str, DATEFULL_PATTERN), DATEFULL_PATTERN));\n        Assert.assertEquals(\"1970-01-01 08:00:02.312\", format(2312, DATEFULL_PATTERN));\n\n        Assert.assertEquals(\"2023-01-03 15:23:45.731\", format(plusMillis(toDate(str, DATEFULL_PATTERN), 410), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-03 15:23:48.321\", format(plusSeconds(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-03 15:26:45.321\", format(plusMinutes(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-03 18:23:45.321\", format(plusHours(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-06 15:23:45.321\", format(plusDays(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-24 15:23:45.321\", format(plusWeeks(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-04-03 15:23:45.321\", format(plusMonths(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2026-01-03 15:23:45.321\", format(plusYears(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n\n        Assert.assertEquals(\"2023-01-03 15:23:45.210\", format(minusMillis(toDate(str, DATEFULL_PATTERN), 111), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-03 15:23:42.321\", format(minusSeconds(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-03 15:20:45.321\", format(minusMinutes(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-03 12:23:45.321\", format(minusHours(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2022-12-31 15:23:45.321\", format(minusDays(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2022-12-13 15:23:45.321\", format(minusWeeks(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2022-10-03 15:23:45.321\", format(minusMonths(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2020-01-03 15:23:45.321\", format(minusYears(toDate(str, DATEFULL_PATTERN), 3), DATEFULL_PATTERN));\n\n        Assert.assertEquals(\"2023-01-03 00:00:00.000\", format(startOfDay(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-03 23:59:59.000\", format(endOfDay(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-02 00:00:00.000\", format(startOfWeek(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-08 23:59:59.000\", format(endOfWeek(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-01 00:00:00.000\", format(startOfMonth(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-31 23:59:59.000\", format(endOfMonth(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-01 00:00:00.000\", format(startOfYear(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-12-31 23:59:59.000\", format(endOfYear(toDate(str, DATEFULL_PATTERN)), DATEFULL_PATTERN));\n\n        Assert.assertEquals(\"2023-01-06 15:23:45.321\", format(withDayOfWeek(toDate(str, DATEFULL_PATTERN), 5), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-05 15:23:45.321\", format(withDayOfMonth(toDate(str, DATEFULL_PATTERN), 5), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-06-05 15:23:45.321\", format(withDayOfMonth(toDate(\"2023-06-25 15:23:45.321\", DATEFULL_PATTERN), 5), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-05 15:23:45.321\", format(withDayOfYear(toDate(str, DATEFULL_PATTERN), 5), DATEFULL_PATTERN));\n        Assert.assertEquals(\"2023-01-05 15:23:45.321\", format(withDayOfYear(toDate(\"2023-06-25 15:23:45.321\", DATEFULL_PATTERN), 5), DATEFULL_PATTERN));\n\n        Assert.assertEquals(15, hourOfDay(toDate(str, DATEFULL_PATTERN)));\n        Assert.assertEquals(2, dayOfWeek(toDate(str, DATEFULL_PATTERN)));\n        Assert.assertEquals(3, dayOfMonth(toDate(str, DATEFULL_PATTERN)));\n        Assert.assertEquals(156, dayOfYear(toDate(\"2023-06-05 15:23:45.321\", DATEFULL_PATTERN)));\n\n        Assert.assertEquals(15, daysBetween(toDate(\"2023-05-21 15:23:45\"),toDate(\"2023-06-05 15:23:45\")));\n\n        Assert.assertEquals(\"45 23 15 3 1 ? 2023\", toCronExpression(toDate(str)));\n    }\n\n    @Test\n    public void testSimpleDateFormat() {\n        SimpleDateFormat format = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n        Date date = new Date();\n        for (int i = 0; i < round; i++) {\n            format.format(date);\n        }\n    }\n\n    @Test\n    public void testFastDateFormat() {\n        FastDateFormat format = FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss.SSS\");\n        Date date = new Date();\n        for (int i = 0; i < round; i++) {\n            format.format(date);\n        }\n    }\n\n    @Test\n    public void testDateFormat() throws ParseException {\n        String json = \"{\\\"createTime\\\":\\\"\" + Dates.ZERO_DATETIME + \"\\\"}\";\n       DateEntity dateEntity = Jsons.fromJson(json,DateEntity.class);\n\n\n       DateEntity entity = Jsons.fromJson(\"{\\\"createTime\\\":\\\"2000-03-01 00:00:00\\\"}\",DateEntity.class);\n        Assert.assertEquals(\"2000-03-01 00:00:00\", Dates.format(entity.getCreateTime()));\n\n        // test format\n        Assert.assertEquals(\"0002-11-30 00:00:00\", DateFormatUtils.format(dateEntity.getCreateTime(), Dates.DATETIME_PATTERN));\n        Assert.assertEquals(\"0002-11-30 00:00:00\", DATE_FORMAT.format(dateEntity.getCreateTime()));\n        //Assert.assertEquals(\"-0001-11-28 00:05:43\", Dates.format(orderDriverEntity.getCreateTime(), Dates.DEFAULT_DATETIME_FORMAT)); error\n\n        long time = -62170185600000L;\n        Assert.assertEquals(time, new Date(time).getTime());\n        Assert.assertTrue(Dates.isZeroDate(new Date(time)));\n\n        // test parse\n        Date zeroDate = new Date(time);\n        Assert.assertEquals(zeroDate, dateEntity.getCreateTime());\n        Assert.assertEquals(zeroDate, new Date(zeroDate.getTime()));\n        Assert.assertEquals(zeroDate, DateUtils.parseDate(Dates.ZERO_DATETIME, Dates.DATETIME_PATTERN));\n        Assert.assertEquals(zeroDate, DATE_FORMAT.parse(Dates.ZERO_DATETIME));\n        //Assert.assertEquals(time, Dates.toDate(zeroDateStr, Dates.DEFAULT_DATETIME_FORMAT).getTime()); error\n    }\n\n    @Test\n    public void testDateMax() {\n        Date a = Dates.toDate(\"2021-10-12 12:34:23\");\n        Date b = Dates.toDate(\"2021-10-12 12:34:24\");\n\n        Assert.assertEquals(null, Dates.max(null, null));\n        Assert.assertEquals(null, Dates.min(null, null));\n\n        Assert.assertEquals(a, Dates.max(a, null));\n        Assert.assertEquals(a, Dates.max(null, a));\n\n        Assert.assertEquals(a, Dates.min(a, null));\n        Assert.assertEquals(a, Dates.min(null, a));\n\n        Assert.assertEquals(b, Dates.max(a, b));\n        Assert.assertEquals(b, Dates.max(b, a));\n\n        Assert.assertEquals(a, Dates.min(a, b));\n        Assert.assertEquals(a, Dates.min(b, a));\n    }\n\n    @Test\n    public void testPeriods() {\n        String format = \"yyyy-MM-dd HH:mm:ss.SSS\";\n        Date origin = Dates.toDate(\"2017-10-21 12:23:32.000\", format);\n        Date target = Dates.toDate(\"2018-10-21 11:54:12.000\", format);\n        int step = 3;\n        DatePeriods periods = DatePeriods.HOURLY;\n        DatePeriods.Segment segment = periods.next(origin, target, step, 0);\n        Assert.assertEquals(\"2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999\", segment.toString());\n\n        segment = periods.next(segment.begin(), segment.begin(), step, 0);\n        Assert.assertEquals(\"2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999\", segment.toString());\n\n        segment = periods.next(segment.begin(), step, 0);\n        Assert.assertEquals(\"2018-10-21 09:23:32.000 ~ 2018-10-21 12:23:31.999\", segment.toString());\n\n        segment = periods.next(segment.begin(), step, 1);\n        Assert.assertEquals(\"2018-10-21 12:23:32.000 ~ 2018-10-21 15:23:31.999\", segment.toString());\n\n        segment = periods.next(segment.begin(), step, 1);\n        Assert.assertEquals(\"2018-10-21 15:23:32.000 ~ 2018-10-21 18:23:31.999\", segment.toString());\n\n        // SECOND\n        periods = DatePeriods.PER_SECOND;\n        origin = Dates.toDate(\"2022-05-21 12:23:12.000\", format);\n        target = Dates.toDate(\"2022-05-21 12:23:23.000\", format);\n        step = 5;\n        segment = periods.next(origin, target, step, 0);\n        Assert.assertEquals(\"2022-05-21 12:23:22.000 ~ 2022-05-21 12:23:26.999\", segment.toString());\n\n        segment = periods.next(segment.begin(), step, 1);\n        Assert.assertEquals(\"2022-05-21 12:23:27.000 ~ 2022-05-21 12:23:31.999\", segment.toString());\n\n        segment = periods.next(segment.begin(), step, 1);\n        Assert.assertEquals(\"2022-05-21 12:23:32.000 ~ 2022-05-21 12:23:36.999\", segment.toString());\n\n        // QUARTERLY\n        periods = DatePeriods.QUARTERLY;\n        origin = Dates.toDate(\"2021-08-21 12:23:12.000\", format);\n        target = Dates.toDate(\"2022-05-21 23:34:23.000\", format);\n        step = 2;\n        segment = periods.next(origin, target, step, 0);\n        Assert.assertEquals(\"2022-02-21 12:23:12.000 ~ 2022-08-21 12:23:11.999\", segment.toString());\n\n        segment = periods.next(segment.begin(), step, 1);\n        Assert.assertEquals(\"2022-08-21 12:23:12.000 ~ 2023-02-21 12:23:11.999\", segment.toString());\n\n        segment = periods.next(segment.begin(), step, 1);\n        Assert.assertEquals(\"2023-02-21 12:23:12.000 ~ 2023-08-21 12:23:11.999\", segment.toString());\n    }\n\n    @Test\n    public void testDuration() {\n        Assert.assertEquals(\"PT5M\", Duration.ofSeconds(300).toString());\n        Assert.assertEquals(3600, Duration.parse(\"PT1H\").getSeconds());\n    }\n\n    @Test\n    public void testDateString() throws ParseException {\n        Date date = new Date();\n        System.out.println(\"Dates.format(date): \" + Dates.format(date));\n        String dateString = date.toString();\n        System.out.println(\"date.toString(): \" + dateString);\n        Date parsed1 = FastDateFormat.getInstance(\"EEE MMM dd HH:mm:ss zzz yyyy\", Locale.ENGLISH).parse(dateString);\n        System.out.println(Dates.format(parsed1));\n        Assert.assertNotEquals(date, parsed1);\n\n        System.out.println(\"--------\");\n        FastDateFormat format = FastDateFormat.getInstance(\"EEE MMM dd HH:mm:ss zzz yyyy\", Locale.CHINA);\n        dateString = format.format(date);\n        System.out.println(\"FastDateFormat.format(date): \" + dateString);\n        Date parsed2 = format.parse(dateString);\n        System.out.println(Dates.format(parsed2));\n        Assert.assertNotEquals(date, parsed2);\n    }\n\n    @Test\n    public void testStreamMax() {\n        Date max = Arrays.stream(new Date[]{Dates.toDate(\"2020-01-03 00:00:00\"), Dates.toDate(\"2020-01-02 00:00:00\")})\n            .max(Comparator.naturalOrder())\n            .orElse(null);\n        System.out.println(Dates.format(max));\n    }\n\n    @Data\n    public static class DateEntity {\n        @JsonProperty(value = \"createTime\")\n        @JsonFormat(pattern = Dates.DATETIME_PATTERN)\n        private Date createTime;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/date/JavaUtilDateFormatTest.java",
    "content": "package cn.ponfee.commons.date;\n\nimport org.apache.commons.lang3.time.FastDateFormat;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.text.ParseException;\nimport java.text.ParsePosition;\nimport java.text.SimpleDateFormat;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.Date;\nimport java.util.Locale;\nimport java.util.TimeZone;\n\nimport static org.junit.Assert.assertEquals;\n\n/**\n * JavaUtilDateFormatTest\n *\n * @author Ponfee\n */\npublic class JavaUtilDateFormatTest {\n\n    @Test\n    public void test0() throws ParseException {\n        Date date = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\").parse(\"2022-01-02 03:04:05.678\");\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", date.toString());\n        assertEquals(\"Sun Jan 02 17:04:05 CST 2022\", new Date(date.toString()).toString());\n        DateTimeFormatter dtf = DateTimeFormatter.ofPattern(\"EEE MMM dd HH:mm:ss zzz yyyy\", Locale.ROOT);\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", Dates.toDate(LocalDateTime.parse(date.toString(), dtf)).toString());\n        assertEquals(\"Sun Jan 02 17:04:05 CST 2022\", Date.from(ZonedDateTime.parse(date.toString(), dtf).toInstant()).toString());\n\n        assertEquals(\"202201\", JavaUtilDateFormat.PATTERN_11.format(date));\n        assertEquals(\"2022-01\", JavaUtilDateFormat.PATTERN_12.format(date));\n        assertEquals(\"2022/01\", JavaUtilDateFormat.PATTERN_13.format(date));\n        assertEquals(\"20220102\", JavaUtilDateFormat.PATTERN_21.format(date));\n        assertEquals(\"2022-01-02\", JavaUtilDateFormat.PATTERN_22.format(date));\n        assertEquals(\"2022/01/02\", JavaUtilDateFormat.PATTERN_23.format(date));\n        assertEquals(\"20220102030405\", JavaUtilDateFormat.PATTERN_31.format(date));\n        assertEquals(\"20220102030405678\", JavaUtilDateFormat.PATTERN_32.format(date));\n        assertEquals(\"2022-01-02 03:04:05\", JavaUtilDateFormat.PATTERN_41.format(date));\n        assertEquals(\"2022/01/02 03:04:05\", JavaUtilDateFormat.PATTERN_42.format(date));\n        assertEquals(\"2022-01-02T03:04:05\", JavaUtilDateFormat.PATTERN_43.format(date));\n        assertEquals(\"2022/01/02T03:04:05\", JavaUtilDateFormat.PATTERN_44.format(date));\n        assertEquals(\"2022-01-02 03:04:05.678\", JavaUtilDateFormat.PATTERN_51.format(date));\n        assertEquals(\"2022/01/02 03:04:05.678\", JavaUtilDateFormat.PATTERN_52.format(date));\n        assertEquals(\"2022-01-02T03:04:05.678\", JavaUtilDateFormat.PATTERN_53.format(date));\n        assertEquals(\"2022/01/02T03:04:05.678\", JavaUtilDateFormat.PATTERN_54.format(date));\n        assertEquals(\"2022-01-02 03:04:05.678Z\", JavaUtilDateFormat.PATTERN_61.format(date));\n        assertEquals(\"2022/01/02 03:04:05.678Z\", JavaUtilDateFormat.PATTERN_62.format(date));\n        assertEquals(\"2022-01-02T03:04:05.678Z\", JavaUtilDateFormat.PATTERN_63.format(date));\n        assertEquals(\"2022/01/02T03:04:05.678Z\", JavaUtilDateFormat.PATTERN_64.format(date));\n        assertEquals(\"2022-01-02T03:04:05.678+08\", JavaUtilDateFormat.PATTERN_73.format(date));\n        assertEquals(\"2022/01/02T03:04:05.678+08\", JavaUtilDateFormat.PATTERN_74.format(date));\n        assertEquals(\"2022-01-02 03:04:05\", JavaUtilDateFormat.DEFAULT.format(date));\n\n\n        assertEquals(\"Sat Jan 01 00:00:00 CST 2022\", JavaUtilDateFormat.PATTERN_11.parse(\"202201\").toString());\n        assertEquals(\"Sat Jan 01 00:00:00 CST 2022\", JavaUtilDateFormat.PATTERN_12.parse(\"2022-01\").toString());\n        assertEquals(\"Sat Jan 01 00:00:00 CST 2022\", JavaUtilDateFormat.PATTERN_13.parse(\"2022/01\").toString());\n        assertEquals(\"Sun Jan 02 00:00:00 CST 2022\", JavaUtilDateFormat.PATTERN_21.parse(\"20220102\").toString());\n        assertEquals(\"Sun Jan 02 00:00:00 CST 2022\", JavaUtilDateFormat.PATTERN_22.parse(\"2022-01-02\").toString());\n        assertEquals(\"Sun Jan 02 00:00:00 CST 2022\", JavaUtilDateFormat.PATTERN_23.parse(\"2022/01/02\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_31.parse(\"20220102030405\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_32.parse(\"20220102030405678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_41.parse(\"2022-01-02 03:04:05\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_42.parse(\"2022/01/02 03:04:05\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_43.parse(\"2022-01-02T03:04:05\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_44.parse(\"2022/01/02T03:04:05\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_51.parse(\"2022-01-02 03:04:05.678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_52.parse(\"2022/01/02 03:04:05.678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_53.parse(\"2022-01-02T03:04:05.678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_54.parse(\"2022/01/02T03:04:05.678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_61.parse(\"2022-01-02 03:04:05.678Z\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_62.parse(\"2022/01/02 03:04:05.678Z\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_63.parse(\"2022-01-02T03:04:05.678Z\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_64.parse(\"2022/01/02T03:04:05.678Z\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_73.parse(\"2022-01-02T03:04:05.678+08\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.PATTERN_74.parse(\"2022/01/02T03:04:05.678+08\").toString());\n\n        assertEquals(\"Sat Jan 01 00:00:00 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"202201\").toString());\n        assertEquals(\"Sat Jan 01 00:00:00 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022-01\").toString());\n        assertEquals(\"Sat Jan 01 00:00:00 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022/01\").toString());\n        assertEquals(\"Sun Jan 02 00:00:00 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"20220102\").toString());\n        assertEquals(\"Sun Jan 02 00:00:00 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022-01-02\").toString());\n        assertEquals(\"Sun Jan 02 00:00:00 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022/01/02\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"20220102030405\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"20220102030405678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022-01-02 03:04:05\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022/01/02 03:04:05\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022-01-02T03:04:05\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022/01/02T03:04:05\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022-01-02 03:04:05.678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022/01/02 03:04:05.678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022-01-02T03:04:05.678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022/01/02T03:04:05.678\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022-01-02 03:04:05.678Z\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022/01/02 03:04:05.678Z\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022-01-02T03:04:05.678Z\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022/01/02T03:04:05.678Z\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022-01-02T03:04:05.678+08\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"2022/01/02T03:04:05.678+08\").toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(\"Sun Jan 02 03:04:05 CST 2022\").toString()); // FIXME\n\n\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(String.valueOf(date.getTime())).toString());\n        assertEquals(\"Sun Jan 02 03:04:05 CST 2022\", JavaUtilDateFormat.DEFAULT.parse(String.valueOf(date.getTime()/1000)).toString());\n    }\n\n    @Test\n    public void test1() throws ParseException {\n        String dateString = \"2022-07-19T13:44:27.873Z\";\n        Date date = JavaUtilDateFormat.PATTERN_73.parse(dateString);\n\n        assertEquals(\"202207\", JavaUtilDateFormat.PATTERN_11.format(date));\n        assertEquals(\"2022-07\", JavaUtilDateFormat.PATTERN_12.format(date));\n        assertEquals(\"2022/07\", JavaUtilDateFormat.PATTERN_13.format(date));\n\n        assertEquals(\"20220719\", JavaUtilDateFormat.PATTERN_21.format(date));\n        assertEquals(\"2022-07-19\", JavaUtilDateFormat.PATTERN_22.format(date));\n        assertEquals(\"2022/07/19\", JavaUtilDateFormat.PATTERN_23.format(date));\n\n        assertEquals(\"20220719214427\", JavaUtilDateFormat.PATTERN_31.format(date));\n        assertEquals(\"20220719214427873\", JavaUtilDateFormat.PATTERN_32.format(date));\n\n        assertEquals(\"2022-07-19 21:44:27\", JavaUtilDateFormat.PATTERN_41.format(date));\n        assertEquals(\"2022/07/19 21:44:27\", JavaUtilDateFormat.PATTERN_42.format(date));\n        assertEquals(\"2022-07-19T21:44:27\", JavaUtilDateFormat.PATTERN_43.format(date));\n        assertEquals(\"2022/07/19T21:44:27\", JavaUtilDateFormat.PATTERN_44.format(date));\n\n        assertEquals(\"2022-07-19 21:44:27.873\", JavaUtilDateFormat.PATTERN_51.format(date));\n        assertEquals(\"2022/07/19 21:44:27.873\", JavaUtilDateFormat.PATTERN_52.format(date));\n        assertEquals(\"2022-07-19T21:44:27.873\", JavaUtilDateFormat.PATTERN_53.format(date));\n        assertEquals(\"2022/07/19T21:44:27.873\", JavaUtilDateFormat.PATTERN_54.format(date));\n\n        assertEquals(\"2022-07-19 21:44:27.873Z\", JavaUtilDateFormat.PATTERN_61.format(date));\n        assertEquals(\"2022/07/19 21:44:27.873Z\", JavaUtilDateFormat.PATTERN_62.format(date));\n        assertEquals(\"2022-07-19T21:44:27.873Z\", JavaUtilDateFormat.PATTERN_63.format(date));\n        assertEquals(\"2022/07/19T21:44:27.873Z\", JavaUtilDateFormat.PATTERN_64.format(date));\n\n        assertEquals(\"2022-07-19T21:44:27.873+08\", JavaUtilDateFormat.PATTERN_73.format(date));\n        assertEquals(\"2022/07/19T21:44:27.873+08\", JavaUtilDateFormat.PATTERN_74.format(date));\n    }\n\n    @Test\n    public void test2() throws ParseException {\n        JavaUtilDateFormat format = JavaUtilDateFormat.DEFAULT;\n        FastDateFormat fastDateFormat = JavaUtilDateFormat.PATTERN_51;\n\n        assertEquals(\"2022-07-01 00:00:00.000\", fastDateFormat.format(format.parse(\"202207\")));\n        assertEquals(\"2022-07-01 00:00:00.000\", fastDateFormat.format(format.parse(\"2022-07\")));\n        assertEquals(\"2022-07-01 00:00:00.000\", fastDateFormat.format(format.parse(\"2022/07\")));\n\n        assertEquals(\"2022-07-19 00:00:00.000\", fastDateFormat.format(format.parse(\"20220719\")));\n        assertEquals(\"2022-07-19 00:00:00.000\", fastDateFormat.format(format.parse(\"2022-07-19\")));\n        assertEquals(\"2022-07-19 00:00:00.000\", fastDateFormat.format(format.parse(\"2022/07/19\")));\n\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"20220719214427\")));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"20220719214427873\")));\n\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022-07-19 21:44:27\")));\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022/07/19 21:44:27\")));\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022-07-19T21:44:27\")));\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022/07/19T21:44:27\")));\n\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022-07-19 21:44:27.873\")));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022/07/19 21:44:27.873\")));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022-07-19T21:44:27.873\")));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022/07/19T21:44:27.873\")));\n\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022-07-19T21:44:27Z\")));\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022-07-19T21:44:27.Z\")));\n        assertEquals(\"2022-07-19 21:44:27.800\", fastDateFormat.format(format.parse(\"2022-07-19T21:44:27.8Z\")));\n        assertEquals(\"2022-07-19 21:44:27.870\", fastDateFormat.format(format.parse(\"2022-07-19T21:44:27.87Z\")));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022-07-19T21:44:27.873Z\")));\n\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022/07/19T21:44:27Z\")));\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022/07/19T21:44:27.Z\")));\n        assertEquals(\"2022-07-19 21:44:27.300\", fastDateFormat.format(format.parse(\"2022/07/19T21:44:27.3Z\")));\n        assertEquals(\"2022-07-19 21:44:27.730\", fastDateFormat.format(format.parse(\"2022/07/19T21:44:27.73Z\")));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022/07/19T21:44:27.873Z\")));\n\n\n\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022-07-19 21:44:27Z\")));\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022-07-19 21:44:27.Z\")));\n        assertEquals(\"2022-07-19 21:44:27.800\", fastDateFormat.format(format.parse(\"2022-07-19 21:44:27.8Z\")));\n        assertEquals(\"2022-07-19 21:44:27.870\", fastDateFormat.format(format.parse(\"2022-07-19 21:44:27.87Z\")));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022-07-19 21:44:27.873Z\")));\n\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022/07/19 21:44:27Z\")));\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"2022/07/19 21:44:27.Z\")));\n        assertEquals(\"2022-07-19 21:44:27.300\", fastDateFormat.format(format.parse(\"2022/07/19 21:44:27.3Z\")));\n        assertEquals(\"2022-07-19 21:44:27.730\", fastDateFormat.format(format.parse(\"2022/07/19 21:44:27.73Z\")));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022/07/19 21:44:27.873Z\")));\n\n\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022-07-19T21:44:27.873+08\")));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(\"2022/07/19T21:44:27.873+08\")));\n\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(\"Tue Jul 19 21:44:27 CST 2022\")));\n\n        String dateString = \"2022-07-19T13:44:27.873Z\";\n        Date date = JavaUtilDateFormat.PATTERN_73.parse(dateString);\n        assertEquals(\"2022-07-19 21:44:27.000\", fastDateFormat.format(format.parse(Long.toString(date.getTime()/1000))));\n        assertEquals(\"2022-07-19 21:44:27.873\", fastDateFormat.format(format.parse(Long.toString(date.getTime()))));\n    }\n\n    @Test\n    public void test() throws ParseException {\n        JavaUtilDateFormat format = JavaUtilDateFormat.DEFAULT;\n        Date date = new Date();\n\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_11.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_12.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_21.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_22.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_31.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_41.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_32.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_51.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_63.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_13.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_23.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_42.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_52.format(date)));\n        System.out.println(format.parse(JavaUtilDateFormat.PATTERN_64.format(date)));\n        System.out.println(format.parse(String.valueOf(date.getTime())));\n        System.out.println(format.parse(String.valueOf(date.getTime() / 1000)));\n\n        System.out.println(\"\\n------------------------\");\n        System.out.println(JavaUtilDateFormat.PATTERN_11.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_12.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_21.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_22.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_31.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_41.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_32.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_51.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_63.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_13.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_23.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_42.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_52.format(date));\n        System.out.println(JavaUtilDateFormat.PATTERN_64.format(date));\n\n        System.out.println(\"\\n------------------------\");\n        Assert.assertTrue(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher(\"Sat Jun 01 22:36:21 CST 2019\").matches());\n        Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher(\"Sat Jun 0122:36:21 CST 2019\").matches());\n        Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher(\"sat Jun 01 22:36:21 CST 2019\").matches());\n        Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher(\"Sat Jun 01 22:36:21 DST 2019\").matches());\n        Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher(\"Sat jun 01 22:36:21 CST 2019\").matches());\n        Assert.assertFalse(JavaUtilDateFormat.DATE_TO_STRING_PATTERN.matcher(\"Sat Jun 01 22:36:21CST 2019\").matches());\n        System.out.println(JavaUtilDateFormat.DEFAULT.parse(\"Sat Jun 01 22:36:21 CST 2019\", new ParsePosition(0)));\n        System.out.println(format.parse(\"Sat Jun 01 22:36:21 CST 2019\"));\n        System.out.println(format.parse(\"2020-12-01 10:33:06\"));\n        System.out.println(format.parse(\"1644894528086\"));\n        System.out.println(format.parse(\"1644894528\"));\n        System.out.println(new JavaUtilDateFormat(\"yyyy\").parse(\"2022\"));\n        System.out.println(JavaUtilDateFormat.DEFAULT.format(new Date(0)));\n\n        Assert.assertTrue(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher(\"0\").matches());\n        Assert.assertTrue(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher(\"1\").matches());\n        Assert.assertTrue(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher(\"9\").matches());\n        Assert.assertTrue(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher(\"1644894528\").matches());\n        Assert.assertFalse(JavaUtilDateFormat.DATE_TIMESTAMP_PATTERN.matcher(\"01644894528\").matches());\n\n        System.out.println(\"\\n------------------------\");\n        System.out.println(JavaUtilDateFormat.PATTERN_41.parse(\"2122-01-01 00:00:00\", new ParsePosition(0)));\n        System.out.println(JavaUtilDateFormat.PATTERN_41.parse(\"2122-01-01 00:00:00\", new ParsePosition(1)));\n        System.out.println(JavaUtilDateFormat.DEFAULT.parse(\"x2122-01-01 00:00:00\", new ParsePosition(1)));\n        System.out.println(JavaUtilDateFormat.DEFAULT.parse(\"2122-01-01 00:00:00\", new ParsePosition(0)));\n        Assert.assertEquals(\"Wed Jan 01 00:00:00 CST 122\", JavaUtilDateFormat.DEFAULT.parse(\"2122-01-01 00:00:00\", new ParsePosition(1)).toString());\n        Assert.assertThrows(ParseException.class, () -> JavaUtilDateFormat.DEFAULT.parse(\"xxx-01-01 00:00:00\"));\n    }\n\n    @Test\n    public void test3() throws ParseException {\n        System.out.println(FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss.SSS\", TimeZone.getTimeZone(ZoneOffset.UTC)).format(new Date()));\n        System.out.println(FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss.SSS\", TimeZone.getTimeZone(\"GMT+8\")).format(new Date()));\n        System.out.println(\"-------\");\n        System.out.println(FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss.SSS\", TimeZone.getTimeZone(ZoneOffset.UTC)).parse(\"2022-12-15 11:53:12.273\"));\n        System.out.println(FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss.SSS\", TimeZone.getTimeZone(\"GMT+8\")).parse(\"2022-12-15 11:53:12.273\"));\n        System.out.println(\"-------\");\n        System.out.println(FastDateFormat.getInstance(\"yyyy-MM-dd'T'HH:mm:ss.SSSX\", TimeZone.getTimeZone(ZoneOffset.UTC)).parse(\"2022-12-15T11:50:29.855+08\"));\n        System.out.println(FastDateFormat.getInstance(\"yyyy-MM-dd'T'HH:mm:ss.SSSX\", TimeZone.getTimeZone(\"GMT+8\")).parse(\"2022-12-15T11:50:29.855+08\"));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/date/LocalDateTimeFormatTest.java",
    "content": "package cn.ponfee.commons.date;\n\nimport org.junit.Test;\n\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.ZoneOffset;\nimport java.time.format.DateTimeFormatter;\nimport java.time.format.DateTimeParseException;\nimport java.util.Date;\nimport java.util.TimeZone;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertThrows;\n\n/**\n * LocalDateTimeFormatTest\n *\n * @author Ponfee\n */\npublic class LocalDateTimeFormatTest {\n\n    @Test\n    public void test9() {\n        LocalDateTime localDateTime = LocalDateTime.of(2022, 01, 02, 03, 04, 05, 123000000);\n        Date date = Dates.toDate(localDateTime);\n\n        assertEquals(\"2022-01-02T03:04:05.123\", localDateTime.toString());\n        assertEquals(\"2022-01-02 03:04:05\", Dates.format(date).toString());\n        assertEquals(\"2022-01-02T07:04:05.123\", Dates.zoneConvert(localDateTime, ZoneId.of(\"UTC+4\"), ZoneId.of(\"UTC+8\")).toString());\n        assertEquals(\"2022-01-02 11:04:05\", Dates.format(Dates.zoneConvert(date, ZoneId.of(\"UTC+0\"), ZoneId.of(\"UTC+8\"))));\n        assertEquals(\"2022-01-01 19:04:05\", Dates.format(Dates.zoneConvert(date, ZoneId.of(\"UTC+8\"), ZoneId.of(\"UTC+0\"))));\n\n        LocalDateTime originLocalDateTime = LocalDateTime.of(1970, 01, 01, 00, 00, 00, 0);\n        Date originDate = Dates.toDate(originLocalDateTime); // defaultZone: UTC+8\n        assertEquals(\"1970-01-01T08:00\", Dates.zoneConvert(originLocalDateTime, ZoneId.of(\"UTC+0\"), ZoneId.of(\"UTC+8\")).toString());\n        assertEquals(\"1969-12-31T16:00\", Dates.zoneConvert(originLocalDateTime, ZoneId.of(\"UTC+8\"), ZoneId.of(\"UTC+0\")).toString());\n        assertEquals(\"1970-01-01 08:00:00\", Dates.format(Dates.zoneConvert(originDate, ZoneId.of(\"UTC+0\"), ZoneId.of(\"UTC+8\"))));\n        assertEquals(\"1969-12-31 16:00:00\", Dates.format(Dates.zoneConvert(originDate, ZoneId.of(\"UTC+8\"), ZoneId.of(\"UTC+0\"))));\n\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Dates.DATETIME_PATTERN);\n        simpleDateFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of(\"UTC+0\")));\n        assertEquals(\"1969-12-31 16:00:00\", simpleDateFormat.format(originDate));\n\n        assertEquals(\"1970-01-01 08:00:00\", Dates.zoneConvert(Dates.toDate(\"1970-01-01 00:00:00\"), ZoneId.of(\"UTC+0\"), ZoneId.of(\"UTC+8\")));\n        assertEquals(\"1970-01-01 00:00:00\", Dates.zoneConvert(Dates.toDate(\"1970-01-01 08:00:00\"), ZoneId.of(\"UTC+8\"), ZoneId.of(\"UTC+0\")));\n    }\n\n    @Test\n    public void test8() {\n        LocalDateTime localDateTime = LocalDateTime.now();\n        Date date = Dates.toDate(localDateTime);\n\n        System.out.println(localDateTime.toString());\n        System.out.println(Dates.format(date).toString());\n\n        System.out.println(\"\\n-------toDate\");\n        System.out.println(Dates.toDate(localDateTime));\n        System.out.println(Dates.toDate(localDateTime));\n\n        System.out.println(\"\\n-------toLocalDateTime\");\n        System.out.println(Dates.toLocalDateTime(date));\n        System.out.println(Dates.toLocalDateTime(date));\n\n        System.out.println(\"\\n-------zoneConvert1\");\n        LocalDateTime targetLocalDateTime = Dates.zoneConvert(localDateTime, ZoneId.of(\"UTC+8\"), ZoneId.of(\"UTC+0\"));\n        Date targetDate = Dates.zoneConvert(date, ZoneId.of(\"UTC+8\"), ZoneId.of(\"UTC+0\"));\n        System.out.println(targetLocalDateTime);\n        System.out.println(targetDate);\n\n        System.out.println(\"\\n-------zoneConvert2\");\n        targetLocalDateTime = Dates.zoneConvert(localDateTime, ZoneId.of(\"UTC+0\"), ZoneId.of(\"UTC+8\"));\n        targetDate = Dates.zoneConvert(date, ZoneId.of(\"UTC+0\"), ZoneId.of(\"UTC+8\"));\n        System.out.println(targetLocalDateTime);\n        System.out.println(targetDate);\n    }\n\n    @Test\n    public void test0() throws ParseException {\n        LocalDateTime localDateTime = LocalDateTime.of(2022, 01, 02, 03, 05, 05, 123000000);\n        assertEquals(\"2022-01-02T03:05:05.123\", localDateTime.toString());\n\n        assertEquals(\"20220102030505\",          LocalDateTimeFormat.PATTERN_01.format(localDateTime));\n        assertEquals(\"2022-01-02 03:05:05\",     LocalDateTimeFormat.PATTERN_11.format(localDateTime));\n        assertEquals(\"2022/01/02 03:05:05\",     LocalDateTimeFormat.PATTERN_12.format(localDateTime));\n        assertEquals(\"2022-01-02T03:05:05\",     LocalDateTimeFormat.PATTERN_13.format(localDateTime));\n        assertEquals(\"2022/01/02T03:05:05\",     LocalDateTimeFormat.PATTERN_14.format(localDateTime));\n        assertEquals(\"2022-01-02 03:05:05.123\", LocalDateTimeFormat.PATTERN_21.format(localDateTime));\n        assertEquals(\"2022/01/02 03:05:05.123\", LocalDateTimeFormat.PATTERN_22.format(localDateTime));\n        assertEquals(\"2022-01-02T03:05:05.123\", LocalDateTimeFormat.PATTERN_23.format(localDateTime));\n        assertEquals(\"2022/01/02T03:05:05.123\", LocalDateTimeFormat.PATTERN_24.format(localDateTime));\n\n        assertEquals(\"{},ISO resolved to 2022-01-02T03:05:05\", LocalDateTimeFormat.PATTERN_01.parse(\"20220102030505\").toString());\n        assertEquals(\"{},ISO resolved to 2022-01-02T03:05:05\", LocalDateTimeFormat.PATTERN_11.parse(\"2022-01-02 03:05:05\").toString());\n        assertEquals(\"{},ISO resolved to 2022-01-02T03:05:05\", LocalDateTimeFormat.PATTERN_12.parse(\"2022/01/02 03:05:05\").toString());\n        assertEquals(\"{},ISO resolved to 2022-01-02T03:05:05\", LocalDateTimeFormat.PATTERN_13.parse(\"2022-01-02T03:05:05\").toString());\n        assertEquals(\"{},ISO resolved to 2022-01-02T03:05:05\", LocalDateTimeFormat.PATTERN_14.parse(\"2022/01/02T03:05:05\").toString());\n        assertEquals(\"{},ISO resolved to 2022-01-02T03:05:05.123\", LocalDateTimeFormat.PATTERN_21.parse(\"2022-01-02 03:05:05.123\").toString());\n        assertEquals(\"{},ISO resolved to 2022-01-02T03:05:05.123\", LocalDateTimeFormat.PATTERN_22.parse(\"2022/01/02 03:05:05.123\").toString());\n        assertEquals(\"{},ISO resolved to 2022-01-02T03:05:05.123\", LocalDateTimeFormat.PATTERN_23.parse(\"2022-01-02T03:05:05.123\").toString());\n        assertEquals(\"{},ISO resolved to 2022-01-02T03:05:05.123\", LocalDateTimeFormat.PATTERN_24.parse(\"2022/01/02T03:05:05.123\").toString());\n\n\n        assertEquals(\"2022-01-02T03:05:05\", LocalDateTimeFormat.DEFAULT.parse(\"20220102030505\").toString());\n        assertEquals(\"2022-01-02T03:05:05\", LocalDateTimeFormat.DEFAULT.parse(\"2022-01-02 03:05:05\").toString());\n        assertEquals(\"2022-01-02T03:05:05\", LocalDateTimeFormat.DEFAULT.parse(\"2022/01/02 03:05:05\").toString());\n        assertEquals(\"2022-01-02T03:05:05\", LocalDateTimeFormat.DEFAULT.parse(\"2022-01-02T03:05:05\").toString());\n        assertEquals(\"2022-01-02T03:05:05\", LocalDateTimeFormat.DEFAULT.parse(\"2022/01/02T03:05:05\").toString());\n        assertEquals(\"2022-01-02T03:05:05.123\", LocalDateTimeFormat.DEFAULT.parse(\"2022-01-02 03:05:05.123\").toString());\n        assertEquals(\"2022-01-02T03:05:05.123\", LocalDateTimeFormat.DEFAULT.parse(\"2022/01/02 03:05:05.123\").toString());\n        assertEquals(\"2022-01-02T03:05:05.123\", LocalDateTimeFormat.DEFAULT.parse(\"2022-01-02T03:05:05.123\").toString());\n        assertEquals(\"2022-01-02T03:05:05.123\", LocalDateTimeFormat.DEFAULT.parse(\"2022/01/02T03:05:05.123\").toString());\n\n\n        assertEquals(\"2022-01-02T03:05:05.123\", LocalDateTimeFormat.DEFAULT.parse(\"2022-01-02T03:05:05.123Z\").toString());\n        assertEquals(\"2022-01-02T03:05:05.123\", LocalDateTimeFormat.DEFAULT.parse(\"2022/01/02T03:05:05.123Z\").toString());\n\n        Date date = Dates.toDate(localDateTime);\n        assertEquals(\"Sun Jan 02 03:05:05 CST 2022\", date.toString());\n        assertEquals(\"2022-01-02T03:05:05.123\", LocalDateTimeFormat.DEFAULT.parse(String.valueOf(date.getTime())).toString());\n        assertEquals(\"2022-01-02T03:05:05\", LocalDateTimeFormat.DEFAULT.parse(String.valueOf(date.getTime() / 1000)).toString());\n    }\n\n    @Test\n    public void test1() {\n        String dateString = \"2022-07-19T13:44:27.873\";\n        LocalDateTime date = LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME);\n        LocalDateTimeFormat format = LocalDateTimeFormat.DEFAULT;\n        assertEquals(dateString, date.toString());\n\n        assertEquals(\"20220719134427\", LocalDateTimeFormat.PATTERN_01.format(date));\n        assertEquals(\"2022-07-19 13:44:27\", LocalDateTimeFormat.PATTERN_11.format(date));\n        assertEquals(\"2022/07/19 13:44:27\", LocalDateTimeFormat.PATTERN_12.format(date));\n        assertEquals(\"2022-07-19T13:44:27\", LocalDateTimeFormat.PATTERN_13.format(date));\n        assertEquals(\"2022/07/19T13:44:27\", LocalDateTimeFormat.PATTERN_14.format(date));\n        assertEquals(\"2022-07-19 13:44:27.873\", LocalDateTimeFormat.PATTERN_21.format(date));\n        assertEquals(\"2022/07/19 13:44:27.873\", LocalDateTimeFormat.PATTERN_22.format(date));\n        assertEquals(\"2022-07-19T13:44:27.873\", LocalDateTimeFormat.PATTERN_23.format(date));\n        assertEquals(\"2022/07/19T13:44:27.873\", LocalDateTimeFormat.PATTERN_24.format(date));\n\n        assertEquals(\"{},ISO resolved to 2022-07-19T13:44:27\", LocalDateTimeFormat.PATTERN_01.parse(LocalDateTimeFormat.PATTERN_01.format(date)).toString());\n        assertEquals(\"{},ISO resolved to 2022-07-19T13:44:27\", LocalDateTimeFormat.PATTERN_11.parse(LocalDateTimeFormat.PATTERN_11.format(date)).toString());\n        assertEquals(\"{},ISO resolved to 2022-07-19T13:44:27\", LocalDateTimeFormat.PATTERN_12.parse(LocalDateTimeFormat.PATTERN_12.format(date)).toString());\n        assertEquals(\"{},ISO resolved to 2022-07-19T13:44:27\", LocalDateTimeFormat.PATTERN_13.parse(LocalDateTimeFormat.PATTERN_13.format(date)).toString());\n        assertEquals(\"{},ISO resolved to 2022-07-19T13:44:27\", LocalDateTimeFormat.PATTERN_14.parse(LocalDateTimeFormat.PATTERN_14.format(date)).toString());\n        assertEquals(\"{},ISO resolved to 2022-07-19T13:44:27.873\", LocalDateTimeFormat.PATTERN_21.parse(LocalDateTimeFormat.PATTERN_21.format(date)).toString());\n        assertEquals(\"{},ISO resolved to 2022-07-19T13:44:27.873\", LocalDateTimeFormat.PATTERN_22.parse(LocalDateTimeFormat.PATTERN_22.format(date)).toString());\n        assertEquals(\"{},ISO resolved to 2022-07-19T13:44:27.873\", LocalDateTimeFormat.PATTERN_23.parse(LocalDateTimeFormat.PATTERN_23.format(date)).toString());\n        assertEquals(\"{},ISO resolved to 2022-07-19T13:44:27.873\", LocalDateTimeFormat.PATTERN_24.parse(LocalDateTimeFormat.PATTERN_24.format(date)).toString());\n\n        assertEquals(\"2022-07-19T13:44:27\", format.parse(LocalDateTimeFormat.PATTERN_01.format(date)).toString());\n        assertEquals(\"2022-07-19T13:44:27\", format.parse(LocalDateTimeFormat.PATTERN_11.format(date)).toString());\n        assertEquals(\"2022-07-19T13:44:27\", format.parse(LocalDateTimeFormat.PATTERN_12.format(date)).toString());\n        assertEquals(\"2022-07-19T13:44:27\", format.parse(LocalDateTimeFormat.PATTERN_13.format(date)).toString());\n        assertEquals(\"2022-07-19T13:44:27\", format.parse(LocalDateTimeFormat.PATTERN_14.format(date)).toString());\n        assertEquals(\"2022-07-19T13:44:27.873\", format.parse(LocalDateTimeFormat.PATTERN_21.format(date)).toString());\n        assertEquals(\"2022-07-19T13:44:27.873\", format.parse(LocalDateTimeFormat.PATTERN_22.format(date)).toString());\n        assertEquals(\"2022-07-19T13:44:27.873\", format.parse(LocalDateTimeFormat.PATTERN_23.format(date)).toString());\n        assertEquals(\"2022-07-19T13:44:27.873\", format.parse(LocalDateTimeFormat.PATTERN_24.format(date)).toString());\n    }\n\n    @Test\n    public void test2() throws ParseException {\n        LocalDateTimeFormat format = LocalDateTimeFormat.DEFAULT;\n        assertEquals(\"2022-07-18T00:00\", format.parse(\"20220718\").toString());\n        assertEquals(\"2022-07-18T00:00\", format.parse(\"2022-07-18\").toString());\n        assertEquals(\"2022-07-18T00:00\", format.parse(\"2022/07/18\").toString());\n\n        assertEquals(\"2022-07-18T15:45:59\", format.parse(\"20220718154559\").toString());\n        assertEquals(\"2022-07-18T15:45:59\", format.parse(\"2022-07-18 15:45:59\").toString());\n        assertEquals(\"2022-07-18T15:45:59\", format.parse(\"2022/07/18 15:45:59\").toString());\n\n        assertEquals(\"2022-07-18T15:45:59\", format.parse(\"2022-07-18T15:45:59\").toString());\n        assertEquals(\"2022-07-18T15:45:59\", format.parse(\"2022/07/18T15:45:59\").toString());\n\n        assertEquals(\"2022-07-18T15:45:59.414\", format.parse(\"2022-07-18 15:45:59.414\").toString());\n        assertEquals(\"2022-07-18T15:45:59.414\", format.parse(\"2022/07/18 15:45:59.414\").toString());\n\n        assertEquals(\"2022-07-18T15:45:59.414\", format.parse(\"2022-07-18T15:45:59.414\").toString());\n        assertEquals(\"2022-07-18T15:45:59.414\", format.parse(\"2022/07/18T15:45:59.414\").toString());\n\n        assertEquals(\"2022-07-18T15:45:59\", format.parse(\"1658130359\").toString());\n        assertEquals(\"2022-07-18T15:45:59\", format.parse(\"1658130359000\").toString());\n        assertEquals(\"2001-09-10T21:59:19\", format.parse(\"1000130359\").toString());\n        assertEquals(\"2001-09-10T21:59:19\", format.parse(\"1000130359000\").toString());\n\n        assertEquals(\"2022-07-18T15:11:11\", format.parse(\"2022-07-18T15:11:11Z\").toString());\n        assertEquals(\"2022-07-18T15:11:11\", format.parse(\"2022-07-18T15:11:11.Z\").toString());\n        assertEquals(\"2022-07-18T15:11:11.100\", format.parse(\"2022-07-18T15:11:11.1Z\").toString());\n        assertEquals(\"2022-07-18T15:11:11.130\", format.parse(\"2022-07-18T15:11:11.13Z\").toString());\n        assertEquals(\"2022-07-18T15:11:11.133\", format.parse(\"2022-07-18T15:11:11.133Z\").toString());\n\n        assertEquals(\"2022-07-18T15:11:11\", format.parse(\"2022/07/18T15:11:11Z\").toString());\n        assertEquals(\"2022-07-18T15:11:11\", format.parse(\"2022/07/18T15:11:11.Z\").toString());\n        assertEquals(\"2022-07-18T15:11:11.100\", format.parse(\"2022/07/18T15:11:11.1Z\").toString());\n        assertEquals(\"2022-07-18T15:11:11.130\", format.parse(\"2022/07/18T15:11:11.13Z\").toString());\n        assertEquals(\"2022-07-18T15:11:11.133\", format.parse(\"2022/07/18T15:11:11.133Z\").toString());\n\n        assertThrows(Exception.class, () -> format.parse(\"2022-07-18T1:1:1Z\"));\n\n    }\n\n    @Test\n    public void test3() {\n        String string1 = \"2022-07-18T15:11:11.133\";\n        assertEquals(\"2022-07-18T15:11:11.133\", LocalDateTime.parse(string1, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toString());\n        String string2 = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.parse(string1, DateTimeFormatter.ISO_LOCAL_DATE_TIME));\n        assertEquals(string1, string2);\n\n        String dateString = \"2022-07-18T15:11:11.133Z\";\n        LocalDateTime date = LocalDateTime.ofInstant(Instant.parse(dateString), ZoneOffset.UTC);\n        assertEquals(date.toString(), \"2022-07-18T15:11:11.133\");\n\n        date = LocalDateTime.ofInstant(Instant.parse(dateString), ZoneOffset.ofHours(8));\n        assertEquals(date.toString(), \"2022-07-18T23:11:11.133\");\n\n        assertThrows(\n            DateTimeParseException.class,\n            () -> LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss.SSSZ\"))\n        );\n    }\n\n    @Test\n    public void test4() {\n        String text = \"2022/07/18T15:11:11.133Z\";\n        //System.out.println(JavaTimeDateFormat.DEFAULT.parse(text));\n        //LocalDateTime.parse(text, DateTimeFormatter.ofPattern(\"yyyy/MM/dd'T'HH:mm:ss.SSSZ\"));\n        //LocalDateTime.ofInstant(DateTimeFormatter.ofPattern(\"yyyy/MM/dd'T'HH:mm:ss.SSSZ\").parse(text, Instant::from), ZoneOffset.UTC);\n\n        /*\n        DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()\n            .parseCaseInsensitive()\n            .append(DateTimeFormatter.ofPattern(\"yyyy/MM/dd\"))\n            .appendLiteral('T')\n            .append(DateTimeFormatter.ISO_LOCAL_TIME)\n            .appendInstant()\n            .toFormatter();\n        */\n\n        LocalDateTimeFormat format = LocalDateTimeFormat.DEFAULT;\n        /*System.out.println(format.parse(\"2022/07/18T15:11:11.1Z\").toString());\n        System.out.println(format.parse(\"2022/07/18T15:11:11Z\").toString());\n        System.out.println(format.parse(\"2022/07/18T15:11:11.13Z\").toString());\n        System.out.println(format.parse(\"2022/07/18T15:11:11.133Z\").toString());*/\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/event/EventBusTest.java",
    "content": "package cn.ponfee.commons.event;\n\nimport com.google.common.eventbus.EventBus;\nimport com.google.common.eventbus.Subscribe;\n\npublic class EventBusTest {\n\n    public static class OrderEvent {\n        private String message;\n\n        public OrderEvent(String message) {\n            this.message = message;\n        }\n\n        public String getMessage() {\n            return message;\n        }\n    }\n\n    public static class OrderEventListener {\n        @Subscribe\n        public void listen(OrderEvent event) {\n            System.out.println(\"OrderEventListener receive msg: \" + event.getMessage());\n        }\n\n        @Subscribe\n        public void listen(String event) {\n            System.out.println(\"OrderEventListener receive msg: \" + event);\n        }\n    }\n\n    public static void main(String[] args) {\n        /*\n         * 通过EventBus.register(Object object)方法来注册订阅者（subscriber），\n         * 使用EventBus.post(Object event)方法来发布事件。\n         */\n        //1.Creates a new EventBus with the given identifier.\n        EventBus eventBus = new EventBus(\"jackson\");\n\n        //2.register all subscriber\n        eventBus.register(new OrderEventListener());\n        //eventBus.register(new HelloListener());\n\n        //publish event\n        eventBus.post(new OrderEvent(\"order-event-message\"));\n        eventBus.post(\"string-event-message\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/exception/ThrowablesTest.java",
    "content": "package cn.ponfee.commons.exception;\n\nimport cn.ponfee.commons.exception.Throwables.ThrowingConsumer;\nimport cn.ponfee.commons.exception.Throwables.ThrowingFunction;\nimport cn.ponfee.commons.exception.Throwables.ThrowingRunnable;\nimport cn.ponfee.commons.exception.Throwables.ThrowingSupplier;\nimport cn.ponfee.commons.util.ImageUtils;\nimport org.junit.Test;\n\nimport java.io.FileInputStream;\n\n/**\n * @author Ponfee\n */\npublic class ThrowablesTest {\n\n    @Test\n    public void test() {\n        ThrowingRunnable.doCaught(ThrowablesTest::get0);\n        System.out.println(\"---------------\\n\");\n\n        ThrowingRunnable.doCaught(ThrowablesTest::get1);\n        System.out.println(\"---------------\\n\");\n\n        String caught = ThrowingSupplier.doCaught(ThrowablesTest::get2);\n        System.out.println(\"---------------\" + caught + \"\\n\");\n\n        ThrowingConsumer.doCaught(ThrowablesTest::get3, \"xxx\");\n        System.out.println(\"---------------\\n\");\n\n        String yyy = ThrowingFunction.doCaught(ThrowablesTest::get4, \"yyy\");\n        System.out.println(\"---------------\" + yyy + \"\\n\");\n    }\n\n    public static void get0() {\n        System.out.println(\"get0\");\n        int i = 1 / 0;\n    }\n\n    public static void get1() throws Throwable {\n        System.out.println(\"get1\");\n        ImageUtils.getImageType(new FileInputStream(\"\"));\n    }\n\n    public static String get2() throws Throwable {\n        System.out.println(\"get2\");\n        ImageUtils.getImageType(new FileInputStream(\"\"));\n        return \"\";\n    }\n\n    public static void get3(String a) throws Throwable {\n        System.out.println(\"get3:\" + a);\n        ImageUtils.getImageType(new FileInputStream(\"\"));\n    }\n\n    public static String get4(String a) throws Throwable {\n        System.out.println(\"get4:\" + a);\n        ImageUtils.getImageType(new FileInputStream(\"\"));\n        return a;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/innerclass/MyInterface.java",
    "content": "package cn.ponfee.commons.innerclass;\n\npublic interface MyInterface {\n    void doSomething();\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/innerclass/TryUsingAnonymousClass.java",
    "content": "package cn.ponfee.commons.innerclass;\n\npublic class TryUsingAnonymousClass {\n\n    public static void main(String[] args) {\n        new TryUsingAnonymousClass().useMyInterface();\n    }\n\n    public void useMyInterface() {\n        final Integer number = 123;\n        System.out.println(number);\n\n        MyInterface myInterface = new MyInterface() {\n            @Override\n            public void doSomething() {\n                System.out.println(number);\n            }\n        };\n        myInterface.doSomething();\n\n        System.out.println(number);\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/BeforeReadInputStreamTest.java",
    "content": "package cn.ponfee.commons.io;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport org.apache.commons.io.IOUtils;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class BeforeReadInputStreamTest {\n\n    @Test\n    public void test() throws IOException {\n        File f = MavenProjects.getTestJavaFile(BeforeReadInputStreamTest.class);\n        //System.out.println(Files.toString(f));\n        byte[] fb, bb;\n\n        /*BeforeReadInputStream binput = new BeforeReadInputStream(new FileInputStream(f), 200);\n        System.out.println(new String(binput.getArray()).replaceAll(\"\\n|\\r\\n\", \"\"));\n        fb = IOUtils.toByteArray(new FileInputStream(f));\n        bb = IOUtils.toByteArray(binput);\n        Assert.assertArrayEquals(fb, bb);*/\n\n        for (int i = 1, n = (int) f.length() + 500; i < n; i += 7) {\n            try (InputStream input1 = new FileInputStream(f);\n                 InputStream input2 = new PrereadInputStream(new FileInputStream(f), i)\n            ){\n                fb = IOUtils.toByteArray(input1);\n                bb = IOUtils.toByteArray(input2);\n                Assert.assertArrayEquals(fb, bb);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/CopyrightTest.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.io;\n\nimport cn.ponfee.commons.exception.Throwables.*;\nimport cn.ponfee.commons.util.MavenProjects;\nimport cn.ponfee.commons.util.Strings;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.junit.Test;\n\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.function.Consumer;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\n/**\n * 添加copyright\n *\n * @author Ponfee\n */\npublic class CopyrightTest {\n\n    private static final String baseDir = MavenProjects.getMainJavaPath(\"\");\n    private static final String copyright = ThrowingSupplier.doChecked(\n        () -> IOUtils.resourceToString(\"copy-right.txt\", UTF_8, CopyrightTest.class.getClassLoader())\n    );\n\n    @Test\n    public void upsertCopyright() {\n        handleFile(file -> {\n            String text = ThrowingSupplier.doChecked(() -> IOUtils.toString(file.toURI(), UTF_8));\n            if (!isOwnerCode(text)) {\n                return;\n            }\n            try {\n                if (!text.contains(\"** \\\\______   \\\\____   _____/ ____\\\\____   ____    Copyright (c) 2017-20\")) {\n                    Writer writer = new FileWriter(file.getAbsolutePath());\n                    IOUtils.write(copyright, writer);\n                    IOUtils.write(text, writer);\n                    writer.flush();\n                    writer.close();\n                    return;\n                }\n\n                if (!text.contains(\"** \\\\______   \\\\____   _____/ ____\\\\____   ____    Copyright (c) 2017-2023 Ponfee  **\")) {\n                    Writer writer = new FileWriter(file.getAbsolutePath());\n                    IOUtils.write(copyright, writer);\n                    IOUtils.write(text.substring(84 * 7 + 1), writer);\n                    writer.flush();\n                    writer.close();\n                    return;\n                }\n            } catch (IOException e) {\n                ExceptionUtils.rethrow(e);\n            }\n        });\n    }\n\n    @Test\n    public void checkCopyright() {\n        handleFile(file -> {\n            String text = ThrowingSupplier.doChecked(() -> IOUtils.toString(file.toURI(), UTF_8));\n            if (Strings.count(text, \" @author \") == 0) {\n                System.out.println(file.getName());\n            } else if (isOwnerCode(text)) {\n                // 自己编写的代码，需要加Copyright\n                if (!text.contains(\" Copyright (c) 2017-2023 Ponfee \")) {\n                    System.out.println(file.getName());\n                }\n            } else {\n                // 引用他人的代码，不用加Copyright\n                if (text.contains(\" Copyright (c) 2017-2023 Ponfee \")) {\n                    System.out.println(file.getName());\n                }\n            }\n        });\n    }\n\n    private static void handleFile(Consumer<File> consumer) {\n        FileUtils\n            .listFiles(new File(baseDir).getParentFile(), new String[]{\"java\"}, true)\n            .forEach(e -> ThrowingRunnable.doChecked(() -> consumer.accept(e)));\n    }\n\n    private boolean isOwnerCode(String sourceCode) {\n        if (sourceCode.contains(\"public class \" + getClass().getSimpleName() + \" {\\n\")) {\n            return true;\n        }\n        return sourceCode.contains(\" * @author Ponfee\\n\") && Strings.count(sourceCode, \" @author \") == 1;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/FileTransformerTest.java",
    "content": "package cn.ponfee.commons.io;\n\nimport org.junit.Test;\n\npublic class FileTransformerTest {\n\n    @Test\n    public void test() {\n        //System.out.println(detectBytesCharset(Streams.file2bytes(\"D:\\\\test\\\\2.png\")));\n        //System.out.println(detectBytesCharset(Streams.file2bytes(\"D:\\\\test\\\\lib\\\\cache\\\\Cache.java\")));\n\n        /*FileTransformer transformer = new FileTransformer(MavenProjects.getMainJavaPath(\"\"), \"/Users/ponfee/test\", \"GBK\");\n        transformer.transform();\n        System.out.println(transformer.getTransformLog());*/\n\n        FileTransformer t = new FileTransformer(\"/Users/ponfee/scm/github/commons-core\", \"/Users/ponfee/scm/github/test111/commons-core\");\n        t.setReplaceEach(new String[]{\"cn.ponfee.commons.\"}, new String[]{\"cn.ponfee.commons.\"});\n        t.transform();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/FileTypeDetector.java",
    "content": "package cn.ponfee.commons.io;\n\nimport cn.ponfee.commons.util.MavenProjects;\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.commons.codec.binary.Hex;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.tika.Tika;\nimport org.apache.tika.metadata.Metadata;\nimport org.apache.tika.parser.AutoDetectParser;\nimport org.apache.tika.parser.ParseContext;\nimport org.xml.sax.helpers.DefaultHandler;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class FileTypeDetector {\n\n    // -----------------------------------------------------------------file type\n    private static final int SUB_PREFIX = 64;\n    public static final Map<String, String> FILE_TYPE_MAGIC = ImmutableMap.<String, String> builder()\n            .put(\"jpg\", \"FFD8FF\") // JPEG (jpg)\n            .put(\"png\", \"89504E47\") // PNG (png)\n            .put(\"gif\", \"47494638\") // GIF (gif)\n            .put(\"tif\", \"49492A00\") // TIFF (tif)\n            .put(\"bmp\", \"424D\") // Windows Bitmap (bmp)\n            .put(\"dwg\", \"41433130\") // CAD (dwg)\n            .put(\"html\",\"68746D6C3E\") // HTML (html)\n            .put(\"rtf\", \"7B5C727466\") // Rich Text Format (rtf)\n            .put(\"xml\", \"3C3F786D6C\")\n            .put(\"zip\", \"504B0304\")\n            .put(\"rar\", \"52617221\")\n            .put(\"psd\", \"38425053\") // Photoshop (psd)\n            .put(\"eml\", \"44656C69766572792D646174653A\") // Email [thorough only] (eml)\n            .put(\"dbx\", \"CFAD12FEC5FD746F\") // Outlook Express (dbx)\n            .put(\"pst\", \"2142444E\") // Outlook (pst)\n            .put(\"xls\", \"D0CF11E0\") // MS Word\n            .put(\"doc\", \"D0CF11E0\") // MS Excel 注意：word 和 excel的文件头一样\n            .put(\"mdb\", \"5374616E64617264204A\") // MS Access (mdb)\n            .put(\"wpd\", \"FF575043\") // WordPerfect (wpd)\n            .put(\"eps\", \"252150532D41646F6265\")\n            .put(\"ps\",  \"252150532D41646F6265\")\n            .put(\"pdf\", \"255044462D312E\") // Adobe Acrobat (pdf)\n            .put(\"qdf\", \"AC9EBD8F\") // Quicken (qdf)\n            .put(\"pwl\", \"E3828596\") // Windows Password (pwl)\n            .put(\"wav\", \"57415645\") // Wave (wav)\n            .put(\"avi\", \"41564920\")\n            .put(\"ram\", \"2E7261FD\") // Real Audio (ram)\n            .put(\"rm\", \"2E524D46\") // Real Media (rm)\n            .put(\"mpg\", \"000001BA\")\n            .put(\"mov\", \"6D6F6F76\") // Quicktime (mov)\n            .put(\"asf\", \"3026B2758E66CF11\") // Windows Media (asf)\n            .put(\"mid\", \"4D546864\") // MIDI (mid)\n            .build();\n\n    /**\n     * 探测文件类型\n     *\n     * @param file\n     * @return\n     * @throws IOException\n     */\n    public static String detectFileType(File file) throws IOException {\n        return detectFileType(Files.readByteArray(file, SUB_PREFIX));\n    }\n\n    public static String detectFileType(byte[] array) {\n        if (array.length > SUB_PREFIX) {\n            array = ArrayUtils.subarray(array, 0, SUB_PREFIX);\n        }\n\n        String hex = Hex.encodeHexString(array, false);\n        for (Map.Entry<String, String> entry : FILE_TYPE_MAGIC.entrySet()) {\n            if (hex.startsWith(entry.getValue())) {\n                return entry.getKey();\n            }\n        }\n       return null;\n    }\n\n    public static void main(String[] args) throws IOException {\n        File file = MavenProjects.getTestJavaFile(FileTypeDetector.class);\n\n        // 1\n        Tika tika1 = new Tika();\n        String fileType = tika1.detect(file);\n        System.out.println(fileType);\n\n        // 2\n        AutoDetectParser parser1 = new AutoDetectParser();\n        parser1.setParsers(new HashMap<>());\n        Metadata metadata1 = new Metadata();\n        //metadata.add(TikaMetadataKeys.RESOURCE_NAME_KEY, file.getName());\n        try (InputStream stream = new FileInputStream(file)) {\n            parser1.parse(stream, new DefaultHandler(), metadata1, new ParseContext());\n            System.out.println(metadata1);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        // 3\n        Tika tika2 = new Tika();\n        Metadata metadata2 = new Metadata();\n        tika2.parse(new FileInputStream(file), metadata2);\n        System.out.println(metadata2);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/FilesTest.java",
    "content": "package cn.ponfee.commons.io;\n\nimport cn.ponfee.commons.extract.DataExtractorBuilder;\nimport cn.ponfee.commons.io.charset.BytesDetector;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.spring.SpringContextHolder;\nimport cn.ponfee.commons.tree.TreeNode;\nimport cn.ponfee.commons.util.MavenProjects;\nimport com.google.common.collect.Lists;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.csv.CSVFormat;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.Test;\nimport org.openjdk.jol.info.ClassLayout;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic class FilesTest {\n\n    @Test\n    public void testx() {\n        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());\n        System.out.println(\"\\n-----------------------\");\n        System.out.println(ClassLayout.parseInstance(new Object[10]).toPrintable());\n        System.out.println(\"\\n-----------------------\");\n        System.out.println(ClassLayout.parseInstance(new long[10]).toPrintable());\n        System.out.println(\"\\n-----------------------\");\n\n        System.out.println(CharsetDetector.detect(MavenProjects.getMainJavaFile(TreeNode.class)));\n    }\n\n    @Test\n    public void test1() throws MalformedURLException {\n        System.out.println(\"GBK.properties -> \"+ CharsetDetector.detect(\"D:/temp/GBK.properties\"));\n        System.out.println(\"UTF8.txt -> \"+ CharsetDetector.detect(\"D:/temp/UTF8.txt\"));\n        System.out.println(\"UTF8-WITH-BOM.xml -> \"+ CharsetDetector.detect(\"D:/temp/UTF8-WITH-BOM.xml\"));\n        System.out.println(\"UTF8-WITHOUT-BOM.xml -> \"+ CharsetDetector.detect(\"D:/temp/UTF8-WITHOUT-BOM.xml\"));\n        System.out.println(\"UTF16-BIG-ENDIAN-WITH-BOM.xml -> \"+ CharsetDetector.detect(\"D:/temp/UTF16-BIG-ENDIAN-WITH-BOM.xml\"));\n        System.out.println(\"UTF16-BIG-ENDIAN-WITHOUT-BOM.xml -> \"+ CharsetDetector.detect(\"D:/temp/UTF16-BIG-ENDIAN-WITHOUT-BOM.xml\"));\n        System.out.println(\"UTF16-WITH-BOM.xml -> \"+ CharsetDetector.detect(\"D:/temp/UTF16-WITH-BOM.xml\"));\n        System.out.println(\"UTF16-WITHOUT-BOM.xml -> \"+ CharsetDetector.detect(\"D:/temp/UTF16-WITHOUT-BOM.xml\"));\n    }\n\n    @Test\n    public void test0() throws IOException {\n        System.out.println(ByteOrderMarks.has(\"D:\\\\temp\\\\withbom\\\\csv-gbk-bom.csv\"));\n        System.out.println(ByteOrderMarks.has(\"D:\\\\temp\\\\withbom\\\\csv-utf8-bom.csv\"));\n        System.out.println(ByteOrderMarks.has(\"D:\\\\temp\\\\withbom\\\\csv-utf16le-bom.csv\"));\n        System.out.println(ByteOrderMarks.has(\"D:\\\\temp\\\\withbom\\\\csv-utf16be-bom.csv\"));\n        System.out.println(ByteOrderMarks.has(\"D:\\\\temp\\\\withbom\\\\csv-unicode-ascii-escaped-bom.csv\"));\n        System.out.println(ByteOrderMarks.has(\"D:\\\\temp\\\\withbom\\\\csv-ansi-ascii-bom.csv\"));\n    }\n\n    @Test\n    public void test2() throws IOException {\n        /*System.out.println(WindowsBOM.add(\"D:\\\\temp\\\\withbom\\\\csv-gbk-bom.csv\"));\n        System.out.println(WindowsBOM.add(\"D:\\\\temp\\\\withbom\\\\csv-utf8-bom.csv\"));\n        System.out.println(WindowsBOM.add(\"D:\\\\temp\\\\withbom\\\\csv-utf16le-bom.csv\"));\n        System.out.println(WindowsBOM.add(\"D:\\\\temp\\\\withbom\\\\csv-utf16be-bom.csv\"));\n        System.out.println(WindowsBOM.add(\"D:\\\\temp\\\\withbom\\\\csv-unicode-ascii-escaped-bom.csv\"));\n        System.out.println(WindowsBOM.add(\"D:\\\\temp\\\\withbom\\\\csv-ansi-ascii-bom.csv\"));*/\n        \n        System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_8, \"D:\\\\temp\\\\withoutbom\\\\test-utf8-bom.csv\"));\n        System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_16LE, \"D:\\\\temp\\\\withoutbom\\\\test-utf16le-bom.csv\"));\n        System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_16BE, \"D:\\\\temp\\\\withoutbom\\\\test-utf16be-bom.csv\"));\n    }\n\n    @Test\n    public void test3() throws IOException {\n        System.out.println(ByteOrderMarks.remove(\"D:\\\\temp\\\\withbom\\\\csv-gbk-bom.csv\"));\n        System.out.println(ByteOrderMarks.remove(\"D:\\\\temp\\\\withbom\\\\csv-utf8-bom.csv\"));\n        System.out.println(ByteOrderMarks.remove(\"D:\\\\temp\\\\withbom\\\\csv-utf16le-bom.csv\"));\n        System.out.println(ByteOrderMarks.remove(\"D:\\\\temp\\\\withbom\\\\csv-utf16be-bom.csv\"));\n        System.out.println(ByteOrderMarks.remove(\"D:\\\\temp\\\\withbom\\\\csv-unicode-ascii-escaped-bom.csv\"));\n        System.out.println(ByteOrderMarks.remove(\"D:\\\\temp\\\\withbom\\\\csv-ansi-ascii-bom.csv\"));\n    }\n\n    @Test\n    public void test4() throws IOException {\n        String[] files = { \n            \"D:\\\\temp\\\\withoutbom\\\\csv-gbk.csv\", \n            \"D:\\\\temp\\\\withbom\\\\csv-gbk-bom.csv\", \n\n            \"D:\\\\temp\\\\withoutbom\\\\csv-utf8.csv\", \n            \"D:\\\\temp\\\\withbom\\\\csv-utf8-bom.csv\", \n\n            \"D:\\\\temp\\\\withoutbom\\\\csv-utf16le.csv\", \n            \"D:\\\\temp\\\\withbom\\\\csv-utf16le-bom.csv\", \n\n            \"D:\\\\temp\\\\withoutbom\\\\csv-utf16be.csv\", \n            \"D:\\\\temp\\\\withbom\\\\csv-utf16be-bom.csv\", \n\n            \"D:\\\\temp\\\\withoutbom\\\\csv-unicode-ascii-escaped.csv\", \n            \"D:\\\\temp\\\\withbom\\\\csv-unicode-ascii-escaped-bom.csv\", \n\n            \"D:\\\\temp\\\\withoutbom\\\\csv-ansi-ascii.csv\", \n            \"D:\\\\temp\\\\withbom\\\\csv-ansi-ascii-bom.csv\", \n        };\n        for (String file : files) {\n            Charset charset = CharsetDetector.detect(file);\n            System.out.println(\"\\n=============================\"+file+\" -> \"+\", \"+charset+\"   \"+Files.toString(new File(file)).replaceAll(\"\\r\\n|\\n\", \";\"));\n            DataExtractorBuilder builder = DataExtractorBuilder.newBuilder(file);\n            builder.build().extract(100).stream().forEach(row -> System.out.println(Jsons.toJson(row)));\n        }\n    }\n    \n    @Test\n    public void test5() throws IOException {\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\c24aafd4f3f24c2a86734b20f9a0edd3.Adobe_Fireworks_CS6_XiaZaiBa.exe\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\csv-gbk.csv\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\csv-gbk-bom.csv\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\csv-utf8.csv\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\csv-utf8-bom.csv\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\csv-utf16.csv\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\csv-utf16-bom.csv\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\2.png\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\IMG_2485.JPG\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\ca.pfx\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\signers.xml\"));\n    }\n    \n    @Test\n    public void test6() throws IOException {\n        System.out.println(Charset.forName(\"utf-8\") == StandardCharsets.UTF_8);\n        System.out.println(Charset.forName(\"utf8\") == StandardCharsets.UTF_8);\n        System.out.println(Charset.forName(\"utf-16\") == StandardCharsets.UTF_16);\n    }\n\n    @Test\n    public void test7() throws IOException {\n        String[] files = { \n            \"D:\\\\temp\\\\withoutbom\\\\test-utf8-bom.csv\", \n\n            \"D:\\\\temp\\\\withoutbom\\\\test-utf16le-bom.csv\", \n\n            \"D:\\\\temp\\\\withoutbom\\\\test-utf16be-bom.csv\", \n        };\n        CSVFormat format = CSVFormat.DEFAULT.withDelimiter(',').withQuote('\"');\n        for (String file : files) {\n            Charset charset = CharsetDetector.detect(file);\n            System.out.println(\"\\n=============================\"+file+\" -> \"+\", \"+charset+\"   \"+Files.toString(new File(file)).substring(1, 1000).replaceAll(\"\\r\\n|\\n\", \";\"));\n            DataExtractorBuilder builder = DataExtractorBuilder.newBuilder(file).csvFormat(format).startRow(1);\n            builder.build().extract(2).stream().forEach(row -> System.out.println(Jsons.toJson(row)));\n        }\n    }\n\n    @Test\n    public void test() throws IOException {\n        String file;\n        DataExtractorBuilder builder;\n        CSVFormat format = CSVFormat.DEFAULT.withDelimiter(',').withQuote('\"');\n\n        /*file =  \"D:\\\\temp\\\\withoutbom\\\\test-utf8.csv\";\n        builder = DataExtractorBuilder.newBuilder(file).csvFormat(format);\n        builder.build().extract(100).stream().forEach(row -> System.out.println(Jsons.toJson(row)));*/\n\n        /*file =  \"D:\\\\temp\\\\withoutbom\\\\test-utf16le.csv\";\n        builder = DataExtractorBuilder.newBuilder(file).csvFormat(format).charset(StandardCharsets.UTF_16LE);\n        builder.build().extract(100).stream().forEach(row -> System.out.println(Jsons.toJson(row)));*/\n\n        file = \"D:\\\\temp\\\\withoutbom\\\\test-utf16be.csv\";\n        builder = DataExtractorBuilder.newBuilder(file).csvFormat(format).charset(StandardCharsets.UTF_16BE);\n        builder.build().extract(100).stream().forEach(row -> System.out.println(Jsons.toJson(row)));\n    }\n\n    @Test\n    public void test8() throws IOException {\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\withoutbom\\\\test-utf8.csv\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\withoutbom\\\\test-utf16le.csv\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\withoutbom\\\\test-utf16be.csv\"));\n        //System.out.println(Files.toString(new File(\"D:\\\\temp\\\\withoutbom\\\\test-utf16be.csv\"), \"UTF-16BE\"));\n        System.out.println(CharsetDetector.detect(\"D:\\\\temp\\\\withoutbom\\\\gbk.txt\"));\n    }\n\n    @Test\n    public void testDetect() throws IOException {\n        File filePath = MavenProjects.getMainJavaFile(SpringContextHolder.class);\n        System.out.println(\"CharsetDetector.detect -->  \" + CharsetDetector.detect(new FileInputStream(filePath)));\n        System.out.println(\"EncodingDetector.detect -->  \" + BytesDetector.detect(Files.readByteArray(new FileInputStream(filePath), 12000)));\n    }\n\n    @Test\n    public void testDetectFile() {\n        //detectFile(MavenProjects.getMainJavaPath(\"cn.ponfee.commons\"));\n        detectFile(MavenProjects.getTestJavaPath(\"cn.ponfee.commons.io.file\"));\n    }\n\n    private static void detectFile(String filePath) {\n        Files.listFiles(filePath).traverse(tree -> {\n            if (CollectionUtils.isEmpty(tree.getChildren())) {\n                try {\n                    File f = tree.getAttach();\n                    System.out.println(f.getName() + \": CharsetDetector=\" + CharsetDetector.detect(new FileInputStream(f)) + \", EncodingDetector=\" + BytesDetector.detect(Files.readByteArray(new FileInputStream(f), 1200)));\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n    }\n\n    @Test\n    public void testFormat() {\n        String text = \"JPFreq[3][74] = 600;\\n\" +\n            \"            JPFreq[3][45] = 599;\\n\" +\n            \"            JPFreq[3][3] = 598;\\n\" +\n            \"            JPFreq[3][24] = 597;\\n\" +\n            \"            JPFreq[3][30] = 596;\\n\" +\n            \"            JPFreq[4][76] = 485;\\n\" +\n            \"            JPFreq[22][65] = 3;\\n\" +\n            \"            JPFreq[42][29] = 2;\\n\" +\n            \"            JPFreq[27][66] = 1;\\n\" +\n            \"            JPFreq[26][89] = 0;\";\n\n        List<String> collect = Arrays.stream(text.split(\";\"))\n            .map(String::trim)\n            .collect(Collectors.toList());\n\n        int maxLeft = collect.stream().mapToInt(s -> s.split(\"=\")[0].trim().length()).max().orElse(0);\n        int maxRight = collect.stream().mapToInt(s -> s.split(\"=\")[1].trim().length()).max().orElse(0);\n\n        for (List<String> line : Lists.partition(collect, 5)) {\n            String s = line.stream().map(e -> {\n                String[] array = e.split(\"=\");\n                return StringUtils.rightPad(array[0].trim(), maxLeft, \" \") + \" = \" + StringUtils.leftPad(array[1].trim(), maxRight, \" \")+\"; \";\n            }).collect(Collectors.joining());\n            System.out.println(s);\n        }\n    }\n\n    public static void main(String[] args) {\n        int[][] GBFreq = new int[94][94];\n        Arrays.stream(GBFreq).forEach(e -> System.out.println(Arrays.toString(e)));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/WindowsBomTest.java",
    "content": "package cn.ponfee.commons.io;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\n\nimport cn.ponfee.commons.util.MavenProjects;\nimport org.junit.Test;\n\npublic class WindowsBomTest {\n\n    @Test\n    public void testhas() throws IOException {\n        System.out.println(ByteOrderMarks.has(MavenProjects.getTestJavaFile(WindowsBomTest.class)));\n        System.out.println(ByteOrderMarks.has(\"D:\\\\temp\\\\withbom\\\\csv-utf8-bom.csv\"));\n        System.out.println(ByteOrderMarks.has(\"D:\\\\temp\\\\withbom\\\\csv-utf16le-bom.csv\"));\n        System.out.println(ByteOrderMarks.has(\"D:\\\\temp\\\\withbom\\\\csv-utf16be-bom.csv\"));\n    }\n\n    @Test\n    public void testadd() throws IOException {\n        System.out.println(ByteOrderMarks.add(\"D:\\\\temp\\\\withbom\\\\csv-utf8-bom.csv\"));\n        System.out.println(ByteOrderMarks.add(\"D:\\\\temp\\\\withbom\\\\csv-utf16le-bom.csv\"));\n        System.out.println(ByteOrderMarks.add(\"D:\\\\temp\\\\withbom\\\\csv-utf16be-bom.csv\"));\n    }\n    \n    @Test\n    public void testremove() throws IOException {\n        System.out.println(ByteOrderMarks.remove(\"D:\\\\temp\\\\withbom\\\\csv-utf8-bom.csv\"));\n        System.out.println(ByteOrderMarks.remove(\"D:\\\\temp\\\\withbom\\\\csv-utf16le-bom.csv\"));\n        System.out.println(ByteOrderMarks.remove(\"D:\\\\temp\\\\withbom\\\\csv-utf16be-bom.csv\"));\n    }\n    \n    @Test\n    public void testaddCharset() throws IOException {\n        System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_8,  \"D:\\\\temp\\\\withbom\\\\csv-utf8-bom.csv\"));\n        System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_16LE,\"D:\\\\temp\\\\withbom\\\\csv-utf16le-bom.csv\"));\n        System.out.println(ByteOrderMarks.add(StandardCharsets.UTF_16BE,\"D:\\\\temp\\\\withbom\\\\csv-utf16be-bom.csv\"));\n    }\n    \n    @Test\n    public void testremoveCharset() throws IOException {\n        System.out.println(ByteOrderMarks.remove(StandardCharsets.UTF_8,\"D:\\\\temp\\\\withbom\\\\csv-utf8-bom.csv\"));\n        System.out.println(ByteOrderMarks.remove(StandardCharsets.UTF_16LE,\"D:\\\\temp\\\\withbom\\\\csv-utf16le-bom.csv\"));\n        System.out.println(ByteOrderMarks.remove(StandardCharsets.UTF_16BE,\"D:\\\\temp\\\\withbom\\\\csv-utf16be-bom.csv\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/WrappedBufferedReaderTest.java",
    "content": "package cn.ponfee.commons.io;\n\nimport cn.ponfee.commons.util.MavenProjects;\n\nimport java.io.File;\n\npublic class WrappedBufferedReaderTest {\n\n    public static void main(String[] args) {\n        try (WrappedBufferedReader reader = new WrappedBufferedReader(MavenProjects.getTestJavaFile(WrappedBufferedReaderTest.class))) {\n            for (String str = reader.readLine(); str != null; str = reader.readLine()) {\n                System.out.println(str);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/ASCII.txt",
    "content": "ASCII (American Standard Code for information exchange) is a computer coding system based on Latin alphabet, which is mainly used to display modern English and other Western European languages.\n\nPowered by gzu-liyujiang\n2020/8/7\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/Big5.txt",
    "content": "ڷRABIG5uc餤\n\nPowered by Q{CHΦ\n2020~85\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/EUC-KR.txt",
    "content": "EUC-KR  ѱ KSX 1001   ( KSC 5601)          ȴ. ԰  KSX 2901 ( Ī KS C 5861)        .\n\nKS X 1001     Ʈ  ǥ Ѵ.\n\n' Ʈ'  0 xA 1 - 0 xFE  Ѵ\n\n' Ʈ'  0 xA 1 - 0 xFE  Ѵ"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/GB18030.txt",
    "content": "Ұй ҐЇ йۤƤޤ  ݧҧݧ ܧڧѧ\n\nPowered by Fݴԣ\n202085\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/GB2312.txt",
    "content": "ҰйGB2312ֻּ֧\n\nPowered by ݴԣ\n202085\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/GBK.txt",
    "content": "Ұй йۤƤޤ  ݧҧݧ ܧڧѧ\n\nPowered by ݴԣ\n202085\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/KOI8-R.txt",
    "content": "KOI8-R    8 -       KOI-8       .\n\n ,  Unicode   , KOI8-R      ,     ISO-8859-5."
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/Shift_JIS.txt",
    "content": "Shift_JIS͓{̃Rs[^VXeł悭gĂR[h\\łBSpƔp̃At@xbgAAЉALƓ{̊邱Ƃł܂B\n\nShift_ƖtĂ܂BJIŠ́ASpuɁA{0 x A 1-0 xDFɒuĂp邽߂łB\n\n}CN\\tgyIBM̓{Rs[^VXeł́ÃR[h\\gĂ܂B̃R[h\\CP 932ƌĂ΂B"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/UTF-8-BOM.txt",
    "content": "﻿KOI8-R представляет собой кодирование 8 - битного текста на славянском языке серии KOI-8 для использования на русском и болгарском языках. \n\n до того, как Unicode не стал популярным, KOI8-R был наиболее широко используемым русским кодом, который даже выше стандарта ISO-8859-5. \n\n我爱中国 我愛中國 中国を愛しています Я люблю китай I love China.\n\nPowered by 貴州穿青人李裕江\n2020年8月7日\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/io/file/UTF-8.txt",
    "content": "我爱中国 中国を愛しています 나 는 중국 을 사랑한다 Я люблю китай\n\nPowered by 貴州穿青人李裕江\n2020年8月5日\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/jce/DesgitTest.java",
    "content": "package cn.ponfee.commons.jce;\n\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.util.Arrays;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport com.google.common.base.Stopwatch;\n\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport cn.ponfee.commons.jce.digest.HmacUtils;\nimport cn.ponfee.commons.jce.implementation.digest.RipeMD160Digest;\nimport cn.ponfee.commons.jce.implementation.digest.SHA1Digest;\nimport cn.ponfee.commons.jce.implementation.rsa.RSAKey;\nimport cn.ponfee.commons.jce.implementation.symmetric.RC4;\nimport cn.ponfee.commons.jce.symmetric.Algorithm;\nimport cn.ponfee.commons.jce.symmetric.SymmetricCryptor;\nimport cn.ponfee.commons.jce.symmetric.SymmetricCryptorBuilder;\nimport cn.ponfee.commons.util.MavenProjects;\nimport cn.ponfee.commons.util.SecureRandoms;\n\npublic class DesgitTest {\n\n    @Test\n    public void test() {\n        byte[] data = \"1234567890\".getBytes();\n        RipeMD160Digest md = RipeMD160Digest.getInstance();\n        String actual = Hex.encodeHexString(md.doFinal(data));\n        if(!\"9d752daa3fb4df29837088e1e5a1acf74932e074\".equals(actual)) {\n            System.err.println(\"fail\");\n        } else {\n            System.out.println(\"success\");\n        }\n        System.out.println(Hex.encodeHexString(md.doFinal(data)));\n        System.out.println(Hex.encodeHexString(md.doFinal(data)));\n        md.update(data);\n        System.out.println(Hex.encodeHexString(md.doFinal()));\n    }\n\n    @Test\n    public void test2() {\n        System.out.println(Hex.encodeHexString(SHA1Digest.getInstance().doFinal()));\n        System.out.println(DigestUtils.sha1Hex(new byte[] {}));\n\n        byte[] data = MavenProjects.getMainJavaFileAsBytes(SHA1Digest.class);\n\n        SHA1Digest sha1 = SHA1Digest.getInstance();\n        System.out.println(Hex.encodeHexString(sha1.doFinal(data)));\n        System.out.println(DigestUtils.sha1Hex(data));\n\n        for (int i = 0; i < 1000; i++) {\n            byte[] data1 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1);\n            byte[] data2 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1);\n            byte[] data3 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1);\n            sha1.reset();\n            sha1.update(data1);\n            sha1.update(data2);\n            sha1.update(data3);\n            byte[] expect = DigestUtils.digest(DigestAlgorithms.SHA1, data1, data2, data3);\n            if (!Arrays.equals(expect, sha1.doFinal())) {\n                System.err.println(\"FAIL\" + \" --> \" + data.length);\n            }\n        }\n    }\n    public static final int RSA_F4 = 65537;\n\n    @Test\n    public void test3() {\n        Stopwatch watch = Stopwatch.createStarted();\n        RSAKey.generateKey(4096, RSA_F4);\n        System.out.println(\"generateKey1: \" + watch.stop());\n\n        watch.reset().start();\n        RSAKey.generateKey(4096, RSA_F4);\n        System.out.println(\"generateKey2: \" + watch.stop());\n    }\n\n    @Test\n    public void test4() {\n        byte[] key = \"0123456789123456\".getBytes();\n        byte[] data = MavenProjects.getMainJavaFileAsBytes(RC4.class);\n        RC4 rc4 = new RC4(key);\n        byte[] encrypted1 = rc4.encrypt(data);\n        byte[] encrypted2 = rc4.encrypt(data);\n\n        Assert.assertArrayEquals(encrypted1, encrypted2);\n\n        if (   !Arrays.equals(rc4.decrypt(encrypted1), data)\n            && !Arrays.equals(rc4.decrypt(encrypted1), data)) {\n            System.err.println(\"rc4 crypt fail!\");\n        } else {\n            //System.out.println(new String(rc4.crypt(encrypted)));\n        }\n\n        SymmetricCryptor rc = SymmetricCryptorBuilder.newBuilder(Algorithm.RC4, key).build();\n        if (   !Arrays.equals(rc.decrypt(encrypted1), data)\n            && !Arrays.equals(rc.decrypt(encrypted1), data)) {\n            System.err.println(\"rc4 crypt fail!\");\n        } else {\n            //System.out.println(new String(rc4.crypt(encrypted)));\n        }\n\n        encrypted1 = rc.encrypt(data);\n        encrypted2 = rc.encrypt(data);\n        if (   !Arrays.equals(rc4.decrypt(encrypted1), data)\n            && !Arrays.equals(rc4.decrypt(encrypted2), data)) {\n            System.err.println(\"rc4 crypt fail!\");\n        } else {\n            //System.out.println(new String(rc4.decrypt(encrypted)));\n        }\n    }\n\n    @Test\n    public void test5() throws Exception {\n        System.out.println(DigestUtils.sha224Hex(\"1\".getBytes()));\n        System.out.println(DigestUtils.ripeMD160Hex(\"1234567890\".getBytes()));\n        //System.out.println(ObjectUtils.toString(shortText(\"http://www.manong5.com/102542001/\")));\n        long start = System.currentTimeMillis();\n        System.out.println(DigestUtils.sha1Hex(new FileInputStream(\"E:\\\\tools\\\\develop\\\\linux\\\\CentOS-6.6-x86_64-bin-DVD1.iso\")));\n        System.out.println((System.currentTimeMillis() - start) / 1000);\n    }\n\n    @Test\n    public void test6() throws FileNotFoundException {\n        byte[] key = SecureRandoms.nextBytes(16);\n        System.out.println(HmacUtils.sha1Hex(key, new FileInputStream(MavenProjects.getMainJavaFile(HmacUtils.class))));\n        System.out.println(HmacUtils.sha1Hex(key, new FileInputStream(MavenProjects.getMainJavaFile(HmacUtils.class))));\n        System.out.println(HmacUtils.ripeMD128Hex(key, \"abc\".getBytes()));\n        System.out.println(HmacUtils.ripeMD160Hex(key, \"abc\".getBytes()));\n        System.out.println(HmacUtils.ripeMD256Hex(key, \"abc\".getBytes()));\n        System.out.println(HmacUtils.ripeMD320Hex(key, \"abc\".getBytes()));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/jce/PBECryptorTest.java",
    "content": "package cn.ponfee.commons.jce;\n\nimport cn.ponfee.commons.jce.symmetric.Mode;\nimport cn.ponfee.commons.jce.symmetric.PBECryptor;\nimport cn.ponfee.commons.jce.symmetric.PBECryptor.PBEAlgorithm;\nimport cn.ponfee.commons.jce.symmetric.PBECryptorBuilder;\nimport cn.ponfee.commons.jce.symmetric.Padding;\n\npublic class PBECryptorTest {\n\n    public static void main(String[] args) {\n        //PBEAlgorithm alg = PBEAlgorithm.PBEWithMD5AndTripleDES;\n        //PBEAlgorithm alg = PBEAlgorithm.PBEWithMD5AndDES;\n        //PBEAlgorithm alg = PBEAlgorithm.PBEWithSHA1AndRC2_40;\n        PBEAlgorithm alg = PBEAlgorithm.PBEWithSHA1AndDESede;\n        char[] pass = \"87654321\".toCharArray();\n        byte[] salt = \"12345678\".getBytes();\n        int iterations = 100;\n\n        // 加密\n        PBECryptor p = PBECryptorBuilder.newBuilder(alg, pass)\n                                        .mode(Mode.CBC).padding(Padding.PKCS5Padding)\n                                        .parameter(salt, iterations)\n                                        .build();\n        byte[] encrypted = p.encrypt(\"abc\".getBytes());\n\n        // 解密\n        p = PBECryptorBuilder.newBuilder(alg, p.getPass())\n            .mode(p.getMode()).padding(p.getPadding())\n            .parameter(p.getSalt(), p.getIterations())\n            .build();\n        \n        byte[] decrypted = p.decrypt(encrypted);\n        System.out.println(new String(decrypted));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/jce/PasswdTest.java",
    "content": "package cn.ponfee.commons.jce;\n\nimport java.security.GeneralSecurityException;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport com.google.common.base.Stopwatch;\n\nimport cn.ponfee.commons.jce.passwd.BCrypt;\nimport cn.ponfee.commons.jce.passwd.Crypt;\nimport cn.ponfee.commons.jce.passwd.PBKDF2;\nimport cn.ponfee.commons.jce.passwd.SCrypt;\nimport cn.ponfee.commons.util.SecureRandoms;\n\npublic class PasswdTest {\n\n    \n    @Test\n    public void testCrypt() {\n        String passwd = \"passwd\";\n        String hashed = Crypt.create(HmacAlgorithms.HmacSHA3_256, passwd, 32, Providers.BC);\n        boolean flag = true;\n        System.out.println(hashed);\n        long start = System.currentTimeMillis();\n        for (int i = 0; i < 100000; i++) {\n            if (!Crypt.check(passwd, hashed)) {\n                flag = false;\n                break;\n            }\n        }\n        System.out.println(\"cost: \" + (System.currentTimeMillis() - start));\n        if (flag) {\n            System.out.println(\"success!\");\n        } else {\n            System.err.println(\"fail!\");\n        }\n    }\n    \n    /**\n     * Tests the basic functionality of the PasswordHash class\n     * @param args ignored\n     * @throws GeneralSecurityException\n     */\n    @Test\n    public void testPBKDF2_1() {\n        // Print out 10 hashes\n        for (int i = 0; i < 10; i++) {\n            System.out.println(PBKDF2.create(HmacAlgorithms.HmacSHA3_256, \"p\\r\\nassw0Rd!\".toCharArray(), 16, 65535, 32));\n        }\n        System.out.println(\"============================================\\n\");\n\n        // Test password validation\n        HmacAlgorithms alg = HmacAlgorithms.HmacSHA3_256;\n        System.out.println(\"Running tests...\");\n        String passwd = \"password\";\n        String hashed = PBKDF2.create(alg, passwd);\n        System.out.println(hashed);\n        boolean failure = false;\n        long start = System.currentTimeMillis();\n        for (int i = 0; i < 100000; i++) { // 20 seconds\n            if (!PBKDF2.check(passwd, hashed)) {\n                failure = true;\n                break;\n            }\n        }\n        System.out.println(\"cost: \" + (System.currentTimeMillis() - start) / 1000);\n        if (failure) {\n            System.err.println(\"TESTS FAILED!\");\n        } else {\n            System.out.println(\"TESTS PASSED!\");\n        }\n    }\n    \n    @Test\n    public void testScrypt() {\n        byte[] pwd = \"123456\".getBytes();\n        byte[] salt = \"0123456789123456\".getBytes();\n        System.out.println(\"\\n=====================PBKDF2=============================\");\n\n        System.out.println(\"\\n=====================scrypt cost=============================\");\n        Stopwatch watch = Stopwatch.createStarted();\n        SCrypt.scrypt(HmacAlgorithms.HmacSHA256, \"123\".getBytes(), \"123\".getBytes(), 16384, 8, 8, 64); // 推荐参数\n        System.out.println(\"16384, 8, 8, 64 cost: \" + watch.stop());\n\n        watch.reset().start();\n        SCrypt.scrypt(HmacAlgorithms.HmacSHA256, \"123\".getBytes(), \"123\".getBytes(), 2, 2, 2, 32); // 推荐参数\n        System.out.println(\"2, 2, 2, 32 cost: \" + watch.stop());\n\n        System.out.println(\"\\n=====================scrypt verify=============================\");\n        String actual = Hex.encodeHexString(SCrypt.scrypt(HmacAlgorithms.HmacSHA256, pwd, salt, 8, 255, 255, 32));\n        if (!\"e488217f72b6c850f82911e78427a78d8a64aa7d313cdc9ee6989915d7548df4\".equals(actual)) {\n            System.err.println(\"scrypt fail!\");\n        } else {\n            System.out.println(\"scrypt success!\");\n        }\n\n        System.out.println(\"\\n=====================Scrypt=============================\");\n        String password = \"passwd\";\n        String hashed = SCrypt.create(password, 1, 2, 2);\n        System.out.println(hashed);\n        System.out.println(\"Test begin...\");\n        boolean flag = true;\n        watch.reset().start();\n        for (int i = 0; i < 100000; i++) { // 20 seconds\n            if (!SCrypt.check(password, hashed)) {\n                flag = false;\n                break;\n            }\n        }\n        if (flag) {\n            System.out.println(\"Test success!\");\n        } else {\n            System.err.println(\"Test fail!\");\n        }\n        System.out.println(\"cost: \" + watch.stop());\n    }\n    \n    @Test\n    public void testBcrypt() {\n        byte[] pwd = \"123456\".getBytes();\n        byte[] salt = \"0123456789123456\".getBytes();\n        String actual = Hex.encodeHexString(BCrypt.crypt(pwd, salt, 5));\n        if (!\"ddc41d0b514ecedb8ae12c42e8c2f4419e71e15c519ecd4b\".equals(actual)) {\n            System.err.println(\"crypt fail!\");\n        } else {\n            System.out.println(\"crypt success!\");\n        }\n        System.out.println();\n\n        String password = \"passwd\";\n        System.out.println(BCrypt.create(password, 11));\n        System.out.println();\n\n        System.out.println(\"Test begin...\");\n        boolean flag = true;\n        String hashed = BCrypt.create(password, 5);\n        long start = System.currentTimeMillis();\n        for (int i = 0; i < 100000; i++) { // 45 seconds\n            if (!BCrypt.check(password, hashed)) {\n                flag = false;\n                break;\n            }\n        }\n        System.out.println(\"cost: \" + (System.currentTimeMillis() - start) / 1000);\n        if (flag) {\n            System.out.println(\"Test success!\");\n        } else {\n            System.err.println(\"Test fail!\");\n        }\n    }\n\n    @Test\n    public void testScrypt2() {\n        byte[] pwd = SecureRandoms.nextBytes(20);\n        byte[] salt = SecureRandoms.nextBytes(16);\n        byte[] except = SCrypt.scrypt(HmacAlgorithms.HmacSHA256, pwd, salt, 8, 255, 255, 32);\n        byte[] actual = org.bouncycastle.crypto.generators.SCrypt.generate(pwd, salt, 8, 255, 255, 32);\n        Assert.assertArrayEquals(except, actual);\n    }\n\n    @Test\n    public void testBcrypt2() {\n        byte[] pwd = SecureRandoms.nextBytes(20);\n        byte[] salt = SecureRandoms.nextBytes(16);\n        byte[] except = BCrypt.crypt(pwd, salt, 5);\n        byte[] actual = org.bouncycastle.crypto.generators.BCrypt.generate(pwd, salt, 5);\n        Assert.assertArrayEquals(except, actual);\n    }\n\n    @Test\n    public void testPBKDF2_2() {\n        String pwd = \"SecureRandoms.nextBytes(20)\";\n        int iterationCount = 20;\n        int dkLen = 32;\n        byte[] salt = SecureRandoms.nextBytes(16);\n        byte[] except = PBKDF2.pbkdf2(HmacAlgorithms.HmacSHA3_256, pwd.toCharArray(), salt, iterationCount, dkLen);\n        byte[] actual = SCrypt.pbkdf2(HmacAlgorithms.HmacSHA3_256, pwd.getBytes(), salt, iterationCount, dkLen);\n        Assert.assertArrayEquals(except, actual);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/jce/SCryptTester.java",
    "content": "// Copyright (C) 2011 - Will Glozer.  All rights reserved.\n\npackage cn.ponfee.commons.jce;\n\nimport static java.lang.Integer.MAX_VALUE;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\nimport java.util.Base64;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.jce.passwd.SCrypt;\n\npublic class SCryptTester {\n    String passwd = \"secret\";\n\n    @Test\n    public void scrypt() {\n        int N = 8388608;\n        int r = 1;\n        int p = 1;\n\n        String hashed = SCrypt.create(passwd, N, r, p);\n        String[] parts = hashed.split(\"\\\\$\");\n\n        assertEquals(5, parts.length);\n        assertEquals(\"s0\", parts[1]);\n        Assert.assertEquals(16, Base64.getUrlDecoder().decode(parts[3]).length);\n        assertEquals(32, Base64.getUrlDecoder().decode(parts[4]).length);\n\n        long params = Long.parseLong(parts[2], 16);\n\n        // 0xe0801 >> 16  ->  0xe\n        assertEquals(N, (int) Math.pow(2, params >> 16 & 0xffff));\n        assertEquals(r, params >> 8 & 0xff);\n        assertEquals(p, params >> 0 & 0xff);\n    }\n\n    @Test\n    public void check() {\n        String hashed = SCrypt.create(passwd, 16384, 8, 1);\n\n        assertTrue(SCrypt.check(passwd, hashed));\n        assertFalse(SCrypt.check(\"s3cr3t\", hashed));\n    }\n\n    @Test\n    public void format_0_rp_max() {\n        int N = 2;\n        int r = 255;\n        int p = 255;\n\n        String hashed = SCrypt.create(passwd, N, r, p);\n        assertTrue(SCrypt.check(passwd, hashed));\n\n        String[] parts = hashed.split(\"\\\\$\");\n        long params = Long.parseLong(parts[2], 16);\n\n        assertEquals(N, (int) Math.pow(2, params >>> 16 & 0xffff));\n        assertEquals(r, params >> 8 & 0xff);\n        assertEquals(p, params >> 0 & 0xff);\n    }\n\n    public static void main(String[] args) {\n        System.out.println(MAX_VALUE / 128 / 255);\n        System.out.println(Long.toString(8388608, 16));\n        System.out.println(Math.pow(2, 0xe));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/jce/security/DHKeyExchangerTest.java",
    "content": "package cn.ponfee.commons.jce.security;\n\nimport javax.crypto.interfaces.DHPrivateKey;\nimport javax.crypto.interfaces.DHPublicKey;\n\nimport org.apache.commons.lang3.tuple.Pair;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class DHKeyExchangerTest {\n\n    public static void main(String[] args) {\n        Providers.set(Providers.BC);\n        Pair<DHPublicKey, DHPrivateKey> partA = DHKeyExchanger.initPartAKey(1024);\n        Pair<DHPublicKey, DHPrivateKey> partB = DHKeyExchanger.initPartBKey(partA.getLeft());\n        byte[] data = MavenProjects.getMainJavaFileAsBytes(DHKeyExchanger.class);\n\n        // 乙方加密甲方解密\n        byte[] encrypted = DHKeyExchanger.encrypt(data, DHKeyExchanger.genSecretKey(partB.getRight(), partA.getLeft()));\n        byte[] decrypted = DHKeyExchanger.decrypt(encrypted, DHKeyExchanger.genSecretKey(partA.getRight(), partB.getLeft()));\n        System.out.println(new String(decrypted));\n\n        // 甲方加密乙方解密\n        encrypted = DHKeyExchanger.encrypt(data, DHKeyExchanger.genSecretKey(partA.getRight(), partB.getLeft()));\n        decrypted = DHKeyExchanger.decrypt(encrypted, DHKeyExchanger.genSecretKey(partB.getRight(), partA.getLeft()));\n        System.out.println(new String(decrypted));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/jce/security/DSASignerTest.java",
    "content": "package cn.ponfee.commons.jce.security;\n\nimport java.security.interfaces.DSAPrivateKey;\nimport java.security.interfaces.DSAPublicKey;\n\nimport org.apache.commons.lang3.tuple.Pair;\n\nimport cn.ponfee.commons.jce.Providers;\n\npublic class DSASignerTest {\n\n    public static void main(String[] args) {\n        Providers.set(Providers.BC);\n        Pair<DSAPublicKey, DSAPrivateKey> keyPair = DSASigner.initKey();\n        byte[] data = \"123456\".getBytes();\n        byte[] signed = DSASigner.sign(data, keyPair.getRight());\n        boolean flag = DSASigner.verify(data, keyPair.getLeft(), signed);\n        System.out.println(flag);\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/jce/security/ECDHKeyExchangerTest.java",
    "content": "package cn.ponfee.commons.jce.security;\n\nimport java.security.interfaces.ECPrivateKey;\nimport java.security.interfaces.ECPublicKey;\n\nimport org.apache.commons.lang3.tuple.Pair;\n\nimport cn.ponfee.commons.jce.Providers;\n\npublic class ECDHKeyExchangerTest {\n\n    public static void main(String[] args) {\n        Providers.set(Providers.BC);\n        Pair<ECPublicKey, ECPrivateKey> partA = ECDHKeyExchanger.initPartAKey(192);\n        Pair<ECPublicKey, ECPrivateKey> partB = ECDHKeyExchanger.initPartBKey(partA.getLeft());\n        byte[] data = \"123456\".getBytes();\n\n        // 乙方加密甲方解密\n        byte[] encrypted = ECDHKeyExchanger.encrypt(data, ECDHKeyExchanger.genSecretKey(partB.getRight(), partA.getLeft()));\n        byte[] decrypted = ECDHKeyExchanger.decrypt(encrypted, ECDHKeyExchanger.genSecretKey(partA.getRight(), partB.getLeft()));\n        System.out.println(new String(decrypted));\n\n        // 甲方加密乙方解密\n        encrypted = ECDHKeyExchanger.encrypt(data, ECDHKeyExchanger.genSecretKey(partA.getRight(), partB.getLeft()));\n        decrypted = ECDHKeyExchanger.decrypt(encrypted, ECDHKeyExchanger.genSecretKey(partB.getRight(), partA.getLeft()));\n        System.out.println(new String(decrypted));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/jce/security/ECDSASignerTest.java",
    "content": "package cn.ponfee.commons.jce.security;\n\nimport java.security.interfaces.ECPrivateKey;\nimport java.security.interfaces.ECPublicKey;\n\nimport org.apache.commons.lang3.tuple.Pair;\n\npublic class ECDSASignerTest {\n\n    public static void main(String[] args) {\n        Pair<ECPublicKey, ECPrivateKey> keyPair = ECDSASigner.generateKeyPair(571);\n        byte[] data = \"123456\".getBytes();\n        byte[] signed = ECDSASigner.signSha512(data, keyPair.getRight());\n        System.out.println(signed.length);\n        System.out.println(ECDSASigner.verifySha512(data, signed, keyPair.getLeft()));\n\n        /*byte[] encrypted = encrypt(data, getPublicKey(keyMap));\n        byte[] decrypted = decrypt(encrypted, getPrivateKey(keyMap));\n        System.out.println(new String(decrypted));*/\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/jce/security/RSAPrivateKeysTest.java",
    "content": "package cn.ponfee.commons.jce.security;\n\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\n\nimport org.junit.Test;\n\nimport cn.ponfee.commons.jce.security.RSACryptor.RSAKeyPair;\n\npublic class RSAPrivateKeysTest {\n\n    @Test\n    public void test1() {\n        RSAKeyPair keyPair = RSACryptor.generateKeyPair(2018);\n        RSAPrivateKey privKey = keyPair.getPrivateKey();\n\n        System.out.println(\"\\n================================================toPkcs1\");\n        System.out.println(RSAPrivateKeys.toPkcs1(privKey));\n\n        System.out.println(\"\\n================================================toPkcs1Pem\");\n        System.out.println(RSAPrivateKeys.toPkcs1Pem(privKey));\n\n        System.out.println(\"\\n================================================toPkcs8\");\n        System.out.println(RSAPrivateKeys.toPkcs8(privKey));\n\n        System.out.println(\"\\n================================================toEncryptedPkcs8\");\n        System.out.println(RSAPrivateKeys.toEncryptedPkcs8(privKey, \"123456\"));\n\n        System.out.println(\"\\n================================================toEncryptedPkcs8Pem\");\n        System.out.println(RSAPrivateKeys.toEncryptedPkcs8Pem(privKey, \"123456\"));\n    }\n\n    @Test\n    public void test2() {\n        RSAKeyPair keyPair = RSACryptor.generateKeyPair(2018);\n        RSAPublicKey pubKey = keyPair.getPublicKey();\n\n        System.out.println(\"\\n================================================toPkcs1\");\n        System.out.println(RSAPublicKeys.toPkcs1(pubKey));\n\n        System.out.println(\"\\n================================================toPkcs8\");\n        System.out.println(RSAPublicKeys.toPkcs8(pubKey));\n\n        System.out.println(\"\\n================================================toPkcs8Pem\");\n        System.out.println(RSAPublicKeys.toPkcs8Pem(pubKey));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/json/BooleanPojoTest.java",
    "content": "package cn.ponfee.commons.json;\n\nimport cn.ponfee.commons.model.Result;\nimport com.alibaba.fastjson.JSON;\n\n/**\n * @author Ponfee\n */\npublic class BooleanPojoTest {\n    \n    private boolean success1;\n    private boolean isSuccess2;\n    private Boolean success3;\n    private Boolean isSuccess4;\n\n    public boolean isSuccess1() {\n        return success1;\n    }\n\n    public void setSuccess1(boolean success1) {\n        this.success1 = success1;\n    }\n\n    public boolean isSuccess2() {\n        return isSuccess2;\n    }\n\n    public void setSuccess2(boolean success2) {\n        isSuccess2 = success2;\n    }\n\n    public Boolean getSuccess3() {\n        return success3;\n    }\n\n    public void setSuccess3(Boolean success3) {\n        this.success3 = success3;\n    }\n\n    public Boolean getSuccess4() {\n        return isSuccess4;\n    }\n\n    public void setSuccess4(Boolean success4) {\n        isSuccess4 = success4;\n    }\n\n    public static void main(String[] args) {\n        // 一空\n        BooleanPojoTest pojo1 = new BooleanPojoTest();\n        pojo1.success1 = true;\n        pojo1.success3 = false;\n        System.out.println(Jsons.toJson(pojo1));\n        System.out.println(JSON.toJSONString(pojo1));\n\n        System.out.println(\"\\n-------------\");\n        BooleanPojoTest pojo2 = new BooleanPojoTest();\n        pojo2.success1 = false;\n        pojo2.isSuccess4 = false;\n        System.out.println(Jsons.toJson(pojo2));\n        System.out.println(JSON.toJSONString(pojo2));\n\n        System.out.println(\"\\n-------------\");\n        BooleanPojoTest pojo3 = new BooleanPojoTest();\n        pojo3.success1 = true;\n        System.out.println(Jsons.toJson(pojo3));\n        System.out.println(JSON.toJSONString(pojo3));\n\n        System.out.println(\"\\n-------------\");\n        BooleanPojoTest pojo4 = new BooleanPojoTest();\n        pojo4.success1 = true;\n        pojo4.success3 = false;\n        pojo4.isSuccess4 = false;\n        System.out.println(Jsons.toJson(pojo4));\n        System.out.println(JSON.toJSONString(pojo4));\n\n        System.out.println(\"\\n-------------\");\n        System.out.println(Jsons.toJson(Result.success()));\n        System.out.println(JSON.toJSONString(Result.success()));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/json/FastJsonUtils.java",
    "content": "package cn.ponfee.commons.json;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.text.ParseException;\nimport java.util.Date;\nimport java.util.Random;\n\nimport org.apache.commons.lang3.time.DateParser;\nimport org.apache.commons.lang3.time.FastDateFormat;\n\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.annotation.JSONField;\nimport com.alibaba.fastjson.parser.DefaultJSONParser;\nimport com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;\nimport com.alibaba.fastjson.serializer.JSONSerializer;\nimport com.alibaba.fastjson.serializer.ObjectSerializer;\n\npublic class FastJsonUtils {\n\n    /*private static final SerializeConfig mapping = new SerializeConfig();\n    static {\n        mapping.put(Date.class, new CustomDateFormatSerializer());\n    }*/\n\n    private static final String[] DATA_PATTERS = { \"yyyy-MM-dd HH:mm:ss,SSS\", \"yyyy-MM-dd HH:mm:ss\" };\n\n    public static class CustomDateFormatSerializer implements ObjectSerializer {\n        @Override\n        public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {\n            if (object == null) {\n                serializer.out.writeNull();\n                return;\n            }\n            serializer.write(FastDateFormat.getInstance(DATA_PATTERS[new Random().nextInt(2)]).format((Date) object));\n        }\n    }\n\n    public static class CustomDateFormatDeserializer implements ObjectDeserializer {\n        private static final Date DEFAULT_DATE = new Date(0L);\n        private static final DateParser FORMAT1 = FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss\");\n        private static final DateParser FORMAT2 = FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss,SSS\");\n\n        @Override\n        @SuppressWarnings(\"unchecked\")\n        public Date deserialze(DefaultJSONParser parser, Type type, Object fieldName) {\n            String dateString = parser.getLexer().stringVal();\n            try {\n                return (dateString.length() == 19 ? FORMAT1 : FORMAT2).parse(dateString);\n            } catch (ParseException e) {\n                return DEFAULT_DATE;\n            }\n        }\n\n        @Override\n        public int getFastMatchToken() {\n            return 0;\n        }\n    }\n\n    public static class DateBean {\n        @JSONField(serializeUsing = CustomDateFormatSerializer.class, deserializeUsing = CustomDateFormatDeserializer.class)\n        private Date date;\n\n        public DateBean() {}\n\n        public DateBean(Date date) {\n            this.date = date;\n        }\n\n        public Date getDate() {\n            return date;\n        }\n\n        public void setDate(Date date) {\n            this.date = date;\n        }\n\n        @Override\n        public String toString() {\n            return \"DateBean [date=\" + FastDateFormat.getInstance(\"yyyy-MM-dd HH:mm:ss SSS\").format(date) + \"]\";\n        }\n    }\n\n    public static void main(String[] args) {\n        DateBean bean = new DateBean(new Date(System.currentTimeMillis()));\n\n        for (int i = 0; i < 100; i++) {\n            String json = JSON.toJSONString(bean);\n            bean = JSON.parseObject(json, DateBean.class);\n            System.out.println(\"json: \" + json + \", bean: \" + bean);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/json/JacksonIgnore.java",
    "content": "package cn.ponfee.commons.json;\n\nimport java.util.Map;\n\nimport com.fasterxml.jackson.annotation.JsonFilter;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;\nimport com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;\n\nimport cn.ponfee.commons.model.Result;\n\npublic class JacksonIgnore {\n\n    public static void main(String[] args) throws Exception {\n        SimpleBeanPropertyFilter fieldFilter = SimpleBeanPropertyFilter.serializeAllExcept(\"code\", \"b\", \"c\");\n        SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter(\"fieldFilter\", fieldFilter);\n\n        ObjectMapper mapper = new ObjectMapper();\n        mapper.setFilterProvider(filterProvider).addMixIn(Map.class, FieldFilterMixIn.class);\n\n        System.out.println(mapper.writeValueAsString(Result.success()));\n    }\n\n    @JsonFilter(\"fieldFilter\")\n    interface FieldFilterMixIn {\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/json/JsonsTest.java",
    "content": "package cn.ponfee.commons.json;\n\nimport cn.ponfee.commons.collect.Maps;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.date.Dates;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.parser.ParserConfig;\nimport com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;\nimport com.alibaba.fastjson.serializer.ObjectSerializer;\nimport com.alibaba.fastjson.serializer.SerializeConfig;\nimport com.alibaba.fastjson.serializer.SimplePropertyPreFilter;\nimport com.alibaba.fastjson.spi.Module;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport lombok.Data;\nimport org.javamoney.moneta.Money;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport javax.money.Monetary;\nimport java.io.Serializable;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.Map;\n\n@SuppressWarnings(\"unchecked\")\npublic class JsonsTest {\n\n    @Test\n    public void testFastjsonMoney() {\n        Module module = new Module() {\n            @Override\n            public ObjectDeserializer createDeserializer(ParserConfig config, Class type) {\n                if (type == Money.class) {\n                    return new FastjsonMoney();\n                }\n                return null;\n            }\n\n            @Override\n            public ObjectSerializer createSerializer(SerializeConfig config, Class type) {\n                if (type == Money.class) {\n                    return new FastjsonMoney();\n                }\n                return null;\n            }\n        };\n        SerializeConfig serializeConfig = new SerializeConfig();\n        ParserConfig parserConfig = new ParserConfig();\n        serializeConfig.register(module);\n        parserConfig.register(module);\n\n        Money money = Money.ofMinor(Monetary.getCurrency(\"CNY\"), 999);\n        String json = JSON.toJSONString(money, serializeConfig);\n        System.out.println(json);\n\n        Object parse = JSON.parseObject(json, Money.class, parserConfig);\n\n        System.out.println(parse.getClass());\n        System.out.println(parse);\n    }\n\n    @Test\n    public void test1() {\n        String json = Jsons.NORMAL.string(Maps.toMap(\"a\", \"abc\", \"b\", 1));\n        System.out.println(json);\n\n        Map<String, Object> map = Jsons.NORMAL.parse(json, Map.class);\n        System.out.println(\"parse(json, target): \" + map);\n\n        map = Jsons.NORMAL.parse(json, new TypeReference<Map<String, Object>>() {});\n        System.out.println(\"parse(json, TypeReference): \" + map);\n    }\n\n    @Test\n    public void test2() {\n        Map<?, ?> map = Maps.toMap(\"a\", \"xx\", \"b\", 1, \"c\", 1.2D, \"d\", null);\n        System.out.println(Jsons.toJson(map));\n\n        Result<String> result = Result.success(\"xx\");\n        System.out.println(Jsons.toJson(result));\n    }\n\n    @Test\n    public void test3() {\n        Map<?, ?> map = Maps.toMap(\"a\", \"xx\", \"b\", 1, \"c\", 1.2D, \"d\", null);\n        System.out.println(JSON.toJSONString(map));\n        System.out.println(JSON.toJSONString(map, FastjsonPropertyFilter.include(\"a\", \"b\")));\n        System.out.println(JSON.toJSONString(map, FastjsonPropertyFilter.exclude(\"a\", \"b\")));\n\n        Result<String> result = Result.success(\"xx\");\n        System.out.println(JSON.toJSONString(result));\n        System.out.println(JSON.toJSONString(result, FastjsonPropertyFilter.include(\"msg\")));\n        System.out.println(JSON.toJSONString(result, FastjsonPropertyFilter.exclude(\"msg\")));\n    }\n\n    @Test\n    public void test4() {\n        Map<?, ?> map = Maps.toMap(\"a\", \"xx\", \"b\", 1, \"c\", 1.2D, \"d\", null);\n        System.out.println(JSON.toJSONString(map));\n        System.out.println(JSON.toJSONString(map, new SimplePropertyPreFilter(\"a\", \"b\")));\n\n        Result<String> result = Result.success(\"xx\");\n        System.out.println(JSON.toJSONString(result));\n        System.out.println(JSON.toJSONString(result, new SimplePropertyPreFilter(\"msg\")));\n    }\n    \n    @Test\n    public void test5() {\n        System.out.println(Jsons.fromJson(Jsons.toJson(new StringBuilder(\"111111111\")), StringBuilder.class));\n        System.out.println(Jsons.fromJson(Jsons.toJson(new StringPlain()), StringPlain.class));\n\n        System.out.println(JSON.parseObject(JSON.toJSONString(new StringBuilder(\"111111111\")), StringBuilder.class));\n        System.out.println(JSON.parseObject(JSON.toJSONString(new StringPlain()), StringPlain.class));\n\n        System.out.println(JSON.parse(JSON.toJSONString(Arrays.asList(1, 2, 3, 4))));\n    }\n\n    @Test\n    public void test6() {\n        String datestring = \"2022-01-01 01:01:01\";\n        \n        Person person1 = new Person();\n        person1.setName(\"tom\");\n        person1.setBirthday(Dates.toDate(datestring));\n        person1.setBalance(Money.ofMinor(Monetary.getCurrency(\"CNY\"), 999));\n        String personString = Jsons.toJson(person1);\n        Assert.assertEquals(\"{\\\"name\\\":\\\"tom\\\",\\\"birthday\\\":\\\"2022-01-01 01:01:01\\\",\\\"balance\\\":{\\\"currency\\\":\\\"CNY\\\",\\\"number\\\":999}}\", personString);\n\n        Person person2 = Jsons.fromJson(\"{\\\"name\\\":\\\"tom\\\",\\\"birthday\\\":\\\"2022-01-01 01:01:01\\\",\\\"balance\\\":{\\\"currency\\\":\\\"CNY\\\",\\\"number\\\":999},\\\"birthday2\\\":null,\\\"balance2\\\":null}\", Person.class);\n        Assert.assertEquals(person1.getBirthday(), person2.getBirthday());\n        Assert.assertEquals(person1.getBalance(), person2.getBalance());\n\n\n        Person person3 = Jsons.fromJson(\"{\\\"name\\\":\\\"tom\\\",\\\"birthday\\\":\\\"2022-01-01T01:01:01.0Z\\\",\\\"balance\\\":{\\\"currency\\\":\\\"CNY\\\",\\\"number\\\":999}}\", Person.class);\n        Assert.assertEquals(person1.getBirthday(), person3.getBirthday());\n        Assert.assertEquals(person1.getBalance(), person3.getBalance());\n\n        Person person4 = new Person();\n        person4.setName(\"person4\");\n        String json4 = Jsons.toJson(person4);\n        Assert.assertEquals(json4, \"{\\\"name\\\":\\\"person4\\\"}\");\n        person4 = Jsons.fromJson(json4, Person.class);\n        Assert.assertEquals(person4.getName(), \"person4\");\n        Assert.assertNull(person4.getBalance());\n    }\n\n    @Test\n    public void test7() {\n        Money money1 = Money.ofMinor(Monetary.getCurrency(\"CNY\"), 999);\n        String moneyStr = Jsons.toJson(money1);\n        System.out.println(moneyStr);\n        Assert.assertEquals(\"{\\\"currency\\\":\\\"CNY\\\",\\\"number\\\":999}\", moneyStr);\n        Money money2 = Jsons.fromJson(moneyStr, Money.class);\n        Assert.assertEquals(money1, money2);\n    }\n\n    public static class StringPlain implements Serializable {\n        private static final long serialVersionUID = 1L;\n        private StringBuilder sb = new StringBuilder(\"xxxxxxxxxx\");\n\n        public StringBuilder getSb() {\n            return sb;\n        }\n\n        public void setSb(StringBuilder sb) {\n            this.sb = sb;\n        }\n\n        @Override\n        public String toString() {\n            return ObjectUtils.toString(this);\n        }\n    }\n    \n    @Data\n    public static class Person implements java.io.Serializable {\n        private String name;\n        private Date birthday;\n        private Money balance;\n        private Date birthday2;\n        private Money balance2;\n    }\n    \n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/loadbalance/AbstractLoadBalance.java",
    "content": "package cn.ponfee.commons.loadbalance;\n\n/**\n * server load balance algorithm\n * \n * @author Ponfee\n */\npublic abstract class AbstractLoadBalance {\n\n    public abstract String select();\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/loadbalance/HashedLoadBalance.java",
    "content": "package cn.ponfee.commons.loadbalance;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport cn.ponfee.commons.math.Maths;\n\n/**\n * 源地址哈希法\n * \n * @author Ponfee\n */\npublic class HashedLoadBalance extends AbstractLoadBalance {\n    private final List<String> servers;\n\n    public HashedLoadBalance(Map<String, Integer> serverMap) {\n        this.servers = new ArrayList<>(serverMap.keySet());\n    }\n\n    @Override\n    public String select() {\n        throw new UnsupportedOperationException();\n    }\n\n    public String select(String invokeIp) {\n        return servers.get(Maths.abs(invokeIp.hashCode()) % servers.size());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/loadbalance/LeastActiveLoadBalance.java",
    "content": "package cn.ponfee.commons.loadbalance;\n\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * 最少活跃数\n *\n * @author Ponfee\n */\npublic class LeastActiveLoadBalance extends AbstractLoadBalance {\n\n    private final Map<String, AtomicInteger> serverMap;\n    private final List<Map.Entry<String, AtomicInteger>> servers;\n\n    public LeastActiveLoadBalance(Map<String, AtomicInteger> serverMap) {\n        this.serverMap = serverMap;\n        this.servers = new ArrayList<>(serverMap.entrySet());\n        this.servers.sort(Comparator.comparing(e -> e.getValue().get()));\n\n        //this.servers.sort(Comparator.comparing(Entry::getValue));\n        //this.servers.sort(Comparator.comparing(e -> e.getValue().get()));\n        //this.servers.sort((o1, o2) -> o1.getValue().compareTo(o2.getValue()));\n        //Collections.sort(servers, Comparator.comparing(Entry<String, Integer>::getValue));\n    }\n\n    @Override\n    public String select() {\n        return servers.get(0).getKey();\n    }\n\n    /**\n     * 调用前活跃数加1\n     *\n     * @param server\n     */\n    public void begin(String server) {\n        serverMap.get(server).incrementAndGet();\n    }\n\n    /**\n     * 调用后活跃数减1\n     *\n     * @param server\n     */\n    public void end(String server) {\n        serverMap.get(server).decrementAndGet();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/loadbalance/RandomLoadBalance.java",
    "content": "package cn.ponfee.commons.loadbalance;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ThreadLocalRandom;\n\n/**\n * 随机法\n * \n * @author Ponfee\n */\npublic class RandomLoadBalance extends AbstractLoadBalance {\n    private final List<String> servers;\n\n    public RandomLoadBalance(Map<String, Integer> serverMap) {\n        this.servers = new ArrayList<>(serverMap.keySet());\n    }\n\n    @Override\n    public String select() {\n        return servers.get(ThreadLocalRandom.current().nextInt(servers.size()));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/loadbalance/RoundRobinLoadBalance.java",
    "content": "package cn.ponfee.commons.loadbalance;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * 轮询法\n * \n * @author Ponfee\n */\npublic class RoundRobinLoadBalance extends AbstractLoadBalance {\n    private final AtomicLong pos = new AtomicLong(0);\n    private final List<String> servers;\n\n    public RoundRobinLoadBalance(Map<String, Integer> serverMap) {\n        this.servers = new ArrayList<>(serverMap.keySet());\n    }\n\n    @Override\n    public String select() {\n        return servers.get((int) (pos.getAndIncrement() % servers.size()));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/loadbalance/WeightRandomLoadBalance.java",
    "content": "package cn.ponfee.commons.loadbalance;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.concurrent.ThreadLocalRandom;\n\n/**\n * 加权随机法\n * \n * @author Ponfee\n */\npublic class WeightRandomLoadBalance extends AbstractLoadBalance {\n    private final List<String> servers;\n\n    public WeightRandomLoadBalance(Map<String, Integer> serverMap) {\n        this.servers = new ArrayList<>();\n        for (Entry<String, Integer> entry : serverMap.entrySet()) {\n            for (int n = entry.getValue(), i = 0; i < n; i++) {\n                this.servers.add(entry.getKey());\n            }\n        }\n        Collections.shuffle(this.servers);\n    }\n\n    @Override\n    public String select() {\n        return servers.get(ThreadLocalRandom.current().nextInt(servers.size()));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/loadbalance/WeightRoundRobinLoadBalance.java",
    "content": "package cn.ponfee.commons.loadbalance;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * 加权轮询法\n * \n * @author Ponfee\n */\npublic class WeightRoundRobinLoadBalance extends AbstractLoadBalance {\n\n    private final AtomicLong pos = new AtomicLong(0);\n    private final List<String> servers;\n\n    public WeightRoundRobinLoadBalance(Map<String, Integer> serverMap) {\n        this.servers = new ArrayList<>();\n        for (Entry<String, Integer> entry : serverMap.entrySet()) {\n            for (int n = entry.getValue(), i = 0; i < n; i++) {\n                this.servers.add(entry.getKey());\n            }\n        }\n        Collections.shuffle(this.servers);\n    }\n\n    @Override\n    public String select() {\n        return servers.get((int) (pos.getAndIncrement() % servers.size()));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/loadbalance/package-info.java",
    "content": "/**\n * server load balance\n * \n * @author Ponfee\n */\npackage cn.ponfee.commons.loadbalance;\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/log/JclLogger.java",
    "content": "package cn.ponfee.commons.log;\n\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\n/**\n * \n */\npublic class JclLogger {\n    private static Log logger = LogFactory.getLog(JclLogger.class);\n\n    public static void main(String[] args) {\n        logger.error(\"JclLogger\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/log/JulLogger.java",
    "content": "package cn.ponfee.commons.log;\n\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport org.slf4j.bridge.SLF4JBridgeHandler;\n\n/**\n * \n */\npublic class JulLogger {\n    private static Logger logger = Logger.getLogger(JulLogger.class.getSimpleName());\n\n    public static void main(String[] args) {\n        SLF4JBridgeHandler.removeHandlersForRootLogger();\n        SLF4JBridgeHandler.install();\n        logger.log(Level.SEVERE, \"JulLogger\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/log/Log4jLogger.java",
    "content": "package cn.ponfee.commons.log;\n\nimport org.apache.log4j.Logger;\n\n/**\n * \n */\npublic class Log4jLogger {\n    private static Logger logger = Logger.getLogger(Log4jLogger.class);\n\n    public static void main(String[] args) {\n        logger.error(\"Log4jLogger\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/log/Slf4jLogger.java",
    "content": "package cn.ponfee.commons.log;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Enumeration;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * \n */\npublic class Slf4jLogger {\n    private static Logger logger = LoggerFactory.getLogger(Slf4jLogger.class);\n\n    public static void main(String[] args) throws IOException {\n        logger.error(\"Slf4jLogger\");\n\n        //SystemClassloader==APPClassloader\n        // 加载-classpath(-cp)参数的指定的jar包\n        Enumeration<URL> r = ClassLoader.getSystemResources(\"org/slf4j/impl/StaticLoggerBinder.class\");\n        while (r.hasMoreElements()) {\n            URL url = (URL) r.nextElement();\n            System.out.println(url.getPath());\n        }\n\n        // ExtClassloader\n        // JAVA_HOME/jre/lib/ext/\n\n        // -Xbootclasspath\n        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();\n        for (int i = 0; i < urls.length; i++) {\n            System.out.println(urls[i].toExternalForm());\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/model/ParamsTest.java",
    "content": "package cn.ponfee.commons.model;\n\nimport org.junit.Test;\n\n/**\n * \n * \n * @author Ponfee\n */\npublic class ParamsTest {\n\n    @Test\n    public void test1() {\n        PageParameter params = new PageParameter();\n        params.setSort(\"name,   test   asc\");\n        params.validateSort(\"name\", \"test\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/mybatis/SQLMapperTest.java",
    "content": "package cn.ponfee.commons.mybatis;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport cn.ponfee.commons.data.DataSourceNaming;\n\npublic class SQLMapperTest {\n\n    @DataSourceNaming(\"'primary'\")\n    public void selectScroll() {\n        String sql = \n            \"<script>\"\n          + \"SELECT row_id id, code_attributes->'$.scanCount' AS cnt \"\n          + \"FROM trace_code \"\n          + \"WHERE \"\n          + \"code_attributes->'$.scanCount' IS NOT NULL \"\n          + \"<if test='id!=null'>AND row_id>#{id}</if> \"\n          + \"ORDER BY row_id ASC \"\n          + \"LIMIT 100\"\n          + \"</script>\";\n        AtomicInteger count = new AtomicInteger(0);\n        new SqlMapper(null).selectScroll(sql, new HashMap<>(), Map.class, (param, list) -> {\n            param.put(\"id\", list.get(list.size() - 1).get(\"id\"));\n            count.addAndGet(list.size());\n            return param;\n        });\n        System.out.println(\"=================\"+count.get());\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/reflect/ClassA.java",
    "content": "package cn.ponfee.commons.reflect;\n\n/**\n * \n * \n * @author Ponfee\n */\npublic abstract class ClassA<T> {\n\n \n    \n    public void test1(T arg) {\n        System.out.println(arg);\n    }\n    public T test2() {\n        return (T)\"\";\n    }\n    \n    public static class ClassB extends ClassA<String> {\n        \n    }\n    \n    public static void main(String[] args) throws Exception {\n        System.out.println(GenericUtils.getMethodArgActualType(ClassB.class, ClassB.class.getMethod(\"test1\", Object.class), 0));\n        System.out.println(GenericUtils.getMethodReturnActualType(ClassB.class, ClassB.class.getMethod(\"test1\", Object.class)));\n        \n        System.out.println();\n        System.out.println(GenericUtils.getMethodArgActualType(ClassB.class, ClassB.class.getMethod(\"test1\", String.class), 0));\n        System.out.println(GenericUtils.getMethodReturnActualType(ClassB.class, ClassB.class.getMethod(\"test1\", String.class)));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/reflect/FieldsTest.java",
    "content": "package cn.ponfee.commons.reflect;\n\nimport cn.ponfee.commons.model.BaseEntity;\n\n/**\n * \n * \n * @author Ponfee\n */\npublic class FieldsTest {\n\n    public static class ClassA extends BaseEntity.Creator<String> {\n        private static final long serialVersionUID = -5617457253295566886L;\n\n    }\n\n    public static void main(String[] args) {\n        ClassA a = new ClassA();\n        System.out.println(a.getId());\n        Fields.put(a, \"id\", 999L);\n        System.out.println(a.getId());\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/reflect/GenericExtendsTest.java",
    "content": "package cn.ponfee.commons.reflect;\n\nimport java.util.List;\n\nimport cn.ponfee.commons.model.BaseEntity;\n\npublic class GenericExtendsTest {\n\n    public static class ClassA<U> {\n    }\n\n    public static abstract class ClassB<T> extends ClassA<T> implements List<T> {\n    }\n\n    public static interface InterfaceC<T> extends List<T>, java.io.Serializable {\n    }\n\n    public static void main(String[] args) {\n        System.out.println(GenericUtils.getActualTypeVariableMapping(ClassB.class));\n        System.out.println(GenericUtils.getGenericTypes(ClassB.class));\n        System.out.println(GenericUtils.getGenericTypes(BaseEntity.Updater.class));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/reflect/GenericTest.java",
    "content": "package cn.ponfee.commons.reflect;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.GenericDeclaration;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.TypeVariable;\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport cn.ponfee.commons.model.BaseEntity;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.ws.adapter.MarshalJsonAdapter;\nimport cn.ponfee.commons.ws.adapter.ResultListAdapter;\nimport cn.ponfee.commons.ws.adapter.ResultListMapNormalAdapter;\n\n/**\n * \n * \n * @author Ponfee\n */\npublic class GenericTest {\n\n    @Test\n    public void test0() {\n        Field creator = ClassUtils.getField(BeanClass.class, \"creator\");\n        Field id = ClassUtils.getField(BeanClass.class, \"id\");\n\n        System.out.println(\"\\n1--------------------------------\");\n        System.out.println(creator.getType());\n        System.out.println(creator.getGenericType().getClass());\n\n        System.out.println(\"\\n2--------------------------------\");\n        System.out.println(id.getType());\n        System.out.println(id.getGenericType().getClass());\n\n        System.out.println(\"\\n3--------------------------------\");\n        System.out.println(GenericUtils.getActualTypeArgument(id));\n        System.out.println(GenericUtils.getActualTypeArgument(creator));\n    }\n\n    @Test\n    public void test1() throws Exception {\n        Method method = B.class.getDeclaredMethod(\"setList\", List.class);\n\n        System.out.println(\"====\" + GenericUtils.getActualArgTypeArgument(method, 0, 0));\n\n        java.lang.reflect.Type type = method.getGenericParameterTypes()[0];\n        System.out.println(type.getClass()); // class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl\n        System.out.println(type.getTypeName()); // java.util.List<test.TestGeneric$A>\n\n        System.out.println(\"\\n1--------------------------------\");\n        ParameterizedType ptype = (ParameterizedType) type;\n        System.out.println(ptype.getRawType()); // interface java.util.List\n        System.out.println(ptype.getActualTypeArguments()[0]);\n        System.out.println(ptype.getOwnerType()); // null\n\n        System.out.println(\"\\n2--------------------------------\");\n        Field field = B.class.getDeclaredField(\"list\");\n        System.out.println(\"====\" + GenericUtils.getActualTypeArgument(field, 0));\n        type = field.getGenericType();\n        System.out.println(type.getTypeName()); // java.util.List<test.TestGeneric$A>\n        System.out.println(type.getClass()); // class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl\n\n        System.out.println(\"\\n3--------------------------------\");\n        method = GenericUtils.class.getDeclaredMethod(\"getActualTypeArgument\", Class.class, int.class);\n        System.out.println(ObjectUtils.toString(method.getParameterTypes()));\n        System.out.println(ObjectUtils.toString(method.getGenericParameterTypes()));\n        System.out.println(ObjectUtils.toString(method.getTypeParameters()));\n        System.out.println(ObjectUtils.toString(GenericUtils.getActualArgTypeArgument(method, 0, 0)));\n    }\n\n    @Test\n    public void test3() throws Exception {\n        //System.out.println(GenericUtils.getActualTypeArgument(ClassUtils.getField(B.class, \"f1\")));\n        System.out.println(GenericUtils.getActualTypeArgument(B.class));\n        System.out.println(GenericUtils.getActualTypeArgument(String.class));\n        System.out.println(GenericUtils.getActualTypeArgument(BeanClass.class));\n    }\n\n    @Test\n    public void test4() throws Exception {\n        System.out.println(GenericUtils.getFieldActualType(BeanClass.class, ClassUtils.getField(BeanClass.class, \"id\")));\n        System.out.println(GenericUtils.getFieldActualType(BeanClass.class, ClassUtils.getField(BeanClass.class, \"creator\")));\n        System.out.println(GenericUtils.getFieldActualType(BeanClass2.class, ClassUtils.getField(BeanClass2.class, \"creator\")));\n    }\n\n    @Test\n    public void test5() throws Exception {\n        TypeVariable<?> type = (TypeVariable<?>) ClassUtils.getField(BeanClass.class, \"creator\").getGenericType();\n        GenericDeclaration gd = type.getGenericDeclaration();\n        System.out.println(gd);\n        for (TypeVariable<?> t : gd.getTypeParameters()) {\n            System.out.println(type == t);\n        }\n    }\n\n    @Test\n    public void test6() throws Exception {\n        /*// ResultListAdapter<T>\n        System.out.println(ResultListAdapter.class.getTypeParameters());\n\n        System.out.println(\"\\n=========================================\");\n        // XmlAdapter<Result<ArrayItem<T>>, Result<List<T>>>\n        Type ggs = ResultListAdapter.class.getGenericSuperclass();\n        ParameterizedType pt = (ParameterizedType) ggs;\n        System.out.println(pt.getTypeName()); // <=> toString()\n        System.out.println(pt.getRawType()); // XmlAdapter\n        System.out.println(pt.getOwnerType()); // 内部类的“父类”，如 Map就是 Map.Entry<String,String>的拥有者\n\n        System.out.println(Arrays.toString(((Class<?>) pt.getRawType()).getTypeParameters()));\n        System.out.println(Arrays.toString(pt.getActualTypeArguments()));*/\n\n        System.out.println(GenericUtils.getActualTypeVariableMapping(ResultListMapNormalAdapter.class));\n        System.out.println(GenericUtils.getActualTypeVariableMapping(ResultListAdapter.class));\n        System.out.println(GenericUtils.getActualTypeVariableMapping(MarshalJsonAdapter.class));\n\n        System.out.println(B.class.toGenericString());\n        System.out.println(B.class.getDeclaredMethod(\"setList\", List.class).toGenericString());\n        System.out.println(B.class.getConstructor().toGenericString());\n        \n        System.out.println(B.class.toString());\n        System.out.println(B.class.getDeclaredMethod(\"setList\", List.class).toString());\n        System.out.println(B.class.getConstructor().toString());\n    }\n\n    // -------------------------------------------------------------\n    public static class BeanClass extends BaseEntity.Creator<String> {\n        private static final long serialVersionUID = 1L;\n    }\n    \n    public static class BeanClass2<E> extends BaseEntity.Creator<List<E>> {\n        private static final long serialVersionUID = 1L;\n    }\n\n    public static class A {\n        private int i;\n        private String s;\n\n        public int getI() {\n            return i;\n        }\n\n        public void setI(int i) {\n            this.i = i;\n        }\n\n        public String getS() {\n            return s;\n        }\n\n        public void setS(String s) {\n            this.s = s;\n        }\n    }\n\n    public static class B<E> {\n        private List<? extends A> list;\n        private List<E[]> f1;\n\n        public List<? extends A> getList() {\n            return list;\n        }\n\n        public void setList(List<? extends A> list) {\n            this.list = list;\n        }\n\n        public List<E[]> getF1() {\n            return f1;\n        }\n\n        public void setF1(List<E[]> f1) {\n            this.f1 = f1;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/reflect/GenericTest2.java",
    "content": "package cn.ponfee.commons.reflect;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.Arrays;\n\n/**\n * Hello world!\n *\n */\npublic class GenericTest2 {\n    public static abstract class QueryParams {\n        private String type;\n\n        public String getType() {\n            return type;\n        }\n\n        public void setType(String type) {\n            this.type = type;\n        }\n    }\n\n    public static class LimitedQueryParams extends QueryParams {\n        private int limit;\n\n        public int getLimit() {\n            return limit;\n        }\n\n        public void setLimit(int limit) {\n            this.limit = limit;\n        }\n    }\n\n    public static abstract class QueryExecutor<Q extends QueryParams> {\n        public abstract String query(Q query);\n    }\n\n    public static class DatabaseQueryExecutor extends QueryExecutor<LimitedQueryParams> {\n        @Override\n        public String query(LimitedQueryParams query) {\n            return query.toString();\n        }\n    }\n\n    public static class MysqlQueryExecutor extends DatabaseQueryExecutor {\n    }\n\n    public static void main(String[] args) throws Exception {\n        // class cn.ponfee.commons.reflect.GenericTest2$QueryExecutor\n        System.out.println(QueryExecutor.class.getMethod(\"query\", QueryParams.class).getDeclaringClass());\n\n        // class cn.ponfee.commons.reflect.GenericTest2$DatabaseQueryExecutor\n        System.out.println(DatabaseQueryExecutor.class.getMethod(\"query\", QueryParams.class).getDeclaringClass());\n\n        // class cn.ponfee.commons.reflect.GenericTest2$DatabaseQueryExecutor\n        System.out.println(DatabaseQueryExecutor.class.getMethod(\"query\", LimitedQueryParams.class).getDeclaringClass());\n\n        // class cn.ponfee.commons.reflect.GenericTest2$DatabaseQueryExecutor\n        System.out.println(MysqlQueryExecutor.class.getMethod(\"query\", QueryParams.class).getDeclaringClass());\n\n        // class cn.ponfee.commons.reflect.GenericTest2$DatabaseQueryExecutor\n        System.out.println(MysqlQueryExecutor.class.getMethod(\"query\", LimitedQueryParams.class).getDeclaringClass());\n\n        // ---------------------------------------------------------------------\n        System.out.println(\"\\n\\n\\n\");\n        // class java.lang.Object\n        System.out.println(GenericUtils.getMethodArgActualType(\n            MysqlQueryExecutor.class, QueryExecutor.class.getMethod(\"query\", QueryParams.class), 0)\n        );\n        \n        // class cn.ponfee.commons.reflect.GenericTest2$LimitedQueryParams\n        System.out.println(GenericUtils.getMethodArgActualType(\n           DatabaseQueryExecutor.class, QueryExecutor.class.getMethod(\"query\", QueryParams.class), 0)\n       );\n        \n        System.out.println(\"\\n\\n\\n\");\n        printMethods(QueryExecutor.class);\n        printMethods(DatabaseQueryExecutor.class);\n        printMethods(MysqlQueryExecutor.class);\n    }\n\n    public static void printMethods(Class<?> clazz) {\n        System.out.println(\"\\n\\n-------------------------\" + clazz);\n        Method[] methods = clazz.getMethods();\n        for (Method method : methods) {\n            if (   Modifier.isAbstract(method.getModifiers()) \n                || Modifier.isPrivate(method.getModifiers()) \n                || method.getDeclaringClass() == Object.class\n                || !\"query\".equals(method.getName())\n            ) {\n                continue;\n            }\n            Class<?>[] params = method.getParameterTypes();\n            if (params.length == 1 && QueryParams.class.isAssignableFrom(params[0])) {\n                System.out.println(method.toGenericString());\n                System.out.println(Arrays.toString(method.getParameters()));\n                System.out.println(Arrays.toString(method.getTypeParameters()));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/serial/JacksonObjectMapperTest.java",
    "content": "package cn.ponfee.commons.serial;\n\nimport java.io.Serializable;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\nimport javax.validation.constraints.Email;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\n\nimport org.hibernate.validator.constraints.Length;\nimport org.junit.Test;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.module.SimpleModule;\n\nimport cn.ponfee.commons.json.JacksonDate;\nimport cn.ponfee.commons.date.JavaUtilDateFormat;\n\npublic class JacksonObjectMapperTest {\n\n    static int round = 9999999;\n\n    @Test\n    public void test1() throws JsonProcessingException {\n        ObjectMapper mapper = new ObjectMapper();\n        mapper.setDateFormat(new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\"));\n        Date date = new Date();\n        System.out.println(mapper.writeValueAsString(date));\n        for (int i = 0; i < round; i++) {\n            mapper.writeValueAsString(date);\n        }\n    }\n\n    @Test\n    public void test2() throws JsonProcessingException {\n        ObjectMapper mapper = new ObjectMapper();\n        mapper.setDateFormat(new JavaUtilDateFormat(\"yyyy-MM-dd HH:mm:ss\"));\n        Date date = new Date();\n        System.out.println(mapper.writeValueAsString(date));\n        for (int i = 0; i < round; i++) {\n            mapper.writeValueAsString(date);\n        }\n    }\n\n    @Test\n    public void test3() throws Exception {\n        String json = \"{\\\"id\\\": 0,\\\"title\\\": \\\"\\\",\\\"content\\\": \\\"xxx\\\",\\\"email\\\": \\\"ponfee.cn@gmail.com\\\",\\\"createDate\\\": \\\"20140202\\\",\\\"updateDate\\\": \\\"2019-10-18 16:02:52\\\"}\";\n        ObjectMapper mapper = new ObjectMapper();\n        mapper.setDateFormat(new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\"));\n\n        JavaUtilDateFormat format = new JavaUtilDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n        SimpleModule module = new SimpleModule();\n        JacksonDate jacksonDate = new JacksonDate(format);\n        module.addSerializer(java.util.Date.class, jacksonDate.serializer());\n        module.addDeserializer(java.util.Date.class, jacksonDate.deserializer());\n        mapper.registerModule(module);\n\n        //mapper.setConfig(mapper.getDeserializationConfig().with(format));\n\n        Article a = mapper.readValue(json, Article.class);\n        System.out.println(a.getCreateDate());\n        System.out.println(a.getUpdateDate());\n    }\n\n    public static class Article implements Serializable {\n\n        private static final long serialVersionUID = 1L;\n\n        /** 编号 */\n        private int id;\n\n        /** 标题 */\n        @NotNull(message = \"标题不能为空\")\n        private String title;\n\n        /** 内容 */\n        @Length(min = 10, max = 100, message = \"内容不能少于10个字符\")\n        @NotBlank(message = \"内容不能为空\")\n        private String content;\n\n        @NotEmpty(message = \"邮箱不能为空\")\n        @Email(regexp = \"^\\\\w+((-\\\\w+)|(\\\\.\\\\w+))*\\\\@[A-Za-z0-9]+((\\\\.|-)[A-Za-z0-9]+)*\\\\.[A-Za-z0-9]+$\", message = \"邮箱格式错误\")\n        private String email;\n\n        @DateTimeFormat(pattern = \"yyyy-MM-dd\") // 字符串转日期（需引入joda-time）\n        @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss.SSS\", timezone = \"GMT+8\") // 日期转字符串（覆盖spring-mvc配置）\n        private Date createDate;\n\n        private Date updateDate = new Date(); // 日期转字符串（使用spring-mvc配置）\n\n        public Article() {}\n\n        public Article(int id, String title, String content) {\n            super();\n            this.id = id;\n            this.title = title;\n            this.content = content;\n        }\n\n        public int getId() {\n            return id;\n        }\n\n        public void setId(int id) {\n            this.id = id;\n        }\n\n        public String getTitle() {\n            return title;\n        }\n\n        public void setTitle(String title) {\n            this.title = title;\n        }\n\n        public String getContent() {\n            return content;\n        }\n\n        public void setContent(String content) {\n            this.content = content;\n        }\n\n        public String getEmail() {\n            return email;\n        }\n\n        public void setEmail(String email) {\n            this.email = email;\n        }\n\n        public Date getCreateDate() {\n            return createDate;\n        }\n\n        public void setCreateDate(Date createDate) {\n            this.createDate = createDate;\n        }\n\n        public Date getUpdateDate() {\n            return updateDate;\n        }\n\n        public void setUpdateDate(Date updateDate) {\n            this.updateDate = updateDate;\n        }\n\n        @Override\n        public String toString() {\n            return \"Article [id=\" + id + \", title=\" + title + \", content=\" + content + \"]\";\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/serial/PersonProtobuf.java",
    "content": "// Generated by the protocol buffer compiler.  DO NOT EDIT!\n// source: person.proto\n\npackage cn.ponfee.commons.serial;\n\npublic final class PersonProtobuf {\n  private PersonProtobuf() {}\n  public static void registerAllExtensions(\n      com.google.protobuf.ExtensionRegistryLite registry) {\n  }\n\n  public static void registerAllExtensions(\n      com.google.protobuf.ExtensionRegistry registry) {\n    registerAllExtensions(\n        (com.google.protobuf.ExtensionRegistryLite) registry);\n  }\n  /**\n   * Protobuf enum {@code PhoneType}\n   */\n  public enum PhoneType\n      implements com.google.protobuf.ProtocolMessageEnum {\n    /**\n     * <code>MOBILE = 0;</code>\n     */\n    MOBILE(0),\n    /**\n     * <code>HOME = 1;</code>\n     */\n    HOME(1),\n    /**\n     * <code>WORK = 2;</code>\n     */\n    WORK(2),\n    UNRECOGNIZED(-1),\n    ;\n\n    /**\n     * <code>MOBILE = 0;</code>\n     */\n    public static final int MOBILE_VALUE = 0;\n    /**\n     * <code>HOME = 1;</code>\n     */\n    public static final int HOME_VALUE = 1;\n    /**\n     * <code>WORK = 2;</code>\n     */\n    public static final int WORK_VALUE = 2;\n\n\n    public final int getNumber() {\n      if (this == UNRECOGNIZED) {\n        throw new java.lang.IllegalArgumentException(\n            \"Can't get the number of an unknown enum value.\");\n      }\n      return value;\n    }\n\n    /**\n     * @deprecated Use {@link #forNumber(int)} instead.\n     */\n    @java.lang.Deprecated\n    public static PhoneType valueOf(int value) {\n      return forNumber(value);\n    }\n\n    public static PhoneType forNumber(int value) {\n      switch (value) {\n        case 0: return MOBILE;\n        case 1: return HOME;\n        case 2: return WORK;\n        default: return null;\n      }\n    }\n\n    public static com.google.protobuf.Internal.EnumLiteMap<PhoneType>\n        internalGetValueMap() {\n      return internalValueMap;\n    }\n    private static final com.google.protobuf.Internal.EnumLiteMap<\n        PhoneType> internalValueMap =\n          new com.google.protobuf.Internal.EnumLiteMap<PhoneType>() {\n            public PhoneType findValueByNumber(int number) {\n              return PhoneType.forNumber(number);\n            }\n          };\n\n    public final com.google.protobuf.Descriptors.EnumValueDescriptor\n        getValueDescriptor() {\n      return getDescriptor().getValues().get(ordinal());\n    }\n    public final com.google.protobuf.Descriptors.EnumDescriptor\n        getDescriptorForType() {\n      return getDescriptor();\n    }\n    public static final com.google.protobuf.Descriptors.EnumDescriptor\n        getDescriptor() {\n      return cn.ponfee.commons.serial.PersonProtobuf.getDescriptor().getEnumTypes().get(0);\n    }\n\n    private static final PhoneType[] VALUES = values();\n\n    public static PhoneType valueOf(\n        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {\n      if (desc.getType() != getDescriptor()) {\n        throw new java.lang.IllegalArgumentException(\n          \"EnumValueDescriptor is not for this type.\");\n      }\n      if (desc.getIndex() == -1) {\n        return UNRECOGNIZED;\n      }\n      return VALUES[desc.getIndex()];\n    }\n\n    private final int value;\n\n    private PhoneType(int value) {\n      this.value = value;\n    }\n\n    // @@protoc_insertion_point(enum_scope:PhoneType)\n  }\n\n  public interface PersonOrBuilder extends\n      // @@protoc_insertion_point(interface_extends:Person)\n      com.google.protobuf.MessageOrBuilder {\n\n    /**\n     * <code>int32 id = 1;</code>\n     */\n    int getId();\n\n    /**\n     * <code>string name = 2;</code>\n     */\n    java.lang.String getName();\n    /**\n     * <code>string name = 2;</code>\n     */\n    com.google.protobuf.ByteString\n        getNameBytes();\n\n    /**\n     * <code>int32 age = 3;</code>\n     */\n    int getAge();\n\n    /**\n     * <code>.Addr addr = 4;</code>\n     */\n    boolean hasAddr();\n    /**\n     * <code>.Addr addr = 4;</code>\n     */\n    cn.ponfee.commons.serial.PersonProtobuf.Addr getAddr();\n    /**\n     * <code>.Addr addr = 4;</code>\n     */\n    cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder getAddrOrBuilder();\n\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    java.util.List<cn.ponfee.commons.serial.PersonProtobuf.Phone> \n        getPhoneList();\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    cn.ponfee.commons.serial.PersonProtobuf.Phone getPhone(int index);\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    int getPhoneCount();\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    java.util.List<? extends cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder> \n        getPhoneOrBuilderList();\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder getPhoneOrBuilder(\n        int index);\n  }\n  /**\n   * Protobuf type {@code Person}\n   */\n  public  static final class Person extends\n      com.google.protobuf.GeneratedMessageV3 implements\n      // @@protoc_insertion_point(message_implements:Person)\n      PersonOrBuilder {\n  private static final long serialVersionUID = 0L;\n    // Use Person.newBuilder() to construct.\n    private Person(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {\n      super(builder);\n    }\n    private Person() {\n      name_ = \"\";\n      phone_ = java.util.Collections.emptyList();\n    }\n\n    @java.lang.Override\n    public final com.google.protobuf.UnknownFieldSet\n    getUnknownFields() {\n      return this.unknownFields;\n    }\n    private Person(\n        com.google.protobuf.CodedInputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      this();\n      if (extensionRegistry == null) {\n        throw new java.lang.NullPointerException();\n      }\n      int mutable_bitField0_ = 0;\n      com.google.protobuf.UnknownFieldSet.Builder unknownFields =\n          com.google.protobuf.UnknownFieldSet.newBuilder();\n      try {\n        boolean done = false;\n        while (!done) {\n          int tag = input.readTag();\n          switch (tag) {\n            case 0:\n              done = true;\n              break;\n            case 8: {\n\n              id_ = input.readInt32();\n              break;\n            }\n            case 18: {\n              java.lang.String s = input.readStringRequireUtf8();\n\n              name_ = s;\n              break;\n            }\n            case 24: {\n\n              age_ = input.readInt32();\n              break;\n            }\n            case 34: {\n              cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder subBuilder = null;\n              if (addr_ != null) {\n                subBuilder = addr_.toBuilder();\n              }\n              addr_ = input.readMessage(cn.ponfee.commons.serial.PersonProtobuf.Addr.parser(), extensionRegistry);\n              if (subBuilder != null) {\n                subBuilder.mergeFrom(addr_);\n                addr_ = subBuilder.buildPartial();\n              }\n\n              break;\n            }\n            case 42: {\n              if (!((mutable_bitField0_ & 0x00000010) != 0)) {\n                phone_ = new java.util.ArrayList<cn.ponfee.commons.serial.PersonProtobuf.Phone>();\n                mutable_bitField0_ |= 0x00000010;\n              }\n              phone_.add(\n                  input.readMessage(cn.ponfee.commons.serial.PersonProtobuf.Phone.parser(), extensionRegistry));\n              break;\n            }\n            default: {\n              if (!parseUnknownField(\n                  input, unknownFields, extensionRegistry, tag)) {\n                done = true;\n              }\n              break;\n            }\n          }\n        }\n      } catch (com.google.protobuf.InvalidProtocolBufferException e) {\n        throw e.setUnfinishedMessage(this);\n      } catch (java.io.IOException e) {\n        throw new com.google.protobuf.InvalidProtocolBufferException(\n            e).setUnfinishedMessage(this);\n      } finally {\n        if (((mutable_bitField0_ & 0x00000010) != 0)) {\n          phone_ = java.util.Collections.unmodifiableList(phone_);\n        }\n        this.unknownFields = unknownFields.build();\n        makeExtensionsImmutable();\n      }\n    }\n    public static final com.google.protobuf.Descriptors.Descriptor\n        getDescriptor() {\n      return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_descriptor;\n    }\n\n    @java.lang.Override\n    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable\n        internalGetFieldAccessorTable() {\n      return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_fieldAccessorTable\n          .ensureFieldAccessorsInitialized(\n              cn.ponfee.commons.serial.PersonProtobuf.Person.class, cn.ponfee.commons.serial.PersonProtobuf.Person.Builder.class);\n    }\n\n    private int bitField0_;\n    public static final int ID_FIELD_NUMBER = 1;\n    private int id_;\n    /**\n     * <code>int32 id = 1;</code>\n     */\n    public int getId() {\n      return id_;\n    }\n\n    public static final int NAME_FIELD_NUMBER = 2;\n    private volatile java.lang.Object name_;\n    /**\n     * <code>string name = 2;</code>\n     */\n    public java.lang.String getName() {\n      java.lang.Object ref = name_;\n      if (ref instanceof java.lang.String) {\n        return (java.lang.String) ref;\n      } else {\n        com.google.protobuf.ByteString bs = \n            (com.google.protobuf.ByteString) ref;\n        java.lang.String s = bs.toStringUtf8();\n        name_ = s;\n        return s;\n      }\n    }\n    /**\n     * <code>string name = 2;</code>\n     */\n    public com.google.protobuf.ByteString\n        getNameBytes() {\n      java.lang.Object ref = name_;\n      if (ref instanceof java.lang.String) {\n        com.google.protobuf.ByteString b = \n            com.google.protobuf.ByteString.copyFromUtf8(\n                (java.lang.String) ref);\n        name_ = b;\n        return b;\n      } else {\n        return (com.google.protobuf.ByteString) ref;\n      }\n    }\n\n    public static final int AGE_FIELD_NUMBER = 3;\n    private int age_;\n    /**\n     * <code>int32 age = 3;</code>\n     */\n    public int getAge() {\n      return age_;\n    }\n\n    public static final int ADDR_FIELD_NUMBER = 4;\n    private cn.ponfee.commons.serial.PersonProtobuf.Addr addr_;\n    /**\n     * <code>.Addr addr = 4;</code>\n     */\n    public boolean hasAddr() {\n      return addr_ != null;\n    }\n    /**\n     * <code>.Addr addr = 4;</code>\n     */\n    public cn.ponfee.commons.serial.PersonProtobuf.Addr getAddr() {\n      return addr_ == null ? cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance() : addr_;\n    }\n    /**\n     * <code>.Addr addr = 4;</code>\n     */\n    public cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder getAddrOrBuilder() {\n      return getAddr();\n    }\n\n    public static final int PHONE_FIELD_NUMBER = 5;\n    private java.util.List<cn.ponfee.commons.serial.PersonProtobuf.Phone> phone_;\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    public java.util.List<cn.ponfee.commons.serial.PersonProtobuf.Phone> getPhoneList() {\n      return phone_;\n    }\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    public java.util.List<? extends cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder> \n        getPhoneOrBuilderList() {\n      return phone_;\n    }\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    public int getPhoneCount() {\n      return phone_.size();\n    }\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    public cn.ponfee.commons.serial.PersonProtobuf.Phone getPhone(int index) {\n      return phone_.get(index);\n    }\n    /**\n     * <code>repeated .Phone phone = 5;</code>\n     */\n    public cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder getPhoneOrBuilder(\n        int index) {\n      return phone_.get(index);\n    }\n\n    private byte memoizedIsInitialized = -1;\n    @java.lang.Override\n    public final boolean isInitialized() {\n      byte isInitialized = memoizedIsInitialized;\n      if (isInitialized == 1) return true;\n      if (isInitialized == 0) return false;\n\n      memoizedIsInitialized = 1;\n      return true;\n    }\n\n    @java.lang.Override\n    public void writeTo(com.google.protobuf.CodedOutputStream output)\n                        throws java.io.IOException {\n      if (id_ != 0) {\n        output.writeInt32(1, id_);\n      }\n      if (!getNameBytes().isEmpty()) {\n        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, name_);\n      }\n      if (age_ != 0) {\n        output.writeInt32(3, age_);\n      }\n      if (addr_ != null) {\n        output.writeMessage(4, getAddr());\n      }\n      for (int i = 0; i < phone_.size(); i++) {\n        output.writeMessage(5, phone_.get(i));\n      }\n      unknownFields.writeTo(output);\n    }\n\n    @java.lang.Override\n    public int getSerializedSize() {\n      int size = memoizedSize;\n      if (size != -1) return size;\n\n      size = 0;\n      if (id_ != 0) {\n        size += com.google.protobuf.CodedOutputStream\n          .computeInt32Size(1, id_);\n      }\n      if (!getNameBytes().isEmpty()) {\n        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, name_);\n      }\n      if (age_ != 0) {\n        size += com.google.protobuf.CodedOutputStream\n          .computeInt32Size(3, age_);\n      }\n      if (addr_ != null) {\n        size += com.google.protobuf.CodedOutputStream\n          .computeMessageSize(4, getAddr());\n      }\n      for (int i = 0; i < phone_.size(); i++) {\n        size += com.google.protobuf.CodedOutputStream\n          .computeMessageSize(5, phone_.get(i));\n      }\n      size += unknownFields.getSerializedSize();\n      memoizedSize = size;\n      return size;\n    }\n\n    @java.lang.Override\n    public boolean equals(final java.lang.Object obj) {\n      if (obj == this) {\n       return true;\n      }\n      if (!(obj instanceof cn.ponfee.commons.serial.PersonProtobuf.Person)) {\n        return super.equals(obj);\n      }\n      cn.ponfee.commons.serial.PersonProtobuf.Person other = (cn.ponfee.commons.serial.PersonProtobuf.Person) obj;\n\n      if (getId()\n          != other.getId()) return false;\n      if (!getName()\n          .equals(other.getName())) return false;\n      if (getAge()\n          != other.getAge()) return false;\n      if (hasAddr() != other.hasAddr()) return false;\n      if (hasAddr()) {\n        if (!getAddr()\n            .equals(other.getAddr())) return false;\n      }\n      if (!getPhoneList()\n          .equals(other.getPhoneList())) return false;\n      if (!unknownFields.equals(other.unknownFields)) return false;\n      return true;\n    }\n\n    @java.lang.Override\n    public int hashCode() {\n      if (memoizedHashCode != 0) {\n        return memoizedHashCode;\n      }\n      int hash = 41;\n      hash = (19 * hash) + getDescriptor().hashCode();\n      hash = (37 * hash) + ID_FIELD_NUMBER;\n      hash = (53 * hash) + getId();\n      hash = (37 * hash) + NAME_FIELD_NUMBER;\n      hash = (53 * hash) + getName().hashCode();\n      hash = (37 * hash) + AGE_FIELD_NUMBER;\n      hash = (53 * hash) + getAge();\n      if (hasAddr()) {\n        hash = (37 * hash) + ADDR_FIELD_NUMBER;\n        hash = (53 * hash) + getAddr().hashCode();\n      }\n      if (getPhoneCount() > 0) {\n        hash = (37 * hash) + PHONE_FIELD_NUMBER;\n        hash = (53 * hash) + getPhoneList().hashCode();\n      }\n      hash = (29 * hash) + unknownFields.hashCode();\n      memoizedHashCode = hash;\n      return hash;\n    }\n\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(\n        java.nio.ByteBuffer data)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(\n        java.nio.ByteBuffer data,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(\n        com.google.protobuf.ByteString data)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(\n        com.google.protobuf.ByteString data,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(byte[] data)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(\n        byte[] data,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(java.io.InputStream input)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(\n        java.io.InputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseDelimitedFrom(java.io.InputStream input)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseDelimitedWithIOException(PARSER, input);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseDelimitedFrom(\n        java.io.InputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(\n        com.google.protobuf.CodedInputStream input)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person parseFrom(\n        com.google.protobuf.CodedInputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input, extensionRegistry);\n    }\n\n    @java.lang.Override\n    public Builder newBuilderForType() { return newBuilder(); }\n    public static Builder newBuilder() {\n      return DEFAULT_INSTANCE.toBuilder();\n    }\n    public static Builder newBuilder(cn.ponfee.commons.serial.PersonProtobuf.Person prototype) {\n      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);\n    }\n    @java.lang.Override\n    public Builder toBuilder() {\n      return this == DEFAULT_INSTANCE\n          ? new Builder() : new Builder().mergeFrom(this);\n    }\n\n    @java.lang.Override\n    protected Builder newBuilderForType(\n        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {\n      Builder builder = new Builder(parent);\n      return builder;\n    }\n    /**\n     * Protobuf type {@code Person}\n     */\n    public static final class Builder extends\n        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements\n        // @@protoc_insertion_point(builder_implements:Person)\n        cn.ponfee.commons.serial.PersonProtobuf.PersonOrBuilder {\n      public static final com.google.protobuf.Descriptors.Descriptor\n          getDescriptor() {\n        return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_descriptor;\n      }\n\n      @java.lang.Override\n      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable\n          internalGetFieldAccessorTable() {\n        return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_fieldAccessorTable\n            .ensureFieldAccessorsInitialized(\n                cn.ponfee.commons.serial.PersonProtobuf.Person.class, cn.ponfee.commons.serial.PersonProtobuf.Person.Builder.class);\n      }\n\n      // Construct using cn.ponfee.commons.serial.PersonProtobuf.Person.newBuilder()\n      private Builder() {\n        maybeForceBuilderInitialization();\n      }\n\n      private Builder(\n          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {\n        super(parent);\n        maybeForceBuilderInitialization();\n      }\n      private void maybeForceBuilderInitialization() {\n        if (com.google.protobuf.GeneratedMessageV3\n                .alwaysUseFieldBuilders) {\n          getPhoneFieldBuilder();\n        }\n      }\n      @java.lang.Override\n      public Builder clear() {\n        super.clear();\n        id_ = 0;\n\n        name_ = \"\";\n\n        age_ = 0;\n\n        if (addrBuilder_ == null) {\n          addr_ = null;\n        } else {\n          addr_ = null;\n          addrBuilder_ = null;\n        }\n        if (phoneBuilder_ == null) {\n          phone_ = java.util.Collections.emptyList();\n          bitField0_ = (bitField0_ & ~0x00000010);\n        } else {\n          phoneBuilder_.clear();\n        }\n        return this;\n      }\n\n      @java.lang.Override\n      public com.google.protobuf.Descriptors.Descriptor\n          getDescriptorForType() {\n        return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Person_descriptor;\n      }\n\n      @java.lang.Override\n      public cn.ponfee.commons.serial.PersonProtobuf.Person getDefaultInstanceForType() {\n        return cn.ponfee.commons.serial.PersonProtobuf.Person.getDefaultInstance();\n      }\n\n      @java.lang.Override\n      public cn.ponfee.commons.serial.PersonProtobuf.Person build() {\n        cn.ponfee.commons.serial.PersonProtobuf.Person result = buildPartial();\n        if (!result.isInitialized()) {\n          throw newUninitializedMessageException(result);\n        }\n        return result;\n      }\n\n      @java.lang.Override\n      public cn.ponfee.commons.serial.PersonProtobuf.Person buildPartial() {\n        cn.ponfee.commons.serial.PersonProtobuf.Person result = new cn.ponfee.commons.serial.PersonProtobuf.Person(this);\n        int from_bitField0_ = bitField0_;\n        int to_bitField0_ = 0;\n        result.id_ = id_;\n        result.name_ = name_;\n        result.age_ = age_;\n        if (addrBuilder_ == null) {\n          result.addr_ = addr_;\n        } else {\n          result.addr_ = addrBuilder_.build();\n        }\n        if (phoneBuilder_ == null) {\n          if (((bitField0_ & 0x00000010) != 0)) {\n            phone_ = java.util.Collections.unmodifiableList(phone_);\n            bitField0_ = (bitField0_ & ~0x00000010);\n          }\n          result.phone_ = phone_;\n        } else {\n          result.phone_ = phoneBuilder_.build();\n        }\n        result.bitField0_ = to_bitField0_;\n        onBuilt();\n        return result;\n      }\n\n      @java.lang.Override\n      public Builder clone() {\n        return super.clone();\n      }\n      @java.lang.Override\n      public Builder setField(\n          com.google.protobuf.Descriptors.FieldDescriptor field,\n          java.lang.Object value) {\n        return super.setField(field, value);\n      }\n      @java.lang.Override\n      public Builder clearField(\n          com.google.protobuf.Descriptors.FieldDescriptor field) {\n        return super.clearField(field);\n      }\n      @java.lang.Override\n      public Builder clearOneof(\n          com.google.protobuf.Descriptors.OneofDescriptor oneof) {\n        return super.clearOneof(oneof);\n      }\n      @java.lang.Override\n      public Builder setRepeatedField(\n          com.google.protobuf.Descriptors.FieldDescriptor field,\n          int index, java.lang.Object value) {\n        return super.setRepeatedField(field, index, value);\n      }\n      @java.lang.Override\n      public Builder addRepeatedField(\n          com.google.protobuf.Descriptors.FieldDescriptor field,\n          java.lang.Object value) {\n        return super.addRepeatedField(field, value);\n      }\n      @java.lang.Override\n      public Builder mergeFrom(com.google.protobuf.Message other) {\n        if (other instanceof cn.ponfee.commons.serial.PersonProtobuf.Person) {\n          return mergeFrom((cn.ponfee.commons.serial.PersonProtobuf.Person)other);\n        } else {\n          super.mergeFrom(other);\n          return this;\n        }\n      }\n\n      public Builder mergeFrom(cn.ponfee.commons.serial.PersonProtobuf.Person other) {\n        if (other == cn.ponfee.commons.serial.PersonProtobuf.Person.getDefaultInstance()) return this;\n        if (other.getId() != 0) {\n          setId(other.getId());\n        }\n        if (!other.getName().isEmpty()) {\n          name_ = other.name_;\n          onChanged();\n        }\n        if (other.getAge() != 0) {\n          setAge(other.getAge());\n        }\n        if (other.hasAddr()) {\n          mergeAddr(other.getAddr());\n        }\n        if (phoneBuilder_ == null) {\n          if (!other.phone_.isEmpty()) {\n            if (phone_.isEmpty()) {\n              phone_ = other.phone_;\n              bitField0_ = (bitField0_ & ~0x00000010);\n            } else {\n              ensurePhoneIsMutable();\n              phone_.addAll(other.phone_);\n            }\n            onChanged();\n          }\n        } else {\n          if (!other.phone_.isEmpty()) {\n            if (phoneBuilder_.isEmpty()) {\n              phoneBuilder_.dispose();\n              phoneBuilder_ = null;\n              phone_ = other.phone_;\n              bitField0_ = (bitField0_ & ~0x00000010);\n              phoneBuilder_ = \n                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?\n                   getPhoneFieldBuilder() : null;\n            } else {\n              phoneBuilder_.addAllMessages(other.phone_);\n            }\n          }\n        }\n        this.mergeUnknownFields(other.unknownFields);\n        onChanged();\n        return this;\n      }\n\n      @java.lang.Override\n      public final boolean isInitialized() {\n        return true;\n      }\n\n      @java.lang.Override\n      public Builder mergeFrom(\n          com.google.protobuf.CodedInputStream input,\n          com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n          throws java.io.IOException {\n        cn.ponfee.commons.serial.PersonProtobuf.Person parsedMessage = null;\n        try {\n          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);\n        } catch (com.google.protobuf.InvalidProtocolBufferException e) {\n          parsedMessage = (cn.ponfee.commons.serial.PersonProtobuf.Person) e.getUnfinishedMessage();\n          throw e.unwrapIOException();\n        } finally {\n          if (parsedMessage != null) {\n            mergeFrom(parsedMessage);\n          }\n        }\n        return this;\n      }\n      private int bitField0_;\n\n      private int id_ ;\n      /**\n       * <code>int32 id = 1;</code>\n       */\n      public int getId() {\n        return id_;\n      }\n      /**\n       * <code>int32 id = 1;</code>\n       */\n      public Builder setId(int value) {\n        \n        id_ = value;\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>int32 id = 1;</code>\n       */\n      public Builder clearId() {\n        \n        id_ = 0;\n        onChanged();\n        return this;\n      }\n\n      private java.lang.Object name_ = \"\";\n      /**\n       * <code>string name = 2;</code>\n       */\n      public java.lang.String getName() {\n        java.lang.Object ref = name_;\n        if (!(ref instanceof java.lang.String)) {\n          com.google.protobuf.ByteString bs =\n              (com.google.protobuf.ByteString) ref;\n          java.lang.String s = bs.toStringUtf8();\n          name_ = s;\n          return s;\n        } else {\n          return (java.lang.String) ref;\n        }\n      }\n      /**\n       * <code>string name = 2;</code>\n       */\n      public com.google.protobuf.ByteString\n          getNameBytes() {\n        java.lang.Object ref = name_;\n        if (ref instanceof String) {\n          com.google.protobuf.ByteString b = \n              com.google.protobuf.ByteString.copyFromUtf8(\n                  (java.lang.String) ref);\n          name_ = b;\n          return b;\n        } else {\n          return (com.google.protobuf.ByteString) ref;\n        }\n      }\n      /**\n       * <code>string name = 2;</code>\n       */\n      public Builder setName(\n          java.lang.String value) {\n        if (value == null) {\n    throw new NullPointerException();\n  }\n  \n        name_ = value;\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>string name = 2;</code>\n       */\n      public Builder clearName() {\n        \n        name_ = getDefaultInstance().getName();\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>string name = 2;</code>\n       */\n      public Builder setNameBytes(\n          com.google.protobuf.ByteString value) {\n        if (value == null) {\n    throw new NullPointerException();\n  }\n  checkByteStringIsUtf8(value);\n        \n        name_ = value;\n        onChanged();\n        return this;\n      }\n\n      private int age_ ;\n      /**\n       * <code>int32 age = 3;</code>\n       */\n      public int getAge() {\n        return age_;\n      }\n      /**\n       * <code>int32 age = 3;</code>\n       */\n      public Builder setAge(int value) {\n        \n        age_ = value;\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>int32 age = 3;</code>\n       */\n      public Builder clearAge() {\n        \n        age_ = 0;\n        onChanged();\n        return this;\n      }\n\n      private cn.ponfee.commons.serial.PersonProtobuf.Addr addr_;\n      private com.google.protobuf.SingleFieldBuilderV3<\n          cn.ponfee.commons.serial.PersonProtobuf.Addr, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder, cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder> addrBuilder_;\n      /**\n       * <code>.Addr addr = 4;</code>\n       */\n      public boolean hasAddr() {\n        return addrBuilder_ != null || addr_ != null;\n      }\n      /**\n       * <code>.Addr addr = 4;</code>\n       */\n      public cn.ponfee.commons.serial.PersonProtobuf.Addr getAddr() {\n        if (addrBuilder_ == null) {\n          return addr_ == null ? cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance() : addr_;\n        } else {\n          return addrBuilder_.getMessage();\n        }\n      }\n      /**\n       * <code>.Addr addr = 4;</code>\n       */\n      public Builder setAddr(cn.ponfee.commons.serial.PersonProtobuf.Addr value) {\n        if (addrBuilder_ == null) {\n          if (value == null) {\n            throw new NullPointerException();\n          }\n          addr_ = value;\n          onChanged();\n        } else {\n          addrBuilder_.setMessage(value);\n        }\n\n        return this;\n      }\n      /**\n       * <code>.Addr addr = 4;</code>\n       */\n      public Builder setAddr(\n          cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder builderForValue) {\n        if (addrBuilder_ == null) {\n          addr_ = builderForValue.build();\n          onChanged();\n        } else {\n          addrBuilder_.setMessage(builderForValue.build());\n        }\n\n        return this;\n      }\n      /**\n       * <code>.Addr addr = 4;</code>\n       */\n      public Builder mergeAddr(cn.ponfee.commons.serial.PersonProtobuf.Addr value) {\n        if (addrBuilder_ == null) {\n          if (addr_ != null) {\n            addr_ =\n              cn.ponfee.commons.serial.PersonProtobuf.Addr.newBuilder(addr_).mergeFrom(value).buildPartial();\n          } else {\n            addr_ = value;\n          }\n          onChanged();\n        } else {\n          addrBuilder_.mergeFrom(value);\n        }\n\n        return this;\n      }\n      /**\n       * <code>.Addr addr = 4;</code>\n       */\n      public Builder clearAddr() {\n        if (addrBuilder_ == null) {\n          addr_ = null;\n          onChanged();\n        } else {\n          addr_ = null;\n          addrBuilder_ = null;\n        }\n\n        return this;\n      }\n      /**\n       * <code>.Addr addr = 4;</code>\n       */\n      public cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder getAddrBuilder() {\n        \n        onChanged();\n        return getAddrFieldBuilder().getBuilder();\n      }\n      /**\n       * <code>.Addr addr = 4;</code>\n       */\n      public cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder getAddrOrBuilder() {\n        if (addrBuilder_ != null) {\n          return addrBuilder_.getMessageOrBuilder();\n        } else {\n          return addr_ == null ?\n              cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance() : addr_;\n        }\n      }\n      /**\n       * <code>.Addr addr = 4;</code>\n       */\n      private com.google.protobuf.SingleFieldBuilderV3<\n          cn.ponfee.commons.serial.PersonProtobuf.Addr, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder, cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder> \n          getAddrFieldBuilder() {\n        if (addrBuilder_ == null) {\n          addrBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<\n              cn.ponfee.commons.serial.PersonProtobuf.Addr, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder, cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder>(\n                  getAddr(),\n                  getParentForChildren(),\n                  isClean());\n          addr_ = null;\n        }\n        return addrBuilder_;\n      }\n\n      private java.util.List<cn.ponfee.commons.serial.PersonProtobuf.Phone> phone_ =\n        java.util.Collections.emptyList();\n      private void ensurePhoneIsMutable() {\n        if (!((bitField0_ & 0x00000010) != 0)) {\n          phone_ = new java.util.ArrayList<cn.ponfee.commons.serial.PersonProtobuf.Phone>(phone_);\n          bitField0_ |= 0x00000010;\n         }\n      }\n\n      private com.google.protobuf.RepeatedFieldBuilderV3<\n          cn.ponfee.commons.serial.PersonProtobuf.Phone, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder, cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder> phoneBuilder_;\n\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public java.util.List<cn.ponfee.commons.serial.PersonProtobuf.Phone> getPhoneList() {\n        if (phoneBuilder_ == null) {\n          return java.util.Collections.unmodifiableList(phone_);\n        } else {\n          return phoneBuilder_.getMessageList();\n        }\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public int getPhoneCount() {\n        if (phoneBuilder_ == null) {\n          return phone_.size();\n        } else {\n          return phoneBuilder_.getCount();\n        }\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public cn.ponfee.commons.serial.PersonProtobuf.Phone getPhone(int index) {\n        if (phoneBuilder_ == null) {\n          return phone_.get(index);\n        } else {\n          return phoneBuilder_.getMessage(index);\n        }\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public Builder setPhone(\n          int index, cn.ponfee.commons.serial.PersonProtobuf.Phone value) {\n        if (phoneBuilder_ == null) {\n          if (value == null) {\n            throw new NullPointerException();\n          }\n          ensurePhoneIsMutable();\n          phone_.set(index, value);\n          onChanged();\n        } else {\n          phoneBuilder_.setMessage(index, value);\n        }\n        return this;\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public Builder setPhone(\n          int index, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder builderForValue) {\n        if (phoneBuilder_ == null) {\n          ensurePhoneIsMutable();\n          phone_.set(index, builderForValue.build());\n          onChanged();\n        } else {\n          phoneBuilder_.setMessage(index, builderForValue.build());\n        }\n        return this;\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public Builder addPhone(cn.ponfee.commons.serial.PersonProtobuf.Phone value) {\n        if (phoneBuilder_ == null) {\n          if (value == null) {\n            throw new NullPointerException();\n          }\n          ensurePhoneIsMutable();\n          phone_.add(value);\n          onChanged();\n        } else {\n          phoneBuilder_.addMessage(value);\n        }\n        return this;\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public Builder addPhone(\n          int index, cn.ponfee.commons.serial.PersonProtobuf.Phone value) {\n        if (phoneBuilder_ == null) {\n          if (value == null) {\n            throw new NullPointerException();\n          }\n          ensurePhoneIsMutable();\n          phone_.add(index, value);\n          onChanged();\n        } else {\n          phoneBuilder_.addMessage(index, value);\n        }\n        return this;\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public Builder addPhone(\n          cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder builderForValue) {\n        if (phoneBuilder_ == null) {\n          ensurePhoneIsMutable();\n          phone_.add(builderForValue.build());\n          onChanged();\n        } else {\n          phoneBuilder_.addMessage(builderForValue.build());\n        }\n        return this;\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public Builder addPhone(\n          int index, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder builderForValue) {\n        if (phoneBuilder_ == null) {\n          ensurePhoneIsMutable();\n          phone_.add(index, builderForValue.build());\n          onChanged();\n        } else {\n          phoneBuilder_.addMessage(index, builderForValue.build());\n        }\n        return this;\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public Builder addAllPhone(\n          java.lang.Iterable<? extends cn.ponfee.commons.serial.PersonProtobuf.Phone> values) {\n        if (phoneBuilder_ == null) {\n          ensurePhoneIsMutable();\n          com.google.protobuf.AbstractMessageLite.Builder.addAll(\n              values, phone_);\n          onChanged();\n        } else {\n          phoneBuilder_.addAllMessages(values);\n        }\n        return this;\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public Builder clearPhone() {\n        if (phoneBuilder_ == null) {\n          phone_ = java.util.Collections.emptyList();\n          bitField0_ = (bitField0_ & ~0x00000010);\n          onChanged();\n        } else {\n          phoneBuilder_.clear();\n        }\n        return this;\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public Builder removePhone(int index) {\n        if (phoneBuilder_ == null) {\n          ensurePhoneIsMutable();\n          phone_.remove(index);\n          onChanged();\n        } else {\n          phoneBuilder_.remove(index);\n        }\n        return this;\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder getPhoneBuilder(\n          int index) {\n        return getPhoneFieldBuilder().getBuilder(index);\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder getPhoneOrBuilder(\n          int index) {\n        if (phoneBuilder_ == null) {\n          return phone_.get(index);  } else {\n          return phoneBuilder_.getMessageOrBuilder(index);\n        }\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public java.util.List<? extends cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder> \n           getPhoneOrBuilderList() {\n        if (phoneBuilder_ != null) {\n          return phoneBuilder_.getMessageOrBuilderList();\n        } else {\n          return java.util.Collections.unmodifiableList(phone_);\n        }\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder addPhoneBuilder() {\n        return getPhoneFieldBuilder().addBuilder(\n            cn.ponfee.commons.serial.PersonProtobuf.Phone.getDefaultInstance());\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder addPhoneBuilder(\n          int index) {\n        return getPhoneFieldBuilder().addBuilder(\n            index, cn.ponfee.commons.serial.PersonProtobuf.Phone.getDefaultInstance());\n      }\n      /**\n       * <code>repeated .Phone phone = 5;</code>\n       */\n      public java.util.List<cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder> \n           getPhoneBuilderList() {\n        return getPhoneFieldBuilder().getBuilderList();\n      }\n      private com.google.protobuf.RepeatedFieldBuilderV3<\n          cn.ponfee.commons.serial.PersonProtobuf.Phone, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder, cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder> \n          getPhoneFieldBuilder() {\n        if (phoneBuilder_ == null) {\n          phoneBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<\n              cn.ponfee.commons.serial.PersonProtobuf.Phone, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder, cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder>(\n                  phone_,\n                  ((bitField0_ & 0x00000010) != 0),\n                  getParentForChildren(),\n                  isClean());\n          phone_ = null;\n        }\n        return phoneBuilder_;\n      }\n      @java.lang.Override\n      public final Builder setUnknownFields(\n          final com.google.protobuf.UnknownFieldSet unknownFields) {\n        return super.setUnknownFields(unknownFields);\n      }\n\n      @java.lang.Override\n      public final Builder mergeUnknownFields(\n          final com.google.protobuf.UnknownFieldSet unknownFields) {\n        return super.mergeUnknownFields(unknownFields);\n      }\n\n\n      // @@protoc_insertion_point(builder_scope:Person)\n    }\n\n    // @@protoc_insertion_point(class_scope:Person)\n    private static final cn.ponfee.commons.serial.PersonProtobuf.Person DEFAULT_INSTANCE;\n    static {\n      DEFAULT_INSTANCE = new cn.ponfee.commons.serial.PersonProtobuf.Person();\n    }\n\n    public static cn.ponfee.commons.serial.PersonProtobuf.Person getDefaultInstance() {\n      return DEFAULT_INSTANCE;\n    }\n\n    private static final com.google.protobuf.Parser<Person>\n        PARSER = new com.google.protobuf.AbstractParser<Person>() {\n      @java.lang.Override\n      public Person parsePartialFrom(\n          com.google.protobuf.CodedInputStream input,\n          com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n          throws com.google.protobuf.InvalidProtocolBufferException {\n        return new Person(input, extensionRegistry);\n      }\n    };\n\n    public static com.google.protobuf.Parser<Person> parser() {\n      return PARSER;\n    }\n\n    @java.lang.Override\n    public com.google.protobuf.Parser<Person> getParserForType() {\n      return PARSER;\n    }\n\n    @java.lang.Override\n    public cn.ponfee.commons.serial.PersonProtobuf.Person getDefaultInstanceForType() {\n      return DEFAULT_INSTANCE;\n    }\n\n  }\n\n  public interface AddrOrBuilder extends\n      // @@protoc_insertion_point(interface_extends:Addr)\n      com.google.protobuf.MessageOrBuilder {\n\n    /**\n     * <code>string contry = 1;</code>\n     */\n    java.lang.String getContry();\n    /**\n     * <code>string contry = 1;</code>\n     */\n    com.google.protobuf.ByteString\n        getContryBytes();\n\n    /**\n     * <code>string city = 2;</code>\n     */\n    java.lang.String getCity();\n    /**\n     * <code>string city = 2;</code>\n     */\n    com.google.protobuf.ByteString\n        getCityBytes();\n  }\n  /**\n   * Protobuf type {@code Addr}\n   */\n  public  static final class Addr extends\n      com.google.protobuf.GeneratedMessageV3 implements\n      // @@protoc_insertion_point(message_implements:Addr)\n      AddrOrBuilder {\n  private static final long serialVersionUID = 0L;\n    // Use Addr.newBuilder() to construct.\n    private Addr(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {\n      super(builder);\n    }\n    private Addr() {\n      contry_ = \"\";\n      city_ = \"\";\n    }\n\n    @java.lang.Override\n    public final com.google.protobuf.UnknownFieldSet\n    getUnknownFields() {\n      return this.unknownFields;\n    }\n    private Addr(\n        com.google.protobuf.CodedInputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      this();\n      if (extensionRegistry == null) {\n        throw new java.lang.NullPointerException();\n      }\n      int mutable_bitField0_ = 0;\n      com.google.protobuf.UnknownFieldSet.Builder unknownFields =\n          com.google.protobuf.UnknownFieldSet.newBuilder();\n      try {\n        boolean done = false;\n        while (!done) {\n          int tag = input.readTag();\n          switch (tag) {\n            case 0:\n              done = true;\n              break;\n            case 10: {\n              java.lang.String s = input.readStringRequireUtf8();\n\n              contry_ = s;\n              break;\n            }\n            case 18: {\n              java.lang.String s = input.readStringRequireUtf8();\n\n              city_ = s;\n              break;\n            }\n            default: {\n              if (!parseUnknownField(\n                  input, unknownFields, extensionRegistry, tag)) {\n                done = true;\n              }\n              break;\n            }\n          }\n        }\n      } catch (com.google.protobuf.InvalidProtocolBufferException e) {\n        throw e.setUnfinishedMessage(this);\n      } catch (java.io.IOException e) {\n        throw new com.google.protobuf.InvalidProtocolBufferException(\n            e).setUnfinishedMessage(this);\n      } finally {\n        this.unknownFields = unknownFields.build();\n        makeExtensionsImmutable();\n      }\n    }\n    public static final com.google.protobuf.Descriptors.Descriptor\n        getDescriptor() {\n      return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_descriptor;\n    }\n\n    @java.lang.Override\n    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable\n        internalGetFieldAccessorTable() {\n      return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_fieldAccessorTable\n          .ensureFieldAccessorsInitialized(\n              cn.ponfee.commons.serial.PersonProtobuf.Addr.class, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder.class);\n    }\n\n    public static final int CONTRY_FIELD_NUMBER = 1;\n    private volatile java.lang.Object contry_;\n    /**\n     * <code>string contry = 1;</code>\n     */\n    public java.lang.String getContry() {\n      java.lang.Object ref = contry_;\n      if (ref instanceof java.lang.String) {\n        return (java.lang.String) ref;\n      } else {\n        com.google.protobuf.ByteString bs = \n            (com.google.protobuf.ByteString) ref;\n        java.lang.String s = bs.toStringUtf8();\n        contry_ = s;\n        return s;\n      }\n    }\n    /**\n     * <code>string contry = 1;</code>\n     */\n    public com.google.protobuf.ByteString\n        getContryBytes() {\n      java.lang.Object ref = contry_;\n      if (ref instanceof java.lang.String) {\n        com.google.protobuf.ByteString b = \n            com.google.protobuf.ByteString.copyFromUtf8(\n                (java.lang.String) ref);\n        contry_ = b;\n        return b;\n      } else {\n        return (com.google.protobuf.ByteString) ref;\n      }\n    }\n\n    public static final int CITY_FIELD_NUMBER = 2;\n    private volatile java.lang.Object city_;\n    /**\n     * <code>string city = 2;</code>\n     */\n    public java.lang.String getCity() {\n      java.lang.Object ref = city_;\n      if (ref instanceof java.lang.String) {\n        return (java.lang.String) ref;\n      } else {\n        com.google.protobuf.ByteString bs = \n            (com.google.protobuf.ByteString) ref;\n        java.lang.String s = bs.toStringUtf8();\n        city_ = s;\n        return s;\n      }\n    }\n    /**\n     * <code>string city = 2;</code>\n     */\n    public com.google.protobuf.ByteString\n        getCityBytes() {\n      java.lang.Object ref = city_;\n      if (ref instanceof java.lang.String) {\n        com.google.protobuf.ByteString b = \n            com.google.protobuf.ByteString.copyFromUtf8(\n                (java.lang.String) ref);\n        city_ = b;\n        return b;\n      } else {\n        return (com.google.protobuf.ByteString) ref;\n      }\n    }\n\n    private byte memoizedIsInitialized = -1;\n    @java.lang.Override\n    public final boolean isInitialized() {\n      byte isInitialized = memoizedIsInitialized;\n      if (isInitialized == 1) return true;\n      if (isInitialized == 0) return false;\n\n      memoizedIsInitialized = 1;\n      return true;\n    }\n\n    @java.lang.Override\n    public void writeTo(com.google.protobuf.CodedOutputStream output)\n                        throws java.io.IOException {\n      if (!getContryBytes().isEmpty()) {\n        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, contry_);\n      }\n      if (!getCityBytes().isEmpty()) {\n        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, city_);\n      }\n      unknownFields.writeTo(output);\n    }\n\n    @java.lang.Override\n    public int getSerializedSize() {\n      int size = memoizedSize;\n      if (size != -1) return size;\n\n      size = 0;\n      if (!getContryBytes().isEmpty()) {\n        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, contry_);\n      }\n      if (!getCityBytes().isEmpty()) {\n        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, city_);\n      }\n      size += unknownFields.getSerializedSize();\n      memoizedSize = size;\n      return size;\n    }\n\n    @java.lang.Override\n    public boolean equals(final java.lang.Object obj) {\n      if (obj == this) {\n       return true;\n      }\n      if (!(obj instanceof cn.ponfee.commons.serial.PersonProtobuf.Addr)) {\n        return super.equals(obj);\n      }\n      cn.ponfee.commons.serial.PersonProtobuf.Addr other = (cn.ponfee.commons.serial.PersonProtobuf.Addr) obj;\n\n      if (!getContry()\n          .equals(other.getContry())) return false;\n      if (!getCity()\n          .equals(other.getCity())) return false;\n      if (!unknownFields.equals(other.unknownFields)) return false;\n      return true;\n    }\n\n    @java.lang.Override\n    public int hashCode() {\n      if (memoizedHashCode != 0) {\n        return memoizedHashCode;\n      }\n      int hash = 41;\n      hash = (19 * hash) + getDescriptor().hashCode();\n      hash = (37 * hash) + CONTRY_FIELD_NUMBER;\n      hash = (53 * hash) + getContry().hashCode();\n      hash = (37 * hash) + CITY_FIELD_NUMBER;\n      hash = (53 * hash) + getCity().hashCode();\n      hash = (29 * hash) + unknownFields.hashCode();\n      memoizedHashCode = hash;\n      return hash;\n    }\n\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(\n        java.nio.ByteBuffer data)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(\n        java.nio.ByteBuffer data,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(\n        com.google.protobuf.ByteString data)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(\n        com.google.protobuf.ByteString data,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(byte[] data)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(\n        byte[] data,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(java.io.InputStream input)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(\n        java.io.InputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseDelimitedFrom(java.io.InputStream input)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseDelimitedWithIOException(PARSER, input);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseDelimitedFrom(\n        java.io.InputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(\n        com.google.protobuf.CodedInputStream input)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr parseFrom(\n        com.google.protobuf.CodedInputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input, extensionRegistry);\n    }\n\n    @java.lang.Override\n    public Builder newBuilderForType() { return newBuilder(); }\n    public static Builder newBuilder() {\n      return DEFAULT_INSTANCE.toBuilder();\n    }\n    public static Builder newBuilder(cn.ponfee.commons.serial.PersonProtobuf.Addr prototype) {\n      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);\n    }\n    @java.lang.Override\n    public Builder toBuilder() {\n      return this == DEFAULT_INSTANCE\n          ? new Builder() : new Builder().mergeFrom(this);\n    }\n\n    @java.lang.Override\n    protected Builder newBuilderForType(\n        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {\n      Builder builder = new Builder(parent);\n      return builder;\n    }\n    /**\n     * Protobuf type {@code Addr}\n     */\n    public static final class Builder extends\n        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements\n        // @@protoc_insertion_point(builder_implements:Addr)\n        cn.ponfee.commons.serial.PersonProtobuf.AddrOrBuilder {\n      public static final com.google.protobuf.Descriptors.Descriptor\n          getDescriptor() {\n        return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_descriptor;\n      }\n\n      @java.lang.Override\n      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable\n          internalGetFieldAccessorTable() {\n        return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_fieldAccessorTable\n            .ensureFieldAccessorsInitialized(\n                cn.ponfee.commons.serial.PersonProtobuf.Addr.class, cn.ponfee.commons.serial.PersonProtobuf.Addr.Builder.class);\n      }\n\n      // Construct using cn.ponfee.commons.serial.PersonProtobuf.Addr.newBuilder()\n      private Builder() {\n        maybeForceBuilderInitialization();\n      }\n\n      private Builder(\n          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {\n        super(parent);\n        maybeForceBuilderInitialization();\n      }\n      private void maybeForceBuilderInitialization() {\n        if (com.google.protobuf.GeneratedMessageV3\n                .alwaysUseFieldBuilders) {\n        }\n      }\n      @java.lang.Override\n      public Builder clear() {\n        super.clear();\n        contry_ = \"\";\n\n        city_ = \"\";\n\n        return this;\n      }\n\n      @java.lang.Override\n      public com.google.protobuf.Descriptors.Descriptor\n          getDescriptorForType() {\n        return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Addr_descriptor;\n      }\n\n      @java.lang.Override\n      public cn.ponfee.commons.serial.PersonProtobuf.Addr getDefaultInstanceForType() {\n        return cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance();\n      }\n\n      @java.lang.Override\n      public cn.ponfee.commons.serial.PersonProtobuf.Addr build() {\n        cn.ponfee.commons.serial.PersonProtobuf.Addr result = buildPartial();\n        if (!result.isInitialized()) {\n          throw newUninitializedMessageException(result);\n        }\n        return result;\n      }\n\n      @java.lang.Override\n      public cn.ponfee.commons.serial.PersonProtobuf.Addr buildPartial() {\n        cn.ponfee.commons.serial.PersonProtobuf.Addr result = new cn.ponfee.commons.serial.PersonProtobuf.Addr(this);\n        result.contry_ = contry_;\n        result.city_ = city_;\n        onBuilt();\n        return result;\n      }\n\n      @java.lang.Override\n      public Builder clone() {\n        return super.clone();\n      }\n      @java.lang.Override\n      public Builder setField(\n          com.google.protobuf.Descriptors.FieldDescriptor field,\n          java.lang.Object value) {\n        return super.setField(field, value);\n      }\n      @java.lang.Override\n      public Builder clearField(\n          com.google.protobuf.Descriptors.FieldDescriptor field) {\n        return super.clearField(field);\n      }\n      @java.lang.Override\n      public Builder clearOneof(\n          com.google.protobuf.Descriptors.OneofDescriptor oneof) {\n        return super.clearOneof(oneof);\n      }\n      @java.lang.Override\n      public Builder setRepeatedField(\n          com.google.protobuf.Descriptors.FieldDescriptor field,\n          int index, java.lang.Object value) {\n        return super.setRepeatedField(field, index, value);\n      }\n      @java.lang.Override\n      public Builder addRepeatedField(\n          com.google.protobuf.Descriptors.FieldDescriptor field,\n          java.lang.Object value) {\n        return super.addRepeatedField(field, value);\n      }\n      @java.lang.Override\n      public Builder mergeFrom(com.google.protobuf.Message other) {\n        if (other instanceof cn.ponfee.commons.serial.PersonProtobuf.Addr) {\n          return mergeFrom((cn.ponfee.commons.serial.PersonProtobuf.Addr)other);\n        } else {\n          super.mergeFrom(other);\n          return this;\n        }\n      }\n\n      public Builder mergeFrom(cn.ponfee.commons.serial.PersonProtobuf.Addr other) {\n        if (other == cn.ponfee.commons.serial.PersonProtobuf.Addr.getDefaultInstance()) return this;\n        if (!other.getContry().isEmpty()) {\n          contry_ = other.contry_;\n          onChanged();\n        }\n        if (!other.getCity().isEmpty()) {\n          city_ = other.city_;\n          onChanged();\n        }\n        this.mergeUnknownFields(other.unknownFields);\n        onChanged();\n        return this;\n      }\n\n      @java.lang.Override\n      public final boolean isInitialized() {\n        return true;\n      }\n\n      @java.lang.Override\n      public Builder mergeFrom(\n          com.google.protobuf.CodedInputStream input,\n          com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n          throws java.io.IOException {\n        cn.ponfee.commons.serial.PersonProtobuf.Addr parsedMessage = null;\n        try {\n          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);\n        } catch (com.google.protobuf.InvalidProtocolBufferException e) {\n          parsedMessage = (cn.ponfee.commons.serial.PersonProtobuf.Addr) e.getUnfinishedMessage();\n          throw e.unwrapIOException();\n        } finally {\n          if (parsedMessage != null) {\n            mergeFrom(parsedMessage);\n          }\n        }\n        return this;\n      }\n\n      private java.lang.Object contry_ = \"\";\n      /**\n       * <code>string contry = 1;</code>\n       */\n      public java.lang.String getContry() {\n        java.lang.Object ref = contry_;\n        if (!(ref instanceof java.lang.String)) {\n          com.google.protobuf.ByteString bs =\n              (com.google.protobuf.ByteString) ref;\n          java.lang.String s = bs.toStringUtf8();\n          contry_ = s;\n          return s;\n        } else {\n          return (java.lang.String) ref;\n        }\n      }\n      /**\n       * <code>string contry = 1;</code>\n       */\n      public com.google.protobuf.ByteString\n          getContryBytes() {\n        java.lang.Object ref = contry_;\n        if (ref instanceof String) {\n          com.google.protobuf.ByteString b = \n              com.google.protobuf.ByteString.copyFromUtf8(\n                  (java.lang.String) ref);\n          contry_ = b;\n          return b;\n        } else {\n          return (com.google.protobuf.ByteString) ref;\n        }\n      }\n      /**\n       * <code>string contry = 1;</code>\n       */\n      public Builder setContry(\n          java.lang.String value) {\n        if (value == null) {\n    throw new NullPointerException();\n  }\n  \n        contry_ = value;\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>string contry = 1;</code>\n       */\n      public Builder clearContry() {\n        \n        contry_ = getDefaultInstance().getContry();\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>string contry = 1;</code>\n       */\n      public Builder setContryBytes(\n          com.google.protobuf.ByteString value) {\n        if (value == null) {\n    throw new NullPointerException();\n  }\n  checkByteStringIsUtf8(value);\n        \n        contry_ = value;\n        onChanged();\n        return this;\n      }\n\n      private java.lang.Object city_ = \"\";\n      /**\n       * <code>string city = 2;</code>\n       */\n      public java.lang.String getCity() {\n        java.lang.Object ref = city_;\n        if (!(ref instanceof java.lang.String)) {\n          com.google.protobuf.ByteString bs =\n              (com.google.protobuf.ByteString) ref;\n          java.lang.String s = bs.toStringUtf8();\n          city_ = s;\n          return s;\n        } else {\n          return (java.lang.String) ref;\n        }\n      }\n      /**\n       * <code>string city = 2;</code>\n       */\n      public com.google.protobuf.ByteString\n          getCityBytes() {\n        java.lang.Object ref = city_;\n        if (ref instanceof String) {\n          com.google.protobuf.ByteString b = \n              com.google.protobuf.ByteString.copyFromUtf8(\n                  (java.lang.String) ref);\n          city_ = b;\n          return b;\n        } else {\n          return (com.google.protobuf.ByteString) ref;\n        }\n      }\n      /**\n       * <code>string city = 2;</code>\n       */\n      public Builder setCity(\n          java.lang.String value) {\n        if (value == null) {\n    throw new NullPointerException();\n  }\n  \n        city_ = value;\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>string city = 2;</code>\n       */\n      public Builder clearCity() {\n        \n        city_ = getDefaultInstance().getCity();\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>string city = 2;</code>\n       */\n      public Builder setCityBytes(\n          com.google.protobuf.ByteString value) {\n        if (value == null) {\n    throw new NullPointerException();\n  }\n  checkByteStringIsUtf8(value);\n        \n        city_ = value;\n        onChanged();\n        return this;\n      }\n      @java.lang.Override\n      public final Builder setUnknownFields(\n          final com.google.protobuf.UnknownFieldSet unknownFields) {\n        return super.setUnknownFields(unknownFields);\n      }\n\n      @java.lang.Override\n      public final Builder mergeUnknownFields(\n          final com.google.protobuf.UnknownFieldSet unknownFields) {\n        return super.mergeUnknownFields(unknownFields);\n      }\n\n\n      // @@protoc_insertion_point(builder_scope:Addr)\n    }\n\n    // @@protoc_insertion_point(class_scope:Addr)\n    private static final cn.ponfee.commons.serial.PersonProtobuf.Addr DEFAULT_INSTANCE;\n    static {\n      DEFAULT_INSTANCE = new cn.ponfee.commons.serial.PersonProtobuf.Addr();\n    }\n\n    public static cn.ponfee.commons.serial.PersonProtobuf.Addr getDefaultInstance() {\n      return DEFAULT_INSTANCE;\n    }\n\n    private static final com.google.protobuf.Parser<Addr>\n        PARSER = new com.google.protobuf.AbstractParser<Addr>() {\n      @java.lang.Override\n      public Addr parsePartialFrom(\n          com.google.protobuf.CodedInputStream input,\n          com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n          throws com.google.protobuf.InvalidProtocolBufferException {\n        return new Addr(input, extensionRegistry);\n      }\n    };\n\n    public static com.google.protobuf.Parser<Addr> parser() {\n      return PARSER;\n    }\n\n    @java.lang.Override\n    public com.google.protobuf.Parser<Addr> getParserForType() {\n      return PARSER;\n    }\n\n    @java.lang.Override\n    public cn.ponfee.commons.serial.PersonProtobuf.Addr getDefaultInstanceForType() {\n      return DEFAULT_INSTANCE;\n    }\n\n  }\n\n  public interface PhoneOrBuilder extends\n      // @@protoc_insertion_point(interface_extends:Phone)\n      com.google.protobuf.MessageOrBuilder {\n\n    /**\n     * <code>string number = 1;</code>\n     */\n    java.lang.String getNumber();\n    /**\n     * <code>string number = 1;</code>\n     */\n    com.google.protobuf.ByteString\n        getNumberBytes();\n\n    /**\n     * <code>.PhoneType type = 2;</code>\n     */\n    int getTypeValue();\n    /**\n     * <code>.PhoneType type = 2;</code>\n     */\n    cn.ponfee.commons.serial.PersonProtobuf.PhoneType getType();\n  }\n  /**\n   * Protobuf type {@code Phone}\n   */\n  public  static final class Phone extends\n      com.google.protobuf.GeneratedMessageV3 implements\n      // @@protoc_insertion_point(message_implements:Phone)\n      PhoneOrBuilder {\n  private static final long serialVersionUID = 0L;\n    // Use Phone.newBuilder() to construct.\n    private Phone(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {\n      super(builder);\n    }\n    private Phone() {\n      number_ = \"\";\n      type_ = 0;\n    }\n\n    @java.lang.Override\n    public final com.google.protobuf.UnknownFieldSet\n    getUnknownFields() {\n      return this.unknownFields;\n    }\n    private Phone(\n        com.google.protobuf.CodedInputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      this();\n      if (extensionRegistry == null) {\n        throw new java.lang.NullPointerException();\n      }\n      int mutable_bitField0_ = 0;\n      com.google.protobuf.UnknownFieldSet.Builder unknownFields =\n          com.google.protobuf.UnknownFieldSet.newBuilder();\n      try {\n        boolean done = false;\n        while (!done) {\n          int tag = input.readTag();\n          switch (tag) {\n            case 0:\n              done = true;\n              break;\n            case 10: {\n              java.lang.String s = input.readStringRequireUtf8();\n\n              number_ = s;\n              break;\n            }\n            case 16: {\n              int rawValue = input.readEnum();\n\n              type_ = rawValue;\n              break;\n            }\n            default: {\n              if (!parseUnknownField(\n                  input, unknownFields, extensionRegistry, tag)) {\n                done = true;\n              }\n              break;\n            }\n          }\n        }\n      } catch (com.google.protobuf.InvalidProtocolBufferException e) {\n        throw e.setUnfinishedMessage(this);\n      } catch (java.io.IOException e) {\n        throw new com.google.protobuf.InvalidProtocolBufferException(\n            e).setUnfinishedMessage(this);\n      } finally {\n        this.unknownFields = unknownFields.build();\n        makeExtensionsImmutable();\n      }\n    }\n    public static final com.google.protobuf.Descriptors.Descriptor\n        getDescriptor() {\n      return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_descriptor;\n    }\n\n    @java.lang.Override\n    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable\n        internalGetFieldAccessorTable() {\n      return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_fieldAccessorTable\n          .ensureFieldAccessorsInitialized(\n              cn.ponfee.commons.serial.PersonProtobuf.Phone.class, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder.class);\n    }\n\n    public static final int NUMBER_FIELD_NUMBER = 1;\n    private volatile java.lang.Object number_;\n    /**\n     * <code>string number = 1;</code>\n     */\n    public java.lang.String getNumber() {\n      java.lang.Object ref = number_;\n      if (ref instanceof java.lang.String) {\n        return (java.lang.String) ref;\n      } else {\n        com.google.protobuf.ByteString bs = \n            (com.google.protobuf.ByteString) ref;\n        java.lang.String s = bs.toStringUtf8();\n        number_ = s;\n        return s;\n      }\n    }\n    /**\n     * <code>string number = 1;</code>\n     */\n    public com.google.protobuf.ByteString\n        getNumberBytes() {\n      java.lang.Object ref = number_;\n      if (ref instanceof java.lang.String) {\n        com.google.protobuf.ByteString b = \n            com.google.protobuf.ByteString.copyFromUtf8(\n                (java.lang.String) ref);\n        number_ = b;\n        return b;\n      } else {\n        return (com.google.protobuf.ByteString) ref;\n      }\n    }\n\n    public static final int TYPE_FIELD_NUMBER = 2;\n    private int type_;\n    /**\n     * <code>.PhoneType type = 2;</code>\n     */\n    public int getTypeValue() {\n      return type_;\n    }\n    /**\n     * <code>.PhoneType type = 2;</code>\n     */\n    public cn.ponfee.commons.serial.PersonProtobuf.PhoneType getType() {\n      @SuppressWarnings(\"deprecation\")\n      cn.ponfee.commons.serial.PersonProtobuf.PhoneType result = cn.ponfee.commons.serial.PersonProtobuf.PhoneType.valueOf(type_);\n      return result == null ? cn.ponfee.commons.serial.PersonProtobuf.PhoneType.UNRECOGNIZED : result;\n    }\n\n    private byte memoizedIsInitialized = -1;\n    @java.lang.Override\n    public final boolean isInitialized() {\n      byte isInitialized = memoizedIsInitialized;\n      if (isInitialized == 1) return true;\n      if (isInitialized == 0) return false;\n\n      memoizedIsInitialized = 1;\n      return true;\n    }\n\n    @java.lang.Override\n    public void writeTo(com.google.protobuf.CodedOutputStream output)\n                        throws java.io.IOException {\n      if (!getNumberBytes().isEmpty()) {\n        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, number_);\n      }\n      if (type_ != cn.ponfee.commons.serial.PersonProtobuf.PhoneType.MOBILE.getNumber()) {\n        output.writeEnum(2, type_);\n      }\n      unknownFields.writeTo(output);\n    }\n\n    @java.lang.Override\n    public int getSerializedSize() {\n      int size = memoizedSize;\n      if (size != -1) return size;\n\n      size = 0;\n      if (!getNumberBytes().isEmpty()) {\n        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, number_);\n      }\n      if (type_ != cn.ponfee.commons.serial.PersonProtobuf.PhoneType.MOBILE.getNumber()) {\n        size += com.google.protobuf.CodedOutputStream\n          .computeEnumSize(2, type_);\n      }\n      size += unknownFields.getSerializedSize();\n      memoizedSize = size;\n      return size;\n    }\n\n    @java.lang.Override\n    public boolean equals(final java.lang.Object obj) {\n      if (obj == this) {\n       return true;\n      }\n      if (!(obj instanceof cn.ponfee.commons.serial.PersonProtobuf.Phone)) {\n        return super.equals(obj);\n      }\n      cn.ponfee.commons.serial.PersonProtobuf.Phone other = (cn.ponfee.commons.serial.PersonProtobuf.Phone) obj;\n\n      if (!getNumber()\n          .equals(other.getNumber())) return false;\n      if (type_ != other.type_) return false;\n      if (!unknownFields.equals(other.unknownFields)) return false;\n      return true;\n    }\n\n    @java.lang.Override\n    public int hashCode() {\n      if (memoizedHashCode != 0) {\n        return memoizedHashCode;\n      }\n      int hash = 41;\n      hash = (19 * hash) + getDescriptor().hashCode();\n      hash = (37 * hash) + NUMBER_FIELD_NUMBER;\n      hash = (53 * hash) + getNumber().hashCode();\n      hash = (37 * hash) + TYPE_FIELD_NUMBER;\n      hash = (53 * hash) + type_;\n      hash = (29 * hash) + unknownFields.hashCode();\n      memoizedHashCode = hash;\n      return hash;\n    }\n\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(\n        java.nio.ByteBuffer data)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(\n        java.nio.ByteBuffer data,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(\n        com.google.protobuf.ByteString data)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(\n        com.google.protobuf.ByteString data,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(byte[] data)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(\n        byte[] data,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws com.google.protobuf.InvalidProtocolBufferException {\n      return PARSER.parseFrom(data, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(java.io.InputStream input)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(\n        java.io.InputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseDelimitedFrom(java.io.InputStream input)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseDelimitedWithIOException(PARSER, input);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseDelimitedFrom(\n        java.io.InputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(\n        com.google.protobuf.CodedInputStream input)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input);\n    }\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone parseFrom(\n        com.google.protobuf.CodedInputStream input,\n        com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n        throws java.io.IOException {\n      return com.google.protobuf.GeneratedMessageV3\n          .parseWithIOException(PARSER, input, extensionRegistry);\n    }\n\n    @java.lang.Override\n    public Builder newBuilderForType() { return newBuilder(); }\n    public static Builder newBuilder() {\n      return DEFAULT_INSTANCE.toBuilder();\n    }\n    public static Builder newBuilder(cn.ponfee.commons.serial.PersonProtobuf.Phone prototype) {\n      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);\n    }\n    @java.lang.Override\n    public Builder toBuilder() {\n      return this == DEFAULT_INSTANCE\n          ? new Builder() : new Builder().mergeFrom(this);\n    }\n\n    @java.lang.Override\n    protected Builder newBuilderForType(\n        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {\n      Builder builder = new Builder(parent);\n      return builder;\n    }\n    /**\n     * Protobuf type {@code Phone}\n     */\n    public static final class Builder extends\n        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements\n        // @@protoc_insertion_point(builder_implements:Phone)\n        cn.ponfee.commons.serial.PersonProtobuf.PhoneOrBuilder {\n      public static final com.google.protobuf.Descriptors.Descriptor\n          getDescriptor() {\n        return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_descriptor;\n      }\n\n      @java.lang.Override\n      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable\n          internalGetFieldAccessorTable() {\n        return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_fieldAccessorTable\n            .ensureFieldAccessorsInitialized(\n                cn.ponfee.commons.serial.PersonProtobuf.Phone.class, cn.ponfee.commons.serial.PersonProtobuf.Phone.Builder.class);\n      }\n\n      // Construct using cn.ponfee.commons.serial.PersonProtobuf.Phone.newBuilder()\n      private Builder() {\n        maybeForceBuilderInitialization();\n      }\n\n      private Builder(\n          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {\n        super(parent);\n        maybeForceBuilderInitialization();\n      }\n      private void maybeForceBuilderInitialization() {\n        if (com.google.protobuf.GeneratedMessageV3\n                .alwaysUseFieldBuilders) {\n        }\n      }\n      @java.lang.Override\n      public Builder clear() {\n        super.clear();\n        number_ = \"\";\n\n        type_ = 0;\n\n        return this;\n      }\n\n      @java.lang.Override\n      public com.google.protobuf.Descriptors.Descriptor\n          getDescriptorForType() {\n        return cn.ponfee.commons.serial.PersonProtobuf.internal_static_Phone_descriptor;\n      }\n\n      @java.lang.Override\n      public cn.ponfee.commons.serial.PersonProtobuf.Phone getDefaultInstanceForType() {\n        return cn.ponfee.commons.serial.PersonProtobuf.Phone.getDefaultInstance();\n      }\n\n      @java.lang.Override\n      public cn.ponfee.commons.serial.PersonProtobuf.Phone build() {\n        cn.ponfee.commons.serial.PersonProtobuf.Phone result = buildPartial();\n        if (!result.isInitialized()) {\n          throw newUninitializedMessageException(result);\n        }\n        return result;\n      }\n\n      @java.lang.Override\n      public cn.ponfee.commons.serial.PersonProtobuf.Phone buildPartial() {\n        cn.ponfee.commons.serial.PersonProtobuf.Phone result = new cn.ponfee.commons.serial.PersonProtobuf.Phone(this);\n        result.number_ = number_;\n        result.type_ = type_;\n        onBuilt();\n        return result;\n      }\n\n      @java.lang.Override\n      public Builder clone() {\n        return super.clone();\n      }\n      @java.lang.Override\n      public Builder setField(\n          com.google.protobuf.Descriptors.FieldDescriptor field,\n          java.lang.Object value) {\n        return super.setField(field, value);\n      }\n      @java.lang.Override\n      public Builder clearField(\n          com.google.protobuf.Descriptors.FieldDescriptor field) {\n        return super.clearField(field);\n      }\n      @java.lang.Override\n      public Builder clearOneof(\n          com.google.protobuf.Descriptors.OneofDescriptor oneof) {\n        return super.clearOneof(oneof);\n      }\n      @java.lang.Override\n      public Builder setRepeatedField(\n          com.google.protobuf.Descriptors.FieldDescriptor field,\n          int index, java.lang.Object value) {\n        return super.setRepeatedField(field, index, value);\n      }\n      @java.lang.Override\n      public Builder addRepeatedField(\n          com.google.protobuf.Descriptors.FieldDescriptor field,\n          java.lang.Object value) {\n        return super.addRepeatedField(field, value);\n      }\n      @java.lang.Override\n      public Builder mergeFrom(com.google.protobuf.Message other) {\n        if (other instanceof cn.ponfee.commons.serial.PersonProtobuf.Phone) {\n          return mergeFrom((cn.ponfee.commons.serial.PersonProtobuf.Phone)other);\n        } else {\n          super.mergeFrom(other);\n          return this;\n        }\n      }\n\n      public Builder mergeFrom(cn.ponfee.commons.serial.PersonProtobuf.Phone other) {\n        if (other == cn.ponfee.commons.serial.PersonProtobuf.Phone.getDefaultInstance()) return this;\n        if (!other.getNumber().isEmpty()) {\n          number_ = other.number_;\n          onChanged();\n        }\n        if (other.type_ != 0) {\n          setTypeValue(other.getTypeValue());\n        }\n        this.mergeUnknownFields(other.unknownFields);\n        onChanged();\n        return this;\n      }\n\n      @java.lang.Override\n      public final boolean isInitialized() {\n        return true;\n      }\n\n      @java.lang.Override\n      public Builder mergeFrom(\n          com.google.protobuf.CodedInputStream input,\n          com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n          throws java.io.IOException {\n        cn.ponfee.commons.serial.PersonProtobuf.Phone parsedMessage = null;\n        try {\n          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);\n        } catch (com.google.protobuf.InvalidProtocolBufferException e) {\n          parsedMessage = (cn.ponfee.commons.serial.PersonProtobuf.Phone) e.getUnfinishedMessage();\n          throw e.unwrapIOException();\n        } finally {\n          if (parsedMessage != null) {\n            mergeFrom(parsedMessage);\n          }\n        }\n        return this;\n      }\n\n      private java.lang.Object number_ = \"\";\n      /**\n       * <code>string number = 1;</code>\n       */\n      public java.lang.String getNumber() {\n        java.lang.Object ref = number_;\n        if (!(ref instanceof java.lang.String)) {\n          com.google.protobuf.ByteString bs =\n              (com.google.protobuf.ByteString) ref;\n          java.lang.String s = bs.toStringUtf8();\n          number_ = s;\n          return s;\n        } else {\n          return (java.lang.String) ref;\n        }\n      }\n      /**\n       * <code>string number = 1;</code>\n       */\n      public com.google.protobuf.ByteString\n          getNumberBytes() {\n        java.lang.Object ref = number_;\n        if (ref instanceof String) {\n          com.google.protobuf.ByteString b = \n              com.google.protobuf.ByteString.copyFromUtf8(\n                  (java.lang.String) ref);\n          number_ = b;\n          return b;\n        } else {\n          return (com.google.protobuf.ByteString) ref;\n        }\n      }\n      /**\n       * <code>string number = 1;</code>\n       */\n      public Builder setNumber(\n          java.lang.String value) {\n        if (value == null) {\n    throw new NullPointerException();\n  }\n  \n        number_ = value;\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>string number = 1;</code>\n       */\n      public Builder clearNumber() {\n        \n        number_ = getDefaultInstance().getNumber();\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>string number = 1;</code>\n       */\n      public Builder setNumberBytes(\n          com.google.protobuf.ByteString value) {\n        if (value == null) {\n    throw new NullPointerException();\n  }\n  checkByteStringIsUtf8(value);\n        \n        number_ = value;\n        onChanged();\n        return this;\n      }\n\n      private int type_ = 0;\n      /**\n       * <code>.PhoneType type = 2;</code>\n       */\n      public int getTypeValue() {\n        return type_;\n      }\n      /**\n       * <code>.PhoneType type = 2;</code>\n       */\n      public Builder setTypeValue(int value) {\n        type_ = value;\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>.PhoneType type = 2;</code>\n       */\n      public cn.ponfee.commons.serial.PersonProtobuf.PhoneType getType() {\n        @SuppressWarnings(\"deprecation\")\n        cn.ponfee.commons.serial.PersonProtobuf.PhoneType result = cn.ponfee.commons.serial.PersonProtobuf.PhoneType.valueOf(type_);\n        return result == null ? cn.ponfee.commons.serial.PersonProtobuf.PhoneType.UNRECOGNIZED : result;\n      }\n      /**\n       * <code>.PhoneType type = 2;</code>\n       */\n      public Builder setType(cn.ponfee.commons.serial.PersonProtobuf.PhoneType value) {\n        if (value == null) {\n          throw new NullPointerException();\n        }\n        \n        type_ = value.getNumber();\n        onChanged();\n        return this;\n      }\n      /**\n       * <code>.PhoneType type = 2;</code>\n       */\n      public Builder clearType() {\n        \n        type_ = 0;\n        onChanged();\n        return this;\n      }\n      @java.lang.Override\n      public final Builder setUnknownFields(\n          final com.google.protobuf.UnknownFieldSet unknownFields) {\n        return super.setUnknownFields(unknownFields);\n      }\n\n      @java.lang.Override\n      public final Builder mergeUnknownFields(\n          final com.google.protobuf.UnknownFieldSet unknownFields) {\n        return super.mergeUnknownFields(unknownFields);\n      }\n\n\n      // @@protoc_insertion_point(builder_scope:Phone)\n    }\n\n    // @@protoc_insertion_point(class_scope:Phone)\n    private static final cn.ponfee.commons.serial.PersonProtobuf.Phone DEFAULT_INSTANCE;\n    static {\n      DEFAULT_INSTANCE = new cn.ponfee.commons.serial.PersonProtobuf.Phone();\n    }\n\n    public static cn.ponfee.commons.serial.PersonProtobuf.Phone getDefaultInstance() {\n      return DEFAULT_INSTANCE;\n    }\n\n    private static final com.google.protobuf.Parser<Phone>\n        PARSER = new com.google.protobuf.AbstractParser<Phone>() {\n      @java.lang.Override\n      public Phone parsePartialFrom(\n          com.google.protobuf.CodedInputStream input,\n          com.google.protobuf.ExtensionRegistryLite extensionRegistry)\n          throws com.google.protobuf.InvalidProtocolBufferException {\n        return new Phone(input, extensionRegistry);\n      }\n    };\n\n    public static com.google.protobuf.Parser<Phone> parser() {\n      return PARSER;\n    }\n\n    @java.lang.Override\n    public com.google.protobuf.Parser<Phone> getParserForType() {\n      return PARSER;\n    }\n\n    @java.lang.Override\n    public cn.ponfee.commons.serial.PersonProtobuf.Phone getDefaultInstanceForType() {\n      return DEFAULT_INSTANCE;\n    }\n\n  }\n\n  private static final com.google.protobuf.Descriptors.Descriptor\n    internal_static_Person_descriptor;\n  private static final \n    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable\n      internal_static_Person_fieldAccessorTable;\n  private static final com.google.protobuf.Descriptors.Descriptor\n    internal_static_Addr_descriptor;\n  private static final \n    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable\n      internal_static_Addr_fieldAccessorTable;\n  private static final com.google.protobuf.Descriptors.Descriptor\n    internal_static_Phone_descriptor;\n  private static final \n    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable\n      internal_static_Phone_fieldAccessorTable;\n\n  public static com.google.protobuf.Descriptors.FileDescriptor\n      getDescriptor() {\n    return descriptor;\n  }\n  private static  com.google.protobuf.Descriptors.FileDescriptor\n      descriptor;\n  static {\n    java.lang.String[] descriptorData = {\n      \"\\n\\014person.proto\\\"[\\n\\006Person\\022\\n\\n\\002id\\030\\001 \\001(\\005\\022\\014\\n\\004\" +\n      \"name\\030\\002 \\001(\\t\\022\\013\\n\\003age\\030\\003 \\001(\\005\\022\\023\\n\\004addr\\030\\004 \\001(\\0132\\005.\" +\n      \"Addr\\022\\025\\n\\005phone\\030\\005 \\003(\\0132\\006.Phone\\\"$\\n\\004Addr\\022\\016\\n\\006c\" +\n      \"ontry\\030\\001 \\001(\\t\\022\\014\\n\\004city\\030\\002 \\001(\\t\\\"1\\n\\005Phone\\022\\016\\n\\006nu\" +\n      \"mber\\030\\001 \\001(\\t\\022\\030\\n\\004type\\030\\002 \\001(\\0162\\n.PhoneType*+\\n\\t\" +\n      \"PhoneType\\022\\n\\n\\006MOBILE\\020\\000\\022\\010\\n\\004HOME\\020\\001\\022\\010\\n\\004WORK\\020\" +\n      \"\\002B,\\n\\032cn.ponfee.commons.serialB\\016PersonP\" +\n      \"rotobufb\\006proto3\"\n    };\n    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =\n        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {\n          public com.google.protobuf.ExtensionRegistry assignDescriptors(\n              com.google.protobuf.Descriptors.FileDescriptor root) {\n            descriptor = root;\n            return null;\n          }\n        };\n    com.google.protobuf.Descriptors.FileDescriptor\n      .internalBuildGeneratedFileFrom(descriptorData,\n        new com.google.protobuf.Descriptors.FileDescriptor[] {\n        }, assigner);\n    internal_static_Person_descriptor =\n      getDescriptor().getMessageTypes().get(0);\n    internal_static_Person_fieldAccessorTable = new\n      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(\n        internal_static_Person_descriptor,\n        new java.lang.String[] { \"Id\", \"Name\", \"Age\", \"Addr\", \"Phone\", });\n    internal_static_Addr_descriptor =\n      getDescriptor().getMessageTypes().get(1);\n    internal_static_Addr_fieldAccessorTable = new\n      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(\n        internal_static_Addr_descriptor,\n        new java.lang.String[] { \"Contry\", \"City\", });\n    internal_static_Phone_descriptor =\n      getDescriptor().getMessageTypes().get(2);\n    internal_static_Phone_fieldAccessorTable = new\n      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(\n        internal_static_Phone_descriptor,\n        new java.lang.String[] { \"Number\", \"Type\", });\n  }\n\n  // @@protoc_insertion_point(outer_class_scope)\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/serial/ProtobufClient.java",
    "content": "package cn.ponfee.commons.serial;\n\nimport java.io.IOException;\nimport java.net.Socket;\n\nimport cn.ponfee.commons.serial.PersonProtobuf.Addr;\nimport cn.ponfee.commons.serial.PersonProtobuf.Person;\nimport cn.ponfee.commons.serial.PersonProtobuf.Phone;\nimport cn.ponfee.commons.serial.PersonProtobuf.PhoneType;\n\n/**\n * \n * @author \n */\npublic class ProtobufClient {\n\n    public static void main(String[] args) throws IOException {\n        Socket socket = new Socket(\"127.0.0.1\", 3030);\n\n\n        Person person = Person.newBuilder()\n            .setId(1).setAge(12).setName(\"ccf\")\n            .setAddr(Addr.newBuilder().setContry(\"china\").setCity(\"shenzhen\").build())\n            .addPhone(Phone.newBuilder().setNumber(\"13418467597\").setType(PhoneType.MOBILE).build())\n            .addPhone(Phone.newBuilder().setNumber(\"0755-41245647\").setType(PhoneType.HOME).build())\n            .build();\n        \n        byte[] messageBody = person.toByteArray();\n\n        int headerLen = 1;\n        byte[] message = new byte[headerLen + messageBody.length];\n        message[0] = (byte) messageBody.length;\n        System.arraycopy(messageBody, 0, message, 1, messageBody.length);\n        System.out.println(\"msg len:\" + message.length);\n        socket.getOutputStream().write(message);\n        socket.close();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/serial/ProtobufServer.java",
    "content": "package cn.ponfee.commons.serial;\n\nimport java.io.IOException;\nimport java.net.ServerSocket;\nimport java.net.Socket;\n\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.serial.PersonProtobuf.Person;\n\npublic class ProtobufServer {\n\n    public static void main(String[] args) throws IOException {\n        try (ServerSocket serverSock = new ServerSocket(3030)) {\n            while (true) {\n                Socket sock = serverSock.accept();\n                byte[] msg = new byte[256];\n                sock.getInputStream().read(msg);\n                int msgBodyLen = msg[0];\n                System.out.println(\"msg body len:\" + msgBodyLen);\n                byte[] msgbody = new byte[msgBodyLen];\n                System.arraycopy(msg, 1, msgbody, 0, msgBodyLen);\n\n                Person person = Person.parseFrom(msgbody);\n\n                System.out.println(\"toString:=============================\");\n                System.out.println(person);\n                //System.out.println(\"toJson:=============================\");\n                //System.out.println(Jsons.toJson(person));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/serial/SerializerTester.java",
    "content": "package cn.ponfee.commons.serial;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport test.TestBean;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.io.GzipProcessor;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class SerializerTester {\n    private String text;\n\n    @Before\n    public void setUp() {\n        text =  MavenProjects.getTestJavaFileAsString(this.getClass());\n    }\n\n    @Test\n    public void testKryo() {\n        Serializer serializer = null;\n        boolean isCompress;\n        byte[] data = null;\n        TestBean b = null;\n        long start = System.currentTimeMillis();\n        System.out.println(\"\\nkryo start =======================================================\");\n\n        System.out.println(\"--------------------no gzip---------------------------------\");\n        isCompress = false;\n        serializer = KryoSerializer.INSTANCE;\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后不压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"--------------------use gzip---------------------------------\");\n        isCompress = true;\n        serializer = KryoSerializer.INSTANCE;\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"kryo end =================================================耗时\" + (System.currentTimeMillis() - start) + \"\\n\");\n\n    }\n\n    @Test\n    public void testJdk() {\n        Serializer serializer = null;\n        boolean isCompress;\n        byte[] data = null;\n        TestBean b = null;\n        long start = System.currentTimeMillis();\n        System.out.println(\"\\njdk start =======================================================\");\n\n        System.out.println(\"--------------------no gzip---------------------------------\");\n        isCompress = false;\n        serializer = new JdkSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后不压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"--------------------use gzip---------------------------------\");\n        isCompress = true;\n        serializer = new JdkSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"jdk end ===============================================耗时\" + (System.currentTimeMillis() - start) + \"\\n\");\n    }\n\n    @Test\n    public void testJson() {\n        Serializer serializer = null;\n        boolean isCompress;\n        byte[] data = null;\n        TestBean b = null;\n        long start = System.currentTimeMillis();\n        System.out.println(\"\\njson start =======================================================\");\n\n        System.out.println(\"--------------------no gzip---------------------------------\");\n        isCompress = false;\n        serializer = new JsonSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后不压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"--------------------use gzip---------------------------------\");\n        isCompress = true;\n        serializer = new JsonSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"json end ==================================================耗时\" + (System.currentTimeMillis() - start) + \"\\n\");\n    }\n\n    @Test\n    public void testHessian() {\n        Serializer serializer = null;\n        boolean isCompress;\n        byte[] data = null;\n        TestBean b = null;\n        long start = System.currentTimeMillis();\n        System.out.println(\"\\nhessian start =======================================================\");\n\n        System.out.println(\"--------------------no gzip---------------------------------\");\n        isCompress = false;\n        serializer = new HessianSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后不压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"--------------------use gzip---------------------------------\");\n        isCompress = true;\n        serializer = new HessianSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"hessian end =================================================耗时\" + (System.currentTimeMillis() - start) + \"\\n\");\n    }\n\n    @Test\n    public void testFst() {\n        Serializer serializer = null;\n        boolean isCompress;\n        byte[] data = null;\n        TestBean b = null;\n        long start = System.currentTimeMillis();\n        System.out.println(\"\\nfst start =======================================================\");\n\n        System.out.println(\"--------------------no gzip---------------------------------\");\n        isCompress = false;\n        serializer = new FstSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后不压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"--------------------use gzip---------------------------------\");\n        isCompress = true;\n        serializer = new FstSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"fst end =================================================耗时\" + (System.currentTimeMillis() - start) + \"\\n\");\n    }\n\n    @Test\n    public void testString() {\n        Serializer serializer = null;\n        boolean isCompress;\n        byte[] data = null;\n        String b = null;\n        long start = System.currentTimeMillis();\n        System.out.println(\"\\nstring start =======================================================\");\n\n        System.out.println(\"--------------------no gzip---------------------------------\");\n        isCompress = false;\n        serializer = new StringSerializer();\n        data = serializer.serialize(text, isCompress);\n        System.out.println(\"序例化后不压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, String.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"--------------------use gzip---------------------------------\");\n        isCompress = true;\n        serializer = new StringSerializer();\n        data = serializer.serialize(text, isCompress);\n        System.out.println(\"序例化后压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, String.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n        System.out.println(\"string end ===============================================耗时\" + (System.currentTimeMillis() - start) + \"\\n\");\n    }\n\n    @Test\n    public void testProtostuff() {\n        Serializer serializer = null;\n        boolean isCompress;\n        byte[] data = null;\n        TestBean b = null;\n        long start = System.currentTimeMillis();\n        System.out.println(\"\\nProtostuff start =======================================================\");\n\n        System.out.println(\"--------------------no gzip---------------------------------\");\n        isCompress = false;\n        serializer = new ProtostuffSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后不压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"--------------------use gzip---------------------------------\");\n        isCompress = true;\n        serializer = new ProtostuffSerializer();\n        data = serializer.serialize(new TestBean(1312321111, 222243222L, text), isCompress);\n        System.out.println(\"序例化后压缩的数据大小：\" + data.length);\n        b = serializer.deserialize(data, TestBean.class, isCompress);\n        System.out.println(\"反序例化后的对象：\" + b);\n\n        System.out.println(\"Protostuff end =================================================耗时\" + (System.currentTimeMillis() - start) + \"\\n\");\n    }\n\n    @Test\n    public void testCompress() {\n        long start = System.currentTimeMillis();\n        System.out.println(\"\\ncompress start =======================================================\");\n        System.out.println(\"压缩前数据：\" + text);\n        byte[] data = text.getBytes();\n        System.out.println(\"压缩前的数据大小：\" + data.length);\n        data = GzipProcessor.compress(data);\n        System.out.println(\"压缩后的数据大小：\" + data.length);\n\n        data = GzipProcessor.decompress(data);\n        System.out.println(\"解压缩后数据：\" + new String(data));\n        System.out.println(\"compress end =======================================================耗时\" + (System.currentTimeMillis() - start) + \"\\n\");\n    }\n\n\n    //@Test\n    public void testDeserializer() throws IOException {\n        String filepath = MavenProjects.getTestResourcesPath(\"test.txt\");\n        String data = Files.toString(new File(filepath));\n        System.out.println(data);\n        ByteArrayOutputStream out = new ByteArrayOutputStream();\n        StringBuilder b = new StringBuilder();\n        for (int i = 0; i < data.length();) {\n            char w1 = data.charAt(i++);\n            b.append(w1);\n            if (w1 == '\\\\') {\n                char w2 = data.charAt(i++);\n                b.append(w2);\n                if (w2 == 'x') {\n                    char w3 = data.charAt(i++);\n                    b.append(w3);\n                    char w4 = data.charAt(i++);\n                    b.append(w4);\n                    out.write(Integer.parseInt(new String(new char[] { w3, w4 }), 16));\n                    System.out.println(new String(new char[] { '0', w2, w3, w4 }));\n                }\n                out.write(w2);\n            }\n            out.write(w1);\n        }\n\n        byte[] bytes = out.toByteArray();\n        System.out.println(Bytes.dumpHex(bytes));\n        System.out.println(b.toString());\n\n        JdkSerializer serializer = new JdkSerializer();\n        System.out.println(serializer.deserialize(bytes, String.class, false));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/serial/person.proto",
    "content": "// protoc --proto_path=E:/commons-code/commons-core/src/test/java/code/ponfee/commons/serial --java_out=E:/commons-code/commons-core/src/test/java E:/commons-code/commons-core/src/test/java/code/ponfee/commons/serial/person.proto\n\nsyntax = \"proto3\";\noption java_package = \"cn.ponfee.commons.serial\";\noption java_outer_classname = \"PersonProtobuf\";\n\nmessage Person {\n  int32  id   = 1;\n  string name = 2;\n  int32  age  = 3;\n  Addr   addr = 4;\n  repeated Phone phone = 5;\n}\n\nmessage Addr {\n  string contry = 1;\n  string city = 2;\n}\n\nenum PhoneType {\n  MOBILE = 0;\n  HOME = 1;\n  WORK = 2;\n}\n\nmessage Phone {\n  string number = 1;\n  PhoneType type = 2;\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/BitSetTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport java.util.ArrayList;\nimport java.util.BitSet;\nimport java.util.List;\nimport java.util.Random;\n\npublic class BitSetTest {\n\n    public static void main(String[] args) {\n        Random random = new Random();\n\n        List<Integer> list = new ArrayList<>();\n        int size = 10000000;\n        BitSet bitSet = new BitSet();\n        for (int i = 0; i < size; i++) {\n            int randomResult = random.nextInt(size);\n            list.add(randomResult);\n            bitSet.set(randomResult);\n        }\n\n        System.out.println(\"0~1亿不在上述随机数中有\" + bitSet.size());\n        for (int i = 0; i < size; i++) {\n            if (!bitSet.get(i)) {\n                System.out.println(i);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/BloomFilterTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport java.util.ArrayList;\nimport java.util.BitSet;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Random;\n\nimport com.google.common.hash.BloomFilter;\nimport com.google.common.hash.Funnels;\n\npublic class BloomFilterTest {\n\n    static int sizeOfNumberSet = Integer.MAX_VALUE >> 10;\n\n    static Random generator = new Random();\n\n    public static void main(String[] args) {\n\n        int error = 0;\n        HashSet<Integer> hashSet = new HashSet<Integer>();\n        BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), sizeOfNumberSet, 0.000001);\n\n        for(int i = 0; i < sizeOfNumberSet; i++) {\n            int number = generator.nextInt();\n            if(filter.mightContain(number) != hashSet.contains(number)) {\n                error++;\n            }\n            filter.put(number);\n            hashSet.add(number);\n        }\n\n        System.out.println(\"Error count: \" + error + \", error rate = \" + String.format(\"%f\", (float)error/(float)sizeOfNumberSet));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/ELParserTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport org.junit.Test;\nimport org.springframework.expression.ExpressionParser;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.StandardEvaluationContext;\n\nimport com.google.common.collect.ImmutableMap;\n\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.parser.ELParser;\n\npublic class ELParserTest {\n\n    private static final Pattern PARAMS_PATTERN = Pattern.compile(\"\\\\$\\\\{\\\\s*([a-zA-Z0-9_\\\\-\\\\.]+)\\\\s*\\\\}\");\n    private static final Pattern DATETM_PATTERN = Pattern.compile(\"\\\\$\\\\[\\\\s*([a-zA-Z0-9_,\\\\-\\\\+\\\\.\\\\(\\\\)\\\\s]+)\\\\s*\\\\]\");\n    private static final Pattern SPEL_PATTERN = Pattern.compile(\"\\\\{\\\\{\\\\s*([^\\\\{\\\\}]+)\\\\s*\\\\}\\\\}\");\n\n    @Test\n    public void test1() {\n        String text = \"dfsa |${abc}| a |${12}|  |$[ yyyyMM \\n]|  xx  |$[start_year(  yyyyMMddHHmmss,  -1y)]| aa |$[now(  timestamp\\n,  -1y)]\";\n        Matcher matcher = PARAMS_PATTERN.matcher(text);\n        while (matcher.find()) {\n            System.out.println(matcher.group(1));\n        }\n    }\n\n    @Test\n    public void test2() {\n        String text = \"dfsa |${abc}| a |${12}|  |$[ yyyyMM \\n]|  xx  |$[start_year(  yyyyMMddHHmmss,  -1y)]| aa |$[now(  timestamp\\n,  -1y)]\";\n        Matcher matcher = DATETM_PATTERN.matcher(text);\n        while (matcher.find()) {\n            System.out.println(matcher.group(1));\n        }\n    }\n\n    @Test\n    public void test3() {\n        String text = \"{{#key1}},{{#key2}}\";\n        Matcher matcher = SPEL_PATTERN.matcher(text);\n        while (matcher.find()) {\n            System.out.println(matcher.group(1));\n        }\n    }\n\n    @Test\n    public void test4() {\n        StandardEvaluationContext context = new StandardEvaluationContext(new String[] { \"a\", \"b\" });\n        ExpressionParser parser = new SpelExpressionParser();\n\n        String name = parser.parseExpression(\"#root[0]\").getValue(context, String.class);\n        System.out.println(name);\n\n        name = parser.parseExpression(\"\\\"asfdsaf\\\"\").getValue(context, String.class);\n        System.out.println(name);\n    }\n\n    @Test\n    public void test5() {\n        System.out.println(ELParser.parse(\"dfsa |${abc}| a |${12}|  |$[ yyyyMM \\n]|  xx  |$[start_year(  yyyyMMddHHmmss,  -1y)]| aa |$[now(  timestamp\\n,  -1y)]\", ImmutableMap.of(\"abc\", 123, \"test.1\", \"xxx\")));\n\n        String params =\n            \"{\\\"from\\\":0,\\\"size\\\":5000,\\\"query\\\":{\\\"bool\\\":{\\\"must\\\":[{\\\"range\\\":{\\\"statEndTime\\\":{\\\"from\\\":$[start_year(timestamp)],\\\"to\\\":$[end_day(timestamp)],\\\"include_lower\\\":true,\\\"include_upper\\\":true}}},{\\\"terms\\\":{\\\"companyName\\\":[\\\"xx\\\",\\\"yy\\\"]}}]}}}\";\n        System.out.println(ELParser.parse(params));\n\n        System.out.println(Jsons.toJson(ImmutableMap.of(\"key\", \"val\")));\n        System.out.println(ELParser.parse(\"{{#key1}},{{#key2}}\", ImmutableMap.of(\"key1\", \"val1\", \"key2\", \"val2\")));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/EscapeRegexTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class EscapeRegexTest {\n\n    static String s = \"t\\\\\\\\\\\\\\\\e\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\st.a.bc\";\n    int round = 9999999;\n\n    @Test\n    public void test1() {\n        System.out.println(Strings.escapeRegex(s));\n        Assert.assertEquals(Strings.escapeRegex(s), escapeExprSpecialWord(s));\n        for (int i = 0; i < round; i++) {\n            Strings.escapeRegex(s);\n        }\n    }\n\n    @Test\n    public void test2() {\n        System.out.println(Strings.escapeRegex(s));\n        Assert.assertEquals(Strings.escapeRegex(s), escapeExprSpecialWord(s));\n        for (int i = 0; i < round; i++) {\n            escapeExprSpecialWord(s);\n        }\n    }\n\n    static String[] fbsArr = { \"\\\\\", \"$\", \"(\", \")\", \"*\", \"+\", \".\", \"[\", \"]\", \"?\", \"^\", \"{\", \"}\", \"|\" };\n\n    /** \n     * 转义正则特殊字符 （$()*+.[]?\\^{},|） \n     *  \n     * @param keyword \n     * @return \n     */\n    public static String escapeExprSpecialWord(String keyword) {\n        if (StringUtils.isNotBlank(keyword)) {\n            for (String key : fbsArr) {\n                if (keyword.contains(key)) {\n                    keyword = keyword.replace(key, \"\\\\\" + key);\n                }\n            }\n        }\n        return keyword;\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/FibonacciTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\n\npublic class FibonacciTest {\n\n    public static int fibonacci(int i) {\n        return (i == 1 || i == 2) ? 1 : fibonacci(i - 1) + fibonacci(i - 2);\n    }\n\n    public static void main(String[] args) {\n        BigDecimal previous = new BigDecimal(1), following = new BigDecimal(1), temp;\n        for (int i = 1; i < 1000; i++) {\n            System.out.println(previous.divide(following, 1000, RoundingMode.HALF_UP).toString());\n            temp = following;\n            following = following.add(previous);\n            previous = temp;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/ForEachTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class ForEachTest {\n\n    public static void main(String[] args) {\n        //foreach();\n        forEachRemaining();\n    }\n\n    private static void foreach() {\n        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);\n        list.forEach(System.out::println);\n        System.out.println(\"===================================\");\n        list.forEach(System.out::println);\n    }\n\n    private static void forEachRemaining() {\n        Iterator<Integer> iter = Arrays.asList(1, 2, 3, 4, 5, 6).iterator();\n        \n        iter.forEachRemaining(System.out::println);\n        System.out.println(\"===================================\");\n        iter.forEachRemaining(System.out::println);\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/IdcardResolverTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.date.Dates;\nimport org.junit.Test;\n\n/**\n * \n * \n * @author Ponfee\n */\npublic class IdcardResolverTest {\n\n    @Test\n    public void test1() {\n        for (int i = 0; i < 100; i++) {\n            System.out.println(IdcardResolver.generate());\n        }\n    }\n\n    @Test\n    public void test2() {\n        long start = Dates.toDate(\"1950-01-01 00:00:00\").getTime();\n        for (int i = 0; i < 100; i++) {\n            System.out.println(Dates.format(Dates.random(start, System.currentTimeMillis()), \"yyyy-MM-dd HH:mm:ss.SSS\"));\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/MathsTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.math.Maths;\nimport cn.ponfee.commons.math.Numbers;\nimport org.junit.Test;\nimport org.testng.Assert;\n\nimport java.util.Random;\n\n/**\n * \n * \n * @author Ponfee\n */\npublic class MathsTest {\n\n    @Test\n    public void test1() {\n        int a1 = 100;\n        int b1 = Integer.MAX_VALUE - a1 + 1;\n        System.out.println(\"\\n==============正+正\");\n        Assert.assertTrue((a1 + b1) < 0); // 溢出\n        Assert.assertEquals(Maths.plus(a1, b1), Integer.MAX_VALUE);\n\n        int a2 = -100;\n        int b2 = Integer.MIN_VALUE - a2 - 1;\n        System.out.println(\"\\n==============负+负\");\n        Assert.assertTrue((a2 + b2)>0); // 溢出\n        Assert.assertEquals(Maths.plus(a2, b2), Integer.MIN_VALUE);\n\n        System.out.println(\"\\n==============正-负\");\n        int a3 = Integer.MAX_VALUE - 100;\n        int b3 = -1000;\n        Assert.assertTrue((a3 - b3) < 0); // 溢出\n        Assert.assertEquals(Maths.minus(a3, b3), Integer.MAX_VALUE);\n\n        System.out.println(\"\\n==============负-正\");\n        int a4 = Integer.MIN_VALUE + 10;\n        int b4 = 1000;\n        Assert.assertTrue((a4 - b4) > 0); // 溢出\n        Assert.assertEquals(Maths.minus(a4, b4), Integer.MIN_VALUE);\n    }\n\n    @Test\n    public void test2() {\n        Random ran = new Random(SecureRandoms.nextLong());\n        for (int i = 0; i < 6000; i++) {\n            double num = Math.random() + ran.nextInt(Integer.MAX_VALUE);\n            double a = Math.sqrt(num), b = /*Maths.sqrtNewton(num)*/ Maths.sqrtBinary(num);\n            //String s = \"0011110100010000000000000000000000000000000000000000000000000000\"; \n            // 0011110110010000000000000000000000000000000000000000000000000000\n            if (a != b) {\n                //System.out.println(Bytes.toBinary(Bytes.toBytes(Math.abs(a - b))));\n                System.err.println(Numbers.format(a, 60) + \", \" + Numbers.format(b, 60) + \", \" + Numbers.format(Math.abs(a - b), 60));\n            }\n        }\n    }\n\n    @Test\n    public void test3() {\n        Assert.assertTrue(Maths.sqrtBinary(4) == Math.sqrt(4));\n        Assert.assertTrue(Maths.sqrtBinary(0.01) == Math.sqrt(0.01));\n        Assert.assertTrue(Maths.sqrtBinary(5) == Math.sqrt(5));\n        Assert.assertFalse(Maths.sqrtBinary(9997.9997D) == Math.sqrt(9997.9997D));\n    }\n\n    private static final int ROUND = 9999999;\n\n    @Test\n    public void test31() {\n        for (int i = 2; i < ROUND; i++) {\n            Math.sqrt(i);\n        }\n    }\n\n    @Test\n    public void test32() {\n        for (int i = 2; i < ROUND; i++) {\n            Maths.sqrtBinary(i);\n        }\n    }\n\n    @Test\n    public void test33() {\n        for (int i = 2; i < ROUND; i++) {\n            Maths.sqrtNewton(i);\n        }\n    }\n\n    @Test\n    public void test34() {\n        System.out.println(Maths.sqrtNewton(0));\n        System.out.println(Maths.sqrtNewton(1));\n        System.out.println(Maths.sqrtNewton(0.01));\n        System.out.println(Maths.sqrtNewton(4.0));\n    }\n\n    @Test\n    public void test4() {\n        System.out.println(find(98));\n        Random ran = new Random();\n        for (int i = 1; i < 100; i++) {\n            int number = ran.nextInt(Integer.MAX_VALUE / 4);\n            int a = find(number);\n            /*System.out.println(number);\n            System.out.println(a);\n            System.out.println(calculate(a));\n            System.out.println(calculate(a - 1));*/\n        }\n    }\n\n    // ------------------------------------------------------------------------------------------\n    public static boolean isBorderline(int value, int start, int end) {\n        return value == start || value == end;\n    }\n    // 2-99\n    public static int find(int value) {\n        int start = 1, end = value, number, less = -1, grater = -1;\n        while (!isBorderline(number = (start + end) / 2, start, end)) {\n            //System.out.println(\"-------\" + temp);\n            int a = calculate(number);\n            if (a == value) {\n                return number;\n            } else if (a > value) {\n                grater = number;\n                end = Math.max(number - 1, start);\n            } else {\n                less = number;\n                start = Math.min(number + 1, end);\n            }\n        }\n\n        System.out.println(String.format(\"[less=%d,start=%d,end=%d,grater=%d]\", less, start, end, grater));\n        if (grater == -1) {\n            return -1;\n        }\n        if (less == -1) {\n            return calculate(start - 1) < value ? start : -1;\n        }\n        for (int i = grater - 1; i >= less; i--) {\n            if (i == less) {\n                return i + 1;\n            } else if (calculate(i) < value) {\n                return i + 1;\n            }\n        }\n        return -1;\n    }\n\n    public static int calculate(int n) {\n        if (n < 1) {\n            throw new IllegalArgumentException();\n        }\n\n        int result = 0;\n        do {\n            result += n;\n        } while (--n > 0);\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/MoneyTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.serial.JdkSerializer;\nimport cn.ponfee.commons.serial.Serializer;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.Currency;\nimport java.util.Locale;\n\n/**\n * @author Ponfee\n */\npublic class MoneyTest {\n\n    \n    @Test\n    public void testx() {\n        Assert.assertEquals(\"MXV\", new String(new char[]{0x4d, 0x58, 0x56}));\n    }\n\n    @Test\n    public void test0() {\n        System.out.println(Arrays.toString(Strings.hexadecimal(CurrencyEnum.CZK.currencySymbol())));\n        System.out.println(Bytes.encodeHex(CurrencyEnum.CZK.currencySymbol().substring(1,2).getBytes(StandardCharsets.UTF_8)));\n        System.out.println();\n        for (char x = 'A'; x <= 'Z'; x++) {\n            for (char y = 'A'; y <= 'Z'; y++) {\n                for (char z = 'A'; z <= 'Z'; z++) {\n                    try {\n                        Currency instance = Currency.getInstance(new String(new char[]{x, y, z}));\n                        if (CurrencyEnum.ofCurrencyCode(instance.getCurrencyCode()) == null && CurrencyEnum.ofNumericCode(String.format(\"%03d\", instance.getNumericCode())) == null) {\n                            String text = String.format(\n                                \"/** %s [%s] */\\n%s(new char[]{%s}),\",\n                                instance.getDisplayName(Locale.CHINA),\n                                instance.getSymbol(Locale.CHINA),\n                                instance.getCurrencyCode(),\n                                Strings.join(Arrays.asList(Strings.hexadecimal(instance.getSymbol(Locale.CHINA))), \", \")\n                            );\n                            System.out.println(text);\n                            System.out.println();\n                        }\n                    } catch (Exception e) {\n                    }\n                }\n            }\n        }\n    }\n\n    /*\n    @Test\n    public void testa() {\n        for (CurrencyEnum1 c1 : CurrencyEnum1.values()) {\n            CurrencyEnum c0 = CurrencyEnum.ofCurrencyCode(c1.currencyCode());\n            if (c0 == null) {\n                System.err.println(\"not found: \"+c1.currencyCode());\n                continue;\n            }\n            if (c1.currencySymbol().equals(c0.currencySymbol())) {\n                continue;\n            }\n            System.out.println(c1.currencyCode()+\", \"+c0.currencySymbol()+\", \"+c1.currencySymbol());\n        }\n    }\n    */\n    \n    @Test\n    public void test1() {\n        System.out.println(CurrencyEnum.INR.currencySymbol());\n        System.out.println(CurrencyEnum.UAH.currencySymbol());\n        System.out.println(CurrencyEnum.CHF.currencySymbol());\n        System.out.println(CurrencyEnum.AZN.currencySymbol());\n        System.out.println(CurrencyEnum.GHS.currencySymbol());\n        System.out.println(CurrencyEnum.TRY.currencySymbol());\n        Money money = new Money(CurrencyEnum.USD.currency(), 53243234);\n        System.out.println(money);\n\n        System.out.println(\"----------------------\");\n        System.out.println(CurrencyEnum.JPY.currencyCode());\n        System.out.println(CurrencyEnum.JPY.numericCode());\n        System.out.println(CurrencyEnum.JPY.currencySymbol());\n\n        System.out.println(\"----------------------\");\n        System.out.println(CurrencyEnum.JPY.currency().getCurrencyCode());\n        System.out.println(CurrencyEnum.JPY.currency().getNumericCode());\n        System.out.println(CurrencyEnum.JPY.currency().getSymbol());\n        System.out.println(CurrencyEnum.JPY.currency().getSymbol(Locale.CHINA));\n    }\n\n    @Test\n    public void test2() {\n        System.out.println(CurrencyEnum.CNY.currency().getNumericCode());\n        for (CurrencyEnum c : CurrencyEnum.values()) {\n            System.out.println(c.currency().getSymbol(Locale.CHINA));\n            if (!c.numericCode().equals(String.format(\"%03d\", c.currency().getNumericCode()))) {\n                System.out.println(c.numericCode() + \" != \" + c.currency().getNumericCode());\n            }\n        }\n    }\n\n    @Test\n    public void test3() {\n        Money money = new Money(CurrencyEnum.USD.currency(), 53243234);\n\n        //Serializer ser = KryoSerializer.INSTANCE;\n        Serializer ser = new JdkSerializer();\n        byte[] data = ser.serialize(money);\n        System.out.println(ser.deserialize(data, Money.class));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/ObjectUtilsTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.IntStream;\n\nimport cn.ponfee.commons.concurrent.ThreadPoolTestUtils;\nimport com.google.common.base.Stopwatch;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport org.openjdk.jol.info.ClassLayout;\n\npublic class ObjectUtilsTest {\n\n    static int round = 100000000;\n\n    private static final Map<String, Object> map = new HashMap<>();\n\n    @Test\n    public void test0() {\n        System.out.println(ObjectUtils.newInstance(int.class));\n        System.out.println(ObjectUtils.newInstance(Integer.class));\n        System.out.println(ObjectUtils.newInstance(String.class));\n        System.out.println(ObjectUtils.newInstance(Map.class));\n        System.out.println(ObjectUtils.newInstance(List.class));\n\n        System.out.println(\"\\n-----------------------\");\n        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());\n        System.out.println(\"\\n-----------------------\");\n        System.out.println(ClassLayout.parseInstance(new Object[10]).toPrintable());\n        System.out.println(\"\\n-----------------------\");\n        System.out.println(ClassLayout.parseInstance(new long[10]).toPrintable());\n    }\n\n    @Test\n    public void test1() {\n        for (int i = 0; i < round; i++) {\n            ObjectUtils.newInstance(Result.class);\n        }\n    }\n\n    @Test\n    public void test2() {\n        for (int i = 0; i < round; i++) {\n            ClassUtils.newInstance(Result.class);\n        }\n    }\n\n    @Test\n    public void test3() throws Exception {\n        for (int i = 0; i < round; i++) {\n            Result.class.getConstructor().newInstance();\n        }\n    }\n\n    @Test\n    public void test4() {\n        Byte b1 = 127;\n        Byte b2 = 127;\n        Byte b3 = new Byte((byte) 127);\n        System.out.println(b1 == b2);\n        System.out.println(b3 == b2);\n    }\n\n    @Test\n    public void test5() {\n        Byte b1 = 127;\n        Byte b2 = 127;\n        Byte b3 = new Byte((byte) 127);\n        System.out.println(b1 == b2);\n        System.out.println(b3 == b2);\n    }\n\n    @Test\n    public void test6() {\n        // XXX: if Executor is CALLER_RUN_EXECUTOR and threadNumber>=33 then will be dead loop\n        execute(32, () -> {\n            get(\"123\");\n        }, 5, ThreadPoolTestUtils.CALLER_RUN_SCHEDULER);\n    }\n\n    @Test\n    public void test7() {\n        // XXX: if Executor is CALLER_RUN_EXECUTOR and threadNumber>=33 then will be dead loop\n        execute(32, () -> {\n            Singleton.getInstance();\n        }, 5, ThreadPoolTestUtils.CALLER_RUN_SCHEDULER);\n    }\n\n    public static Object get(String key) {\n        Object val = map.get(key);\n        if (val == null) {\n            synchronized (map) {\n                if ((val = map.get(key)) == null) {\n                    map.put(key, val = new Object());\n                    System.err.println(\"================\");\n                }\n            }\n        }\n        return val;\n    }\n\n    public static class Singleton {\n        private static Singleton instance = null;\n\n        private Singleton() {}\n\n        public static Singleton getInstance() {\n            if (instance == null) {\n                synchronized (Singleton.class) {\n                    if (instance == null) {\n                        System.err.println(\"================\");\n                        instance = new Singleton();\n                    }\n                }\n            }\n            return instance;\n        }\n    }\n\n    class Helper {\n    }\n\n    class Foo {\n        /** \n         * If perThreadInstance.get() returns a non-null value, this thread\n         * has done synchronization needed to see initialization\n         * of helper \n         */\n        private final ThreadLocal perThreadInstance = new ThreadLocal();\n        private Helper helper = null;\n\n        public Helper getHelper() {\n            if (perThreadInstance.get() == null) createHelper();\n            return helper;\n        }\n\n        private final void createHelper() {\n            synchronized (this) {\n                if (helper == null) helper = new Helper();\n            }\n            // Any non-null value would do as the argument here\n            perThreadInstance.set(perThreadInstance);\n        }\n    }\n\n\n    /**\n     * Exec async, usual use in test case\n     *\n     * @param parallelism the parallelism\n     * @param command     the command\n     * @param execSeconds the execSeconds\n     * @param executor    the executor\n     */\n    public static void execute(int parallelism, Runnable command,\n                               int execSeconds, Executor executor) {\n        Stopwatch watch = Stopwatch.createStarted();\n        AtomicBoolean flag = new AtomicBoolean(true);\n\n        // CALLER_RUNS: caller run will be dead loop\n        // caller thread will be loop exec command, can't to run the after code{flag.set(false)}\n        // threadNumber > 32\n        CompletableFuture<?>[] futures = IntStream\n            .range(0, parallelism)\n            .mapToObj(i -> (Runnable) () -> {\n                while (flag.get() && !Thread.currentThread().isInterrupted()) {\n                    command.run();\n                }\n            })\n            .map(runnable -> CompletableFuture.runAsync(runnable, executor))\n            .toArray(CompletableFuture[]::new);\n\n        try {\n            // parent thread sleep\n            Thread.sleep(execSeconds * 1000L);\n            flag.set(false);\n            CompletableFuture.allOf(futures).join();\n        } catch (InterruptedException e) {\n            flag.set(false);\n            Thread.currentThread().interrupt();\n            throw new RuntimeException(e);\n        } finally {\n            System.out.println(\"multi thread exec async duration: \" + watch.stop());\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/ProxyTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.junit.Test;\nimport org.springframework.cglib.proxy.Enhancer;\nimport org.springframework.cglib.proxy.MethodInterceptor;\n\nimport java.util.Arrays;\n\n/**\n * @author Ponfee\n */\npublic class ProxyTest {\n\n    @Test\n    public void test() {\n        User user = LazyLoader.of(User.class, ProxyTest::findById, 1);\n        System.out.println(\"------222\");\n        System.out.println(\"bean1: \" + System.identityHashCode(user) + \", \" + user.getClass());\n        System.out.println(\"---------user.getId()\");\n        System.out.println(user.getId());\n        System.out.println(user.getName());\n        System.out.println(user.getSex());\n        System.out.println(user.toString());\n    }\n\n    public static User findById(Integer id) {\n        System.out.println(\"-------bbb\");\n        return new User(1, \"bob\", \"男\");\n    }\n\n    @Data\n    @NoArgsConstructor\n    @AllArgsConstructor\n    public static class User {\n        private Integer id;\n        private String name;\n        private String sex;\n    }\n\n\n    //---------------------------------------------------------------------------------------\n\n    @Test\n    public void test2() {\n        TargetBean bean2 = (TargetBean) createProxyBean(new TargetBean());\n        System.out.println(\"bean1: \" + System.identityHashCode(bean2) + \", \" + bean2.getClass());\n        bean2.say();\n    }\n\n    /**\n     * 创建代理类\n     *\n     * @param targetBean 源Bean\n     * @return 代理Bean\n     */\n    private Object createProxyBean(final Object targetBean) {\n        System.out.println(\"targetBean: \" + System.identityHashCode(targetBean) + \", \" + targetBean.getClass());\n        Enhancer enhancer = new Enhancer();\n        enhancer.setSuperclass(targetBean.getClass());\n        enhancer.setUseCache(true);\n        enhancer.setInterceptDuringConstruction(false);\n        enhancer.setCallback((MethodInterceptor) (proxy, method, args, methodProxy) -> {\n            System.out.println(\"proxy: \" + System.identityHashCode(proxy) + \", \" + proxy.getClass());\n            System.out.println(\"method: \" + method.toGenericString());\n            System.out.println(\"args: \" + Arrays.toString(args));\n            System.out.println(\"methodProxy: \" + System.identityHashCode(methodProxy) + \", \" + methodProxy.getClass());\n            System.err.println(\"代理前\");\n            //System.out.println(\"method.invoke(bean1, args): \" + method.invoke(bean1, args)); // bean1为代理类，此处会死循环\n            //System.out.println(\"method.invoke(targetBean, args): \" + method.invoke(targetBean, args));\n            Object result = methodProxy.invokeSuper(proxy, args); // 调用父类方法\n            System.err.println(\"代理后\");\n            return result;\n        });\n        Object targetProxyBean = enhancer.create();\n        return targetProxyBean;\n    }\n\n    public static class TargetBean {\n        public TargetBean() {\n\n        }\n\n        public void say() {\n            System.err.println(\"Hello\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/RegexUtilsTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\npublic class RegexUtilsTest {\n\n    @Test\n    public void testIsIPv4() {\n        assertTrue(RegexUtils.isIpv4(\"127.0.0.1\"));\n        assertTrue(RegexUtils.isIpv4(\"0.0.0.0\"));\n        assertTrue(RegexUtils.isIpv4(\"255.255.255.255\"));\n    }\n\n    @Test\n    public void testIsIPv6() {\n        assertTrue(RegexUtils.isIpv6(\"::1\"));\n        assertFalse(RegexUtils.isIpv6(\"1200::AB00:1234::2552:7777:1313\"));\n        assertTrue(RegexUtils.isIpv6(\"1200:0000:AB00:1234:0000:2552:7777:1313\"));\n        assertTrue(RegexUtils.isIpv6(\"21DA:D3:0:2F3B:2AA:FF:FE28:9C5A\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/SqlUtilsTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport static org.junit.Assert.assertEquals;\n\nimport org.junit.Test;\n\npublic class SqlUtilsTest {\n\n    @Test\n    public void trimSql() {\n        assertEquals(\"\",                         SqlUtils.trim(\"         ; ; ; ;;   \"));\n        assertEquals(\"select * from t lImit 10\", SqlUtils.trim(\"  select * from t lImit 10\"));\n        assertEquals(\"select * from t lImit 10\", SqlUtils.trim(\"select * from t lImit 10    \"));\n        assertEquals(\"select * from t lImit 10\", SqlUtils.trim(\"select * from t lImit 10;    \"));\n        assertEquals(\"select * from t lImit 10\", SqlUtils.trim(\"select * from t lImit 10 ;\"));\n        assertEquals(\"select * from t lImit 10\", SqlUtils.trim(\"select * from t lImit 10    ;; ; ;; ;  \"));\n    }\n\n    @Test\n    public void limitMsql() {\n        assertEquals(\"select * from t lImit 10\",       SqlUtils.limitMysql(\"select * from t lImit 10\", 100));\n        assertEquals(\"select * from t lImit 1000,10\",  SqlUtils.limitMysql(\"select * from t lImit 1000,10\", 100));\n        assertEquals(\"select * from t LIMIT 100\",      SqlUtils.limitMysql(\"select * from t lImit 118\", 100));\n        assertEquals(\"select * from t LIMIT 1951,100\", SqlUtils.limitMysql(\"select * from t liMiT 1951 , 210\", 100));\n        assertEquals(\"select * from t  LIMIT 100\",     SqlUtils.limitMysql(\"select * from t \", 100));\n    }\n\n    @Test\n    public void limitPgsql() {\n        assertEquals(\"select * from t lImit 12 \",            SqlUtils.limitPgsql(\"select * from t lImit 12 \", 100));\n        assertEquals(\"select * from t LIMIT 11 OFFSET 9999 \",SqlUtils.limitPgsql(\"select * from t LIMIT 11 OFFSET 9999 \", 100));\n        assertEquals(\"select * from t LIMIT 100\",            SqlUtils.limitPgsql(\"select * from t lImit 118\", 100));\n        assertEquals(\"select * from t LIMIT 100 OFFSET 210\", SqlUtils.limitPgsql(\"select * from t liMiT 1951 ofFseT  210\", 100));\n        assertEquals(\"select * from t  LIMIT 100\",           SqlUtils.limitPgsql(\"select * from t \", 100));\n    }\n\n    @Test\n    public void limitOracle() {\n        assertEquals(\"select * from t WHERE  ROWNUM<100\",                                   SqlUtils.limitOracle(\"select * from t\", 100));\n        assertEquals(\"select * from where rownum<=10\",                                      SqlUtils.limitOracle(\"select * from where rownum<=10\", 100));\n        assertEquals(\"select * from where rownum<=10 and  1=1\",                             SqlUtils.limitOracle(\"select * from where rownum<=10 and  1=1\", 100));\n        assertEquals(\"select * from where   1=1 aNd rownum<=10\",                            SqlUtils.limitOracle(\"select * from where   1=1 aNd rownum<=10\", 100));\n        assertEquals(\"select * from WHERE  ROWNUM<100\",                                     SqlUtils.limitOracle(\"select * from rownum<1000\", 100));\n        assertEquals(\"select * from WHERE  ROWNUM<100 AND 1=1\",                             SqlUtils.limitOracle(\"select * from rownum<=999 AND 1=1\", 100));\n        assertEquals(\"select * from where ROWNUM<100\",                                      SqlUtils.limitOracle(\"select * from where rownum<=12346\", 100));\n        assertEquals(\"select * from (select * from where 1=1) t WHERE  ROWNUM<100\",         SqlUtils.limitOracle(\"select * from (select * from where 1=1) t\", 100));\n        assertEquals(\"select * from (select * from where 1=1) t where 1=1 AND  ROWNUM<100\", SqlUtils.limitOracle(\"select * from (select * from where 1=1) t where 1=1\", 100));\n        assertEquals(\"select * from (select * from where 1=1) t where 1=1 AND  ROWNUM<100 AND 2=2\", SqlUtils.limitOracle(\"select * from (select * from where 1=1) t where 1=1 AND  rownuM<1000 AND 2=2\", 100));\n    }\n\n    @Test\n    public void limitSqlServer() {\n        assertEquals(\"select TOP 100 * from t \",                          SqlUtils.limitMssql(\"select * from t \", 100));\n        assertEquals(\"select TOP 100 * from t \",                          SqlUtils.limitMssql(\"select TOP 1000 * from t \", 100));\n        assertEquals(\"select * from (select TOP 100 * from a) b\",         SqlUtils.limitMssql(\"select * from (select TOP 1000 * from a) b\", 100));\n        assertEquals(\"select TOP 100 * from (select TOP 1000 * from a) b\",SqlUtils.limitMssql(\"select TOP 101 * from (select TOP 1000 * from a) b\", 100));\n    }\n\n    @Test\n    public void limitHive() {\n        assertEquals(\"select * from t lImit 10\",                   SqlUtils.limitHive(\"select * from t lImit 10\", 100));\n        assertEquals(\"select * from t LIMIT 100\",                  SqlUtils.limitHive(\"select * from t lImit 118\", 100));\n        assertEquals(\"select * from t  LIMIT 100\",                 SqlUtils.limitHive(\"select * from t \", 100));\n        assertEquals(\"select * from t lImit 1000,10 LIMIT 100\",    SqlUtils.limitHive(\"select * from t lImit 1000,10\", 100));\n        assertEquals(\"select * from t liMiT 1951 , 210 LIMIT 100\", SqlUtils.limitHive(\"select * from t liMiT 1951 , 210\", 100));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/StreamForkerTest.java",
    "content": "package cn.ponfee.commons.util;\n\nimport java.util.stream.Stream;\n\nimport cn.ponfee.commons.collect.StreamForker;\n\npublic class StreamForkerTest {\n\n    public static void main(String[] args) throws Exception {\n        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 4, 5, 5);\n        StreamForker.Results results = new StreamForker<>(stream)\n                .fork(1, s -> s.max(Integer::compareTo)) // 直接聚合\n                //.fork(2, s -> s.distinct().collect(Collectors.reducing(Integer::sum))) // 先收集再聚合\n                //.fork(2, s -> s.distinct().collect(Collectors.summingInt(Integer::intValue))) // 先收集再聚合\n                .fork(2, s -> s.distinct().reduce(0, Integer::sum))\n                .getResults();\n        System.out.println(results.get(1) + \"\");\n        System.out.println(results.get(2) + \"\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/StringsTest.java",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\npackage cn.ponfee.commons.util;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/**\n * Strings test\n *\n * @author Ponfee\n */\npublic class StringsTest {\n\n    @Test\n    public void testWildcardMatch() {\n        Assert.assertFalse(Strings.isMatch(\"aa\", \"a\"));\n        Assert.assertTrue(Strings.isMatch(\"aa\", \"aa\"));\n        Assert.assertFalse(Strings.isMatch(\"aaa\", \"aa\"));\n        Assert.assertTrue(Strings.isMatch(\"aa\", \"*\"));\n        Assert.assertTrue(Strings.isMatch(\"aa\", \"a*\"));\n        Assert.assertTrue(Strings.isMatch(\"ab\", \"?*\"));\n        Assert.assertFalse(Strings.isMatch(\"aab\", \"c*a*b\"));\n    }\n\n    @Test\n    public void testToSeparatedName() {\n        Assert.assertEquals(\"test.to.separated.name\", Strings.toSeparatedName(\"testToSeparatedName\", '.'));\n        Assert.assertEquals(\"testToSeparatedName\", Strings.toCamelcaseName(\"test.to.separated.name\", '.'));\n\n        Assert.assertEquals(\"test-to-separated-name\", Strings.toSeparatedName(\"testToSeparatedName\", '-'));\n        Assert.assertEquals(\"testToSeparatedName\", Strings.toCamelcaseName(\"test-to-separated-name\", '-'));\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/cn/ponfee/commons/util/TestSerialize.java",
    "content": "package cn.ponfee.commons.util;\n\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.serial.WrappedSerializer;\n\nimport java.lang.reflect.Field;\nimport java.util.Date;\n\n/**\n * \n * @author Ponfee\n */\npublic class TestSerialize {\n\n    public static enum E {\n        A, B\n    }\n\n    public TestSerialize(byte a, Byte b, int c, Integer d, float f, Float g, E h, Date i) {\n        super();\n        this.a = a;\n        this.b = b;\n        this.c = c;\n        this.d = d;\n        this.f = f;\n        this.g = g;\n        this.h = h;\n        this.i = i;\n    }\n\n    private byte a;\n    private Byte b;\n    private int c;\n    private Integer d;\n    private float f;\n    private Float g;\n    private E h;\n    private Date i;\n\n    public byte getA() {\n        return a;\n    }\n\n    public void setA(byte a) {\n        this.a = a;\n    }\n\n    public Byte getB() {\n        return b;\n    }\n\n    public void setB(Byte b) {\n        this.b = b;\n    }\n\n    public int getC() {\n        return c;\n    }\n\n    public void setC(int c) {\n        this.c = c;\n    }\n\n    public Integer getD() {\n        return d;\n    }\n\n    public void setD(Integer d) {\n        this.d = d;\n    }\n\n    public float getF() {\n        return f;\n    }\n\n    public void setF(float f) {\n        this.f = f;\n    }\n\n    public Float getG() {\n        return g;\n    }\n\n    public void setG(Float g) {\n        this.g = g;\n    }\n\n    public E getH() {\n        return h;\n    }\n\n    public void setH(E h) {\n        this.h = h;\n    }\n\n    public Date getI() {\n        return i;\n    }\n\n    public void setI(Date i) {\n        this.i = i;\n    }\n\n    @Override\n    public String toString() {\n        return \"TestSerialize [a=\" + a + \", b=\" + b + \", c=\" + c + \", d=\" + d + \", f=\" + f + \", g=\" + g + \", h=\" + h + \", i=\" + i + \"]\";\n    }\n\n    public static void main(String[] args) {\n        WrappedSerializer serializer = WrappedSerializer.WRAPPED_KRYO_SERIALIZER;\n        //byte[] data = serializer.serialize(new Integer(1));\n        byte[] data = serializer.serialize((Integer)null);\n        System.out.println(serializer.deserialize(data, int.class));\n        \n        TestSerialize source = new TestSerialize((byte) 12, (Byte) (byte) 23, 34, 45, 56f, 67f, E.A, new Date());\n        System.out.println(source);\n        TestSerialize target = new TestSerialize((byte) 0, null, 1, 2, 3, null, E.B, null);\n        System.out.println(target);\n\n        for (Field field : ClassUtils.listFields(TestSerialize.class)) {\n            byte[] value = serializer.serialize(Fields.get(source, field));\n            Fields.put(target, field, serializer.deserialize(value, field.getType()));\n        }\n        System.out.println(target);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/Cat.java",
    "content": "package test;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.Arrays;\n\n// java test.Cat -n  < Cat.java | java test.Cat -n\npublic class Cat {\n    public static void main(String[] args) throws IOException {\n        //是否显示行号，使用参数 -n 启用\n        boolean showNumber = args.length > 0 && Arrays.asList(args).contains(\"-n\");\n        int num = 0;\n        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));\n        String line = reader.readLine();\n        while (line != null) {\n            if (showNumber) {\n                System.out.printf(\"%1$8s %2$s%n\",  num++, line);\n            } else {\n                System.out.println(line);\n            }\n            line = reader.readLine();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/CsvWrappedCharTest.java",
    "content": "package test;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.google.common.io.Files;\nimport com.google.common.io.LineProcessor;\n\nimport cn.ponfee.commons.io.WrappedBufferedWriter;\n\npublic class CsvWrappedCharTest {\n\n    public static void main(String[] args) throws IOException {\n        String file = \"D:\\\\download\\\\出库报表.csv\";\n\n        WrappedBufferedWriter writer = new WrappedBufferedWriter(new File(\"d:\\\\出库报表-修正.csv\"), StandardCharsets.UTF_8);\n\n        Files.asCharSource(new File(file), StandardCharsets.UTF_8).readLines(new LineProcessor<String>() {\n            @Override\n            public boolean processLine(String line) throws IOException {\n                String[] array = new String[8];\n                String[] s = line.split(\",\");\n\n                array[0] = s[0];\n                array[1] = s[1];\n                array[2] = s[2];\n                array[3] = s[3];\n\n                array[7] = s[s.length - 1];\n                array[6] = s[s.length - 2];\n                array[5] = s[s.length - 3];\n\n                array[4] = StringUtils.join(s, \",\", 4, s.length - 3);\n\n                String str = Arrays.stream(array).collect(Collectors.joining(\",\", \"\\\"\", \"\\\"\"));\n                System.out.println(str);\n\n                writer.write(str);\n                writer.write(cn.ponfee.commons.io.Files.UNIX_LINE_SEPARATOR);\n\n                return StringUtils.isNotBlank(line);\n            }\n\n            @Override\n            public String getResult() {\n                return null;\n            }\n        });\n\n        writer.close();\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/CustomClassLoader.java",
    "content": "package test;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Arrays;\n\nimport com.google.common.io.Files;\n\npublic class CustomClassLoader extends ClassLoader {\n\n    private final byte[] classBytes;\n\n    public CustomClassLoader() {\n        this.classBytes = null;\n    }\n\n    public CustomClassLoader(File classFile) throws IOException {\n        this.classBytes = Files.toByteArray(classFile);\n    }\n\n    protected Class<?> findClass(String name) throws ClassNotFoundException {\n        return defineClass(name, classBytes, 0, classBytes.length);\n    }\n\n    public static void main(String[] args) throws Exception {\n        CustomClassLoader classLoader1 = new CustomClassLoader();\n        CustomClassLoader classLoader2 = new CustomClassLoader(new File(\"D:\\\\test\\\\ToolProvider.class\"));\n\n        System.out.println(Arrays.toString(classLoader1.loadClass(\"javax.tools.ToolProvider\").getDeclaredMethods()));\n        System.out.println(\"\\n============================================\");\n\n        Class<?> objClass = classLoader2.findClass(\"javax.tools.ToolProvider\");\n        System.out.println(Arrays.toString(objClass.getDeclaredMethods()));\n        objClass.getDeclaredMethod(\"say\").invoke(objClass.newInstance());\n        \n        \n        cn.ponfee.commons.io.Files.touch(new File(\"D:\\\\test\\\\a\\\\b\\\\ToolProvider1.class\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/GuavaCacheRefreshTest.java",
    "content": "package test;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.junit.Test;\n\nimport com.google.common.cache.CacheBuilder;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\nimport com.google.common.util.concurrent.Futures;\nimport com.google.common.util.concurrent.ListenableFuture;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\n/**\n * @author Ponfee\n */\npublic class GuavaCacheRefreshTest {\n    public class SkuCache {\n        private String skuId;\n        private String skuCode;\n        private Long realQuantity;\n\n        public String getSkuId() {\n            return skuId;\n        }\n\n        public String getSkuCode() {\n            return skuCode;\n        }\n\n        public Long getRealQuantity() {\n            return realQuantity;\n        }\n\n        public void setSkuId(String skuId) {\n            this.skuId = skuId;\n        }\n\n        public void setSkuCode(String skuCode) {\n            this.skuCode = skuCode;\n        }\n\n        public void setRealQuantity(Long realQuantity) {\n            this.realQuantity = realQuantity;\n        }\n\n    }\n\n    AtomicInteger loadTimes = new AtomicInteger(0);\n    AtomicInteger count = new AtomicInteger(0);\n\n    @Test\n    public void testCacheUse() throws Exception {\n        LoadingCache<String, SkuCache> loadingCache = CacheBuilder.newBuilder().refreshAfterWrite(1000, TimeUnit.MILLISECONDS)\n            //Prevent data reloading from failing, but the value of memory remains the same\n            .expireAfterWrite(1500, TimeUnit.MILLISECONDS).build(new CacheLoader<String, SkuCache>() {\n                @Override\n                public SkuCache load(String key) {\n                    System.out.println(\"============================load \" + key);\n                    return load0(key);\n                }\n\n                @Override\n                public ListenableFuture<SkuCache> reload(String key, SkuCache oldValue) throws Exception {\n                    checkNotNull(key);\n                    checkNotNull(oldValue);\n                    System.out.println(\"============================reload \" + key);\n                    return Futures.immediateFuture(load0(key));\n                }\n\n                private SkuCache load0(String key) {\n                    SkuCache skuCache = new SkuCache();\n                    skuCache.setSkuCode(key + \"---\" + (loadTimes.incrementAndGet()));\n                    skuCache.setSkuId(key);\n                    skuCache.setRealQuantity(100L);\n                    return skuCache;\n                }\n            });\n\n        int count = 5;\n        Thread[] threads = new Thread[count];\n        for (int i = 0; i < count; i++) {\n            threads[i] = new Thread(() -> {\n                try {\n                    getValue(loadingCache);\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            });\n        }\n\n        for (Thread t : threads) {\n            t.start();\n        }\n        for (Thread t : threads) {\n            t.join();\n        }\n        System.out.println(\"finish\");\n    }\n\n    private void getValue(LoadingCache<String, SkuCache> loadingCache) throws Exception {\n        for (int i = 0; i < 10; i++) {\n            Thread.sleep(300l);\n            System.out.println(loadingCache.get(\"sku\").toString() + \" - \" + count.incrementAndGet());\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/Test1.java",
    "content": "package test;\n\nimport java.io.ByteArrayOutputStream;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.BiConsumer;\nimport java.util.function.BiFunction;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\n\nimport cn.ponfee.commons.base.tuple.Tuple2;\nimport cn.ponfee.commons.collect.ArrayHashKey;\nimport cn.ponfee.commons.concurrent.ThreadPoolTestUtils;\nimport cn.ponfee.commons.util.CurrencyEnum;\nimport cn.ponfee.commons.util.Money;\nimport com.alibaba.fastjson.JSON;\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.Test;\n\nimport com.google.common.base.Stopwatch;\nimport com.google.common.collect.Lists;\n\nimport cn.ponfee.commons.jce.CryptoProvider;\nimport cn.ponfee.commons.resource.ResourceLoaderFacade;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.date.Dates;\nimport cn.ponfee.commons.util.MavenProjects;\n\n/**\n *\n * @author Ponfee\n */\npublic class Test1 {\n\n    public static void main(String[] args) throws InterruptedException, ExecutionException, JsonProcessingException {\n        Money money = Money.of(CurrencyEnum.CNY.currency(), 100);\n        String json = JSON.toJSONString(money);\n        System.out.println(json);\n        System.out.println(new ObjectMapper().writeValueAsString(money));\n        Money money1 = JSON.parseObject(json, Money.class);\n        Money money2 = new ObjectMapper().readValue(json, Money.class);\n        System.out.println(money1.equals(money2));\n        System.out.println(new ObjectMapper().writeValueAsString(money1));\n\n\n\n        String nids = Arrays.stream(new Integer[]{1,2,3}).map(e -> String.valueOf(e)).collect(Collectors.joining(\",\"));\n        System.out.println(nids);\n        String[] arr = {\"a\", \"b\"};\n        System.out.println(Arrays.toString(Arrays.copyOfRange(arr, 1, 1)));\n        System.out.println(null==null);\n        Tuple2 a = Tuple2.of(String.class, ArrayHashKey.of(new Class[]{IntStream.class, Object.class}));\n        Tuple2 b = Tuple2.of(String.class, ArrayHashKey.of(new Class[]{IntStream.class, Object.class}));\n        System.out.println(a.equals(b));\n        System.out.println(a.hashCode() == b.hashCode());\n        System.out.println((-1L ^ (-1L << 10)) == (~(-1L << 10)));\n        System.out.println(String.format(\"%02d\", 1));\n        Date d1 = Dates.toDate(\"2019-05-10 10:23:34\");\n        Date d2 = Dates.toDate(\"2019-05-11 08:23:34\");\n\n        System.out.println(Dates.daysBetween(Dates.startOfDay(d1), Dates.endOfDay(d2)));\n\n        System.out.println(List.class.isInstance(null));\n        Stopwatch watch = Stopwatch.createStarted();\n        CompletableFuture<String> future1 = new CompletableFuture<>();\n        new Thread(()->{\n            try {\n                Thread.sleep(2000);\n                future1.complete(\"test1\"); // 完成，会通知CompletableFuture.get()\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }).start();\n        System.out.println(\"===================\");\n        System.out.println(future1.get() + \" cost: \" + watch.stop().toString());\n        System.out.println(\"===================\");\n\n        System.out.println();\n        watch.reset().start();\n        CompletableFuture<String> future2 = CompletableFuture.completedFuture(\"test2\");\n        System.out.println(\"===================\");\n        System.out.println(future2.get() + \" cost: \" + watch.stop().toString());\n        System.out.println(\"===================\");\n    }\n\n    @Test\n    public void test() throws InterruptedException, ExecutionException {\n        System.out.println(\"开始...\");\n\n        CompletableFuture<Object> fu = CompletableFuture.supplyAsync(new Supplier<String>() {\n            @Override\n            public String get() {\n                try {\n                    TimeUnit.SECONDS.sleep(5);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n\n                System.out.println(\"返回 zhang\");\n                return \"zhang\";\n            }\n        }).thenCombine(CompletableFuture.supplyAsync(new Supplier<String>() {\n            @Override\n            public String get() {\n                try {\n                    TimeUnit.SECONDS.sleep(3);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n\n                System.out.println(\"返回 phil\");\n                return \"phil\";\n            }\n        }), new BiFunction<String, String, Object>() {\n            @Override\n            public Object apply(String s1, String s2) {\n                String s = s1 + \" , \" + s2;\n                System.out.println(\"apply:\" + s);\n                return s;\n            }\n        }).whenCompleteAsync(new BiConsumer<Object, Throwable>() {\n            @Override\n            public void accept(Object o, Throwable throwable) {\n                System.out.println(\"accept:\" + o.toString());\n            }\n        });\n\n\n        System.out.println(fu.get());\n    }\n\n    @Test\n    public void test2() throws InterruptedException {\n        Stream.of(1,2,3,4,5).map(i -> CompletableFuture.runAsync(()-> {\n            try {\n                Thread.sleep(ThreadLocalRandom.current().nextInt(2000));\n            } catch (InterruptedException e) {\n                // TODO Auto-generated catch block\n                e.printStackTrace();\n            }\n            System.out.println(i);\n        }))\n        .count();\n        Thread.sleep(5000);\n    }\n    @Test\n    public void test3() throws InterruptedException {\n        System.out.println(ThreadPoolTestUtils.INFINITY_QUEUE_EXECUTOR);\n        Lists.newArrayList(1,2,3)\n            .parallelStream().parallel().sequential() // 并行或串行由最后一个设置决定\n        ;\n        // ClassName::staticMethodName\n        // ClassName::new\n        // TypeName[]::new           int[]::new <=> (x -> new int[x])\n        // ClassName::instanceMethodName\n        // instance::instanceMethodName\n        // super::instanceMethodName\n        // this::instanceMethodName\n        Lists.newArrayList(1,2,3).stream().filter(this::test).forEach(System.out::println);\n    }\n\n\n    public boolean test(Integer i) {\n        return i > 2;\n    }\n    @Test\n    public void test5() throws Exception {\n        byte[] data = MavenProjects.getTestJavaFileAsBytes(this.getClass());\n        System.out.println((data.length + 15) / 16);\n        System.out.println(Integer.toHexString((data.length + 15) / 16));\n        String lineNumberFormat = \"%0\" + Integer.toHexString((data.length + 15) / 16).length() + \"x: \";\n        System.out.println(lineNumberFormat);\n        System.out.println(Bytes.dumpHex(data));\n\n        ByteArrayOutputStream out = new ByteArrayOutputStream();\n        /*org.apache.commons.io.HexDump.dump(MavenProjects.getTestJavaFileAsByteArray(this.getClass()), 0, out, 0);\n        System.out.println(new String(out.toByteArray()));*/\n\n        /*new sun.misc.HexDumpEncoder().encode(MavenProjects.getTestJavaFileAsByteArray(this.getClass()), out);\n        System.out.println(new String(out.toByteArray()));*/\n\n\n        //System.out.println(Bytes.hexDump(Bytes.fromChar(Numbers.CHAR_ZERO)));\n    }\n\n    @Test\n    public void test6() throws Exception {\n        //System.out.println(IOUtils.toString(ResourceLoaderFacade.getResource(\"/gbkxxx.txt\", CryptoProvider.class).getStream(), \"GBK\"));\n        System.out.println(IOUtils.toString(ResourceLoaderFacade.getResource(\"cert/gbkyyy.txt\", CryptoProvider.class).getStream(), \"GBK\"));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/Test2.java",
    "content": "package test;\n\nimport cn.ponfee.commons.base.tuple.Tuple;\nimport cn.ponfee.commons.base.tuple.Tuple1;\nimport cn.ponfee.commons.base.tuple.Tuple2;\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.collect.ValueSortedMap;\nimport cn.ponfee.commons.io.HumanReadables;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.math.Maths;\nimport cn.ponfee.commons.math.Numbers;\nimport cn.ponfee.commons.model.Page;\nimport cn.ponfee.commons.reflect.BeanCopiers;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.date.Dates;\nimport cn.ponfee.commons.util.Snowflake;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport com.google.common.base.Stopwatch;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.junit.Assert;\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport org.springframework.objenesis.ObjenesisHelper;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.concurrent.ForkJoinPool;\nimport java.util.concurrent.ThreadLocalRandom;\n\n/**\n *\n * @author Ponfee\n */\npublic class Test2 {\n\n    @Test\n    public void test0() {\n        Map<Object, Object> map = new HashMap<>();\n        map.put(1, null);\n        map.put(\"a\", \"\");\n        System.out.println(Jsons.toJson(map));\n        System.out.println(Jsons.ALL.string(map));\n    }\n\n    @Test\n    public void test1() {\n        Map<String, Integer> map = new HashMap<>();\n        for (int i = 0; i < 10; i++) {\n            map.put(RandomStringUtils.randomAlphabetic(1), ThreadLocalRandom.current().nextInt(100));\n        }\n        map.put(\"b\", 8);\n        System.out.println(map);\n        TreeMap<String, Integer> tree = ValueSortedMap.nullsFirst(map, Comparator.comparing(v->v));\n        System.out.println(tree);\n\n        TreeMap<String, Integer> tree2 = new TreeMap<>(Comparator.comparing(k -> map.get(k)));\n        tree2.putAll(map);\n        System.out.println(tree2);\n\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    @Test\n    public void test2() {\n        Page<Map<String, Object>> source = new Page<>();\n        Page<Map> target = new Page<>();\n        BeanCopiers.copy(source, target);\n        System.out.println(source.copy());\n    }\n\n    @Test\n    public void test3() {\n        ForkJoinPool.commonPool().shutdownNow();\n        byte b = 127;\n        System.out.println(Integer.toBinaryString( b                ));\n        System.out.println(Integer.toBinaryString((b & 0xFF)        ));\n        System.out.println(Integer.toBinaryString((b & 0xFF) | 0x100));\n        System.out.println(Integer.toBinaryString((b & 0xFF) + 0x100));\n    }\n\n    @Test\n    public void test4() {\n        System.out.println(Dates.format(Dates.ofUnixTimestamp(-2000000000)));\n    }\n\n    @Test @Ignore\n    public void test5() throws IOException {\n        Files.delete(Paths.get(\"D:\\\\test\\\\framework\"));\n    }\n\n    @Test\n    public void test6() throws IOException {\n        System.out.println(ObjenesisHelper.newInstance(HashMap.class));\n\n        System.out.println(Double.isFinite(Math.PI));\n        System.out.println(Double.isInfinite(Double.POSITIVE_INFINITY));\n        System.out.println(Double.isNaN(0.0/0.0));\n        System.out.println(Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY);\n    }\n\n    @Test\n    public void test10() throws IOException {\n        Map<String, Object> map = new HashMap<>();\n        System.out.println(map.replace(\"xx\", 1));\n        System.out.println(map);\n        System.out.println(map.put(\"a\", 1));\n        System.out.println(map);\n        System.out.println(map.put(\"a\", 2));\n        System.out.println(map);\n    }\n\n    @Test @Ignore\n    public void test11() throws IOException {\n        File source = new File(\"D:\\\\test\\\\test1\\\\CentOS-6.6-x86_64-bin-DVD1.iso\");\n        File target = new File(\"D:\\\\test\\\\test1\\\\CentOS-6.6-x86_64-bin-DVD2.iso\");\n\n        Stopwatch watch = Stopwatch.createStarted();\n        Files.move(source.toPath(), target.toPath());\n        System.out.println(\"move cost: \" + watch.stop());\n\n        watch.reset().start();\n        source = target;\n        target = new File(\"D:\\\\test\\\\test1\\\\CentOS-6.6-x86_64-bin-DVD3.iso\");\n        boolean f = source.renameTo(target);\n        System.out.println(f);\n        System.out.println(\"move cost: \" + watch.stop());\n    }\n\n    @Test\n    public void test13() throws IOException {\n        System.out.println(HumanReadables.BINARY.parse(\"-1,023.56 GiB\"));\n        System.out.println(HumanReadables.BINARY.human(-1099039181373L).length());\n        long size = 1, max = 0;\n        for (int i = 0; i < 100; i++) {\n            String s = HumanReadables.BINARY.human(size);\n            System.out.println(s);\n            max = Math.max(max, s.length());\n            size *= 7;\n            //Assert.assertEquals(s, HumanReadables.BINARY.human(size).replace(\"i\", \"\"));\n            //System.out.println(HumanReadables.BINARY.parse(s, false));\n        }\n        System.out.println(\"=======\"+max);\n    }\n\n    @Test\n    public void test14() throws IOException {\n        System.out.println(HumanReadables.BINARY.human(1047552));\n        System.out.println(HumanReadables.BINARY.human(123456789123456L));\n        System.out.println(HumanReadables.BINARY.human(999_949_999_999_999_999L));\n        System.out.println(HumanReadables.BINARY.human(-12345678));\n        System.out.println(HumanReadables.BINARY.human(0));\n        System.out.println(HumanReadables.BINARY.human(-0));\n        System.out.println(HumanReadables.BINARY.human(5));\n        System.out.println(HumanReadables.BINARY.human(-5));\n        System.out.println(HumanReadables.BINARY.human(98745612));\n\n        System.out.println(\"\\n===============================\");\n        System.out.println(HumanReadables.BINARY.parse(\"1,023KB\"));\n        System.out.println(HumanReadables.BINARY.parse(\"1047552\"));\n        System.out.println(HumanReadables.BINARY.parse(\"112.28TB\"));\n        System.out.println(HumanReadables.BINARY.parse(\"888.13PB\"));\n        System.out.println(HumanReadables.BINARY.parse(\"-11.77MB\"));\n        System.out.println(HumanReadables.BINARY.parse(\"0B\"));\n        System.out.println(HumanReadables.BINARY.parse(\"5B\"));\n        System.out.println(HumanReadables.BINARY.parse(\"-5B\"));\n        System.out.println(HumanReadables.BINARY.parse(\"94.17MB\"));\n        System.out.println(HumanReadables.BINARY.parse(\"-1KB\"));\n        System.out.println(HumanReadables.BINARY.parse(\"0B\"));\n        System.out.println(HumanReadables.BINARY.parse(\"-0B\"));\n        System.out.println(HumanReadables.BINARY.parse(\"123B\"));\n        System.out.println(HumanReadables.BINARY.parse(\"-123B\"));\n        System.out.println(HumanReadables.BINARY.parse(\"6MB\"));\n    }\n\n    @Test\n    public void test15() throws IOException {\n        System.out.println(HumanReadables.BINARY.parse(\"888.13   PiB\"));\n        System.out.println(HumanReadables.BINARY.parse(\"888.13PiB\", true));\n\n        System.out.println(HumanReadables.BINARY.parse(\"888.13PB\"));\n        //System.out.println(HumanReadables.BINARY.parse(\"888.13PB\", true));\n    }\n\n    @Test\n    public void test16() throws IOException {\n        System.out.println(HumanReadables.BINARY.parse(\"888.13PiB\", true));\n        //System.out.println(HumanReadables.BINARY.parse(\"888.13PB\", true));\n    }\n\n    @Test\n    public void test18() throws IOException {\n        System.out.println(Long.MAX_VALUE);\n        System.out.println(HumanReadables.SI.human(Long.MAX_VALUE));\n        System.out.println(HumanReadables.BINARY.human(Long.MAX_VALUE));\n\n        System.out.println(HumanReadables.SI.parse(\"9.223372036854776 EB\", true));\n        System.out.println(HumanReadables.BINARY.parse(\"8EiB\", true));\n    }\n\n    @Test\n    public void test19() throws IOException {\n        System.out.println(HumanReadables.SI.human(Long.MIN_VALUE));\n        System.out.println(HumanReadables.BINARY.human(Long.MIN_VALUE));\n\n        System.out.println(HumanReadables.SI.parse(\"-9.22EB\", true));\n        System.out.println(HumanReadables.BINARY.parse(\"-8EiB\", true));\n        System.out.println(Long.MIN_VALUE);\n    }\n\n    @Test\n    public void test20() throws IOException {\n        System.out.println(HumanReadables.SI.parse(\"888.13P\", false));\n        System.out.println(HumanReadables.SI.parse(\"888.13P\", true));\n\n        //System.out.println(HumanReadables.BINARY.parse(\"888.13Pi\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"888.13Pi\", true));\n\n        System.out.println(HumanReadables.BINARY.parse(\"888.13P\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"888.13P\", true));\n    }\n\n    @Test\n    public void test21() throws IOException {\n        System.out.println(HumanReadables.SI.parse(\"888.13\", false));\n        System.out.println(HumanReadables.SI.parse(\"888.13\", true));\n\n        System.out.println(HumanReadables.SI.parse(\"888.13B\", false));\n        System.out.println(HumanReadables.SI.parse(\"888.13B\", true));\n\n        System.out.println(HumanReadables.SI.parse(\"888.13PB\", false));\n        System.out.println(HumanReadables.SI.parse(\"888.13PB\", true));\n\n        //System.out.println(HumanReadables.SI.parse(\"888.13PiB\", false));\n        //System.out.println(HumanReadables.SI.parse(\"888.13PiB\", true));\n\n        System.out.println(HumanReadables.SI.parse(\"888.13P\", false));\n        System.out.println(HumanReadables.SI.parse(\"888.13P\", true));\n    }\n\n    @Test\n    public void test22() throws IOException {\n        System.out.println(HumanReadables.BINARY.parse(\"888.13\", false));\n        System.out.println(HumanReadables.BINARY.parse(\"888.13\", true));\n\n        System.out.println(HumanReadables.BINARY.parse(\"888.13B\", false));\n        System.out.println(HumanReadables.BINARY.parse(\"888.13B\", true));\n\n        System.out.println(HumanReadables.BINARY.parse(\"888.13PB\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"888.13PB\", true));\n\n        System.out.println(HumanReadables.BINARY.parse(\"888.13PiB\", false));\n        System.out.println(HumanReadables.BINARY.parse(\"888.13PiB\", true));\n\n        System.out.println(HumanReadables.BINARY.parse(\"888.13P\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"888.13P\", true));\n    }\n\n    @Test\n    public void test23() throws IOException {\n        //System.out.println(HumanReadables.BINARY.parse(\"888.13AB\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"888.13BA\", true));\n\n        //System.out.println(HumanReadables.SI.parse(\"888.13AB\", false));\n        //System.out.println(HumanReadables.SI.parse(\"888.13BA\", true));\n\n    }\n\n    @Test\n    public void test24() throws IOException {\n        System.out.println(FileUtils.byteCountToDisplaySize(1047552));\n        System.out.println(FileUtils.byteCountToDisplaySize(123456789123456L));\n        System.out.println(FileUtils.byteCountToDisplaySize(999_949_999_999_999_999L));\n        System.out.println(FileUtils.byteCountToDisplaySize(-12345678));\n        System.out.println(FileUtils.byteCountToDisplaySize(0));\n        System.out.println(FileUtils.byteCountToDisplaySize(-0));\n        System.out.println(FileUtils.byteCountToDisplaySize(5));\n        System.out.println(FileUtils.byteCountToDisplaySize(-5));\n        System.out.println(FileUtils.byteCountToDisplaySize(98745612));\n    }\n\n    @Test\n    public void test25() throws IOException {\n        System.out.println(HumanReadables.BINARY.human(1099382778757L));\n        System.out.println(HumanReadables.BINARY.parse(\"-1,023.88GiB \", false));\n        System.out.println(HumanReadables.BINARY.parse(\"-1,023.88   GiB  \", false));\n        System.out.println(HumanReadables.BINARY.parse(\"-1,023.88    B\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"-1,023.88  Gi B\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"-1,023.88   G iB\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"-B\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\".B\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"TB\", false));\n        //System.out.println(HumanReadables.BINARY.parse(\"xTB\", false));\n    }\n\n    @Test\n    public void test26() throws IOException {\n        System.out.println(Bytes.toBinary(Bytes.toBytes(new Snowflake(10).nextId())));\n\n        String format = \"yyyy-MM-dd HH:mm:ss.SSS\";\n        System.out.println(Long.toBinaryString(Long.MAX_VALUE));\n        System.out.println(Long.toBinaryString(System.currentTimeMillis()));\n        System.out.println(Bytes.toBinary(Bytes.toBytes(Long.MAX_VALUE)));\n        System.out.println(Dates.format(new Date(0B0000000000000000000000011111111111111111111111111111111111111111L), format));\n        System.out.println(Dates.format(new Date(0B0000000000000000000011111111111111111111111111111111111111111111L), format));\n        System.out.println(Long.toBinaryString(Long.MAX_VALUE).length());\n\n        System.out.println(\"====\\n\");\n        System.out.println(\"0  -> \" + Maths.bitsMask(0) + \" -> \" + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(0))));\n        System.out.println(\"1  -> \" + Maths.bitsMask(1) + \" -> \" + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(1))));\n        System.out.println(\"2  -> \" + Maths.bitsMask(2) + \" -> \" + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(2))));\n        System.out.println(\"10 -> \" + Maths.bitsMask(10) + \" -> \" + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(10))));\n        System.out.println(\"20 -> \" + Maths.bitsMask(20) + \" -> \" + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(20))));\n        System.out.println(\"63 -> \" + Maths.bitsMask(63) + \" -> \" + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(63))));\n        System.out.println(\"64 -> \" + Maths.bitsMask(64) + \" -> \" + Bytes.toBinary(Bytes.toBytes(Maths.bitsMask(64))));\n        System.out.println(\"====\\n\");\n\n        System.out.println(Dates.format(new Date(17592186044415L), format));\n\n        int bits = 41;\n        System.out.println((1L << bits) - 1);\n        Assert.assertEquals((1L << bits) - 1, -1L ^ (-1L << bits));\n        Assert.assertEquals((1L << bits) - 1, Long.MAX_VALUE >>> (63 - bits));\n\n        System.out.println(Long.toBinaryString((1L << bits) - 1));\n        System.out.println(Long.toBinaryString(-1L ^ (-1L << bits)));\n        System.out.println(Long.toBinaryString(Long.MAX_VALUE >>> (63 - 41)));\n    }\n\n    @Test\n    public void test27() throws IOException {\n        System.out.println(10);\n        System.out.println(0B010);\n        System.out.println(0010);\n        System.out.println(010);\n        System.out.println(0X010);\n        System.out.println(0E10);\n    }\n\n    @Test\n    public void test28() throws IOException {\n        try {\n            Long.parseLong(\"321.0\");\n        } catch (NumberFormatException e) {\n            e.printStackTrace();\n        }\n    }\n\n    @Test\n    public void test29() throws IOException {\n        System.out.println(\"321.0\".indexOf('.'));\n        System.out.println(Numbers.toInt(\"321.0\"));\n    }\n\n    @Test()\n    public void test30() {\n        String str = null;\n        Long num = null;\n        System.out.println(str == null ? num : (Long) Long.parseLong(str));\n        System.out.println(str == null ? null : Long.parseLong(str));\n        NullPointerException ex = null;\n        try {\n            System.out.println(str == null ? num : Long.parseLong(str));\n        } catch (NullPointerException e) {\n            ex = e;\n        }\n        Assert.assertNotNull(ex);\n    }\n\n    @Test\n    public void tes31() {\n        System.out.println(Character.toUpperCase('y'));\n        System.out.println(Character.toUpperCase('Y'));\n        System.out.println(Arrays.toString(Numbers.slice(0, 2)));\n        System.out.println(Arrays.toString(Numbers.slice(2, 3)));\n        System.out.println(Arrays.toString(Numbers.slice(5, 100)));\n        System.out.println(Arrays.toString(Numbers.slice(3, 1)));\n        System.out.println(Arrays.toString(Numbers.slice(9, 3)));\n        System.out.println(Arrays.toString(Numbers.slice(10, 3)));\n        System.out.println(Arrays.toString(Numbers.slice(11, 3)));\n        System.out.println(Arrays.toString(Numbers.slice(12, 3)));\n\n        System.out.println(\"-----\\n\\n\");\n\n        System.out.println(Numbers.partition( 0, 2));\n        System.out.println(Numbers.partition( 2, 3));\n        System.out.println(Numbers.partition( 3, 1));\n        System.out.println(Numbers.partition( 9, 3));\n        System.out.println(Numbers.partition(10, 3));\n        System.out.println(Numbers.partition(11, 3));\n        System.out.println(Numbers.partition(12, 3));\n    }\n\n    @Test\n    public void tes32() {\n        System.out.println(String.format(\"Holder(%s)\", \"null\"));\n        List<Integer> x = Arrays.asList(1, 2, 3);\n        List<Integer> y = Arrays.asList(4, 5, 6);\n        System.out.println(Collects.cartesian(x, y, (a, b) -> a * b));\n        System.out.println(SecureRandoms.random(512));\n        System.out.println(Bytes.toBigInteger(SecureRandoms.nextBytes(10)));\n    }\n\n    @Test\n    public void tes33() {\n        List<Tuple1<Object>> list = new ArrayList<>();\n        /*list.add(Tuple1.of(new Object()));*/\n        list.add(Tuple1.of(SecureRandoms.nextInt(10)));\n        list.add(Tuple1.of(SecureRandoms.nextInt(10)));\n        list.add(Tuple1.of(SecureRandoms.nextInt(10)));\n        list.add(Tuple1.of(SecureRandoms.nextInt(10)));\n        list.add(Tuple1.of(SecureRandoms.nextInt(10)));\n        System.out.println(list);\n        list.sort(Comparator.naturalOrder());\n        System.out.println(list);\n        Assert.assertTrue(Object.class.isInstance(new Object()));\n        Assert.assertTrue(Object.class.isInstance(new Object[0]));\n        Assert.assertTrue(Object.class.isInstance(1));\n        Assert.assertTrue(Integer.class.isInstance(1));\n        Assert.assertFalse(Integer.class.isInstance(1L));\n        Assert.assertTrue(Number.class.isInstance(1L));\n        Assert.assertTrue(Number.class.isInstance(1));\n\n        List<Tuple> list1 = new ArrayList<>();\n        list1.add(Tuple1.of(new Object()));\n        list1.add(Tuple1.of(1));\n        list1.add(Tuple1.of(\"z\"));\n        list1.add(Tuple2.of(\"x\", 4));\n        list1.add(Tuple1.of(null));\n        list1.add(Tuple1.of(new RuntimeException()));\n        list1.add(null);\n        list1.add(Tuple1.of(\"b\"));\n        list1.add(Tuple1.of(null));\n        list1.add(Tuple1.of(2));\n        list1.add(Tuple1.of(null));\n        System.out.println(list1);\n        list1.sort(Comparator.nullsLast(Comparator.naturalOrder()));\n        System.out.println(list1);\n\n\n        System.out.println();\n        List<Tuple> list2 = new ArrayList<>();\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of(\"a\"));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of('x'));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of(null));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of(\"x\"));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of('z'));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of('c'));\n        list2.add(Tuple1.of(new Object()));\n        list2.add(Tuple1.of(new Object()));\n        list2.add(Tuple1.of('a'));\n        list2.add(Tuple1.of(new Object()));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of(\"y\"));\n        list2.add(Tuple1.of(null));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(100)));\n        list2.add(Tuple1.of('a'));\n        list2.add(Tuple1.of(SecureRandoms.nextInt(10)));\n        System.out.println(\"un-sorted: \"+list2);\n        list2.sort(Comparator.nullsLast(Comparator.naturalOrder()));\n        System.out.println(\"do-sorted: \"+list2);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/Test3.java",
    "content": "package test;\n\nimport cn.ponfee.commons.collect.FilterableIterator;\nimport cn.ponfee.commons.exception.UnimplementedException;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.reflect.Fields;\nimport org.junit.Assert;\nimport org.openjdk.jol.info.ClassLayout;\n\npublic class Test3 {\n\n    public static abstract class Animal implements java.io.Serializable {\n        private static final long serialVersionUID = 3890678647435825868L;\n        private String name;\n        private int age;\n\n        public String getName() {\n            return name;\n        }\n\n        public void setName(String name) {\n            this.name = name;\n        }\n\n        public int getAge() {\n            return age;\n        }\n\n        public void setAge(int age) {\n            this.age = age;\n        }\n    }\n\n    public static class Dog extends Animal {\n        private static final long serialVersionUID = -2829034009272727923L;\n        private String ext1;\n\n        public String getExt1() {\n            return ext1;\n        }\n\n        public void setExt1(String ext1) {\n            this.ext1 = ext1;\n        }\n\n    }\n\n    public static interface A {\n        static A fromByteArray(byte[] arg) {\n            throw new UnimplementedException();\n        }\n    }\n\n    public static class B implements A {\n        public B() {\n            System.out.println(\"Create B.\");\n        }\n\n        static B fromByteArray(byte[] arg) {\n            return new B();\n        }\n    }\n\n    public static class C implements A {\n        public C() {\n            System.out.println(\"Create C.\");\n        }\n\n        static C fromByteArray(byte[] arg) {\n            return new C();\n        }\n    }\n\n    public static void main(String[] args) throws Exception {\n        Assert.assertTrue(null == null);\n        System.out.println(Double.doubleToLongBits(0.1D));\n        System.out.println(Double.doubleToLongBits(0.0D));\n\n        System.out.println();\n        for (Integer s : FilterableIterator.of(e -> e != null && e % 2 == 1, null, 3, 4, 5)) {\n            System.out.println(\"|\" + s + \"|\");\n        }\n        System.out.println();\n\n        System.out.println(0 >> 1);\n        System.out.println(1 >> 1);\n        System.out.println(4 >> 1);\n        System.out.println(System.getProperty(\"user.home\"));\n        B.fromByteArray(new byte[]{});\n        Class<? extends A> clazz1 = B.class;\n        clazz1.getDeclaredMethod(\"fromByteArray\", byte[].class).invoke(null, new byte[]{});\n\n        Dog dog = new Dog();\n        dog.setName(\"xxx\");\n        dog.setAge(10);\n        dog.setExt1(\"extxxx\");\n        System.out.println(Jsons.toJson(dog));\n\n        Fields.put(dog, \"ext1\", \"extyyy\");\n        System.out.println(Jsons.toJson(dog));\n        Jsons.toJson(dog);\n\n        System.out.println(\"---------------------------------------------\");\n        System.out.println(ClassLayout.parseClass(B.class).toPrintable());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/TestBean.java",
    "content": "package test;\n\nimport cn.ponfee.commons.constrain.Constraint;\n\nimport java.io.Serializable;\n\npublic class TestBean implements Serializable {\n    static final int MAXIMUM_CAPACITY = 1 << 30;\n    private static final long serialVersionUID = 1716190333294826147L;\n    @Constraint(tense = Constraint.Tense.FUTURE)\n    private int i;\n    private Long l;\n    private String s;\n    private String str;\n\n    public TestBean() {}\n\n    public TestBean(int i, Long l, String s) {\n        super();\n        this.i = i;\n        this.l = l;\n        this.s = s;\n    }\n\n    public int getI() {\n        return i;\n    }\n\n    public void setI(int i) {\n        this.i = i;\n    }\n\n    public Long getL() {\n        return l;\n    }\n\n    public void setL(Long l) {\n        this.l = l;\n    }\n\n    public String getS() {\n        return s;\n    }\n\n    @Override\n    public String toString() {\n        return \"Bean [i=\" + i + \", l=\" + l + \", s=\" + s + \"]\";\n    }\n\n    public void setS(String s) {\n        this.s = s;\n    }\n\n    public String getStr() {\n        return str;\n    }\n\n    public void setStr(String str) {\n        this.str = str;\n    }\n    static final int tableSizeFor(int cap) {\n        int n = cap - 1;\n        n |= n >>> 1;\n        n |= n >>> 2;\n        n |= n >>> 4;\n        n |= n >>> 8;\n        n |= n >>> 16;\n        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;\n    }\n    \n    public static void main(String[] args) {\n        System.out.println(tableSizeFor(9));\n        System.out.println(MAXIMUM_CAPACITY);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/TestSynthetic.java",
    "content": "package test;\n\nimport java.lang.reflect.Method;\n\nimport junit.framework.TestCase;\n\n/**\n * \n * \n * @author Ponfee\n */\npublic class TestSynthetic extends TestCase {\n\n    public void testSynthetic() {\n        try {\n            new User().age = 1;\n            System.out.println(new User().age);\n            Method[] methods = User.class.getDeclaredMethods();\n            for (Method method : methods) {\n                System.out.println(method.toString() + \", \" + method.isSynthetic());\n            }\n        } catch (SecurityException e) {\n            e.printStackTrace();\n        }\n    }\n\n    class User {\n        private int age;\n        private String name;\n\n        private User() {}\n\n        private User(int age, String name) {\n            this.age = age;\n            this.name = name;\n        }\n\n        private int getAge() {\n            return age;\n        }\n\n        private void setAge(int age) {\n            this.age = age;\n        }\n\n        private String getName() {\n            return name;\n        }\n\n        private void setName(String name) {\n            this.name = name;\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/ThrowEggsTest.java",
    "content": "package test;\n\n// 动态规划，https://blog.csdn.net/qq_32782339/article/details/100836449\npublic class ThrowEggsTest {\n\n    public int countMinSetp(int egg, int num) {\n        if (egg < 1 || num < 1) return 0;\n        int[][] f = new int[egg + 1][num + 1];//代表egg个鸡蛋，从num楼层冷下来所需的最小的次数\n        for (int i = 1; i <= egg; i++) {\n            for (int j = 1; j <= num; j++)\n                f[i][j] = j;//初始化，最坏的步数\n        }\n\n        for (int n = 2; n <= egg; n++) {\n            for (int m = 1; m <= num; m++) {\n                for (int k = 1; k < m; k++) {\n                    //这里的DP的递推公式为f[n][m] = 1+max(f[n-1][k-1],f[n][m-k]) k属于[1,m-1]\n                    //从1-m层中随机抽出来一层k\n                    //如果第一个鸡蛋在k层碎了，那么我们将测试1~k-1层，就可以找出来，也即1+f[1][k-1]\n                    //如果第一个鸡蛋在k层没有碎，那么我们将测试k+1~m也即m-k层，\n                    //      这里也是重点！！！！ \n                    //      现在我们手里有2个鸡蛋，要测试m-k层，那么我想问，此时和你手里有2个鸡蛋要测试1~m-k层有什么区别？\n                    //      没有区别！是的在已知k层不碎的情况下，测试k+1~m层的方法和测试1~m-k没区别，所以可以写成 1+f[n][m-k] 其中1表示为 在k层的那一次测试\n                    f[n][m] = Math.min(f[n][m], 1 + Math.max(f[n - 1][k - 1], f[n][m - k]));\n                }\n            }\n        }\n        return f[egg][num];\n    }\n\n    public static void main(String[] args) {\n        ThrowEggsTest e = new ThrowEggsTest();\n        System.out.println(e.countMinSetp(2, 100));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/concurrent/AsnycBatchProcessorTest.java",
    "content": "package test.concurrent;\n\nimport cn.ponfee.commons.concurrent.AsyncBatchProcessor;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class AsnycBatchProcessorTest {\n\n    @Test\n    public void test1() throws InterruptedException {\n        System.out.println(20 + (20 >>> 1));\n\n        AtomicInteger summer = new AtomicInteger();\n        AtomicBoolean end = new AtomicBoolean(false);\n\n        final AsyncBatchProcessor<Integer> processor = new AsyncBatchProcessor<>((list, isEnd) -> {\n            summer.addAndGet(list.size());\n            System.out.println(list.size() + \"==\" + Thread.currentThread().getId() + \"-\" + Thread.currentThread().getName() + \", \" + isEnd);\n            if (isEnd) {\n                end.set(true);\n            }\n            //System.out.println(1 / 0); // submit方式不会打印异常\n        }, 100, 200, 10);\n\n        AtomicBoolean flag = new AtomicBoolean(true);\n        AtomicInteger increment = new AtomicInteger(0);\n        int n = 13;\n        Thread[] threads = new Thread[n];\n        for (int i = 0; i < n; i++) {\n            Thread thread = new Thread(() -> {\n                while (flag.get()) {\n                    try {\n                        processor.put(increment.incrementAndGet());\n                        Thread.sleep(3 + ThreadLocalRandom.current().nextInt(7));\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n            });\n            thread.setDaemon(true);\n            thread.start();\n            threads[i] = thread;\n        }\n        Thread.sleep(10000);\n        flag.set(false);\n        processor.stopAndAwait();\n        for (Thread thread : threads) {\n            thread.join();\n        }\n        while (!end.get()) {\n            // noop-loop\n        }\n        System.out.println(increment.get());\n        System.out.println(summer.get());\n        Assert.assertEquals(increment.get(), summer.get());\n        System.out.println(\"end\");\n    }\n\n    @Test\n    public void test2() throws InterruptedException {\n        AtomicInteger summer = new AtomicInteger();\n        AtomicBoolean end = new AtomicBoolean(false);\n\n        final AsyncBatchProcessor<Integer> processor = new AsyncBatchProcessor<>((list, isEnd) -> {\n            summer.addAndGet(list.size());\n            System.out.println(list.size() + \"==\" + Thread.currentThread().getId() + \"-\" + Thread.currentThread().getName() + \", \" + isEnd);\n            if (isEnd) {\n                end.set(true);\n            }\n            //System.out.println(1 / 0); // submit方式不会打印异常\n        }, 200, 50, 10);\n\n        AtomicBoolean flag = new AtomicBoolean(true);\n        AtomicInteger increment = new AtomicInteger(0);\n        int n = 20;\n        Thread[] threads = new Thread[n];\n        for (int i = 0; i < n; i++) {\n            Thread thread = new Thread(() -> {\n                while (flag.get()) {\n                    try {\n                        processor.put(increment.incrementAndGet());\n                        Thread.sleep(3 + ThreadLocalRandom.current().nextInt(7));\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n            });\n            thread.setDaemon(true);\n            thread.start();\n            threads[i] = thread;\n        }\n        Thread.sleep(10000);\n        flag.set(false);\n        processor.stopAndAwait();\n        for (Thread thread : threads) {\n            thread.join();\n        }\n        while (!end.get()) {\n            // noop-loop\n        }\n        System.out.println(increment.get());\n        System.out.println(summer.get());\n        Assert.assertEquals(increment.get(), summer.get());\n        System.out.println(\"end\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/concurrent/ForkJoinPoolTest1.java",
    "content": "package test.concurrent;\n\nimport java.util.concurrent.ForkJoinPool;\nimport java.util.concurrent.ForkJoinTask;\nimport java.util.concurrent.RecursiveAction;\n\n//RecursiveAction为ForkJoinTask的抽象子类，没有返回值的任务  \nclass PrintTask extends RecursiveAction {\n    private static final long serialVersionUID = 3840971532382813847L;\n\n    // 每个\"小任务\"最多只打印50个数  \n    private static final int MAX = 50;\n\n    private int start;\n    private int end;\n\n    PrintTask(int start, int end) {\n        this.start = start;\n        this.end = end;\n    }\n\n    @Override\n    protected void compute() {\n        // 当end-start的值小于MAX时候，开始打印  \n        if ((end - start) < MAX) {\n            for (int i = start; i < end; i++) {\n                System.out.println(Thread.currentThread().getName() + \"的i值:\" + i);\n            }\n        } else {\n            // 将大任务分解成两个小任务  \n            int middle = (start + end) / 2;\n            PrintTask left = new PrintTask(start, middle);\n            PrintTask right = new PrintTask(middle, end);\n            // 并行执行两个小任务  \n            left.fork();\n            right.fork();\n            left.join();\n            right.join();\n        }\n    }\n}\n\npublic class ForkJoinPoolTest1 {\n    /** \n     * @param args \n     * @throws Exception \n     */\n    public static void main(String[] args) throws Exception {\n        // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool\n        ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();\n\n        ForkJoinTask<Void> future = forkJoinPool.submit(new PrintTask(0, 200));\n        future.get();\n\n        /*forkJoinPool.execute(new PrintTask(0, 200));\n        forkJoinPool.awaitTermination(20, TimeUnit.SECONDS);*/\n\n        forkJoinPool.shutdown(); // 关闭线程池  \n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/concurrent/ForkJoinPoolTest2.java",
    "content": "package test.concurrent;\n\nimport java.util.Random;\nimport java.util.concurrent.ForkJoinPool;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.RecursiveTask;\n\nclass CalTask extends RecursiveTask<Integer> {\n    private static final long serialVersionUID = 4854215345100228419L;\n\n    // 每个“小任务”只最多只累加20个数\n    private static final int THRESHOLD = 20;\n    private int arr[];\n    private int start;\n    private int end;\n\n    // 累加从start到end的数组元素\n    public CalTask(int[] arr, int start, int end) {\n        this.arr = arr;\n        this.start = start;\n        this.end = end;\n    }\n\n    @Override\n    protected Integer compute() {\n        int sum = 0;\n        // 当end与start之间的差小于THRESHOLD时，开始进行实际累加\n        if (end - start < THRESHOLD) {\n            for (int i = start; i < end; i++) {\n                sum += arr[i];\n            }\n            return sum;\n        } else {\n            // 如果当end与start之间的差大于THRESHOLD时，即要打印的数超过20个\n            // 将大任务分解成两个小任务。\n            int middle = (start + end) / 2;\n            CalTask left = new CalTask(arr, start, middle);\n            CalTask right = new CalTask(arr, middle, end);\n            // 并行执行两个“小任务”\n            left.fork();\n            right.fork();\n            // 把两个“小任务”累加的结果合并起来\n            return left.join() + right.join();\n        }\n    }\n}\n\npublic class ForkJoinPoolTest2 {\n    public static void main(String[] args) throws Exception {\n        int[] arr = new int[1000000];\n        Random rand = new Random();\n        int total = 0;\n        // 初始化100个数字元素\n        for (int i = 0, len = arr.length; i < len; i++) {\n            int tmp = rand.nextInt(20);\n            // 对数组元素赋值，并将数组元素的值添加到total总和中。\n            total += (arr[i] = tmp);\n        }\n        System.out.println(total);\n\n        ForkJoinPool pool = new ForkJoinPool();\n        // 提交可分解的CalTask任务\n        Future<Integer> future = pool.submit(new CalTask(arr, 0, arr.length));\n        System.out.println(future.get());\n        // 关闭线程池\n        pool.shutdown();\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/concurrent/InheritableThreadLocalTest.java",
    "content": "package test.concurrent;\n\npublic class InheritableThreadLocalTest {\n\n    private static InheritableThreadLocal<StringBuffer> ITL = new InheritableThreadLocal<StringBuffer>() {\n        protected StringBuffer initialValue() {\n            return new StringBuffer(\"hello\");\n        }\n    };\n\n    public static void main(String[] args) {\n        System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n        new Thread(new Runnable() {\n            public void run() {\n                System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n                new Thread(new Runnable() {\n                    public void run() {\n                        System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n                        ITL.get().append(\", wqf\");\n                        System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n                    }\n                }).start();\n                try {\n                    Thread.sleep(1000);\n                } catch (InterruptedException ex) {\n                    ex.printStackTrace();\n                }\n                System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n            }\n        }).start();\n        try {\n            Thread.sleep(1000);\n        } catch (InterruptedException ex) {\n            ex.printStackTrace();\n        }\n        System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/concurrent/InheritableThreadLocalTest2.java",
    "content": "package test.concurrent;\n\nimport com.alibaba.ttl.TransmittableThreadLocal;\n\npublic class InheritableThreadLocalTest2 {\n\n    //private static InheritableThreadLocal<String> ITL = new InheritableThreadLocal<String>() {\n    private static TransmittableThreadLocal<String> ITL = new TransmittableThreadLocal<String>() {\n        protected String initialValue() {\n            return \"hello\";\n        }\n    };\n\n    public static void main(String[] args) {\n        System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n        new Thread(new Runnable() {\n            public void run() {\n                System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n                new Thread(new Runnable() {\n                    public void run() {\n                        System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n                        try {\n                            Thread.sleep(3000);\n                        } catch (InterruptedException e) {\n                            e.printStackTrace();\n                        }\n                        System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n                    }\n                }).start();\n                try {\n                    Thread.sleep(1000);\n                } catch (InterruptedException ex) {\n                    ex.printStackTrace();\n                }\n                System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n            }\n        }).start();\n        try {\n            Thread.sleep(1000);\n        } catch (InterruptedException ex) {\n            ex.printStackTrace();\n        }\n\n        ITL.set(\"word\"); // 创建子线程的时候拷贝父线程的threadLocalMap数据到子线程，之后与父线程没有关系了\n        System.out.println(Thread.currentThread().getName() + \" : \" + ITL.get());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/concurrent/ReadWriteLock.java",
    "content": "package test.concurrent;\n\npublic class ReadWriteLock {\n\n    private int readThreadCounter = 0; // 正在读取的线程数（0个或多个）\n    private int waitingWriteCounter = 0; // 等待写入的线程数（0个或多个）\n    private int writeThreadCounter = 0; // 正在写入的线程数（0个或1个）\n    private boolean writable = true; // 是否对写入优先（默认为是）\n\n    private ReadWriteLock() {}\n\n    public static ReadWriteLock create() {\n        return new ReadWriteLock();\n    }\n\n    // 读取加锁\n    public synchronized void readLock() throws InterruptedException {\n        // 若存在正在写入的线程，或当写入优先时存在等待写入的线程，则将当前线程设置为等待状态\n        while (writeThreadCounter > 0 || (writable && waitingWriteCounter > 0)) {\n            wait();\n        }\n        // 使正在读取的线程数加一\n        readThreadCounter++;\n    }\n\n    // 读取解锁\n    public synchronized void readUnlock() {\n        // 使正在读取的线程数减一\n        readThreadCounter--;\n        // 读取结束，对写入优先\n        writable = true;\n        // 通知所有处于 wait 状态的线程\n        notifyAll();\n    }\n\n    // 写入加锁\n    public synchronized void writeLock() throws InterruptedException {\n        // 使等待写入的线程数加一\n        waitingWriteCounter++;\n        try {\n            // 若存在正在读取的线程，或存在正在写入的线程，则将当前线程设置为等待状态\n            while (readThreadCounter > 0 || writeThreadCounter > 0) {\n                wait();\n            }\n        } finally {\n            // 使等待写入的线程数减一\n            waitingWriteCounter--;\n        }\n        // 使正在写入的线程数加一\n        writeThreadCounter++;\n    }\n\n    // 写入解锁\n    public synchronized void writeUnlock() {\n        // 使正在写入的线程数减一\n        writeThreadCounter--;\n        // 写入结束，对读取优先\n        writable = false;\n        // 通知所有处于等待状态的线程\n        notifyAll();\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/concurrent/TestThread.java",
    "content": "package test.concurrent;\n\nimport java.util.Date;\nimport java.util.Map;\nimport java.util.TreeSet;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReadWriteLock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\n\nimport org.joda.time.DateTime;\nimport org.joda.time.format.DateTimeFormat;\n\npublic class TestThread {\n\n    private static final Date ORIGIN_DATE = toDate(\"1950-01-01 00:00:00\", \"yyyy-MM-dd HH:mm:ss\");\n    private static final long ORIGIN_DATE_TIME = ORIGIN_DATE.getTime();\n\n    private static final Lock LOCK = new ReentrantLock();\n    private static volatile ReadWriteLock readWriteLock = new ReentrantReadWriteLock();\n    private static volatile Map<String, Boolean> lastTwoBatch = new ConcurrentHashMap<>();\n\n    public static void loop(int num) {\n        Lock readLock = readWriteLock.readLock();\n        for (int i = 0; i < num; i++) {\n            readLock.lock();\n            lastTwoBatch.put(randomBirthday(), true);\n            readLock.unlock();\n        }\n        String lastButOne = null;\n        LOCK.lock();\n        try {\n            if (lastTwoBatch.size() > 2) {\n                Map<String, Boolean> temp = lastTwoBatch;\n                lastTwoBatch = new ConcurrentHashMap<>();\n                \n                Lock writeLock = readWriteLock.writeLock();\n                readWriteLock = new ReentrantReadWriteLock();\n                writeLock.lock();\n                TreeSet<String> set = new TreeSet<>(temp.keySet());\n                temp.clear();\n                writeLock.unlock();\n\n                String lastOne = set.pollLast();\n                lastButOne = set.pollLast();\n                lastTwoBatch.put(lastOne, true);\n                lastTwoBatch.put(lastButOne, true);\n                set.clear();\n            }\n        } finally {\n            LOCK.unlock();\n        }\n    }\n\n    public static Date toDate(String dateStr, String pattern) {\n        return DateTimeFormat.forPattern(pattern).parseDateTime(dateStr).toDate();\n    }\n\n    private static String randomBirthday() {\n        long diffSeconds = (new Date().getTime() - ORIGIN_DATE_TIME) / 1000;\n        long date = new DateTime(ORIGIN_DATE_TIME).plusSeconds((int) (Math.random() * diffSeconds)).getMillis();\n        return new DateTime(date).toString(\"yyyy-MM-dd HH:mm:ss\"); // 2017-04-26 16:59:29\n    }\n\n    public static void main(String[] args) throws InterruptedException {\n        long start = System.currentTimeMillis();\n        int num = 5000;\n        CountDownLatch latch = new CountDownLatch(num);\n        for (int i = 0; i < num; i++) {\n            new Thread(() -> {\n                loop(10);\n                latch.countDown();\n            }).start();\n        }\n        latch.await();\n        System.out.println(lastTwoBatch);\n        System.out.println(System.currentTimeMillis() - start);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/concurrent/TheadPoolExecTester.java",
    "content": "package test.concurrent;\n\nimport java.util.Random;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\npublic class TheadPoolExecTester {\n\n    /*private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(2, 10, 300, TimeUnit.SECONDS, \n                                                                           new SynchronousQueue<Runnable>(), \n                                                                           new ThreadPoolExecutor.CallerRunsPolicy());*/\n    private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(2, 10, 300, TimeUnit.SECONDS, \n                                                                           new LinkedBlockingQueue<>(300),\n                                                                           new ThreadPoolExecutor.CallerRunsPolicy());\n    \n    public static void main(String[] args) {\n        System.out.println(\"main thread \"+Thread.currentThread().getId());\n        for (int i = 0; i < 200; i++) {\n            EXECUTOR.submit(new Caller());\n        }\n        EXECUTOR.shutdown();\n    }\n    \n    private static final class Caller implements Callable<Boolean> {\n\n        @Override\n        public Boolean call() throws Exception {\n            System.out.println(Thread.currentThread().getId() + \"----start\");\n            Thread.sleep(1000+new Random().nextInt(10000));\n            System.out.println(Thread.currentThread().getId() + \"----end\");\n            return true;\n        }\n        \n    }\n}\n"
  },
  {
    "path": "src/test/java/test/concurrent/TtlTest.java",
    "content": "package test.concurrent;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport com.alibaba.ttl.TransmittableThreadLocal;\nimport com.alibaba.ttl.TtlRunnable;\n\n/**\n */\npublic class TtlTest {\n\n    static ExecutorService executorService = Executors.newFixedThreadPool(1);\n\n    public static void main(String[] args) {\n        //子线程每次new 所以会复制线程的InheritableThreadLocal,结果正确\n        //withoutThreadPool(10);\n\n        //因线程池复用线程,不会每次new 所以不会更新父线程InheritableThreadLocal 的值,导致结果错误\n        withThreadPool(10);\n    }\n\n    public static void withoutThreadPool(int c) {\n        for (int i = 0; i < c; i++) {\n            Integer var1 = (int) (Math.random() * 100);\n            Integer var2 = (int) (Math.random() * 100);\n            MyContextHolder.set(var1);\n            threadRun(var1, var2);\n        }\n    }\n\n    public static void withThreadPool(int c) {\n        for (int i = 0; i < c; i++) {\n            Integer var1 = (int) (Math.random() * 100);\n            Integer var2 = (int) (Math.random() * 100);\n            MyContextHolder.set(var1);\n            threadPoolExecute(var1, var2);\n        }\n    }\n\n    public static void threadRun(Integer var1, Integer var2) {\n        new Thread(() -> assert1(var1, var2)).start();\n    }\n\n    public static void threadPoolExecute(Integer var1, Integer var2) {\n        executorService.execute(TtlRunnable.get(() -> assert1(var1, var2)));  // XXX TtlRunnable\n    }\n\n    public static void assert1(Integer var1, Integer var2) {\n        System.out.println(MyContextHolder.get() * var2 == var1 * var2);\n    }\n\n    public static class MyContextHolder {\n\n        //private static ThreadLocal<Integer> stringThreadLocal = new InheritableThreadLocal<>();\n        private static ThreadLocal<Integer> stringThreadLocal = new TransmittableThreadLocal<>(); // XXX TransmittableThreadLocal\n\n        public static void set(Integer data) {\n            stringThreadLocal.set(data);\n        }\n\n        public static Integer get() {\n            return stringThreadLocal.get();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/constraint/TestConstraint.java",
    "content": "package test.constraint;\n\nimport java.util.Date;\n\nimport cn.ponfee.commons.constrain.Constraint;\nimport cn.ponfee.commons.constrain.Constraint.Tense;\nimport cn.ponfee.commons.constrain.FieldValidator;\n\npublic class TestConstraint {\n\n    @Constraint(maxLen=0)\n    private String s1;\n    @Constraint(minLen=1)\n    private String s2;\n    \n    @Constraint(tense=Tense.FUTURE)\n    private Date date = new Date();\n    \n    @Constraint(datePattern=\"yyyy-MM-dd\", tense=Tense.FUTURE)\n    private String d = \"2016-05-01\";\n    \n    public static void main(String[] args) {\n        TestConstraint t = new TestConstraint();\n        t.s1 = \"\";\n        t.s2 = \"1\";\n        t.date = new Date(System.currentTimeMillis()+5000000);\n\n        try {\n            FieldValidator.newInstance().constrain(t);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/disruptor/InParkingDataEvent.java",
    "content": "package test.disruptor;\n\npublic class InParkingDataEvent {\n    private String carLicense;\n\n    public void setCarLicense(String carLicense) {\n        this.carLicense = carLicense;\n    }\n\n    public String getCarLicense() {\n        return carLicense;\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/disruptor/Main.java",
    "content": "package test.disruptor;\n\nimport java.util.concurrent.CountDownLatch;\n\nimport org.apache.commons.lang3.RandomStringUtils;\n\nimport com.lmax.disruptor.YieldingWaitStrategy;\nimport com.lmax.disruptor.dsl.Disruptor;\nimport com.lmax.disruptor.dsl.EventHandlerGroup;\nimport com.lmax.disruptor.dsl.ProducerType;\n\nimport cn.ponfee.commons.concurrent.NamedThreadFactory;\n\n/**\n * 测试 P1生产消息，C1，C2消费消息，C1和C2会共享所有的event元素! C3依赖C1，C2处理结果\n */\n@SuppressWarnings({ \"unchecked\", \"deprecation\" })\npublic class Main {\n    private static final int COUNT = 50;\n    private static final int RING_BUFFER_SIZE = 1;\n\n    public static void main(String[] args) throws InterruptedException {\n        CountDownLatch latch = new CountDownLatch(COUNT);\n\n        //构造缓冲区与事件生成\n        Disruptor<InParkingDataEvent> disruptor = new Disruptor<>(() -> new InParkingDataEvent(), RING_BUFFER_SIZE,\n                                                                  NamedThreadFactory.builder().prefix(\"disruptor\").build(),\n                                                                  ProducerType.SINGLE, new YieldingWaitStrategy());\n\n        //使用disruptor创建消费者组C1,C2\n        EventHandlerGroup<InParkingDataEvent> handlerGroup = disruptor.handleEventsWith(new ParkingDataToKafkaHandler(),\n                                                                                        new ParkingDataInDbHandler());\n\n        //声明在C1,C2完事之后执行SMS消息发送操作 也就是流程走到C3\n        handlerGroup.then(new ParkingDataSmsHandler(latch));\n\n        //启动\n        disruptor.start();\n\n        long beginTime = System.currentTimeMillis();\n        //生产\n        for (int i = 0; i < COUNT; i++) {\n            /*RingBuffer<InParkingDataEvent> ringBuffer = disruptor.getRingBuffer();\n            long seq = ringBuffer.next();\n            try {\n                ringBuffer.get(seq).setCarLicense(\"[粤B·\" + RandomStringUtils.randomAlphanumeric(5).toUpperCase() + \"]\");\n            } finally {\n                ringBuffer.publish(seq);\n            }*/\n            disruptor.publishEvent((inParkingEvent, sequence) -> {\n                inParkingEvent.setCarLicense(\"[粤B·\" + RandomStringUtils.randomAlphanumeric(5).toUpperCase() + \"]\");\n                System.out.println(\"[\"+Thread.currentThread().getId() + \", \" +\n                                   Thread.currentThread().getName() + \"] in parking \" + inParkingEvent.getCarLicense());\n            });\n        }\n\n        latch.await(); // 等待全部处理结束\n        System.out.println(\"已通行\" + COUNT + \"车辆，总耗时:\" + (System.currentTimeMillis() - beginTime));\n        disruptor.shutdown();\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/disruptor/ParkingDataInDbHandler.java",
    "content": "package test.disruptor;\n\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport com.lmax.disruptor.EventHandler;\nimport com.lmax.disruptor.WorkHandler;\n\npublic class ParkingDataInDbHandler implements EventHandler<InParkingDataEvent>, WorkHandler<InParkingDataEvent> {\n\n    @Override\n    public void onEvent(InParkingDataEvent event) throws Exception {\n        long start = System.currentTimeMillis();\n        Thread.sleep(ThreadLocalRandom.current().nextInt(400));\n        long threadId = Thread.currentThread().getId();\n        String threadName = Thread.currentThread().getName();\n        String carLicense = event.getCarLicense();\n        System.out.println(String.format(\"[%s, %s] save %s into db %s\", threadId, threadName, carLicense, System.currentTimeMillis()-start));\n    }\n\n    @Override\n    public void onEvent(InParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception {\n        // TODO Auto-generated method stub\n        this.onEvent(event);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/disruptor/ParkingDataSmsHandler.java",
    "content": "package test.disruptor;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport com.lmax.disruptor.EventHandler;\n\npublic class ParkingDataSmsHandler implements EventHandler<InParkingDataEvent> {\n    private final CountDownLatch latch;\n    public ParkingDataSmsHandler(CountDownLatch latch) {\n        this.latch = latch;\n    }\n\n    @Override\n    public void onEvent(InParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception {\n        long start = System.currentTimeMillis();\n        Thread.sleep(ThreadLocalRandom.current().nextInt(400) + 100);\n        long threadId = Thread.currentThread().getId();\n        String threadName = Thread.currentThread().getName();\n        String carLicense = event.getCarLicense();\n        System.out.println(String.format(\"[%s, %s] send %s in plaza sms to user %s\", threadId, threadName, carLicense, System.currentTimeMillis()-start));\n        latch.countDown();\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/disruptor/ParkingDataToKafkaHandler.java",
    "content": "package test.disruptor;\n\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport com.lmax.disruptor.EventHandler;\n\npublic class ParkingDataToKafkaHandler implements EventHandler<InParkingDataEvent> {\n\n    @Override\n    public void onEvent(InParkingDataEvent event, long sequence,\n                        boolean endOfBatch) throws Exception {\n        long start = System.currentTimeMillis();\n        Thread.sleep(ThreadLocalRandom.current().nextInt(300));\n        long threadId = Thread.currentThread().getId();\n        String threadName = Thread.currentThread().getName();\n        String carLicense = event.getCarLicense();\n        System.out.println(String.format(\"[%s, %s] send %s in plaza messsage to kafka %s\", threadId, threadName, carLicense, (System.currentTimeMillis()-start)));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/disruptor/Sequence.java",
    "content": "package test.disruptor;\n\nimport com.lmax.disruptor.util.Util;\n\n/*\n * Copyright 2012 LMAX Ltd.\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 */\nimport sun.misc.Unsafe;\n\n/**\n * <p>Concurrent sequence class used for tracking the progress of\n * the ring buffer and event processors.  Support a number\n * of concurrent operations including CAS and order writes.\n *\n * <p>Also attempts to be more efficient with regards to false\n * sharing by adding padding around the volatile field.\n */\n@SuppressWarnings(\"restriction\")\npublic class Sequence extends RhsPadding {\n    static final long INITIAL_VALUE = -1L;\n    private static final Unsafe UNSAFE;\n    private static final long VALUE_OFFSET;\n\n    static {\n        UNSAFE = Util.getUnsafe();\n        try {\n            VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField(\"value\"));\n        } catch (final Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Create a sequence initialised to -1.\n     */\n    public Sequence() {\n        this(INITIAL_VALUE);\n    }\n\n    /**\n     * Create a sequence with a specified initial value.\n     *\n     * @param initialValue The initial value for this sequence.\n     */\n    public Sequence(final long initialValue) {\n        UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue);\n    }\n\n    /**\n     * Perform a volatile read of this sequence's value.\n     *\n     * @return The current value of the sequence.\n     */\n    public long get() {\n        return value;\n    }\n\n    /**\n     * Perform an ordered write of this sequence.  The intent is\n     * a Store/Store barrier between this write and any previous\n     * store.\n     *\n     * @param value The new value for the sequence.\n     */\n    public void set(final long value) {\n        UNSAFE.putOrderedLong(this, VALUE_OFFSET, value);\n    }\n\n    /**\n     * Performs a volatile write of this sequence.  The intent is\n     * a Store/Store barrier between this write and any previous\n     * write and a Store/Load barrier between this write and any\n     * subsequent volatile read.\n     *\n     * @param value The new value for the sequence.\n     */\n    public void setVolatile(final long value) {\n        UNSAFE.putLongVolatile(this, VALUE_OFFSET, value);\n    }\n\n    /**\n     * Perform a compare and set operation on the sequence.\n     *\n     * @param expectedValue The expected current value.\n     * @param newValue The value to update to.\n     * @return true if the operation succeeds, false otherwise.\n     */\n    public boolean compareAndSet(final long expectedValue, final long newValue) {\n        return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue);\n    }\n\n    /**\n     * Atomically increment the sequence by one.\n     *\n     * @return The value after the increment\n     */\n    public long incrementAndGet() {\n        return addAndGet(1L);\n    }\n\n    /**\n     * Atomically add the supplied value.\n     *\n     * @param increment The value to add to the sequence.\n     * @return The value after the increment.\n     */\n    public long addAndGet(final long increment) {\n        long currentValue;\n        long newValue;\n\n        do {\n            currentValue = get();\n            newValue = currentValue + increment;\n        } while (!compareAndSet(currentValue, newValue));\n\n        return newValue;\n    }\n\n    @Override\n    public String toString() {\n        return Long.toString(get());\n    }\n\n}\n\nclass LhsPadding {\n    protected final long p1 = 0, p2 = 0, p3 = 0, p4 = 0, p5 = 0, p6 = 0, p7 = 0;\n}\n\nclass Value extends LhsPadding {\n    protected volatile long value;\n}\n\nclass RhsPadding extends Value {\n    protected final long p9 = 0, p10 = 0, p11 = 0, p12 = 0, p13 = 0, p14 = 0, p15 = 0;\n}\n"
  },
  {
    "path": "src/test/java/test/elasticsearch/CateEsDaoImpl.java",
    "content": "//package test.elasticsearch;\n//\n//public class CateEsDaoImpl extends EsDbUtils implements CateEsDao {  \n//    @Override  \n//    public List<CateVenderData> queryListByFilterQuery(EsQueryObj esQueryObj) throws Exception {  \n//        return (List<CateVenderData>)queryObjectListByFilterQuery(esQueryObj,CateVenderData.class);  \n//    }  \n//   \n//    @Override  \n//    public List<CateVenderData> queryListByFilterQueryWithAgg(EsQueryObj esQueryObj) throws Exception {  \n//        return (List<CateVenderData>)queryObjectListByFilterQueryWithAgg(esQueryObj,CateVenderData.class);  \n//    }  \n//}  \n//   \n//   \n//public class RealTimeEsDaoImpl extends EsDbUtils implements RealTimeEsDao  {  \n//    @Override  \n//    public List<OdpOperatorSum> queryListByFilterQuery(EsQueryObj esQueryObj) throws Exception {  \n//        return (List<OdpOperatorSum>)queryObjectListByFilterQuery(esQueryObj,OdpOperatorSum.class);  \n//    }  \n//   \n//    @Override  \n//    public List<OdpOperatorSum> queryListByFilterQueryWithAgg(EsQueryObj esQueryObj) throws Exception {  \n//        return (List<OdpOperatorSum>)queryObjectListByFilterQueryWithAgg(esQueryObj,OdpOperatorSum.class);  \n//    }  \n//}  \n"
  },
  {
    "path": "src/test/java/test/elasticsearch/EsClientFactory.java",
    "content": "//package test.elasticsearch;\n//\n//import java.net.InetAddress;\n//import java.net.UnknownHostException;\n//\n//import org.apache.commons.logging.Log;\n//import org.apache.commons.logging.LogFactory;\n//import org.elasticsearch.client.transport.TransportClient;\n//import org.elasticsearch.common.settings.Settings;\n//import org.elasticsearch.common.transport.InetSocketTransportAddress;\n//import org.elasticsearch.transport.client.PreBuiltTransportClient;\n//\n//import com.google.common.base.Preconditions;\n// \n//public class EsClientFactory {\n//    private static final Log logger = LogFactory.getLog(EsClientFactory.class);\n// \n//    // 是否扫描集群\n//    private static boolean sniff = false;\n//    // ES 集群名称\n//    private static String clusterName;\n//    // IP地址\n//    private static String[] ips;\n//    // 端口\n//    private static int esPort;\n// \n//    private TransportClient esClient;//ES 客户端对象\n// \n//    public  EsClientFactory(String clusterName,String[] ips,int esPort) {\n//        this.clusterName=clusterName;\n//        this.ips=ips;\n//        this.esPort=esPort;\n//        init();\n//    }\n// \n//    /**\n//     * ES 客户端连接初始化\n//     *\n//     * @return ES客户端对象\n//     */\n//    private void init() {\n//        Preconditions.checkNotNull(clusterName, \"es 服务clusterName未配置\");\n//        Preconditions.checkNotNull(ips, \"es 服务ip未配置\");\n//        Preconditions.checkArgument(esPort > 0, \"es 服务服务port未配置\");\n//        //设置集群的名字\n//        Settings settings = Settings.builder()\n//                .put(\"cluster.name\", clusterName)\n//                .put(\"client.transport.sniff\", sniff)\n//                .build();\n//        //创建集群client并添加集群节点地址\n//        esClient = new PreBuiltTransportClient(settings);\n//        for (String ip : ips) {\n//            try {\n//                esClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(ip), esPort));\n//            } catch (UnknownHostException e) {\n//            }\n//        }\n//    }\n// \n//    public TransportClient getEsClient() {\n//        return esClient;\n//    }\n// \n//    public boolean isSniff() {\n//        return sniff;\n//    }\n// \n//    public String getClusterName() {\n//        return clusterName;\n//    }\n// \n// \n//    public String[] getIps() {\n//        return ips;\n//    }\n// \n// \n//    public int getEsPort() {\n//        return esPort;\n//    }\n// \n//} \n"
  },
  {
    "path": "src/test/java/test/elasticsearch/EsDbUtils.java",
    "content": "//package test.elasticsearch;\n//\n//import java.lang.reflect.Field;\n//import java.math.BigDecimal;\n//import java.util.ArrayList;\n//import java.util.Date;\n//import java.util.HashMap;\n//import java.util.List;\n//import java.util.Map;\n//\n//import org.apache.commons.lang3.StringUtils;\n//import org.apache.poi.ss.usermodel.DateUtil;\n//import org.elasticsearch.action.search.SearchRequestBuilder;\n//import org.elasticsearch.action.search.SearchResponse;\n//import org.elasticsearch.action.search.SearchType;\n//import org.elasticsearch.index.query.QueryBuilders;\n//import org.elasticsearch.search.SearchHit;\n//import org.elasticsearch.search.aggregations.Aggregation;\n//import org.elasticsearch.search.aggregations.AggregationBuilders;\n//import org.elasticsearch.search.aggregations.bucket.terms.Terms;\n//import org.elasticsearch.search.aggregations.bucket.terms.TermsBuilder;\n//import org.elasticsearch.search.aggregations.metrics.avg.Avg;\n//import org.elasticsearch.search.aggregations.metrics.sum.Sum;\n//import org.elasticsearch.search.sort.SortOrder;\n//import org.slf4j.Logger;\n//import org.slf4j.LoggerFactory;\n//\n//import com.fasterxml.jackson.dataformat.yaml.snakeyaml.introspector.PropertyUtils;\n//import com.google.gson.Gson;\n// \n//public class EsDbUtils <T>{\n//    private static final Logger logger = LoggerFactory.getLogger(EsDbUtils.class);\n//    private static final int FromIndex = 0;\n//    private static final int MinSize = 100;\n//    private static final int MaxSize = 100000;\n//    private static final int GroupMinSize = 100;\n//    private static final int GroupMaxSize = 500;\n//    private EsClientFactory esClientFactory;//ES 客户端工厂类\n// \n//    /**\n//     * 根据过滤条件查询出数据列表.需要传递索引和表名\n//     * @param esQueryObj ES查询对象\n//     * @param targetClass ES结果需要转换的类型\n//     * @return\n//     */\n//    public List<? extends Object> queryObjectListByFilterQuery(EsQueryObj esQueryObj,Class targetClass) throws Exception {\n//        validationEsQuery(esQueryObj);\n//        List<Object> esRecords = new ArrayList<Object>();\n//        long startCountTime = System.currentTimeMillis();\n//        //创建ES查询Request对象\n//        SearchRequestBuilder esSearch= esClientFactory.getEsClient().prepareSearch(esQueryObj.getIndexName());\n//        esSearch.setTypes(esQueryObj.getTypeName())\n//                .setSearchType(SearchType.QUERY_THEN_FETCH)\n//                .setFrom((esQueryObj.getFromIndex() > 0) ? esQueryObj.getFromIndex() : FromIndex)\n//                .setSize((0 <esQueryObj.getSize() && esQueryObj.getSize() <= MaxSize) ? esQueryObj.getSize() : MinSize);\n//        //添加查询条件\n//        if(esQueryObj.getAndFilterBuilder()!=null){\n//            esSearch.setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), esQueryObj.getAndFilterBuilder()));\n//        }\n//        //添加多级排序\n//        if(esQueryObj.getSortField()!=null) {\n//            for (Map.Entry<String, SortOrder> entry : esQueryObj.getSortField().entrySet()) {\n//                esSearch.addSort(entry.getKey(), entry.getValue());\n//            }\n//        }\n//        //执行查询\n//        SearchResponse response =esSearch .execute().actionGet();\n//        for (SearchHit hit : response.getHits()) {\n//            Object t = mapResult(hit.sourceAsMap(), targetClass);\n//            esRecords.add(t);\n//        }\n//        logger.info(\"queryObjectListByFilterQuery search \" + response.getHits().getTotalHits() + \" data ,esQueryObj=\" + new Gson().toJson(esQueryObj)+\"-----------------------------------------! use \" + (System.currentTimeMillis() - startCountTime) + \" ms.\");\n//        return esRecords;\n//    }\n// \n//    /**\n//     * 根据过滤条件和分组键SUM/AVG键获取分组结果(目前分组结果不支持LIMIT操作)\n//     * @param esQueryObj ES查询对象\n//     * @param targetClass ES结果需要转换的类型\n//     * @return\n//     * @throws Exception\n//     */\n//    public List<? extends Object> queryObjectListByFilterQueryWithAgg(EsQueryObj esQueryObj,Class targetClass) throws Exception {\n//        validationEsGroupQuery(esQueryObj);\n//        List<Object> esRecords = new ArrayList<Object>();\n//        long startCountTime = System.currentTimeMillis();\n//        if( esQueryObj.getSumFields()==null){\n//            esQueryObj.setSumFields(new ArrayList<String>());\n//        }\n//        if(esQueryObj.getAvgFields()==null){\n//            esQueryObj.setAvgFields(new ArrayList<String>());\n//        }\n//        TermsBuilder agg = getEsAgg(esQueryObj);\n//        //创建ES查询Request对象\n//        SearchRequestBuilder esSearch= esClientFactory.getEsClient().prepareSearch(esQueryObj.getIndexName());\n//        esSearch.setTypes(esQueryObj.getTypeName())\n//                .setSearchType(SearchType.QUERY_THEN_FETCH)\n//                .addAggregation(agg);\n//        //添加查询条件\n//        if(esQueryObj.getAndFilterBuilder()!=null){\n//            esSearch.setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), esQueryObj.getAndFilterBuilder()));\n//        }\n//        //添加多级排序\n//        if(esQueryObj.getSortField()!=null) {\n//            for (Map.Entry<String, SortOrder> entry : esQueryObj.getSortField().entrySet()) {\n//                esSearch.addSort(entry.getKey(), entry.getValue());\n//            }\n//        }\n//        //执行查询\n//        SearchResponse response =esSearch .execute().actionGet();\n//        List<Map<String, Object>> aggMaps= getAggMap(response,esQueryObj.getGroupFields(), esQueryObj.getSumFields(), esQueryObj.getAvgFields());\n//        for(Map<String, Object> aggMap : aggMaps){\n//            Object t = mapResult(aggMap, targetClass);\n//            esRecords.add(t);\n//        }\n//        logger.info(\"queryObjectListByFilterQuery search \" + response.getHits().getTotalHits() + \" data,esQueryObj=\" + new Gson().toJson(esQueryObj)+\"-----------------------------------------! use \" + (System.currentTimeMillis() - startCountTime) + \" ms.\");\n//        return esRecords;\n//    }\n//    /**\n//     * 根据分组键和SUM/AVG键组合AGG条件\n//     * @param esQueryObj\n//     * @return\n//     */\n//    private TermsBuilder getEsAgg(EsQueryObj esQueryObj) throws Exception{\n//        List<String> groupFields= esQueryObj.getGroupFields();\n//        List<String> sumFields= esQueryObj.getSumFields();\n//        List<String> avgFields= esQueryObj.getAvgFields();\n//        int groupSize=esQueryObj.getGroupSize();\n//        Map<String, SortOrder> groupSortMap=esQueryObj.getGroupSortField();\n//        TermsBuilder termsBuilder = AggregationBuilders.terms(groupFields.get(0)).field(groupFields.get(0));\n//        if (groupFields.size() == 1) {\n//            //设置排序后最后一层的结果数目\n//            termsBuilder.size((0 <groupSize && groupSize <= GroupMaxSize) ? groupSize : GroupMinSize);\n//            //添加group排序字段\n//            if(groupSortMap!=null) {\n//                List<Terms.Order> termsOrders=new ArrayList<Terms.Order>();\n//                for (Map.Entry<String, SortOrder> entry : groupSortMap.entrySet()) {\n//                    if(entry.getValue().equals(SortOrder.ASC)){\n//                        termsOrders.add(Terms.Order.aggregation(entry.getKey(),true));\n//                    }else{\n//                        termsOrders.add(Terms.Order.aggregation(entry.getKey(), false));\n//                    }\n//                }\n//                termsBuilder.order(Terms.Order.compound(termsOrders));\n//            }\n//            for (String avgField : avgFields) {\n//                termsBuilder.subAggregation(AggregationBuilders.avg(avgField).field(avgField));\n//            }\n//            for (String sumField : sumFields) {\n//                termsBuilder.subAggregation(AggregationBuilders.sum(sumField).field(sumField));\n//            }\n//        } else {\n//            termsBuilder.subAggregation(getChildTermsBuilder(groupFields, 1, sumFields, avgFields,groupSize,groupSortMap));\n//            //设置最外层分组量\n//            termsBuilder.size(GroupMaxSize);\n//        }\n//        return termsBuilder;\n//    }\n// \n//    /**\n//     * 通过递归的方式获取bucket agg分组语句\n//     * @param groupFields\n//     * @param i\n//     * @param sumFields\n//     * @param avgFields\n//     * @return\n//     */\n//    private  TermsBuilder getChildTermsBuilder(List<String> groupFields,int i,List<String> sumFields, List<String> avgFields,int groupSize,Map<String, SortOrder> groupSortMap){\n//        if(i+1==groupFields.size()){\n//            TermsBuilder termsBuilderLast = AggregationBuilders.terms(groupFields.get(i)).field(groupFields.get(i));\n//            //设置排序后最后一层的结果数目\n//            termsBuilderLast.size((0 <groupSize && groupSize <= GroupMaxSize) ? groupSize : GroupMinSize);\n//            //添加group排序字段\n//            if(groupSortMap!=null) {\n//                for (Map.Entry<String, SortOrder> entry : groupSortMap.entrySet()) {\n//                    if(entry.getValue().equals(SortOrder.ASC)){\n//                        termsBuilderLast.order(Terms.Order.aggregation(entry.getKey(),true));\n//                    }else{\n//                        termsBuilderLast.order(Terms.Order.aggregation(entry.getKey(),false));\n//                    }\n// \n//                }\n//            }\n//            for (String avgField : avgFields) {\n//                termsBuilderLast.subAggregation(AggregationBuilders.avg(avgField).field(avgField));\n//            }\n//            for (String sumField : sumFields) {\n//                termsBuilderLast.subAggregation(AggregationBuilders.sum(sumField).field(sumField));\n//            }\n//            return termsBuilderLast;\n//        }\n//        else{\n//            TermsBuilder termsBuilder= AggregationBuilders.terms(groupFields.get(i)).field(groupFields.get(i));\n//            //设置最外层分组量\n//            termsBuilder.size(GroupMaxSize);\n//            return  termsBuilder.subAggregation(getChildTermsBuilder(groupFields,i+1,sumFields,avgFields,groupSize,groupSortMap));\n//        }\n//    }\n// \n//    /**\n//     * 根据汇总键和SUM/AVG键，组合返回的查询值为MAP格式\n//     * @param response\n//     * @param groupFields\n//     * @param sumFields\n//     * @param avgFields\n//     * @return\n//     */\n//    private   List<Map<String, Object>>  getAggMap(SearchResponse response,List <String>groupFields,List<String> sumFields, List<String> avgFields){\n// \n//        List<Map<String, Object>> aggMaps = new ArrayList<Map<String, Object>>();\n//        //首先获取最外层的AGG结果\n//        Terms tempAggregation = response.getAggregations().get(groupFields.get(0));\n//        //只有一个分组键不用进行递归\n//        if(groupFields.size()==1){\n//            for(Terms.Bucket tempBk:tempAggregation.getBuckets()){\n//                Map<String, Object> tempMap = new HashMap<String, Object>();\n//                tempMap.put(tempAggregation.getName(), tempBk.getKey());\n//                for (Map.Entry<String, Aggregation> entry : tempBk.getAggregations().getAsMap().entrySet()) {\n//                    String key = entry.getKey();\n//                    if (sumFields.contains(key)) {\n//                        Sum aggSum = (Sum) entry.getValue();\n//                        double value = aggSum.getValue();\n//                        tempMap.put(key, value);\n//                    }\n//                    if (avgFields.contains(key)) {\n//                        Avg aggAvg = (Avg) entry.getValue();\n//                        double value = aggAvg.getValue();\n//                        tempMap.put(key, value);\n//                    }\n//                }\n//                aggMaps.add(tempMap);\n//            }\n//        }\n//        else {\n//            for (Terms.Bucket bk : tempAggregation.getBuckets()) {\n//                //每个最外层的分组键生成一个键值对MAP\n//                Map<String, Object> nkMap = new HashMap<String, Object>();\n//                nkMap.put(tempAggregation.getName(), bk.getKey());\n//                //通过递归的方式填充键值对MAP并加到最终的列表中\n//                getChildAggMap(bk, 1, groupFields, sumFields, avgFields, nkMap, aggMaps);\n//            }\n//        }\n//        return aggMaps;\n//    }\n// \n//    /**\n//     * 深层递归所有的AGG返回值，组合成最终的MAP列表\n//     * @param bk 每次递归的单个Bucket\n//     * @param i 控制分组键列表到了哪一层\n//     * @param groupFields 分组键列表\n//     * @param sumFields SUM键列表\n//     * @param avgFields AVG键列表\n//     * @param nkMap 键值对MAP\n//     * @param aggMaps 最终结果的中间值\n//     * @return\n//     */\n//    private  List<Map<String, Object>> getChildAggMap(Terms.Bucket bk,int i,List <String>groupFields,List<String> sumFields, List<String> avgFields,Map<String, Object> nkMap,List<Map<String, Object>> aggMaps){\n// \n//        if(i==groupFields.size()-1){\n//            Terms tempAggregation = bk.getAggregations().get(groupFields.get(i));\n//            for(Terms.Bucket tempBk:tempAggregation.getBuckets()){\n//                Map<String, Object> tempMap = new HashMap<String, Object>();\n//                tempMap.putAll(nkMap);\n//                tempMap.put(tempAggregation.getName(), tempBk.getKey());\n//                for (Map.Entry<String, Aggregation> entry : tempBk.getAggregations().getAsMap().entrySet()) {\n//                    String key = entry.getKey();\n//                    if (sumFields.contains(key)) {\n//                        Sum aggSum = (Sum) entry.getValue();\n//                        double value = aggSum.getValue();\n//                        tempMap.put(key, value);\n//                    }\n//                    if (avgFields.contains(key)) {\n//                        Avg aggAvg = (Avg) entry.getValue();\n//                        double value = aggAvg.getValue();\n//                        tempMap.put(key, value);\n//                    }\n//                }\n//                aggMaps.add(tempMap);\n//            }\n//            return  aggMaps;\n//        } else{\n//            Terms tempAggregation = bk.getAggregations().get(groupFields.get(i));\n//            for(Terms.Bucket tempBk:tempAggregation.getBuckets()){\n//                nkMap.put(tempAggregation.getName(), tempBk.getKey());\n//                getChildAggMap(tempBk, i + 1, groupFields, sumFields, avgFields, nkMap, aggMaps);\n//            }\n//            return  aggMaps;\n//        }\n//    }\n// \n//    /**\n//     * 将ES结果映射到指定对象\n//     * @param resultMap\n//     * @param cls\n//     * @return\n//     * @throws Exception\n//     */\n//    public T mapResult(Map<String, Object> resultMap, Class<T> cls) throws  Exception{\n//        T result = cls.newInstance();\n//        Field[] fields = cls.getDeclaredFields();\n//        for (Field field : fields) {\n//            Object object = resultMap.get(field.getName());\n//            if (object != null) {\n//                //根据几种基本类型做转换\n//                if (field.getType().equals(Long.class) || field.getType().equals(long.class)) {\n//                    if(object.toString().indexOf('.')>0){\n//                        PropertyUtils.setProperty(result, field.getName(), Long.parseLong(object.toString().substring(0, object.toString().indexOf('.'))));\n//                    }else{\n//                        PropertyUtils.setProperty(result, field.getName(), Long.parseLong(object.toString()));\n//                    }\n//                }else if (field.getType().equals(long.class) || field.getType().equals(long.class)) {\n//                    if(object.toString().indexOf('.')>0){\n//                        PropertyUtils.setProperty(result, field.getName(), Long.parseLong(object.toString().substring(0, object.toString().indexOf('.'))));\n//                    }else{\n//                        PropertyUtils.setProperty(result, field.getName(), Long.parseLong(object.toString()));\n//                    }\n//                }else if (field.getType().equals(Integer.class) || field.getType().equals(Integer.class)) {\n//                    if(object.toString().indexOf('.')>0){\n//                        PropertyUtils.setProperty(result, field.getName(), Integer.parseInt(object.toString().substring(0, object.toString().indexOf('.'))));\n//                    }else{\n//                        PropertyUtils.setProperty(result, field.getName(), Integer.parseInt(object.toString()));\n//                    }\n//                }else if (field.getType().equals(int.class) || field.getType().equals(int.class)) {\n//                    if(object.toString().indexOf('.')>0){\n//                        PropertyUtils.setProperty(result, field.getName(), Integer.parseInt(object.toString().substring(0, object.toString().indexOf('.'))));\n//                    }else{\n//                        PropertyUtils.setProperty(result, field.getName(), Integer.parseInt(object.toString()));\n//                    }\n// \n//                }else if (field.getType().equals(BigDecimal.class) || field.getType().equals(BigDecimal.class)) {\n//                    PropertyUtils.setProperty(result, field.getName(), BigDecimal.valueOf(Double.parseDouble(object.toString())));\n//                }else if (field.getType().equals(Double.class) || field.getType().equals(Double.class)) {\n//                    PropertyUtils.setProperty(result, field.getName(), Double.parseDouble(object.toString()));\n//                }else if (field.getType().equals(double.class) || field.getType().equals(double.class)) {\n//                    PropertyUtils.setProperty(result, field.getName(), Double.parseDouble(object.toString()));\n//                }else if (field.getType().equals(Date.class) || field.getType().equals(Date.class)) {\n//                    PropertyUtils.setProperty(result, field.getName(), DateUtil.createDate(object.toString()));\n//                }else if (field.getType().equals(String.class) || field.getType().equals(String.class)) {\n//                    PropertyUtils.setProperty(result, field.getName(), object);\n//                }\n//            }\n//        }\n//        return result;\n//    }\n// \n//    /**\n//     * 验证ES查询对象\n//     * @param esQueryObj\n//     * @throws Exception\n//     */\n//    private void validationEsQuery(EsQueryObj esQueryObj) throws Exception{\n//        if(StringUtils.isEmpty(esQueryObj.getIndexName())&&StringUtils.isEmpty(esQueryObj.getTypeName())){\n//            throw  new Exception(\"please check indexName and typeName\");\n//        }\n//    }\n//    /**\n//     * 验证ES查询分组对象\n//     * @param esQueryObj\n//     * @throws Exception\n//     */\n//    private void validationEsGroupQuery(EsQueryObj esQueryObj) throws Exception{\n//        if(StringUtils.isEmpty(esQueryObj.getIndexName())&&StringUtils.isEmpty(esQueryObj.getTypeName())){\n//            throw  new Exception(\"please check indexName and typeName\");\n//        }\n//        boolean groupOrderStatus=true;\n//        st:for (Map.Entry<String, SortOrder> entry : esQueryObj.getGroupSortField().entrySet()) {\n//            if(!esQueryObj.getSumFields().contains(entry.getKey())&&!esQueryObj.getAvgFields().contains(entry.getKey())){\n//                groupOrderStatus=false;\n//                break st;\n//            }\n//        }\n//        if(!groupOrderStatus){\n//            throw  new Exception(\"please check groupSortField\");\n//        }\n//        if (esQueryObj.getGroupFields().isEmpty() || esQueryObj.getGroupFields().size() <= 0 ||(esQueryObj.getSumFields().isEmpty()&&esQueryObj.getAvgFields().isEmpty())) {\n//            throw  new Exception(\"please check groupFields and sumFields and avgFields\");\n//        }\n//    }\n//    public EsClientFactory getEsClientFactory() {\n//        return esClientFactory;\n//    }\n// \n//    public void setEsClientFactory(EsClientFactory esClientFactory) {\n//        this.esClientFactory = esClientFactory;\n//    }\n//}\n"
  },
  {
    "path": "src/test/java/test/elasticsearch/EsQueryObj.java",
    "content": "//package test.elasticsearch;\n//\n//import java.util.List;\n//import java.util.Map;\n//\n//import org.elasticsearch.index.query.AndFilterBuilder;\n//import org.elasticsearch.index.query.BoolQueryBuilder;\n//import org.elasticsearch.search.sort.SortOrder;\n// \n//public class EsQueryObj {\n//    /**\n//     * ES索引名\n//     */\n//    String indexName;\n//    /**\n//     * ES TYPE名（表名)\n//     */\n//    String typeName;\n//    /**\n//     * 查询条件组合，类似于 \"boolQuery().must( QueryBuilders.termQuery(\"opTime\", \"2016-03-30\")).must(QueryBuilders.termQuery(\"operErpID\", \"xingkai\"))\"\n//     */\n//    BoolQueryBuilder bq;\n//    /**\n//     * 查询条件组合，类似于 \"boolQuery().must( QueryBuilders.termQuery(\"opTime\", \"2016-03-30\")).must(QueryBuilders.termQuery(\"operErpID\", \"xingkai\"))\"\n//     */\n//    AndFilterBuilder andFilterBuilder;\n//    /**\n//     * 分组键值列表，类似于group by 之后的字段\n//     */\n//    List <String> groupFields;\n//    /**\n//     * 分组SUM键值列表\n//     */\n//    List <String> sumFields;\n//    /**\n//     * 分组AVG键值列表\n//     */\n//    List <String> avgFields;\n//    /**\n//     * 排序字段键值对，可以添加多个，类似于(\"opTime\",\"DESC\")\n//     */\n//    Map<String,SortOrder> sortField;\n//    /**\n//     * 分组后排序字段键值对，可以添加多个，类似于(\"opTime\",\"DESC\"),注意此处最后PUT的键最先排序,排序键必须在sumFields或avgFields(只针对最后一层分组)\n//     */\n//    Map<String,SortOrder> groupSortField;\n//    /**\n//     * 分组后返回数据数量，默认为100，最大不能超过500(只针对最后一层分组)\n//     */\n//    int groupSize;\n//    /**\n//     * 取值的起始位置，默认为0\n//     */\n//    int fromIndex;\n//    /**\n//     * 返回数据数量，默认为100，最大不能超过100000\n//     */\n//    int size;\n// \n//    public String getIndexName() {\n//        return indexName;\n//    }\n// \n//    public void setIndexName(String indexName) {\n//        this.indexName = indexName;\n//    }\n// \n//    public String getTypeName() {\n//        return typeName;\n//    }\n// \n//    public void setTypeName(String typeName) {\n//        this.typeName = typeName;\n//    }\n// \n//    public BoolQueryBuilder getBq() {\n//        return bq;\n//    }\n// \n//    public void setBq(BoolQueryBuilder bq) {\n//        this.bq = bq;\n//    }\n// \n//    public AndFilterBuilder getAndFilterBuilder() {\n//        return andFilterBuilder;\n//    }\n// \n//    public void setAndFilterBuilder(AndFilterBuilder andFilterBuilder) {\n//        this.andFilterBuilder = andFilterBuilder;\n//    }\n// \n//    public List<String> getGroupFields() {\n//        return groupFields;\n//    }\n// \n//    public void setGroupFields(List<String> groupFields) {\n//        this.groupFields = groupFields;\n//    }\n// \n//    public List<String> getSumFields() {\n//        return sumFields;\n//    }\n// \n//    public void setSumFields(List<String> sumFields) {\n//        this.sumFields = sumFields;\n//    }\n// \n//    public List<String> getAvgFields() {\n//        return avgFields;\n//    }\n// \n//    public void setAvgFields(List<String> avgFields) {\n//        this.avgFields = avgFields;\n//    }\n// \n//    public Map<String, SortOrder> getSortField() {\n//        return sortField;\n//    }\n// \n//    public void setSortField(Map<String, SortOrder> sortField) {\n//        this.sortField = sortField;\n//    }\n// \n//    public int getFromIndex() {\n//        return fromIndex;\n//    }\n// \n//    public void setFromIndex(int fromIndex) {\n//        this.fromIndex = fromIndex;\n//    }\n// \n//    public int getSize() {\n//        return size;\n//    }\n// \n//    public void setSize(int size) {\n//        this.size = size;\n//    }\n// \n//    public Map<String, SortOrder> getGroupSortField() {\n//        return groupSortField;\n//    }\n// \n//    public void setGroupSortField(Map<String, SortOrder> groupSortField) {\n//        this.groupSortField = groupSortField;\n//    }\n// \n//    public int getGroupSize() {\n//        return groupSize;\n//    }\n// \n//    public void setGroupSize(int groupSize) {\n//        this.groupSize = groupSize;\n//    }\n//}\n"
  },
  {
    "path": "src/test/java/test/export/ConsoleExportTest.java",
    "content": "package test.export;\n\nimport cn.ponfee.commons.export.AbstractDataExporter;\nimport cn.ponfee.commons.export.ConsoleExporter;\nimport cn.ponfee.commons.export.Table;\nimport org.junit.Test;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.UUID;\n\npublic class ConsoleExportTest {\n    private int multiple = 20;\n\n    @Test\n    public void test() {\n        //AbstractDataExporter<String> export = new ConsoleExporter(System.out, 30, true);\n        AbstractDataExporter<String> export = new ConsoleExporter(System.out, 36, false);\n        Table table1 = new Table(new String[]{\"name1\", \"age\", \"gender\", \"birthday\", \"x\"});\n\n        List<Object[]> data1 = Arrays.asList(\n                new Object[]{\"alalicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealiceice\", 20, \"male\", \"2000-05-30\", 0},\n                new Object[]{\"bob\", 20, \"female\", \"2001-05-30\", 1},\n                new Object[]{UUID.randomUUID().toString(), 25, \"male\", \"2000-04-30\", 2}\n        );\n\n        table1.setCaption(\"test\");\n        table1.addRowsAndEnd(data1);\n        export.setName(\"报表1\").build(table1);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/export/ExportTester.java",
    "content": "package test.export;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport cn.ponfee.commons.tree.PlainNode;\nimport cn.ponfee.commons.util.MavenProjects;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.apache.poi.xssf.usermodel.XSSFWorkbook;\nimport org.junit.Test;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Lists;\n\nimport cn.ponfee.commons.export.AbstractDataExporter;\nimport cn.ponfee.commons.export.CellStyleOptions;\nimport cn.ponfee.commons.export.CsvStringExporter;\nimport cn.ponfee.commons.export.ExcelExporter;\nimport cn.ponfee.commons.export.HtmlExporter;\nimport cn.ponfee.commons.export.Table;\nimport cn.ponfee.commons.export.Thead;\nimport cn.ponfee.commons.io.ByteOrderMarks;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.model.ResultCode;\nimport cn.ponfee.commons.tree.BaseNode;\nimport cn.ponfee.commons.util.Captchas;\n\npublic class ExportTester {\n\n    public static final String baseDir = MavenProjects.getProjectBaseDir()+\"/test/\";\n    static {\n        try {\n            FileUtils.forceMkdir(new File(baseDir));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private int multiple = 20;\n\n    public @Test void testHtml1() throws IOException {\n        AbstractDataExporter html = new HtmlExporter();\n        AbstractDataExporter csv = new CsvStringExporter();\n        List<BaseNode<Integer, Thead>> list = new ArrayList<>();\n\n        list.add(new PlainNode<>(1, 0, new Thead(\"区域\")));\n        list.add(new PlainNode<>(2, 0, new Thead(\"分公司\")));\n\n        list.add(new PlainNode<>(3, 0, new Thead(\"昨天\")));\n        list.add(new PlainNode<>(4, 3, new Thead(\"项目数\")));\n        list.add(new PlainNode<>(5, 3, new Thead(\"项目应收(元)\")));\n        list.add(new PlainNode<>(6, 3, new Thead(\"成交套数\")));\n        list.add(new PlainNode<>(7, 3, new Thead(\"套均收入(元)\")));\n        list.add(new PlainNode<>(8, 3, new Thead(\"团购项目数\")));\n        list.add(new PlainNode<>(9, 3, new Thead(\"导客项目数\")));\n        list.add(new PlainNode<>(10, 3, new Thead(\"代收项目数\")));\n        list.add(new PlainNode<>(11, 3, new Thead(\"线上项目数\")));\n        list.add(new PlainNode<>(12, 0, new Thead(\"本月\")));\n        list.add(new PlainNode<>(13, 12,new Thead(\"应收(万)\")));\n        list.add(new PlainNode<>(14, 12,new Thead(\"实收(万)\")));\n        list.add(new PlainNode<>(15, 12,new Thead(\"成交套数\")));\n        list.add(new PlainNode<>(16, 12,new Thead(\"套均收入(元)\")));\n        list.add(new PlainNode<>(17, 12,new Thead(\"团购项目应收(万)\")));\n        list.add(new PlainNode<>(18, 12,new Thead(\"团购项目成交套数\")));\n        list.add(new PlainNode<>(19, 12,new Thead(\"团购项目经服成交套数\")));\n        list.add(new PlainNode<>(20, 12,new Thead(\"团购项目套均收入(元)\")));\n        list.add(new PlainNode<>(21, 12,new Thead(\"团购项目经服成交应收(万)\")));\n        list.add(new PlainNode<>(22, 12,new Thead(\"团购项目中介应付外佣(万)\")));\n        list.add(new PlainNode<>(23, 12,new Thead(\"团购项目经服成交套数占比\")));\n        list.add(new PlainNode<>(24, 12,new Thead(\"团购项目中介分佣比例\")));\n        list.add(new PlainNode<>(25, 12,new Thead(\"导客项目应收(万)\")));\n        list.add(new PlainNode<>(26, 12,new Thead(\"导客项目成交套数\")));\n        list.add(new PlainNode<>(27, 12,new Thead(\"导客项目套均收入(元)\")));\n        list.add(new PlainNode<>(28, 12,new Thead(\"导客项目中介应付外佣(万)\")));\n        list.add(new PlainNode<>(29, 12,new Thead(\"导客项目中介分佣比例\")));\n        list.add(new PlainNode<>(30, 12,new Thead(\"代收项目应收(万)\")));\n        list.add(new PlainNode<>(31, 12,new Thead(\"代收项目成交套数\")));\n        list.add(new PlainNode<>(32, 12,new Thead(\"线上项目应收(万)\")));\n        list.add(new PlainNode<>(33, 12,new Thead(\"线上项目成交套数\")));\n        list.add(new PlainNode<>(34, 12,new Thead(\"月指标(万)\")));\n        list.add(new PlainNode<>(35, 12,new Thead(\"指标完成率\")));\n\n        Table table = new Table(list);\n        System.out.println(Jsons.toJson(table.getThead()));\n        table.setCaption(\"abc\");\n        table.addRowsAndEnd(Lists.newArrayList(new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},\n                                          new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}\n        ));\n        table.setTfoot(new Object[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1});\n        table.setComment(\"comment1;comment2;comment3;comment4;comment5;comment6;\");\n        html.build(table);\n        csv.build(table);\n\n        table = new Table(list);\n        table.setCaption(\"123\");\n        table.toEnd();\n        html.build(table);\n\n        table = new Table(list);\n        table.toEnd();\n        table.setCaption(\"bnm\");\n        html.build(table);\n\n        IOUtils.write((String) html.setName(\"报表\").export(), new FileOutputStream(baseDir+\"testHtml1.html\"), \"UTF-8\");\n        ByteOrderMarks.add(baseDir+\"testHtml1.html\");\n        IOUtils.write((String) csv.export(), new FileOutputStream(baseDir+\"testHtml1.csv\"), \"UTF-8\");\n        ByteOrderMarks.add(baseDir+\"testHtml1.csv\");\n\n        html.close();\n        csv.close();\n    }\n\n    @Test\n    public void testHtml2() throws FileNotFoundException, IOException {\n        AbstractDataExporter html = new HtmlExporter();\n        html.build(new Table(\"a,b,c,d,e\".split(\",\")).toEnd());\n        IOUtils.write((String) html.export(), new FileOutputStream(baseDir+\"testHtml2.html\"), \"UTF-8\");\n        ByteOrderMarks.add(baseDir+\"testHtml2.html\");\n        html.close();\n    }\n\n    @Test\n    public void testExcel() throws IOException {\n        ExcelExporter excel = new ExcelExporter();\n        List<PlainNode<Integer, Thead>> list = new ArrayList<>();\n\n        list.add(new PlainNode<>(1, 0, new Thead(\"区域\")));\n        list.add(new PlainNode<>(2, 0, new Thead(\"分公司\")));\n\n        list.add(new PlainNode<>(3, 0, new Thead(\"昨天\")));\n        list.add(new PlainNode<>(4, 3, new Thead(\"项目数\")));\n        list.add(new PlainNode<>(5, 3, new Thead(\"项目应收(元)\")));\n        list.add(new PlainNode<>(6, 3, new Thead(\"成交套数\")));\n        list.add(new PlainNode<>(7, 3, new Thead(\"套均收入(元)\")));\n        list.add(new PlainNode<>(8, 3, new Thead(\"团购项目数\")));\n        list.add(new PlainNode<>(9, 3, new Thead(\"导客项目数\")));\n        list.add(new PlainNode<>(10, 3, new Thead(\"代收项目数\")));\n        list.add(new PlainNode<>(11, 3, new Thead(\"线上项目数\")));\n        list.add(new PlainNode<>(12, 0, new Thead(\"本月\")));\n        list.add(new PlainNode<>(13, 12, new Thead(\"应收(万)\")));\n        list.add(new PlainNode<>(14, 12, new Thead(\"实收(万)\")));\n        list.add(new PlainNode<>(15, 12, new Thead(\"成交套数\")));\n        list.add(new PlainNode<>(16, 12, new Thead(\"套均收入(元)\")));\n        list.add(new PlainNode<>(17, 12, new Thead(\"团购项目应收(万)\")));\n        list.add(new PlainNode<>(18, 12, new Thead(\"团购项目成交套数\")));\n        list.add(new PlainNode<>(19, 12, new Thead(\"团购项目经服成交套数\")));\n        list.add(new PlainNode<>(20, 12, new Thead(\"团购项目套均收入(元)\")));\n        list.add(new PlainNode<>(21, 12, new Thead(\"团购项目经服成交应收(万)\")));\n        list.add(new PlainNode<>(22, 12, new Thead(\"团购项目中介应付外佣(万)\")));\n        list.add(new PlainNode<>(23, 12, new Thead(\"团购项目经服成交套数占比\")));\n        list.add(new PlainNode<>(24, 12, new Thead(\"团购项目中介分佣比例\")));\n        list.add(new PlainNode<>(25, 12, new Thead(\"导客项目应收(万)\")));\n        list.add(new PlainNode<>(26, 12, new Thead(\"导客项目成交套数\")));\n        list.add(new PlainNode<>(27, 12, new Thead(\"导客项目套均收入(元)\")));\n        list.add(new PlainNode<>(28, 12, new Thead(\"导客项目中介应付外佣(万)\")));\n        list.add(new PlainNode<>(29, 12, new Thead(\"导客项目中介分佣比例\")));\n        list.add(new PlainNode<>(30, 12, new Thead(\"代收项目应收(万)\")));\n        list.add(new PlainNode<>(31, 12, new Thead(\"代收项目成交套数\")));\n        list.add(new PlainNode<>(32, 12, new Thead(\"线上项目应收(万)\")));\n        list.add(new PlainNode<>(33, 12, new Thead(\"线上项目成交套数\")));\n        list.add(new PlainNode<>(34, 12, new Thead(\"月指标(万)\")));\n        list.add(new PlainNode<>(35, 12, new Thead(\"指标完成率\")));\n\n        List<Object[]> data1 = new ArrayList<>();\n        for (int i = 0; i < 2*multiple; i++) {\n            data1.add(new Object[] { \"1234563.918%\", \"2017-02-03\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\",\n                \"abd\", \"abd\",\n                \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"1\" });\n        }\n\n        List<Object[]> data2 = new ArrayList<>();\n        for (int i = 0; i < 5*multiple; i++) {\n            data2.add(new Object[] { \"1234563.918%\", \"2017-02-03\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\",\n                \"abd\", \"abd\",\n                \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"1\" });\n        }\n\n        List<Object[]> data3 = new ArrayList<>();\n        for (int i = 0; i < 3*multiple; i++) {\n            data3.add(new Object[] { \"1234563.918%\", \"2017-02-03\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\",\n                \"abd\", \"abd\",\n                \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"1\" });\n        }\n        Object[] tfoot = new Object[] {\"1\", \"2\", \"3\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\",\"abd\", \"abd\",\n            \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"abd\", \"1\" };\n        Map<CellStyleOptions, Object> options = ImmutableMap.of(CellStyleOptions.HIGHLIGHT, ImmutableMap.of(\"cells\", Lists.newArrayList(Lists.newArrayList(1,1),Lists.newArrayList(2,2)), \"color\", \"#FF3030\"));\n        long start = System.currentTimeMillis();\n        System.out.println(\"========================================start\");\n\n        Table<Object[]> table1 = new Table<>(list);\n        table1.setCaption(\"test1\");\n        table1.addRowsAndEnd(data1);\n        table1.setTfoot(tfoot);\n        table1.setOptions(options);\n        excel.setName(\"报表1\").build(table1);\n\n        // ------------------------------------------\n        Table table2 = new Table(list);\n        table2.setCaption(\"test2\");\n        table2.addRowsAndEnd(data2);\n        table2.setTfoot(tfoot);\n        table2.setOptions(options);\n        excel.setName(\"报表2\").build(table2);\n\n        // ------------------------------------------\n        Table table3 = new Table(list);\n        table3.setCaption(\"test3\");\n        table3.addRowsAndEnd(data3);\n        table3.setTfoot(tfoot);\n        table3.setOptions(options);\n        excel.setName(\"报表1\").build(table3);\n\n        // ------------------------------------------\n        excel.setName(\"图表\");\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        Captchas.generate(200, baos, RandomStringUtils.randomAlphanumeric(10));\n        excel.insertImage(baos.toByteArray());\n        baos = new ByteArrayOutputStream();\n        Captchas.generate(200, baos, RandomStringUtils.randomAlphanumeric(10));\n        excel.insertImage(baos.toByteArray());\n\n        OutputStream out = new FileOutputStream(baseDir+\"abc1.xlsx\");\n        excel.write(out);\n        out.close();\n        excel.close();\n        System.out.println(\"========================================excel: \" + (System.currentTimeMillis() - start));\n\n        start = System.currentTimeMillis();\n        // -------------------------csv\n        CsvStringExporter csv = new CsvStringExporter();\n        table1 = new Table<>(list);\n        table1.setCaption(\"test1\");\n        table1.addRowsAndEnd(data1);\n        table1.setTfoot(tfoot);\n        table1.setOptions(options);\n        csv.build(table1);\n        IOUtils.write(csv.export().toString(), new FileOutputStream(baseDir+\"testExcel.csv\"), \"UTF-8\");\n        ByteOrderMarks.add(new File(baseDir+\"testExcel.csv\"));\n        csv.close();\n        System.out.println(\"========================================csv: \" + (System.currentTimeMillis() - start));\n\n        start = System.currentTimeMillis();\n        HtmlExporter html = new HtmlExporter();\n        table1 = new Table(list);\n        table1.setCaption(\"test1\");\n        table1.addRowsAndEnd(data1);\n        table1.setTfoot(tfoot);\n        table1.setOptions(options);\n        html.build(table1);\n        html.setName(\"test\");\n        IOUtils.write((String) html.export(), new FileOutputStream(baseDir+\"testExcel.html\"), \"UTF-8\");\n        ByteOrderMarks.add(baseDir+\"testExcel.html\");\n        html.close();\n        System.out.println(\"========================================html: \" + (System.currentTimeMillis() - start));\n    }\n\n    @Test\n    public void testExcel2() throws IOException {\n        AbstractDataExporter excel = new ExcelExporter();\n\n        Table table = new Table(\"a,b,c,d,e\".split(\",\"));\n        table.setCaption(\"title\");\n        List<Object[]> data = new ArrayList<>();\n        data.add(new Object[] { \"11111111111111111111111111111111111111111\", \"2\", \"3\", \"4\", \"5\" });\n        table.addRowsAndEnd(data);\n        excel.setName(\"21321\");\n        excel.build(table);\n        IOUtils.write((byte[]) excel.export(), new FileOutputStream(baseDir+\"testExcel2.xlsx\"));\n        excel.close();\n    }\n\n    @Test\n    public void testExcel5() throws IOException {\n        AbstractDataExporter<byte[]> excel = new ExcelExporter();\n\n        Table<Result<Void>> table = new Table<>(\n            \"a,b\".split(\",\"),\n            o -> new Object[] { o.getCode(), o.getMsg() }\n        );\n        table.setCaption(\"title\");\n        table.addRow(Result.success());\n        table.addRow(Result.failure(ResultCode.BAD_REQUEST));\n        table.toEnd();\n\n        excel.setName(\"21321\");\n        excel.build(table);\n        IOUtils.write((byte[]) excel.export(), new FileOutputStream(baseDir+\"11111xxxx.xlsx\"));\n        excel.close();\n    }\n\n\n    @Test\n    public void testExcel3() throws IOException {\n        XSSFWorkbook wb = new XSSFWorkbook();\n        FileOutputStream out = new FileOutputStream(baseDir+\"test_empty.xlsx\");\n        wb.createSheet();\n        wb.write(out);\n        wb.close();\n        out.close();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/export/ExportTester2.java",
    "content": "package test.export;\n\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.apache.commons.io.IOUtils;\nimport org.junit.Test;\n\nimport com.google.common.base.Stopwatch;\n\nimport cn.ponfee.commons.concurrent.ThreadPoolExecutors;\nimport cn.ponfee.commons.export.AbstractDataExporter;\nimport cn.ponfee.commons.export.CsvFileExporter;\nimport cn.ponfee.commons.export.ExcelExporter;\nimport cn.ponfee.commons.export.SplitCsvFileExporter;\nimport cn.ponfee.commons.export.SplitExcelExporter;\nimport cn.ponfee.commons.export.Table;\n\npublic class ExportTester2 {\n    static final ExecutorService EXECUTOR = ThreadPoolExecutors.builder()\n        .corePoolSize(4)\n        .maximumPoolSize(16)\n        .workQueue(new SynchronousQueue<>())\n        .keepAliveTimeSeconds(120)\n        .build();\n\n    @Test // 19.5\n    public void testExcel1() throws IOException {\n        AbstractDataExporter excel = new ExcelExporter();\n\n        Table table = new Table(\"a,b,c,d,e\".split(\",\"));\n        table.setCaption(\"title\");\n        int n = 10;\n        AtomicInteger count = new AtomicInteger(0);\n        Stopwatch watch = Stopwatch.createStarted();\n        for (int j = 0; j < n; j++) {\n            EXECUTOR.submit(()-> {\n                List<Object[]> data = new ArrayList<>();\n                for (int i = 0; i < 100000; i++) {\n                    data.add(new Object[] { \"1\", \"2\", \"3\", \"4\", \"5\" });\n                }\n                table.addRows(data);\n                if (count.incrementAndGet() == n) {\n                    table.toEnd();\n                }\n            });\n        }\n        System.out.println(\"***************\"+watch.stop());\n        watch.reset().start();\n        excel.setName(\"21321\");\n        excel.build(table);\n        IOUtils.write((byte[]) excel.export(), new FileOutputStream(ExportTester.baseDir+\"test11.xlsx\"));\n        excel.close();\n        System.out.println(watch.stop());\n    }\n\n\n    @Test // 18.5\n    public void testExcel2() throws IOException {\n        AbstractDataExporter excel = new ExcelExporter();\n\n        Table table = new Table(\"a,b,c,d,e\".split(\",\"));\n        table.setCaption(\"title\");\n        int n = 10;\n        AtomicInteger count = new AtomicInteger(0);\n        Stopwatch watch = Stopwatch.createStarted();\n        for (int j = 0; j < n; j++) {\n            EXECUTOR.submit(()-> {\n                for (int i = 0; i < 100000; i++) {\n                    table.addRow(new Object[] { \"1\", \"2\", \"3\", \"4\", \"5\" });\n                }\n                if (count.incrementAndGet() == n) {\n                    table.toEnd();\n                }\n            });\n        }\n        System.out.println(\"================\"+watch.stop());\n        watch.reset().start();\n        excel.setName(\"21321\");\n        excel.build(table);\n        IOUtils.write((byte[]) excel.export(), new FileOutputStream(ExportTester.baseDir+\"test22.xlsx\"));\n        excel.close();\n        System.out.println(watch.stop());\n    }\n\n    @Test // 11\n    public void testCsv1() throws IOException {\n        CsvFileExporter excel = new CsvFileExporter(ExportTester.baseDir+\"test.csv\", true);\n\n        Table table = new Table(\"中,文,b,o,m\".split(\",\"));\n        table.setCaption(\"title\");\n        int n = 100;\n        AtomicInteger count = new AtomicInteger(0);\n        Stopwatch watch = Stopwatch.createStarted();\n        for (int j = 0; j < n; j++) {\n            EXECUTOR.submit(()-> {\n                for (int i = 0; i < 100000; i++) {\n                    table.addRow(new Object[] { \"1\", \"2\", \"3\", \"4\", \"5\" });\n                }\n                if (count.incrementAndGet() == n) {\n                    table.toEnd();\n                }\n            });\n        }\n        System.out.println(\"================\"+watch.stop());\n        watch.reset().start();\n        excel.setName(\"21321\");\n        excel.build(table);\n        excel.close();\n        System.out.println(watch.stop());\n    }\n\n    @Test // 1.485 min\n    public void testSplitExcel() throws IOException {\n        Table table = new Table(\"a,b,c,d,e\".split(\",\"));\n        table.setCaption(\"title\");\n        int n = 100;\n        AtomicInteger count = new AtomicInteger(0);\n        Stopwatch watch = Stopwatch.createStarted();\n        for (int j = 0; j < n; j++) {\n            EXECUTOR.submit(()-> {\n                for (int i = 0; i < 100000; i++) {\n                    table.addRow(new Object[] { \"1111111111111111111111111111\", \"2\", \"3\", \"4\", \"5\" });\n                }\n                if (count.incrementAndGet() == n) {\n                    table.toEnd();\n                }\n            });\n        }\n\n        SplitExcelExporter excel = new SplitExcelExporter(65537,ExportTester.baseDir+\"test_excel_\", EXECUTOR);\n        excel.setName(\"21321\");\n        System.out.println(\"================\"+watch.stop());\n        watch.reset().start();\n        excel.build(table);\n        excel.close();\n        System.out.println(watch.stop());\n    }\n\n    @Test // 1.485 min\n    public void testSplitCsv() throws IOException {\n        Table table = new Table(\"中国,人,c,d,e\".split(\",\"));\n        table.setCaption(\"title\");\n        int n = 10;\n        AtomicInteger count = new AtomicInteger(0);\n        Stopwatch watch = Stopwatch.createStarted();\n        for (int j = 0; j < n; j++) {\n            EXECUTOR.submit(()-> {\n                for (int i = 0; i < 100000; i++) {\n                    table.addRow(new Object[] { \"1\", \"2\", \"3\", \"4\", \"5\" });\n                }\n                if (count.incrementAndGet() == n) {\n                    table.toEnd();\n                }\n            });\n        }\n\n        SplitCsvFileExporter csv = new SplitCsvFileExporter(65537,ExportTester.baseDir+\"test_csv_\", true, EXECUTOR);\n        System.out.println(\"================\"+watch.stop());\n        watch.reset().start();\n        csv.build(table);\n        csv.close();\n        System.out.println(watch.stop());\n    }\n}\n\n"
  },
  {
    "path": "src/test/java/test/extract/ExampleEventUserModel.java",
    "content": "package test.extract;\n\nimport java.io.InputStream;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport org.apache.poi.ooxml.util.SAXHelper;\nimport org.apache.poi.openxml4j.opc.OPCPackage;\nimport org.apache.poi.openxml4j.opc.PackageAccess;\nimport org.apache.poi.xssf.eventusermodel.XSSFReader;\nimport org.apache.poi.xssf.model.SharedStrings;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.InputSource;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.XMLReader;\nimport org.xml.sax.helpers.DefaultHandler;\n\npublic class ExampleEventUserModel {\n    public void processWorkebook(String filename) throws Exception {\n        try (OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ)) {\n            XSSFReader r = new XSSFReader(pkg);\n            SharedStrings sst = r.getSharedStringsTable();\n            XMLReader parser = SAXHelper.newXMLReader();\n            parser.setContentHandler(new SheetHandler(sst)); // custome handler\n            for (Iterator<InputStream> iter = r.getSheetsData(); iter.hasNext();) {\n                InputStream sheet = iter.next();\n                InputSource sheetSource = new InputSource(sheet);\n                parser.parse(sheetSource);\n                sheet.close();\n            }\n        }\n    }\n\n    /** \n     * See org.xml.sax.helpers.DefaultHandler javadocs 重写 startElement characters endElements方法 \n     */\n    private static class SheetHandler extends DefaultHandler {\n        private SharedStrings sst;\n        private String lastContents;\n        private boolean nextIsString; //是否为string格式标识\n        private final LruCache<Integer, String> lruCache = new LruCache<>(60);\n        /*private int sheetIndex = -1;\n        private int curRow = 0;\n        private int curCol = 0;\n        private List<String> rowlist = new ArrayList<String>(); */\n\n        /**\n         * 缓存\n         * @author Administrator\n         *\n         * @param <A>\n         * @param <B>\n         */\n        private static class LruCache<A, B> extends LinkedHashMap<A, B> {\n            private final int maxEntries;\n\n            public LruCache(final int maxEntries) {\n                super(maxEntries + 1, 1.0f, true);\n                this.maxEntries = maxEntries;\n            }\n\n            @Override\n            protected boolean removeEldestEntry(final Map.Entry<A, B> eldest) {\n                return super.size() > maxEntries;\n            }\n        }\n\n        private SheetHandler(SharedStrings sst) {\n            this.sst = sst;\n        }\n\n        /**  \n         * 该方法自动被调用，每读一行调用一次，在方法中写自己的业务逻辑即可 \n         * @param sheetIndex 工作簿序号 \n         * @param curRow 处理到第几行 \n         * @param rowList 当前数据行的数据集合 \n         */\n        /* public void optRow(int sheetIndex, int curRow, List<String> rowList) {   \n            String temp = \"\";   \n            for(String str : rowList) {   \n                temp += str + \"_\";   \n            } \n            this.rowlist.clear();\n            this.curRow++;\n            this.curCol=0;\n            System.out.println(temp);   \n        } */\n\n        @Override\n        public void startElement(String uri, String localName, String name,\n            Attributes attributes) throws SAXException {\n            // c => cell 代表单元格\n            if (name.equals(\"c\")) {\n                // Print the cell reference\n                //获取单元格的位置，如A1,B1\n                System.out.print(attributes.getValue(\"r\") + \" - \");\n                // Figure out if the value is an index in the SST 如果下一个元素是 SST 的索引，则将nextIsString标记为true\n                //单元格类型\n                String cellType = attributes.getValue(\"t\");\n                //cellType值 s:字符串 b:布尔 e:错误处理\n                if (cellType != null && cellType.equals(\"s\")) {\n                    //标识为true 交给后续endElement处理\n                    nextIsString = true;\n                } else {\n                    nextIsString = false;\n                }\n            }\n            // Clear contents cache\n            lastContents = \"\";\n        }\n\n        /**\n         * 得到单元格对应的索引值或是内容值\n         * 如果单元格类型是字符串、INLINESTR、数字、日期，lastIndex则是索引值\n         * 如果单元格类型是布尔值、错误、公式，lastIndex则是内容值\n         */\n        @Override\n        public void characters(char[] ch, int start, int length)\n            throws SAXException {\n            lastContents += new String(ch, start, length);\n        }\n\n        @Override\n        public void endElement(String uri, String localName, String name)\n            throws SAXException {\n            // Process the last contents as required.\n            // Do now, as characters() may be called more than once\n            if (nextIsString) {\n                int idx = Integer.parseInt(lastContents);\n                lastContents = lruCache.get(idx);\n                //如果内容为空 或者Cache中存在相同key 不保存到Cache中\n                if (lastContents == null && !lruCache.containsKey(idx)) {\n                    lastContents = sst.getItemAt(idx).getString();\n                    lruCache.put(idx, lastContents);\n                }\n                nextIsString = false;\n            }\n\n            // v => contents of a cell\n            // Output after we've seen the string contents\n            if (name.equals(\"v\")) {\n                System.out.println(lastContents);\n                //rowlist.add(curCol++,lastContents);\n            } else {\n                //如果标签名称为 row , 已到行尾\n                if (name.equals(\"row\")) {\n                    //optRow(sheetIndex, curRow, rowlist);\n                    System.out.println(lruCache);\n                    lruCache.clear();\n                }\n            }\n        }\n\n    }\n\n    public static void main(String[] args) throws Exception {\n        new ExampleEventUserModel().processWorkebook(\"d:/abc1.xlsx\");\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/extract/ExcelExtractorTest.java",
    "content": "package test.extract;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.junit.Test;\n\nimport cn.ponfee.commons.extract.DataExtractor;\nimport cn.ponfee.commons.extract.DataExtractorBuilder;\n\n/**\n * 性能：Path > File > Input\n * @author Ponfee\n */\npublic class ExcelExtractorTest {\n\n    @Test\n    public void testXLS1() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"e:\\\\writeTest2.xls\")\n            .streaming(true)\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\", \"f\" }).build();\n        et.extract((n, d) -> {\n            System.out.println(Arrays.toString((String[])d));\n        });\n    }\n\n    @Test\n    public void testXLS2() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"e:\\\\writeTest2.xls\")\n            .streaming(false)\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\", \"f\" }).build();\n        et.extract((n, d) -> {\n            System.out.println(Arrays.toString((String[])d));\n        });\n    }\n\n    @Test\n    public void testXLSX1() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"e:\\\\mergeTest.xlsx\")\n            .streaming(false)\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\", \"f\" }).build();\n        et.extract((n, d) -> {\n            System.out.println(Arrays.toString((String[])d));\n        });\n    }\n    \n    @Test\n    public void testXLSX2() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"e:\\\\mergeTest.xlsx\")\n            .streaming(true)\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\", \"f\" }).build();\n        et.extract((n, d) -> {\n            System.out.println(Arrays.toString((String[])d));\n        });\n    }\n    \n    @Test\n    public void testFile() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(new File(\"D:\\\\test\\\\test_excel_14.xlsx\"))\n            /*.headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\" })*/.build();\n        et.extract((n, d) -> {\n            System.out.println(Arrays.toString((String[])d));\n        });\n    }\n    \n    @Test\n    public void testInput() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(new FileInputStream(\"D:\\\\test\\\\test_excel_14.xlsx\"), \"test_excel_16.xlsx\", null)\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\" }).build();\n        et.extract((n, d) -> {\n            if (n == 0) {\n                System.out.println(Arrays.toString((String[])d));\n            }\n            if (n == 1) {\n                System.out.println(Arrays.toString((String[])d));\n            }\n            System.out.println(Arrays.toString((String[])d));\n        });\n    }\n    \n    @Test\n    public void test1() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"e:\\\\data_expert_temp.xls\")\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\" }).build();\n        et.extract((n, d) -> {\n            System.out.println(Arrays.toString((String[])d));\n        });\n    }\n    \n    @Test\n    public void test2() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"E:\\\\test.xlsx\")\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\" }).build();\n        et.extract((n, d) -> {\n            System.out.println(String.join(\"|\",(String[])d));\n        });\n    }\n    \n    @Test\n    public void test3() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"src/test/java/test/extract/advices_export.xls\")\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\"}).build();\n        et.extract((n, d) -> {\n            System.out.println(String.join(\"|\",(String[])d));\n        });\n    }\n\n    @Test\n    public void testCsvPath() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"E:\\\\test.csv\")\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\" }).build();\n        et.extract((n, d) -> {\n            if (n < 10)\n            System.out.println(Arrays.toString((String[])d));\n        });\n    }\n    \n    @Test\n    public void testCsv1() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"E:\\\\test.csv\")\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\" }).build();\n        et.extract((n, d) -> {\n            System.out.println(Arrays.toString((String[])d));\n        });\n    }\n    \n    @Test\n    public void testCsv2() throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(\"E:\\\\test.csv\")\n            .headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\" }).build();\n        et.extract(1).forEach(e -> System.out.println(Arrays.toString(e)));\n    }\n    \n    // ------------------------------------------------------\n    @Test\n    public void test6() throws FileNotFoundException, IOException {\n        //test(\"E:\\\\test20.xlsx\", false); // 9.1 s\n        //test(\"E:\\\\test100.xlsx\", true); // 7.8\n        //test(\"E:\\\\writeTest.xls\", false); // 2.6\n        test(\"E:\\\\writeTest.xls\", true); // 2.0\n    }\n\n    private void test(String filename, boolean streaming) throws FileNotFoundException, IOException {\n        DataExtractor et = DataExtractorBuilder.newBuilder(filename)\n            .streaming(streaming).headers(new String[] { \"a\", \"b\", \"c\", \"d\", \"e\" }).build();\n        \n        AtomicInteger count = new AtomicInteger();\n        et.extract((n, d) -> {\n            if (n < 10) {\n                System.out.println(Arrays.toString((String[])d));\n            }\n            count.incrementAndGet();\n        });\n        System.out.println(count.get());\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/extract/TestHSSFStreaming.java",
    "content": "package test.extract;\n\nimport java.util.Iterator;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadPoolExecutor;\n\nimport org.apache.poi.ss.usermodel.Cell;\nimport org.apache.poi.ss.usermodel.Row;\nimport org.apache.poi.ss.usermodel.Sheet;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.concurrent.ThreadPoolExecutors;\nimport cn.ponfee.commons.extract.streaming.xls.HSSFStreamingReader;\nimport cn.ponfee.commons.extract.streaming.xls.HSSFStreamingRow;\nimport cn.ponfee.commons.extract.streaming.xls.HSSFStreamingSheet;\nimport cn.ponfee.commons.extract.streaming.xls.HSSFStreamingWorkbook;\n\npublic class TestHSSFStreaming {\n\n    static ExecutorService exec = Executors.newFixedThreadPool(4);\n\n    @Test\n    public void test1() throws InterruptedException {\n        //String file = \"e:/data_expert.xls\";\n        HSSFStreamingWorkbook wb = HSSFStreamingReader.create(40, 0).open(\"src/test/java/test/extract/writeTest2.xls\", exec);\n        HSSFStreamingSheet sheet = (HSSFStreamingSheet) wb.getSheetAt(0);\n        for (Row row : sheet) {\n            System.out.print(row.getRowNum() + \", \" + ((HSSFStreamingRow) row).getRowOrder() + \":  \");\n            for (Cell cell : row) {\n                System.out.print(cell == null ? \"null, \" : cell.getStringCellValue() + \", \");\n            }\n            System.out.println();\n        }\n        System.out.println();\n        for (Iterator<Sheet> iter = wb.iterator(); iter.hasNext();) {\n            HSSFStreamingSheet sst = (HSSFStreamingSheet) iter.next();\n            System.out.println(\"SheetIndex: \"+sst.getSheetIndex()+\"，SheetName: \"+sst.getSheetName()+\"，cheRowCount: \"+sheet.getCacheRowCount());\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/extract/XLSEventTest.java",
    "content": "package test.extract;\n\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport org.apache.poi.hssf.eventusermodel.HSSFEventFactory;\nimport org.apache.poi.hssf.eventusermodel.HSSFListener;\nimport org.apache.poi.hssf.eventusermodel.HSSFRequest;\nimport org.apache.poi.hssf.record.BOFRecord;\nimport org.apache.poi.hssf.record.BoundSheetRecord;\nimport org.apache.poi.hssf.record.ExtSSTRecord;\nimport org.apache.poi.hssf.record.LabelSSTRecord;\nimport org.apache.poi.hssf.record.NumberRecord;\nimport org.apache.poi.hssf.record.Record;\nimport org.apache.poi.hssf.record.RowRecord;\nimport org.apache.poi.hssf.record.SSTRecord;\nimport org.apache.poi.poifs.filesystem.POIFSFileSystem;\n\n/**\n * \n * @author Ponfee\n */\n/**\n * This example shows how to use the event API for reading a file.\n */\npublic class XLSEventTest\n    implements HSSFListener {\n    private SSTRecord sstrec;\n\n    /**\n     * This method listens for incoming records and handles them as required.\n     * @param record    The record that was found while reading.\n     */\n    @Override\n    public void processRecord(Record record) {\n        switch (record.getSid()) {\n            // the BOFRecord can represent either the beginning of a sheet or the workbook\n            case BOFRecord.sid:\n                BOFRecord bof = (BOFRecord) record;\n                if (bof.getType() == BOFRecord.TYPE_WORKBOOK) {\n                    System.out.println(\"Encountered workbook\");\n                    // assigned to the class level member\n                } else if (bof.getType() == BOFRecord.TYPE_WORKSHEET) {\n                    System.out.println(\"Encountered sheet reference\");\n                } else {\n                    System.out.println(\"others \"+bof);\n                }\n                break;\n            case BoundSheetRecord.sid:\n                BoundSheetRecord bsr = (BoundSheetRecord) record;\n                System.out.println(\"New sheet named: \" + bsr.getSheetname());\n                break;\n            case RowRecord.sid:\n                RowRecord rowrec = (RowRecord) record;\n                System.out.println(\"Row found \"+rowrec.getRowNumber()+\", first column at \" + rowrec.getFirstCol() + \" last column at \" + rowrec.getLastCol());\n                break;\n            // SSTRecords store a array of unique strings used in Excel.\n            case SSTRecord.sid:\n                sstrec = (SSTRecord) record;\n                for (int k = 0; k < sstrec.getNumUniqueStrings(); k++) {\n                    System.out.println(\"String table value \" + k + \" = \" + sstrec.getString(k));\n                }\n                break;\n            case NumberRecord.sid:\n                NumberRecord numrec = (NumberRecord) record;\n                System.out.println(\"Cell found with value \" + numrec.getValue() + \" at row \" + numrec.getRow() + \" and column \" + numrec.getColumn());\n                break;\n            case LabelSSTRecord.sid:\n                LabelSSTRecord lrec = (LabelSSTRecord) record;\n                System.out.println(\"String cell found with value \" + sstrec.getString(lrec.getSSTIndex())+ \" at row \" + lrec.getRow() + \" and column \" + lrec.getColumn());\n                break;\n            case ExtSSTRecord.sid:\n                ExtSSTRecord extsstrec =  (ExtSSTRecord) record;\n                for (int k = 0; k < extsstrec.getRecordSize(); k++) {\n                }\n            default:\n                //System.out.println(\"==others\"+record.getClass()+\"---\"+record.getSid());\n                // discard;\n        }\n    }\n\n    /**\n     * Read an excel file and spit out what we find.\n     *\n     * @param args      Expect one argument that is the file to read.\n     * @throws IOException  When there is an error processing the file.\n     */\n    public static void main(String[] args) throws IOException {\n        // create a new file input stream with the input file specified\n        // at the command line\n        FileInputStream fin = new FileInputStream(\"src/test/java/test/extract/advices_export.xls\");\n        // create a new org.apache.poi.poifs.filesystem.Filesystem\n        POIFSFileSystem poifs = new POIFSFileSystem(fin);\n        // get the Workbook (excel part) stream in a InputStream\n        InputStream din = poifs.createDocumentInputStream(\"Workbook\");\n        // construct out HSSFRequest object\n        HSSFRequest req = new HSSFRequest();\n        // lazy listen for ALL records with the listener shown above\n        req.addListenerForAllRecords(new XLSEventTest());\n        // create our event factory\n        HSSFEventFactory factory = new HSSFEventFactory();\n        // process our events based on the document input stream\n        factory.processEvents(req, din);\n        // once all the events are processed close our file input stream\n        poifs.close();\n        fin.close();\n        // and our document input stream (don't want to leak these!)\n        din.close();\n        System.out.println(\"done.\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/extract/XLSX2CSV.java",
    "content": "package test.extract;\n\n/* ====================================================================\nLicensed to the Apache Software Foundation (ASF) under one or more\ncontributor license agreements.  See the NOTICE file distributed with\nthis work for additional information regarding copyright ownership.\nThe 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\nthe License.  You may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n==================================================================== */\n\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintStream;\n\nimport javax.xml.parsers.ParserConfigurationException;\n\nimport org.apache.poi.ooxml.util.SAXHelper;\nimport org.apache.poi.openxml4j.exceptions.OpenXML4JException;\nimport org.apache.poi.openxml4j.opc.OPCPackage;\nimport org.apache.poi.openxml4j.opc.PackageAccess;\nimport org.apache.poi.ss.usermodel.DataFormatter;\nimport org.apache.poi.ss.util.CellAddress;\nimport org.apache.poi.ss.util.CellReference;\nimport org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;\nimport org.apache.poi.xssf.eventusermodel.XSSFReader;\nimport org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;\nimport org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;\nimport org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;\nimport org.apache.poi.xssf.model.SharedStrings;\nimport org.apache.poi.xssf.model.Styles;\nimport org.apache.poi.xssf.model.StylesTable;\nimport org.apache.poi.xssf.usermodel.XSSFComment;\nimport org.xml.sax.ContentHandler;\nimport org.xml.sax.InputSource;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.XMLReader;\n\n/**\n* A rudimentary XLSX -> CSV processor modeled on the\n* POI sample program XLS2CSVmra from the package\n* org.apache.poi.hssf.eventusermodel.examples.\n* As with the HSSF version, this tries to spot missing\n*  rows and cells, and output empty entries for them.\n* <p>\n* Data sheets are read using a SAX parser to keep the\n* memory footprint relatively small, so this should be\n* able to read enormous workbooks.  The styles table and\n* the shared-string table must be kept in memory.  The\n* standard POI styles table class is used, but a custom\n* (read-only) class is used for the shared string table\n* because the standard POI SharedStringsTable grows very\n* quickly with the number of unique strings.\n* <p>\n* For a more advanced implementation of SAX event parsing\n* of XLSX files, see {@link XSSFEventBasedExcelExtractor}\n* and {@link XSSFSheetXMLHandler}. Note that for many cases,\n* it may be possible to simply use those with a custom \n* {@link SheetContentsHandler} and no SAX code needed of\n* your own!\n*/\npublic class XLSX2CSV {\n /**\n  * Uses the XSSF Event SAX helpers to do most of the work\n  *  of parsing the Sheet XML, and outputs the contents\n  *  as a (basic) CSV.\n  */\n private class SheetToCSV implements SheetContentsHandler {\n     private boolean firstCellOfRow;\n     private int currentRow = -1;\n     private int currentCol = -1;\n     \n     private void outputMissingRows(int number) {\n         for (int i=0; i<number; i++) {\n             for (int j=0; j<minColumns; j++) {\n                 output.append(',');\n             }\n             output.append('\\n');\n         }\n     }\n\n     @Override\n     public void startRow(int rowNum) {\n         // If there were gaps, output the missing rows\n         outputMissingRows(rowNum-currentRow-1);\n         // Prepare for this row\n         firstCellOfRow = true;\n         currentRow = rowNum;\n         currentCol = -1;\n     }\n\n     @Override\n     public void endRow(int rowNum) {\n         // Ensure the minimum number of columns\n         for (int i=currentCol; i<minColumns; i++) {\n             output.append(',');\n         }\n         output.append('\\n');\n     }\n\n     @Override\n     public void cell(String cellReference, String formattedValue,\n             XSSFComment comment) {\n         if (firstCellOfRow) {\n             firstCellOfRow = false;\n         } else {\n             output.append(',');\n         }\n\n         // gracefully handle missing CellRef here in a similar way as XSSFCell does\n         if(cellReference == null) {\n             cellReference = new CellAddress(currentRow, currentCol).formatAsString();\n         }\n\n         // Did we miss any cells?\n         int thisCol = (new CellReference(cellReference)).getCol();\n         int missedCols = thisCol - currentCol - 1;\n         for (int i=0; i<missedCols; i++) {\n             output.append(',');\n         }\n         currentCol = thisCol;\n         \n         // Number or string?\n         try {\n             //noinspection ResultOfMethodCallIgnored\n             Double.parseDouble(formattedValue);\n             output.append(formattedValue);\n         } catch (NumberFormatException e) {\n             output.append('\"');\n             output.append(formattedValue);\n             output.append('\"');\n         }\n     }\n }\n\n\n ///////////////////////////////////////\n\n private final OPCPackage xlsxPackage;\n\n /**\n  * Number of columns to read starting with leftmost\n  */\n private final int minColumns;\n\n /**\n  * Destination for data\n  */\n private final PrintStream output;\n\n /**\n  * Creates a new XLSX -> CSV examples\n  *\n  * @param pkg        The XLSX package to process\n  * @param output     The PrintStream to output the CSV to\n  * @param minColumns The minimum number of columns to output, or -1 for no minimum\n  */\n public XLSX2CSV(OPCPackage pkg, PrintStream output, int minColumns) {\n     this.xlsxPackage = pkg;\n     this.output = output;\n     this.minColumns = minColumns;\n }\n\n /**\n  * Parses and shows the content of one sheet\n  * using the specified styles and shared-strings tables.\n  *\n  * @param styles The table of styles that may be referenced by cells in the sheet\n  * @param strings The table of strings that may be referenced by cells in the sheet\n  * @param sheetInputStream The stream to read the sheet-data from.\n\n  * @exception java.io.IOException An IO exception from the parser,\n  *            possibly from a byte stream or character stream\n  *            supplied by the application.\n  * @throws SAXException if parsing the XML data fails.\n  */\n public void processSheet(\n         Styles styles,\n         SharedStrings strings,\n         SheetContentsHandler sheetHandler, \n         InputStream sheetInputStream) throws IOException, SAXException {\n     DataFormatter formatter = new DataFormatter();\n     InputSource sheetSource = new InputSource(sheetInputStream);\n     try {\n         XMLReader sheetParser = SAXHelper.newXMLReader();\n         ContentHandler handler = new XSSFSheetXMLHandler(\n               styles, null, strings, sheetHandler, formatter, false);\n         sheetParser.setContentHandler(handler);\n         sheetParser.parse(sheetSource);\n      } catch(ParserConfigurationException e) {\n         throw new RuntimeException(\"SAX parser appears to be broken - \" + e.getMessage());\n      }\n }\n\n /**\n  * Initiates the processing of the XLS workbook file to CSV.\n  *\n  * @throws IOException If reading the data from the package fails.\n  * @throws SAXException if parsing the XML data fails.\n  */\n public void process() throws IOException, OpenXML4JException, SAXException {\n     ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(this.xlsxPackage);\n     XSSFReader xssfReader = new XSSFReader(this.xlsxPackage);\n     StylesTable styles = xssfReader.getStylesTable();\n     XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();\n     int index = 0;\n     while (iter.hasNext()) {\n         try (InputStream stream = iter.next()) {\n             String sheetName = iter.getSheetName();\n             this.output.println();\n             this.output.println(sheetName + \" [index=\" + index + \"]:\");\n             processSheet(styles, strings, new SheetToCSV(), stream);\n         }\n         ++index;\n     }\n }\n\n public static void main(String[] args) throws Exception {\n     File xlsxFile = new File(\"d:/test/test_excel_3.xlsx\");\n     if (!xlsxFile.exists()) {\n         System.err.println(\"Not found or not a file: \" + xlsxFile.getPath());\n         return;\n     }\n\n     int minColumns = -1;\n     if (args.length >= 2)\n         minColumns = Integer.parseInt(args[1]);\n\n     // The package open is instantaneous, as it should be.\n     try (OPCPackage p = OPCPackage.open(xlsxFile.getPath(), PackageAccess.READ)) {\n         XLSX2CSV xlsx2csv = new XLSX2CSV(p, System.out, minColumns);\n         xlsx2csv.process();\n     }\n }\n}\n"
  },
  {
    "path": "src/test/java/test/extract/XLSXEventTest.java",
    "content": "package test.extract;\n\nimport java.io.InputStream;\nimport java.util.Iterator;\n\nimport org.apache.poi.ooxml.util.SAXHelper;\nimport org.apache.poi.openxml4j.opc.OPCPackage;\nimport org.apache.poi.xssf.eventusermodel.XSSFReader;\nimport org.apache.poi.xssf.model.SharedStrings;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.ContentHandler;\nimport org.xml.sax.InputSource;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.XMLReader;\nimport org.xml.sax.helpers.DefaultHandler;\n\nimport javax.xml.parsers.ParserConfigurationException;\n\npublic class XLSXEventTest {\n    public void processOneSheet(String filename) throws Exception {\n        OPCPackage pkg = OPCPackage.open(filename);\n        XSSFReader r = new XSSFReader( pkg );\n        SharedStrings sst = r.getSharedStringsTable();\n\n        XMLReader parser = fetchSheetParser(sst);\n\n        // To look up the Sheet Name / Sheet Order / rID,\n        //  you need to process the core Workbook stream.\n        // Normally it's of the form rId# or rSheet#\n        InputStream sheet2 = r.getSheet(\"rId1\");\n        InputSource sheetSource = new InputSource(sheet2);\n        parser.parse(sheetSource);\n        sheet2.close();\n    }\n\n    public void processAllSheets(String filename) throws Exception {\n        OPCPackage pkg = OPCPackage.open(filename);\n        XSSFReader r = new XSSFReader( pkg );\n        SharedStrings sst = r.getSharedStringsTable();\n\n        XMLReader parser = fetchSheetParser(sst);\n\n        Iterator<InputStream> sheets = r.getSheetsData();\n        while(sheets.hasNext()) {\n            System.out.println(\"Processing new sheet:\\n\");\n            InputStream sheet = sheets.next();\n            InputSource sheetSource = new InputSource(sheet);\n            parser.parse(sheetSource);\n            sheet.close();\n            System.out.println(\"\");\n        }\n    }\n\n    public XMLReader fetchSheetParser(SharedStrings sst) throws SAXException, ParserConfigurationException {\n        XMLReader parser = SAXHelper.newXMLReader();\n        ContentHandler handler = new SheetHandler(sst);\n        parser.setContentHandler(handler);\n        return parser;\n    }\n\n    /**\n     * See org.xml.sax.helpers.DefaultHandler javadocs\n     */\n    private static class SheetHandler extends DefaultHandler {\n        private SharedStrings sst;\n        private String lastContents;\n        private boolean nextIsString;\n\n        private SheetHandler(SharedStrings sst) {\n            this.sst = sst;\n        }\n\n        public void startElement(String uri, String localName, String name,\n                                 Attributes attributes) throws SAXException {\n            // c => cell\n            if(name.equals(\"c\")) {\n                // Print the cell reference\n                System.out.print(attributes.getValue(\"r\") + \" - \");\n                // Figure out if the value is an index in the SST\n                String cellType = attributes.getValue(\"t\");\n                if(cellType != null && cellType.equals(\"s\")) {\n                    nextIsString = true;\n                } else {\n                    nextIsString = false;\n                }\n            }\n            // Clear contents cache\n            lastContents = \"\";\n        }\n\n        public void endElement(String uri, String localName, String name)\n                throws SAXException {\n            // Process the last contents as required.\n            // Do now, as characters() may be called more than once\n            if(nextIsString) {\n                int idx = Integer.parseInt(lastContents);\n                lastContents = sst.getItemAt(idx).getString();\n                nextIsString = false;\n            }\n\n            // v => contents of a cell\n            // Output after we've seen the string contents\n            if(name.equals(\"v\")) {\n                System.out.println(lastContents);\n            }\n        }\n\n        public void characters(char[] ch, int start, int length) {\n            lastContents += new String(ch, start, length);\n        }\n    }\n\n    public static void main(String[] args) throws Exception {\n        XLSXEventTest example = new XLSXEventTest();\n        //example.processOneSheet(\"d:/test/test_excel_3.xlsx\");\n        example.processAllSheets(\"d:/test/test_excel_3.xlsx\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/http/HttpClientUtils.java",
    "content": "package test.http;\n\nimport java.io.IOException;\nimport java.net.SocketException;\nimport java.nio.charset.Charset;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nimport org.apache.http.HttpStatus;\nimport org.apache.http.NameValuePair;\nimport org.apache.http.NoHttpResponseException;\nimport org.apache.http.client.HttpRequestRetryHandler;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.entity.UrlEncodedFormEntity;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClients;\nimport org.apache.http.impl.conn.PoolingHttpClientConnectionManager;\nimport org.apache.http.message.BasicNameValuePair;\nimport org.apache.http.protocol.HttpContext;\nimport org.apache.http.util.EntityUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport cn.ponfee.commons.json.Jsons;\n\npublic class HttpClientUtils {\n    private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);\n    private final static Charset UTF8 = Charset.forName(\"UTF-8\");\n\n    public static final CloseableHttpClient HTTP_CLIENT;\n    static {\n        // 初始化线程池\n        RequestConfig params = RequestConfig.custom().setConnectTimeout(3000).setConnectionRequestTimeout(1000)\n                                            .setSocketTimeout(4000).setExpectContinueEnabled(true).build();\n\n        PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager();\n        pccm.setMaxTotal(300); // 连接池最大并发连接数\n        pccm.setDefaultMaxPerRoute(50); // 单路由最大并发数\n        HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {\n            @Override\n            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {\n                if (executionCount > 1) {\n                    return false; // 重试1次,从1开始\n                } else if (exception instanceof NoHttpResponseException) {\n                    logger.info(\"[NoHttpResponseException has retry request:\" + context.toString() + \"][executionCount:\" + executionCount + \"]\");\n                    return true;\n                } else if (exception instanceof SocketException) {\n                    logger.info(\"[SocketException has retry request:\" + context.toString() + \"][executionCount:\" + executionCount + \"]\");\n                    return true;\n                } else {\n                    return false;\n                }\n            }\n        };\n        HTTP_CLIENT = HttpClients.custom().setConnectionManager(pccm).setDefaultRequestConfig(params)\n                                 .setRetryHandler(retryHandler).build();\n    }\n\n    public static String post(String url, Map<String, ?> params, Integer connReqTimeout, \n                              Integer connTimeout, Integer socketTimeout) {\n        List<NameValuePair> nvps = new ArrayList<NameValuePair>();\n        if (params != null && !params.isEmpty()) {\n            for (Entry<String, ?> entry : params.entrySet()) {\n                nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));\n            }\n        }\n\n        logger.info(\"post-req:url:{},param:{}\", url, Jsons.NORMAL.string(params));\n        HttpPost post = new HttpPost(url);\n        RequestConfig.Builder builder = RequestConfig.custom();\n        if (connReqTimeout != null && connReqTimeout > 0) {\n            builder.setConnectionRequestTimeout(connReqTimeout);\n        }\n        if (connTimeout != null && connTimeout > 0) {\n            builder.setConnectTimeout(connTimeout);\n        }\n        if (socketTimeout != null && socketTimeout > 0) {\n            builder.setSocketTimeout(socketTimeout);\n        }\n        post.setConfig(builder.build());\n        post.setEntity(new UrlEncodedFormEntity(nvps, UTF8));\n\n        CloseableHttpResponse response = null;\n        try {\n            response = HTTP_CLIENT.execute(post);\n            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {\n                return EntityUtils.toString(response.getEntity(), UTF8);\n            } else {\n                return null;\n            }\n        } catch (IOException e) {\n            logger.error(\"[HttpClientUtils][invoke][method:\" + post.getMethod() + \" URI:\" + post.getURI() + \"] is request exception\", e);\n            return null;\n        } finally {\n            if (response != null) {\n                try {\n                    response.close();\n                } catch (IOException e) {\n                    logger.error(\"[HttpClientUtils][invoke][method:\" + post.getMethod() + \" URI:\" + post.getURI() + \"] is closed exception\", e);\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/http/HttpParamsTest.java",
    "content": "package test.http;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport cn.ponfee.commons.http.ContentType;\nimport cn.ponfee.commons.http.Http;\nimport cn.ponfee.commons.http.HttpParams;\nimport cn.ponfee.commons.util.ObjectUtils;\n\npublic class HttpParamsTest {\n\n    @Test\n    public void test1() {\n        String str = \"service=http%3A%2F%2Flocalhost%2Fcas-client%2F&test=fds中文a\";\n        System.out.println(\"\\n=============parseParams==============\");\n        System.out.println(ObjectUtils.toString(HttpParams.parseParams(str, \"UTF-8\")));\n\n        System.out.println(\"\\n=============parseUrlParams==============\");\n        str = \"http://localhost:8080/test?service=http%3A%2F%2Flocalhost%2Fcas-client%2F&test=fds中文a\";\n        System.out.println(ObjectUtils.toString(HttpParams.parseUrlParams(str)));\n\n        System.out.println(\"\\n=============buildParams==============\");\n        Map<String, Object> map = new LinkedHashMap<String, Object>();\n        map.put(\"a\", new String[] { \"1\" });\n        map.put(\"b\", new String[] { \"2\" });\n        map.put(\"merReserved\", new String[] { \"{a=1&b=2}\" });\n        String queryString = HttpParams.buildParams(map, \"utf-8\");\n        System.out.println(queryString);\n        System.out.println(ObjectUtils.toString(HttpParams.parseParams(queryString, \"utf-8\")));\n\n        System.out.println(\"\\n=============buildUrlPath==============\");\n        System.out.println(HttpParams.buildUrlPath(\"/index.html\", \"utf-8\", map));\n\n        System.out.println(\"\\n=============buildForm==============\");\n        System.out.println(HttpParams.buildForm(\"http://localhost:8080\", map));\n    }\n\n    @Test\n    public void test2() {\n        String url = \"http://10.118.58.74:8000/open/api/test?a=1=32=14=12=4=3214=2&abcdef&\" + Math.random();\n        System.out.println(ObjectUtils.toString(HttpParams.parseUrlParams(url, \"UTF-8\")));\n    }\n\n    @Test\n    public void test3() {\n        System.out.println(HttpParams.buildUrlPath(\"url\", \"UTF-8\", \"a\", \"1\",\"b\",\"2\"));\n    }\n\n    @Test\n    public void test4() {\n        String soap = \n            \"<soapenv:Envelope xmlns:soapenv=\\\"http://schemas.xmlsoap.org/soap/envelope/\\\" xmlns:ns=\\\"http://WebXml.com.cn/\\\">\\n\" + \n            \"  <soapenv:Body>\\n\" + \n            \"    <ns:getCountryCityByIp>\\n\" + \n            \"      <ns:theIpAddress>119.139.199.75</ns:theIpAddress>\\n\" + \n            \"    </ns:getCountryCityByIp>\\n\" + \n            \"  </soapenv:Body>\\n\" + \n            \"</soapenv:Envelope>\";\n\n        //soap = HttpParams.buildSoap(\"getCountryCityByIp\", \"http://WebXml.com.cn/\", ImmutableMap.of(\"theIpAddress\", \"119.139.199.75\"));\n        System.out.println(soap);\n\n        String resp = Http.post(\"http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx\").contentType(ContentType.TEXT_XML).data(soap).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void test5() {\n        String soap = \n            \"<soapenv:Envelope xmlns:soapenv=\\\"http://schemas.xmlsoap.org/soap/envelope/\\\" xmlns:ns=\\\"http://service.screen.ddt.sf.com/\\\">\\n\" + \n            \"  <soapenv:Body>\\n\" + \n            \"    <ns:testListJavaBean>\\n\" + \n            \"      <arg0>119.139.199.75</arg0>\\n\" + \n            \"    </ns:testListJavaBean>\\n\" + \n            \"  </soapenv:Body>\\n\" + \n            \"</soapenv:Envelope>\";\n\n        //soap = HttpParams.buildSoap(\"testListJavaBean\", \"http://service.screen.ddt.sf.com/\", ImmutableMap.of(\"arg0\", \"1\"));\n        System.out.println(soap);\n        String resp = Http.post(\"http://localhost:8009/ws/testScreen?wsdl\").contentType(ContentType.TEXT_XML).data(soap).request();\n        System.out.println(resp);\n    }\n    \n}\n"
  },
  {
    "path": "src/test/java/test/http/HttpPostTester.java",
    "content": "package test.http;\n\nimport java.io.IOException;\nimport java.net.SocketException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\n\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.HttpStatus;\nimport org.apache.http.NoHttpResponseException;\nimport org.apache.http.StatusLine;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.client.HttpRequestRetryHandler;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.entity.UrlEncodedFormEntity;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.impl.client.HttpClients;\nimport org.apache.http.impl.conn.PoolingHttpClientConnectionManager;\nimport org.apache.http.message.BasicNameValuePair;\nimport org.apache.http.protocol.HttpContext;\nimport org.apache.http.util.EntityUtils;\n\npublic class HttpPostTester {\n\n    public static String post(String reqURL, Map<String, String> params) throws Exception {\n        HttpPost httpPost = new HttpPost(reqURL);\n        if (params != null) {\n            List<BasicNameValuePair> nvps = new ArrayList<>();\n            Set<Entry<String, String>> paramEntrys = params.entrySet();\n            for (Entry<String, String> entry : paramEntrys) {\n                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));\n            }\n            httpPost.setEntity(new UrlEncodedFormEntity(nvps, \"utf-8\"));\n        }\n\n        httpPost.setHeader(\"User-Agent\", \"datagrand/datareport/java sdk v1.0.0\");\n        httpPost.setHeader(\"Content-Type\", \"application/x-www-form-urlencoded\");\n\n        // 设置默认时间 \n        RequestConfig config = RequestConfig.custom().setConnectTimeout(3000)\n                                            .setConnectionRequestTimeout(1000).setSocketTimeout(4000)\n                                            .setExpectContinueEnabled(true).build();\n        PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager();\n        pccm.setMaxTotal(300); // 连接池最大并发连接数\n        pccm.setDefaultMaxPerRoute(50); // 单路由最大并发数\n        HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {\n            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {\n                if (executionCount > 3) {\n                    return false;\n                }\n                if (exception instanceof NoHttpResponseException) {\n                    System.out.println(\"[NoHttpResponseException has retry request:\" + context.toString() \n                                     + \"][executionCount:\" + executionCount + \"]\");\n                    return true;\n                } else if (exception instanceof SocketException) {\n                    System.out.println(\"[SocketException has retry request:\" + context.toString() \n                                     + \"][executionCount:\" + executionCount + \"]\");\n                    return true;\n                }\n                return false;\n            }\n        };\n        HttpClient httpClient = HttpClients.custom().setConnectionManager(pccm)\n                                           .setDefaultRequestConfig(config)\n                                           .setRetryHandler(retryHandler).build();\n        \n        HttpResponse response = httpClient.execute(httpPost);\n        StatusLine status = response.getStatusLine();\n        if (status.getStatusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {\n            System.out.printf(\"Did not receive successful HTTP response: status code = {}, status message = {}\", status.getStatusCode(), status.getReasonPhrase());\n            httpPost.abort();\n        }\n\n        String responseContent = \"\";\n        HttpEntity entity = response.getEntity();\n        if (entity != null) {\n            responseContent = EntityUtils.toString(entity, \"utf-8\");\n            EntityUtils.consume(entity);\n        } else {\n            System.out.printf(\"Http entity is null! request url is {},response status is {}\", reqURL, response.getStatusLine());\n        }\n        return responseContent;\n    }\n\n    public static void main(String[] args) {\n        Map<String, String> params = new HashMap<String, String>();\n        params.put(\"appid\", \"12345\");\n        params.put(\"title\", \"abc\");\n        params.put(\"textid\", \"123456778\");\n        params.put(\"text\", \"abcdefg\");\n\n        String res;\n        try {\n            res = post(\"http://commentapi.datagrand.com/bad_comment/meituan\", params);\n            System.out.println(res);\n        } catch (Exception e) {\n\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/http/HttpTester.java",
    "content": "package test.http;\n\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.net.ssl.SSLSocketFactory;\n\nimport org.apache.commons.io.IOUtils;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.http.ContentType;\nimport cn.ponfee.commons.http.Http;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType;\nimport cn.ponfee.commons.resource.Resource;\nimport cn.ponfee.commons.resource.ResourceLoaderFacade;\nimport cn.ponfee.commons.util.Bytes;\n\npublic class HttpTester {\n    //private static final String URL = \"http://192.168.1.49:8080/web/\";\n    private static final String URL = \"http://192.168.1.120:8100/\";\n\n    @Test\n    public void testHttps() {\n        InputStream keyInput = Object.class.getResourceAsStream(\"d:/cert.p12\");\n        KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, keyInput, \"1253089901\");\n        SSLSocketFactory sockFact = resolver.getSSLContext(\"1253089901\").getSocketFactory();\n\n        String url = \"https://api.mch.weixin.qq.com/secapi/pay/refund\";\n        String data = \"<xml><appid><![CDATA[wxf66ea2204a7a1c58]]></appid><mch_id><![CDATA[1253089901]]></mch_id><nonce_str><![CDATA[rvcmjyhg9205v4wo]]></nonce_str><op_user_id><![CDATA[1253089901]]></op_user_id><out_refund_no><![CDATA[TEST3344556677]]></out_refund_no><refund_fee><![CDATA[1]]></refund_fee><refund_fee_type><![CDATA[CNY]]></refund_fee_type><sign><![CDATA[3D07071971352BCFF5E007B2B7FA9495]]></sign><total_fee><![CDATA[1]]></total_fee><transaction_id><![CDATA[1003110578201511281803217943]]></transaction_id></xml>\";\n        String resp = Http.post(url).setSSLSocketFactory(sockFact).data(data).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void upload() throws IOException {\n        String url = URL + \"account/v1/user/photoupdate.json\";\n        Map<String, String> params = new HashMap<>();\n        params.put(\"time\", \"1478859839449\");\n        params.put(\"deviceid\", \"991182d512da8f4615f7a4eddb878512\");\n        params.put(\"platform\", \"H5\");\n        params.put(\"nickName\", \"厚大司考等哈说11\");\n        params.put(\"authToken\", \"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ2aHg0amVkekRsNGUrZElOOWh0RnhIaG5vcnV0UmpyV1YySmFRdWVWODRvPSIsImV4cCI6MTQ4MTQ1MTQ2OCwicmZoIjoxNDc4OTQ1ODY4fQ.kg17ETWHUFbdJVlJnEUgYPC-34PxYnp9eCVvJt3X4ZfM8-FmM112M799Q8vTRyTnG637pfJJfU2PcrB18Xf1MQ\");\n\n        String resp = Http.post(url).addPart(\"photo\", \"photo.jpg\", IOUtils.toByteArray(new FileInputStream(\"D:\\\\photo.jpg\"))).addParam(params).request();\n        //String resp = Http.post(url).part(\"photo\", \"photo.jpg\", new Byte[]{123,34}).params(params).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testParams() {\n        String url = URL + \"account/v1/user/info.json\";\n        Map<String, String> params = new HashMap<>();\n        params.put(\"time\", \"1478859839449\");\n        params.put(\"deviceid\", \"991182d512da8f4615f7a4eddb878512\");\n        params.put(\"platform\", \"H5\");\n        params.put(\"nickName\", \"厚大司考等哈说11\");\n        params.put(\"authToken\", \"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ2aHg0amVkekRsNGUrZElOOWh0RnhIaG5vcnV0UmpyV1YySmFRdWVWODRvPSIsImV4cCI6MTQ4MTQ1MTQ2OCwicmZoIjoxNDc4OTQ1ODY4fQ.kg17ETWHUFbdJVlJnEUgYPC-34PxYnp9eCVvJt3X4ZfM8-FmM112M799Q8vTRyTnG637pfJJfU2PcrB18Xf1MQ\");\n\n        String resp = Http.post(url).addParam(params).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testData() throws IOException {\n        Resource resource = ResourceLoaderFacade.getResource(\"qq.coupon/createConfig.json\", \"UTF-8\");\n        String json = IOUtils.toString(resource.getStream(), Charset.forName(\"UTF-8\"));\n        Http http = Http.post(\"http://3bc98c2a.ngrok.io/pay-center-testdemo/test/json\");\n        String s = http.contentType(ContentType.APPLICATION_JSON, \"utf-8\").data(json).request();\n        System.out.println(\"======================================================\\n\" + s);\n\n        http = Http.post(\"http://3bc98c2a.ngrok.io/pay-center-testdemo/test/post\");\n        s = http.data(\"a=1&b=2\").request();\n        System.out.println(\"======================================================\\n\" + s);\n    }\n    \n    \n    @Test\n    public void test2() throws IOException {\n        String url = \"http://10.118.58.74:8080/express/risk/outeridentify.html\";\n        String resp = Http.post(url).addPart(\"file\", \"import.txt\", IOUtils.toByteArray(new FileInputStream(\"D:\\\\import.txt\"))).request();\n        System.out.println(resp);\n    }\n\n    @Test // 创建个性大屏\n    public void test11() throws IOException {\n        String url = \"http://10.118.40.20:8080/customizedScreen/addScreenInfo\";\n        String resp = Http.post(url)\n            .addParam(\"activityId\", \"\")\n            .addParam(\"screenId\", \"7\")\n            .addParam(\"activityName\", \"test_ponfee4\")\n            .addParam(\"activityStartDate\", \"2019-03-08\")\n            .addParam(\"activityEndDate\", \"2019-03-13\")\n            .addParam(\"showTop\", \"3\")\n            .addParam(\"monthlyCard\", \"2017112915,9999999999\")\n            .addParam(\"expressProductsStr\", \"SE0004,顺丰特惠\")\n            .addParam(\"keyword\", \"kw\")\n            .addParam(\"expressAddress\", \"北京市_北京市\")\n            .addParam(\"page_src\", \"2019-03-08\\\\4b480f74-6752-495a-ae76-04ef0e2d436f-1061493_7.jpg\")\n            .addParam(\"edit_src\", \"edit_src\")\n            .addHeader(\"Cookie\", \"JSESSIONID=1j8qfy7sh1jf61n5fgc25zwjo4\")\n            .request();\n        System.out.println(resp);\n    }\n    \n    @Test // 创建仓储大屏\n    public void test12() throws IOException {\n        String url = \"http://10.118.40.20:8080/battleRoom/saveActiveInfo2\";\n        String resp = Http.post(url)\n            .addParam(\"activeName\", \"test3423432\")\n            .addParam(\"activeReady\", \"2019-02-24\")\n            .addParam(\"activeDate\", \"2019-02-28\")\n            .addParam(\"activeDay\", \"2\")\n            .addParam(\"activeNum\", \"12\")\n            .addParam(\"act_warehouseCode\", \"DV1,DV2\")\n            .addParam(\"active_warahouseName\", \"监视器仓库1,监视器仓库2\")\n            .addParam(\"active_sku\", \"67445276542155002,77445276542155003\")\n            .addParam(\"warehouseCodes\", \"DV1,DV2\")\n            .addParam(\"skus\", \"67445276542155002,77445276542155003\")\n            .addHeader(\"Cookie\", \"JSESSIONID=1j8qfy7sh1jf61n5fgc25zwjo4\")\n            .request();\n        System.out.println(resp);\n    }\n    \n    @Test // 创建快递大屏\n    public void test13() throws IOException {\n        String url = \"http://10.118.40.20:8080/battleRoomExpress/saveActiveInfo\";\n        String resp = Http.post(url)\n            .addParam(\"activeName\", \"test3423432\")\n            .addParam(\"activeReady\", \"2019-02-24\")\n            .addParam(\"activeDate\", \"2019-02-28\")\n            .addParam(\"activeDay\", \"2\")\n            .addParam(\"activeState\", \"0\")\n            .addParam(\"activeNum\", \"12\")\n            .addParam(\"id\", \"0\")\n            .addParam(\"customerCodes\", \"2017112815,2017112915,9999999999,3333333333,5555555555,0203002395\")\n            .addHeader(\"Cookie\", \"JSESSIONID=1j8qfy7sh1jf61n5fgc25zwjo4\")\n            .request();\n        System.out.println(resp);\n    }\n    \n    public static void main(String[] args) throws FileNotFoundException {\n        //System.out.println(Bytes.hexDump(Http.get(\"http://www.apachelounge.com/download/VC14/binaries/httpd-2.4.25-win64-VC14.zip\").download()));\n        \n        //Http http = Http.get(\"http://www.baidu.com\");\n        //http.download(new FileOutputStream(\"d:/baidu.com.txt\"));\n        //System.out.println(http.getStatus());\n        \n        Http http = Http.get(\"http://www.stockstar.com\");\n        System.out.println(Bytes.dumpHex(http.download()));\n        System.out.println(http.getRespHeaders());\n        System.out.println(http.getStatus());\n        \n        //Http.get(\"http://www.baidu.com\").download(\"d:/baidu.html\");\n        //System.out.println(Http.get(\"http://localhost:8081/audit/getImg\").data(ImmutableMap.of(\"imgPath\", \"imgPath\")).request());\n        //String[] params = new String[]{\"{\\\"analyze_type\\\":\\\"mine_all_cust\\\",\\\"date_type\\\":4,\\\"class_name\\\":\\\"\\\"}\", \"{\\\"analyze_type\\\":\\\"mine_all_cust\\\",\\\"date_type\\\":4,\\\"class_name\\\":\\\"衬衫\\\"}\"};\n        //Http.post(\"http://10.118.58.156:8080/market/custgroup/kanban/count/recommend\").data(ImmutableMap.of(\"conditions[]\", params)).request();\n        /*@SuppressWarnings(\"unchecked\") \n        Map<String, Object> resp = Http.post(\"http://10.118.58.74:8080/uploaded/file\")\n                                       .addParam(\"param1\", \"test1213\")\n                                       .addPart(\"uploadFile\", \"abc.pdf\", new File(\"d:/test/abc.pdf\"))\n                                       .addPart(\"uploadFile\", \"word.pdf\", new File(\"d:/test/word.pdf\"))\n                                       .contentType(\"multipart/form-data\", \"UTF-8\") // <input type=\"file\" name=\"upload\" />\n                                       //.contentType(\"application/json\", \"UTF-8\") // @RequestBody\n                                       //.contentType(\"application/x-www-form-urlencoded\", \"UTF-8\") // form data\n                                       .accept(\"application/json\") // @ResponseBody\n                                       //.setSSLSocketFactory(factory) // trust certs store\n                                       .request(Map.class);\n        System.out.println(resp);*/\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/http/NewApiTester.java",
    "content": "package test.http;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport cn.ponfee.commons.http.Http;\n\npublic class NewApiTester {\n    private static final String TOKEN = \"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJtVU9SMk5XVzJsUXVNd1VxUlRuWjR3PT0iLCJleHAiOjE0OTEwMTY1NTUsInJmaCI6MTQ4ODUxMDk1NX0.mjeVWE3vEfI89r65DtoqRGFkUSx-KeQL0SE5liUrwSIG3mh7ptovcDkpjq6oqJNnMrul3vOHNWhMjBbwt0lFAQ\";\n    private static final String URL = \"http://192.168.1.122:8100\";\n\n    @Test\n    public void testChildrenAdd() {\n        String url = URL + \"/account/v1/contact/childrenadd.json\";\n        Map<String, String> params = new HashMap<>();\n        params.put(\"time\", \"1478859839449\");\n        params.put(\"deviceid\", \"991182d512da8f4615f7a4eddb878512\");\n        params.put(\"platform\", \"H5\");\n        params.put(\"authToken\", TOKEN);\n\n        params.put(\"realName\", \"abcdef\");\n        params.put(\"certNo\", \"430121198901227354\");\n\n        String resp = Http.post(url).addParam(params).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testChildrenUpd() {\n        String url = URL + \"/account/v1/contact/childrenupd.json\";\n        Map<String, String> params = new HashMap<>();\n        params.put(\"time\", \"431121198910163461\");\n        params.put(\"deviceid\", \"991182d512da8f4615f7a4eddb878512\");\n        params.put(\"platform\", \"H5\");\n        params.put(\"authToken\", TOKEN);\n\n        params.put(\"realName\", \"ccccccc\");\n        params.put(\"certNo\", \"430121198901227354\");\n        params.put(\"id\", \"5\");\n\n        String resp = Http.post(url).addParam(params).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testChildrenDel() {\n        String url = URL + \"/account/v1/contact/childrendel.json\";\n        Map<String, String> params = new HashMap<>();\n        params.put(\"time\", \"1478859839449\");\n        params.put(\"deviceid\", \"991182d512da8f4615f7a4eddb878512\");\n        params.put(\"platform\", \"H5\");\n        params.put(\"authToken\", TOKEN);\n\n        params.put(\"id\", \"6\");\n\n        String resp = Http.post(url).addParam(params).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testChildrenlist() {\n        String url = URL + \"/account/v1/contact/childrenlist.json\";\n        Map<String, String> params = new HashMap<>();\n        params.put(\"time\", \"1478859839449\");\n        params.put(\"deviceid\", \"991182d512da8f4615f7a4eddb878512\");\n        params.put(\"platform\", \"H5\");\n        params.put(\"authToken\", TOKEN);\n\n        String resp = Http.post(url).addParam(params).request();\n        System.out.println(resp);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/http/OldApiTester.java",
    "content": "package test.http;\n\nimport java.io.IOException;\nimport java.net.URLEncoder;\nimport java.security.MessageDigest;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.SecretKeySpec;\n\nimport org.apache.commons.codec.binary.Base64;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.http.Http;\nimport cn.ponfee.commons.http.HttpParams;\nimport cn.ponfee.commons.jce.digest.DigestUtils;\n\npublic class OldApiTester {\n    private static final String URL = \"http://192.168.1.49:8080/service-webapp\";\n\n    private static final String SECRET = \"abc\";\n    private static final String KEY = \"cde\";\n\n    @Test\n    public void testOldInf() {\n        Map<String, String> headers = new HashMap<>();\n        String time = String.valueOf(System.currentTimeMillis());\n        headers.put(\"source\", \"IOS\");\n        headers.put(\"time\", time);\n        headers.put(\"auth\", DigestUtils.md5Hex(SECRET + time));\n\n        Map<String, String> params = new HashMap<>();\n        params.put(\"abc\", \"123\");\n        params.put(\"sign\", buildSign(params, KEY));\n\n        String resp = Http.post(URL + \"/city/getStartCityList.srv\").addParam(params).addHeader(headers).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testcarrychildrenaddorupd() {\n        Map<String, String> headers = new HashMap<>();\n        String userId = \"87\";\n        String time = String.valueOf(System.currentTimeMillis());\n        headers.put(\"source\", \"IOS\");\n        headers.put(\"time\", time);\n        headers.put(\"auth\", DigestUtils.md5Hex(SECRET + time));\n        headers.put(\"accToken\", buildAcctoken(userId));\n\n        Map<String, String> params = new HashMap<>();\n        params.put(\"userId\", userId);\n        params.put(\"certNo\", \"430122198210130031\");\n        params.put(\"realName\", \"realNamecde\");\n        params.put(\"sign\", buildSign(params, KEY));\n\n        String resp = Http.post(URL + \"/user/carrychildrenaddorupd.srv\").addParam(params).addHeader(headers).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testcarrychildrendel() {\n        Map<String, String> headers = new HashMap<>();\n        String userId = \"87\";\n        String time = String.valueOf(System.currentTimeMillis());\n        headers.put(\"source\", \"IOS\");\n        headers.put(\"time\", time);\n        headers.put(\"auth\", DigestUtils.md5Hex(SECRET + time));\n        headers.put(\"accToken\", buildAcctoken(userId));\n\n        Map<String, String> params = new HashMap<>();\n        params.put(\"userId\", userId);\n        params.put(\"id\", \"2\");\n        params.put(\"sign\", buildSign(params, KEY));\n\n        String resp = Http.post(URL + \"/user/carrychildrendel.srv\").addParam(params).addHeader(headers).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testcarrychildrenlist() {\n        Map<String, String> headers = new HashMap<>();\n        String userId = \"87\";\n        String time = String.valueOf(System.currentTimeMillis());\n        headers.put(\"source\", \"IOS\");\n        headers.put(\"time\", time);\n        headers.put(\"auth\", DigestUtils.md5Hex(SECRET + time));\n        headers.put(\"accToken\", buildAcctoken(userId));\n\n        Map<String, String> params = new HashMap<>();\n        params.put(\"userId\", userId);\n        params.put(\"sign\", buildSign(params, KEY));\n\n        String resp = Http.post(URL + \"/user/carrychildrenlist.srv\").addParam(params).addHeader(headers).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testgetUserInfo() {\n        Map<String, String> headers = new HashMap<>();\n        String userId = \"1\";\n        String time = String.valueOf(System.currentTimeMillis());\n        headers.put(\"source\", \"IOS\");\n        headers.put(\"time\", time);\n        headers.put(\"auth\", DigestUtils.md5Hex(SECRET + time));\n        headers.put(\"accToken\", buildAcctoken(userId));\n\n        Map<String, String> params = new HashMap<>();\n        params.put(\"userId\", userId);\n        params.put(\"sign\", buildSign(params, KEY));\n\n        String resp = Http.post(URL + \"/user/getUserInfo.srv\").addParam(params).addHeader(headers).request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void testBanner() {\n        Map<String, String> headers = new HashMap<>();\n        String time = String.valueOf(System.currentTimeMillis());\n        headers.put(\"source\", \"MOBILE\");\n        headers.put(\"time\", time);\n        headers.put(\"auth\", DigestUtils.md5Hex(SECRET + time));\n\n        Map<String, String> params = new HashMap<>();\n        params.put(\"module\", \"huodong\");\n        params.put(\"clientIp\", \"127.0.0.1\");\n        params.put(\"sign\", buildSign(params, KEY));\n\n        String resp = Http.post(URL + \"/sys/activeAdvert.srv\").addParam(params).addHeader(headers).request();\n        System.out.println(resp);\n    }\n\n    private String buildSign(Map<String, String> params, String key) {\n        String signing = HttpParams.buildSigning(params, new String[] { \"sign\" });\n        if (signing.length() > 0) signing += \"&\";\n        signing += key;\n        return DigestUtils.md5Hex(signing.getBytes()).toUpperCase();\n    }\n\n    // 加密\n    private static String buildAcctoken(String str) {\n        try {\n            byte[] bkey = GetKeyBytes(\"acd\");\n\n            SecretKey deskey = new SecretKeySpec(bkey, \"DESede\"); // 加密\n            Cipher c1 = Cipher.getInstance(\"DESede\");\n            c1.init(Cipher.ENCRYPT_MODE, deskey);\n            byte[] bytes = c1.doFinal(URLEncoder.encode(str, \"utf-8\").getBytes());\n            return Base64.encodeBase64String(bytes);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    private static byte[] GetKeyBytes(String strKey) throws Exception {\n        if (null == strKey || strKey.length() < 1) {\n            throw new Exception(\"key is null or empty!\");\n        }\n        MessageDigest alg = MessageDigest.getInstance(\"MD5\");\n        alg.update(strKey.getBytes());\n        byte[] bkey = alg.digest();\n        int start = bkey.length;\n        byte[] bkey24 = new byte[24];\n        for (int i = 0; i < start; i++) {\n            bkey24[i] = bkey[i];\n        }\n        for (int i = start; i < 24; i++) {\n            // 为了与.net16位key兼容\n            bkey24[i] = bkey[i - start];\n        }\n        return bkey24;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/http/TestHttpUploadFile.java",
    "content": "package test.http;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport cn.ponfee.commons.http.ContentType;\nimport cn.ponfee.commons.http.Http;\nimport cn.ponfee.commons.io.Files;\n\n@SuppressWarnings(\"unchecked\")\npublic class TestHttpUploadFile {\n\n    @Test\n    public void test0() {\n        List<Object> resp = Http.post(\"http://10.118.58.74:8080/battleRoom/shouSkuInfo2\")\n                                .accept(ContentType.APPLICATION_JSON) // @ResponseBody\n                                .request(List.class);\n        System.out.println(resp);\n    }\n    \n    @Test\n    public void test1() {\n        Map<String, Object> resp = Http.post(\"http://10.118.58.74:8080/battleRoom/importsku\")\n                                       .addPart(\"skuFile\", \"importsku.xlsx\", \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\", new File(\"d:/大屏批量配置-data-2.xlsx\"))\n                                       .contentType(ContentType.MULTIPART_FORM_DATA, \"UTF-8\") // <input type=\"file\" name=\"upload\" />\n                                       .accept(ContentType.APPLICATION_JSON) // @ResponseBody\n                                       .request(Map.class);\n        System.out.println(resp);\n    }\n\n    @Test\n    public void test2() {\n        Http.post(\"http://10.118.58.74:8080/battleRoom/exportsku\").addParam(\"fileId\",\"ad\").download(\"d:/abc2dd.xlsx\");\n    }\n\n    public static void main(String[] args) throws FileNotFoundException {\n        InputStream in = new FileInputStream(\"d:/abc2dd.csv\");\n        try (InputStream input = in;\n            BufferedInputStream buffInput = new BufferedInputStream(input); \n            OutputStream output = new FileOutputStream(\"D:/1111.csv\"); \n            BufferedOutputStream buffOutput = new BufferedOutputStream(output)\n        ) {\n           byte[] buffer = new byte[8192];\n           for (int len; (len = buffInput.read(buffer)) != Files.EOF;) {\n               buffOutput.write(buffer, 0, len);\n           }\n       } catch (IOException e) {\n       }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/http/TestJsoup.java",
    "content": "package test.http;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.http.Http;\n\n/**\n * \n * @author Ponfee\n */\npublic class TestJsoup {\n\n    private static final String COOKIE =\n        \"\";\n\n    // ========================================================see\n    @Test(timeout = 999999999)\n    public void viewSeeme() {\n        viewSee(0);\n    }\n\n    @Test(timeout = 999999999)\n    public void deleteSeeme() { // 谁看过我\n        deleteSee(0);\n    }\n\n    @Test(timeout = 999999999)\n    public void viewMesee() {\n        viewSee(1);\n    }\n\n    @Test(timeout = 999999999)\n    public void deleteMesee() { // 我看过谁\n        deleteSee(1);\n    }\n\n    // ========================================================send\n    @Test(timeout = 999999999)\n    public void viewSendme() { // 查看收件箱\n        viewSend(1);\n    }\n\n    @Test(timeout = 999999999)\n    public void deleteSendme() { // 删除收件箱\n        delSend(1);\n    }\n\n    @Test(timeout = 999999999)\n    public void viewMesend() { // 查看发件箱\n        viewSend(4);\n    }\n\n    @Test(timeout = 999999999)\n    public void deleteMesend() { // 删除发件箱\n        delSend(4);\n    }\n\n    // ==================================================================\n    private void viewSee(int type) {\n        Http page = Http.get(\"http://profile.zhenai.com/v2/visit/ajax.do\").addHeader(\"COOKIE\", COOKIE).addParam(\"type\", type);\n        System.out.println(page.addParam(\"page\", \"1\").request());\n\n        Http delete = Http.get(\"http://profile.zhenai.com/v2/visit/delete.do\").addHeader(\"COOKIE\", COOKIE).addParam(\"type\", type);\n        System.out.println(delete.addParam(\"memberid\", \"999999999\").request());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private void deleteSee(int type) {\n        Http page = Http.get(\"http://profile.zhenai.com/v2/visit/ajax.do\").addHeader(\"COOKIE\", COOKIE).addParam(\"type\", type);\n        Http delete = Http.get(\"http://profile.zhenai.com/v2/visit/delete.do\").addHeader(\"COOKIE\", COOKIE).addParam(\"type\", type);\n        for (;;) {\n            try {\n                page.addParam(\"page\", 1);\n                Map<String, Object> map = page.request(Map.class);\n                if ((int) map.get(\"code\") == 0) {\n                    System.err.println(\"page fail\");\n                    break;\n                }\n                List<Map<String, Object>> list = (List<Map<String, Object>>) map.get(\"data\");\n                for (Map<String, Object> item : list) {\n                    System.out.print(item.get(\"memberId\") + \", \");\n                    Thread.sleep(50);\n                    Map<String, Object> delRes = delete.addParam(\"memberid\", item.get(\"memberId\")).request(Map.class);\n                    if ((int) delRes.get(\"data\") == 0) {\n                        System.err.println(\"del fail\");\n                    }\n                }\n                System.out.println();\n                Thread.sleep(200);\n            } catch (Exception e) {}\n        }\n    }\n\n    // ==================================================================\n    private void viewSend(int type) {\n        Http page = Http.get(\"http://profile.zhenai.com/v2/mail/list.do\").addHeader(\"COOKIE\", COOKIE).addParam(\"showType\", type);\n        String html = page.addParam(\"pageNo\", 1).request();\n        Document doc = Jsoup.parse(html, \"UTF-8\");\n        Elements mes = doc.select(\"section[class='mod-msg-item exp-mail-item'] > a[class='new-icon-close deleteMail-js']\");\n        for (Element elem : mes) {\n            System.out.print(elem.attr(\"memberid\") + \", \");\n        }\n        System.out.println();\n        Http delete = Http.post(\"http://profile.zhenai.com/v2/mail/deleteMemberNew.do\").addHeader(\"COOKIE\", COOKIE);\n        System.out.println(delete.data(\"memberId=999999999\").request());\n    }\n\n    private void delSend(int type) {\n        Http page = Http.get(\"http://profile.zhenai.com/v2/mail/list.do\").addHeader(\"COOKIE\", COOKIE).addParam(\"showType\", type);\n        for (;;) {\n            try {\n                String html = page.addParam(\"pageNo\", 1).request();\n                Document doc = Jsoup.parse(html, \"UTF-8\");\n                Elements mes = doc.select(\"section[class='mod-msg-item exp-mail-item'] > a[class='new-icon-close deleteMail-js']\");\n                boolean found = false;\n                for (Element elem : mes) {\n                    found = true;\n                    Http delete = Http.post(\"http://profile.zhenai.com/v2/mail/deleteMemberNew.do\").addHeader(\"COOKIE\", COOKIE);\n                    String memberid = elem.attr(\"memberid\");\n                    System.out.print(memberid + \", \");\n                    delete.data(\"memberId=\" + memberid).request();\n                    Thread.sleep(50);\n                }\n                System.out.println();\n                if (!found) {\n                    break;\n                }\n                Thread.sleep(200);\n            } catch (Exception e) {}\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/http/TestOpenApi.java",
    "content": "package test.http;\n\nimport java.util.Map;\nimport java.util.Random;\n\nimport cn.ponfee.commons.util.UuidUtils;\nimport org.junit.Test;\n\nimport com.google.common.collect.ImmutableMap;\n\nimport cn.ponfee.commons.http.ContentType;\nimport cn.ponfee.commons.http.Http;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.ObjectUtils;\n\n@SuppressWarnings(\"unchecked\")\npublic class TestOpenApi {\n    String host = \"http://10.202.64.167:8080\";\n    //String host = \"http://10.118.58.74:8000\";\n    @Test\n    public void test1() {\n        String username = \"rnT2s9YMKGHkannrr-kNFSZYk-XOLBRKIIjnY62szVR0E31j8SQx50cym_KVXT5m1yjzcHUHh-M8g24q-oHnPa-MgaNaKR8IK-TUa9I9S9Vo8Xseg25EjgrvONK2X4ahKbSBHqrJfyIrPmE7rO70XHnhhma5HtLLXVukhY7eVJk\";\n        String passowrd = \"m6bZQrh72pkbBXBmvA-OoSSYRDDtht4mnH4c9VZ77dPnE6qbD8mMJPC_1Wn6Dhr28IfkSv2o3bS44zraBVZ0b-wxMRJ9meZ9i7Vvkwab2uL-eJOvYQ25xjHbdEinsdEyb7rMq0u8Flw9_KqmojEPQVJvSQyV_fJFin8Ns6p_li4\";\n\n        //String username = \"d_OWfBL99lSVQC13OmhNA4H5G65brRYIDXatGnNOkIfAnHmZl-qDP8nf-8twOtaDw3ctnH1hO3CiIW85HxTn3M_CmRmYXmjKkLR-Vcrx8RbsoMuJvACliIwbzo5F8xZGHk4-yk-UP9e_NvbJaE1UZBN5jiY5RyHgNvLuy8MvAeQ\";\n        //String passowrd = \"LRDCcDwnzMWKQMqoEza2V4aFa5TPmFACcDeJiDfR3BBjiEGGWfCaMgbi_rE3hPM5kWs2iJdPxK-CrLkWozhcLsyB8S0Seg9r0ybJL5VJ0iVRi2dVqN4rUrj7i2x9lSdTFsyrGqCNLz57dK1TuY02QVw8iEgi6m9wwFgGpbnhoME\";\n\n        Object data = ImmutableMap.of(\"head\", ImmutableMap.of(\"app_id\", \"FQ\", \"trans_id\", UuidUtils.uuid22()),\n                                      \"body\", ImmutableMap.of(\"username\", username, \n                                                              \"password\", passowrd));\n\n        String resp = Http.post(host+\"/open/api/auth\")\n                                       .contentType(ContentType.APPLICATION_JSON, \"UTF-8\") \n                                       .data(Jsons.NORMAL.string(data))\n                                       .accept(ContentType.APPLICATION_JSON) \n                                       .request();\n        System.out.println(resp);\n    }\n\n    @Test\n    public void test2() {\n        Object data = ImmutableMap.of(\"head\", ImmutableMap.of(\"app_id\", \"FQ\", \"trans_id\", UuidUtils.uuid22()),\n                                      \"body\", ImmutableMap.of(\"username\", \"rnT2s9YMKGHkannrr-kNFSZYk-XOLBRKIIjnY62szVR0E31j8SQx50cym_KVXT5m1yjzcHUHh-M8g24q-oHnPa-MgaNaKR8IK-TUa9I9S9Vo8Xseg25EjgrvONK2X4ahKbSBHqrJfyIrPmE7rO70XHnhhma5HtLLXVukhY7eVJk\"));\n\n        Map<String, Object> resp = Http.post(host+\"/open/api/userinfo\")\n                                       .contentType(ContentType.APPLICATION_JSON, \"UTF-8\") \n                                       .data(Jsons.NORMAL.string(data))\n                                       .accept(ContentType.APPLICATION_JSON) \n                                       .request(Map.class);\n        System.out.println(resp);\n    }\n\n    @Test\n    public void test3() {\n        System.out.println(Http.post(host + \"/open/api/test?a=1=32=14=12=4=3214=2&abcdef&\" + Math.random()).request());\n    }\n\n    public static void main(String[] args) {\n        String captcha = \"1234\";\n        long number = new Random(captcha.hashCode()).nextLong();\n        System.out.println(number);\n        System.out.println(captcha.hashCode());\n        number = Bytes.crc32(Bytes.toBytes(captcha.hashCode()));\n        System.out.println(number);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/http/WSClientTester.java",
    "content": "package test.http;\n\nimport java.io.BufferedReader;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.net.URLConnection;\n\nimport javax.xml.namespace.QName;\nimport javax.xml.soap.MessageFactory;\nimport javax.xml.soap.SOAPBody;\nimport javax.xml.soap.SOAPBodyElement;\nimport javax.xml.soap.SOAPElement;\nimport javax.xml.soap.SOAPEnvelope;\nimport javax.xml.soap.SOAPMessage;\nimport javax.xml.ws.Dispatch;\nimport javax.xml.ws.Service;\n\nimport org.junit.Test;\n\npublic class WSClientTester {\n\n    @Test\n    public void testSoap1() throws Exception {\n        String soap = \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?><soap:Envelope xmlns:xsi=\\\"http://www.w3.org/2001/XMLSchema-instance\\\" xmlns:xsd=\\\"http://www.w3.org/2001/XMLSchema\\\" xmlns:soap=\\\"http://schemas.xmlsoap.org/soap/envelope/\\\"><soap:Body><getSupportCity xmlns=\\\"http://WebXml.com.cn/\\\"><byProvinceName></byProvinceName></getSupportCity></soap:Body></soap:Envelope>\";\n        URL url = new URL(\"http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl\");\n        URLConnection conn = url.openConnection();\n        conn.setUseCaches(false);\n        conn.setDoInput(true);\n        conn.setDoOutput(true);\n\n        conn.setRequestProperty(\"Content-Length\", Integer.toString(soap.length()));\n        conn.setRequestProperty(\"Content-Type\", \"text/xml; charset=utf-8\");\n        conn.setRequestProperty(\"SOAPAction\", \"http://WebXml.com.cn/getSupportCity\");\n\n        OutputStream os = conn.getOutputStream();\n        OutputStreamWriter osw = new OutputStreamWriter(os, \"utf-8\");\n        osw.write(soap);\n        osw.flush();\n        osw.close();\n        StringBuilder sTotalString = new StringBuilder();\n        String sCurrentLine = \"\";\n        InputStream is = conn.getInputStream();\n        BufferedReader l_reader = new BufferedReader(new InputStreamReader(is));\n        while ((sCurrentLine = l_reader.readLine()) != null) {\n            sTotalString.append(sCurrentLine);\n        }\n        System.out.println(sTotalString.toString());\n    }\n\n    @Test\n    public void testSoap2() throws Exception {\n        SOAPMessage message = MessageFactory.newInstance().createMessage();\n        SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();\n\n        /*SOAPHeader header = envelope.getHeader();\n        if (header == null) header = envelope.addHeader();\n        header.addHeaderElement( new QName(\"http://www.tyky.com.cn/cMashup/\" , \"license\" , \"tns\")).setValue(\"this a license\" );*/\n\n        SOAPBody body = envelope.getBody();\n        SOAPBodyElement elem = body.addBodyElement(new QName(\"http://www.tyky.com.cn/cMashup/\", \"UpdateCAStatusResult\", \"tns\"));\n\n        SOAPElement arrayOfKeyValueElem = elem.addChildElement(\"ArrayOfKeyValueOfstringstring\");\n\n        SOAPElement keyValueElem1 = arrayOfKeyValueElem.addChildElement(\"KeyValueOfstringstring\");\n        keyValueElem1.addChildElement(\"Key\").setValue(\"123456\");\n        keyValueElem1.addChildElement(\"value\").setValue(\"org\");\n\n        SOAPElement keyValueElem2 = arrayOfKeyValueElem.addChildElement(\"KeyValueOfstringstring\");\n        keyValueElem2.addChildElement(\"Key\").setValue(\"0001\");\n        keyValueElem2.addChildElement(\"value\").setValue(\"user\");\n\n        URL url = new URL(\"http://112.95.149.106:8088/SystemPadServices.svc?wsdl\");\n        QName qName = new QName(\"http://tempuri.org/\", \"SystemPadService\");\n        Service service = Service.create(url, qName);\n        Dispatch<SOAPMessage> dispatch = service.createDispatch(new QName(\"http://www.tyky.com.cn/cMashup/\", \"SystemPadServicePort\"), SOAPMessage.class, Service.Mode.MESSAGE);\n        SOAPMessage msg = dispatch.invoke(message);\n        System.out.println(msg.getSOAPBody().getElementsByTagName(\"addResult\").item(0).getTextContent());\n    }\n\n    @Test\n    public void testPost() throws Exception {\n        StringBuilder sTotalString = new StringBuilder();\n        URL urlTemp = new URL(\"http://112.95.149.106:8088/SystemPadServices.svc/UpdateCAStatus/Platform\");\n        HttpURLConnection connection = (HttpURLConnection) urlTemp.openConnection();\n        connection.setDoOutput(true);\n        connection.setDoInput(true);\n        connection.setRequestProperty(\"Content-type\", \"application/json\");\n        connection.setRequestMethod(\"POST\");\n        OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), \"UTF-8\");\n        out.write(\"{\\\"caUser\\\":[{\\\"Key\\\":\\\"123123\\\",\\\"Value\\\":\\\"org\\\"}]}\");\n        out.flush();\n        out.close();\n\n        String sCurrentLine;\n        InputStream l_urlStream = connection.getInputStream();// 请求\n        BufferedReader l_reader = new BufferedReader(new InputStreamReader(l_urlStream));\n        while ((sCurrentLine = l_reader.readLine()) != null) {\n            sTotalString.append(sCurrentLine);\n        }\n        System.out.println(sTotalString.toString());\n    }\n\n    /*@Test\n    public void testCXFDynamic() {\n        org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory clientFactory = org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory.newInstance();\n        String url = \"http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?wsdl\"; // http://www.fjyxd.com:17001/DefDispatcher/dispatche?wsdl\n        org.apache.cxf.endpoint.Client clientTemp = clientFactory.createClient(url);\n        try {\n            // 查询QQ在线状态：Y在线；N离线；E号码错误；A商业用户验证失败；V免费用户超过数量；\n            Object[] objects = clientTemp.invoke(\"qqCheckOnline\", \"8698053\");\n            System.out.println(com.alibaba.fastjson.JSON.toJSONString(objects));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }*/\n\n    /*String endpoint = \"http://10.202.16.116:8080/cos_webservice/services/CreateComplainService\";\n    String request = \"{\\\"carryId\\\":\\\"070034424401\\\",\\\"title\\\":\\\"\\\",\\\"complainLevel\\\":\\\"15\\\",\\\"urgency\\\":\\\"11\\\",\\\"complainChannel\\\":\\\"60014708\\\",\\\"complainDetailChannel\\\":\\\"\\\",\\\"complainSource\\\":\\\"30\\\",\\\"linkman\\\":\\\"张先生\\\",\\\"linkmanPhone\\\":\\\"15623850085\\\",\\\"receiveSendChoose\\\":\\\"19057\\\",\\\"contactLink\\\":\\\"101000000\\\",\\\"focus\\\":\\\"101030000\\\",\\\"customerFeedback\\\":\\\"101030100\\\",\\\"content\\\":\\\"此单客户来电反馈，商品其在4月3日上午9点订购的，5号19点多才发出来已经超过48小时了，其现在要求退款，还请将此单商品拦截不要派送，点部作废即可，谢谢SSM姜亮\\\",\\\"internalComplainSource\\\":\\\"CCS5-SYSTEM\\\",\\\"dealWithAreaCode\\\":\\\"755Y\\\",\\\"problemLink\\\":\\\"\\\",\\\"monthAccount\\\":\\\"\\\"}\";\n    @Test\n    public void test1() throws Exception {\n        net.bingosoft.complain.CreateComplainServicePortType Service = new net.bingosoft.complain.CreateComplainServicePortTypeProxy(endpoint);\n        String resp = Service.newComplainFromCommon(request);\n        System.out.println(resp);\n    }\n    @Test\n    public void test2() throws Exception {\n        org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory dcf = org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory.newInstance();\n        org.apache.cxf.endpoint.Client client = dcf.createClient(endpoint + \"?wsdl\");\n        Object[] objects = client.invoke(\"newComplainFromCommon\", request);\n        System.out.println(com.alibaba.fastjson.JSON.toJSONString(objects));\n    }*/\n}\n"
  },
  {
    "path": "src/test/java/test/http/jdk/HTTPServerSample.java",
    "content": "package test.http.jdk;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.InetSocketAddress;\nimport java.time.LocalDateTime;\n\nimport com.sun.net.httpserver.HttpExchange;\nimport com.sun.net.httpserver.HttpHandler;\nimport com.sun.net.httpserver.HttpServer;\n\n/**\n * like as NanoHTTPD\n */\n@SuppressWarnings(\"restriction\")\npublic class HTTPServerSample {\n\n    private static final int BACK_LOG = 10; // 允许最大连接数\n\n    public static void main(String[] args) {\n        try {\n            HttpServer server = HttpServer.create(new InetSocketAddress(8888), BACK_LOG);\n            server.createContext(\"/HTTPServerSample\", new MyHandler()); // 用MyHandler类处理请求\n            server.setExecutor(null); // creates a default executor\n            server.start(); // http://localhost:8888/HTTPServerSample\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n}\n\n@SuppressWarnings(\"restriction\")\nclass MyHandler implements HttpHandler {\n    public void handle(HttpExchange exchange) throws IOException {\n        InputStream input = exchange.getRequestBody();\n        String response = \"<h3>\" + LocalDateTime.now() + \"</h3>\";\n        exchange.sendResponseHeaders(200, response.length());\n        OutputStream output = exchange.getResponseBody();\n        output.write(response.getBytes());\n        output.close();\n        input.close();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/http/jdk/WSProvider.java",
    "content": "package test.http.jdk;\n\nimport javax.jws.Oneway;\nimport javax.jws.WebMethod;\nimport javax.jws.WebParam;\nimport javax.jws.WebResult;\nimport javax.jws.WebService;\n\nimport cn.ponfee.commons.ws.JAXWS;\n\n@WebService(targetNamespace = \"http://jdk6.webservice/demo\", serviceName = \"HelloService\")\npublic class WSProvider {\n\n    @WebResult(name = \"Greetings\") //自定义该方法返回值在WSDL中相关的描述  \n    @WebMethod\n    public String sayHi(@WebParam(name = \"MyName\") String name) {\n        return \"Hi,\" + name; //@WebParam是自定义参数name在WSDL中相关的描述  \n    }\n\n    @Oneway //表明该服务方法是单向的,既没有返回值,也不应该声明检查异常  \n    @WebMethod(action = \"printSystemTime\", operationName = \"printSystemTime\") //自定义该方法在WSDL中相关的描述  \n    public void printTime() {\n        System.out.println(System.currentTimeMillis());\n    }\n\n    private static class WSPublisher implements Runnable {\n        public void run() {\n            //发布WSProvider到http://localhost:8889/demo/WSProvider这个地址,之前必须调用wsgen命令   \n            //生成服务类WSProvider的支持类,命令如下:  \n            //wsgen -cp . test.http.jdk.WSProvider  \n            JAXWS.publish(\"http://localhost:8889/demo/WSProvider\", new WSProvider());\n\n            // 访问  http://localhost:8889/demo/WSProvider?wsdl\n        }\n    }\n\n    public static void main(String[] args) {\n        Thread wsPublisher = new Thread(new WSPublisher());\n        wsPublisher.start();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/http/ssl/HttpsCert.java",
    "content": "package test.http.ssl;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.security.KeyStore;\nimport java.security.MessageDigest;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\n\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLException;\nimport javax.net.ssl.SSLSocket;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509TrustManager;\n\n/**\n * 从网站获取java所需的证书，调用时传入域名。\n */\npublic class HttpsCert {\n\n    public static void main(String[] args) throws Exception {\n        args = new String[] { \"www.baidu.com:443\", \"changeit\" };\n        String host;\n        int port;\n        char[] passphrase;\n        if ((args.length == 1) || (args.length == 2)) {\n            String[] c = args[0].split(\":\");\n            host = c[0];\n            port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);\n            String p = (args.length == 1) ? \"changeit\" : args[1];\n            passphrase = p.toCharArray();\n        } else {\n            System.out.println(\"Usage: java InstallCert <host>[:port] [passphrase]\");\n            return;\n        }\n\n        File file = new File(\"jssecacerts\");\n        if (file.isFile() == false) {\n            char SEP = File.separatorChar;\n            File dir = new File(System.getProperty(\"java.home\") + SEP + \"lib\" + SEP + \"security\");\n            file = new File(dir, \"jssecacerts\");\n            if (file.isFile() == false) {\n                file = new File(dir, \"cacerts\");\n            }\n        }\n        System.out.println(\"Loading KeyStore \" + file + \"...\");\n        InputStream in = new FileInputStream(file);\n        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());\n        ks.load(in, passphrase);\n        in.close();\n\n        SSLContext context = SSLContext.getInstance(\"TLS\");\n        TrustManagerFactory tmf =\n            TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n        tmf.init(ks);\n        X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];\n        SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);\n        context.init(null, new TrustManager[] { tm }, null);\n        SSLSocketFactory factory = context.getSocketFactory();\n\n        System.out.println(\"Opening connection to \" + host + \":\" + port + \"...\");\n        SSLSocket socket = (SSLSocket) factory.createSocket(host, port);\n        socket.setSoTimeout(10000);\n        try {\n            System.out.println(\"Starting SSL handshake...\");\n            socket.startHandshake();\n            socket.close();\n            System.out.println();\n            System.out.println(\"No errors, certificate is already trusted\");\n        } catch (SSLException e) {\n            System.out.println();\n            e.printStackTrace(System.out);\n        }\n\n        X509Certificate[] chain = tm.chain;\n        if (chain == null) {\n            System.out.println(\"Could not obtain server certificate chain\");\n            return;\n        }\n\n        BufferedReader reader =\n            new BufferedReader(new InputStreamReader(System.in));\n\n        System.out.println();\n        System.out.println(\"Server sent \" + chain.length + \" certificate(s):\");\n        System.out.println();\n        MessageDigest sha1 = MessageDigest.getInstance(\"SHA1\");\n        MessageDigest md5 = MessageDigest.getInstance(\"MD5\");\n        for (int i = 0; i < chain.length; i++) {\n            X509Certificate cert = chain[i];\n            System.out.println(\" \" + (i + 1) + \" Subject \" + cert.getSubjectDN());\n            System.out.println(\"   Issuer  \" + cert.getIssuerDN());\n            sha1.update(cert.getEncoded());\n            System.out.println(\"   sha1    \" + toHexString(sha1.digest()));\n            md5.update(cert.getEncoded());\n            System.out.println(\"   md5     \" + toHexString(md5.digest()));\n            System.out.println();\n        }\n\n        System.out.println(\"Enter certificate to add to trusted keystore or 'q' to quit: [1]\");\n        String line = reader.readLine().trim();\n        int k;\n        try {\n            k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;\n        } catch (NumberFormatException e) {\n            System.out.println(\"KeyStore not changed\");\n            return;\n        }\n\n        X509Certificate cert = chain[k];\n        String alias = host + \"-\" + (k + 1);\n        ks.setCertificateEntry(alias, cert);\n\n        OutputStream out = new FileOutputStream(\"jssecacerts\");\n        ks.store(out, passphrase);\n        out.close();\n\n        System.out.println();\n        System.out.println(cert);\n        System.out.println();\n        System.out.println(\"Added certificate to keystore 'jssecacerts' using alias '\" + alias + \"'\");\n    }\n\n    private static final char[] HEXDIGITS = \"0123456789abcdef\".toCharArray();\n\n    private static String toHexString(byte[] bytes) {\n        StringBuilder sb = new StringBuilder(bytes.length * 3);\n        for (int b : bytes) {\n            b &= 0xff;\n            sb.append(HEXDIGITS[b >> 4]);\n            sb.append(HEXDIGITS[b & 15]);\n            sb.append(' ');\n        }\n        return sb.toString();\n    }\n\n    private static class SavingTrustManager implements X509TrustManager {\n        private final X509TrustManager tm;\n        private X509Certificate[] chain;\n\n        SavingTrustManager(X509TrustManager tm) {\n            this.tm = tm;\n        }\n\n        public X509Certificate[] getAcceptedIssuers() {\n            return new X509Certificate[0];\n        }\n\n        public void checkClientTrusted(X509Certificate[] chain, String authType)\n            throws CertificateException {\n            throw new UnsupportedOperationException();\n        }\n\n        public void checkServerTrusted(X509Certificate[] chain, String authType)\n            throws CertificateException {\n            this.chain = chain;\n            tm.checkServerTrusted(chain, authType);\n        }\n    }\n\n    /*\n     * 调用https的webservice接口，如果不注册证书的话就会报错。下面是注册证书的步骤： 设置证书\n     */\n    static {\n        javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {\n            public boolean verify(String hostname,\n                javax.net.ssl.SSLSession sslSession) {\n                //域名或ip地址\n                if (hostname.equals(\"www.baidu.com\")) {\n                    return true;\n                }\n                return false;\n            }\n        });\n        //第二个参数为证书的路径\n        System.setProperty(\"javax.net.ssl.trustStore\", \".\\\\jssecacerts\");\n        System.setProperty(\"javax.net.ssl.trustStorePassword\", \"changeit\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/http/ssl/HttpsClient.java",
    "content": "//package test.http.ssl;\n//\n//import java.io.BufferedReader;\n//import java.io.File;\n//import java.io.FileInputStream;\n//import java.io.InputStream;\n//import java.io.InputStreamReader;\n//import java.security.KeyStore;\n//\n//import org.apache.http.HttpEntity;\n//import org.apache.http.HttpResponse;\n//import org.apache.http.client.HttpClient;\n//import org.apache.http.client.methods.HttpGet;\n//import org.apache.http.conn.scheme.Scheme;\n//import org.apache.http.conn.ssl.SSLSocketFactory;\n//import org.apache.http.impl.client.DefaultHttpClient;\n//import org.apache.http.util.EntityUtils;\n//\n//public class HttpsClient {\n//\n//    private static final String KEY_STORE_TYPE_JKS = \"jks\";\n//    private static final String KEY_STORE_TYPE_P12 = \"PKCS12\";\n//    private static final String SCHEME_HTTPS = \"https\";\n//    private static final int HTTPS_PORT = 8443;\n//    private static final String HTTPS_URL = \"https://127.0.0.1:8443/\";\n//    private static final String KEY_STORE_CLIENT_PATH = \"D:/ssl/client.p12\";\n//    private static final String KEY_STORE_TRUST_PATH = \"D:/ssl/client.truststore\";\n//    private static final String KEY_STORE_PASSWORD = \"123456\";\n//    private static final String KEY_STORE_TRUST_PASSWORD = \"123456\";\n//\n//    public static void main(String[] args) throws Exception {\n//        ssl();\n//    }\n//\n//    private static void ssl() throws Exception {\n//        HttpClient httpClient = new DefaultHttpClient();\n//        try {\n//            // 加载客户端密钥库\n//            KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);\n//            InputStream ksIn = new FileInputStream(KEY_STORE_CLIENT_PATH);\n//            keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());\n//            ksIn.close();\n//\n//            // 加载客户端信任库\n//            KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_JKS);\n//            InputStream tsIn = new FileInputStream(new File(KEY_STORE_TRUST_PATH));\n//            trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());\n//            tsIn.close();\n//            \n//            // 初始化socketFactory\n//            SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore);\n//            Scheme sch = new Scheme(SCHEME_HTTPS, HTTPS_PORT, socketFactory);\n//            httpClient.getConnectionManager().getSchemeRegistry().register(sch);\n//            HttpGet httpget = new HttpGet(HTTPS_URL);\n//            System.out.println(\"executing request\" + httpget.getRequestLine());\n//            HttpResponse response = httpClient.execute(httpget);\n//            HttpEntity entity = response.getEntity();\n//            System.out.println(\"----------------------------------------\");\n//            System.out.println(response.getStatusLine());\n//            if (entity != null) {\n//                System.out.println(\"Response content length: \"\n//                        + entity.getContentLength());\n//                BufferedReader bufferedReader = new BufferedReader(\n//                        new InputStreamReader(entity.getContent()));\n//                String text;\n//                while ((text = bufferedReader.readLine()) != null) {\n//                    System.out.println(text);\n//                }\n//                bufferedReader.close();\n//            }\n//            EntityUtils.consume(entity);\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        } finally {\n//            httpClient.getConnectionManager().shutdown();\n//        }\n//    }\n//\n//}\n"
  },
  {
    "path": "src/test/java/test/http/ssl/SSLClient.java",
    "content": "package test.http.ssl;\n\nimport java.io.BufferedReader;\nimport java.io.FileInputStream;\nimport java.io.InputStreamReader;\nimport java.io.PrintWriter;\nimport java.net.Socket;\nimport java.security.KeyStore;\n\nimport javax.net.SocketFactory;\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\n\npublic class SSLClient {\n\n    private static String CLIENT_KEY_STORE = SSLClient.class.getClassLoader().getResource(\"\").getPath()+\"/META-INF/client_ks\";\n    private static String CLIENT_KEY_STORE_PASSWORD = \"123456\";\n\n    public static void main(String[] args) throws Exception {\n        // 打印网络通信信息\n        System.setProperty(\"javax.net.debug\", \"ssl,handshake\");\n\n        // 设置客户端对服务端的信任库\n        System.setProperty(\"javax.net.ssl.trustStore\", CLIENT_KEY_STORE);\n        \n        SSLClient client = new SSLClient();\n//        Socket s = client.clientWithoutCert(); // 单向\n        Socket s = client.clientWithCert(); // 双向\n\n        PrintWriter writer = new PrintWriter(s.getOutputStream());\n        BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));\n        writer.println(\"你 好！\");\n        writer.flush();\n        System.out.println(reader.readLine());\n        s.close();\n    }\n\n    // 单向认证\n    private Socket clientWithoutCert() throws Exception {\n        SocketFactory sf = SSLSocketFactory.getDefault();\n        Socket s = sf.createSocket(\"localhost\", 8000);\n        return s;\n    }\n    \n    // 双向认证\n    private Socket clientWithCert() throws Exception {\n        // 加载客户端密钥库\n        KeyStore ks = KeyStore.getInstance(\"jceks\");\n        ks.load(new FileInputStream(CLIENT_KEY_STORE), null);\n        \n        KeyManagerFactory kf = KeyManagerFactory.getInstance(\"SunX509\");\n        kf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());\n        \n        SSLContext context = SSLContext.getInstance(\"TLS\");\n        context.init(kf.getKeyManagers(), null, null);\n\n        SocketFactory factory = context.getSocketFactory();\n        Socket s = factory.createSocket(\"localhost\", 8000);\n        return s;\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/http/ssl/SSLServer.java",
    "content": "package test.http.ssl;\n\nimport java.io.BufferedReader;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStreamWriter;\nimport java.io.PrintWriter;\nimport java.net.Socket;\nimport java.security.KeyManagementException;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.UnrecoverableKeyException;\nimport java.security.cert.CertificateException;\n\nimport javax.net.ServerSocketFactory;\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLServerSocket;\n\npublic class SSLServer extends Thread {\n\n    private static final String SERVER_KEY_STORE = SSLServer.class.getClassLoader().getResource(\"\").getPath()+\"/META-INF/server_ks\";\n    private static final String PWD = \"123456\";\n    private Socket socket;\n    \n    public SSLServer(Socket socket) {\n        this.socket = socket;\n    }\n\n    @Override\n    public void run() {\n        BufferedReader reader = null;\n        PrintWriter writer = null;\n        try {\n            reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));\n            writer = new PrintWriter(new OutputStreamWriter(this.socket.getOutputStream()));\n            \n            String receptStr = reader.readLine();\n            writer.write(receptStr);\n            \n            writer.close();\n            socket.close();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n    \n    public static void main(String[] args) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {\n        // 设置服务端信任库\n        System.setProperty(\"javax.net.ssl.trustStore\", SERVER_KEY_STORE);\n        \n        // 加载服务端密钥库\n        KeyStore keyStore = KeyStore.getInstance(\"jceks\");\n        keyStore.load(new FileInputStream(SERVER_KEY_STORE), null);\n        \n        // 初始化密钥\n        KeyManagerFactory factory = KeyManagerFactory.getInstance(\"SunX509\");\n        factory.init(keyStore, PWD.toCharArray());\n        \n        // 初始化上下文\n        SSLContext context = SSLContext.getInstance(\"TLS\");\n        context.init(factory.getKeyManagers(), null, null);\n        \n        // 创建服务端通信接口\n        ServerSocketFactory serverSocketFactory = context.getServerSocketFactory();\n        SSLServerSocket sslServerSocket = (SSLServerSocket)serverSocketFactory.createServerSocket(8000);\n        //sslServerSocket.setNeedClientAuth(false); // 单向\n        sslServerSocket.setNeedClientAuth(true); // 双向\n        \n        while (true) {\n            new SSLServer(sslServerSocket.accept()).start();\n        }\n    }\n    \n}\n"
  },
  {
    "path": "src/test/java/test/jce/Argon2Test.java",
    "content": "package test.jce;\n\nimport java.io.IOException;\n\nimport org.junit.Ignore;\nimport org.junit.Test;\n\nimport com.google.common.base.Stopwatch;\n\nimport de.mkammerer.argon2.Argon2;\nimport de.mkammerer.argon2.Argon2Factory;\nimport de.mkammerer.argon2.Argon2Helper;\n\npublic class Argon2Test {\n\n    @Test\n    public void test1() throws IOException {\n     // Create instance\n        Argon2 argon2 = Argon2Factory.create();\n\n        // Read password from user\n        char[] password = \"passwd\".toCharArray();\n\n        try {\n            // Hash password\n            String hash = argon2.hash(8, 65536, 1, password);\n            System.out.println(hash);\n            // Verify password\n            if (argon2.verify(hash, password)) {\n                // Hash matches password\n            } else {\n                // Hash doesn't match password\n            }\n        } finally {\n            // Wipe confidential data\n            argon2.wipeArray(password);\n        }\n    }\n    \n    @Test @Ignore\n    public void test2() throws IOException {\n        Argon2 argon2 = Argon2Factory.create();\n        // 1000 = The hash call must take at most 1000 ms\n        // 65536 = Memory cost\n        // 1 = parallelism\n        int iterations = Argon2Helper.findIterations(argon2, 1000, 65536, 1);\n        System.out.println(\"Optimal number of iterations: \" + iterations);\n    }\n    \n    @Test @Ignore\n    public void test3() throws IOException {\n        Stopwatch stopwatch = Stopwatch.createStarted();\n        for (int i = 0; i < 10; i++) {\n            Argon2Factory.create().hash(8, 65536, 1, \"findIterations\".toCharArray());\n        }\n        System.out.println(stopwatch.stop().toString());\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/CryptoProviderTest.java",
    "content": "package test.jce;\n\nimport java.util.Map;\n\nimport cn.ponfee.commons.jce.CryptoProvider;\nimport cn.ponfee.commons.jce.ECParameters;\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.sm.SM2;\nimport cn.ponfee.commons.jce.symmetric.Algorithm;\nimport cn.ponfee.commons.jce.symmetric.Mode;\nimport cn.ponfee.commons.jce.symmetric.Padding;\nimport cn.ponfee.commons.jce.symmetric.SymmetricCryptorBuilder;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class CryptoProviderTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"\\n============================RSA crypt==========================\");\n        CryptoProvider rsa = CryptoProvider.rsaPrivateKeyProvider(\"MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA9pU2mWa+yJwXF1VQb3WL5uk06Rc2jARYPlcV0JK0x4fMXboR9rpMlpJ9cr4B1wbJdBEa8H+kSgbJROFKsmkhFQIDAQABAkAcGiNP1krV+BwVl66EFWRtW5ShH/kiefhImoos7BtYReN5WZyYyxFCAf2yjMJigq2GFm8qdkQK+c+E7Q3lY6zdAiEA/wVfy+wGQcFh3gdFKhaQ12fBYMCtywxZ3Edss0EmxBMCIQD3h4vfENmbIMH+PX5dAPbRfrBFcx77/MxFORMESN0bNwIgL5kJMD51TICTi6U/u4NKtWmgJjbQOT2s5/hMyYg3fBECIEqRc+qUKenYuXg80Dd2VeSQlMunPZtN8b+czQTKaomLAiEA02qUv/p1dT/jc2BDtp9bl8jDiWFg5FNFcH6bBDlwgts=\");\n        String str = MavenProjects.getMainJavaFileAsString(CryptoProvider.class);\n        String data = rsa.encrypt(str);\n        System.out.println(\"加密后：\" + data);\n        System.out.println(\"解密后：\" + rsa.decrypt(data));\n\n        System.out.println(\"\\n============================RSA sign==========================\");\n        String signed = rsa.sign(str);\n        System.out.println(\"签名：\"+signed);\n        System.out.println(\"验签：\"+rsa.verify(str, signed));\n\n        System.out.println(\"\\n============================AES crypt==========================\");\n        CryptoProvider aes = CryptoProvider.symmetricKeyProvider(SymmetricCryptorBuilder.newBuilder(Algorithm.AES, \"z]_5Fi!X$ed4OY8j\".getBytes(), Providers.BC)\n                                                                         .mode(Mode.CBC).parameter(\"SVE<r[)qK`n%zQ'o\".getBytes())\n                                                                         .padding(Padding.PKCS7Padding)\n                                                                         .build());\n        data = aes.encrypt(str);\n        System.out.println(\"加密后：\" + data);\n        System.out.println(\"解密后：\" + aes.decrypt(data));\n\n        System.out.println(\"\\n============================SM2 crypt==========================\");\n        ECParameters ecParameter = ECParameters.secp256k1;\n        Map<String, byte[]> sm2KeyMap = SM2.generateKeyPair(ecParameter);\n        CryptoProvider sm2 = CryptoProvider.sm2PrivateKeyProvider(ecParameter, SM2.getPublicKey(sm2KeyMap), SM2.getPrivateKey(sm2KeyMap));\n        data = sm2.encrypt(str);\n        System.out.println(\"加密后：\" + data);\n        System.out.println(\"解密后：\" + sm2.decrypt(data));\n\n        System.out.println(\"\\n============================SM2 sign==========================\");\n        signed = sm2.sign(str);\n        System.out.println(\"签名：\"+signed);\n        System.out.println(\"验签：\"+sm2.verify(str, signed));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/DigestTest.java",
    "content": "package test.jce;\n\nimport static cn.ponfee.commons.util.SecureRandoms.nextBytes;\n\nimport java.security.Provider;\n\nimport org.apache.commons.codec.binary.Hex;\n\nimport cn.ponfee.commons.jce.DigestAlgorithms;\nimport cn.ponfee.commons.jce.HmacAlgorithms;\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport cn.ponfee.commons.jce.digest.HmacUtils;\nimport cn.ponfee.commons.jce.sm.SM3Digest;\nimport cn.ponfee.commons.jce.sm.SM4;\nimport cn.ponfee.commons.jce.symmetric.Algorithm;\nimport cn.ponfee.commons.jce.symmetric.Mode;\nimport cn.ponfee.commons.jce.symmetric.Padding;\nimport cn.ponfee.commons.jce.symmetric.SymmetricCryptor;\nimport cn.ponfee.commons.jce.symmetric.SymmetricCryptorBuilder;\nimport cn.ponfee.commons.util.SecureRandoms;\n\npublic class DigestTest {\n\n    public static void main(String[] args) {\n        byte[] data = SecureRandoms.nextBytes(1204);\n        byte[] key = SecureRandoms.nextBytes(1204);\n        //System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SHAKE128, Providers.BC, data)));\n        //System.out.println(Hex.encodeHexString(HmacUtils.crypt(key, data, HmacAlgorithms.HmacSHAKE256)));\n\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SM3, Providers.BC, data)));\n        System.out.println(Hex.encodeHexString(SM3Digest.getInstance().doFinal(data)));\n        //System.out.println(Hex.encodeHexString(HmacUtils.crypt(key, data, HmacAlgorithms.HmacSM3)));\n\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SKEIN_512_256, Providers.BC, data)));\n        System.out.println(Hex.encodeHexString(HmacUtils.crypt(key, data, HmacAlgorithms.HmacSKEIN_512_256)));\n\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SHA512, Providers.BC, data)));\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SKEIN_512_512, Providers.BC, data)));\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SKEIN_1024_512, Providers.BC, data)));\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SKEIN_1024_1024, Providers.BC, data)));\n        System.out.println(Hex.encodeHexString(HmacUtils.crypt(key, data, HmacAlgorithms.HmacSKEIN_1024_1024)));\n        \n        System.out.println(\"========================\");\n        Provider bc = Providers.BC;\n        key = nextBytes(16);\n        byte[] iv = nextBytes(16);\n        SymmetricCryptor coder = null;\n        coder = SymmetricCryptorBuilder.newBuilder(Algorithm.SM4, key, bc).mode(Mode.CBC)\n                      .padding(Padding.X9_23Padding).parameter(iv).build();\n        \n        data = \"1234\".getBytes();\n        byte[] encrypted = coder.encrypt(data);\n        System.out.println(new String(coder.decrypt(encrypted)));\n        System.out.println(new String(SM4.decrypt(true, key, iv, encrypted)));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/Paillier.java",
    "content": "package test.jce;\n\n/** \n* This program is free software: you can redistribute it and/or modify it \n* under the terms of the GNU General Public License as published by the Free \n* Software Foundation, either version 3 of the License, or (at your option) \n* any later version. \n* \n* This program is distributed in the hope that it will be useful, but WITHOUT \n* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or \n* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for \n* more details. \n* \n* You should have received a copy of the GNU General Public License along with \n* this program. If not, see <http://www.gnu.org/licenses/>. \n*/\n\nimport java.math.*;\nimport java.util.*;\n\n/** \n * Paillier Cryptosystem <br> \n * <br> \n * References: <br> \n * [1] Pascal Paillier, \n * \"Public-Key Cryptosystems Based on Composite Degree Residuosity Classes,\" \n * EUROCRYPT'99. URL: \n * <a href=\"http://www.gemplus.com/smart/rd/publications/pdf/Pai99pai.pdf\">http: \n * //www.gemplus.com/smart/rd/publications/pdf/Pai99pai.pdf</a><br> \n * \n * [2] Paillier cryptosystem from Wikipedia. URL: \n * <a href=\"http://en.wikipedia.org/wiki/Paillier_cryptosystem\">http://en. \n * wikipedia.org/wiki/Paillier_cryptosystem</a> \n *  \n * @author Kun Liu (kunliu1@cs.umbc.edu) \n * @version 1.0 \n */\npublic class Paillier {\n\n    /** \n     * p and q are two large primes. lambda = lcm(p-1, q-1) = \n     * (p-1)*(q-1)/gcd(p-1, q-1). \n     */\n    private BigInteger p, q, lambda;\n    /** \n     * n = p*q, where p and q are two large primes. \n     */\n    public BigInteger n;\n    /** \n     * nsquare = n*n \n     */\n    public BigInteger nsquare;\n    /** \n     * a random integer in Z*_{n^2} where gcd (L(g^lambda mod n^2), n) = 1. \n     */\n    private BigInteger g;\n    /** \n     * number of bits of modulus \n     */\n    private int bitLength;\n\n    /** \n     * Constructs an instance of the Paillier cryptosystem. \n     *  \n     * @param bitLengthVal \n     *            number of bits of modulus \n     * @param certainty \n     *            The probability that the new BigInteger represents a prime \n     *            number will exceed (1 - 2^(-certainty)). The execution time of \n     *            this constructor is proportional to the value of this \n     *            parameter. \n     */\n    public Paillier(int bitLengthVal, int certainty) {\n        KeyGeneration(bitLengthVal, certainty);\n    }\n\n    /** \n     * Constructs an instance of the Paillier cryptosystem with 512 bits of \n     * modulus and at least 1-2^(-64) certainty of primes generation. \n     */\n    public Paillier() {\n        KeyGeneration(512, 64);\n    }\n\n    /** \n     * Sets up the public key and private key. \n     *  \n     * @param bitLengthVal \n     *            number of bits of modulus. \n     * @param certainty \n     *            The probability that the new BigInteger represents a prime \n     *            number will exceed (1 - 2^(-certainty)). The execution time of \n     *            this constructor is proportional to the value of this \n     *            parameter. \n     */\n    public void KeyGeneration(int bitLengthVal, int certainty) {\n        bitLength = bitLengthVal;\n        /* \n         * Constructs two randomly generated positive BigIntegers that are \n         * probably prime, with the specified bitLength and certainty. \n         */\n        p = new BigInteger(bitLength / 2, certainty, new Random());\n        q = new BigInteger(bitLength / 2, certainty, new Random());\n\n        n = p.multiply(q);\n        nsquare = n.multiply(n);\n\n        g = new BigInteger(\"2\");\n        lambda = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE)).divide(p.subtract(BigInteger.ONE).gcd(q.subtract(BigInteger.ONE)));\n        /* check whether g is good. */\n        if (g.modPow(lambda, nsquare).subtract(BigInteger.ONE).divide(n).gcd(n).intValue() != 1) {\n            System.out.println(\"g is not good. Choose g again.\");\n            System.exit(1);\n        }\n    }\n\n    /** \n     * Encrypts plaintext m. ciphertext c = g^m * r^n mod n^2. This function \n     * explicitly requires random input r to help with encryption. \n     *  \n     * @param m \n     *            plaintext as a BigInteger \n     * @param r \n     *            random plaintext to help with encryption \n     * @return ciphertext as a BigInteger \n     */\n    public BigInteger Encryption(BigInteger m, BigInteger r) {\n        return g.modPow(m, nsquare).multiply(r.modPow(n, nsquare)).mod(nsquare);\n    }\n\n    /** \n     * Encrypts plaintext m. ciphertext c = g^m * r^n mod n^2. This function \n     * automatically generates random input r (to help with encryption). \n     *  \n     * @param m \n     *            plaintext as a BigInteger \n     * @return ciphertext as a BigInteger \n     */\n    public BigInteger Encryption(BigInteger m) {\n        BigInteger r = new BigInteger(bitLength, new Random());\n        return g.modPow(m, nsquare).multiply(r.modPow(n, nsquare)).mod(nsquare);\n\n    }\n\n    /** \n     * Decrypts ciphertext c. plaintext m = L(c^lambda mod n^2) * u mod n, where \n     * u = (L(g^lambda mod n^2))^(-1) mod n. \n     *  \n     * @param c \n     *            ciphertext as a BigInteger \n     * @return plaintext as a BigInteger \n     */\n    public BigInteger Decryption(BigInteger c) {\n        BigInteger u = g.modPow(lambda, nsquare).subtract(BigInteger.ONE).divide(n).modInverse(n);\n        return c.modPow(lambda, nsquare).subtract(BigInteger.ONE).divide(n).multiply(u).mod(n);\n    }\n\n    /** \n     * sum of (cipher) em1 and em2 \n     *  \n     * @param em1 \n     * @param em2 \n     * @return \n     */\n    public BigInteger cipher_add(BigInteger em1, BigInteger em2) {\n        return em1.multiply(em2).mod(nsquare);\n    }\n\n    /** \n     * main function \n     *  \n     * @param str \n     *            intput string \n     */\n    public static void main(String[] str) {\n        /* instantiating an object of Paillier cryptosystem */\n        Paillier paillier = new Paillier();\n        /* instantiating two plaintext msgs */\n        BigInteger m1 = new BigInteger(\"20\");\n        BigInteger m2 = new BigInteger(\"60\");\n        /* encryption */\n        BigInteger em1 = paillier.Encryption(m1);\n        BigInteger em2 = paillier.Encryption(m2);\n        /* printout encrypted text */\n        System.out.println(em1);\n        System.out.println(em2);\n        /* printout decrypted text */\n        System.out.println(paillier.Decryption(em1).toString());\n        System.out.println(paillier.Decryption(em2).toString());\n\n        /* \n         * test homomorphic properties -> D(E(m1)*E(m2) mod n^2) = (m1 + m2) mod \n         * n \n         */\n        // m1+m2,求明文数值的和  \n        BigInteger sum_m1m2 = m1.add(m2).mod(paillier.n);\n        System.out.println(\"original sum: \" + sum_m1m2.toString());\n        // em1+em2，求密文数值的乘  \n        BigInteger product_em1em2 = em1.multiply(em2).mod(paillier.nsquare);\n        System.out.println(\"encrypted sum: \" + product_em1em2.toString());\n        System.out.println(\"decrypted sum: \" + paillier.Decryption(product_em1em2).toString());\n\n        /* test homomorphic properties -> D(E(m1)^m2 mod n^2) = (m1*m2) mod n */\n        // m1*m2,求明文数值的乘  \n        BigInteger prod_m1m2 = m1.multiply(m2).mod(paillier.n);\n        System.out.println(\"original product: \" + prod_m1m2.toString());\n        // em1的m2次方，再mod paillier.nsquare  \n        BigInteger expo_em1m2 = em1.modPow(m2, paillier.nsquare);\n        System.out.println(\"encrypted product: \" + expo_em1m2.toString());\n        System.out.println(\"decrypted product: \" + paillier.Decryption(expo_em1m2).toString());\n\n        //sum test  \n        System.out.println(\"--------------------------------\");\n        Paillier p = new Paillier();\n        BigInteger t1 = new BigInteger(\"21\");\n        System.out.println(t1.toString());\n        BigInteger t2 = new BigInteger(\"50\");\n        System.out.println(t2.toString());\n        BigInteger t3 = new BigInteger(\"50\");\n        System.out.println(t3.toString());\n        BigInteger et1 = p.Encryption(t1);\n        System.out.println(et1.toString());\n        BigInteger et2 = p.Encryption(t2);\n        System.out.println(et2.toString());\n        BigInteger et3 = p.Encryption(t3);\n        System.out.println(et3.toString());\n        BigInteger sum = new BigInteger(\"1\");\n        sum = p.cipher_add(sum, et1);\n        sum = p.cipher_add(sum, et2);\n        sum = p.cipher_add(sum, et3);\n        System.out.println(\"sum: \" + sum.toString());\n        System.out.println(\"decrypted sum: \" + p.Decryption(sum).toString());\n        System.out.println(\"--------------------------------\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/cert/CryptoMessageSyntaxTester.java",
    "content": "package test.jce.cert;\n\nimport java.io.FileInputStream;\nimport java.security.PrivateKey;\nimport java.security.cert.X509Certificate;\n\nimport org.bouncycastle.asn1.ASN1ObjectIdentifier;\nimport org.junit.Test;\n\nimport com.google.common.io.Files;\n\nimport cn.ponfee.commons.http.Http;\nimport cn.ponfee.commons.jce.pkcs.CryptoMessageSyntax;\nimport cn.ponfee.commons.jce.pkcs.PKCS7Signature;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class CryptoMessageSyntaxTester {\n\n    public @Test void testEnvelop() throws Exception {\n        KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream(\"d:/test/subject.pfx\"), \"123456\");\n        X509Certificate cert = resolver.getX509CertChain()[0];\n        PrivateKey privateKey = resolver.getPrivateKey(\"123456\");\n        //byte[] data = Streams.file2bytes(MavenProjects.getTestJavaFile(CMSTester.class));\n        byte[] data = Http.get(\"http://www.baidu.com\").download();\n        long start = System.currentTimeMillis();\n        //System.out.println(Bytes.hexDump(data));\n        System.out.println(\"origin len------------\" + data.length);\n        System.out.println(\"===============================================\");\n\n        //byte[] enveloped = CryptoMessageSyntax.envelop(data, cert, new ASN1ObjectIdentifier(\"1.2.840.113549.3.7\"));\n        byte[] enveloped = CryptoMessageSyntax.envelop(data, cert, new ASN1ObjectIdentifier(\"2.16.840.1.101.3.4.1.2\"));\n        System.out.println(\"cost\" + (System.currentTimeMillis() - start));\n        start = System.currentTimeMillis();\n        //System.out.println(Bytes.hexDump(enveloped));\n        System.out.println(\"enveloped len------------\" + enveloped.length);\n        System.out.println(\"===============================================\");\n\n        byte[] unveloped = CryptoMessageSyntax.unenvelop(enveloped, cert, privateKey);\n        System.out.println(new String(unveloped));\n\n        // 用PKCS7Envelope解会报错\n        //unveloped = PKCS7Envelope.unenvelop(enveloped, cert, privateKey);\n        //System.out.println(new String(unveloped));\n        //System.out.println(\"===============================================\");\n    }\n\n    public @Test void testCMSSign() throws Exception {\n        KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream(\"d:/test/subject.pfx\"), \"123456\");\n        X509Certificate[] certChain = resolver.getX509CertChain();\n        PrivateKey privateKey = resolver.getPrivateKey(\"123456\");\n        byte[] data = Files.toByteArray(MavenProjects.getTestJavaFile(CryptoMessageSyntaxTester.class));\n        System.out.println(\"origin len------------\" + data.length);\n        byte[] signed = CryptoMessageSyntax.sign(data, privateKey, certChain);\n        System.out.println(\"signed len------------\" + signed.length);\n        CryptoMessageSyntax.verify(signed);\n        PKCS7Signature.verify(signed); // PKCS7验证CMS签名\n    }\n\n    public @Test void testPKCS7Sign() throws Exception {\n        KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream(\"d:/test/cas_test.pfx\"), \"1234\");\n        X509Certificate[] certChain = resolver.getX509CertChain();\n        PrivateKey privateKey = resolver.getPrivateKey(\"1234\");\n        byte[] data = Files.toByteArray(MavenProjects.getTestJavaFile(CryptoMessageSyntaxTester.class));\n        System.out.println(\"origin len------------\" + data.length);\n        byte[] signed = PKCS7Signature.sign(privateKey, certChain[0], data, true);\n        System.out.println(\"signed len------------\" + signed.length);\n        PKCS7Signature.verify(signed);\n        CryptoMessageSyntax.verify(signed); // CMS验证PKCS7签名\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/cert/KeyStoreResolverTester.java",
    "content": "package test.jce.cert;\n\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.security.cert.X509Certificate;\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\nimport java.util.Base64;\nimport java.util.Date;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.jce.RSASignAlgorithms;\nimport cn.ponfee.commons.jce.cert.CertSignedVerifier;\nimport cn.ponfee.commons.jce.cert.X509CertGenerator;\nimport cn.ponfee.commons.jce.cert.X509CertInfo;\nimport cn.ponfee.commons.jce.cert.X509CertUtils;\nimport cn.ponfee.commons.jce.pkcs.PKCS1Signature;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType;\nimport cn.ponfee.commons.jce.security.RSACryptor;\nimport cn.ponfee.commons.jce.security.RSACryptor.RSAKeyPair;\nimport cn.ponfee.commons.jce.security.RSAPrivateKeys;\nimport cn.ponfee.commons.jce.security.RSAPublicKeys;\nimport cn.ponfee.commons.resource.ResourceLoaderFacade;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.date.Dates;\n\npublic class KeyStoreResolverTester {\n\n    public @Test void testLoad() {\n        KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, ResourceLoaderFacade.getResource(\"cas_test.pfx\").getStream(), \"1234\");\n        String alias = resolver.listAlias().get(0);\n        test0((RSAPrivateKey)resolver.getPrivateKey(alias, \"1234\"), (RSAPublicKey)resolver.getCertificate(alias).getPublicKey());\n\n        String pem = X509CertUtils.exportToPem(resolver.getX509CertChain()[0]);\n        resolver = new KeyStoreResolver(KeyStoreType.JKS);\n        resolver.setCertificateEntry(\"pem\", X509CertUtils.loadPemCert(pem));\n        System.out.println(resolver.getKeyStore()); \n    }\n\n    public @Test void testCreateCert() throws Exception {\n        Date before = Dates.toDate(\"2017-03-01 00:00:00\"), after = Dates.toDate(\"2027-08-01 00:00:00\");\n        RSAKeyPair p1 = RSACryptor.generateKeyPair(2048), p2 = RSACryptor.generateKeyPair(2048);\n        RSASignAlgorithms alg = RSASignAlgorithms.SHA256withRSA;\n        String caPwd = \"1234\", subjectPwd = \"123456\";\n        String _issuer = \"CN=ca,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN\";\n        String _subject = \"CN=subject,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN\";\n\n        // --------------------------------------------------\n        X509Certificate ccert = X509CertGenerator.createRootCert(null, _issuer, alg, p1.getPrivateKey(), p1.getPublicKey(), before, after);\n        KeyStoreResolver ca = new KeyStoreResolver(KeyStoreType.PKCS12);\n        ca.setKeyEntry(X509CertUtils.getCertInfo(ccert, X509CertInfo.SUBJECT_CN), p1.getPrivateKey(), caPwd, new X509Certificate[] { ccert });\n        test0((RSAPrivateKey)ca.getPrivateKey(\"1234\"), (RSAPublicKey)ca.getCertificate().getPublicKey());\n\n        System.out.println(\"\\n\\n------------------------------------------------------------\\n\\n\");\n\n        // --------------------------------------------------\n        X509Certificate scert = X509CertGenerator.createSubjectCert(ccert, p1.getPrivateKey(), null, _subject, alg, p2.getPrivateKey(), p2.getPublicKey(), before, after);\n        KeyStoreResolver subject = new KeyStoreResolver(KeyStoreType.PKCS12);\n        String scn = X509CertUtils.getCertInfo(scert, X509CertInfo.SUBJECT_CN);\n        subject.setKeyEntry(scn, p2.getPrivateKey(), subjectPwd, new X509Certificate[] { scert, ccert });\n        test0((RSAPrivateKey)subject.getPrivateKey(subjectPwd), (RSAPublicKey)subject.getCertificate().getPublicKey());\n\n        // --------------------------------------------------\n        ca.export(new FileOutputStream(\"d:/test/ca.pfx\"), caPwd);\n        subject.export(new FileOutputStream(\"d:/test/subject.pfx\"), subjectPwd);\n    }\n\n    public @Test void testVerify() throws Exception {\n        X509Certificate root = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream(\"d:/test/ca.pfx\"), \"1234\").getX509CertChain()[0];\n        X509Certificate[] subjectChain = new KeyStoreResolver(KeyStoreType.PKCS12, new FileInputStream(\"d:/test/subject.pfx\"), \"123456\").getX509CertChain();\n\n        // 方法一：获取证书的签名内容\n        System.out.println(PKCS1Signature.verify(root.getTBSCertificate(), root.getSignature(), root));\n        System.out.println(PKCS1Signature.verify(subjectChain[0].getTBSCertificate(), subjectChain[0].getSignature(), subjectChain[1]));\n\n        // 方法二：通过证书接口验证cert.verify(publicKey);\n        CertSignedVerifier.verifyIssuingSign(root, root);\n        CertSignedVerifier.verifyIssuingSign(subjectChain[0], subjectChain[1]);\n    }\n\n    public @Test void test1() throws Exception {\n        Date before = Dates.toDate(\"2017-03-01 00:00:00\"), after = Dates.toDate(\"2027-08-01 00:00:00\");\n        RSAKeyPair p1 = RSACryptor.generateKeyPair(2048), p2 = RSACryptor.generateKeyPair(2048);\n        RSASignAlgorithms alg = RSASignAlgorithms.SHA256withRSA;\n        String caPwd = \"1234\", subjectPwd = \"123456\";\n        String _issuer = \"CN=ca,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN\";\n        String _subject = \"CN=subject,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN\";\n\n        // --------------------------------------------------\n        X509Certificate ccert = X509CertGenerator.createRootCert(null, _issuer, alg, p1.getPrivateKey(), p1.getPublicKey(), before, after);\n        KeyStoreResolver ca = new KeyStoreResolver(KeyStoreType.PKCS12);\n\n        byte[] pkcs8Key = Base64.getDecoder().decode(RSAPrivateKeys.toEncryptedPkcs8(p1.getPrivateKey(), caPwd));\n        ca.setKeyEntry(X509CertUtils.getCertInfo(ccert, X509CertInfo.SUBJECT_CN), pkcs8Key, new X509Certificate[] { ccert });\n        //ca.setKeyEntry(X509CertUtils.getCertInfo(ccert, X509CertInfo.SUBJECT_CN), p1.getPrivateKey(), caPwd, new X509Certificate[] { ccert });\n        test0((RSAPrivateKey) ca.getPrivateKey(\"1234\"), (RSAPublicKey) ca.getCertificate().getPublicKey());\n\n        System.out.println(\"\\n\\n------------------------------------------------------------\\n\\n\");\n\n        // --------------------------------------------------\n        X509Certificate scert =\n            X509CertGenerator.createSubjectCert(ccert, p1.getPrivateKey(), null, _subject, alg, p2.getPrivateKey(), p2.getPublicKey(), before, after);\n        KeyStoreResolver subject = new KeyStoreResolver(KeyStoreType.PKCS12);\n        String scn = X509CertUtils.getCertInfo(scert, X509CertInfo.SUBJECT_CN);\n        pkcs8Key = Base64.getDecoder().decode(RSAPrivateKeys.toEncryptedPkcs8(p2.getPrivateKey(), subjectPwd));\n        subject.setKeyEntry(scn, pkcs8Key, new X509Certificate[] { scert, ccert });\n        //subject.setKeyEntry(scn, p2.getPrivateKey(), subjectPwd, new X509Certificate[] { scert, ccert });\n        test0((RSAPrivateKey) subject.getPrivateKey(subjectPwd), (RSAPublicKey) subject.getCertificate().getPublicKey());\n\n        // --------------------------------------------------\n        ca.export(new FileOutputStream(\"d:/test/ca.pfx\"), caPwd);\n        subject.export(new FileOutputStream(\"d:/test/subject.pfx\"), subjectPwd);\n    }\n\n    public @Test void test2() throws Exception {\n        RSAKeyPair p1 = RSACryptor.generateKeyPair(2048);\n        String caPwd = \"1234\";\n        System.out.println(RSAPrivateKeys.fromEncryptedPkcs8(RSAPrivateKeys.toEncryptedPkcs8(p1.getPrivateKey(), caPwd), caPwd));\n        System.out.println(RSAPrivateKeys.fromEncryptedPkcs8Pem(RSAPrivateKeys.toEncryptedPkcs8Pem(p1.getPrivateKey(), caPwd), caPwd));\n        System.out.println(RSAPrivateKeys.fromPkcs1(RSAPrivateKeys.toPkcs1(p1.getPrivateKey())));\n        System.out.println(RSAPrivateKeys.fromPkcs8(RSAPrivateKeys.toPkcs8(p1.getPrivateKey())));\n\n        System.out.println(RSAPublicKeys.fromPkcs1(RSAPublicKeys.toPkcs1(p1.getPublicKey())));\n        System.out.println(RSAPublicKeys.fromPkcs8(RSAPublicKeys.toPkcs8(p1.getPublicKey())));\n        System.out.println(RSAPublicKeys.fromPkcs8Pem(RSAPublicKeys.toPkcs8Pem(p1.getPublicKey())));\n    }\n\n    // -----------------------------------------------------------------------------------\n    private static void test0(RSAPrivateKey privateKey, RSAPublicKey publicKey) {\n        try {\n            System.out.println(\"=============================加密测试==============================\");\n            //byte[] data = \"加解密测试\".getBytes();\n            byte[] data = IOUtils.toByteArray(ResourceLoaderFacade.getResource(\"2.png\").getStream());\n            System.out.println(\"加密前：\");\n            System.out.println(Bytes.dumpHex(ArrayUtils.subarray(data, 0, 100)));\n            byte[] encodedData = RSACryptor.encrypt(data, publicKey);\n            System.out.println(\"加密后：\");\n            System.out.println(Bytes.dumpHex(ArrayUtils.subarray(encodedData, 0, 100)));\n            System.out.println(\"解密后：\");\n            System.out.println(Bytes.dumpHex(ArrayUtils.subarray(RSACryptor.decrypt(encodedData, privateKey), 0, 100)));\n\n            System.out.println(\"\\n\\n===========================签名测试=========================\");\n            data = Base64.getDecoder().decode(\"\");\n            byte[] signed = RSACryptor.signSha1(data, privateKey);\n            String hex = Hex.encodeHexString(signed);\n            System.out.println(\"签名结果：\" + hex.length() + \" --> \" + hex);\n            System.out.println(\"验签结果：\" + RSACryptor.verifySha1(data, publicKey, signed));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/cert/SM2CertTest.java",
    "content": "package test.jce.cert;\n\nimport java.security.KeyPair;\nimport java.security.KeyPairGenerator;\n\nimport org.bouncycastle.asn1.x500.X500Name;\nimport org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;\nimport org.bouncycastle.operator.ContentSigner;\nimport org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;\nimport org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;\nimport org.junit.Test;\n\npublic class SM2CertTest {\n\n    public @Test void test() {\n        try {\n            genCSR(\"CN=subject,OU=hackwp,O=wp,L=BJ,S=BJ,C=CN\", \"RSA1024\", \"BC\");\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n    \n    public static String genCSR(String subject, String alg, String provider) throws Exception {\n        String signalg = \"\";\n        int alglength = 0;\n        String keyAlg = \"\";\n        if (alg.toUpperCase().equals(\"RSA1024\")) {\n            signalg = \"SHA1WithRSA\";\n            alglength = 1024;\n            keyAlg = \"RSA\";\n        } else if (alg.toUpperCase().equals(\"RSA2048\")) {\n            signalg = \"SHA1WithRSA\";\n            alglength = 2048;\n            keyAlg = \"RSA\";\n        } else if (alg.toUpperCase().equals(\"SM2\")) {\n            signalg = \"SM3withSM2\";\n            alglength = 256;\n            keyAlg = \"SM2\";\n        }\n        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyAlg);\n        keyGen.initialize(alglength);\n        KeyPair kp = keyGen.generateKeyPair();\n        PKCS10CertificationRequestBuilder builder = new PKCS10CertificationRequestBuilder(new X500Name(subject), SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded()));\n        JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signalg);\n        jcaContentSignerBuilder.setProvider(provider);\n        ContentSigner contentSigner = jcaContentSignerBuilder.build(kp.getPrivate());\n        builder.build(contentSigner);\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/cert/TestPem.java",
    "content": "package test.jce.cert;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.security.Key;\nimport java.security.PrivateKey;\nimport java.security.PublicKey;\nimport java.security.Security;\nimport java.util.Base64;\n\nimport javax.crypto.Cipher;\n\nimport org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;\nimport org.bouncycastle.openssl.PEMKeyPair;\nimport org.bouncycastle.openssl.PEMParser;\nimport org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.jce.Providers;\n\npublic class TestPem {\n\n    public @Test void testPEMKeyPair() throws IOException {\n        Security.addProvider(Providers.BC);\n\n        String privateKeyString = \"-----BEGIN RSA PRIVATE KEY-----\\n\" \n            + \"MIICXQIBAAKBgQDKQtJAyCu5FHwDncK2LB/J5ClJhulGggyc7vwtji6TJHtSJfgD\\n\"\n            + \"4TLpHRIHh/cHqf3brhpQtYB9yjKlwogji/OzedY2mdTdSOP8O6suJYu3QENN2xG/\\n\" \n            + \"HvT8UiYK3feVLbJtukhJm7eSuwfMDsjHh4AK7g11fVs6EmY+foh3mjoKLQIDAQAB\\n\"\n            + \"AoGAR8N/wDaFtOx8t/fAv0xWlxaaQ5lXqYm5GfF9jlhVVCXsj5AjOJUtsCJ9ZCis\\n\" \n            + \"0I5TIR/b/Gj5xyf34nJsRViBxbnf6XdLGyXmzsNxWZoWbM70JaqU3iQKm605/EnD\\n\"\n            + \"vPgrI0AMfc/h6Kog0zLrKWKkna+wE5839yMmm7WPqgvxSc0CQQDoud5e3yZu/1e+\\n\" \n            + \"7piFZZl6StAecl+k10Wq5kzJeVQRffDB3JCca65H/W1EZIzEh76pUNr7SYAIIcbK\\n\"\n            + \"jzOdbj1vAkEA3n0AudM3mBzklLEUSHs1ZSqFkUMNP9MNIikwkZ/9Z2AlhW5gnwiv\\n\" \n            + \"dgeXonTqlTFux4e7uyKZoJpJcKAgmMicIwJBAIMl206TalE6y/Po+UKTUr470rSV\\n\"\n            + \"t5hpR/Va+wK+wMVqt3ZIGaZMeFZRVnYoQ7us06EO05iwftoWTrRvpqKdMTkCQBkE\\n\"\n            + \"QzWhy0l+TjFt69Luj6Vtb5FS0cWQbJSfvwdQzwR1qiJjs9eN+XSzC9jHfq0B3uvu\\n\" \n            + \"lixHirClSIayapfjTrMCQQCM8d97py4u9hCdCpsHBDt54dXkHsDA2abNzaPri/YA\\n\"\n            + \"pNFZGrfXKVGSLFOfsuf7Wj+yL7ew6ZVKOMYdJ+zb9Wwv\\n\" \n            + \"-----END RSA PRIVATE KEY-----\"; // 128\n\n        PEMParser privatePemParser = new PEMParser(new StringReader(privateKeyString));\n        Object privateObject = privatePemParser.readObject();\n        System.out.println(\"PEMParser.readObject(): \" + privateObject.getClass());\n        if (privateObject instanceof PEMKeyPair) {\n            PEMKeyPair pemKeyPair = (PEMKeyPair) privateObject;\n            System.out.println(\"private: \" + pemKeyPair.getPrivateKeyInfo());\n            System.out.println(\"public: \" + pemKeyPair.getPublicKeyInfo());\n\n            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(Providers.BC);\n            PublicKey publicKey = converter.getPublicKey(pemKeyPair.getPublicKeyInfo());\n            PrivateKey privateKey = converter.getPrivateKey(pemKeyPair.getPrivateKeyInfo());\n\n            String message = \"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\";\n\n            System.out.println(\"\\npublicKey key encrypt.\");\n            byte[] encripted = encrypt(publicKey, message.getBytes());\n            System.out.println(\"encripted: \" + Base64.getEncoder().encodeToString(encripted));\n            byte[] decrypted = decrypt(privateKey, encripted);\n            System.out.println(\"decrypted: \" + new String(decrypted));\n\n            System.out.println(\"\\nprivate key encrypt.\");\n            encripted = encrypt(privateKey, message.getBytes());\n            System.out.println(\"encripted: \" + Base64.getEncoder().encodeToString(encripted));\n            decrypted = decrypt(publicKey, encripted);\n            System.out.println(\"decrypted: \" + new String(decrypted));\n        }\n        privatePemParser.close();\n    }\n\n    public @Test void testSubjectPublicKeyInfo() throws IOException {\n        String publicKeyString = \"-----BEGIN PUBLIC KEY-----\\n\" \n                               + \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKQtJAyCu5FHwDncK2LB/J5ClJ\\n\"\n                               + \"hulGggyc7vwtji6TJHtSJfgD4TLpHRIHh/cHqf3brhpQtYB9yjKlwogji/OzedY2\\n\" \n                               + \"mdTdSOP8O6suJYu3QENN2xG/HvT8UiYK3feVLbJtukhJm7eSuwfMDsjHh4AK7g11\\n\"\n                               + \"fVs6EmY+foh3mjoKLQIDAQAB\\n\" \n                               + \"-----END PUBLIC KEY-----\";\n\n        PEMParser publicPemParser = new PEMParser(new StringReader(publicKeyString));\n        Object publicObject = publicPemParser.readObject();\n        System.out.println(\"PEMParser.readObject(): \" + publicObject.getClass());\n        if (publicObject instanceof SubjectPublicKeyInfo) {\n            SubjectPublicKeyInfo publicSubjectPublicKeyInfo = (SubjectPublicKeyInfo) publicObject;\n            System.out.println(\"public: \" + publicSubjectPublicKeyInfo);\n        }\n        publicPemParser.close();\n    }\n\n    private static byte[] encrypt(Key pubkey, byte[] data) {\n        try {\n            Cipher rsa;\n            rsa = Cipher.getInstance(\"RSA\", \"BC\");\n            rsa.init(Cipher.ENCRYPT_MODE, pubkey);\n            return rsa.doFinal(data);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    private static byte[] decrypt(Key decryptionKey, byte[] encrypted) {\n        try {\n            Cipher rsa;\n            rsa = Cipher.getInstance(\"RSA\", \"BC\");\n            rsa.init(Cipher.DECRYPT_MODE, decryptionKey);\n            return rsa.doFinal(encrypted);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/cert/X500NameTest.java",
    "content": "package test.jce.cert;\n\n\nimport java.io.IOException;\nimport java.security.cert.X509Certificate;\n\nimport org.bouncycastle.asn1.x500.X500Name;\nimport org.bouncycastle.jce.PrincipalUtil;\nimport org.bouncycastle.jce.X509Principal;\n\nimport cn.ponfee.commons.jce.cert.X509CertUtils;\n\n\npublic class X500NameTest {\n\n    public static void main(String[] args) throws Exception {\n        X509Certificate cert = X509CertUtils.loadX509Cert(Thread.currentThread().getContextClassLoader().getResourceAsStream(\"sm2-1.cer\"));\n        System.out.println(new X509Principal(cert.getIssuerX500Principal().getEncoded()).getName());\n        System.out.println(cert.getIssuerDN().getName());\n        System.out.println(cert.getSubjectDN().getName());\n        //System.out.println(X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(cert).getName()));\n        System.out.println(new sun.security.x509.X500Name(cert.getIssuerX500Principal().getEncoded()).getName());\n        //new X500Name(rDNs)\n        //System.out.println(new X500NameBuilder(),.(cert.getIssuerX500Principal().getEncoded()).getName());\n\n        \n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/cert/X509CertUtilsTester.java",
    "content": "package test.jce.cert;\n\nimport java.io.IOException;\nimport java.security.cert.X509Certificate;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport cn.ponfee.commons.jce.cert.X509CertInfo;\nimport cn.ponfee.commons.jce.cert.X509CertUtils;\nimport cn.ponfee.commons.resource.ResourceLoaderFacade;\n\npublic class X509CertUtilsTester {\n\n    public static void main(String[] args) throws IOException {\n        //X509Certificate cert = X509CertUtils.loadX509Cert(ResourceLoaderFacade.getResource(\"cacert.pem\").getStream());\n        X509Certificate cert = X509CertUtils.loadX509Cert(ResourceLoaderFacade.getResource(\"sm2-1.cer\").getStream());\n        System.out.println(cert);\n        for (X509CertInfo c : X509CertInfo.values())\n            System.out.println(StringUtils.rightPad(c.name(), 15) + X509CertUtils.getCertInfo(cert, c));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/crypto/EncryptTester.java",
    "content": "package test.jce.crypto;\n\nimport static cn.ponfee.commons.util.SecureRandoms.nextBytes;\n\nimport java.nio.charset.StandardCharsets;\nimport java.security.Provider;\nimport java.util.Base64;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.symmetric.Algorithm;\nimport cn.ponfee.commons.jce.symmetric.Mode;\nimport cn.ponfee.commons.jce.symmetric.Padding;\nimport cn.ponfee.commons.jce.symmetric.SymmetricCryptor;\nimport static cn.ponfee.commons.jce.symmetric.SymmetricCryptorBuilder.newBuilder;\n\npublic class EncryptTester {\n\n    public static void main(String[] args) {\n        Provider bc = Providers.BC;\n\n        test(newBuilder(Algorithm.DESede).build());\n        test(newBuilder(Algorithm.RC2, nextBytes(5)).build());\n        test(newBuilder(Algorithm.RC2, nextBytes(16), bc).mode(Mode.ECB).padding(Padding.NoPadding).build());\n        test(newBuilder(Algorithm.AES, nextBytes(16), bc).padding(Padding.ISO10126_Padding).mode(Mode.ECB).build());\n        test(newBuilder(Algorithm.AES, nextBytes(16)).build());\n        test(newBuilder(Algorithm.AES, nextBytes(16), bc).mode(Mode.ECB).padding(Padding.PKCS5Padding).build());\n        test(newBuilder(Algorithm.AES, nextBytes(16), bc).mode(Mode.OFB).padding(Padding.NoPadding).parameter(nextBytes(16)).build());\n        test(newBuilder(Algorithm.AES, nextBytes(32), bc).mode(Mode.CBC).padding(Padding.PKCS7Padding).parameter(nextBytes(16)).build());\n        test(newBuilder(Algorithm.DES, nextBytes(8), bc).mode(Mode.CBC).padding(Padding.NoPadding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.DES, nextBytes(8), bc).build());\n        test(newBuilder(Algorithm.DES, nextBytes(8), bc).mode(Mode.CBC).padding(Padding.PKCS5Padding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.DESede, nextBytes(16), bc).build());\n        test(newBuilder(Algorithm.SM4, nextBytes(16), bc).mode(Mode.CBC).padding(Padding.X9_23Padding).parameter(nextBytes(16)).build());\n        test(newBuilder(Algorithm.DESede, nextBytes(16), bc).mode(Mode.ECB).padding(Padding.PKCS5Padding).build());\n        test(newBuilder(Algorithm.DESede, nextBytes(16), bc).mode(Mode.CBC).padding(Padding.PKCS5Padding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.SEED, 16, bc).mode(Mode.CBC).padding(Padding.PKCS5Padding).parameter(nextBytes(16)).build());\n\n        test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.PKCS5Padding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.PKCS7Padding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.ISO10126_Padding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.ISO10126_2Padding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.ISO7816_4Padding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.X9_23Padding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.TBCPadding).parameter(nextBytes(8)).build());\n\n        test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.CS3Padding).parameter(nextBytes(8)).build());\n        test(newBuilder(Algorithm.SM4, 16, bc).mode(Mode.CBC).padding(Padding.ISO10126_Padding).parameter(nextBytes(16)).build());\n        test(newBuilder(Algorithm.SM4, 16, bc).mode(Mode.CBC).padding(Padding.ISO10126_2Padding).parameter(nextBytes(16)).build());\n\n        //test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.CS1Padding).parameter(nextBytes(8)).build()); // Padding CS1Padding unknown.\n        //test(newBuilder(Algorithm.GOST, 32, bc).mode(Mode.CBC).padding(Padding.CS2Padding).parameter(nextBytes(8)).build()); // Padding CS2Padding unknown.\n    }\n\n    public static void test(SymmetricCryptor cryptor) {\n        byte[] encrypted = cryptor.encrypt(\"12345678\".getBytes()); // 加密\n        byte[] origin = cryptor.decrypt(encrypted); // 解密\n        System.out.println(new String(origin));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/crypto/RSACryptoTester.java",
    "content": "package test.jce.crypto;\n\nimport static cn.ponfee.commons.jce.security.RSACryptor.generateKeyPair;\n\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\nimport java.util.Base64;\nimport java.util.Random;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\n\nimport com.google.common.io.Files;\n\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport cn.ponfee.commons.jce.security.RSACryptor;\nimport cn.ponfee.commons.jce.security.RSACryptor.RSAKeyPair;\nimport cn.ponfee.commons.jce.security.RSAPrivateKeys;\nimport cn.ponfee.commons.jce.security.RSAPublicKeys;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class RSACryptoTester {\n\n    @BeforeClass\n    public static void beforeClass() {\n        Providers.set(Providers.BC);\n    }\n\n    @Before\n    public void before() {\n        Providers.set(Providers.BC);\n    }\n\n    @Test\n    public void test1() throws Exception {\n        RSAKeyPair keyPair = generateKeyPair(4096);\n        \n        // 签名解密－－－－\n        byte [] bytes = \"123456\".getBytes();\n        byte[] signed = RSACryptor.signSha1(bytes, keyPair.getPrivateKey());\n        byte[] decrypted = RSACryptor.decrypt(signed, keyPair.getPublicKey());\n        System.out.println(Hex.encodeHexString(DigestUtils.sha1(bytes))); // 7c4a8d09ca3762af61e59520943dc26494f8941b\n        System.out.println(Hex.encodeHexString(decrypted)); // 3021300906052b0e03021a050004147c4a8d09ca3762af61e59520943dc26494f8941b\n        // -------------\n        \n        System.out.println(keyPair.toPkcs8PrivateKey());\n        System.out.println(keyPair.toPkcs8PublicKey());\n        test(keyPair.getPrivateKey(), RSAPrivateKeys.extractPublicKey(keyPair.getPrivateKey()));\n        \n        test(RSAPrivateKeys.fromPkcs1Pem(RSAPrivateKeys.toPkcs1Pem(RSAPrivateKeys.fromPkcs1(keyPair.toPkcs1PrivateKey()))),\n             RSAPublicKeys.fromPkcs8Pem(RSAPublicKeys.toPkcs8Pem(RSAPublicKeys.fromPkcs1(keyPair.toPkcs1PublicKey()))));\n        \n        test(RSAPrivateKeys.fromPkcs1(RSAPrivateKeys.toPkcs1(keyPair.getPrivateKey())),\n             RSAPublicKeys.fromPkcs1(keyPair.toPkcs1PublicKey()));\n\n        test(RSAPrivateKeys.fromPkcs8(keyPair.toPkcs8PrivateKey()),\n             RSAPublicKeys.fromPkcs8(keyPair.toPkcs8PublicKey()));\n\n        System.out.println(RSAPrivateKeys.fromEncryptedPkcs8Pem(RSAPrivateKeys.toEncryptedPkcs8Pem(keyPair.getPrivateKey(),\"123\"), \"123\"));\n\n        System.out.println(RSAPrivateKeys.toPkcs1(keyPair.getPrivateKey()));\n        System.out.println(RSAPrivateKeys.toPkcs8(keyPair.getPrivateKey()));\n        System.out.println(RSAPrivateKeys.toPkcs1Pem(keyPair.getPrivateKey()));\n        System.out.println(RSAPrivateKeys.toEncryptedPkcs8Pem(keyPair.getPrivateKey(), \"1234\"));\n    }\n\n    @Test\n    public void test2() throws Exception {\n        String privateKeyStr = \"MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAocbCrurZGbC5GArEHKlAfDSZi7gFBnd4yxOt0rwTqKBFzGyhtQLu5PRKjEiOXVa95aeIIBJ6OhC2f8FjqFUpawIDAQABAkAPejKaBYHrwUqUEEOe8lpnB6lBAsQIUFnQI/vXU4MV+MhIzW0BLVZCiarIQqUXeOhThVWXKFt8GxCykrrUsQ6BAiEA4vMVxEHBovz1di3aozzFvSMdsjTcYRRo82hS5Ru2/OECIQC2fAPoXixVTVY7bNMeuxCP4954ZkXp7fEPDINCjcQDywIgcc8XLkkPcs3Jxk7uYofaXaPbg39wuJpEmzPIxi3k0OECIGubmdpOnin3HuCP/bbjbJLNNoUdGiEmFL5hDI4UdwAdAiEAtcAwbm08bKN7pwwvyqaCBC//VnEWaq39DCzxr+Z2EIk=\";\n        String publicKeyStr = \"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKHGwq7q2RmwuRgKxBypQHw0mYu4BQZ3eMsTrdK8E6igRcxsobUC7uT0SoxIjl1WveWniCASejoQtn/BY6hVKWsCAwEAAQ==\";\n        RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs8(privateKeyStr);\n        System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey))); // publicKeyStr\n        test(privateKey, RSAPrivateKeys.extractPublicKey(privateKey));\n\n        RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr);\n        test(privateKey, publicKey);\n\n        test(RSAPublicKeys.inverse(publicKey), RSAPrivateKeys.inverse(privateKey));\n    }\n\n    @Test\n    public void test3() throws Exception {\n        String privateKeyStr = \"MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQABAoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fvxTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeHm7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAFz/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIMV7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATeaTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5AzilpsLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Ozuku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876\";\n        String publicKeyStr = \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB\";\n        RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr);\n        System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey))); // publicKeyStr\n        test(privateKey, RSAPrivateKeys.extractPublicKey(privateKey));\n\n        RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr);\n        test(privateKey, publicKey);\n\n        test(RSAPublicKeys.inverse(publicKey), RSAPrivateKeys.inverse(privateKey));\n    }\n\n    @Test\n    public void test4() throws Exception {\n        String privateKeyStr = \"MIICXAIBAAKBgQDBZ86MIZ2ytsFX9jML+nhYTIC2LdlWzXrN9HV9Ba4yK812S1pgeQpgmt0lFkd378eqb4qb2cC7Z+XT7IOEaSJTp9fP+aKjFG/rKEKG4YPvRD0IKTfm6yDEd9A4bf8a1RxO+5wip9KAGCFdNScwT6DlpDH7gmrzHFWOUPpTsPDNPQIDAQABAoGBAIzz7bl9KmQ8Ay7rNIrPUXPw1YFwasxzVsPRHOsv/6N6/vPuuQBEVsbPNsq3sQB9FURmpFsvWOJ8Nyi7X6JZyPRv9Dal0FuzcLMMU0NLSoW7nAJmzjiU5abS3v5Bj3TfTlAGD7QcXRCM4s5wS18Zm9JPl+vFJkK9Tj1gSoqMhuQVAkEA7g45nL5UgpGny0Ua8xV/PCHq6e6q7VVYFPcWetp8ugJw91ZBJuXzmgp0V+FrmkMuGF2Kx0cauoMMiTqKEosU5wJBAM/791qF07auuXdbxEz9a7ofS3n49sTbMuInsiLWB6m5aRtWb7Wawj2oTvfLOEmIYaMcj1GqFlq7PIYC/rOppDsCQHcQ4Fn4jHZd+cnef5MznlbqM//bYtygAhVCXJkH7LhwfiYHm0CkZQoXzoch9VrL3SNMrhvsAX9mCoAcqnCJ5eMCQCz886RBDmqVoMiQsQV2S7cWzdy0Xax3Paptq7qdUUsFMBcZu1AtCZcMsQgojSRau8PsiZPAltVJau4R98YlC8ECQCMdLS8/lZaOASPocm/fvIno4//NoXrsOi7Wph5vt9OQhQEoYVhdCsk6/28kEy51xAywZ2SjD3IiI/Ygt1uUn8E=\";\n        String publicKeyStr = \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBZ86MIZ2ytsFX9jML+nhYTIC2LdlWzXrN9HV9Ba4yK812S1pgeQpgmt0lFkd378eqb4qb2cC7Z+XT7IOEaSJTp9fP+aKjFG/rKEKG4YPvRD0IKTfm6yDEd9A4bf8a1RxO+5wip9KAGCFdNScwT6DlpDH7gmrzHFWOUPpTsPDNPQIDAQAB\";\n        RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr);\n        System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey))); // publicKeyStr\n        test(privateKey, RSAPrivateKeys.extractPublicKey(privateKey));\n\n        RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr);\n        test(privateKey, publicKey);\n\n        test(RSAPublicKeys.inverse(publicKey), RSAPrivateKeys.inverse(privateKey));\n    }\n\n    @Test\n    public void test5() throws Exception {\n        String privateKeyStr = \"MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQABAoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fvxTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeHm7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAFz/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIMV7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATeaTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5AzilpsLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Ozuku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876\";\n        String publicKeyStr = \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB\";\n        RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr);\n\n        System.out.println(RSAPrivateKeys.toPkcs1(privateKey));\n        System.out.println(RSAPrivateKeys.toPkcs8(privateKey));\n\n        System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey)));\n        System.out.println(RSAPublicKeys.toPkcs8(RSAPublicKeys.fromPkcs8(publicKeyStr)));\n\n        System.out.println(RSAPublicKeys.toPkcs1(RSAPrivateKeys.extractPublicKey(privateKey)));\n        System.out.println(RSAPublicKeys.toPkcs1(RSAPublicKeys.fromPkcs8(publicKeyStr)));\n\n        test(privateKey, RSAPublicKeys.fromPkcs1(RSAPublicKeys.toPkcs1(RSAPublicKeys.fromPkcs8(publicKeyStr))));\n    }\n\n    @Test\n    public void test6() throws Exception {\n        String privateKeyStr = \"MIIEpgIBAAKCAQEAudN1YfE4lOSBoRTZC4ErZ8H2IedOjnSKOk9T1MhmefBVkVwi4emT+walqsowtLw6pHP1whoOBbkqZcQjhz5EgDiMJQ8fmeFYjV5eb+5jZVYefdUKYw1kPqt1aOLysnLpLZRaIQfR+6yHJRbBRWEKWvXWkJ1/3nZulYs4UHpJlPc/1pFyppOsYji/xZj74bCbQkMtasngcuemXphU5zxB/ll2P/jR9x3zxjM8/6/nrF3c4dSFDzMzh8qTAwumT5L3G6tQTErZ4bKsDL/AU1wBUx6Y49iPNxnf8IWG+k7TBK9rIgPIQ/rQUKVJ+3NnoEEWQyPvSwS2yFPnqoDq9qijgwIDAQABAoIBAQC3qCqndkU5wu3rSjOJj0xa6/RbZcTaPowvPR/ZeYbulX28gJdpN/Wtb9BkkBi7SB2dU45dHGsndO5WThffHseNAlZgeiX9bB6c+dvUPIO4L/lK3De71gxxc/xCgarke3XCOpEpfBUo7EdVfLvf2hzl8XryyvcJ43tACazKvVHkCxir/tRkHECEt8ssOibvG2GeWpP3pNhAWcfH7ienVnYBerzLCn7ojtkQM11Z2CkoAMT3D+OcfHewkHhhkYYDJMgEsK6awiZhHHr8kgabzYOhO7CKlSURFOS4WmWQQjiBd6lc4m4IC4l5db4FHd1fZ2Kf/kGD+AKsb7pW/f+azvCBAoGBAPDB6l0hQxIB3ZxCV7z608huSGs/oMUGrwTlrYMnupQTeAqBEDPpO80IEhc1LzQy84fN70Md5JfhqUlINvbclr4bs9qSCIPIl/Cv0FSX5zjswO7xW17dV7K2QxVCturqHA08732EW7WHRda7EHVO0KM5V3XlPwE/1/aKNxljZP+RAoGBAMWXPU1xRCCDtFHFSPrSbYXwyIXtJz32GhYvhdjTIiNxrvrweT+9XBUCJ7vlQwx64MWTziJCYHjJDVlHMa1kLTwbxb4OrYTVUZLvOwIifCLpvmzyKooLNcx8TGmMj7LX8FSAgFZHjznhKz/8iJAXBVlh2mFj5pkLAxWqnk8hKI/TAoGBALwF1XBx/51ak6XrMfZGtYr8hdYsVPRKafkbHk0lg9MM+VzKusqvxaI0QVyajojnmcVfkRILkHEFLV4r5bEZSSijHez+y2OQDwlLZRoLn+qXC34QRFlr54eMTAuYlJ4Vw16bTjXqXm0Afgxa/1l9+fbfW2yZYoEpSRIjkzBirYfhAoGBAJIuCr9Rbap0ZaIdR5mwtjBia6eRRPf1K2WAcRBxWw9H2sFxyPIcAJTWTFkZCtqfyczCRb1YyBB0BbkoD5uMwl522Xt7VmowezIuZMR2iMo3jZcCLfCEzJ9k0g9AW0tfsECD9O5f8JlMeXfUN6AKN/3hg/OLOh29ZOHRoV8/U8fbAoGBAKLvvaAlcSaB57GC3BWc+ckQjCVItM+sunPePY4WSCytT6zjyB6EQBQzfimjY7O4xktDQYb9b9m0WNKAHTLGf+0Otk2iMRhL8dKLICodwoHNR+4izQTzcuHpouMNndRLXTqrMiBIIO0k0LtvQzDffPi1AYw/PNwruvZRmaFsOLWu\";\n        String publicKeyStr = \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudN1YfE4lOSBoRTZC4ErZ8H2IedOjnSKOk9T1MhmefBVkVwi4emT+walqsowtLw6pHP1whoOBbkqZcQjhz5EgDiMJQ8fmeFYjV5eb+5jZVYefdUKYw1kPqt1aOLysnLpLZRaIQfR+6yHJRbBRWEKWvXWkJ1/3nZulYs4UHpJlPc/1pFyppOsYji/xZj74bCbQkMtasngcuemXphU5zxB/ll2P/jR9x3zxjM8/6/nrF3c4dSFDzMzh8qTAwumT5L3G6tQTErZ4bKsDL/AU1wBUx6Y49iPNxnf8IWG+k7TBK9rIgPIQ/rQUKVJ+3NnoEEWQyPvSwS2yFPnqoDq9qijgwIDAQAB\";\n        RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr);\n        System.out.println(RSAPublicKeys.toPkcs8(RSAPrivateKeys.extractPublicKey(privateKey))); // publicKeyStr\n        test(privateKey, RSAPrivateKeys.extractPublicKey(privateKey));\n\n        RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr);\n        test(privateKey, publicKey);\n\n        test(RSAPublicKeys.inverse(publicKey), RSAPrivateKeys.inverse(privateKey));\n    }\n\n    @Test\n    public void test7() {\n        String privateKeyStr =\n            \"MIIEpgIBAAKCAQEAudN1YfE4lOSBoRTZC4ErZ8H2IedOjnSKOk9T1MhmefBVkVwi4emT+walqsowtLw6pHP1whoOBbkqZcQjhz5EgDiMJQ8fmeFYjV5eb+5jZVYefdUKYw1kPqt1aOLysnLpLZRaIQfR+6yHJRbBRWEKWvXWkJ1/3nZulYs4UHpJlPc/1pFyppOsYji/xZj74bCbQkMtasngcuemXphU5zxB/ll2P/jR9x3zxjM8/6/nrF3c4dSFDzMzh8qTAwumT5L3G6tQTErZ4bKsDL/AU1wBUx6Y49iPNxnf8IWG+k7TBK9rIgPIQ/rQUKVJ+3NnoEEWQyPvSwS2yFPnqoDq9qijgwIDAQABAoIBAQC3qCqndkU5wu3rSjOJj0xa6/RbZcTaPowvPR/ZeYbulX28gJdpN/Wtb9BkkBi7SB2dU45dHGsndO5WThffHseNAlZgeiX9bB6c+dvUPIO4L/lK3De71gxxc/xCgarke3XCOpEpfBUo7EdVfLvf2hzl8XryyvcJ43tACazKvVHkCxir/tRkHECEt8ssOibvG2GeWpP3pNhAWcfH7ienVnYBerzLCn7ojtkQM11Z2CkoAMT3D+OcfHewkHhhkYYDJMgEsK6awiZhHHr8kgabzYOhO7CKlSURFOS4WmWQQjiBd6lc4m4IC4l5db4FHd1fZ2Kf/kGD+AKsb7pW/f+azvCBAoGBAPDB6l0hQxIB3ZxCV7z608huSGs/oMUGrwTlrYMnupQTeAqBEDPpO80IEhc1LzQy84fN70Md5JfhqUlINvbclr4bs9qSCIPIl/Cv0FSX5zjswO7xW17dV7K2QxVCturqHA08732EW7WHRda7EHVO0KM5V3XlPwE/1/aKNxljZP+RAoGBAMWXPU1xRCCDtFHFSPrSbYXwyIXtJz32GhYvhdjTIiNxrvrweT+9XBUCJ7vlQwx64MWTziJCYHjJDVlHMa1kLTwbxb4OrYTVUZLvOwIifCLpvmzyKooLNcx8TGmMj7LX8FSAgFZHjznhKz/8iJAXBVlh2mFj5pkLAxWqnk8hKI/TAoGBALwF1XBx/51ak6XrMfZGtYr8hdYsVPRKafkbHk0lg9MM+VzKusqvxaI0QVyajojnmcVfkRILkHEFLV4r5bEZSSijHez+y2OQDwlLZRoLn+qXC34QRFlr54eMTAuYlJ4Vw16bTjXqXm0Afgxa/1l9+fbfW2yZYoEpSRIjkzBirYfhAoGBAJIuCr9Rbap0ZaIdR5mwtjBia6eRRPf1K2WAcRBxWw9H2sFxyPIcAJTWTFkZCtqfyczCRb1YyBB0BbkoD5uMwl522Xt7VmowezIuZMR2iMo3jZcCLfCEzJ9k0g9AW0tfsECD9O5f8JlMeXfUN6AKN/3hg/OLOh29ZOHRoV8/U8fbAoGBAKLvvaAlcSaB57GC3BWc+ckQjCVItM+sunPePY4WSCytT6zjyB6EQBQzfimjY7O4xktDQYb9b9m0WNKAHTLGf+0Otk2iMRhL8dKLICodwoHNR+4izQTzcuHpouMNndRLXTqrMiBIIO0k0LtvQzDffPi1AYw/PNwruvZRmaFsOLWu\";\n        String publicKeyStr =\n            \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudN1YfE4lOSBoRTZC4ErZ8H2IedOjnSKOk9T1MhmefBVkVwi4emT+walqsowtLw6pHP1whoOBbkqZcQjhz5EgDiMJQ8fmeFYjV5eb+5jZVYefdUKYw1kPqt1aOLysnLpLZRaIQfR+6yHJRbBRWEKWvXWkJ1/3nZulYs4UHpJlPc/1pFyppOsYji/xZj74bCbQkMtasngcuemXphU5zxB/ll2P/jR9x3zxjM8/6/nrF3c4dSFDzMzh8qTAwumT5L3G6tQTErZ4bKsDL/AU1wBUx6Y49iPNxnf8IWG+k7TBK9rIgPIQ/rQUKVJ+3NnoEEWQyPvSwS2yFPnqoDq9qijgwIDAQAB\";\n        RSAPrivateKey privateKey = RSAPrivateKeys.fromPkcs1(privateKeyStr);\n        RSAPublicKey publicKey = RSAPublicKeys.fromPkcs8(publicKeyStr);\n\n        byte[] data = \"1234\".getBytes();\n        String sha1 = DigestUtils.sha1Hex(data);\n        byte[] signature = RSACryptor.signSha1(data, privateKey);\n        byte[] array = RSACryptor.decrypt(signature, publicKey);\n        System.out.println(sha1);\n        System.out.println(Hex.encodeHexString(array));\n    }\n\n    @Test // RSA乘法同态加密\n    public void test8() {\n        RSAKeyPair kp = RSACryptor.generateKeyPair(1024);\n        RSAPublicKey publicKey = kp.getPublicKey();\n        RSAPrivateKey privateKey = kp.getPrivateKey();\n\n        BigInteger n = privateKey.getModulus();\n        BigInteger d = privateKey.getPrivateExponent();\n        BigInteger e = publicKey.getPublicExponent();\n\n        Random ran = new Random();\n        long num1 = ran.nextInt(65537), num2 = ran.nextInt(65537), num3 = ran.nextInt(65537), product = num1*num2*num3;\n        BigInteger r1 = new BigInteger(num1 + \"\").modPow(e, n);\n        BigInteger r2 = new BigInteger(num2 + \"\").modPow(e, n);\n        BigInteger r3 = new BigInteger(num3 + \"\").modPow(e, n);\n        Assert.assertEquals(product, r1.multiply(r2).multiply(r3).modPow(d, n).longValue()); // num1*num2*num3\n        System.out.println(product);\n    }\n\n    private static void test(RSAPrivateKey privateKey, RSAPublicKey publicKey) throws IOException {\n        byte[] data = Files.toByteArray(MavenProjects.getTestJavaFile(RSACryptoTester.class));\n        System.out.println(\"=============================加密测试==============================\");\n        long i = System.currentTimeMillis();\n        System.out.println(\"原文：\");\n        System.out.println(Bytes.dumpHex(ArrayUtils.subarray(data, 0, 100)));\n        byte[] encodedData = RSACryptor.encrypt(data, publicKey);\n        System.out.println(\"密文：\");\n        System.out.println(Bytes.dumpHex(ArrayUtils.subarray(encodedData, 0, 100)));\n        System.out.println(\"解密：\");\n        System.out.println(Bytes.dumpHex(ArrayUtils.subarray(RSACryptor.decrypt(encodedData, privateKey), 0, 100)));\n        \n        System.out.println(\"=============================加密测试==============================\");\n        i = System.currentTimeMillis();\n        System.out.println(\"原文：\");\n        System.out.println(Bytes.dumpHex(ArrayUtils.subarray(data, 0, 100)));\n        encodedData = RSACryptor.encrypt(data, privateKey);\n        System.out.println(\"密文：\");\n        System.out.println(Bytes.dumpHex(ArrayUtils.subarray(encodedData, 0, 100)));\n        System.out.println(\"解密：\");\n        System.out.println(Bytes.dumpHex(ArrayUtils.subarray(RSACryptor.decrypt(encodedData, publicKey), 0, 100)));\n\n        System.out.println(\"===========================签名测试=========================\");\n        data = Base64.getDecoder().decode(\"\");\n        byte[] signed = RSACryptor.signSha1(data, privateKey);\n        System.out.println(\"签名数据：len->\" + signed.length + \" ， b64->\" + Base64.getEncoder().encodeToString(signed));\n        System.out.println(\"验签结果：\" + RSACryptor.verifySha1(data, publicKey, signed));\n\n        System.out.println(\"cost time: \" + (System.currentTimeMillis() - i));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/demo/CertService.java",
    "content": "//package test.jce.demo;\n//\n//import java.util.Map;\n//import java.util.Date;\n//import java.util.Vector;\n//import java.util.HashMap;\n//import java.util.Hashtable;\n//import java.math.BigInteger;\n//import java.security.KeyPair;\n//import java.security.Security;\n//import java.security.PublicKey;\n//import java.security.Signature;\n//import java.io.FileOutputStream;\n//import java.security.PrivateKey;\n//import org.bouncycastle.asn1.DERSet;\n//import java.security.KeyPairGenerator;\n//import org.bouncycastle.asn1.DERUTCTime;\n//import org.bouncycastle.asn1.DERInteger;\n//import org.bouncycastle.asn1.DERSequence;\n//import org.bouncycastle.asn1.DERBitString;\n//import org.bouncycastle.asn1.x500.X500Name;\n//import org.bouncycastle.asn1.x509.KeyUsage;\n//import org.bouncycastle.asn1.x509.Attribute;\n//import org.bouncycastle.asn1.DERTaggedObject;\n//import org.bouncycastle.util.encoders.Base64;\n//import org.bouncycastle.asn1.x509.GeneralName;\n//import org.bouncycastle.asn1.x509.GeneralNames;\n//import org.bouncycastle.asn1.x509.CRLDistPoint;\n//import org.bouncycastle.asn1.x509.KeyPurposeId;\n//import org.bouncycastle.asn1.DERPrintableString;\n//import org.bouncycastle.asn1.DERGeneralizedTime;\n//import org.bouncycastle.asn1.x509.GeneralSubtree;\n//import org.bouncycastle.asn1.x509.PolicyMappings;\n//import org.bouncycastle.asn1.DERObjectIdentifier;\n//import org.bouncycastle.asn1.ASN1EncodableVector;\n//import org.bouncycastle.asn1.x509.X509Extensions;\n//import org.bouncycastle.asn1.x509.NameConstraints;\n//import org.bouncycastle.asn1.x509.ExtendedKeyUsage;\n//import org.bouncycastle.asn1.x509.BasicConstraints;\n//import org.bouncycastle.asn1.x509.AccessDescription;\n//import org.bouncycastle.asn1.x509.PolicyInformation;\n//import org.bouncycastle.asn1.x509.DistributionPoint;\n//import org.bouncycastle.asn1.x509.AlgorithmIdentifier;\n//import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;\n//import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;\n//import org.bouncycastle.asn1.x509.DistributionPointName;\n//import org.bouncycastle.asn1.x509.PrivateKeyUsagePeriod;\n//import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;\n//import org.bouncycastle.asn1.x509.TBSCertificateStructure;\n//import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;\n//import org.bouncycastle.jce.provider.BouncyCastleProvider;\n//import org.bouncycastle.asn1.x509.X509CertificateStructure;\n//import org.bouncycastle.jce.provider.X509CertificateObject;\n//import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;\n//import org.bouncycastle.asn1.x509.SubjectDirectoryAttributes;\n//\n//public class CertService {\n//\n//    public static void main(String[] agrs) throws Exception {\n//\n//        Security.addProvider(new BouncyCastleProvider()); // 加载BC Provider\n//\n//        int certValidity = 1; // 证书有效期\n//\n//        X500Name subject = new X500Name(\"CN=root,O=O,OU=OU\"); //证书主题\n//\n//        X500Name issuer = new X500Name(\"CN=root,O=O,OU=OU\"); //证书颁发者\n//\n//        KeyPair kp = genKeyPair(\"RSA\"); // 产生RSA算法密钥对\n//\n//        PrivateKey priKey = kp.getPrivate();\n//        PublicKey pubKey = kp.getPublic();\n//\n//        // 签发根证书\n//        TBSCertificateStructure tbsCert = createTbsCert(certValidity, issuer, subject, pubKey, pubKey);\n//\n//        // 签名算法\n//        AlgorithmIdentifier alg = tbsCert.getSignature();\n//\n//        // 对证书主题进行签名\n//        byte[] signData = sign(pubKey.getAlgorithm(), tbsCert.getEncoded(), priKey);\n//\n//        // 构建证书\n//        ASN1EncodableVector asn1Vector = new ASN1EncodableVector();\n//        asn1Vector.add(tbsCert.getDERObject());\n//        asn1Vector.add(alg.getDERObject());\n//        asn1Vector.add(new DERBitString(signData));\n//        X509CertificateObject cert = new X509CertificateObject(new X509CertificateStructure(new DERSequence(asn1Vector)));\n//\n//        // 打印证书的base64编码\n//        System.out.println(\"certBuf:\"\n//            + new String(Base64.encode(cert.getEncoded())));\n//        cert.verify(cert.getPublicKey()); // 验证签名,无异常验签通过\n//\n//        FileOutputStream fos = new FileOutputStream(\"cert.cer\");\n//        fos.write(cert.getEncoded());\n//        fos.flush();\n//        fos.close();\n//    }\n//\n//    // 创建证书\n//    public static TBSCertificateStructure createTbsCert(int certValidity,\n//        X500Name IssuerName, X500Name subjectName, PublicKey certPubKey,\n//        PublicKey issuerKey) throws Exception {\n//        // 证书有效期\n//        Date notBefore = new Date();\n//        long validity = certValidity * 1000 * 60 * 60 * 24;\n//        Date notAfter = new Date(notBefore.getTime() + validity);\n//\n//        // 证书公钥信息\n//        SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfo.getInstance(certPubKey.getEncoded());\n//\n//        // 组装证书主体\n//        V3TBSCertificateGenerator genCert = new V3TBSCertificateGenerator();\n//        genCert.setStartDate(new DERUTCTime(notBefore));\n//        genCert.setSubject(new X500Name(\"CN=root,O=O,OU=OU\"));\n//        genCert.setIssuer(new X500Name(\"CN=root,O=O,OU=OU\"));\n//        genCert.setEndDate(new DERUTCTime(notAfter));\n//        genCert.setSerialNumber(new DERInteger((int) System.currentTimeMillis()));\n//        genCert.setSubjectPublicKeyInfo(pubInfo);\n//        genCert.setSignature(new AlgorithmIdentifier(\"1.2.840.113549.1.1.5\")); // SHA1withRSA\n//        X509Extensions exts = genCertExtensions(issuerKey, certPubKey); // 创建证书扩展项\n//        genCert.setExtensions(exts);\n//\n//        return genCert.generateTBSCertificate();\n//    }\n//\n//    @SuppressWarnings(\"deprecation\")\n//    public static X509Extensions genCertExtensions(PublicKey issuerKey,\n//        PublicKey subjectKey) throws Exception {\n//        X509ExtensionsGenerator extGen = new X509ExtensionsGenerator();\n//\n//        /**\n//         * 基本用途限制\n//         * \n//         * BasicConstraints := SEQUENCE { cA BOOLEAN DEFAULT FALSE, 是否是CA证书\n//         * pathLenConstraint INTEGER (0..MAX) OPTIONAL 证书链长度约束 }\n//         */\n//        BasicConstraints basicConstraints = new BasicConstraints(false, 0);\n//        extGen.addExtension(X509Extensions.BasicConstraints, true, basicConstraints);\n//\n//        /**\n//         * 密钥用法 The KeyUsage object.\n//         * \n//         *  id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }\n//         * \n//         *  KeyUsage ::= BIT STRING {\n//         *   digitalSignature (0), 数据验签：除了签发证书/签发CRL之外的各种数字签名操作，\n//         *     数据完整性、身份鉴别、数据源鉴别。检查算法可以做签名  digitalSignature位被断\n//         *     言，当主题公开密钥用一数字的签名算法来支持安全 服务而非抗抵赖性（位1）、签名\n//         *     证书（位5）或者签名撤销信息（位6） 的时候。数字的签名算法常常为实体和数据起源\n//         *     （做）完整性验证。\n//         *     \n//         *   nonRepudiation   (1), 不可抵赖性:证书对应的私钥，用于生成非否认的证据，证书\n//         *      用于验证非否认证据。\n//         *      \n//         *   keyEncipherment  (2), 密钥加密：用于加密传输其他的密钥。检查可以加密密钥\n//         *        keyEncipherment位被断言，当主题公开密钥被用于密钥传输的时候。例如，当一\n//         *        RSA密钥用于密钥管理时候，那么这位将被断言。\n//         *        \n//         *   dataEncipherment (3), 数据加密：用于直接加解密应用数据，\n//         *     通常都是公钥->对称密钥->应用数据。一般很少用这种方式的应用，因为：在密钥长度\n//         *     安全的情况下，公钥密钥计算都是慢于对称密钥计算。检查可以加密数据  当主题公开密\n//         *     钥用于（除了密码学的密钥）将用户数据加密使用的时候，dataEncipherment位被断言。\n//         *   keyAgreement     (4), 密钥协商:在通信方之间协商对称密钥,例如：TLS、\n//         *      Diffie-Hellman的密钥协商。不同于keyEncipherment. KeyEncipherment是直接对\n//         *      Session Key进行加密  KeyAgreement是协商，别公钥加密的数据并不是直接作为密钥，\n//         *      而是经过了一个多次步骤的过程，再导出Session Key。\n//         *      \n//         *   keyCertSign      (5), 签发证书:用于签发CA证书。 keyAgreement位被断言，当主题\n//         *       公开密钥为用于密钥协议的时候。例如，当一Diffie Hellman密钥是要为密钥管理被使\n//         *       用的时候，那么这位将被断言。\n//         *     \n//         *   cRLSign          (6), 签发crl：签发CRL，CA或者CRL Issuer\n//         *     \n//         *   encipherOnly     (7), 证书公钥在密钥协商过程中，仅仅进行加密计算，配合\n//         *       KeyAgreement用法才有意义\n//         *     \n//         *   decipherOnly     (8) }证书公钥在密钥协商过程中，仅仅进行解密计算，配合\n//         *       KeyAgreement用法才有意义\n//         *  }\n//         * \n//         */\n//\n//        int usage = KeyUsage.digitalSignature;\n//        usage += KeyUsage.nonRepudiation;\n//        usage += KeyUsage.keyEncipherment;\n//        usage += KeyUsage.dataEncipherment;\n//        usage += KeyUsage.keyAgreement;\n//        usage += KeyUsage.keyCertSign;\n//        usage += KeyUsage.cRLSign;\n//        usage += KeyUsage.encipherOnly;\n//        usage += KeyUsage.decipherOnly;\n//\n//        KeyUsage keyUsage = new KeyUsage(usage);\n//\n//        extGen.addExtension(X509Extensions.KeyUsage, true, keyUsage);\n//\n//        /**\n//         * 增强型密钥用法 The extendedKeyUsage object.\n//         * \n//         * <pre>\n//         *      extendedKeyUsage ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId\n//         * </pre>\n//         */\n//        ASN1EncodableVector asn1ExtKeyUsage = new ASN1EncodableVector();\n//        asn1ExtKeyUsage.add(KeyPurposeId.anyExtendedKeyUsage); // 任何用途\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_serverAuth); // SSL的服务器认证\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_clientAuth); // SSL的客户端认证\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_codeSigning); // 代码签名\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_emailProtection); // 电子邮件的加解密、签名等\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_ipsecEndSystem); //\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_ipsecTunnel); //\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_ipsecUser); //\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_timeStamping); // 时间戳 认证\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_OCSPSigning); // ocsp证书认证\n//        /*\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_dvcs);\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_sbgpCertAAServerAuth);\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_scvp_responder);\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_eapOverPPP);\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_eapOverLAN);\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_scvpServer);\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_scvpClient);\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_ipsecIKE);\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_capwapAC);\n//         * asn1ExtKeyUsage.add(KeyPurposeId.id_kp_capwapWTP);\n//         */\n//        asn1ExtKeyUsage.add(KeyPurposeId.id_kp_smartcardlogon);\n//\n//        ExtendedKeyUsage extendedKeyUsage = new ExtendedKeyUsage(new DERSequence(asn1ExtKeyUsage));\n//\n//        extGen.addExtension(X509Extensions.ExtendedKeyUsage, true, extendedKeyUsage);\n//\n//        /**\n//         * 证书撤销：已证书扩展的形式，给出了“检查本证书所需要的CRL文件，到上面地方获取” CRL\n//         * DP中的信息是有多个DisributionPoint组成：每个DisributionPoint都存放CRL，\n//         * CA可以在多个地方存放CRL\n//         * \n//         * crl Produce an object suitable for an ASN1OutputStream.\n//         * \n//         * <pre>\n//         * CRLDistPoint ::= SEQUENCE SIZE {1..MAX} OF DistributionPoint\n//         * </pre>\n//         * \n//         * -- CRL distribution points extension OID and syntax\n//         * \n//         * id-ce-cRLDistributionPoints OBJECT IDENTIFIER ::= {id-ce 31}\n//         * \n//         * CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint\n//         * \n//         * DistributionPoint ::= SEQUENCE { distributionPoint [0]\n//         * DistributionPointName OPTIONAL, reasons [1] ReasonFlags OPTIONAL,\n//         * cRLIssuer [2] GeneralNames OPTIONAL }\n//         * \n//         * DistributionPointName ::= CHOICE { fullName [0] GeneralNames,\n//         * nameRelativeToCRLIssuer [1] RelativeDistinguishedName }\n//         */\n//        Map<Integer, String> map = new HashMap<Integer, String>();\n//        // map.put(GeneralName.otherName, \"cn=otherName\");\n//        map.put(GeneralName.rfc822Name, \"cn=rfc822Name\");\n//        map.put(GeneralName.dNSName, \"192.168.30.241\");\n//        // map.put(GeneralName.x400Address, \"cn=x400Address\");\n//        map.put(GeneralName.directoryName, \"cn=root\");\n//        // map.put(GeneralName.ediPartyName, \"cn=ediPartyName\");\n//        map.put(GeneralName.uniformResourceIdentifier, \"http://certService/crl\");\n//        map.put(GeneralName.iPAddress, \"192.168.30.24\");\n//        map.put(GeneralName.registeredID, \"1.1.0.2.1\");\n//\n//        DistributionPoint[] dps = new DistributionPoint[map.size()];\n//        int i = 0;\n//        for (int key : map.keySet()) {\n//            GeneralName gn = null;\n//            if (key == GeneralName.otherName || key == GeneralName.ediPartyName) {\n//                continue;\n//            } else if (key == GeneralName.x400Address) {\n//                continue;\n//            } else {\n//                gn = new GeneralName(key, map.get(key));\n//            }\n//\n//            GeneralNames gns = new GeneralNames(gn);\n//            DistributionPointName dpn = new DistributionPointName(gns);\n//            DistributionPoint dp = new DistributionPoint(dpn, null, null);\n//            dps[i] = dp;\n//            i++;\n//        }\n//\n//        CRLDistPoint crlDistPoint = new CRLDistPoint(dps);\n//\n//        extGen.addExtension(X509Extensions.CRLDistributionPoints, true, crlDistPoint);\n//\n//        /**\n//         * 增量crl Produce an object suitable for an ASN1OutputStream.\n//         * \n//         * <pre>\n//         * CRLDistPoint ::= SEQUENCE SIZE {1..MAX} OF DistributionPoint\n//         * </pre>\n//         */\n//\n//        extGen.addExtension(X509Extensions.FreshestCRL, false, crlDistPoint);\n//        /**\n//         * 主题备用名称\n//         */\n//        GeneralName subjectAlternativeName = new GeneralName(GeneralName.directoryName, map.get(GeneralName.directoryName));\n//        extGen.addExtension(X509Extensions.SubjectAlternativeName, false, new DERSequence(subjectAlternativeName));\n//\n//        /**\n//         * 颁发者备用名称 ： 放置签发者的各种不同的命名，map 是各种名称形式\n//         */\n//        GeneralName issuerAlternativeName = new GeneralName(GeneralName.directoryName, map.get(GeneralName.directoryName));\n//        extGen.addExtension(X509Extensions.IssuerAlternativeName, false, new DERSequence(issuerAlternativeName));\n//\n//        /**\n//         * 密钥周期 ： 对应私钥的使用期限\n//         * \n//         * <pre>\n//         *    PrivateKeyUsagePeriod ::= SEQUENCE {\n//         *      notBefore       [0]     GeneralizedTime OPTIONAL,\n//         *      notAfter        [1]     GeneralizedTime OPTIONAL }\n//         * </pre>\n//         */\n//        Date notAfter = new Date();\n//        Date notBefter = new Date();\n//        notBefter.setYear(notAfter.getYear() + 10);\n//        DERGeneralizedTime notAfterKey = new DERGeneralizedTime(notAfter);\n//        DERGeneralizedTime notBefterKey = new DERGeneralizedTime(notBefter);\n//\n//        DERTaggedObject dtoNotBefterKey = new DERTaggedObject(false, 0, notBefterKey);\n//        DERTaggedObject dtoNotAfterKey = new DERTaggedObject(false, 1, notAfterKey);\n//\n//        ASN1EncodableVector aevPriKeyUsagePeriod = new ASN1EncodableVector();\n//        aevPriKeyUsagePeriod.add(dtoNotBefterKey);\n//        aevPriKeyUsagePeriod.add(dtoNotAfterKey);\n//        PrivateKeyUsagePeriod pkup = PrivateKeyUsagePeriod.getInstance(new DERSequence(aevPriKeyUsagePeriod));\n//        extGen.addExtension(X509Extensions.PrivateKeyUsagePeriod, false, pkup);\n//\n//        /**\n//         * 策略限制 PolicyConstraints ::= SEQUENCE { requireExplicitPolicy [0]\n//         * SkipCerts OPTIONAL, inhibitPolicyMapping [1] SkipCerts OPTIONAL }\n//         */\n//\n//        int requireExplicitPolicy = 10; // 表明额外的证书的数量\n//        int inhibitPolicyMapping = 10; // 应用程序支持数量\n//        ASN1EncodableVector pcVector = new ASN1EncodableVector();\n//        pcVector.add(new DERTaggedObject(false, 0, new DERInteger(requireExplicitPolicy)));\n//        pcVector.add(new DERTaggedObject(false, 1, new DERInteger(inhibitPolicyMapping)));\n//\n//        extGen.addExtension(X509Extensions.PolicyConstraints, false, new DERSequence(pcVector));\n//\n//        /**\n//         * 禁止任何策略：\n//         * 扩展项的值是整数N,N表示：在证书路径中，本证书之下的N个证书可带有Any-Policy的证书\n//         * （N+1之下的证书就不能有Any-policy）\n//         * \n//         * id-ce-inhibitAnyPolicy OBJECT IDENTIFIER ::= { id-ce 54 }\n//         * \n//         * InhibitAnyPolicy ::= SkipCerts\n//         * \n//         * SkipCerts ::= INTEGER (0..MAX)\n//         */\n//        int inhibitAnyPolicy = 10;\n//        extGen.addExtension(X509Extensions.InhibitAnyPolicy, false, new DERInteger(inhibitAnyPolicy));\n//\n//        /**\n//         * 策略映射 扩展项仅仅存在于交叉证书中，说明了不同CA域之间的CP等级的相互映射关系\n//         * \n//         * PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE {\n//         * issuerDomainPolicy CertPolicyId, subjectDomainPolicy CertPolicyId }\n//         */\n//        Hashtable<String, String> policyHashMap = new Hashtable<String, String>();\n//        policyHashMap.put(\"1.1.1.2.3.1\", \"1.1.1.2.3.4\");\n//        policyHashMap.put(\"1.1.1.2.3.2\", \"1.1.1.2.3.5\");\n//\n//        PolicyMappings pms = new PolicyMappings(policyHashMap);\n//\n//        extGen.addExtension(X509Extensions.PolicyMappings, false, pms);\n//\n//        /**\n//         * 使用者密钥标示符 SubjectKeyIdentifier ::= KeyIdentifier\n//         */\n//        extGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectKey.getEncoded())));\n//        /**\n//         * 颁发者密钥标示符 IssuerKeyIdentifier ::= KeyIdentifier\n//         */\n//\n//        extGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectKey.getEncoded())));\n//        /**\n//         * 主题目录属性 原则上可以加入与Subject有关信息 因为使用了Atrribute Type的OID的、然后说明相对应的值\n//         */\n//        // http://asn1.elibel.tm.fr/cgi-bin/oid/display?oid=1.3.6.1.5.5.7.9&action=display\n//        // PKIX personal data gender\n//        String genderOidStr = \"1.3.6.1.5.5.7.9.4\";\n//\n//        // PKIX personal data dateOfBirth\n//        String dateOfBirthOidStr = \"1.3.6.1.5.5.7.9.1\";\n//\n//        // 2.5.4.20 - id-at-telephoneNumber\n//        // http://www.alvestrand.no/objectid/2.5.4.html\n//        String streetAddressOidStr = \"2.5.4.9\";\n//\n//        String telephoneNumberOidStr = \"2.5.4.20\";\n//        // http://oid.elibel.tm.fr/0.9.2342.19200300.100.1.41\n//        String mobileTelephoneNumberOidStr = \"0.9.2342.19200300.100.1.41\";\n//\n//        Vector<Attribute> attributes = new Vector<Attribute>();\n//\n//        Attribute genderAttribute = new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString(\"汉族\".getBytes(\"UTF-8\"))));\n//        Attribute dateOfBirthAttribute =\n//            new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString(\"1992-02-20\".getBytes(\"UTF-8\"))));\n//        Attribute streetAddressAttribute =\n//            new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString(\"北京市大王庄胡同13号\".getBytes(\"UTF-8\"))));\n//        Attribute telephoneNumberAttribute =\n//            new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString(\"010-82961368\".getBytes(\"UTF-8\"))));\n//        Attribute mobileTelephoneNumberAttribute =\n//            new Attribute(new DERObjectIdentifier(genderOidStr), new DERSet(new DERPrintableString(\"13843838438\".getBytes(\"UTF-8\"))));\n//\n//        attributes.add(genderAttribute);\n//        attributes.add(dateOfBirthAttribute);\n//        attributes.add(streetAddressAttribute);\n//        attributes.add(telephoneNumberAttribute);\n//        attributes.add(mobileTelephoneNumberAttribute);\n//\n//        // 构建主题目录属性\n//        SubjectDirectoryAttributes sda = new SubjectDirectoryAttributes(attributes);\n//\n//        extGen.addExtension(X509Extensions.SubjectDirectoryAttributes, false, sda);\n//\n//        /**\n//         * 名称限制 ： 只在ca中出现，并不是出现在用户证书中，名称限制同时对Subject和SubjeectAltertiveName\n//         * 起作用。可以对多种命名进行限制如Email、DNS、X509 DN 等\n//         * 如果发现用户证书中的命名（Subject和SubjeectAltertiveName）与CA证书中的Name\n//         * Constraints违背，就直接认为该证书无效。必须满足Name Constraints 中的多个不同类型命名的限制\n//         * NameConstraints ::= SEQUENCE { permittedSubtrees [0] GeneralSubtrees\n//         * OPTIONAL, excludedSubtrees [1] GeneralSubtrees OPTIONAL }\n//         * GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree\n//         */\n//        Vector<GeneralSubtree> permitted = new Vector<GeneralSubtree>(); // 允许名称列表\n//        Vector<GeneralSubtree> excluded = new Vector<GeneralSubtree>(); // 限制名称列表\n//\n//        // 添加允许名称\n//        GeneralName permitteedNcGn = new GeneralName(GeneralName.directoryName, map.get(GeneralName.directoryName));\n//        GeneralSubtree permittedGsNcGn = new GeneralSubtree(permitteedNcGn, BigInteger.ONE, BigInteger.TEN);\n//        permitted.add(permittedGsNcGn);\n//        // 添加限制名称\n//        GeneralName excludedNcGn = new GeneralName(GeneralName.directoryName, map.get(GeneralName.directoryName));\n//        GeneralSubtree excludedGsNcGn = new GeneralSubtree(excludedNcGn, BigInteger.ONE, BigInteger.TEN);\n//        excluded.add(excludedGsNcGn);\n//\n//        NameConstraints nc = new NameConstraints(permitted, excluded);\n//\n//        extGen.addExtension(X509Extensions.NameConstraints, false, nc);\n//\n//        /**\n//         * 机构信息访问 id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 }\n//         * AuthorityInfoAccessSyntax ::= SEQUENCE SIZE (1..MAX) OF\n//         * AccessDescription AccessDescription ::= SEQUENCE { accessMethod\n//         * OBJECT IDENTIFIER, accessLocation GeneralName } id-ad OBJECT\n//         * IDENTIFIER ::= { id-pkix 48 } id-ad-caIssuers OBJECT IDENTIFIER ::= {\n//         * id-ad 2 } id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }\n//         */\n//        ASN1EncodableVector authorityInnfoAccess = new ASN1EncodableVector();\n//\n//        DERObjectIdentifier id_ad_caIssuers = AccessDescription.id_ad_caIssuers;\n//        DERObjectIdentifier id_ad_ocsp = AccessDescription.id_ad_ocsp;\n//        DERObjectIdentifier id_ad_caRepository = new DERObjectIdentifier(\"1.3.6.1.5.5.7.48.5\");\n//\n//        AccessDescription caIssuers =\n//            new AccessDescription(id_ad_caIssuers, new GeneralName(GeneralName.uniformResourceIdentifier, \"http://certService/caIssuers\"));\n//        AccessDescription ocsp = new AccessDescription(id_ad_caIssuers, new GeneralName(GeneralName.uniformResourceIdentifier, \"http://certService/ocsp\"));\n//        AccessDescription caRepository =\n//            new AccessDescription(id_ad_caIssuers, new GeneralName(GeneralName.uniformResourceIdentifier, \"http://certService/caRepository\"));\n//\n//        authorityInnfoAccess.add(caIssuers);\n//        authorityInnfoAccess.add(ocsp);\n//        authorityInnfoAccess.add(caRepository);\n//\n//        extGen.addExtension(X509Extensions.AuthorityInfoAccess, false, new DERSequence(authorityInnfoAccess));\n//        /**\n//         * \n//         */\n//\n//        /**\n//         * 证书策略\n//         * \n//         * <pre>\n//         * \n//         * certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation\n//         * \n//         * PolicyInformation ::= SEQUENCE {\n//         *   policyIdentifier   CertPolicyId,\n//         *   policyQualifiers   SEQUENCE SIZE (1..MAX) OF\n//         *                           PolicyQualifierInfo OPTIONAL }\n//         * \n//         * CertPolicyId ::= OBJECT IDENTIFIER\n//         * \n//         * PolicyQualifierInfo ::= SEQUENCE {\n//         *   policyQualifierId  PolicyQualifierId,\n//         *   qualifier          ANY DEFINED BY policyQualifierId }\n//         * \n//         * PolicyQualifierId ::=\n//         *   OBJECT IDENTIFIER (id-qt-cps | id-qt-unotice)\n//         * </pre>\n//         * \n//         * @deprecated use an ASN1Sequence of PolicyInformation\n//         */\n//        PolicyInformation policyInfo1 = new PolicyInformation(new DERObjectIdentifier(\"1.1.1.2.3.1\"));\n//        PolicyInformation policyInfo2 = new PolicyInformation(new DERObjectIdentifier(\"1.1.1.2.3.2\"));\n//        PolicyInformation policyInfo3 = new PolicyInformation(new DERObjectIdentifier(\"1.1.1.2.3.3\"));\n//\n//        ASN1EncodableVector certificatePolicies = new ASN1EncodableVector();\n//        certificatePolicies.add(policyInfo1);\n//        certificatePolicies.add(policyInfo2);\n//        certificatePolicies.add(policyInfo3);\n//\n//        extGen.addExtension(X509Extensions.CertificatePolicies, true, new DERSequence(certificatePolicies));\n//\n//        return extGen.generate();\n//    }\n//\n//    /**\n//     * 产生密钥对\n//     * @param alg  签名算法\n//     * @return  密钥对\n//     * @throws Exception\n//     */\n//    public static KeyPair genKeyPair(String alg) throws Exception {\n//        KeyPairGenerator genKeyPair = KeyPairGenerator.getInstance(alg);\n//        genKeyPair.initialize(2048);\n//        return genKeyPair.genKeyPair();\n//    }\n//\n//    /**\n//     * 签名 \n//     * @param alg  签名算法 \n//     * @param planText 签名原文\n//     * @param priKey   签名私钥\n//     * @return   签名信息  byte[]\n//     * @throws Exception\n//     */\n//    public static byte[] sign(String alg, byte[] planText, PrivateKey priKey)\n//        throws Exception {\n//        if (alg == null) return null;\n//        if (\"RSA\".equals(alg)) alg = \"SHA1withRSA\";\n//        else throw new Exception(\"不支持的算法。\");\n//\n//        Signature sign = Signature.getInstance(alg);\n//        sign.initSign(priKey);\n//        sign.update(planText);\n//        return sign.sign();\n//    }\n//\n//}\n"
  },
  {
    "path": "src/test/java/test/jce/demo/CreateCert.java",
    "content": "package test.jce.demo;\n//package test.jce.cert;\n//\n//import java.io.ByteArrayInputStream;\n//import java.io.IOException;\n//import java.io.InputStream;\n//import java.math.BigInteger;\n//import java.security.PrivateKey;\n//import java.security.PublicKey;\n//import java.security.cert.Certificate;\n//import java.security.cert.CertificateFactory;\n//import java.security.cert.Extension;\n//import java.security.cert.X509Certificate;\n//import java.util.Date;\n//import java.util.List;\n//\n//import javax.security.cert.CertificateException;\n//\n//import org.bouncycastle.asn1.ASN1Encodable;\n//import org.bouncycastle.asn1.ASN1ObjectIdentifier;\n//import org.bouncycastle.asn1.x500.X500Name;\n//import org.bouncycastle.cert.X509CertificateHolder;\n//import org.bouncycastle.cert.X509v3CertificateBuilder;\n//import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;\n//import org.bouncycastle.operator.ContentSigner;\n//import org.bouncycastle.operator.OperatorCreationException;\n//import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;\n//\n//public class CreateCert {\n//\n//    public static Certificate generateV3(String issuer, String subject,\n//        BigInteger serial, Date notBefore, Date notAfter,\n//        PublicKey publicKey, PrivateKey privKey, List<Extension> extensions)\n//        throws OperatorCreationException, CertificateException, IOException {\n//\n//        X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(new X500Name(issuer), serial, notBefore, notAfter, new X500Name(subject), publicKey);\n//        ContentSigner sigGen = new JcaContentSignerBuilder(\"SHA1withRSA\").setProvider(\"BC\").build(privKey);\n//        //privKey:使用自己的私钥进行签名，CA证书  \n//        if (extensions != null) {\n//            for (Extension ext : extensions) {\n//                builder.addExtension(new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), ASN1Encodable.fromByteArray(ext.getValue()));\n//            }\n//        }\n//        X509CertificateHolder holder = builder.build(sigGen);\n//        CertificateFactory cf = CertificateFactory.getInstance(\"X.509\");\n//        InputStream is1 = new ByteArrayInputStream(holder.toASN1Structure().getEncoded());\n//        X509Certificate theCert = (X509Certificate) cf.generateCertificate(is1);\n//        is1.close();\n//        return theCert;\n//    }\n//}\n"
  },
  {
    "path": "src/test/java/test/jce/demo/GenX509Cert.java",
    "content": "package test.jce.demo;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.FileReader;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.security.InvalidKeyException;\nimport java.security.KeyPair;\nimport java.security.KeyPairGenerator;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.NoSuchProviderException;\nimport java.security.PrivateKey;\nimport java.security.SecureRandom;\nimport java.security.Signature;\nimport java.security.SignatureException;\nimport java.security.UnrecoverableKeyException;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Vector;\n\nimport sun.misc.BASE64Encoder;\nimport sun.security.tools.keytool.CertAndKeyGen;\nimport sun.security.util.ObjectIdentifier;\nimport sun.security.x509.AlgorithmId;\nimport sun.security.x509.CertificateAlgorithmId;\nimport sun.security.x509.CertificateExtensions;\nimport sun.security.x509.CertificateSerialNumber;\nimport sun.security.x509.CertificateValidity;\nimport sun.security.x509.CertificateVersion;\nimport sun.security.x509.CertificateX509Key;\nimport sun.security.x509.ExtendedKeyUsageExtension;\nimport sun.security.x509.Extension;\nimport sun.security.x509.KeyIdentifier;\nimport sun.security.x509.KeyUsageExtension;\nimport sun.security.x509.SubjectKeyIdentifierExtension;\nimport sun.security.x509.X500Name;\nimport sun.security.x509.X509CertImpl;\nimport sun.security.x509.X509CertInfo;\n\n/**\n * 首先生成CA的根证书，然后有CA的根证书签署生成ScriptX的证书\n * \n * @author Ponfee\n * \n */\npublic class GenX509Cert {\n    \n    public static final Map<String, String> HASH_SIGN_ALG = new HashMap<String, String>() {\n        private static final long serialVersionUID = 8252202658190109593L;\n        {\n            put(\"1.2.840.113549.1.1.5\", \"SHA-1\");\n            put(\"1.2.840.113549.1.1.11\", \"SHA-256\");\n            put(\"1.2.840.113549.1.1.12\", \"SHA-384\");\n            put(\"1.2.840.113549.1.1.13\", \"SHA-512\");\n            put(\"1.2.840.113549.1.1.4\", \"MD5\");\n        }\n    };\n    \n    /**\n     * 获取证书HASH算法\n     * @param oid\n     * @return\n     */\n    private static AlgorithmId getHashAlg(String oid) {\n        try {\n            return AlgorithmId.get(HASH_SIGN_ALG.get(oid));\n        } catch (NoSuchAlgorithmException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n\tprivate String rootCertPath;\n\tprivate String selfCertPath;\n\tprivate String rootCertName;\n\tprivate String selfCertName;\n\t\n\t/** 提供强加密随机数生成器 (RNG)* */\n\tprivate SecureRandom sr;\n\n\tpublic GenX509Cert(String rootCertPath,String rootCertName,String selfCertPath,String selfCertName) throws NoSuchAlgorithmException,\n\t\t\tNoSuchProviderException {\n\t\t// 返回实现指定随机数生成器 (RNG) 算法的 SecureRandom 对象。\n\t\tsr = SecureRandom.getInstance(\"SHA1PRNG\", \"SUN\");\n\t\tthis.rootCertName = rootCertName;\n\t\tthis.rootCertPath = rootCertPath;\n\t\tthis.selfCertName = selfCertName;\n\t\tthis.selfCertPath = selfCertPath;\n\t}\n\n\tpublic void  createCert(X509Certificate certificate,PrivateKey rootPrivKey,KeyPair kp) throws CertificateException, IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException{\n\t\tbyte certbytes[] = certificate.getEncoded();\n\t\tX509CertImpl x509certimpl = new X509CertImpl(certbytes);\n\t\t\n\t\tX509CertInfo x509certinfo = (X509CertInfo)x509certimpl.get(\"x509.info\");\n\t\t\n\t\tx509certinfo.set(\"key\", new CertificateX509Key(kp.getPublic()));\n\t\t\n\t\tCertificateExtensions certificateextensions = new CertificateExtensions();\n        certificateextensions.set(\"SubjectKeyIdentifier\", new SubjectKeyIdentifierExtension((new KeyIdentifier(kp.getPublic())).getIdentifier()));\n        x509certinfo.set(\"extensions\", certificateextensions);\n\t\t\n        //设置issuer域\n\t\tX500Name issuer = new X500Name(\"CN=\"+rootCertName+\",OU=hackwp,O=wp,L=BJ,S=BJ,C=CN\");\n\t\tx509certinfo.set(\"issuer.dname\",issuer);\n\t\tX500Name subject = new X500Name(\"CN=\"+selfCertName+\", OU=wps, O=wps, L=BJ, ST=BJ, C=CN\");\n\t\tx509certinfo.set(\"subject.dname\",subject);\n\t\t\n\t\tSignature signature = Signature.getInstance(\"SHA1WithRSA\");\n\t\tsignature.initSign(kp.getPrivate()); \n\n\t\t//X500Signer signer=new X500Signer(signature,issuer);\n\t\t//AlgorithmId algorithmid = signer.getAlgorithmId();\n\t\tAlgorithmId algorithmid = getHashAlg(certificate.getSigAlgOID());\n\t\t\n\t\t\n\t\tx509certinfo.set(\"algorithmID\", new CertificateAlgorithmId(algorithmid));\n\t\t\n\t\tDate bdate = new Date();\n\t\tDate edate = new Date();\n\t\t//天  小时 分 秒 毫秒\n\t\tedate.setTime(bdate.getTime() + 3650 * 24L * 60L * 60L * 1000L);\n\t\t//validity为有效时间长度 单位为秒\n\t\tCertificateValidity certificatevalidity = new CertificateValidity(bdate, edate);\n\t\tx509certinfo.set(\"validity\", certificatevalidity);\n\t\t//设置有效期域（包含开始时间和到期时间）域名等同与x509certinfo.VALIDITY\n\t\tx509certinfo.set(\"serialNumber\", new CertificateSerialNumber((int)(new Date().getTime() / 1000L)));\n\t\t//设置序列号域\n\t\tCertificateVersion cv = new CertificateVersion(CertificateVersion.V3);\n\t\tx509certinfo.set(X509CertInfo.VERSION,cv);\n\t\t//设置版本号 只有v1 ,v2,v3这几个合法值\n\t\t/**\n\t\t*以上是证书的基本信息 如果要添加用户扩展信息 则比较麻烦 首先要确定version必须是v3否则不行 然后按照以下步骤\n\t\t**/\n\t\tObjectIdentifier oid = new ObjectIdentifier(new int[]{2,5,29,15});\n\t\t//生成扩展域的id 是个int数组 第1位最大2 第2位最大39 最多可以几位不明....\n\t\tString userData = \"Digital Signature, Non-Repudiation, Key Encipherment, Data Encipherment (f0)\";\n\t\t\n\t\tbyte l = (byte)userData.length();//数据总长17位\n\t\tbyte f = 0x04;\n\t\tbyte[] bs = new byte[userData.length()+2];\n\t\tbs[0] = f;\n\t\tbs[1] = l;\n\t\tfor(int i=2;i<bs.length;i++)\n\t\t{\n\t\t  bs[i] = (byte)userData.charAt(i-2);\n\t\t}\n\t\tExtension ext = new Extension(oid,true,bs);\n\t\t// 生成一个extension对象 参数分别为 oid，是否关键扩展，byte[]型的内容值\n\t\t//其中内容的格式比较怪异 第一位是flag 这里取4暂时没出错 估计用来说明数据的用处的 第2位是后面的实际数据的长度，然后就是数据\n\t\t//密钥用法\n\t\tKeyUsageExtension keyUsage=new KeyUsageExtension();\n\t\tkeyUsage.set(KeyUsageExtension.DIGITAL_SIGNATURE, true);\n\t\tkeyUsage.set(KeyUsageExtension.NON_REPUDIATION, true);\n\t\tkeyUsage.set(KeyUsageExtension.KEY_ENCIPHERMENT, true);\n\t\tkeyUsage.set(KeyUsageExtension.DATA_ENCIPHERMENT, true);\n\t\t\n\t\t//增强密钥用法\n\t\tObjectIdentifier ekeyOid = new ObjectIdentifier(new int[]{1, 3, 6, 1, 5, 5, 7, 3, 3});\n\t\tVector<ObjectIdentifier> vkeyOid=new Vector<ObjectIdentifier>();\n\t\tvkeyOid.add(ekeyOid);\n\t\tExtendedKeyUsageExtension exKeyUsage=new ExtendedKeyUsageExtension(vkeyOid);\n\t\t\n\t\tCertificateExtensions exts = new CertificateExtensions();\n\t\t\n\t\texts.set(\"keyUsage\",keyUsage);\n\t\texts.set(\"extendedKeyUsage\",exKeyUsage);\n\t\t\n\t\t//如果有多个extension则都放入CertificateExtensions 类中，\n\t\tx509certinfo.set(X509CertInfo.EXTENSIONS,exts);\n\t\t//设置extensions域\n\n\t\tX509CertImpl x509certimpl1 = new X509CertImpl(x509certinfo);\n\t\tx509certimpl1.sign(rootPrivKey, \"SHA1WithRSA\");\n\t\t//使用另一个证书的私钥来签名此证书 这里使用 md5散列 用rsa来加密\n\n\t\tBASE64Encoder base64 = new BASE64Encoder();\n\t\tFileOutputStream fos = new FileOutputStream(new File(selfCertPath+selfCertName+\".crt\"));\n\t\tbase64.encodeBuffer(x509certimpl1.getEncoded(), fos);\n\t\t\n\t\ttry {\n\t\t\tCertificate[] certChain={x509certimpl1};\n\t\t\tsavePfx(\"scriptx\",kp.getPrivate(),\"123456\", certChain,selfCertPath+selfCertName+\".pfx\");\n\t\t\t\n\t\t\tFileInputStream in = new FileInputStream(selfCertPath+selfCertName+\".pfx\");\n\t\t\tKeyStore inputKeyStore = KeyStore.getInstance(\"pkcs12\");\n\t\t\tinputKeyStore.load(in, \"123456\".toCharArray());\n\t\t\t\n\t\t\tCertificate cert=inputKeyStore.getCertificate(\"scriptx\");\n\t\t\tSystem.out.print(cert.getPublicKey());\n\t\t\t\n\t\t\tPrivateKey privk=(PrivateKey)inputKeyStore.getKey(\"scriptx\", \"123456\".toCharArray());\n\t\t\t\n\t\t\tFileOutputStream privKfos = new FileOutputStream(new File(selfCertPath+selfCertName+\".pvk\"));\n\t\t\t\n\t\t\tprivKfos.write(privk.getEncoded());\n\t\t\tSystem.out.print(privk);\n\t\t\t//base64.encode(key.getEncoded(), privKfos);\n\t\t\t\n\t\t\tin.close();\n\t\t\t\n\t\t} catch (Exception e) {\n\t\t\t// TODO Auto-generated catch block\n\t\t\te.printStackTrace();\n\t\t}\n\t\t\n\t\t//生成文件\n\t\t//x509certimpl1.verify(certificate.getPublicKey(),null);\n\n\t}\n\n\t/**\n\t * 保存此根证书信息KeyStore Personal Information Exchange\n\t * \n\t * @param alias\n\t * @param privKey\n\t * @param pwd\n\t * @param certChain\n\t * @param filepath\n\t * @throws Exception\n\t */\n\tpublic void savePfx(String alias, PrivateKey privKey, String pwd,\n\t\t\tCertificate[] certChain, String filepath) throws Exception {\n\t\t// 此类表示密钥和证书的存储设施。\n\t\t// 返回指定类型的 keystore 对象。此方法从首选 Provider 开始遍历已注册安全提供者列表。返回一个封装 KeyStoreSpi\n\t\t// 实现的新 KeyStore 对象，该实现取自第一个支持指定类型的 Provider。\n\t\tKeyStore outputKeyStore = KeyStore.getInstance(\"pkcs12\");\n\n\t\tSystem.out.println(\"KeyStore类型：\" + outputKeyStore.getType());\n\n\t\t// 从给定输入流中加载此 KeyStore。可以给定一个密码来解锁 keystore（例如，驻留在硬件标记设备上的 keystore）或检验\n\t\t// keystore 数据的完整性。如果没有指定用于完整性检验的密码，则不会执行完整性检验。如果要创建空\n\t\t// keystore，或者不能从流中初始化 keystore，则传递 null 作为 stream 的参数。注意，如果此 keystore\n\t\t// 已经被加载，那么它将被重新初始化，并再次从给定输入流中加载。\n\t\toutputKeyStore.load(null, pwd.toCharArray());\n\n\t\t// 将给定密钥（已经被保护）分配给给定别名。如果受保护密钥的类型为\n\t\t// java.security.PrivateKey，则它必须附带证明相应公钥的证书链。如果底层 keystore 实现的类型为\n\t\t// jks，则必须根据 PKCS #8 标准中的定义将 key 编码为\n\t\t// EncryptedPrivateKeyInfo。如果给定别名已经存在，则与别名关联的 keystore\n\t\t// 信息将被给定密钥（还可能包括证书链）重写。\n\t\toutputKeyStore\n\t\t\t\t.setKeyEntry(alias, privKey, pwd.toCharArray(), certChain);\n\n\t\tFileOutputStream out = new FileOutputStream(filepath);\n\n\t\t// 将此 keystore 存储到给定输出流，并用给定密码保护其完整性。\n\t\toutputKeyStore.store(out, pwd.toCharArray());\n\n\t\tout.close();\n\t}\n\n\tpublic void saveJks(String alias, PrivateKey privKey, String pwd,\n\t\t\tCertificate[] certChain, String filepath) throws Exception {\n\t\tKeyStore outputKeyStore = KeyStore.getInstance(\"jks\");\n\t\tSystem.out.println(outputKeyStore.getType());\n\t\toutputKeyStore.load(null, pwd.toCharArray());\n\t\toutputKeyStore\n\t\t\t\t.setKeyEntry(alias, privKey, pwd.toCharArray(), certChain);\n\t\tFileOutputStream out = new FileOutputStream(filepath);\n\t\toutputKeyStore.store(out, pwd.toCharArray());\n\t\tout.close();\n\t}\n\n\t/**\n\t * 颁布根证书，自己作为CA\n\t */\n\tpublic void createRootCA() throws Exception {\n\n\t\t// 参数分别为公钥算法、签名算法 providername（因为不知道确切的 只好使用null 既使用默认的provider）\n\t\t// Generate a pair of keys, and provide access to them.\n\t\tCertAndKeyGen cak = new CertAndKeyGen(\"RSA\", \"SHA1WithRSA\", null);\n\n\t\t// Sets the source of random numbers used when generating keys.\n\t\tcak.setRandom(sr);\n\n\t\t// Generates a random public/private key pair, with a given key size.\n\t\tcak.generate(1024);\n\n\t\t// Constructs a name from a conventionally formatted string, such as\n\t\t// \"CN=Dave, OU=JavaSoft, O=Sun Microsystems, C=US\". (RFC 1779 or RFC\n\t\t// 2253 style)\n\t\tX500Name subject = new X500Name(\n\t\t\t\t\"CN=\"+rootCertName+\",OU=hackwp,O=wp,L=BJ,S=BJ,C=CN\");\n\n\t\t// Returns a self-signed X.509v3 certificate for the public key. The\n\t\t// certificate is immediately valid. No extensions.\n\t\t// Such certificates normally are used to identify a \"Certificate\n\t\t// Authority\" (CA). Accordingly, they will not always be accepted by\n\t\t// other parties. However, such certificates are also useful when you\n\t\t// are bootstrapping your security infrastructure, or deploying system\n\t\t// prototypes.自签名的根证书\n\t\tX509Certificate certificate = cak.getSelfCertificate(subject, new Date(), 3650 * 24L * 60L * 60L);\n\n\t\tX509Certificate[] certs = { certificate };\n\n\t\ttry {\n\n\t\t\tsavePfx(rootCertName, cak.getPrivateKey(), \"123456\", certs,\n\t\t\t\t\trootCertPath+rootCertName+\".pfx\");\n\n\t\t} catch (Exception e) {\n\n\t\t\te.printStackTrace();\n\n\t\t}\n\n\t\t// 后一个long型参数代表从现在开始的有效期 单位为秒（如果不想从现在开始算 可以在后面改这个域）\n\t\tBASE64Encoder base64 = new BASE64Encoder();\n\n\t\tFileOutputStream fos = new FileOutputStream(new File(rootCertPath+rootCertName+\".crt\"));\n\n\t\t// fos.write(certificate.getEncoded());\n\n\t\t// 生成（保存）cert文件 base64加密 当然也可以不加密\n\t\tbase64.encodeBuffer(certificate.getEncoded(), fos);\n\n\t\tfos.close();\n\n\t}\n\n\tpublic void signCert() throws NoSuchAlgorithmException,\n\t\t\tCertificateException, IOException, UnrecoverableKeyException,\n\t\t\tInvalidKeyException, NoSuchProviderException, SignatureException {\n\n\t\ttry {\n\t\t\tKeyStore ks = KeyStore.getInstance(\"pkcs12\");\n\t\t\tFileInputStream ksfis = new FileInputStream(rootCertPath+rootCertName+\".pfx\");\n\t\t\tchar[] storePwd = \"123456\".toCharArray();\n\t\t\tchar[] keyPwd = \"123456\".toCharArray();\n\t\t\t// 从给定输入流中加载此 KeyStore。\n\t\t\tks.load(ksfis, storePwd);\n\t\t\tksfis.close();\n\n\t\t\t// 返回与给定别名关联的密钥(私钥)，并用给定密码来恢复它。必须已经通过调用 setKeyEntry，或者以\n\t\t\t// PrivateKeyEntry\n\t\t\t// 或 SecretKeyEntry 为参数的 setEntry 关联密钥与别名。\n\t\t\tPrivateKey privK = (PrivateKey) ks.getKey(rootCertName, keyPwd);\n\n\t\t\t// 返回与给定别名关联的证书。如果给定的别名标识通过调用 setCertificateEntry 创建的条目，或者通过调用以\n\t\t\t// TrustedCertificateEntry 为参数的 setEntry\n\t\t\t// 创建的条目，则返回包含在该条目中的可信证书。如果给定的别名标识通过调用 setKeyEntry 创建的条目，或者通过调用以\n\t\t\t// PrivateKeyEntry 为参数的 setEntry 创建的条目，则返回该条目中证书链的第一个元素。\n\t\t\tX509Certificate certificate = (X509Certificate) ks\n\t\t\t\t\t.getCertificate(rootCertName);\n\t\t\tcreateCert(certificate, privK, genKey());\n\t\t} catch (KeyStoreException e) {\n\t\t\t// TODO Auto-generated catch block\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\n\tpublic KeyPair genKey() throws NoSuchAlgorithmException {\n\t\tKeyPairGenerator kpg = KeyPairGenerator.getInstance(\"RSA\");\n\t\tkpg.initialize(1024, sr);\n\t\tKeyPair kp = kpg.generateKeyPair();\n\t\treturn kp;\n\t}\n\t//加上标示否则ca无法被CertificateFactory读取\n\tpublic void addDeco(String caPath){\n\t\tFile file = new File(caPath);\n\t\tStringBuilder builder = new StringBuilder();\n\t\tbuilder.append(\"-----BEGIN CERTIFICATE-----\"+\"\\n\");\n\t\ttry {\n\t\t\tBufferedReader reader = new BufferedReader(new FileReader(caPath));\n\t\t\tString line = null;\n\t\t\twhile((line = reader.readLine()) != null){\n\t\t\t\t\tbuilder.append(line+\"\\n\");\n\t\t\t}\n\t\t\tbuilder.append(\"-----END CERTIFICATE-----\");\n\t\t\tBufferedWriter writer = new BufferedWriter(new FileWriter(caPath));\n\t\t\twriter.write(builder.toString());\n\t\t\treader.close();\n\t\t\twriter.close();\n\t\t} catch (FileNotFoundException e) {\n\t\t\t// TODO Auto-generated catch block\n\t\t\te.printStackTrace();\n\t\t} catch (IOException e) {\n\t\t\t// TODO Auto-generated catch block\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\tpublic static void main(String[] args) {\n\n\t\ttry {\n\t\t\tGenX509Cert gcert = new GenX509Cert(\"D://cert//\",\"Server\",\"D://cert//\",\"Bob\");\n\n\t\t\tgcert.createRootCA();\n\n\t\t\tgcert.signCert();\n\t\t\t\n\t\t\tgcert.addDeco(\"D://cert//Bob.crt\");\n\t\t} catch (Exception e) {\n\t\t\t// TODO Auto-generated catch block\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc0/CryptoInputStream.java",
    "content": "package test.jce.ecc0;\n\nimport java.io.DataInputStream;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport cn.ponfee.commons.jce.implementation.Cryptor;\nimport cn.ponfee.commons.jce.implementation.Key;\n\npublic class CryptoInputStream extends InputStream {\n\n    private DataInputStream in;\n    private Cryptor cs;\n    private Key key;\n    private byte[] buffer;\n    private int top;\n    private int blocksize;\n\n    public CryptoInputStream(InputStream in, Cryptor cs, Key key) {\n        this.in = new DataInputStream(in);\n        this.cs = cs;\n        this.key = key;\n        buffer = new byte[0];\n    }\n\n    public @Override int read() throws IOException {\n        if (top == buffer.length) {\n            try {\n                blocksize = in.readInt();\n            } catch (EOFException e) {\n                return -1;\n            }\n            byte[] cipher = new byte[blocksize];\n            in.read(cipher);\n            buffer = cs.decrypt(cipher, key);\n            top = 0;\n        }\n        top++;\n        return buffer[top - 1];\n    }\n\n    public @Override void close() throws IOException {\n        in.close();\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc0/CryptoOutputStream.java",
    "content": "package test.jce.ecc0;\n\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\nimport cn.ponfee.commons.jce.implementation.Cryptor;\nimport cn.ponfee.commons.jce.implementation.Key;\n\npublic class CryptoOutputStream extends OutputStream {\n    private DataOutputStream out;\n    private Cryptor cs;\n    private Key key;\n    private byte[] buffer;\n    private int top;\n\n    public CryptoOutputStream(OutputStream out, Cryptor cs, Key key) {\n        this.out = new DataOutputStream(out);\n        this.cs = cs;\n        this.key = key;\n        buffer = new byte[64];\n    }\n\n    private void writeOut() throws IOException {\n        if (top == 0) return;\n        byte[] cipher = cs.encrypt(buffer, top, key);\n        out.writeInt(cipher.length);\n        out.write(cipher);\n        top = 0;\n    }\n\n    public @Override void write(int b) throws IOException {\n        buffer[top] = (byte) b;\n        top++;\n        if (top == buffer.length) writeOut();\n    }\n\n    public @Override void flush() throws IOException {\n        writeOut();\n        out.flush();\n    }\n\n    public @Override void close() throws IOException {\n        out.close();\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc0/ECCryptor.java.bak",
    "content": "package cn.ponfee.commons.jce.security;\n\nimport java.math.BigInteger;\nimport java.security.interfaces.ECKey;\nimport java.security.interfaces.ECPrivateKey;\nimport java.security.interfaces.ECPublicKey;\nimport java.security.spec.ECFieldF2m;\nimport java.security.spec.ECParameterSpec;\nimport java.security.spec.ECPoint;\nimport java.security.spec.EllipticCurve;\nimport java.util.Map;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.NullCipher;\n\nimport com.google.common.collect.ImmutableMap;\n\nimport sun.security.ec.ECPrivateKeyImpl;\nimport sun.security.ec.ECPublicKeyImpl;\n\n/**\n * 基于椭圆曲线算法的非对称加解密（未实现）\n * @author Ponfee\n */\n@SuppressWarnings(\"restriction\")\npublic abstract class ECCryptor {\n\n    public static final String ALGORITHM = \"EC\";\n    private static final String PUBLIC_KEY = \"ECPublicKey\";\n    private static final String PRIVATE_KEY = \"ECPrivateKey\";\n\n    /**\n     * 初始化密钥\n     * @return\n     * @throws Exception\n     */\n    public static Map<String, ECKey> initKey() throws Exception {\n        BigInteger x1 = new BigInteger(\"2fe13c0537bbc11acaa07d793de4e6d5e5c94eee8\", 16);\n        BigInteger x2 = new BigInteger(\"289070fb05d38ff58321f2e800536d538ccdaa3d9\", 16);\n\n        ECPoint g = new ECPoint(x1, x2);\n\n        // the order of generator\n        BigInteger n = new BigInteger(\"5846006549323611672814741753598448348329118574063\", 10);\n        // the cofactor\n        int h = 2;\n        int m = 163;\n        int[] ks = { 7, 6, 3 };\n        ECFieldF2m ecField = new ECFieldF2m(m, ks);\n        // y^2+xy=x^3+x^2+1\n        BigInteger a = new BigInteger(\"1\", 2);\n        BigInteger b = new BigInteger(\"1\", 2);\n\n        EllipticCurve ellipticCurve = new EllipticCurve(ecField, a, b);\n\n        ECParameterSpec ecParameterSpec = new ECParameterSpec(ellipticCurve, g, n, h);\n\n        BigInteger s = new BigInteger(\"1234006549323611672814741753598448348329118574063\", 10);\n\n        return ImmutableMap.of(PUBLIC_KEY, new ECPublicKeyImpl(g, ecParameterSpec), // 66 byte\n                               PRIVATE_KEY, new ECPrivateKeyImpl(s, ecParameterSpec) // 52 byte\n                              );\n    }\n\n    public static byte[] boByteArray(ECPrivateKey priKey) throws Exception {\n        return priKey.getEncoded();\n    }\n\n    public static byte[] boByteArray(ECPublicKey pubKey) throws Exception {\n        return pubKey.getEncoded();\n    }\n\n    /**\n     * 解密<br>\n     * 用私钥解密\n     * \n     * @param data\n     * @param key\n     * @return\n     * @throws Exception\n     */\n    public static byte[] decrypt(byte[] data, ECPrivateKey priKey) throws Exception {\n        // 对数据解密\n        // TODO Chipher不支持EC算法 未能实现\n        Cipher cipher = new NullCipher();\n        // Cipher.getInstance(ALGORITHM, keyFactory.getProvider());\n        cipher.init(Cipher.DECRYPT_MODE, priKey, priKey.getParams());\n\n        return cipher.doFinal(data);\n    }\n\n    /**\n     * 加密<br>\n     * 用公钥加密\n     * \n     * @param data\n     * @param privateKey\n     * @return\n     * @throws Exception\n     */\n    public static byte[] encrypt(byte[] data, ECPublicKey pubKey) throws Exception {\n        // 对数据加密\n        // TODO Chipher不支持EC算法 未能实现\n        Cipher cipher = new NullCipher();\n        // Cipher.getInstance(ALGORITHM, keyFactory.getProvider());\n        cipher.init(Cipher.ENCRYPT_MODE, pubKey, pubKey.getParams());\n\n        return cipher.doFinal(data);\n    }\n\n    /**\n     * 取得私钥\n     * \n     * @param keyMap\n     * @return\n     * @throws Exception\n     */\n    public static ECPrivateKey getPrivateKey(Map<String, ECKey> keyMap) throws Exception {\n        return (ECPrivateKey) keyMap.get(PRIVATE_KEY);\n    }\n\n    /**\n     * 取得公钥\n     * @param keyMap\n     * @return\n     * @throws Exception\n     */\n    public static ECPublicKey getPublicKey(Map<String, ECKey> keyMap) throws Exception {\n        return (ECPublicKey) keyMap.get(PUBLIC_KEY);\n    }\n\n    public static void main(String[] args) throws Exception {\n        String inputStr = \"abc\";\n        byte[] data = inputStr.getBytes();\n\n        Map<String, ECKey> keyMap = ECCryptor.initKey();\n\n        byte[] encodedData = ECCryptor.encrypt(data, ECCryptor.getPublicKey(keyMap));\n\n        byte[] decodedData = ECCryptor.decrypt(encodedData, ECCryptor.getPrivateKey(keyMap));\n\n        System.out.println(new String(data));\n        System.out.println(new String(encodedData));\n        System.out.println(new String(decodedData));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc0/ECCryptorTest.java",
    "content": "package test.jce.ecc0;\n\nimport java.util.Arrays;\n\nimport org.junit.Test;\n\nimport cn.ponfee.commons.jce.ECParameters;\nimport cn.ponfee.commons.jce.implementation.Cryptor;\nimport cn.ponfee.commons.jce.implementation.Key;\nimport cn.ponfee.commons.jce.implementation.NoopCryptor;\nimport cn.ponfee.commons.jce.implementation.ecc.ECCryptor;\nimport cn.ponfee.commons.jce.implementation.ecc.EllipticCurve;\nimport cn.ponfee.commons.util.IdcardResolver;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class ECCryptorTest {\n\n    private static byte[] origin = MavenProjects.getMainJavaFileAsBytes(IdcardResolver.class);\n\n    @Test\n    public void testECCryptor() {\n        Cryptor cs = new ECCryptor(new EllipticCurve(ECParameters.secp112r1));\n        Key dk = cs.generateKey();\n        Key ek = dk.getPublic();\n        System.out.println(dk + \"\\n\" + ek);\n\n        byte[] encrypted = cs.encrypt(origin, ek);\n        byte[] decrypted = cs.decrypt(encrypted, dk);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n            System.out.println(\"=====ECCryptor Decrypted text is: \\n\" + new String(decrypted));\n        } else {\n            System.out.println(\"=====ECCryptor Decrypted text is: \\n\" + new String(decrypted));\n        }\n    }\n\n    @Test\n    public void testNullCryptor() {\n        Cryptor cs = NoopCryptor.SINGLETON;\n        Key dk = cs.generateKey();\n        Key ek = dk.getPublic();\n\n        byte[] encrypted = cs.encrypt(origin, ek);\n        byte[] decrypted = cs.decrypt(encrypted, dk);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            System.out.println(\"\\n\\n=====NullCryptor Decrypted text is: \\n\" + new String(decrypted));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc0/EllipticCurveTest.java",
    "content": "package test.jce.ecc0;\n\nimport java.math.BigInteger;\n\nimport cn.ponfee.commons.jce.ECParameters;\nimport cn.ponfee.commons.jce.implementation.ecc.ECPoint;\nimport cn.ponfee.commons.jce.implementation.ecc.EllipticCurve;\n\npublic class EllipticCurveTest {\n\n    public static void main(String[] args) {\n        BigInteger a = new BigInteger(\"1\");\n        BigInteger b = new BigInteger(\"6\");\n        BigInteger mod = new BigInteger(\"11\");\n        EllipticCurve e = new EllipticCurve(a, b, mod);\n        System.out.println(\"EllipticCurve: \" + e + \" created succesfully!\");\n\n        ECPoint p1 = new ECPoint(e, new BigInteger(\"2\"), new BigInteger(\"7\"));\n        ECPoint p2 = new ECPoint(e, new BigInteger(\"7\"), new BigInteger(\"2\"));\n        ECPoint p3 = new ECPoint(e, new BigInteger(\"2\"), new BigInteger(\"-7\"));\n        System.out.println(p1 + \" + \" + p2 + \" = \" + p1.add(p2));\n        System.out.println(p1 + \" + \" + p1 + \" = \" + p1.add(p1));\n        System.out.println(p1 + \" + \" + p3 + \" = \" + p1.add(p3));\n        System.out.println(p1 + \" * \" + mod + \" = \" + p1.multiply(mod));\n        System.out.println(p1 + \" + \" + e.getZero() + \" = \" + p1.add(e.getZero()));\n        System.out.println(e.getZero() + \" + \" + e.getZero() + \" = \" + e.getZero().add(e.getZero()));\n\n        System.out.println(\"\\nTesting secp256r1===============\");\n        e = new EllipticCurve(ECParameters.secp256r1);\n        System.out.println(\"New curve: \" + e + \" OK!\");\n        System.out.println(\"Generator: \" + e.getBasePointG());\n        System.out.println(\"N: \" + e.getN());\n\n        System.out.println(\"\\nTesting decompression of compression...\");\n        p1 = new ECPoint(e.getBasePointG().compress(), e);\n        if (e.getBasePointG().getX().compareTo(p1.getX()) == 0) {\n            System.out.println(\"x values agree...\");\n        } else {\n            System.out.println(\"x values disagree...\");\n            System.out.println(\"x-before:\");\n            System.out.println(e.getBasePointG().getY());\n            System.out.println(\"y-after:\");\n            System.out.println(p1.getY());\n        }\n        if (e.getBasePointG().getY().compareTo(p1.getY()) == 0) {\n            System.out.println(\"y values agree...\");\n        } else {\n            System.out.println(\"y values disagree...\");\n            System.out.println(\"y-before:\");\n            System.out.println(e.getBasePointG().getY());\n            System.out.println(\"y-after:\");\n            System.out.println(p1.getY());\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc0/Login.java",
    "content": "package test.jce.ecc0;\n\nimport java.awt.Font;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\nimport javax.swing.JButton;\nimport javax.swing.JComponent;\nimport javax.swing.JFrame;\nimport javax.swing.JLabel;\nimport javax.swing.JPasswordField;\nimport javax.swing.JTextField;\n\npublic class Login extends JFrame implements ActionListener {\n    private static final long serialVersionUID = -8266964331082261609L;\n    JTextField untf;\n    JPasswordField pwpf;\n    JLabel unl, pwl;\n    JButton Next, Close;\n\n    public Login() {\n        try {\n            setLayout(null);\n            unl = new JLabel(\"User Name : \");\n            untf = new JTextField(15);\n            untf.setFont(new Font(\"Times New Roman\", Font.BOLD, 12));\n\n            addc(unl, 50, 40, 100, 25);\n            addc(untf, 120, 40, 150, 25);\n\n            pwl = new JLabel(\"PassWord : \");\n            pwpf = new JPasswordField(15);\n            pwpf.setEchoChar('*');\n            pwpf.setFont(new Font(\"Times New Roman\", Font.BOLD, 12));\n\n            addc(pwl, 50, 80, 100, 25);\n            addc(pwpf, 120, 80, 150, 25);\n\n            Next = new JButton(\"Next\");\n            Next.setMnemonic('N');\n            Next.addActionListener(this);\n\n            addc(Next, 90, 140, 70, 20);\n\n            Close = new JButton(\"Close\");\n            Close.setMnemonic('C');\n            Close.addActionListener(this);\n\n            addc(Close, 180, 140, 70, 20);\n\n            setTitle(\"Login Screen\");\n            setVisible(true);\n            setSize(300, 220);\n            setResizable(false);\n            setDefaultCloseOperation(EXIT_ON_CLOSE);\n\n        } catch (Exception e) {\n            System.out.println(\"Exception in Constructor:\" + e);\n            System.exit(0);\n        }\n    }\n\n    public void addc(JComponent c, int x, int y, int w, int h) {\n        c.setBounds(x, y, w, h);\n        add(c);\n    }\n\n    public void actionPerformed(ActionEvent ae) {\n        JButton b = (JButton) ae.getSource();\n        if (b == Close) {\n            System.exit(0);\n        }\n        if (b != Next) {\n            return;\n        }\n        new Screen();\n        dispose();\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc0/Main.java",
    "content": "package test.jce.ecc0;\n\npublic class Main {\n\n    public static void main(String[] args) {\n        new Login();\n        // new View(600,600, new RSACryptor());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc0/Screen.java",
    "content": "package test.jce.ecc0;\n\nimport java.awt.FileDialog;\nimport java.awt.Font;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\n\nimport javax.swing.JButton;\nimport javax.swing.JComponent;\nimport javax.swing.JFileChooser;\nimport javax.swing.JFrame;\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport javax.swing.JScrollPane;\nimport javax.swing.JTextArea;\nimport javax.swing.JTextField;\n\nimport cn.ponfee.commons.jce.ECParameters;\nimport cn.ponfee.commons.jce.implementation.ecc.ECCryptor;\nimport cn.ponfee.commons.jce.implementation.ecc.EllipticCurve;\n\npublic class Screen extends JFrame implements ActionListener {\n    private static final long serialVersionUID = 1L;\n    JTextArea ta;\n    JLabel path, title;\n    JTextField pathtf;\n    FileDialog fd;\n    JButton Load, Close, Next, fdl;\n    JPanel p1, p2;\n    String s, file, p;\n    File targetFile;\n    String filePath;\n\n    public Screen() {\n        try {\n            setLayout(null);\n            path = new JLabel(\"Path  :\");\n            title = new JLabel(\"Choose a text file for encryption\");\n            pathtf = new JTextField(15);\n            pathtf.setFont(new Font(\"TimesNewRoman\", Font.BOLD, 12));\n            pathtf.setEditable(false);\n\n            fdl = new JButton(\"Select File\");\n            fdl.setMnemonic('S');\n            fdl.addActionListener(this);\n\n            Load = new JButton(\"Load\");\n            Load.setMnemonic('L');\n            Load.addActionListener(this);\n\n            addc(path, 50, 40, 180, 25);\n            addc(pathtf, 120, 40, 120, 25);\n            addc(fdl, 300, 40, 100, 25);\n            addc(Load, 50, 280, 90, 25);\n\n            Next = new JButton(\"Next\");\n            Next.setMnemonic('N');\n            Next.addActionListener(this);\n\n            Close = new JButton(\"Close\");\n            Close.setMnemonic('C');\n            Close.addActionListener(this);\n\n            ta = new JTextArea();\n            ta.setFont(new Font(\"Times New Roman\", Font.BOLD, 12));\n            ta.setEditable(false);\n            ta.setText(\"<<Please select the file having the plain text, and click on Load. The text should appear here.>>\");\n\n            addc(new JScrollPane(ta, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), 50, 100, 350, 150);\n            ta.setLineWrap(true);\n            addc(Close, 190, 280, 90, 25);\n            addc(Next, 300, 280, 90, 25);\n\n            setTitle(\"Pick a text file for encrypting using ECC\");\n            setVisible(true);\n            setSize(450, 400);\n            setResizable(false);\n            setDefaultCloseOperation(EXIT_ON_CLOSE);\n        } catch (Exception e) {\n            System.out.println(\"Exception Here\" + e);\n            System.exit(0);\n        }\n    }\n\n    public void addc(JComponent c, int x, int y, int w, int h) {\n        c.setBounds(x, y, w, h);\n        add(c);\n    }\n\n    public void actionPerformed(ActionEvent a) {\n        JButton b = (JButton) a.getSource();\n        if (b == Close) System.exit(0);\n        if (b == Next) {\n            s = ta.getText();\n            try {\n                EllipticCurve ec = new EllipticCurve(ECParameters.secp112r1);\n                View v = new View(600, 600, new ECCryptor(ec));//ch);\n                v.filePath = this.filePath;\n            } catch (Exception e) {\n                System.out.println(\"EC Exception\");\n            }\n        }\n        if (b == fdl) {\n\n            //JFileChooser x=new JFileChooser();\n            File f = new File(\".\");\n            String currdir = new String(\"hi\");\n            try {\n                currdir = f.getCanonicalPath();\n            } catch (Exception e) {\n                System.out.println(\"\\n Error getting the current dir\");\n            }\n\n            JFileChooser chooser = new JFileChooser(currdir);\n            int returnVal = chooser.showOpenDialog(this);\n            if (returnVal == JFileChooser.APPROVE_OPTION) {\n                targetFile = chooser.getSelectedFile();\n                filePath = targetFile.getPath();\n                pathtf.setText(filePath);\n                System.out.println(\"\\nPath is \" + filePath);\n            } else System.out.println(\"Error in opening File\");\n        }\n        if (b == Load) {\n            try {\n                ta.setText(\"\");\n                FileInputStream fin = new FileInputStream(filePath);\n                ByteArrayOutputStream bout = new ByteArrayOutputStream();\n                while (fin.available() > 0) {\n                    bout.write(fin.read());\n                }\n                fin.close();\n                s = bout.toString();\n                ta.insert(s, 0);\n                ta.setFont(new Font(\"TimesNewRoman\", Font.BOLD, 12));\n            } catch (Exception e) {\n                System.out.println(\"Error at File loading :\" + e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc0/View.java",
    "content": "package test.jce.ecc0;\n\nimport java.awt.BorderLayout;\nimport java.awt.Container;\nimport java.awt.FlowLayout;\nimport java.awt.Font;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.WindowAdapter;\nimport java.awt.event.WindowEvent;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\nimport javax.swing.JButton;\nimport javax.swing.JEditorPane;\nimport javax.swing.JFileChooser;\nimport javax.swing.JFrame;\nimport javax.swing.JLabel;\nimport javax.swing.JOptionPane;\nimport javax.swing.JPanel;\nimport javax.swing.JScrollPane;\n\nimport cn.ponfee.commons.jce.implementation.Cryptor;\nimport cn.ponfee.commons.jce.implementation.Key;\n\npublic class View extends JFrame implements ActionListener {\n    private static final long serialVersionUID = 1L;\n    public final JButton ENCRYPT = new JButton(\"Encrypt\");\n    public final JButton DECRYPT = new JButton(\"Decrypt\");\n    public final JButton LOADKEY = new JButton(\"Load key\");\n    public final JButton SAVEKEY = new JButton(\"Display key\");\n    public final JButton QUIT = new JButton(\"Quit\");\n    public final String TITLE = \"JECC\";\n    public String filePath;\n\n    private JScrollPane scroll_pane;\n    private JLabel infoLabel = new JLabel(\"Welcome to JECC\");\n    private JLabel statusLabel = new JLabel();\n    private JEditorPane pane;\n    private Cryptor cs;\n    private File targetFile;\n    private Key pk;\n    private Key sk;\n\n    public View(int width, int height, Cryptor cs) {\n        try {\n            System.out.print(\"Loading \" + TITLE + \"...\");\n\n            Container cp = getContentPane();\n            cp.setLayout(new BorderLayout());\n\n            pane = new JEditorPane();\n            pane.setEditable(false);\n            pane.setFont(new Font(\"TimesNewRoman\", Font.BOLD, 12));\n            String text = \"Welcome to the encryption phase. Here the text from the file displayed in the last \"\n                        + \"step will be encrypted, using the standard key for ECC( \" + cs + \" ). You can view \"\n                        + \"the secret and public keys used by clicking the Display Key button, or load a set of\"\n                        + \" keys from a file.\";\n            pane.setText(text);\n\n            scroll_pane = new JScrollPane(pane);\n            cp.add(scroll_pane, BorderLayout.CENTER);\n\n            JPanel top = new JPanel(new FlowLayout());\n            top.add(ENCRYPT);\n            ENCRYPT.addActionListener(this);\n            top.add(DECRYPT);\n            DECRYPT.addActionListener(this);\n            //\t\t\ttop.add(GENKEY);     GENKEY.addActionListener(this);\n            top.add(LOADKEY);\n            LOADKEY.addActionListener(this);\n            top.add(SAVEKEY);\n            SAVEKEY.addActionListener(this);\n            top.add(infoLabel);\n            top.add(QUIT);\n            QUIT.addActionListener(this);\n            cp.add(top, BorderLayout.NORTH);\n\n            JPanel bottom = new JPanel(new FlowLayout());\n            bottom.add(statusLabel);\n            cp.add(bottom, BorderLayout.SOUTH);\n\n            addWindowListener(new ExitController());\n\n            setTitle(TITLE);\n            setSize(width, height);\n            setVisible(true);\n\n            this.cs = cs;\n\n            if (cs != null) {\n                setInfo(\"Using: \" + this.cs);\n                sk = cs.generateKey();\n                pk = sk.getPublic();\n            } else setInfo(\"No CS loaded\");\n\n            setStatus(\"Ready\");\n            System.out.println(\"[OK]\");\n            System.out.println(\"Using: \" + cs);\n            System.out.println(\"Ready\");\n        } catch (Exception e) {\n            pane.setText(\"\\n\\tError\\n\\tE GUI-error in \" + TITLE);\n        }\n\n    }\n\n    public void setStatus(String s) {\n        statusLabel.setText(s);\n        statusLabel.repaint();\n        try {\n            Thread.sleep(10);\n        } catch (InterruptedException e) {\n        }\n    }\n\n    public void setInfo(String s) {\n        infoLabel.setText(s);\n        infoLabel.repaint();\n        try {\n            Thread.sleep(10);\n        } catch (InterruptedException e) {\n        }\n    }\n\n    public void actionPerformed(ActionEvent e) {\n        if (e.getSource() == QUIT) {\n            System.out.println(\"Exiting \" + TITLE);\n            System.exit(0);\n        } else if (e.getSource() == ENCRYPT) {\n            encrypt();\n            this.setVisible(false);\n            try {\n                Thread.sleep(250);\n            } catch (InterruptedException E) {\n            }\n\n            ENCRYPT.setVisible(false);\n            LOADKEY.setVisible(false);\n            SAVEKEY.setVisible(false);\n\n            this.repaint();\n            this.setVisible(true);\n        } else if (e.getSource() == DECRYPT) {\n            this.setVisible(false);\n            decrypt();\n            try {\n                Thread.sleep(250);\n            } catch (InterruptedException E) {\n            }\n            this.setVisible(true);\n\n        } else if (e.getSource() == LOADKEY) {\n            loadKey();\n        } else if (e.getSource() == SAVEKEY) {\n            saveKey();\n        }\n\n    }\n\n    public File openFile() {\n        JFileChooser chooser = new JFileChooser();\n        int returnVal = chooser.showOpenDialog(this);\n        if (returnVal == JFileChooser.APPROVE_OPTION) {\n            targetFile = chooser.getSelectedFile();\n            filePath = targetFile.getPath();\n            System.out.println(\"\\nPath is \" + filePath);\n            return targetFile;\n        } else return null;\n    }\n\n    public File saveFile() {\n        JFileChooser chooser = new JFileChooser();\n        int returnVal = chooser.showSaveDialog(this);\n        if (returnVal == JFileChooser.APPROVE_OPTION) {\n            targetFile = chooser.getSelectedFile();\n            filePath = targetFile.getPath();\n            return targetFile;\n        } else return null;\n    }\n\n    private void encrypt() {\n        File f = new File(filePath);\n        if (!f.exists()) {\n            new JOptionPane(\"No file selected for encryption!\");\n            return;\n        }\n        try {\n            InputStream in = new FileInputStream(f);\n            OutputStream out = new CryptoOutputStream(new FileOutputStream(new File(f.getParent(), f.getName() + \".enc\")), cs, pk);\n\n            ByteArrayOutputStream bout = new ByteArrayOutputStream();\n            String s;\n\n            setStatus(\"Encrypting: \" + f.getName() + \" -> \" + \"encrypted.txt ...\");\n            System.out.print(\"Encrypting: \" + f.getName() + \" -> \" + \"encrypted.txt ...\");\n\n            //For storing encrypted text in encrypted.txt file\n            OutputStream eout = new FileOutputStream(new File(f.getParent(), \"encrypted.txt\"));\n\n            int read;\n            while ((read = in.read()) != -1) {\n                bout.write(read);\n                out.write(read);\n            }\n            out.flush();\n            in.close();\n            out.close();\n            setStatus(\"done\");\n\n            InputStream enc = new FileInputStream(new File(f.getParent(), f.getName() + \".enc\"));\n            ByteArrayOutputStream encout = new ByteArrayOutputStream();\n            while ((read = enc.read()) != -1) {\n                encout.write(read);\n            }\n\n            String encrypted = encout.toString();\n            String plain = bout.toString();\n            String printed = \"\";\n            printed = encrypted;\n            eout.write(printed.getBytes());\n            eout.flush();\n            eout.close();\n\n            s = \"Plain Text: \" + plain + \"\\n\" + \"Encrypted Text:\\n \" + printed;\n            pane.setFont(new Font(\"TimesNewRoman\", Font.BOLD, 12));\n            pane.setText(s);\n            enc.close();\n\n            System.out.println(\"OK\");\n\n        } catch (IOException e) {\n            e.printStackTrace();\n            System.out.println(\"Error in encryption!\");\n        }\n    }\n\n    private void decrypt() {\n        filePath = filePath + \".enc\";\n        File f = new File(filePath);\n        if (!f.exists()) {\n            new JOptionPane(\"No file selected for decryption!\");\n            return;\n        }\n        try {\n            InputStream in = new CryptoInputStream(new FileInputStream(f), cs, sk);\n            OutputStream out = new FileOutputStream(new File(f.getParent(), f.getName().substring(0, f.getName().length() - 4) + \".dec\"));\n\n            System.out.print(\"Decrypting: \" + f.getName() + \" -> \" + f.getName().substring(0, f.getName().length() - 4) + \".dec ...\");\n            setStatus(\"Decrypting: \" + f.getName() + \" -> \" + f.getName().substring(0, f.getName().length() - 4) + \".dec ...\");\n            ByteArrayOutputStream bout = new ByteArrayOutputStream();\n            String s;\n\n            int read;\n            while ((read = in.read()) != -1) {\n                out.write(read);\n                bout.write(read);\n            }\n            s = \"Decrypted Text: \" + bout.toString();\n            pane.setFont(new Font(\"TimesNewRoman\", Font.BOLD, 12));\n            pane.setText(s);\n\n            out.flush();\n            in.close();\n            out.close();\n            System.out.println(\"OK\");\n            setStatus(\"done\");\n        } catch (Exception e) {\n            System.out.println(\"Error in decryption!\");\n        }\n\n    }\n\n    private void loadKey() {\n        File f = openFile();\n        if (f == null) {\n            new JOptionPane(\"No file selected for key\");\n            return;\n        }\n        try {\n            sk = readKey(f);\n            System.out.println(\"sk is:\" + sk);\n            pk = sk.getPublic();\n        } catch (Exception e) {\n            System.out.println(\"Error loading key!\");\n            e.printStackTrace();\n        }\n    }\n\n    private void saveKey() {\n        String keydisp = \" \" + sk + \"\\n\" + \" \" + pk;\n        pane.setFont(new Font(\"TimesNewRoman\", Font.BOLD, 12));\n        pane.setText(keydisp);\n    }\n\n    /** Writes the Key instance to a File f*/\n    public void writeKey(File f, Key k) {\n        try {\n            FileOutputStream out = new FileOutputStream(f);\n            k.writeKey(out);\n            out.flush();\n            out.close();\n        } catch (IOException e) {\n            System.out.println(\"Error writing key!\\n\");\n            e.printStackTrace(System.out);\n            System.exit(0);\n        }\n    }\n\n    public Key readKey(File f) {\n        try {\n            Key k = cs.generateKey();\n            FileInputStream in = new FileInputStream(f);\n            k = k.readKey(in);\n            in.close();\n            return k;\n        } catch (IOException e) {\n            System.out.println(\"Error reading key file!\\n\" + e);\n            return null;\n        }\n    }\n\n    public class ExitController extends WindowAdapter {\n        public void windowClosing(WindowEvent e) {\n            System.out.println(\"Exiting \" + TITLE);\n            System.exit(0);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc2/BaseConvert.java",
    "content": "package test.jce.ecc2;\n\nimport static javax.xml.bind.DatatypeConverter.parseBase64Binary;\nimport static javax.xml.bind.DatatypeConverter.parseHexBinary;\nimport static javax.xml.bind.DatatypeConverter.printBase64Binary;\nimport static javax.xml.bind.DatatypeConverter.printHexBinary;\n\n/**\n * Utilities for converting various strings into byte arrays they encode for given bases\n */\npublic class BaseConvert {\n\n    /**\n     * Convert a string to a byte array it encodes\n     *\n     * @param string A string representing an array of bytes\n     * @param radix  The base the string is encoded with\n     * @return The byte array the string represents\n     * @throws UnsupportedBaseException Thrown if an unsupported base is handed as an argument\n     */\n    public static byte[] baseEncodedStringToByteArray(\n            String string,\n            int radix) throws UnsupportedBaseException {\n        switch (radix) {\n            case 16:\n                return parseHexBinary(string);\n            case 64:\n                return parseBase64Binary(string);\n            default:\n                throw new UnsupportedBaseException(\"Unknown base given when trying to parse an encoded string to a byte array\");\n        }\n    }\n\n    /**\n     * Convert a byte array into a encoded string\n     *\n     * @param bytes The bytes to encode as a string\n     * @param radix The base of the encoding\n     * @return An encoded string\n     * @throws UnsupportedBaseException Thrown if an unsupported base is handed as an argument\n     */\n    public static String byteArrayToBaseEncodedString(\n            byte[] bytes,\n            int radix) throws UnsupportedBaseException {\n        switch (radix) {\n            case 16:\n                return printHexBinary(bytes).toLowerCase();\n            case 64:\n                return printBase64Binary(bytes);\n            default:\n                throw new UnsupportedBaseException(\"Unknown base given when trying to write an string encoding a byte array\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc2/CurveParameters.java",
    "content": "package test.jce.ecc2;\n\n\nimport org.bouncycastle.asn1.sec.SECNamedCurves;\nimport org.bouncycastle.asn1.x9.X9ECParameters;\nimport org.bouncycastle.crypto.params.ECDomainParameters;\n\npublic class CurveParameters {\n\n    /**\n     * Creates an ECDomainParameters from a named curve.\n     * @param curveName The name of the curve to use\n     */\n    public static ECDomainParameters getCurveParametersByName(String curveName) {\n        X9ECParameters x9ECParameters = SECNamedCurves.getByName(curveName);\n        return new ECDomainParameters(\n                x9ECParameters.getCurve(),\n                x9ECParameters.getG(),\n                x9ECParameters.getN(),\n                x9ECParameters.getH());\n    }\n\n    public static final ECDomainParameters secp256k1 = getCurveParametersByName(\"secp256k1\");\n    public static final ECDomainParameters secp256r1 = getCurveParametersByName(\"secp256r1\");\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc2/PrivateKey.java",
    "content": "package test.jce.ecc2;\n\nimport static javax.xml.bind.DatatypeConverter.printHexBinary;\nimport static test.jce.ecc2.BaseConvert.byteArrayToBaseEncodedString;\nimport static test.jce.ecc2.Utils.*;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.SecureRandom;\nimport java.util.Arrays;\n\nimport org.bouncycastle.asn1.ASN1Integer;\nimport org.bouncycastle.asn1.DERSequenceGenerator;\nimport org.bouncycastle.crypto.digests.GeneralDigest;\nimport org.bouncycastle.crypto.digests.SHA256Digest;\nimport org.bouncycastle.crypto.params.ECDomainParameters;\nimport org.bouncycastle.crypto.signers.HMacDSAKCalculator;\nimport org.bouncycastle.math.ec.ECPoint;\n\nfinal public class PrivateKey {\n    private final BigInteger d;\n    private final ECDomainParameters curveParameters;\n    private final PublicKey publicKey;\n\n    /**\n     * Construct a PrivateKey\n     *\n     * @param curveParameters The parameters of the elliptic curve to be used\n     * @param d               The coefficient to be used as a private key\n     * @throws SecurityException Thrown if the specified private key coefficient is not a valid value permitted by the elliptic curve specified by curveParameters\n     */\n    public PrivateKey(ECDomainParameters curveParameters, BigInteger d) throws SecurityException {\n        if (d.compareTo(BigInteger.ZERO) != 1)\n            throw new SecurityException(\"Private key must be positive.\");\n        if (curveParameters.getN().compareTo(d) != 1)\n            throw new SecurityException(\"Private key cannot be larger than the curve modulus.\");\n        this.d = d;\n        this.curveParameters = curveParameters;\n        this.publicKey = new PublicKey(curveParameters, curveParameters.getG().multiply(d).normalize());\n    }\n\n    /**\n     * Parse a PrivateKey from a byte array\n     *\n     * @param curveParameters The parameters of the elliptic curve to be used\n     * @param bytes           The byte array to be parsed\n     * @return The private key corresponding to the specified bytes\n     */\n    public static PrivateKey fromByteArray(ECDomainParameters curveParameters, byte[] bytes) {\n        return new PrivateKey(curveParameters, new BigInteger(1, bytes));\n    }\n\n    /**\n     * Parse a PrivateKey from an encoded string\n     *\n     * @param curveParameters The parameters of the elliptic curve to be used\n     * @param string          The encoded string, which is to be parsed into bytes specified by the radix\n     * @param radix           The radix of the encoded string\n     * @return The private key corresponding to the specified bytes\n     * @throws UnsupportedBaseException Thrown if the specified radix is not a supported base for encoding strings\n     */\n    public static PrivateKey fromString(\n            ECDomainParameters curveParameters,\n            String string,\n            int radix) throws UnsupportedBaseException {\n        if (radix == 10)\n            return new PrivateKey(curveParameters, new BigInteger(string, 10));\n        return fromByteArray(curveParameters, BaseConvert.baseEncodedStringToByteArray(string, radix));\n    }\n\n    /**\n     * Convert the coefficient of the private key into a byte array\n     *\n     * @return A byte array representing the coefficient of the private key\n     */\n    public byte[] toByteArray() {\n        final byte[] data = d.toByteArray();\n        return Arrays.copyOfRange(data, countLeadingZeroBytes(data), data.length);\n    }\n\n    /**\n     * Convert the coefficient of the private key into a string encoding a byte array using the specified radix\n     *\n     * @param radix The radix to use for encoding the coefficient's representative byte array as a string\n     * @return A string encoding a byte array representing the coefficient of the private key\n     * @throws UnsupportedBaseException Thrown if the specified radix is not a supported base for encoding strings\n     */\n    public String toString(int radix) throws UnsupportedBaseException {\n        if (radix == 10)\n            return d.toString(10);\n        return byteArrayToBaseEncodedString(toByteArray(), radix);\n    }\n\n    /**\n     * Convert the coefficient of the private key into a hexadecimal string\n     *\n     * @return A hexadecimal string encoding a byte array representing the coefficient of the private key\n     */\n    @Override\n    public String toString() {\n        return printHexBinary(toByteArray()).toLowerCase();\n    }\n\n    /**\n     * Get the public key for this private key\n     *\n     * @return The public key that corresponds to this private key\n     */\n    public PublicKey getPublicKey() {\n        return publicKey;\n    }\n\n    /**\n     * Create a new RFC 6979 deterministic nonce generator for deterministic ECDSA signatures\n     *\n     * @param hash       The hash of value to be signed\n     * @param hashDigest A hashing function to use in signing\n     * @return A cryptographic deterministic PRNG where the first returned value is compliant with RFC 6979\n     * @throws SecurityException Thrown if a fresh hash digest cannot be generated from the one hash handed as an argument\n     */\n    private HMacDSAKCalculator deterministicKGenerator(\n            byte[] hash,\n            GeneralDigest hashDigest) throws SecurityException {\n        final GeneralDigest freshHashDigest = freshDigestFromDigest(hashDigest);\n        final HMacDSAKCalculator generator = new HMacDSAKCalculator(freshHashDigest);\n        generator.init(curveParameters.getN(), d, hash);\n        return generator;\n    }\n\n    /**\n     * Compute a recovery byte for a compressed ECDSA signature given R and S parameters\n     *\n     * @param kp The elliptic curve point computed as part of the ECDSA algorithm that must be recovered\n     * @param r  The R value of the ECDSA signature\n     * @param s  The S value of the ECDSA signature\n     * @return The recovery byte, following the convention in BitCoin\n     */\n    private byte computeRecoveryByte(ECPoint kp, BigInteger r, BigInteger s, boolean canonical) {\n        final BigInteger n = curveParameters.getN();\n        final boolean bigR = r.compareTo(n) >= 0;\n        final boolean bigS = canonical && s.add(s).compareTo(n) >= 0;\n        final boolean yOdd = kp.getYCoord().toBigInteger().testBit(0);\n        return (byte) (0x1B + ((bigS != yOdd) ? 1 : 0) + (bigR ? 2 : 0));\n    }\n\n    private static SecureRandom rng = new SecureRandom();\n\n    private static class TimeStampAndNonce {\n        final long timeStamp;\n        final long nonce;\n\n        private TimeStampAndNonce() {\n            this.timeStamp = System.currentTimeMillis();\n            this.nonce = rng.nextLong();\n        }\n    }\n\n    /**\n     * A configuration that is read when constructing ECDSA signatures\n     */\n    public static class SignatureConfig {\n        final boolean recover;\n        final TimeStampAndNonce timeStampAndNonce;\n        final boolean canonical;\n        final GeneralDigest rfc6979Digest;\n        final GeneralDigest messageDigest;\n\n        public SignatureConfig(\n                boolean recover,\n                boolean timeStampAndNonce,\n                boolean canonical,\n                GeneralDigest rfc6979Digest,\n                GeneralDigest messageDigest) throws IllegalArgumentException {\n            if (timeStampAndNonce && !recover) {\n                throw new IllegalArgumentException(\n                        \"Cannot configure signatures to include a timestamp and nonce without a recovery byte\");\n            }\n            this.recover = recover;\n            this.canonical = canonical;\n            this.timeStampAndNonce = timeStampAndNonce ? new TimeStampAndNonce() : null;\n            this.rfc6979Digest = rfc6979Digest;\n            this.messageDigest = messageDigest;\n        }\n    }\n\n    /**\n     * A builder for configurations for ECDSA signatures\n     */\n    public static class SignatureConfigBuilder {\n        private boolean recover = true;\n        private boolean timeStampAndNonce = true;\n        private boolean canonical = true;\n        private GeneralDigest rfc6979Digest = new SHA256Digest();\n        private GeneralDigest messageDigest = new SHA256Digest();\n\n        public SignatureConfigBuilder() {\n        }\n\n        public SignatureConfigBuilder setRecover(boolean recover) {\n            this.recover = recover;\n            if (!recover)\n                this.timeStampAndNonce = false;\n            return this;\n        }\n\n        public SignatureConfigBuilder setTimeStampAndNonce(boolean timeStampAndNonce) {\n            if (timeStampAndNonce && !recover)\n                throw new IllegalArgumentException(\n                        \"Cannot configure signatures to include a timestamp and nonce without a recovery byte\");\n            this.timeStampAndNonce = timeStampAndNonce;\n            return this;\n        }\n\n        public SignatureConfigBuilder setCanonical(boolean canonical) {\n            this.canonical = canonical;\n            return this;\n        }\n\n        public SignatureConfigBuilder setRfc6979Digest(GeneralDigest rfc6979Digest) {\n            this.rfc6979Digest = rfc6979Digest;\n            return this;\n        }\n\n        public SignatureConfigBuilder setMessageDigest(GeneralDigest messageDigest) {\n            this.messageDigest = messageDigest;\n            return this;\n        }\n\n        public SignatureConfig build() {\n            return new SignatureConfig(recover, timeStampAndNonce, canonical, rfc6979Digest, messageDigest);\n        }\n    }\n\n    /**\n     * A default configuration for ECDSA signatures\n     */\n    public static SignatureConfig getDefaultSignatureConfig() {\n        return new SignatureConfigBuilder().build();\n    }\n\n    private byte[] signHash(byte[] hash, SignatureConfig config)\n            throws SecurityException {\n        if ((hash.length << 3) > curveParameters.getN().bitLength())\n            throw new SecurityException(\"Hash must not have more bytes than the curve specifies\");\n        final HMacDSAKCalculator rng = deterministicKGenerator(hash, config.rfc6979Digest);\n        final BigInteger z = new BigInteger(1, hash);\n        while (true) {\n            final BigInteger k = rng.nextK();\n            final BigInteger n = curveParameters.getN();\n            final ECPoint kp = curveParameters.getG().multiply(k).normalize();\n            final BigInteger r = kp.getXCoord().toBigInteger().mod(n);\n            if (r.equals(BigInteger.ZERO)) continue;\n            final BigInteger s_ = k.modInverse(n).multiply(r.multiply(d).add(z)).mod(n);\n            if (s_.equals(BigInteger.ZERO)) continue;\n            final BigInteger s = config.canonical && (s_.add(s_).compareTo(n) >= 0) ? n.subtract(s_) : s_;\n            ByteArrayOutputStream bos = new ByteArrayOutputStream();\n            try {\n                final DERSequenceGenerator derSequenceGenerator = new DERSequenceGenerator(bos);\n                derSequenceGenerator.addObject(new ASN1Integer(r));\n                derSequenceGenerator.addObject(new ASN1Integer(s));\n                if (config.recover || config.timeStampAndNonce != null)\n                    derSequenceGenerator.addObject(new ASN1Integer(computeRecoveryByte(kp, r, s_, config.canonical)));\n                if (config.timeStampAndNonce != null) {\n                    derSequenceGenerator.addObject(new ASN1Integer(config.timeStampAndNonce.timeStamp));\n                    derSequenceGenerator.addObject(new ASN1Integer(config.timeStampAndNonce.nonce));\n                }\n                derSequenceGenerator.close();\n                return bos.toByteArray();\n            } catch (IOException e) {\n                throw new SecurityException(e);\n            }\n        }\n    }\n\n    public byte[] sign(byte[] data, SignatureConfig config)\n            throws SecurityException {\n        GeneralDigest hashDigest = freshDigestFromDigest(config.messageDigest);\n        final byte[] hash = new byte[hashDigest.getDigestSize()];\n        if (config.timeStampAndNonce != null) {\n            final byte[] timeStampBytes = longToBytes(config.timeStampAndNonce.timeStamp);\n            hashDigest.update(timeStampBytes, 0, timeStampBytes.length);\n            final byte[] nonceBytes = longToBytes(config.timeStampAndNonce.nonce);\n            hashDigest.update(nonceBytes, 0, nonceBytes.length);\n        }\n        hashDigest.update(data, 0, data.length);\n        hashDigest.doFinal(hash, 0);\n        return signHash(hash, config);\n    }\n\n    public byte[] sign(byte[] data) throws SecurityException {\n        return sign(data, getDefaultSignatureConfig());\n    }\n\n    public byte[] signUTF8String(String string, SignatureConfig config)\n            throws SecurityException {\n        return sign(stringToUTF8Bytes(string), config);\n    }\n\n    public byte[] signUTF8String(String string)\n            throws SecurityException {\n        return signUTF8String(string, getDefaultSignatureConfig());\n    }\n\n    public byte[] diffieHelmanSharedSecret(PublicKey publicKey) throws SecurityException {\n        if (!(curveParameters.getCurve().equals(publicKey.curveParameters.getCurve())\n                && curveParameters.getG().equals(publicKey.curveParameters.getG())\n                && curveParameters.getN().equals(publicKey.curveParameters.getN())\n                && curveParameters.getH().equals(publicKey.curveParameters.getH())))\n            throw new SecurityException(\"Public key does not have the same curve parameters as private key\");\n        return publicKey.point.multiply(this.d).getEncoded(true);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        final PrivateKey privateKey = (PrivateKey) o;\n\n        return d.equals(privateKey.d)\n                && curveParameters.getCurve().equals(privateKey.curveParameters.getCurve())\n                && curveParameters.getG().equals(privateKey.curveParameters.getG())\n                && curveParameters.getN().equals(privateKey.curveParameters.getN())\n                && curveParameters.getH().equals(privateKey.curveParameters.getH());\n\n    }\n\n    @Override\n    public int hashCode() {\n        int code = d.hashCode();\n        code = 31 * code + curveParameters.getCurve().hashCode();\n        code = 37 * code + curveParameters.getG().hashCode();\n        code = 41 * code + curveParameters.getN().hashCode();\n        code = 43 * code + curveParameters.getH().hashCode();\n        return code;\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc2/PrivateKeyTest.java",
    "content": "package test.jce.ecc2;\n\nimport static test.jce.ecc2.BaseConvert.baseEncodedStringToByteArray;\nimport static test.jce.ecc2.BaseConvert.byteArrayToBaseEncodedString;\nimport static test.jce.ecc2.CurveParameters.secp256k1;\nimport static test.jce.ecc2.CurveParameters.secp256r1;\nimport static test.jce.ecc2.PrivateKey.getDefaultSignatureConfig;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.math.BigInteger;\n\nimport org.bouncycastle.crypto.digests.SHA256Digest;\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class PrivateKeyTest {\n\n    @Test\n    public void testFromAndToString() throws Exception {\n        Assert.assertEquals(\n                \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).toString(16));\n        Assert.assertEquals(PrivateKey.fromString(\n                secp256k1,\n                \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                16).toString(),\n                \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\");\n        Assert.assertEquals(PrivateKey.fromString(\n                secp256k1,\n                \"01\",\n                16).toString(),\n                \"01\");\n        Assert.assertEquals(PrivateKey.fromString(\n                secp256k1,\n                \"01\",\n                10).toString(10),\n                \"1\");\n    }\n\n    @Test(expected = SecurityException.class)\n    public void testFromStringEmptyStringThrows() throws Exception {\n        PrivateKey.fromString(secp256k1, \"\", 16);\n    }\n\n    @Test(expected = SecurityException.class)\n    public void testFromStringZeroStringThrows() throws Exception {\n        PrivateKey.fromString(secp256k1, \"00\", 16);\n    }\n\n    @Test(expected = SecurityException.class)\n    public void testFromStringVeryBigInputStringThrows() throws Exception {\n        PrivateKey.fromString(secp256k1, \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08cc6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\", 16);\n    }\n\n    @Test(expected = UnsupportedBaseException.class)\n    public void testFromStringUnsupportedBase() throws Exception {\n        PrivateKey.fromString(secp256k1, \"01\", 1234);\n    }\n\n    @Test\n    public void testGetPublicKey() throws Exception {\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).getPublicKey().toString(16),\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\"\n        );\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).getPublicKey(),\n                PublicKey.fromString(secp256k1,\n                        \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                        16));\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        CurveParameters.getCurveParametersByName(\"secp256k1\"),\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).getPublicKey(),\n                PublicKey.fromString(secp256k1,\n                        \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                        16));\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testSetTimeStampAndNonceSignatureConfigBuilderSadPath() {\n        new PrivateKey.SignatureConfigBuilder().setRecover(false).setTimeStampAndNonce(true);\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testSetTimeStampAndNonceSignatureConfigSadPath() {\n        //noinspection ConstantConditions\n        new PrivateKey.SignatureConfig(false, true, true, new SHA256Digest(), new SHA256Digest());\n    }\n\n    private static PrivateKey examplePrivateKey;\n\n    static {\n        try {\n            examplePrivateKey = PrivateKey.fromString(secp256k1,\n                    \"AgC/Dji4Yyn4TqkJcuDd3ltenDh\",\n                    64);\n        } catch (UnsupportedBaseException ignored) {\n        }\n    }\n\n    @Test(expected = UnsupportedBaseException.class)\n    public void testToStringUnsupportedBaseException() throws Exception {\n        String out = examplePrivateKey.toString(12345);\n        throw new RuntimeException(out);\n    }\n\n    @Test\n    public void testEquality() throws Exception {\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16),\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16));\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16),\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"000000c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16));\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16),\n                PrivateKey.fromString(\n                        CurveParameters.getCurveParametersByName(\"secp256k1\"),\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16));\n    }\n\n    @Test\n    public void testDeterministicSignatureNoRecover() throws Exception {\n        PrivateKey privateKey = PrivateKey.fromString(\n                secp256k1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        String input = \"コトドリ属（コトドリぞく、学名 Menura）はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。\";\n        byte[] signatureBytes = privateKey.signUTF8String(input,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(false)\n                        .build());\n        Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes));\n        String signature = byteArrayToBaseEncodedString(signatureBytes, 16);\n        Assert.assertEquals(\"3045022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b5048834\", signature);\n    }\n\n    @Test\n    public void testDeterministicSignatureWithRecover() throws Exception {\n        PrivateKey privateKey = PrivateKey.fromString(\n                secp256k1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        String input = \"コトドリ属（コトドリぞく、学名 Menura）はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。\";\n        byte[] signatureBytes = privateKey.signUTF8String(input,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setTimeStampAndNonce(false)\n                        .build());\n        Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes));\n        String signature = byteArrayToBaseEncodedString(signatureBytes, 16);\n        Assert.assertEquals(\"3048022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b504883402011b\", signature);\n    }\n\n    @Test\n    public void testDeterministicSignatureWithRecoverTestCanonical() throws Exception {\n        PrivateKey privateKey = PrivateKey.fromString(\n                secp256k1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        String input = \"コトドリ属（コトドリぞく、学名 Menura）はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。XXX\";\n        byte[] canonicalSignatureBytes = privateKey.signUTF8String(input,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setTimeStampAndNonce(false)\n                        .setCanonical(true)\n                        .build());\n        Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, canonicalSignatureBytes));\n        String canonicalSignature = byteArrayToBaseEncodedString(canonicalSignatureBytes, 16);\n        byte[] nonCanonicalSignatureBytes = privateKey.signUTF8String(input,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setTimeStampAndNonce(false)\n                        .setCanonical(false)\n                        .build());\n        Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, nonCanonicalSignatureBytes));\n        String nonCanonicalSignature = byteArrayToBaseEncodedString(nonCanonicalSignatureBytes, 16);\n        Assert.assertFalse(canonicalSignature.equals(nonCanonicalSignature));\n    }\n\n    @Test\n    public void testDeterministicSignatureWithRecoverByteArray() throws Exception {\n        PrivateKey privateKey = PrivateKey.fromByteArray(\n                secp256k1,\n                BaseConvert.baseEncodedStringToByteArray(\"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\", 16));\n        String input = \"コトドリ属（コトドリぞく、学名 Menura）はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。\";\n        byte[] signatureBytes = privateKey.signUTF8String(input,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setTimeStampAndNonce(false)\n                        .build());\n        Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes));\n        String signature = byteArrayToBaseEncodedString(signatureBytes, 16);\n        Assert.assertEquals(\"3048022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b504883402011b\", signature);\n    }\n\n    @Test\n    public void testDeterministicSignatureWithRecoverByteArrayBackAndForth() throws Exception {\n        PrivateKey privateKey =\n                PrivateKey.fromByteArray(\n                        secp256k1,\n                        PrivateKey.fromByteArray(\n                                secp256k1,\n                                BaseConvert.baseEncodedStringToByteArray(\"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\", 16))\n                                .toByteArray());\n        String input = \"コトドリ属（コトドリぞく、学名 Menura）はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。\";\n        byte[] signatureBytes = privateKey.signUTF8String(input,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setTimeStampAndNonce(false)\n                        .build());\n        Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes));\n        String signature = byteArrayToBaseEncodedString(signatureBytes, 16);\n        Assert.assertEquals(\"3048022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b504883402011b\", signature);\n    }\n\n    @Test\n    public void testDeterministicSignatureWithRecoverConstructor() throws Exception {\n        PrivateKey privateKey = new PrivateKey(\n                secp256k1,\n                new BigInteger(1, BaseConvert.baseEncodedStringToByteArray(\"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\", 16)));\n        String input = \"コトドリ属（コトドリぞく、学名 Menura）はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。\";\n        byte[] signatureBytes = privateKey.signUTF8String(input,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setTimeStampAndNonce(false)\n                        .build());\n        Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes));\n        String signature = byteArrayToBaseEncodedString(signatureBytes, 16);\n        Assert.assertEquals(\"3048022100a28224c02e60f4e0a345cfc1043de9be408301393eec9225ab849d6bed8b794302205d09d76f6ae27094c005883d41e7059bb14afb0d9b61f9c051dea384b504883402011b\", signature);\n    }\n\n    @Test\n    public void testDeterministicSignatureWithRecoverBigIntegerONE() throws Exception {\n        PrivateKey privateKey = new PrivateKey(secp256k1, BigInteger.ONE);\n        String input = \"コトドリ属（コトドリぞく、学名 Menura）はコトドリ上科コトドリ科 Menuridae に属する鳥の属の一つ。コトドリ科は単型である。\";\n        byte[] signatureBytes = privateKey.signUTF8String(input,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setTimeStampAndNonce(false)\n                        .build());\n        Assert.assertTrue(privateKey.getPublicKey().verifySignedUTF8String(input, signatureBytes));\n        String signature = byteArrayToBaseEncodedString(signatureBytes, 16);\n        Assert.assertEquals(\"3048022100972b6487837a509cc781ad73fa07c92bbbb65fb8aa35de97a341a0dcdb5244ba022031d27cbe8d4998806862d4df7a75b5b4a102db13aea1b51a080d13f45fda57a402011c\", signature);\n    }\n\n    @Test\n    public void testSignHash() throws Exception {\n        PrivateKey privateKey = PrivateKey.fromString(\n                secp256k1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        Method signHash = PrivateKey.class.getDeclaredMethod(\"signHash\", byte[].class, PrivateKey.SignatureConfig.class);\n        signHash.setAccessible(true);\n        signHash.invoke(privateKey, new byte[secp256k1.getN().bitLength() / 8], getDefaultSignatureConfig());\n    }\n\n    @Test(expected = InvocationTargetException.class)\n    public void testSignHashBigHashSadPath() throws Exception {\n        PrivateKey privateKey = PrivateKey.fromString(\n                secp256k1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        Method signHash = PrivateKey.class.getDeclaredMethod(\"signHash\", byte[].class, PrivateKey.SignatureConfig.class);\n        signHash.setAccessible(true);\n        signHash.invoke(privateKey, new byte[512], getDefaultSignatureConfig());\n    }\n\n    @Test\n    public void testHashCode() throws Exception {\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).hashCode(),\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).hashCode());\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).hashCode(),\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"000000c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).hashCode());\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).hashCode(),\n                PrivateKey.fromString(\n                        CurveParameters.getCurveParametersByName(\"secp256k1\"),\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).hashCode());\n    }\n\n    @Test\n    public void testDiffieHelman() throws Exception {\n        PrivateKey privateKey1 = PrivateKey.fromString(\n                secp256k1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        PrivateKey privateKey2 = PrivateKey.fromString(\n                secp256k1,\n                \"0ffffffffffffffffffffffffffffffffff9252c7f55610b8d0859d8752235a9\",\n                16);\n        Assert.assertArrayEquals(\n                privateKey1.diffieHelmanSharedSecret(privateKey2.getPublicKey()),\n                privateKey2.diffieHelmanSharedSecret(privateKey1.getPublicKey()));\n    }\n\n    @Test(expected = SecurityException.class)\n    public void testDiffieHelmanSadPath() throws Exception {\n        PrivateKey privateKey1 = PrivateKey.fromString(\n                secp256k1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        PrivateKey privateKey2 = PrivateKey.fromString(\n                secp256r1,\n                \"0ffffffffffffffffffffffffffffffffff9252c7f55610b8d0859d8752235a9\",\n                16);\n        privateKey1.diffieHelmanSharedSecret(privateKey2.getPublicKey());\n    }\n\n    @Test\n    public void testSignatureSadPath() throws Exception {\n        PrivateKey privateKey1 = PrivateKey.fromString(\n                secp256k1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        PrivateKey privateKey2 = PrivateKey.fromString(\n                secp256k1,\n                \"0ffffffffffffffffffffffffffffffffff9252c7f55610b8d0859d8752235a9\",\n                16);\n        String message = \"Moloch!\";\n        String data = byteArrayToBaseEncodedString(privateKey2.signUTF8String(message), 16);\n        Assert.assertFalse(\n                privateKey1.getPublicKey().verifySignedUTF8String(\n                        message,\n                        baseEncodedStringToByteArray(data, 16)));\n    }\n\n    @Test\n    public void testTimeStampChronologicalOrder() throws Exception {\n        PrivateKey privateKey1 = PrivateKey.fromString(\n                secp256k1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        PrivateKey privateKey2 = PrivateKey.fromString(\n                secp256r1,\n                \"22c49372a7506d162e6551fca36eb59235a9252c7f55610b8d0859d8752235a9\",\n                16);\n        String message = \"Moloch!\";\n        Long timeStamp1 = PublicKey.getTimeStampFromSignature(privateKey1.signUTF8String(message));\n        Long timeStamp2 = PublicKey.getTimeStampFromSignature(privateKey2.signUTF8String(message));\n        Long now = System.currentTimeMillis();\n        Assert.assertTrue(timeStamp1 <= timeStamp2);\n        Assert.assertTrue(timeStamp2 <= now);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc2/PublicKey.java",
    "content": "package test.jce.ecc2;\n\nimport static javax.xml.bind.DatatypeConverter.printHexBinary;\nimport static test.jce.ecc2.BaseConvert.baseEncodedStringToByteArray;\nimport static test.jce.ecc2.BaseConvert.byteArrayToBaseEncodedString;\nimport static test.jce.ecc2.Utils.*;\n\nimport java.math.BigInteger;\n\nimport org.bouncycastle.asn1.ASN1InputStream;\nimport org.bouncycastle.asn1.ASN1Integer;\nimport org.bouncycastle.asn1.DLSequence;\nimport org.bouncycastle.crypto.digests.GeneralDigest;\nimport org.bouncycastle.crypto.digests.SHA256Digest;\nimport org.bouncycastle.crypto.params.ECDomainParameters;\nimport org.bouncycastle.crypto.params.ECPublicKeyParameters;\nimport org.bouncycastle.crypto.signers.ECDSASigner;\nimport org.bouncycastle.math.ec.ECAlgorithms;\nimport org.bouncycastle.math.ec.ECCurve;\nimport org.bouncycastle.math.ec.ECPoint;\n\nfinal public class PublicKey {\n\n    final ECPoint point;\n    final ECDomainParameters curveParameters;\n    private final ECDSASigner verifier = new ECDSASigner();\n\n    /**\n     * Verify if a point is valid for this curve.\n     *\n     * @param curveParameters The parameters of the elliptic curve to be used\n     * @param point           An elliptic curve point\n     * @return Whether this point was valid or not.\n     */\n    private static boolean isValidPoint(ECDomainParameters curveParameters, ECPoint point) {\n        final BigInteger x = point.getXCoord().toBigInteger();\n        final BigInteger y = point.getYCoord().toBigInteger();\n        final ECCurve ec = curveParameters.getCurve();\n        final BigInteger a = ec.getA().toBigInteger();\n        final BigInteger b = ec.getB().toBigInteger();\n        final BigInteger p = ec.getField().getCharacteristic();\n        return x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p)\n                .equals(y.multiply(y).mod(p));\n    }\n\n    /**\n     * Construct a PublicKey given specified curve parameters and an elliptic curve point.\n     *\n     * @param curveParameters The parameters of the elliptic curve to use\n     * @param point           The point to use as a public key on the elliptic curve\n     * @throws SecurityException A security exception is thrown in the event that point is not valid\n     */\n    public PublicKey(ECDomainParameters curveParameters, ECPoint point) throws SecurityException {\n        if (point.getCurve() != curveParameters.getCurve())\n            throw new SecurityException(\"Point is not on curve specified by curve parameters\");\n        if (!isValidPoint(curveParameters, point))\n            throw new SecurityException(\"Cannot initialize an invalid elliptic curve point\");\n        this.point = point;\n        this.curveParameters = curveParameters;\n        verifier.init(false, new ECPublicKeyParameters(this.point, curveParameters));\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        PublicKey publicKey = (PublicKey) o;\n\n        return point.equals(publicKey.point)\n                && curveParameters.getCurve().equals(publicKey.curveParameters.getCurve())\n                && curveParameters.getG().equals(publicKey.curveParameters.getG())\n                && curveParameters.getN().equals(publicKey.curveParameters.getN())\n                && curveParameters.getH().equals(publicKey.curveParameters.getH());\n\n    }\n\n    @Override\n    public int hashCode() {\n        int code = point.hashCode();\n        code = 31 * code + curveParameters.getCurve().hashCode();\n        code = 37 * code + curveParameters.getG().hashCode();\n        code = 41 * code + curveParameters.getN().hashCode();\n        code = 43 * code + curveParameters.getH().hashCode();\n        return code;\n    }\n\n    /**\n     * Recover an X9.62 encoded public key from an array of bytes\n     *\n     * @param curveParameters The parameters of the elliptic curve to be used\n     * @param bytes           The X9.62 encoded public key\n     * @return The public key corresponding to the input bytes\n     * @throws SecurityException Throws a SecurityException if the point given is invalid (ie, not on the specified curve)\n     */\n    public static PublicKey fromByteArray(\n            ECDomainParameters curveParameters,\n            byte[] bytes) throws SecurityException {\n        return new PublicKey(curveParameters, curveParameters.getCurve().decodePoint(bytes));\n    }\n\n    /**\n     * Recover an uncompressed X9.62 encoded public key from an array of bytes\n     *\n     * @param curveParameters The parameters of the elliptic curve to be used\n     * @param bytes           The X9.62 encoded public key\n     * @return The public key corresponding to the input bytes\n     * @throws SecurityException Throws a SecurityException if the point given is invalid (ie, not on the specified curve or compressed)\n     */\n    public static PublicKey fromUncompressedByteArray(\n            ECDomainParameters curveParameters,\n            byte[] bytes) throws SecurityException {\n        if (bytes[0] != 0x04)\n            throw new SecurityException(String.format(\"Expected first bytes of array to be 0x04: %s\", printHexBinary(bytes)));\n        return new PublicKey(curveParameters, curveParameters.getCurve().decodePoint(bytes));\n    }\n\n    /**\n     * Recover an X9.62 encoded public key from a string encoding an array of bytes\n     *\n     * @param curveParameters The parameters of the elliptic curve to be used\n     * @param string          The X9.62 encoded public key as a string encoding a byte array\n     * @param base            The base of encoded string\n     * @return The public key corresponding to the input string\n     * @throws SecurityException        In the event of an invalid key\n     * @throws UnsupportedBaseException If the user is trying to convert from an unsupported base\n     */\n    public static PublicKey fromString(\n            ECDomainParameters curveParameters,\n            String string,\n            int base) throws SecurityException, UnsupportedBaseException {\n        return fromByteArray(curveParameters, baseEncodedStringToByteArray(string, base));\n    }\n\n    /**\n     * Convert the elliptic curve point to a X9.62 compressed byte array encoding\n     *\n     * @return The compressed X9.62 byte array encoding of the curve point associated with the public key\n     */\n    public byte[] toByteArray() {\n        return toByteArray(true);\n    }\n\n    /**\n     * Convert the elliptic curve point to a X9.62 encoded byte array, with compression used as specified\n     *\n     * @param compressed Whether the resulting curve point is to be compressed when converted to a byte array\n     * @return A X9.62 encoded byte array representing the curve point associated with the public key\n     */\n    public byte[] toByteArray(boolean compressed) {\n        return point.getEncoded(compressed);\n    }\n\n    /**\n     * Convert the elliptic curve point to a string representing a X9.62 compressed byte array encoding with the given base\n     *\n     * @param radix Base to use when encoding\n     * @return A string representing the compressed X9.62 byte array encoding\n     * @throws UnsupportedBaseException Thrown when the specified base is not supported\n     */\n    public String toString(int radix) throws UnsupportedBaseException {\n        return byteArrayToBaseEncodedString(toByteArray(), radix);\n    }\n\n    /**\n     * Convert the elliptic curve point to a string representing a X9.62 byte array encoding with the given base\n     *\n     * @param base       Base to use when encoding\n     * @param compressed Whether to use compression or not when constructing the byte array\n     * @return A string representing the X9.62 byte array encoding\n     * @throws UnsupportedBaseException Thrown when the specified base is not supported\n     */\n    public String toString(int base, boolean compressed) throws UnsupportedBaseException {\n        return byteArrayToBaseEncodedString(toByteArray(compressed), base);\n    }\n\n    /**\n     * Convert the elliptic curve point to a hexadecimal string encoding a X9.62 compressed byte array\n     *\n     * @return A hexadecimal string encoding a X9.62 compressed byte array\n     */\n    @Override\n    public String toString() {\n        return printHexBinary(toByteArray()).toLowerCase();\n    }\n\n    private static class TimeStampAndNonce {\n        final byte[] timeStampBytes;\n        final byte[] nonceBytes;\n\n        TimeStampAndNonce(long timeStamp, long nonce) {\n            this.timeStampBytes = longToBytes(timeStamp);\n            this.nonceBytes = longToBytes(nonce);\n        }\n    }\n\n    private static class DeserializedSignature {\n        final BigInteger r;\n        final BigInteger s;\n        final byte recover;\n        final TimeStampAndNonce timeStampAndNonce;\n\n        DeserializedSignature(byte[] signature) {\n            try (ASN1InputStream decoder = new ASN1InputStream(signature)) {\n                final DLSequence sequence = (DLSequence) decoder.readObject();\n                final int length = sequence.toArray().length;\n                this.r = ((ASN1Integer) sequence.getObjectAt(0)).getValue();\n                this.s = ((ASN1Integer) sequence.getObjectAt(1)).getValue();\n                this.recover = (length >= 3) ? (byte) ((ASN1Integer) sequence.getObjectAt(2)).getValue().intValue()\n                        : 0;\n                this.timeStampAndNonce = (length >= 5) ?\n                        new TimeStampAndNonce(\n                                ((ASN1Integer) sequence.getObjectAt(3)).getValue().longValue(),\n                                ((ASN1Integer) sequence.getObjectAt(4)).getValue().longValue()) : null;\n                if (length == 4)\n                    throw new SecurityException(\"Signature cannot specify a time stamp without a nonce\");\n                if (length > 5)\n                    throw new SecurityException(\"Signature cannot have more than 5 entries\");\n            } catch (Throwable e) {\n                throw new SecurityException(e);\n            }\n        }\n    }\n\n    /**\n     * Extracts the timestamp from an ASN.1 signature\n     *\n     * @param signature The signature bytes to extract the timestamp from\n     * @return The timestamp in the signature, represented as milliseconds since epoch\n     */\n    public static Long getTimeStampFromSignature(byte[] signature) {\n        final DeserializedSignature deserializedSignature = new DeserializedSignature(signature);\n        if (deserializedSignature.timeStampAndNonce == null)\n            throw new SecurityException(\"Signature did not contain a timestamp\");\n        return bytesToLong(deserializedSignature.timeStampAndNonce.timeStampBytes);\n    }\n\n    /**\n     * Compute an elliptic curve point from a specified X coordinate given the parity of the Y coordinate\n     *\n     * @param curveParameters The parameters of the curve to use when specifying the X coordinate\n     * @param yEven           Whether the Y coordinate is even or not\n     * @param xCoordinate     The X coordinate of the curve point to be computed\n     * @return The elliptic curve point given the specified curve parameters that has the appropriate X coordinate and Y coordinate\n     * @throws SecurityException Thrown when the specified point is invalid (ie, not on the given curve)\n     */\n    private static ECPoint computePoint(ECDomainParameters curveParameters, boolean yEven, BigInteger xCoordinate)\n            throws SecurityException {\n        final int bitCount = curveParameters.getN().bitLength();\n        if ((bitCount & 0x07) != 0)\n            throw new SecurityException(String.format(\"Curve does not have an even number of bytes (number of bits is: %d)\", bitCount));\n        final int curveLength = bitCount / 8;\n        final byte[] raw = xCoordinate.toByteArray();\n        final byte[] input = new byte[curveLength + 1];\n        if (raw.length > curveLength) {\n            final int zeros = countLeadingZeroBytes(raw);\n            if (raw.length - zeros > curveLength)\n                throw new SecurityException(\"X Coordinate has more bytes than curve length\");\n            System.arraycopy(raw, zeros, input, curveLength - (raw.length - zeros) + 1, raw.length - zeros);\n        } else\n            System.arraycopy(raw, 0, input, curveLength - raw.length + 1, raw.length);\n        input[0] = (byte) (yEven ? 0x02 : 0x03);\n        try {\n            return curveParameters.getCurve().decodePoint(input);\n        } catch (IllegalArgumentException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * Recovers a {@code PublicKey}, given a hash, from an extended ECDSA signature that includes a recovery byte\n     *\n     * @param curveParameters The parameters of the curve to use when recovering a {@code PublicKey}\n     * @param hash            The hash of the data that has been signed\n     * @param signature       An extended ECDSA signature including a recovery byte\n     * @return The public key recovered from the signature\n     * @throws SecurityException Thrown when there is an invalid recovery byte\n     */\n    public static PublicKey recoverPublicKeyWithHash(\n            ECDomainParameters curveParameters,\n            byte[] hash,\n            byte[] signature) throws SecurityException {\n        DeserializedSignature sig = new DeserializedSignature(signature);\n        if (!(0x1B <= sig.recover && sig.recover <= 0x1E))\n            throw new SecurityException(\"Invalid recovery byte\");\n        final boolean yEven = ((sig.recover - 0x1B) & 1) == 0;\n        final boolean isSecondKey = (((sig.recover - 0x1B) >> 1) & 1) == 1;\n        final BigInteger n = curveParameters.getN();\n        final ECPoint kp = computePoint(curveParameters, yEven, isSecondKey ? sig.r.add(n) : sig.r);\n        final BigInteger eInverse = n.subtract(new BigInteger(1, hash));\n        final BigInteger rInverse = sig.r.modInverse(n);\n        final ECPoint recoveredPointCandidate =\n                ECAlgorithms\n                        .sumOfTwoMultiplies(curveParameters.getG(), eInverse, kp, sig.s)\n                        .multiply(rInverse)\n                        .normalize();\n        return new PublicKey(curveParameters, recoveredPointCandidate);\n    }\n\n    /**\n     * Recovers a {@code PublicKey}, given a byte array of data, from an extended ECDSA signature of a hash of that data that includes a recovery byte\n     *\n     * @param curveParameters The parameters of the curve\n     * @param data            The data that is to be hashed and signed\n     * @param signature       An extended ECDSA signature including a recovery byte\n     * @param hashDigest      The hashing digest to use to generate the hash that was signed\n     * @return A recovered public key\n     * @throws SecurityException Thrown when the recovered elliptic curve point is not on the specified curve\n     */\n    public static PublicKey recoverPublicKey(\n            ECDomainParameters curveParameters,\n            byte[] data,\n            byte[] signature,\n            GeneralDigest hashDigest) throws SecurityException {\n        final GeneralDigest freshHashDigest = freshDigestFromDigest(hashDigest);\n        final byte[] hash = new byte[freshHashDigest.getDigestSize()];\n        final DeserializedSignature sig = new DeserializedSignature(signature);\n        if (sig.timeStampAndNonce != null) {\n            freshHashDigest.update(sig.timeStampAndNonce.timeStampBytes, 0,\n                    sig.timeStampAndNonce.timeStampBytes.length);\n            freshHashDigest.update(sig.timeStampAndNonce.nonceBytes, 0,\n                    sig.timeStampAndNonce.nonceBytes.length);\n        }\n        freshHashDigest.update(data, 0, data.length);\n        freshHashDigest.doFinal(hash, 0);\n        return recoverPublicKeyWithHash(curveParameters, hash, signature);\n    }\n\n    /**\n     * Recovers a {@code PublicKey}, given a byte array of data, from an extended ECDSA signature of a SHA-256 hash of that data that includes a recovery byte\n     *\n     * @param curveParameters The parameters of the curve\n     * @param data            The data that is to be SHA-256 hashed and signed\n     * @param signature       An extended ECDSA signature including a recovery byte\n     * @return A recovered public key\n     * @throws SecurityException Thrown when the recovered elliptic curve point is not on the specified curve or the signature is otherwise incorrectly formatted\n     */\n    public static PublicKey recoverPublicKey(\n            ECDomainParameters curveParameters,\n            byte[] data,\n            byte[] signature) throws SecurityException {\n        return recoverPublicKey(curveParameters, data, signature, new SHA256Digest());\n    }\n\n    public static PublicKey recoverPublicKeyFromSignedUTF8String(\n            ECDomainParameters curveParameters,\n            String string,\n            byte[] signature,\n            GeneralDigest hashDigest) throws SecurityException {\n        return recoverPublicKey(curveParameters, stringToUTF8Bytes(string), signature, hashDigest);\n    }\n\n    public static PublicKey recoverPublicKeyFromSignedUTF8String(\n            ECDomainParameters curveParameters,\n            String string,\n            byte[] signature) throws SecurityException {\n        return recoverPublicKeyFromSignedUTF8String(curveParameters, string, signature, new SHA256Digest());\n    }\n\n    /**\n     * Verify an ECDSA signature of a hash\n     *\n     * @param hash      A hashed value that has been signed\n     * @param signature An ECDSA signature\n     * @return Whether the signature is valid\n     * @throws SecurityException If there is an invalid recovery byte\n     */\n    private boolean verifySignatureFromHash(byte[] hash, byte[] signature) throws SecurityException {\n        DeserializedSignature sig = new DeserializedSignature(signature);\n        if (sig.recover != 0)\n            return this.equals(recoverPublicKeyWithHash(this.curveParameters, hash, signature));\n        return verifier.verifySignature(hash, sig.r, sig.s);\n    }\n\n    /**\n     * Verify an ECDSA Signature of a hash of specified input\n     *\n     * @param data       Data to be hashed\n     * @param signature  Signature of the hashed data\n     * @param hashDigest The hashing digest to use to generate the hash to check against the signature\n     * @return Whether the signature is valid\n     * @throws SecurityException If the signature cannot be safely deserialized or there is an invalid recovery byte\n     */\n    public boolean verifySignature(byte[] data, byte[] signature, GeneralDigest hashDigest)\n            throws SecurityException {\n        final GeneralDigest freshHashDigest = freshDigestFromDigest(hashDigest);\n        final byte[] hash = new byte[hashDigest.getDigestSize()];\n        final DeserializedSignature sig = new DeserializedSignature(signature);\n        if (sig.timeStampAndNonce != null) {\n            freshHashDigest.update(sig.timeStampAndNonce.timeStampBytes, 0,\n                    sig.timeStampAndNonce.timeStampBytes.length);\n            freshHashDigest.update(sig.timeStampAndNonce.nonceBytes, 0,\n                    sig.timeStampAndNonce.nonceBytes.length);\n        }\n        freshHashDigest.update(data, 0, data.length);\n        freshHashDigest.doFinal(hash, 0);\n        return verifySignatureFromHash(hash, signature);\n    }\n\n    /**\n     * Verify an ECDSA Signature of a SHA-256 hash of specified input\n     *\n     * @param data      Data to be hashed\n     * @param signature Signature of the hashed data\n     * @return Whether the signature is valid\n     * @throws SecurityException When a signature cannot be properly deserialized or contains an invalid recovery byte\n     */\n    public boolean verifySignature(byte[] data, byte[] signature)\n            throws SecurityException {\n        return verifySignature(data, signature, new SHA256Digest());\n    }\n\n    /**\n     * Verify an ECDSA Signature of a hash of a UTF-8 encoded string\n     *\n     * @param string     The string to be hashed\n     * @param signature  Signature of the hashed string\n     * @param hashDigest The hashing digest to use to generate the hash to check against the signature\n     * @return Whether the signature is valid\n     * @throws SecurityException If the string is not a properly formatted ASN.1 signature\n     */\n    public boolean verifySignedUTF8String(String string, byte[] signature, GeneralDigest hashDigest)\n            throws SecurityException {\n        return verifySignature(stringToUTF8Bytes(string), signature, hashDigest);\n    }\n\n    /**\n     * Verify an ECDSA Signature of a SHA-256 hash of a UTF-8 encoded string\n     *\n     * @param string    The string to be hashed\n     * @param signature Signature of the hashed string\n     * @return Whether the signature is valid\n     * @throws SecurityException If the string is not a properly formatted ASN.1 signature\n     */\n    public boolean verifySignedUTF8String(String string, byte[] signature)\n            throws SecurityException {\n        return verifySignedUTF8String(string, signature, new SHA256Digest());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc2/PublicKeyTest.java",
    "content": "package test.jce.ecc2;\n\nimport org.bouncycastle.crypto.digests.GeneralDigest;\nimport org.bouncycastle.crypto.digests.SHA224Digest;\nimport org.bouncycastle.crypto.digests.SHA256Digest;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport static test.jce.ecc2.BaseConvert.baseEncodedStringToByteArray;\nimport static test.jce.ecc2.BaseConvert.byteArrayToBaseEncodedString;\nimport static test.jce.ecc2.CurveParameters.secp256k1;\nimport static test.jce.ecc2.CurveParameters.secp256r1;\nimport static test.jce.ecc2.PrivateKey.getDefaultSignatureConfig;\n\nimport java.math.BigInteger;\nimport java.security.MessageDigest;\n\npublic class PublicKeyTest {\n\n    @Test\n    public void testToString() throws Exception {\n        Assert.assertEquals(PublicKey.fromString(secp256k1,\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                16).toString(),\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\");\n        Assert.assertEquals(PublicKey.fromString(secp256k1,\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                16).toString(16),\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\");\n        Assert.assertEquals(PublicKey.fromString(secp256k1,\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                16).toString(16, true),\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\");\n        Assert.assertEquals(PublicKey.fromString(secp256k1,\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                16).toString(16, false),\n                \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\");\n        Assert.assertEquals(PublicKey.fromString(secp256k1,\n                \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                16).toString(16, false),\n                \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\");\n        Assert.assertEquals(PublicKey.fromString(secp256k1,\n                \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                16).toString(16, true),\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\");\n        Assert.assertEquals(\n                \"AgC/Dji4Yyn4TqkJcuD5AdXqAUXx66yMUP3td3ltenDh\",\n                PublicKey.fromString(secp256k1,\n                        \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                        16).toString(64, true));\n        Assert.assertEquals(\n                \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                PublicKey.fromString(secp256k1,\n                        \"AgC/Dji4Yyn4TqkJcuD5AdXqAUXx66yMUP3td3ltenDh\",\n                        64).toString(16, false));\n    }\n\n    @Test\n    public void testToByteArray() throws Exception {\n        Assert.assertEquals(\"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                PublicKey.fromByteArray(secp256k1, PublicKey.fromString(secp256k1,\n                        \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                        16).toByteArray()).toString(16, false));\n        Assert.assertEquals(\"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                PublicKey.fromUncompressedByteArray(secp256k1, PublicKey.fromString(secp256k1,\n                        \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                        16).toByteArray(false)).toString(16, false));\n        Assert.assertEquals(\n                \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                PublicKey.fromByteArray(secp256k1, PublicKey.fromString(secp256k1,\n                        \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                        16).toByteArray(true)).toString(16, false));\n        Assert.assertEquals(\n                \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                PublicKey.fromByteArray(secp256k1, PublicKey.fromString(secp256k1,\n                        \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                        16).toByteArray(false)).toString(16, false));\n        Assert.assertEquals(\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                PublicKey.fromByteArray(secp256k1, PublicKey.fromString(secp256k1,\n                        \"0400bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1be9e001b7ece071fb3986b5e96699fe28dbdeec8956682da78a5f6a115b9f14c\",\n                        16).toByteArray(false)).toString(16, true));\n    }\n\n    private static PublicKey examplePublicKey;\n\n    static {\n        try {\n            examplePublicKey = PublicKey.fromString(secp256k1,\n                    \"AgC/Dji4Yyn4TqkJcuD5AdXqAUXx66yMUP3td3ltenDh\",\n                    64);\n        } catch (UnsupportedBaseException ignored) {\n        }\n    }\n\n    @Test(expected = UnsupportedBaseException.class)\n    public void testToStringUnsupportedBaseException() throws Exception {\n        String out = examplePublicKey.toString(12345);\n        throw new RuntimeException(out);\n    }\n\n    @Test(expected = UnsupportedBaseException.class)\n    public void testFromStringSadPathUnsupportedBaseException() throws Exception {\n        PublicKey.fromString(secp256k1,\n                \"AgC/Dji4Yyn4TqkJcuD5AdXqAUXx66yMUP3td3ltenDh\",\n                12345);\n    }\n\n    @Test\n    public void testVerifySignedUTF8String() throws Exception {\n        PrivateKey privateKey = PrivateKey.fromString(\n                secp256k1,\n                \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                16);\n        PublicKey publicKey = privateKey.getPublicKey();\n\n        PrivateKey.SignatureConfig noRecoverNotCanonicalConfig = new PrivateKey.SignatureConfigBuilder()\n                .setRecover(false)\n                .setCanonical(false)\n                .build();\n\n        PrivateKey.SignatureConfig recoverButNoTimeStampsAndNonce = new PrivateKey.SignatureConfigBuilder()\n                .setRecover(false)\n                .setTimeStampAndNonce(false)\n                .build();\n\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"foo\",\n                privateKey.signUTF8String(\"foo\")));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"foo\",\n                privateKey.sign(\"foo\".getBytes(\"UTF-8\"))));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"foo\",\n                privateKey.signUTF8String(\"foo\", noRecoverNotCanonicalConfig)));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"foo\",\n                privateKey.sign(\"foo\".getBytes(\"UTF-8\"), noRecoverNotCanonicalConfig)));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"foo\",\n                privateKey.sign(\"foo\".getBytes(\"UTF-8\"), recoverButNoTimeStampsAndNonce)));\n        Assert.assertArrayEquals(\n                privateKey.sign(\"foo\".getBytes(\"UTF-8\"), noRecoverNotCanonicalConfig),\n                privateKey.signUTF8String(\"foo\", noRecoverNotCanonicalConfig));\n        PrivateKey.SignatureConfig defaultConfig = getDefaultSignatureConfig();\n        Assert.assertArrayEquals(\n                privateKey.sign(\"foo\".getBytes(\"UTF-8\"), defaultConfig),\n                privateKey.signUTF8String(\"foo\", defaultConfig));\n        Assert.assertTrue(publicKey.verifySignature(\"foo\".getBytes(\"UTF-8\"),\n                privateKey.sign(\"foo\".getBytes(\"UTF-8\"), noRecoverNotCanonicalConfig)));\n        Assert.assertTrue(publicKey.verifySignature(\"foo\".getBytes(\"UTF-8\"),\n                privateKey.sign(\"foo\".getBytes(\"UTF-8\"), noRecoverNotCanonicalConfig),\n                new SHA256Digest()));\n    }\n\n    @Test\n    public void testVerifySignedUTF8StringWithSecp256r1() throws Exception {\n        PrivateKey privateKey = PrivateKey.fromString(\n                secp256r1,\n                \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                16);\n        PublicKey publicKey = privateKey.getPublicKey();\n        PrivateKey.SignatureConfig noRecoverButCanonicalConfig = new PrivateKey.SignatureConfigBuilder()\n                .setRecover(false)\n                .setTimeStampAndNonce(false)\n                .build();\n        PrivateKey.SignatureConfig noRecoverNotCanonicalConfig = new PrivateKey.SignatureConfigBuilder()\n                .setRecover(false)\n                .setCanonical(false)\n                .setTimeStampAndNonce(false)\n                .build();\n        PrivateKey.SignatureConfig recoverNotCanonicalConfig = new PrivateKey.SignatureConfigBuilder()\n                .setRecover(true)\n                .setCanonical(false)\n                .setTimeStampAndNonce(false)\n                .build();\n        GeneralDigest sha256digest = new SHA256Digest();\n        PrivateKey.SignatureConfig recoverNotCanonicalExplicitMessageDigestConfig =\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setCanonical(false)\n                        .setTimeStampAndNonce(false)\n                        .setMessageDigest(sha256digest)\n                        .build();\n        PrivateKey.SignatureConfig recoverNotCanonicalExplicitRFC6979DigestConfig =\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setCanonical(false)\n                        .setTimeStampAndNonce(false)\n                        .setRfc6979Digest(sha256digest)\n                        .build();\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"foo\",\n                privateKey.signUTF8String(\"foo\")));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"foo\",\n                privateKey.sign(\"foo\".getBytes(\"UTF-8\"))));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"bar\",\n                privateKey.signUTF8String(\"bar\", noRecoverButCanonicalConfig)));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"bar\",\n                privateKey.signUTF8String(\"bar\", noRecoverButCanonicalConfig), new SHA256Digest()));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"baz\",\n                privateKey.sign(\"baz\".getBytes(\"UTF-8\"), noRecoverButCanonicalConfig)));\n        Assert.assertTrue(publicKey.verifySignature(\"قفقاز\".getBytes(\"UTF-8\"),\n                privateKey.sign(\"قفقاز\".getBytes(\"UTF-8\"), noRecoverNotCanonicalConfig)));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"कॉकेशस\",\n                privateKey.signUTF8String(\"कॉकेशस\", recoverNotCanonicalConfig)));\n        Assert.assertTrue(publicKey.verifySignedUTF8String(\"კავკაცია\",\n                privateKey.sign(\"კავკაცია\".getBytes(\"UTF-8\"), recoverNotCanonicalConfig)));\n        Assert.assertTrue(publicKey.verifySignature(\"കൊക്കേഷ്യ\".getBytes(\"UTF-8\"),\n                privateKey.sign(\"കൊക്കേഷ്യ\".getBytes(\"UTF-8\"), noRecoverNotCanonicalConfig)));\n        Assert.assertTrue(publicKey.verifySignature(\"കൊക്കേഷ്യ\".getBytes(\"UTF-8\"),\n                privateKey.sign(\"കൊക്കേഷ്യ\".getBytes(\"UTF-8\"), recoverNotCanonicalExplicitMessageDigestConfig)));\n        Assert.assertTrue(publicKey.verifySignature(\"കൊക്കേഷ്യ\".getBytes(\"UTF-8\"),\n                privateKey.sign(\"കൊക്കേഷ്യ\".getBytes(\"UTF-8\"), recoverNotCanonicalExplicitRFC6979DigestConfig)));\n    }\n\n    @Test\n    public void testRecoverPublicKey() throws Exception {\n        final PrivateKey privateKey = PrivateKey.fromString(\n                secp256k1,\n                \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                16);\n        final PublicKey publicKey = privateKey.getPublicKey();\n        Assert.assertEquals(\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                publicKey.toString(16));\n        final String message = \"foo\";\n        final byte[] bareSignature = privateKey.signUTF8String(message,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(false)\n                        .build());\n        Assert.assertEquals(\n                \"304402203dece00b786bb9d49ce00b87323e98afdd3c7ff67f45f56502dc281e98fae20102206efbfc836f990775edc60f50e8a74f913968288e30ae94703813b09db3f4f3dd\",\n                byteArrayToBaseEncodedString(bareSignature, 16));\n        final byte[] signature = privateKey.signUTF8String(message,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setTimeStampAndNonce(false)\n                        .build());\n        final PublicKey recoveredPublicKey = PublicKey.recoverPublicKey(\n                secp256k1,\n                message.getBytes(\"UTF-8\"),\n                signature);\n        Assert.assertEquals(publicKey.toString(16), recoveredPublicKey.toString(16));\n        final byte[] hash = MessageDigest.getInstance(\"SHA-256\").digest(message.getBytes(\"UTF-8\"));\n        final PublicKey recoveredPublicKeyFromHash = PublicKey.recoverPublicKeyWithHash(\n                secp256k1,\n                hash,\n                signature);\n        Assert.assertEquals(publicKey.toString(16), recoveredPublicKeyFromHash.toString(16));\n        final byte[] signatureWithTimeStampAndNonce = privateKey.signUTF8String(message,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setTimeStampAndNonce(true)\n                        .build());\n        Assert.assertEquals(publicKey.toString(16), PublicKey.recoverPublicKey(\n                secp256k1,\n                message.getBytes(\"UTF-8\"),\n                signatureWithTimeStampAndNonce).toString(16));\n        Assert.assertEquals(publicKey.toString(16), PublicKey.recoverPublicKey(\n                secp256k1,\n                message.getBytes(\"UTF-8\"),\n                signatureWithTimeStampAndNonce,\n                new SHA256Digest()).toString(16));\n    }\n\n    @Test\n    public void testRecoverPublicKeySHA224() throws Exception {\n        final PrivateKey privateKey = PrivateKey.fromString(\n                secp256k1,\n                \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                16);\n        final PublicKey publicKey = privateKey.getPublicKey();\n        Assert.assertEquals(\n                \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                publicKey.toString(16));\n        final String message = \"foo\";\n        final byte[] bareSignature = privateKey.signUTF8String(message,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(false)\n                        .setMessageDigest(new SHA224Digest())\n                        .build());\n        Assert.assertEquals(\n                \"3044022060d071aa96053205693a3c78ee4e1dd62676c4f73bc70f98fc44792d7b6a6c9902206d1b2532cee810d7f264d2cb77341b0e1197dc37a84858cdcdcbe82173ffdd38\",\n                byteArrayToBaseEncodedString(bareSignature, 16));\n        final byte[] signature = privateKey.signUTF8String(message,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setMessageDigest(new SHA224Digest())\n                        .setTimeStampAndNonce(false)\n                        .build());\n        final PublicKey recoveredPublicKey = PublicKey.recoverPublicKey(\n                secp256k1,\n                message.getBytes(\"UTF-8\"),\n                signature,\n                new SHA224Digest());\n        Assert.assertEquals(publicKey.toString(16), recoveredPublicKey.toString(16));\n        final byte[] hash = MessageDigest.getInstance(\"SHA-224\").digest(message.getBytes(\"UTF-8\"));\n        final PublicKey recoveredPublicKeyFromHash = PublicKey.recoverPublicKeyWithHash(\n                secp256k1,\n                hash,\n                signature);\n        Assert.assertEquals(publicKey.toString(16), recoveredPublicKeyFromHash.toString(16));\n        final byte[] signatureWithTimeStampAndNonce = privateKey.signUTF8String(message,\n                new PrivateKey.SignatureConfigBuilder()\n                        .setRecover(true)\n                        .setMessageDigest(new SHA224Digest())\n                        .setTimeStampAndNonce(true)\n                        .build());\n        Assert.assertEquals(publicKey.toString(16), PublicKey.recoverPublicKey(\n                secp256k1,\n                message.getBytes(\"UTF-8\"),\n                signatureWithTimeStampAndNonce,\n                new SHA224Digest()).toString(16));\n    }\n\n    @Test\n    public void testHashCodeTest() throws Exception {\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        secp256k1,\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).getPublicKey().hashCode(),\n                PublicKey.fromString(secp256k1,\n                        \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                        16).hashCode());\n        Assert.assertEquals(\n                PrivateKey.fromString(\n                        CurveParameters.getCurveParametersByName(\"secp256k1\"),\n                        \"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\",\n                        16).getPublicKey().hashCode(),\n                PublicKey.fromString(secp256k1,\n                        \"0200bf0e38b86329f84ea90972e0f901d5ea0145f1ebac8c50fded77796d7a70e1\",\n                        16).hashCode());\n    }\n\n    @Test(expected = SecurityException.class)\n    public void testPublicKeyConstructorPointOnWrongCurveSadPath() {\n        BigInteger d = new BigInteger(\"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\", 16);\n        new PublicKey(secp256k1, secp256r1.getG().multiply(d));\n    }\n\n    @Test(expected = SecurityException.class)\n    public void testPublicKeyConstructorInvalidPointSadPath() {\n        BigInteger d = new BigInteger(\"c6b7f6bfe5bb19b1e390e55ed4ba5df8af6068d0eb89379a33f9c19aacf6c08c\", 16);\n        // FYI, you need to normalize points before trying to make them PublicKeys!\n        new PublicKey(secp256k1, secp256k1.getG().multiply(d));\n    }\n\n    @Test\n    public void testRecoverPublicKeyFromSignedUTF8String() throws Exception {\n        byte[] signature = baseEncodedStringToByteArray(\"305a022100d55f884dd948b035a78c088af461f65dac80b35940891e914bbd9ba185778771022067eef94e9ae8217b24927a4d4016e9f6d2ac27e077ad11d02f2496d83ecdc9b502011c020601573f8f27af02086662fe5c7db4b76b\", 16);\n        PrivateKey privateKey2 = PrivateKey.fromString(\n                secp256r1,\n                \"0ffffffffffffffffffffffffffffffffff9252c7f55610b8d0859d8752235a9\",\n                16);\n        String message = \"Moloch!\";\n        Assert.assertEquals(privateKey2.getPublicKey(),\n                PublicKey.recoverPublicKeyFromSignedUTF8String(secp256r1, message, signature));\n        Assert.assertEquals(privateKey2.getPublicKey(),\n                PublicKey.recoverPublicKeyFromSignedUTF8String(secp256r1, message, signature, new SHA256Digest()));\n    }\n\n    @Test(expected = SecurityException.class)\n    public void testRecoverPublicKeyFromSignedUTF8StringSadPath() throws Exception {\n        byte[] signature = baseEncodedStringToByteArray(\"305a022100d55f884dd948b035a78c088af461f65dac80b35940891e914bbd9ba185778771022067eef94e9ae8217b24927a4d4016e9f6d2ac27e077ad11d02f2496d83ecdc9b502011c020601573f8f27af02086662fe5c7db4b76b\", 16);\n        String message = \"Moloch!\";\n        PublicKey.recoverPublicKeyFromSignedUTF8String(secp256k1, message, signature);\n    }\n\n    @Test(expected = SecurityException.class)\n    public void testRecoverPublicKeyFromSignedUTF8StringSadPathBadDER() throws Exception {\n        byte[] signature = baseEncodedStringToByteArray(\"305c022100d55f884dd948b035a78c088af461f65dac80b35940891e914bbd9ba185778771022067eef94e9ae8217b24927a4d4016e9f6d2ac27e077ad11d02f2496d83ecdc9b502011c020601573f8f27af02086662fe5c7db4b76b\", 16);\n        String message = \"Moloch!\";\n        PublicKey.recoverPublicKeyFromSignedUTF8String(secp256k1, message, signature);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc2/UnsupportedBaseException.java",
    "content": "package test.jce.ecc2;\n\n/**\n * An exception for reporting unsupported bases encountered when\n * marshalling/unmarshalling byte arrays from encoded strings.\n */\npublic class UnsupportedBaseException extends Exception {\n    private static final long serialVersionUID = -8963563995880472365L;\n\n    public UnsupportedBaseException(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/ecc2/Utils.java",
    "content": "package test.jce.ecc2;\nimport org.bouncycastle.crypto.digests.GeneralDigest;\n\nimport java.io.UnsupportedEncodingException;\nimport java.nio.ByteBuffer;\n\npublic class Utils {\n    /**\n     * Construct a fresh GeneralDigest from an existing one\n     *\n     * @param hashDigest A general hash digest to make a new instance of\n     * @return A new GeneralDigest with the same class as the input\n     * @throws SecurityException Thrown if a fresh hash digest cannot be constructed from the digest handed as an argument\n     */\n    public static GeneralDigest freshDigestFromDigest(GeneralDigest hashDigest) throws SecurityException {\n        GeneralDigest freshHashDigest;\n        try {\n            freshHashDigest = hashDigest.getClass().getConstructor().newInstance();\n        } catch (Throwable e) {\n            throw new SecurityException(e);\n        }\n        return freshHashDigest;\n    }\n\n    /**\n     * Convert a UTF8 encoded string as bytes\n     *\n     * @param string The byte to convert\n     * @return An array of bytes that correspond to the UTF8 encoded string\n     * @throws SecurityException Thrown if the string cannot be validly converted to a UTF-8 string\n     */\n    public static byte[] stringToUTF8Bytes(String string) throws SecurityException {\n        try {\n            return string.getBytes(\"UTF-8\");\n        } catch (UnsupportedEncodingException e) {\n            throw new SecurityException(e);\n        }\n    }\n\n    /**\n     * Convert a long to an array of bytes\n     *\n     * @param x Long to convert\n     * @return An array of bytes corresponding to the converted long\n     */\n    public static byte[] longToBytes(long x) {\n        ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);\n        buffer.putLong(x);\n        return buffer.array();\n    }\n\n    /**\n     * Convert an array of bytes to a long\n     *\n     * @param bytes Bytes of a long\n     * @return The long represented by the bytes\n     */\n    public static long bytesToLong(byte[] bytes) {\n        ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);\n        buffer.put(bytes);\n        buffer.flip();\n        return buffer.getLong();\n    }\n\n    public static int countLeadingZeroBytes(byte[] bytes) {\n        int i = -1;\n        //noinspection StatementWithEmptyBody\n        while (++i < bytes.length && bytes[i] == 0) {\n        }\n        return i;\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/rsa/RSAKeyTest.java",
    "content": "package test.jce.rsa;\n\nimport cn.ponfee.commons.jce.security.RSACryptor;\nimport cn.ponfee.commons.jce.security.RSACryptor.RSAKeyPair;\nimport cn.ponfee.commons.jce.security.RSAPrivateKeys;\nimport cn.ponfee.commons.jce.security.RSAPublicKeys;\n\npublic class RSAKeyTest {\n\n    public static void main(String[] args) {\n        RSAKeyPair keyPair = RSACryptor.generateKeyPair(2048);\n\n        System.out.println(\"pkcs1 pub: \" + keyPair.toPkcs1PublicKey());\n        System.out.println(\"pkcs8 pub: \" + keyPair.toPkcs8PublicKey());\n        System.out.println(\"\\n\");\n        System.out.println(\"pkcs1 pri: \" + keyPair.toPkcs1PrivateKey());\n        System.out.println(\"pkcs8 pri: \" + keyPair.toPkcs8PrivateKey());\n        System.out.println(\"\\n\");\n        System.out.println(\"pkcs1 pub: \" + RSAPublicKeys.fromPkcs1(keyPair.toPkcs1PublicKey()));\n        System.out.println(\"pkcs8 pub: \" + RSAPublicKeys.fromPkcs8(keyPair.toPkcs8PublicKey()));\n        System.out.println(\"\\n\");\n        System.out.println(\"pkcs1 pri: \" + RSAPrivateKeys.fromPkcs1(keyPair.toPkcs1PrivateKey()));\n        System.out.println(\"pkcs8 pri: \" + RSAPrivateKeys.fromPkcs8(keyPair.toPkcs8PrivateKey()));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/rsa/RSASignerTest.java",
    "content": "package test.jce.rsa;\n\nimport static org.junit.Assert.assertTrue;\n\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\n\nimport org.junit.Test;\n\nimport com.google.common.base.Stopwatch;\n\nimport cn.ponfee.commons.jce.implementation.rsa.RSAKey;\nimport cn.ponfee.commons.jce.implementation.rsa.RSASigner;\nimport cn.ponfee.commons.jce.security.RSACryptor;\nimport cn.ponfee.commons.jce.security.RSAPrivateKeys;\nimport cn.ponfee.commons.jce.security.RSAPublicKeys;\nimport cn.ponfee.commons.util.IdcardResolver;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class RSASignerTest {\n\n    private static byte[] origin = MavenProjects.getMainJavaFileAsBytes(IdcardResolver.class);\n\n    @Test\n    public void testRSASign() {\n        RSAKey dk = new RSAKey(1024);\n        RSAKey ek = dk.getPublic();\n        RSAPublicKey pub = RSAPublicKeys.toRSAPublicKey(dk.n, dk.e);\n        RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d);\n\n        // ＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝验证签名\n        Stopwatch watch = Stopwatch.createStarted();\n        byte[] signature = new RSASigner(dk).signSha1(origin); // 签名\n\n        assertTrue(new RSASigner(ek).verifySha1(origin, signature)); // 验证\n        assertTrue(RSACryptor.verifySha1(origin, pub, signature)); // 验证\n\n        // ＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝验证签名\n        signature = RSACryptor.signSha1(origin, pri); // 签名\n        assertTrue(RSACryptor.verifySha1(origin, pub, signature)); // 验证\n        assertTrue(new RSASigner(ek).verifySha1(origin, signature)); // 验证\n\n        System.out.println(watch.stop());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/rsa/RSAryptorTest.java",
    "content": "package test.jce.rsa;\n\nimport java.io.ByteArrayInputStream;\nimport java.security.interfaces.RSAPrivateKey;\nimport java.security.interfaces.RSAPublicKey;\nimport java.util.Arrays;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.apache.commons.io.output.ByteArrayOutputStream;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.Test;\n\nimport com.google.common.base.Stopwatch;\n\nimport cn.ponfee.commons.jce.implementation.Key;\nimport cn.ponfee.commons.jce.implementation.rsa.AbstractRSACryptor;\nimport cn.ponfee.commons.jce.implementation.rsa.RSAHashCryptor;\nimport cn.ponfee.commons.jce.implementation.rsa.RSAKey;\nimport cn.ponfee.commons.jce.implementation.rsa.RSANoPaddingCryptor;\nimport cn.ponfee.commons.jce.implementation.rsa.RSAPKCS1PaddingCryptor;\nimport cn.ponfee.commons.jce.security.RSACryptor;\nimport cn.ponfee.commons.jce.security.RSAPrivateKeys;\nimport cn.ponfee.commons.jce.security.RSAPublicKeys;\nimport cn.ponfee.commons.util.IdcardResolver;\nimport cn.ponfee.commons.util.MavenProjects;\nimport cn.ponfee.commons.util.SecureRandoms;\n\npublic class RSAryptorTest {\n\n    private static byte[] origin = MavenProjects.getMainJavaFileAsBytes(IdcardResolver.class);\n    private static boolean isPrint = false;\n\n    private static void print(String s) {\n        if (isPrint) {\n            System.out.println(s);\n        }\n    }\n\n    @Test\n    public void testRSANoPadding() {\n        System.out.println(\"\\n\\ntestRSANoPadding======================================\");\n        RSAKey dk = new RSAKey(1024);\n        Key ek = dk.getPublic();\n        RSANoPaddingCryptor cs = new RSANoPaddingCryptor();\n\n        Stopwatch watch = Stopwatch.createStarted();\n        byte[] encrypted = cs.encrypt(origin, ek);\n        byte[] decrypted = cs.decrypt(encrypted, dk);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSA1 Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n\n        watch.reset().start();\n        /*RSAPublicKey pub = RSAPublicKeys.toRSAPublicKey(dk.n, dk.e);\n        encrypted = RSACryptor.encrypt(origin, pub);*/\n        RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d);\n        decrypted = RSACryptor.decryptNoPadding(encrypted, pri);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSA2 Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n    }\n\n    @Test\n    public void testRSAPKCS1Padding() {\n        System.out.println(\"\\n\\ntestRSAPKCS1Padding======================================\");\n        RSAKey dk = new RSAKey(1024);\n        Key ek = dk.getPublic();\n        AbstractRSACryptor cs = new RSAPKCS1PaddingCryptor();\n        RSAPublicKey pub = RSAPublicKeys.toRSAPublicKey(dk.n, dk.e);\n        RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d);\n\n        // ＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝公钥加密，私钥解密\n        Stopwatch watch = Stopwatch.createStarted();\n        byte[] encrypted = cs.encrypt(origin, ek);\n        byte[] decrypted = cs.decrypt(encrypted, dk);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSA1 Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n\n        watch.reset().start();\n        decrypted = RSACryptor.decrypt(encrypted, pri);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSA2 Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n\n        // ＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝私钥加密，公钥解密\n        watch = Stopwatch.createStarted();\n        encrypted = cs.encrypt(origin, dk);\n        decrypted = cs.decrypt(encrypted, ek);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSA1 Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n\n        watch.reset().start();\n        decrypted = RSACryptor.decrypt(encrypted, pub);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSA2 Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n\n        // =======================================加密－解密\n        encrypted = RSACryptor.encrypt(origin, pub);\n        decrypted = cs.decrypt(encrypted, dk);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSA1 Decrypted text is: \\n\" + new String(decrypted));\n        }\n\n        // =======================================加密－解密\n        encrypted = RSACryptor.encrypt(origin, pri);\n        decrypted = cs.decrypt(encrypted, ek);\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSA1 Decrypted text is: \\n\" + new String(decrypted));\n        }\n    }\n\n    @Test\n    public void testRSAHash() {\n        System.out.println(\"\\n\\ntestRSAHash======================================\");\n        for (int i = 0; i < 10; i++) {\n            byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(99999) + 1);\n\n            RSAKey dk = new RSAKey(1024);\n            Key ek = dk.getPublic();\n            RSAHashCryptor cs = new RSAHashCryptor();\n\n            byte[] encrypted = cs.encrypt(data, ek);\n            byte[] decrypted = cs.decrypt(encrypted, dk);\n            if (!Arrays.equals(data, decrypted)) {\n                System.err.println(\"FAIL!\");\n            } else {\n                print(\"\\n\\n=====RSAHashCryptor Decrypted text is: \\n\" + new String(decrypted));\n            }\n        }\n    }\n\n    @Test\n    public void testRSARandom() {\n        System.out.println(\"\\n\\ntestRSARandom======================================\");\n        RSAKey dk = new RSAKey(1024);\n        Key ek = dk.getPublic();\n        RSANoPaddingCryptor cs = new RSANoPaddingCryptor();\n        RSAPKCS1PaddingCryptor cs2 = new RSAPKCS1PaddingCryptor();\n        RSAPublicKey pub = RSAPublicKeys.toRSAPublicKey(dk.n, dk.e);\n        RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d);\n        for (int i = 0; i < 1000; i++) {\n\n            /*int length = ThreadLocalRandom.current().nextInt(65537) + 1;\n            int offset = ThreadLocalRandom.current().nextInt(origin.length - length);\n            System.out.println(length + \" -> \" + offset);\n            byte[] data = Arrays.copyOfRange(origin, offset, offset + length);*/\n\n            //byte[] data = new byte[] { 0, 1, 1, 0, 0 }; // occur wrong\n\n            byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(255) + 1);\n\n            // 1\n            byte[] encrypted1 = cs.encrypt(data, ek);\n            byte[] decrypted1 = cs.decrypt(encrypted1, dk);                   // XXX DATA!=decrypted1    1%\n\n            // 2\n            byte[] encrypted2 = encrypted1;\n            byte[] decrypted2 = RSACryptor.decryptNoPadding(encrypted2, pri); // XXX DATA!=decrypted1    1%\n\n            // 3\n            byte[] encrypted3 = RSACryptor.encryptNoPadding(data, pub);\n            byte[] decrypted3 = RSACryptor.decryptNoPadding(encrypted3, pri); // XXX DATA!=decrypted1    5%\n\n            // 4\n            byte[] encrypted4 = RSACryptor.encrypt(data, pub);\n            byte[] decrypted4 = RSACryptor.decrypt(encrypted4, pri);\n\n            // 5\n            byte[] encrypted5 = cs2.encrypt(data, ek);\n            byte[] decrypted5 = cs2.decrypt(encrypted5, dk);\n            // -------------------------------------------------------\n            if (!Arrays.equals(data, decrypted1)) {\n                System.err.println(\"[\" + StringUtils.leftPad(i + \"\", 4, \"0\") + \"]decrypt1 FAIL!: \" + Hex.encodeHexString(data) + \" -> \"\n                    + Hex.encodeHexString(decrypted1));\n            }\n\n            if (!Arrays.equals(data, decrypted2)) {\n                System.err.println(\"[\" + StringUtils.leftPad(i + \"\", 4, \"0\") + \"]decrypt2 FAIL!: \" + Hex.encodeHexString(data) + \" -> \"\n                    + Hex.encodeHexString(decrypted2));\n            }\n\n            if (!Arrays.equals(data, decrypted3)) {\n                System.err.println(\"[\" + StringUtils.leftPad(i + \"\", 4, \"0\") + \"]decrypt3 FAIL!: \" + Hex.encodeHexString(data) + \" -> \"\n                    + Hex.encodeHexString(decrypted3));\n            }\n\n            if (!Arrays.equals(data, decrypted4)) {\n                System.err.println(\"[\" + StringUtils.leftPad(i + \"\", 4, \"0\") + \"]decrypt4 FAIL!: \" + Hex.encodeHexString(data) + \" -> \"\n                    + Hex.encodeHexString(decrypted4));\n            }\n\n            if (!Arrays.equals(data, decrypted5)) {\n                System.err.println(\"[\" + StringUtils.leftPad(i + \"\", 4, \"0\") + \"]decrypt5 FAIL!: \" + Hex.encodeHexString(data) + \" -> \"\n                    + Hex.encodeHexString(decrypted5));\n            }\n        }\n    }\n\n    @Test\n    public void testRSAHashInverseKey() {\n        System.out.println(\"\\n\\ntestRSAHashInverseKey======================================\");\n        RSAKey dk = new RSAKey(1024);\n        Key ek = dk.getPublic();\n        RSAHashCryptor cs = new RSAHashCryptor();\n        for (int i = 0; i < 100; i++) {\n            byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(255) + 1);\n            byte[] encrypted1 = cs.encrypt(data, dk); // 私钥加密\n            byte[] decrypted1 = cs.decrypt(encrypted1, ek); // 公钥解密\n            if (!Arrays.equals(data, decrypted1)) {\n                System.err.println(\"[\" + StringUtils.leftPad(i + \"\", 4, \"0\") + \"]decrypt1 FAIL!: \" + Hex.encodeHexString(data) + \" -> \"\n                    + Hex.encodeHexString(decrypted1));\n            }\n        }\n    }\n\n    @Test\n    public void testRSANoPaddingInverseKey() {\n        System.out.println(\"\\n\\ntestRSANoPaddingInverseKey======================================\");\n        RSAKey dk = new RSAKey(1024);\n        Key ek = dk.getPublic();\n        RSANoPaddingCryptor cs = new RSANoPaddingCryptor();\n        for (int i = 0; i < 1000; i++) {\n            byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(255) + 1);\n            byte[] encrypted1 = cs.encrypt(data, dk); // 私钥加密\n            byte[] decrypted1 = cs.decrypt(encrypted1, ek); // 公钥解密\n            if (!Arrays.equals(data, decrypted1)) {\n                System.err.println(\"[\" + StringUtils.leftPad(i + \"\", 4, \"0\") + \"]decrypt1 FAIL!: \" + Hex.encodeHexString(data) + \" -> \"\n                    + Hex.encodeHexString(decrypted1));\n            }\n        }\n    }\n\n    @Test\n    public void testRSAPKCS1InverseKey() {\n        System.out.println(\"\\n\\ntestRSAPKCS1InverseKey======================================\");\n        RSAKey dk = new RSAKey(1024);\n        Key ek = dk.getPublic();\n        RSAPKCS1PaddingCryptor cs = new RSAPKCS1PaddingCryptor();\n        for (int i = 0; i < 100; i++) {\n            byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(255) + 1);\n            byte[] encrypted1 = cs.encrypt(data, dk); // 私钥加密\n            byte[] decrypted1 = cs.decrypt(encrypted1, ek); // 公钥解密\n            if (!Arrays.equals(data, decrypted1)) {\n                System.err.println(\"[\" + StringUtils.leftPad(i + \"\", 4, \"0\") + \"]decrypt1 FAIL!: \" + Hex.encodeHexString(data) + \" -> \"\n                    + Hex.encodeHexString(decrypted1));\n            }\n        }\n    }\n    @Test\n    public void testRSANoPaddingStream() {\n        System.out.println(\"\\n\\ntestRSANoPaddingStream======================================\");\n        RSAKey dk = new RSAKey(2048);\n        Key ek = dk.getPublic();\n        RSANoPaddingCryptor cs = new RSANoPaddingCryptor();\n\n        ByteArrayInputStream input = new ByteArrayInputStream(origin);\n        ByteArrayOutputStream output = new ByteArrayOutputStream();\n        Stopwatch watch = Stopwatch.createStarted();\n        cs.encrypt(input, ek, output);\n        byte[] encrypted = output.toByteArray();\n\n        input = new ByteArrayInputStream(encrypted);\n        output = new ByteArrayOutputStream();\n        cs.decrypt(input, dk, output);\n        byte[] decrypted = output.toByteArray();\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSAStream Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n\n        watch.reset().start();\n        input = new ByteArrayInputStream(encrypted);\n        output = new ByteArrayOutputStream();\n        RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d);\n        RSACryptor.decryptNoPadding(input, pri, output);\n        decrypted = output.toByteArray();\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSAStream Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n    }\n\n    @Test\n    public void testRSAPKCS1PaddingStream() {\n        System.out.println(\"\\n\\ntestRSAPKCS1PaddingStream======================================\");\n        RSAKey dk = new RSAKey(1024);\n        Key ek = dk.getPublic();\n        AbstractRSACryptor cs = new RSAPKCS1PaddingCryptor();\n\n        ByteArrayInputStream input = new ByteArrayInputStream(origin);\n        ByteArrayOutputStream output = new ByteArrayOutputStream();\n        Stopwatch watch = Stopwatch.createStarted();\n        cs.encrypt(input, ek, output);\n        byte[] encrypted = output.toByteArray();\n\n        input = new ByteArrayInputStream(encrypted);\n        output = new ByteArrayOutputStream();\n        cs.decrypt(input, dk, output);\n        byte[] decrypted = output.toByteArray();\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSAStream Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n\n        watch.reset().start();\n        input = new ByteArrayInputStream(encrypted);\n        output = new ByteArrayOutputStream();\n        RSAPrivateKey pri = RSAPrivateKeys.toRSAPrivateKey(dk.n, dk.d);\n        RSACryptor.decrypt(input, pri, output);\n        decrypted = output.toByteArray();\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSAStream Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n    }\n\n    @Test\n    public void testRSAHashStream() {\n        System.out.println(\"\\n\\ntestRSAHashStream======================================\");\n        RSAKey dk = new RSAKey(2048);\n        Key ek = dk.getPublic();\n        RSAHashCryptor cs = new RSAHashCryptor();\n\n        ByteArrayInputStream input = new ByteArrayInputStream(origin);\n        ByteArrayOutputStream output = new ByteArrayOutputStream();\n        Stopwatch watch = Stopwatch.createStarted();\n        cs.encrypt(input, ek, output);\n        byte[] encrypted = output.toByteArray();\n        print(\"encrypted len: \" + encrypted.length + \",  origin len: \" + origin.length);\n\n        input = new ByteArrayInputStream(encrypted);\n        output = new ByteArrayOutputStream();\n        cs.decrypt(input, dk, output);\n        byte[] decrypted = output.toByteArray();\n        if (!Arrays.equals(origin, decrypted)) {\n            System.err.println(\"FAIL!\");\n        } else {\n            print(\"\\n\\n=====RSAStream Decrypted text is: \\n\" + new String(decrypted));\n        }\n        System.out.println(watch.stop());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/sha1/SHA1BrokenTest.java",
    "content": "package test.jce.sha1;\n\nimport java.io.File;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.jce.DigestAlgorithms;\nimport cn.ponfee.commons.jce.HmacAlgorithms;\nimport cn.ponfee.commons.jce.Providers;\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport cn.ponfee.commons.jce.digest.HmacUtils;\nimport cn.ponfee.commons.jce.implementation.digest.SHA1Digest;\nimport cn.ponfee.commons.jce.sm.SM3Digest;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class SHA1BrokenTest {\n\n    public @Test void test1() {\n        byte[] pdf1 = Files.toByteArray(new File(MavenProjects.getTestJavaPath(\"test.jce.sha1\", \"shattered-1.pdf\")));\n        byte[] pdf2 = Files.toByteArray(new File(MavenProjects.getTestJavaPath(\"test.jce.sha1\", \"shattered-2.pdf\")));\n        System.out.println(Hex.encodeHexString(SHA1Digest.getInstance().doFinal(pdf1)));\n        System.out.println(Hex.encodeHexString(SHA1Digest.getInstance().doFinal(pdf2)));\n        System.out.println(Hex.encodeHexString(DigestUtils.sha1(pdf1)));\n        System.out.println(Hex.encodeHexString(DigestUtils.sha1(pdf2)));\n\n        System.out.println(Hex.encodeHexString(DigestUtils.sha256(pdf1)));\n        System.out.println(Hex.encodeHexString(DigestUtils.sha256(pdf2)));\n    }\n\n    public @Test void test2() {\n        byte[] pdf2 = Files.toByteArray(new File(MavenProjects.getTestJavaPath(\"test.jce.sha1\", \"shattered-2.pdf\")));\n\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SHA3_256, Providers.BC, pdf2)));\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.KECCAK256, Providers.BC, pdf2)));\n        System.out.println();\n\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SHA3_512, Providers.BC, pdf2)));\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.KECCAK512, Providers.BC, pdf2)));\n        System.out.println(Hex.encodeHexString(DigestUtils.digest(DigestAlgorithms.SM3, Providers.BC, pdf2)));\n        System.out.println(Hex.encodeHexString(SM3Digest.getInstance().doFinal(pdf2)));\n        System.out.println();\n\n        System.out.println(Hex.encodeHexString(HmacUtils.crypt(\"1234\".getBytes(), pdf2, HmacAlgorithms.HmacSHA3_256)));\n        System.out.println(Hex.encodeHexString(HmacUtils.crypt(\"1234\".getBytes(), pdf2, HmacAlgorithms.HmacKECCAK256)));\n        System.out.println();\n\n        System.out.println(Hex.encodeHexString(HmacUtils.crypt(\"1234\".getBytes(), pdf2, HmacAlgorithms.HmacSHA3_512)));\n        System.out.println(Hex.encodeHexString(HmacUtils.crypt(\"1234\".getBytes(), pdf2, HmacAlgorithms.HmacKECCAK512)));\n        System.out.println();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/jce/sm/SM2KeyExchangeTest.java",
    "content": "package test.jce.sm;\n\nimport java.util.Arrays;\nimport java.util.Map;\n\nimport org.apache.commons.codec.binary.Hex;\n\nimport cn.ponfee.commons.jce.ECParameters;\nimport cn.ponfee.commons.jce.sm.SM2;\nimport cn.ponfee.commons.jce.sm.SM2KeyExchanger;\nimport cn.ponfee.commons.jce.sm.SM2KeyExchanger.TransportEntity;\n\npublic class SM2KeyExchangeTest {\n\n    public static void main(String[] args) {\n        ECParameters ecParameter = ECParameters.SM2_BEST;\n\n        System.out.println(\"=============================密钥协商============================\");\n        Map<String, byte[]> keyMap = SM2.generateKeyPair(ecParameter);\n        byte[] id1 = \"AAAAAAAAAAAAA\".getBytes();\n        SM2KeyExchanger aKeyExchange = new SM2KeyExchanger(id1, SM2.getPublicKey(ecParameter, SM2.getPublicKey(keyMap)), SM2.getPrivateKey(SM2.getPrivateKey(keyMap)));\n\n        Map<String, byte[]> keyMap2 = SM2.generateKeyPair(ecParameter);\n        byte[] id2 = \"BBBBBBBBBBBBB\".getBytes();\n        SM2KeyExchanger bKeyExchange = new SM2KeyExchanger(id2, SM2.getPublicKey(ecParameter, SM2.getPublicKey(keyMap2)), SM2.getPrivateKey(SM2.getPrivateKey(keyMap2)));\n        TransportEntity entity1 = aKeyExchange.step1PartA();\n        TransportEntity entity2 = bKeyExchange.step2PartB(entity1);\n\n        TransportEntity entity3 = aKeyExchange.step3PartA(entity2);\n        System.out.println(Hex.encodeHexString(bKeyExchange.getKey()));\n\n        if (!bKeyExchange.step4PartB(entity3)\n            || !Arrays.equals(aKeyExchange.getKey(), bKeyExchange.getKey())) {\n            System.err.println(\"FAIL!\");\n        }\n        System.out.println(Hex.encodeHexString(aKeyExchange.getKey())); // 16 byte\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/sm/SM2Test.java",
    "content": "package test.jce.sm;\n\nimport java.util.Base64;\nimport java.util.Map;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.jce.ECParameters;\nimport cn.ponfee.commons.jce.sm.SM2;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class SM2Test {\n    static ECParameters ecParameter = ECParameters.SM2_BEST;\n\n    @Test\n    public void test1() {\n        byte[] data = MavenProjects.getMainJavaFileAsString(SM2.class).substring(0, 100).getBytes();\n        Map<String, byte[]> keyMap = SM2.generateKeyPair(ecParameter);\n\n        System.out.println(Base64.getUrlEncoder().encodeToString(SM2.getPublicKey(keyMap)));\n        System.out.println(Base64.getUrlEncoder().encodeToString(SM2.getPrivateKey(keyMap)));\n\n\n        byte[] encrypted = SM2.encrypt(ecParameter, SM2.getPublicKey(keyMap), data);\n        System.out.println(Base64.getUrlEncoder().encodeToString(encrypted));\n\n        byte[] decrypted = SM2.decrypt(ecParameter, SM2.getPrivateKey(keyMap), encrypted);\n        System.out.println(new String(decrypted));\n    }\n\n    @Test\n    public void test2() throws Exception {\n        byte[] privateKey = Hex.decodeHex(\"19F4279987CACA6780592C2B330E932B7C6C27919ED56D63785E398A7C3B568F\");\n        byte[] encrypted = Hex.decodeHex(\"04AD22DF19CC60E231B74E4F510537C9D71EB57D3734F7EC44188E5655374BC742469A3BF8D247769AF712A06A4D2EAC24470F9851AB6A2F106600E3B5D8DADD2DCD81D6C781A26C7DCBACD9F947A461FF555537A75E9A19B92EE6E447373C9B776CF0236BCA769A7515795A32AF00F94E9B5EBCDE9B8774F19129815AF04C7D03122982448AFB586F8895C2A695FFC034B1B77E350E925E0BC04A683759C763AC396FC3EFDD9EF4A09E53EFFCCA80C29B42B11AB82A7CEACBBF2E748E028035E4D4E71693\");\n        byte[] decrypted = SM2.decrypt(ecParameter, privateKey, encrypted);\n        System.out.println(new String(decrypted));\n    }\n\n    @Test\n    public void test3() {\n        //ECParameters ecParameter = ECParameters.secp256r1;\n        //ECParameters ecParameter = ECParameters.EC_PARAMETERS.get(\"secp256r1\");\n        //ECParameters ecParameter = ECParameters.EC_PARAMETERS.get(\"secp256k1\");\n        ECParameters ecParameter = ECParameters.SM2_BEST;\n\n        //byte[] publicKey1 = Base64.getDecoder().decode(\"BDky9CNygRTBGh7eGdzdbxP5eGRozk4wQfFmREncwEKnYHhNy1OoBvh0wY/RMH/3ikfYejClHxlI1T+jRa0m2wU=\");\n        byte[] privateKey1 = Base64.getDecoder().decode(\"AJaz6VNm1Wl9ba1YCEkYi36+m+8DDL4LDuLM172tg5Ao\");\n        byte[] encrypted1 = Base64.getDecoder().decode(\"BDrcVKWOJ2J/x0YRmX99ksvwZJ5DgbgxPpwDNf3u94FH65JHuxG6U6Xp7ST7G7dsTlg6QFiSIANKyb42DvmpLpby1CU5kw0q/C4t1eH08VTAtpZrg2M6+Qz4pdOpp0iGpUp1w5ympuezCxgsEUfhDYihG5MjerLz+8Ss8qTRz3/ZpNvRJaCDk9k=\");\n        System.out.println(new String(SM2.decrypt(privateKey1, encrypted1)));\n\n        for (int i = 0; i < 5; i++) {\n            byte[] data = MavenProjects.getMainJavaFileAsString(SM2.class).getBytes();\n            Map<String, byte[]> keyMap = SM2.generateKeyPair(ecParameter);\n\n            System.out.println(\"\\n=============================加密/解密============================\");\n            byte[] encrypted = SM2.encrypt(ecParameter, keyMap.get(SM2.PUBLIC_KEY), data);\n            System.out.println(Base64.getEncoder().encodeToString(SM2.getPublicKey(keyMap)));\n            System.out.println(Base64.getEncoder().encodeToString(SM2.getPrivateKey(keyMap)));\n            System.out.println(Base64.getEncoder().encodeToString(encrypted));\n            byte[] decrypted = SM2.decrypt(ecParameter, keyMap.get(SM2.PRIVATE_KEY), encrypted);\n            System.out.println(new String(decrypted));\n\n            System.out.println(\"\\n=============================签名/验签============================\");\n            byte[] signed = SM2.sign(ecParameter, data, \"IDA\".getBytes(), SM2.getPublicKey(keyMap), SM2.getPrivateKey(keyMap));\n            System.out.println(Base64.getUrlEncoder().withoutPadding().encodeToString(signed));\n            System.out.println(SM2.verify(ecParameter, data, \"IDA\".getBytes(), signed, SM2.getPublicKey(keyMap)));\n        }\n\n        byte[] data = MavenProjects.getMainJavaFileAsString(SM2.class).substring(0, 100).getBytes();\n        Map<String, byte[]> keyMap = SM2.generateKeyPair(ecParameter);\n        System.out.println(\"\\ncheckPublicKey: \"+SM2.checkPublicKey(ecParameter, SM2.getPublicKey(keyMap)));\n        for (int i = 0; i < 5; i++) {\n            System.out.println(\"\\n=============================加密/解密============================\");\n            byte[] encrypted = SM2.encrypt(ecParameter, keyMap.get(SM2.PUBLIC_KEY), data);\n            byte[] decrypted = SM2.decrypt(ecParameter, keyMap.get(SM2.PRIVATE_KEY), encrypted);\n            System.out.println(new String(decrypted));\n\n            System.out.println(\"\\n=============================签名/验签============================\");\n            byte[] signed = SM2.sign(ecParameter, data, \"IDA\".getBytes(), SM2.getPublicKey(keyMap), SM2.getPrivateKey(keyMap));\n            System.out.println(Base64.getUrlEncoder().withoutPadding().encodeToString(signed));\n            System.out.println(SM2.verify(ecParameter, data, \"IDA\".getBytes(), signed, SM2.getPublicKey(keyMap)));\n        }\n    }\n\n    @Test\n    public void test4() {\n        byte[] publicKey1 = Base64.getDecoder().decode(\"BDky9CNygRTBGh7eGdzdbxP5eGRozk4wQfFmREncwEKnYHhNy1OoBvh0wY/RMH/3ikfYejClHxlI1T+jRa0m2wU=\");\n        byte[] privateKey1 = Base64.getDecoder().decode(\"AJaz6VNm1Wl9ba1YCEkYi36+m+8DDL4LDuLM172tg5Ao\");\n        byte[] encrypted1 = Base64.getDecoder().decode(\"BBdOqZ+7WMaYzyyEZui0b5tdfJqquXH0pMxyoSv04BzYCqKIaeXPcc/vIek8tcK4kCkp022OCZ2TW2bLgVgGvFxeMOzGUgliTQP521HmqbQaawS4FFHxGa1vy+lk9UrkOIaTiEqjIRFSB2SW4cOi3u1mwvqK8EuaYFb143K539HIuqu84Bo3RD2zIpssO+yZzduG4KDcn4iqOJ+NJ1RB5wlI1xEyyiKit6c0mVBfSnXbUkuAyml50dM=\");\n        System.out.println(new String(SM2.decrypt(privateKey1, encrypted1)));\n\n\n        byte[]   encrypted = SM2.encrypt(publicKey1, MavenProjects.getTestJavaFileAsBytes(SM2Test.class));\n        System.out.println(Base64.getEncoder().encodeToString(encrypted));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/sm/SM3DigestTest.java",
    "content": "package test.jce.sm;\n\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport org.apache.commons.codec.binary.Hex;\nimport org.junit.Assert;\n\nimport cn.ponfee.commons.jce.sm.SM3Digest;\nimport cn.ponfee.commons.util.SecureRandoms;\n\npublic class SM3DigestTest {\n\n    public static void main(String[] args) {\n        String actual = Hex.encodeHexString(SM3Digest.getInstance().doFinal(\"0123456789\".getBytes()));\n        if (!\"09093b72553f5d9d622d6c62f5ffd916ee959679b1bd4d169c3e12aa8328e743\".equals(actual)) {\n            System.err.println(\"sm3 digest error!\");\n        } else {\n            System.out.println(\"SUCCESS!\");\n        }\n\n        byte[] data = \"0123456789\".getBytes();\n\n        byte[] hash = SM3Digest.getInstance().doFinal(data);\n        System.out.println(Hex.encodeHexString(hash));\n\n        SM3Digest sm3 = SM3Digest.getInstance();\n\n        hash = sm3.doFinal(data);\n        System.out.println(Hex.encodeHexString(hash));\n\n        hash = sm3.doFinal(data);\n        System.out.println(Hex.encodeHexString(hash));\n\n        hash = sm3.doFinal(data);\n        System.out.println(Hex.encodeHexString(hash));\n\n        SM3Digest sm3_1 = SM3Digest.getInstance();\n        org.bouncycastle.crypto.digests.SM3Digest sm3_2 = new org.bouncycastle.crypto.digests.SM3Digest();\n        for (int i = 0; i < 100; i++) {\n            byte[] data1 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1);\n            byte[] data2 = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(65537) + 1);\n            sm3_1.update(data1);\n            sm3_2.update(data1, 0, data1.length);\n            sm3_2.update(data2, 0, data2.length);\n            byte[] dig = new byte[sm3_2.getDigestSize()];\n            sm3_2.doFinal(dig, 0);\n            Assert.assertArrayEquals(sm3_1.doFinal(data2), dig);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/jce/sm/SM4Test.java",
    "content": "package test.jce.sm;\n\nimport java.util.Base64;\n\nimport cn.ponfee.commons.jce.sm.SM4;\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class SM4Test {\n\n    public static void main(String[] args) {\n        //byte[] data = Files.toString(MavenProjects.getMainJavaFile(SM4.class)).replaceAll(\"\\r|\\n\", \"\").getBytes();\n        byte[] data = MavenProjects.getMainJavaFileAsString(SM4.class).substring(0, 997).getBytes();\n        byte[] key = \"1234567785465466\".getBytes();\n        byte[] iv = \"1a345677b546d4de\".getBytes();\n        System.out.println(new String(SM4.decrypt( key, iv, Base64.getDecoder().decode(\"+31e6VuKcGDl4qG5rxfiYy35LFwmbS4VY4AF/t7lmeu2wjEUneKEVWTEPBnaSo3+lRKsqfBVp4khbD830Qiy8R66AdHm1/ato7OzepfCxAs=\")))+\"|\");\n        System.out.println(new String(SM4.decrypt( key, iv, Base64.getDecoder().decode(\"A5/GbdIvh2v44y8izoqzhu6ne96tkt/xI0ZBkFaPyX+rD6G/+MuyARV4lawjH/Cy6N+vVRYTwb6T5l4dMTJ7mEN1X0XiYxlrbX3PDGd96OM=\")))+\"|\");\n\n        byte[] encrypted = SM4.encrypt(key, data);\n        System.out.println(data.length + \"-->\" + encrypted.length + \"\\t|\" + new String(SM4.decrypt(key, encrypted)) + \"|\");\n\n        encrypted = SM4.encrypt(key, iv, data);\n        System.out.println(Base64.getEncoder().encodeToString(encrypted));\n        System.out.println(data.length + \"-->\" + encrypted.length + \"\\t|\" + new String(SM4.decrypt(key, iv, encrypted)) + \"|\");\n\n        encrypted = SM4.encrypt(false, key, data);\n        System.out.println(data.length + \"-->\" + encrypted.length + \"\\t|\" + new String(SM4.decrypt(false, key, encrypted)) + \"|\");\n\n        encrypted = SM4.encrypt(false, key, iv, data);\n        System.out.println(data.length + \"-->\" + encrypted.length + \"\\t|\" + new String(SM4.decrypt(false, key, iv, encrypted)) + \"|\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/log4j/TestLog4j.java",
    "content": "package test.log4j;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class TestLog4j {\n\n    private static Logger logger = LoggerFactory.getLogger(TestLog4j.class);\n    public static void main(String[] args) {\n        logger.trace(\"abcd\");\n        logger.info(\"abcd\");\n        logger.info(MavenProjects.getTestJavaFileAsString(TestLog4j.class));\n        logger.info(\"abcd\");\n        logger.info(\"abcd\");\n        logger.warn(\"abcd\");\n        logger.error(\"abcd\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/model/PageInfo.java",
    "content": "package test.model;\n\nimport java.io.Serializable;\n\npublic class PageInfo implements Serializable {\n\n    private static final long serialVersionUID = 587754556498974978L;\n\n    // pagesize ，每一页显示多少\n    private int showCount = 3;\n    // 总页数\n    private int totalPage;\n    // 总记录数\n    private int totalResult;\n    // 当前页数\n    private int currentPage;\n    // 当前显示到的ID, 在mysql limit 中就是第一个参数.\n    private int currentResult;\n    private String sortField;\n    private String order;\n\n    public int getShowCount() {\n        return showCount;\n    }\n\n    public void setShowCount(int showCount) {\n        this.showCount = showCount;\n    }\n\n    public int getTotalPage() {\n        return totalPage;\n    }\n\n    public void setTotalPage(int totalPage) {\n        this.totalPage = totalPage;\n    }\n\n    public int getTotalResult() {\n        return totalResult;\n    }\n\n    public void setTotalResult(int totalResult) {\n        this.totalResult = totalResult;\n    }\n\n    public int getCurrentPage() {\n        return currentPage;\n    }\n\n    public void setCurrentPage(int currentPage) {\n        this.currentPage = currentPage;\n    }\n\n    public int getCurrentResult() {\n        return currentResult;\n    }\n\n    public void setCurrentResult(int currentResult) {\n        this.currentResult = currentResult;\n    }\n\n    public String getSortField() {\n        return sortField;\n    }\n\n    public void setSortField(String sortField) {\n        this.sortField = sortField;\n    }\n\n    public String getOrder() {\n        return order;\n    }\n\n    public void setOrder(String order) {\n        this.order = order;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/model/PagePlugin.java",
    "content": "package test.model;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\n\nimport javax.xml.bind.PropertyException;\n\nimport org.apache.ibatis.scripting.xmltags.ForEachSqlNode;\nimport org.apache.ibatis.executor.ErrorContext;\nimport org.apache.ibatis.executor.ExecutorException;\nimport org.apache.ibatis.executor.statement.BaseStatementHandler;\nimport org.apache.ibatis.executor.statement.RoutingStatementHandler;\nimport org.apache.ibatis.executor.statement.StatementHandler;\nimport org.apache.ibatis.mapping.BoundSql;\nimport org.apache.ibatis.mapping.MappedStatement;\nimport org.apache.ibatis.mapping.ParameterMapping;\nimport org.apache.ibatis.mapping.ParameterMode;\nimport org.apache.ibatis.plugin.Interceptor;\nimport org.apache.ibatis.plugin.Intercepts;\nimport org.apache.ibatis.plugin.Invocation;\nimport org.apache.ibatis.plugin.Plugin;\nimport org.apache.ibatis.plugin.Signature;\nimport org.apache.ibatis.reflection.MetaObject;\nimport org.apache.ibatis.reflection.property.PropertyTokenizer;\nimport org.apache.ibatis.session.Configuration;\nimport org.apache.ibatis.type.TypeHandler;\nimport org.apache.ibatis.type.TypeHandlerRegistry;\n\nimport cn.ponfee.commons.reflect.Fields;\n\n/**\n * <?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n * <!DOCTYPE configuration PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\"\n * \"http://mybatis.org/dtd/mybatis-3-config.dtd\">\n * <configuration>\n *     <typeAliases>\n *         <typeAlias alias=\"PageInfo\" type=\"com.jsoft.testmybatis.util.PageInfo\" />\n *     </typeAliases>\n *     <plugins>\n *         <plugin interceptor=\"com.jsoft.testmybatis.util.PagePlugin\">\n *             <property name=\"dialect\" value=\"mysql\" />\n *             <property name=\"pageSqlId\" value=\".*ListPage.*\" />\n *         </plugin>\n *     </plugins>\n * </configuration>\n * @author Ponfee\n */\n@Intercepts({ @Signature(type = StatementHandler.class, method = \"prepare\", args = { Connection.class, Integer.class }) })\npublic class PagePlugin implements Interceptor {\n\n    private static String dialect = \"\";\n    private static String pageSqlId = \"\";\n\n    @SuppressWarnings(\"unchecked\")\n    public Object intercept(Invocation ivk) throws Throwable {\n\n        if (ivk.getTarget() instanceof RoutingStatementHandler) {\n            RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk.getTarget();\n            BaseStatementHandler delegate = (BaseStatementHandler) Fields.get(statementHandler, \"delegate\");\n            MappedStatement mappedStatement = (MappedStatement) Fields.get(delegate, \"mappedStatement\");\n\n            if (mappedStatement.getId().matches(pageSqlId)) {\n                BoundSql boundSql = delegate.getBoundSql();\n                Object parameterObject = boundSql.getParameterObject();\n                if (parameterObject == null) {\n                    throw new NullPointerException(\"parameterObject error\");\n                } else {\n                    Connection connection = (Connection) ivk.getArgs()[0];\n                    String sql = boundSql.getSql();\n                    String countSql = \"select count(0) from (\" + sql + \") myCount\";\n                    PreparedStatement countStmt = connection.prepareStatement(countSql);\n                    BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);\n                    setParameters(countStmt, mappedStatement, countBS, parameterObject);\n                    ResultSet rs = countStmt.executeQuery();\n                    int count = 0;\n                    if (rs.next()) {\n                        count = rs.getInt(1);\n                    }\n                    rs.close();\n                    countStmt.close();\n\n                    PageInfo page = null;\n                    if (parameterObject instanceof PageInfo) {\n                        page = (PageInfo) parameterObject;\n                        page.setTotalResult(count);\n                    } else if (parameterObject instanceof Map) {\n                        Map<String, Object> map = (Map<String, Object>) parameterObject;\n                        page = (PageInfo) map.get(\"page\");\n                        if (page == null) page = new PageInfo();\n                        page.setTotalResult(count);\n                    } else {\n                        page = (PageInfo) Fields.get(parameterObject, \"page\");\n                        if (page == null) page = new PageInfo();\n                        page.setTotalResult(count);\n                        Fields.put(parameterObject, \"page\", page);\n                    }\n                    String pageSql = generatePageSql(sql, page);\n                    Fields.put(boundSql, \"sql\", pageSql);\n                }\n            }\n        }\n        return ivk.proceed();\n    }\n\n    @SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {\n        ErrorContext.instance().activity(\"setting parameters\").object(mappedStatement.getParameterMap().getId());\n        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();\n        if (parameterMappings != null) {\n            Configuration configuration = mappedStatement.getConfiguration();\n            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();\n            MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);\n            for (int i = 0; i < parameterMappings.size(); i++) {\n                ParameterMapping parameterMapping = parameterMappings.get(i);\n                if (parameterMapping.getMode() != ParameterMode.OUT) {\n                    Object value;\n                    String propertyName = parameterMapping.getProperty();\n                    PropertyTokenizer prop = new PropertyTokenizer(propertyName);\n                    if (parameterObject == null) {\n                        value = null;\n                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {\n                        value = parameterObject;\n                    } else if (boundSql.hasAdditionalParameter(propertyName)) {\n                        value = boundSql.getAdditionalParameter(propertyName);\n                    } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) {\n                        value = boundSql.getAdditionalParameter(prop.getName());\n                        if (value != null) {\n                            value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));\n                        }\n                    } else {\n                        value = metaObject == null ? null : metaObject.getValue(propertyName);\n                    }\n                    TypeHandler typeHandler = parameterMapping.getTypeHandler();\n                    if (typeHandler == null) {\n                        throw new ExecutorException(\"There was no TypeHandler found for parameter \" + propertyName + \" of statement \"\n                            + mappedStatement.getId());\n                    }\n                    typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());\n                }\n            }\n        }\n    }\n\n    private String generatePageSql(String sql, PageInfo page) {\n        if (page != null && (dialect != null || !dialect.equals(\"\"))) {\n            StringBuffer pageSql = new StringBuffer();\n            if (\"mysql\".equals(dialect)) {\n                pageSql.append(sql);\n                pageSql.append(\" limit \" + page.getCurrentResult() + \",\" + page.getShowCount());\n            } else if (\"oracle\".equals(dialect)) {\n                pageSql.append(\"select * from (select tmp_tb.*,ROWNUM row_id from (\");\n                pageSql.append(sql);\n                pageSql.append(\")  tmp_tb where ROWNUM<=\");\n                pageSql.append(page.getCurrentResult() + page.getShowCount());\n                pageSql.append(\") where row_id>\");\n                pageSql.append(page.getCurrentResult());\n            }\n            return pageSql.toString();\n        } else {\n            return sql;\n        }\n    }\n\n    public Object plugin(Object arg0) {\n        // TODO Auto-generated method stub\n        return Plugin.wrap(arg0, this);\n    }\n\n    public void setProperties(Properties p) {\n        dialect = p.getProperty(\"dialect\");\n        if (dialect == null || dialect.equals(\"\")) {\n            try {\n                throw new PropertyException(\"dialect property is not found!\");\n            } catch (PropertyException e) {\n                // TODO Auto-generated catch block\n                e.printStackTrace();\n            }\n        }\n        pageSqlId = p.getProperty(\"pageSqlId\");\n        if (dialect == null || dialect.equals(\"\")) {\n            try {\n                throw new PropertyException(\"pageSqlId property is not found!\");\n            } catch (PropertyException e) {\n                // TODO Auto-generated catch block\n                e.printStackTrace();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/pdf/ItextUtil.java",
    "content": "package test.pdf;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.GeneralSecurityException;\nimport java.security.PrivateKey;\nimport java.security.cert.Certificate;\n\nimport com.itextpdf.text.DocumentException;\nimport com.itextpdf.text.Image;\nimport com.itextpdf.text.Rectangle;\nimport com.itextpdf.text.pdf.PdfReader;\nimport com.itextpdf.text.pdf.PdfSignatureAppearance;\nimport com.itextpdf.text.pdf.PdfStamper;\nimport com.itextpdf.text.pdf.security.BouncyCastleDigest;\nimport com.itextpdf.text.pdf.security.DigestAlgorithms;\nimport com.itextpdf.text.pdf.security.ExternalDigest;\nimport com.itextpdf.text.pdf.security.ExternalSignature;\nimport com.itextpdf.text.pdf.security.MakeSignature;\nimport com.itextpdf.text.pdf.security.PrivateKeySignature;\n\nimport cn.ponfee.commons.jce.security.KeyStoreResolver;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType;\nimport cn.ponfee.commons.resource.ResourceLoaderFacade;\n\npublic class ItextUtil {\n\n    /**\n     * 单多次签章通用\n     * @param src\n     * @param target\n     * @param signatureInfos\n     * @throws GeneralSecurityException\n     * @throws IOException\n     * @throws DocumentException\n     */\n    public void sign(String src, String target, SignatureInfo... signatureInfos){\n        InputStream inputStream = null;\n        FileOutputStream outputStream = null;\n        ByteArrayOutputStream result = new ByteArrayOutputStream();\n        try {\n            inputStream = new FileInputStream(src);\n            for (SignatureInfo signatureInfo : signatureInfos) {\n                ByteArrayOutputStream tempArrayOutputStream = new ByteArrayOutputStream();\n                PdfReader reader = new PdfReader(inputStream);\n                //创建签章工具PdfStamper ，最后一个boolean参数是否允许被追加签名\n                PdfStamper stamper = PdfStamper.createSignature(reader, tempArrayOutputStream, '\\0', null, true);\n                // 获取数字签章属性对象\n                PdfSignatureAppearance appearance = stamper.getSignatureAppearance();\n                appearance.setReason(signatureInfo.getReason());\n                appearance.setLocation(signatureInfo.getLocation());\n                //设置签名的签名域名称，多次追加签名的时候，签名域名称不能一样，图片大小受表单域大小影响（过小导致压缩）\n                //appearance.setVisibleSignature(signatureInfo.getFieldName());\n                appearance.setVisibleSignature(new Rectangle(200, 200, 400, 400), 1, \"pdf seal[\" + System.nanoTime() + \"]\");\n                //读取图章图片\n                Image image = Image.getInstance(signatureInfo.getImagePath());\n                appearance.setSignatureGraphic(image);\n                appearance.setCertificationLevel(signatureInfo.getCertificationLevel());\n                //设置图章的显示方式，如下选择的是只显示图章（还有其他的模式，可以图章和签名描述一同显示）\n                appearance.setRenderingMode(signatureInfo.getRenderingMode());\n                // 摘要算法\n                ExternalDigest digest = new BouncyCastleDigest();\n                // 签名算法\n                ExternalSignature signature = new PrivateKeySignature(signatureInfo.getPk(), signatureInfo.getDigestAlgorithm(), null);\n                // 调用itext签名方法完成pdf签章\n                MakeSignature.signDetached(appearance, digest, signature, signatureInfo.getChain(), null, null, null, 0, signatureInfo.getSubfilter());\n                //定义输入流为生成的输出流内容，以完成多次签章的过程\n                inputStream = new ByteArrayInputStream(tempArrayOutputStream.toByteArray());\n                result = tempArrayOutputStream;\n            }\n            outputStream = new FileOutputStream(new File(target));\n            outputStream.write(result.toByteArray());\n            outputStream.flush();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if(null!=outputStream){\n                    outputStream.close();\n                }\n                if(null!=inputStream){\n                    inputStream.close();\n                }\n                if(null!=result){\n                    result.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    public static void main(String[] args) {\n        try {\n            ItextUtil app = new ItextUtil();\n            //将证书文件放入指定路径，并读取keystore ，获得私钥和证书链\n            KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, ResourceLoaderFacade.getResource(\"cas_test.pfx\").getStream(), \"1234\");\n            PrivateKey pk = resolver.getPrivateKey(\"1234\");\n            Certificate[] chain = resolver.getX509CertChain();\n            String src = ResourceLoaderFacade.getResource(\"ElasticSearch.pdf\").getFilePath();  \n            //封装签章信息\n            SignatureInfo info = new SignatureInfo();\n            info.setReason(\"理由\");\n            info.setLocation(\"位置\");\n            info.setPk(pk);\n            info.setChain(chain);\n            info.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);\n            info.setDigestAlgorithm(DigestAlgorithms.SHA1);\n            info.setFieldName(\"sig1\");\n            info.setImagePath(ResourceLoaderFacade.getResource(\"2.png\").getFilePath());\n            info.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);\n\n            SignatureInfo info1 = new SignatureInfo();\n            info1.setReason(\"理由1\");\n            info1.setLocation(\"位置1\");\n            info1.setPk(pk);\n            info1.setChain(chain);\n            info1.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);\n            info1.setDigestAlgorithm(DigestAlgorithms.SHA1);\n            info1.setFieldName(\"sig2\");\n            info1.setImagePath(ResourceLoaderFacade.getResource(\"2.png\").getFilePath());\n            info1.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);\n\n            app.sign(src, \"D://sign.pdf\", info,info1);\n        } catch (Exception e) {\n            // TODO Auto-generated catch block\n            e.printStackTrace();\n        }\n    }\n    \n    public static class SignatureInfo {\n        private String reason; //理由\n        private String location;//位置\n        private String digestAlgorithm;//摘要类型\n        private String imagePath;//图章路径\n        private String fieldName;//表单域名称\n        private Certificate[] chain;//证书链\n        private PrivateKey pk;//私钥\n        private int certificationLevel = 0; //批准签章\n        private PdfSignatureAppearance.RenderingMode renderingMode;//表现形式：仅描述，仅图片，图片和描述，签章者和描述\n        private MakeSignature.CryptoStandard subfilter;//支持标准，CMS,CADES\n        public String getReason() {\n            return reason;\n        }\n        public void setReason(String reason) {\n            this.reason = reason;\n        }\n        public String getLocation() {\n            return location;\n        }\n        public void setLocation(String location) {\n            this.location = location;\n        }\n        public String getDigestAlgorithm() {\n            return digestAlgorithm;\n        }\n        public void setDigestAlgorithm(String digestAlgorithm) {\n            this.digestAlgorithm = digestAlgorithm;\n        }\n        public String getImagePath() {\n            return imagePath;\n        }\n        public void setImagePath(String imagePath) {\n            this.imagePath = imagePath;\n        }\n        public String getFieldName() {\n            return fieldName;\n        }\n        public void setFieldName(String fieldName) {\n            this.fieldName = fieldName;\n        }\n        public Certificate[] getChain() {\n            return chain;\n        }\n        public void setChain(Certificate[] chain) {\n            this.chain = chain;\n        }\n        public PrivateKey getPk() {\n            return pk;\n        }\n        public void setPk(PrivateKey pk) {\n            this.pk = pk;\n        }\n        public int getCertificationLevel() {\n            return certificationLevel;\n        }\n        public void setCertificationLevel(int certificationLevel) {\n            this.certificationLevel = certificationLevel;\n        }\n        public PdfSignatureAppearance.RenderingMode getRenderingMode() {\n            return renderingMode;\n        }\n        public void setRenderingMode(PdfSignatureAppearance.RenderingMode renderingMode) {\n            this.renderingMode = renderingMode;\n        }\n        public MakeSignature.CryptoStandard getSubfilter() {\n            return subfilter;\n        }\n        public void setSubfilter(MakeSignature.CryptoStandard subfilter) {\n            this.subfilter = subfilter;\n        }\n        \n        \n    }\n}\n"
  },
  {
    "path": "src/test/java/test/pdf/PdfP7Sign.java",
    "content": "package test.pdf;\n\nimport java.io.FileOutputStream;\nimport java.util.Base64;\nimport java.util.Calendar;\nimport java.util.HashMap;\n\nimport org.apache.commons.io.IOUtils;\n\nimport com.itextpdf.text.Image;\nimport com.itextpdf.text.Rectangle;\nimport com.itextpdf.text.pdf.PdfDate;\nimport com.itextpdf.text.pdf.PdfDictionary;\nimport com.itextpdf.text.pdf.PdfName;\nimport com.itextpdf.text.pdf.PdfReader;\nimport com.itextpdf.text.pdf.PdfSignature;\nimport com.itextpdf.text.pdf.PdfSignatureAppearance;\nimport com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;\nimport com.itextpdf.text.pdf.PdfStamper;\nimport com.itextpdf.text.pdf.PdfString;\nimport com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;\nimport com.itextpdf.text.pdf.security.PdfPKCS7;\nimport com.itextpdf.text.pdf.security.TSAClient;\n\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport cn.ponfee.commons.jce.pkcs.PKCS1Signature;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType;\nimport cn.ponfee.commons.resource.ResourceLoaderFacade;\n\npublic class PdfP7Sign {\n    \n    private static void sign(String src, String dest)\n        throws Exception {\n        byte[] img = IOUtils.toByteArray(ResourceLoaderFacade.getResource(\"2.png\").getStream());\n\n        // ------------------------------------------------------------------//\n        // 1、用户上传自己的证书到服务器，从服务器上拿取待签名文件的hash数据\n        KeyStoreResolver resolver = new KeyStoreResolver(KeyStoreType.PKCS12, ResourceLoaderFacade.getResource(\"subject.pfx\").getStream(), \"123456\");\n        PdfReader reader = new PdfReader(src);\n        FileOutputStream fout = new FileOutputStream(dest);\n        PdfStamper stp = PdfStamper.createSignature(reader, fout, '\\0', null, true);\n\n        PdfSignatureAppearance appearance = stp.getSignatureAppearance();\n        appearance.setVisibleSignature(new Rectangle(100, 250, 288, 426), 1, \"Signature\");\n        appearance.setSignDate(Calendar.getInstance()); // 设置签名时间为当前日期\n        appearance.setSignatureGraphic(Image.getInstance(img));\n        appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);\n        appearance.setRenderingMode(RenderingMode.GRAPHIC);\n\n        PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, new PdfName(\"adbe.pkcs7.detached\"));\n        dic.setReason(appearance.getReason());\n        dic.setLocation(appearance.getLocation());\n        dic.setContact(appearance.getContact());\n        dic.setDate(new PdfDate(appearance.getSignDate()));\n        appearance.setCryptoDictionary(dic);\n\n        int estimatedSize = 8192;\n        HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();\n        exc.put(PdfName.CONTENTS, new Integer((estimatedSize << 1) + 2));\n        appearance.preClose(exc);\n        \n        byte[] bytes1 = IOUtils.toByteArray(appearance.getRangeStream());\n        byte[] hash = DigestUtils.sha1(bytes1);\n        // ------------------------------------------------------------------//\n\n        // ------------------------------------------------------------------//\n        //2、用户本地签名此HASH数据\n        TSAClient tsc = null;\n        /*boolean withTS = false;\n        if (withTS)\n        {\n            String tsa_url = properties.getProperty(\"TSA\");\n            String tsa_login = properties.getProperty(\"TSA_LOGIN\");\n            String tsa_passw = properties.getProperty(\"TSA_PASSWORD\");\n            tsc = new TSAClientBouncyCastle(tsa_url, tsa_login, tsa_passw);\n        }*/\n        byte[] ocsp = null;\n        /*boolean withOCSP = false;\n        if (withOCSP)\n        {\n            String url = PdfPKCS7.getOCSPURL((X509Certificate)chain[0]);\n            CertificateFactory cf = CertificateFactory.getInstance(\"X509\");\n            FileInputStream is = new FileInputStream(properties.getProperty(\"ROOTCERT\"));\n            X509Certificate root = (X509Certificate)cf.generateCertificate(is);\n            ocsp = new OcspClientBouncyCastle((X509Certificate)chain[0], root, url).getEncoded();\n        }*/\n        PdfPKCS7 pkcs7 = new PdfPKCS7(resolver.getPrivateKey(\"123456\"), resolver.getX509CertChain(), \"SHA1\", null, null, false);\n        byte[] bytes = pkcs7.getAuthenticatedAttributeBytes(hash, ocsp, null, CryptoStandard.CMS);\n        byte[] signed = PKCS1Signature.sign(bytes, resolver.getPrivateKey(\"123456\"), resolver.getX509CertChain()[0]);\n        System.out.println(\"signed：\" + Base64.getEncoder().encodeToString(signed));\n        pkcs7.setExternalDigest(signed, null, \"RSA\");\n\n        //sgn.update(sh, 0, sh.length);\n        byte[] encodedSig = pkcs7.getEncodedPKCS7(hash, tsc, ocsp, null, CryptoStandard.CMS);\n        byte[] paddedSig = new byte[encodedSig.length];\n        System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);\n        PdfDictionary dic2 = new PdfDictionary();\n        dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));\n        appearance.close(dic2);\n    }\n\n    \n    public static void main(String[] args)\n        throws Exception {\n        String src = \"D:\\\\test\\\\123.pdf\";\n        String dest = \"D:\\\\test\\\\result.pdf\";\n        sign(src, dest);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/pdf/TestPdfSign.java",
    "content": "package test.pdf;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport org.apache.commons.io.IOUtils;\n\nimport com.google.common.io.Files;\n\nimport cn.ponfee.commons.jce.security.KeyStoreResolver;\nimport cn.ponfee.commons.jce.security.KeyStoreResolver.KeyStoreType;\nimport cn.ponfee.commons.pdf.sign.PdfSignature;\nimport cn.ponfee.commons.pdf.sign.Signer;\nimport cn.ponfee.commons.pdf.sign.Stamp;\nimport cn.ponfee.commons.resource.ResourceLoaderFacade;\n\npublic class TestPdfSign {\n\n    public static void main(String[] args) throws IOException {\n        String sign = \"subject.pfx\";\n        String pwd = \"123456\";\n        KeyStoreResolver r = new KeyStoreResolver(KeyStoreType.PKCS12, ResourceLoaderFacade.getResource(sign).getStream(), pwd);\n        byte[] img = IOUtils.toByteArray(ResourceLoaderFacade.getResource(\"2.png\").getStream());\n        Signer signer = new Signer(r.getPrivateKey(pwd), r.getX509CertChain(), img, true);\n\n        Stamp stamp1 = new Stamp(1, 50, 250);\n        Stamp stamp2 = new Stamp(2, 150, 250);\n        Stamp stamp3 = new Stamp(3, 300, 250);\n\n        byte[] pdf = IOUtils.toByteArray(ResourceLoaderFacade.getResource(\"SM3密码杂凑算法.pdf\").getStream());\n\n        byte[] result = PdfSignature.sign(pdf, new Stamp[] { stamp1, stamp2, stamp3 }, signer);\n        Files.write(result, new File(\"d:/test/123.pdf\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/qrcode/Qrcode.java",
    "content": "package test.qrcode;\n\nimport java.io.*;\n\n/**\n *\n * QRcode class library 0.50beta10<BR>\n * (c)2003-2005 Y.Swetake<BR>\n * This version supports QRcode model2 version 1-40.<BR>\n * Some functions are not supported.<BR>\n *<BR>\n * @author Y.Swetake\n * @version 0.50beta10\n *\n *\n */\npublic class Qrcode {\n\n    static final String QRCODE_DATA_PATH = \"qrcode_data\";\n    char qrcodeErrorCorrect;\n    char qrcodeEncodeMode;\n    int qrcodeVersion;\n\n    int qrcodeStructureappendN;\n    int qrcodeStructureappendM;\n    int qrcodeStructureappendParity;\n    String qrcodeStructureappendOriginaldata;\n\n    public Qrcode() {\n        qrcodeErrorCorrect = 'M';\n        qrcodeEncodeMode = 'B';\n        qrcodeVersion = 0;\n\n        qrcodeStructureappendN = 0;\n        qrcodeStructureappendM = 0;\n        qrcodeStructureappendParity = 0;\n        qrcodeStructureappendOriginaldata = \"\";\n    }\n\n    /**\n     *エラ〖柠赖レベルを肋年します。\n     *@param ecc-エラ〖柠赖レベル('L','M','Q','H')\n     *\n     */\n    public void setQrcodeErrorCorrect(char ecc) {\n        qrcodeErrorCorrect = ecc;\n    }\n\n    /**\n     *附哼肋年されているエラ〖柠赖レベルを艰评します。\n     *@return エラ〖柠赖レベル('L','M','Q','H')\n     *\n     */\n    public char getQrcodeErrorCorrect() {\n        return qrcodeErrorCorrect;\n    }\n\n    /**\n     *附哼肋年されているバ〖ジョンを艰评します。\n     *@return バ〖ジョン 0から40の腊眶。0の眷圭は极瓢肋年。\n     *\n     */\n    public int getQrcodeVersion() {\n        return qrcodeVersion;\n    }\n\n    /**\n     *バ〖ジョンを肋年します。\n     *0を肋年すると极瓢肋年になります。\n     *@param version  0から40の腊眶\n     *\n     */\n    public void setQrcodeVersion(int ver) {\n        if (ver >= 0 && ver <= 40) {\n            qrcodeVersion = ver;\n        }\n    }\n\n    /**\n     *エンコ〖ドモ〖ドを肋年します。\n     *'N':眶机モ〖ド  'A':毖眶机モ〖ド　その戮:8bit byteモ〖ド\n     *@param encMode エンコ〖ドモ〖ド('N','A' or other)\n     *\n     */\n    public void setQrcodeEncodeMode(char encMode) {\n        qrcodeEncodeMode = encMode;\n    }\n\n    /**\n     *附哼肋年されているエンコ〖ドモ〖ドを艰评します。\n     *@return エンコ〖ドモ〖ド ('N','A' or other)\n     *\n     */\n    public char getQrcodeEncodeMode() {\n        return qrcodeEncodeMode;\n    }\n\n    /**\n     *息冯簇息のメソッドです。\n     *(活赋瞥掐です。)\n     *\n     */\n    public void setStructureappend(int m, int n, int p) {\n        if (n > 1 && n <= 16 && m > 0 && m <= 16 && p >= 0 && p <= 255) {\n            qrcodeStructureappendM = m;\n            qrcodeStructureappendN = n;\n            qrcodeStructureappendParity = p;\n        }\n    }\n\n    /**\n     *息冯に脱いるパリティを换叫します。\n     *(活赋瞥掐です。)\n     */\n    public int calStructureappendParity(byte[] originaldata) {\n        int originaldataLength;\n        int i = 0;\n        int structureappendParity = 0;\n\n        originaldataLength = originaldata.length;\n\n        if (originaldataLength > 1) {\n            structureappendParity = 0;\n            while (i < originaldataLength) {\n                structureappendParity = (structureappendParity ^ (originaldata[i] & 0xFF));\n                i++;\n            }\n        } else {\n            structureappendParity = -1;\n        }\n        return structureappendParity;\n    }\n\n    /**\n     *涂えられたデ〖タ误からQRコ〖ドエンコ〖ドデ〖タを\n     *boolean企肌傅芹误で手します。\n     *@param data エンコ〖ドするデ〖タ\n     *@return　QRコ〖ドエンコ〖ドデ〖タ\n     */\n    public boolean[][] calQrcode(byte[] qrcodeData) {\n        int dataLength;\n        int dataCounter = 0;\n\n        dataLength = qrcodeData.length;\n\n        int[] dataValue = new int[dataLength + 32];\n        byte[] dataBits = new byte[dataLength + 32];\n\n        if (dataLength <= 0) {\n            boolean ret[][] = { { false } };\n            return ret;\n        }\n\n        if (qrcodeStructureappendN > 1) {\n            dataValue[0] = 3;\n            dataBits[0] = 4;\n\n            dataValue[1] = qrcodeStructureappendM - 1;\n            dataBits[1] = 4;\n\n            dataValue[2] = qrcodeStructureappendN - 1;\n            dataBits[2] = 4;\n\n            dataValue[3] = qrcodeStructureappendParity;\n            dataBits[3] = 8;\n\n            dataCounter = 4;\n        }\n        dataBits[dataCounter] = 4;\n\n        /*  --- determine encode mode --- */\n\n        int[] codewordNumPlus;\n        int codewordNumCounterValue;\n\n        switch (qrcodeEncodeMode) {\n\n            /* ---- alphanumeric mode ---  */\n            case 'A':\n\n                codewordNumPlus = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n                    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 };\n\n                dataValue[dataCounter] = 2;\n                dataCounter++;\n                dataValue[dataCounter] = dataLength;\n                dataBits[dataCounter] = 9;\n                codewordNumCounterValue = dataCounter;\n\n                dataCounter++;\n                for (int i = 0; i < dataLength; i++) {\n                    char chr = (char) qrcodeData[i];\n                    byte chrValue = 0;\n                    if (chr >= 48 && chr < 58) {\n                        chrValue = (byte) (chr - 48);\n                    } else {\n                        if (chr >= 65 && chr < 91) {\n                            chrValue = (byte) (chr - 55);\n                        } else {\n                            if (chr == 32) {\n                                chrValue = 36;\n                            }\n                            if (chr == 36) {\n                                chrValue = 37;\n                            }\n                            if (chr == 37) {\n                                chrValue = 38;\n                            }\n                            if (chr == 42) {\n                                chrValue = 39;\n                            }\n                            if (chr == 43) {\n                                chrValue = 40;\n                            }\n                            if (chr == 45) {\n                                chrValue = 41;\n                            }\n                            if (chr == 46) {\n                                chrValue = 42;\n                            }\n                            if (chr == 47) {\n                                chrValue = 43;\n                            }\n                            if (chr == 58) {\n                                chrValue = 44;\n                            }\n                        }\n                    }\n                    if ((i % 2) == 0) {\n                        dataValue[dataCounter] = chrValue;\n                        dataBits[dataCounter] = 6;\n                    } else {\n                        dataValue[dataCounter] = dataValue[dataCounter] * 45 + chrValue;\n                        dataBits[dataCounter] = 11;\n                        if (i < dataLength - 1) {\n                            dataCounter++;\n                        }\n                    }\n                }\n                dataCounter++;\n                break;\n\n            /* ---- numeric mode ---- */\n            case 'N':\n\n                codewordNumPlus = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n                    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 };\n\n                dataValue[dataCounter] = 1;\n                dataCounter++;\n                dataValue[dataCounter] = dataLength;\n\n                dataBits[dataCounter] = 10; /* #version 1-9*/\n                codewordNumCounterValue = dataCounter;\n\n                dataCounter++;\n                for (int i = 0; i < dataLength; i++) {\n\n                    if ((i % 3) == 0) {\n                        dataValue[dataCounter] = (int) (qrcodeData[i] - 0x30);\n                        dataBits[dataCounter] = 4;\n                    } else {\n\n                        dataValue[dataCounter] = dataValue[dataCounter] * 10 + (int) (qrcodeData[i] - 0x30);\n\n                        if ((i % 3) == 1) {\n                            dataBits[dataCounter] = 7;\n                        } else {\n                            dataBits[dataCounter] = 10;\n                            if (i < dataLength - 1) {\n                                dataCounter++;\n                            }\n                        }\n                    }\n                }\n                dataCounter++;\n                break;\n\n            /* ---- 8bit byte ---- */\n            default:\n\n                codewordNumPlus = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n                    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };\n                dataValue[dataCounter] = 4;\n                dataCounter++;\n                dataValue[dataCounter] = dataLength;\n                dataBits[dataCounter] = 8; /* #version 1-9 */\n                codewordNumCounterValue = dataCounter;\n\n                dataCounter++;\n\n                for (int i = 0; i < dataLength; i++) {\n                    dataValue[i + dataCounter] = (qrcodeData[i] & 0xFF);\n                    dataBits[i + dataCounter] = 8;\n                }\n                dataCounter += dataLength;\n\n                break;\n        }\n\n        int totalDataBits = 0;\n        for (int i = 0; i < dataCounter; i++) {\n            totalDataBits += dataBits[i];\n        }\n\n        int ec;\n        switch (qrcodeErrorCorrect) {\n            case 'L':\n                ec = 1;\n                break;\n            case 'Q':\n                ec = 3;\n                break;\n            case 'H':\n                ec = 2;\n                break;\n            default:\n                ec = 0;\n        }\n\n        int[][] maxDataBitsArray = {\n            { 0, 128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728,\n                2032, 2320, 2672, 2920, 3320, 3624, 4056, 4504, 5016, 5352,\n                5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10136, 10984,\n                11640, 12328, 13048, 13800, 14496, 15312, 15936, 16816, 17728, 18672 },\n\n            { 0, 152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192,\n                2592, 2960, 3424, 3688, 4184, 4712, 5176, 5768, 6360, 6888,\n                7456, 8048, 8752, 9392, 10208, 10960, 11744, 12248, 13048, 13880,\n                14744, 15640, 16568, 17528, 18448, 19472, 20528, 21616, 22496, 23648 },\n\n            { 0, 72, 128, 208, 288, 368, 480, 528, 688, 800, 976,\n                1120, 1264, 1440, 1576, 1784, 2024, 2264, 2504, 2728, 3080,\n                3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960,\n                6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10208 },\n\n            { 0, 104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232,\n                1440, 1648, 1952, 2088, 2360, 2600, 2936, 3176, 3560, 3880,\n                4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880,\n                8264, 8920, 9368, 9848, 10288, 10832, 11408, 12016, 12656, 13328 }\n        };\n\n        int maxDataBits = 0;\n\n        if (qrcodeVersion == 0) {\n            /* auto version select */\n\n            qrcodeVersion = 1;\n            for (int i = 1; i <= 40; i++) {\n                if ((maxDataBitsArray[ec][i]) >= totalDataBits + codewordNumPlus[qrcodeVersion]) {\n                    maxDataBits = maxDataBitsArray[ec][i];\n                    break;\n                }\n                qrcodeVersion++;\n            }\n        } else {\n            maxDataBits = maxDataBitsArray[ec][qrcodeVersion];\n        }\n        totalDataBits += codewordNumPlus[qrcodeVersion];\n        dataBits[codewordNumCounterValue] += codewordNumPlus[qrcodeVersion];\n\n        int[] maxCodewordsArray = { 0, 26, 44, 70, 100, 134, 172, 196, 242,\n            292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085, 1156,\n            1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465,\n            2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706 };\n\n        int maxCodewords = maxCodewordsArray[qrcodeVersion];\n        int maxModules1side = 17 + (qrcodeVersion << 2);\n\n        int[] matrixRemainBit = { 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3,\n            4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 };\n\n        /* ---- read version ECC data file */\n\n        int byte_num = matrixRemainBit[qrcodeVersion] + (maxCodewords << 3);\n\n        byte[] matrixX = new byte[byte_num];\n        byte[] matrixY = new byte[byte_num];\n        byte[] maskArray = new byte[byte_num];\n        byte[] formatInformationX2 = new byte[15];\n        byte[] formatInformationY2 = new byte[15];\n        byte[] rsEccCodewords = new byte[1];\n        byte[] rsBlockOrderTemp = new byte[128];\n\n        try {\n            String filename = QRCODE_DATA_PATH + \"/qrv\" + Integer.toString(qrcodeVersion) + \"_\" + Integer.toString(ec) + \".dat\";\n\n            InputStream fis = Qrcode.class.getResourceAsStream(filename);\n            BufferedInputStream bis = new BufferedInputStream(fis);\n            bis.read(matrixX);\n            bis.read(matrixY);\n            bis.read(maskArray);\n            bis.read(formatInformationX2);\n            bis.read(formatInformationY2);\n            bis.read(rsEccCodewords);\n            bis.read(rsBlockOrderTemp);\n            bis.close();\n            fis.close();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        byte rsBlockOrderLength = 1;\n        for (byte i = 1; i < 128; i++) {\n            if (rsBlockOrderTemp[i] == 0) {\n                rsBlockOrderLength = i;\n                break;\n            }\n        }\n        byte[] rsBlockOrder = new byte[rsBlockOrderLength];\n        System.arraycopy(rsBlockOrderTemp, 0, rsBlockOrder, 0, rsBlockOrderLength);\n\n        byte[] formatInformationX1 = { 0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8 };\n        byte[] formatInformationY1 = { 8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0 };\n\n        int maxDataCodewords = maxDataBits >> 3;\n\n        /* -- read frame data  -- */\n\n        int modules1Side = 4 * qrcodeVersion + 17;\n        int matrixTotalBits = modules1Side * modules1Side;\n        byte[] frameData = new byte[matrixTotalBits + modules1Side];\n\n        try {\n            String filename = QRCODE_DATA_PATH + \"/qrvfr\" + Integer.toString(qrcodeVersion) + \".dat\";\n\n            InputStream fis = Qrcode.class.getResourceAsStream(filename);\n            BufferedInputStream bis = new BufferedInputStream(fis);\n            bis.read(frameData);\n            bis.close();\n            fis.close();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        /*  --- set terminator */\n\n        if (totalDataBits <= maxDataBits - 4) {\n            dataValue[dataCounter] = 0;\n            dataBits[dataCounter] = 4;\n        } else {\n            if (totalDataBits < maxDataBits) {\n                dataValue[dataCounter] = 0;\n                dataBits[dataCounter] = (byte) (maxDataBits - totalDataBits);\n            } else {\n                if (totalDataBits > maxDataBits) {\n                    System.out.println(\"overflow\");\n                }\n            }\n        }\n        byte[] dataCodewords = divideDataBy8Bits(dataValue, dataBits, maxDataCodewords);\n        byte[] codewords = calculateRSECC(dataCodewords, rsEccCodewords[0], rsBlockOrder, maxDataCodewords, maxCodewords);\n\n        /* ---- flash matrix */\n\n        byte[][] matrixContent = new byte[modules1Side][modules1Side];\n\n        for (int i = 0; i < modules1Side; i++) {\n            for (int j = 0; j < modules1Side; j++) {\n                matrixContent[j][i] = 0;\n            }\n        }\n\n        /* --- attach data */\n        for (int i = 0; i < maxCodewords; i++) {\n\n            byte codeword_i = codewords[i];\n            for (int j = 7; j >= 0; j--) {\n\n                int codewordBitsNumber = (i * 8) + j;\n\n                matrixContent[matrixX[codewordBitsNumber] & 0xFF][matrixY[codewordBitsNumber] & 0xFF] = (byte) ((255 * (codeword_i & 1))\n                    ^ maskArray[codewordBitsNumber]);\n\n                codeword_i = (byte) ((codeword_i & 0xFF) >>> 1);\n            }\n        }\n\n        for (int matrixRemain = matrixRemainBit[qrcodeVersion]; matrixRemain > 0; matrixRemain--) {\n            int remainBitTemp = matrixRemain + (maxCodewords * 8) - 1;\n            matrixContent[matrixX[remainBitTemp] & 0xFF][matrixY[remainBitTemp] & 0xFF] = (byte) (255 ^ maskArray[remainBitTemp]);\n        }\n\n        /* --- mask select --- */\n        byte maskNumber = selectMask(matrixContent, matrixRemainBit[qrcodeVersion] + maxCodewords * 8);\n        byte maskContent = (byte) (1 << maskNumber);\n\n        /* --- format information --- */\n\n        byte formatInformationValue = (byte) (ec << 3 | maskNumber);\n\n        String[] formatInformationArray = { \"101010000010010\", \"101000100100101\",\n            \"101111001111100\", \"101101101001011\", \"100010111111001\", \"100000011001110\",\n            \"100111110010111\", \"100101010100000\", \"111011111000100\", \"111001011110011\",\n            \"111110110101010\", \"111100010011101\", \"110011000101111\", \"110001100011000\",\n            \"110110001000001\", \"110100101110110\", \"001011010001001\", \"001001110111110\",\n            \"001110011100111\", \"001100111010000\", \"000011101100010\", \"000001001010101\",\n            \"000110100001100\", \"000100000111011\", \"011010101011111\", \"011000001101000\",\n            \"011111100110001\", \"011101000000110\", \"010010010110100\", \"010000110000011\",\n            \"010111011011010\", \"010101111101101\" };\n\n        for (int i = 0; i < 15; i++) {\n\n            byte content = Byte.parseByte(formatInformationArray[formatInformationValue].substring(i, i + 1));\n\n            matrixContent[formatInformationX1[i] & 0xFF][formatInformationY1[i] & 0xFF] = (byte) (content * 255);\n            matrixContent[formatInformationX2[i] & 0xFF][formatInformationY2[i] & 0xFF] = (byte) (content * 255);\n\n        }\n\n        boolean[][] out = new boolean[modules1Side][modules1Side];\n\n        int c = 0;\n        for (int i = 0; i < modules1Side; i++) {\n            for (int j = 0; j < modules1Side; j++) {\n\n                if ((matrixContent[j][i] & maskContent) != 0 || frameData[c] == (char) 49) {\n                    out[j][i] = true;\n                } else {\n                    out[j][i] = false;\n                }\n                c++;\n            }\n            c++;\n        }\n\n        return out;\n\n    }\n\n    private static byte[] divideDataBy8Bits(int[] data, byte[] bits, int maxDataCodewords) {\n        /* divide Data By 8bit and add padding char */\n        int l1 = bits.length;\n        int l2;\n        int codewordsCounter = 0;\n        int remainingBits = 8;\n        int max = 0;\n        int buffer;\n        int bufferBits;\n        boolean flag;\n\n        if (l1 != data.length) {\n        }\n        for (int i = 0; i < l1; i++) {\n            max += bits[i];\n        }\n        l2 = (max - 1) / 8 + 1;\n        byte[] codewords = new byte[maxDataCodewords];\n        for (int i = 0; i < l2; i++) {\n            codewords[i] = 0;\n        }\n        for (int i = 0; i < l1; i++) {\n            buffer = data[i];\n            bufferBits = bits[i];\n            flag = true;\n\n            if (bufferBits == 0) {\n                break;\n            }\n            while (flag) {\n                if (remainingBits > bufferBits) {\n                    codewords[codewordsCounter] = (byte) ((codewords[codewordsCounter] << bufferBits) | buffer);\n                    remainingBits -= bufferBits;\n                    flag = false;\n                } else {\n                    bufferBits -= remainingBits;\n                    codewords[codewordsCounter] = (byte) ((codewords[codewordsCounter] << remainingBits) | (buffer >> bufferBits));\n\n                    if (bufferBits == 0) {\n                        flag = false;\n                    } else {\n                        buffer = (buffer & ((1 << bufferBits) - 1));\n                        flag = true;\n                    }\n                    codewordsCounter++;\n                    remainingBits = 8;\n                }\n            }\n        }\n        if (remainingBits != 8) {\n            codewords[codewordsCounter] = (byte) (codewords[codewordsCounter] << remainingBits);\n        } else {\n            codewordsCounter--;\n        }\n        if (codewordsCounter < maxDataCodewords - 1) {\n            flag = true;\n            while (codewordsCounter < maxDataCodewords - 1) {\n                codewordsCounter++;\n                if (flag) {\n                    codewords[codewordsCounter] = -20;\n                } else {\n                    codewords[codewordsCounter] = 17;\n                }\n                flag = !(flag);\n            }\n        }\n        return codewords;\n    }\n\n    private static byte[] calculateRSECC(byte[] codewords, byte rsEccCodewords, byte[] rsBlockOrder, int maxDataCodewords, int maxCodewords) {\n\n        byte[][] rsCalTableArray = new byte[256][rsEccCodewords];\n        try {\n            String filename = QRCODE_DATA_PATH + \"/rsc\" + Byte.toString(rsEccCodewords) + \".dat\";\n            InputStream fis = Qrcode.class.getResourceAsStream(filename);\n            BufferedInputStream bis = new BufferedInputStream(fis);\n            for (int i = 0; i < 256; i++) {\n                bis.read(rsCalTableArray[i]);\n            }\n            bis.close();\n            fis.close();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        /* ---- RS-ECC prepare */\n\n        int i = 0;\n        int j = 0;\n        int rsBlockNumber = 0;\n\n        byte[][] rsTemp = new byte[rsBlockOrder.length][];\n        byte res[] = new byte[maxCodewords];\n        System.arraycopy(codewords, 0, res, 0, codewords.length);\n\n        i = 0;\n        while (i < rsBlockOrder.length) {\n            rsTemp[i] = new byte[(rsBlockOrder[i] & 0xFF) - rsEccCodewords];\n            i++;\n        }\n        i = 0;\n        while (i < maxDataCodewords) {\n            rsTemp[rsBlockNumber][j] = codewords[i];\n            j++;\n            if (j >= (rsBlockOrder[rsBlockNumber] & 0xFF) - rsEccCodewords) {\n                j = 0;\n                rsBlockNumber++;\n            }\n            i++;\n        }\n\n        /* ---  RS-ECC main --- */\n\n        rsBlockNumber = 0;\n        while (rsBlockNumber < rsBlockOrder.length) {\n            byte[] rsTempData;\n            rsTempData = (byte[]) rsTemp[rsBlockNumber].clone();\n\n            int rsCodewords = (rsBlockOrder[rsBlockNumber] & 0xFF);\n            int rsDataCodewords = rsCodewords - rsEccCodewords;\n\n            j = rsDataCodewords;\n            while (j > 0) {\n                byte first = rsTempData[0];\n                if (first != 0) {\n                    byte[] leftChr = new byte[rsTempData.length - 1];\n                    System.arraycopy(rsTempData, 1, leftChr, 0, rsTempData.length - 1);\n                    byte[] cal = rsCalTableArray[(first & 0xFF)];\n                    rsTempData = calculateByteArrayBits(leftChr, cal, \"xor\");\n                } else {\n                    if (rsEccCodewords < rsTempData.length) {\n                        byte[] rsTempNew = new byte[rsTempData.length - 1];\n                        System.arraycopy(rsTempData, 1, rsTempNew, 0, rsTempData.length - 1);\n                        rsTempData = (byte[]) rsTempNew.clone();\n                    } else {\n                        byte[] rsTempNew = new byte[rsEccCodewords];\n                        System.arraycopy(rsTempData, 1, rsTempNew, 0, rsTempData.length - 1);\n                        rsTempNew[rsEccCodewords - 1] = 0;\n                        rsTempData = (byte[]) rsTempNew.clone();\n                    }\n                }\n                j--;\n            }\n\n            System.arraycopy(rsTempData, 0, res, codewords.length + rsBlockNumber * rsEccCodewords, rsEccCodewords);\n            rsBlockNumber++;\n        }\n        return res;\n    }\n\n    private static byte[] calculateByteArrayBits(byte[] xa, byte[] xb, String ind) {\n        int ll;\n        int ls;\n        byte[] res;\n        byte[] xl;\n        byte[] xs;\n\n        if (xa.length > xb.length) {\n            xl = (byte[]) xa.clone();\n            xs = (byte[]) xb.clone();\n        } else {\n            xl = (byte[]) xb.clone();\n            xs = (byte[]) xa.clone();\n        }\n        ll = xl.length;\n        ls = xs.length;\n        res = new byte[ll];\n\n        for (int i = 0; i < ll; i++) {\n            if (i < ls) {\n                if (ind == \"xor\") {\n                    res[i] = (byte) (xl[i] ^ xs[i]);\n                } else {\n                    res[i] = (byte) (xl[i] | xs[i]);\n                }\n            } else {\n                res[i] = xl[i];\n            }\n        }\n        return res;\n    }\n\n    private static byte selectMask(byte[][] matrixContent, int maxCodewordsBitWithRemain) {\n        int l = matrixContent.length;\n        int[] d1 = { 0, 0, 0, 0, 0, 0, 0, 0 };\n        int[] d2 = { 0, 0, 0, 0, 0, 0, 0, 0 };\n        int[] d3 = { 0, 0, 0, 0, 0, 0, 0, 0 };\n        int[] d4 = { 0, 0, 0, 0, 0, 0, 0, 0 };\n\n        int d2And = 0;\n        int d2Or = 0;\n        int[] d4Counter = { 0, 0, 0, 0, 0, 0, 0, 0 };\n\n        for (int y = 0; y < l; y++) {\n            int[] xData = { 0, 0, 0, 0, 0, 0, 0, 0 };\n            int[] yData = { 0, 0, 0, 0, 0, 0, 0, 0 };\n            boolean[] xD1Flag = { false, false, false, false, false, false, false, false };\n            boolean[] yD1Flag = { false, false, false, false, false, false, false, false };\n\n            for (int x = 0; x < l; x++) {\n\n                if (x > 0 && y > 0) {\n                    d2And = matrixContent[x][y] & matrixContent[x - 1][y] & matrixContent[x][y - 1] & matrixContent[x - 1][y - 1] & 0xFF;\n\n                    d2Or = (matrixContent[x][y] & 0xFF) | (matrixContent[x - 1][y] & 0xFF) | (matrixContent[x][y - 1] & 0xFF) | (matrixContent[x - 1][y - 1]\n                        & 0xFF);\n                }\n\n                for (int maskNumber = 0; maskNumber < 8; maskNumber++) {\n\n                    xData[maskNumber] = ((xData[maskNumber] & 63) << 1) | (((matrixContent[x][y] & 0xFF) >>> maskNumber) & 1);\n\n                    yData[maskNumber] = ((yData[maskNumber] & 63) << 1) | (((matrixContent[y][x] & 0xFF) >>> maskNumber) & 1);\n\n                    if ((matrixContent[x][y] & (1 << maskNumber)) != 0) {\n                        d4Counter[maskNumber]++;\n                    }\n\n                    if (xData[maskNumber] == 93) {\n                        d3[maskNumber] += 40;\n                    }\n\n                    if (yData[maskNumber] == 93) {\n                        d3[maskNumber] += 40;\n                    }\n\n                    if (x > 0 && y > 0) {\n\n                        if (((d2And & 1) != 0) || ((d2Or & 1) == 0)) {\n                            d2[maskNumber] += 3;\n                        }\n\n                        d2And = d2And >> 1;\n                        d2Or = d2Or >> 1;\n                    }\n\n                    if (((xData[maskNumber] & 0x1F) == 0) || ((xData[maskNumber] & 0x1F) == 0x1F)) {\n                        if (x > 3) {\n                            if (xD1Flag[maskNumber]) {\n                                d1[maskNumber]++;\n                            } else {\n                                d1[maskNumber] += 3;\n                                xD1Flag[maskNumber] = true;\n                            }\n                        }\n                    } else {\n                        xD1Flag[maskNumber] = false;\n                    }\n                    if (((yData[maskNumber] & 0x1F) == 0) || ((yData[maskNumber] & 0x1F) == 0x1F)) {\n                        if (x > 3) {\n                            if (yD1Flag[maskNumber]) {\n                                d1[maskNumber]++;\n                            } else {\n                                d1[maskNumber] += 3;\n                                yD1Flag[maskNumber] = true;\n                            }\n                        }\n                    } else {\n                        yD1Flag[maskNumber] = false;\n                    }\n\n                }\n            }\n        }\n        int minValue = 0;\n        byte res = 0;\n        int[] d4Value = { 90, 80, 70, 60, 50, 40, 30, 20, 10, 0, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 90 };\n        for (int maskNumber = 0; maskNumber < 8; maskNumber++) {\n\n            d4[maskNumber] = d4Value[(int) ((20 * d4Counter[maskNumber]) / maxCodewordsBitWithRemain)];\n\n            int demerit = d1[maskNumber] + d2[maskNumber] + d3[maskNumber] + d4[maskNumber];\n\n            if (demerit < minValue || maskNumber == 0) {\n                res = (byte) maskNumber;\n                minValue = demerit;\n            }\n        }\n        return res;\n    }\n\n    /*--- class end ---*/\n}\n"
  },
  {
    "path": "src/test/java/test/qrcode/QrcodeTest.java",
    "content": "package test.qrcode;\n\n/**\n *QRcodeクラスライブラリ脱sample\n *\n *妈办苞眶をデ〖タとしたQRcodeを\n *テキストで叫蜗します\n */\npublic class QrcodeTest {\n    public static void main(String[] args) {\n\n        Qrcode x = new Qrcode();\n        x.setQrcodeErrorCorrect('M'); //エラ〖柠赖レベルM\n        x.setQrcodeEncodeMode('B'); //8bit byte モ〖ド\n        boolean[][] matrix = x.calQrcode(args[0].getBytes());\n\n        for (int i = 0; i < matrix.length; i++) {\n            for (int j = 0; j < matrix.length; j++) {\n                if (matrix[j][i]) {\n                    System.out.print(\"@\");\n                } else {\n                    System.out.print(\" \");\n                }\n            }\n            System.out.print(\"\\n\");\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/reflect/ClassUtilsTest.java",
    "content": "package test.reflect;\n\nimport cn.ponfee.commons.base.Predicates;\nimport cn.ponfee.commons.base.PrimitiveTypes;\nimport cn.ponfee.commons.base.tuple.*;\nimport cn.ponfee.commons.cache.Cache;\nimport cn.ponfee.commons.cache.CacheBuilder;\nimport cn.ponfee.commons.collect.ByteArrayWrapper;\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.concurrent.ThreadPoolExecutors;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.spring.LocalizedMethodArgumentResolver;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.openjdk.jol.vm.VM;\n\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * @author Ponfee\n */\npublic class ClassUtilsTest {\n\n    static int n = 10000000;\n\n    @Test\n    public void test0() {\n        for (int i = 0; i < n; i++) {\n            ClassUtils.getConstructor(ByteArrayWrapper.class, byte[].class);\n        }\n    }\n\n    @Test\n    public void test1() throws Exception {\n        for (int i = 0; i < n; i++) {\n            ByteArrayWrapper.class.getConstructor(byte[].class);\n        }\n    }\n\n    @Test\n    public void test20() {\n        for (int i = 0; i < n; i++) {\n            ClassUtils.getConstructor(ClassUtilsTest.class);\n        }\n    }\n\n    @Test\n    public void test21() throws NoSuchMethodException {\n        for (int i = 0; i < n; i++) {\n            ClassUtilsTest.class.getConstructor();\n        }\n    }\n\n    @Test\n    public void test3() throws Exception {\n        int n = 1000;\n        Method method = ClassUtils.class.getMethod(\"newInstance\", Class.class, Class[].class, Object[].class);\n        System.out.println(Arrays.toString(ClassUtils.getMethodParamNames(method)));\n        for (int i = 0; i < n; i++) {\n            ClassUtils.getMethodParamNames(method);\n        }\n    }\n\n    @Test\n    public void test4() throws Exception {\n        int n = 1000000;\n        Cache<Method, String[]> METHOD_ARGSNAME = CacheBuilder.<Method, String[]>newBuilder().build();\n        Method method = ClassUtils.class.getMethod(\"newInstance\", Class.class, Class[].class, Object[].class);\n        for (int i = 0; i < n; i++) {\n            String[] argsName = METHOD_ARGSNAME.get(method);\n            if (argsName == null) {\n                argsName = ClassUtils.getMethodParamNames(method);\n                METHOD_ARGSNAME.put(method, argsName);\n                System.out.println(Arrays.toString(argsName));\n            }\n        }\n    }\n\n    @Test\n    public void test5() throws Exception {\n        Assert.assertEquals(ClassUtils.class.getMethod(\"newInstance\", Class.class, Class[].class, Object[].class), ClassUtils.class.getMethod(\"newInstance\", Class.class, Class[].class, Object[].class));\n        Assert.assertEquals(Result.class.getDeclaredField(\"code\"), Result.class.getDeclaredField(\"code\"));\n    }\n\n    @Test\n    public void test6() throws Exception {\n        System.out.println(Arrays.toString(ClassUtils.getMethodParamNames(ClassUtils.class.getMethod(\"newInstance\", Class.class, Class[].class, Object[].class))));\n        System.out.println(ClassUtils.getMethodSignature(ClassUtils.class.getMethod(\"newInstance\", Class.class, Class[].class, Object[].class)));\n        System.out.println(ClassUtils.getClassName(ClassUtils.class));\n        System.out.println(ClassUtils.getPackagePath(ClassUtils.class));\n        System.out.println(ClassUtils.getClassFilePath(ClassUtils.class));\n        System.out.println(ClassUtils.getClassFilePath(org.apache.commons.lang3.StringUtils.class));\n        System.out.println(ClassUtils.getClasspath(ClassUtils.class));\n        System.out.println(ClassUtils.getClasspath(org.apache.commons.lang3.StringUtils.class));\n        System.out.println(ClassUtils.getClasspath());\n        System.out.println(ClassUtils.newInstance(Tuple3.class, new Object[]{1, 2, 3}));\n        System.out.println(ClassUtils.newInstance(Tuple3.class, new Object[]{null, null, null}));\n        System.out.println(ClassUtils.newInstance(Tuple2.class, new Object[]{new String[]{\"a\", \"b\"}, new Integer[]{1, 2}}));\n        System.out.println(\"Tuple.class.isAssignableFrom(Tuple.class)=\" + Tuple.class.isAssignableFrom(Tuple.class));\n        System.out.println(\"Tuple.class.isAssignableFrom(Tuple1.class)=\" + Tuple.class.isAssignableFrom(Tuple1.class));\n        System.out.println(\"Tuple1.class.isAssignableFrom(Tuple.class)=\" + Tuple1.class.isAssignableFrom(Tuple.class));\n        System.out.println(PrimitiveTypes.FLOAT.isCastable(PrimitiveTypes.DOUBLE));\n        System.out.println(PrimitiveTypes.FLOAT.isCastable(PrimitiveTypes.FLOAT));\n        System.out.println(PrimitiveTypes.FLOAT.isCastable(PrimitiveTypes.LONG));\n\n        System.out.println(ClassUtils.newInstance(Tuple0.class));\n        System.out.println(ClassUtils.newInstance(Tuple0.class, new Object[]{}));\n        System.out.println(ClassUtils.newInstance(Tuple1.class, new Object[]{null}));\n        System.out.println(ClassUtils.newInstance(A.class, new Object[]{1}));\n\n        Assert.assertTrue(new Object().getClass() == Object.class);\n        Assert.assertTrue(\"\".getClass() == String.class);\n        Assert.assertEquals(String.class.getSimpleName(), \"String\");\n        Assert.assertFalse(String.class.equals(Class.class));\n        Assert.assertTrue(String.class.getClass() == Class.class);\n        Assert.assertTrue(Object.class.getClass() == Class.class);\n        Assert.assertTrue(Class.class.getClass() == Class.class);\n        Assert.assertTrue(Class.class.getClass().getClass().getClass().getClass() == Class.class);\n        Assert.assertTrue(String.class instanceof Class);\n        Assert.assertTrue(Object.class instanceof Class);\n        Assert.assertTrue(Class.class instanceof Class);\n\n        Assert.assertTrue(Predicates.Y.equals(true));\n        Assert.assertFalse(Predicates.Y.equals(false));\n        Assert.assertTrue(Predicates.N.equals(false));\n        Assert.assertFalse(Predicates.N.equals(true));\n        Assert.assertEquals(Predicates.N.code(), 'N');\n        Assert.assertTrue(Predicates.Y.state());\n        Assert.assertFalse(Predicates.N.state());\n        Assert.assertEquals(ClassUtils.invoke(A.class, \"max\", new Object[]{2, 4}), new Integer(4));\n        Assert.assertEquals(ClassUtils.invoke(new A(5), \"add\", new Object[]{2}), new Integer(7));\n        Assert.assertEquals(Boolean.TYPE, boolean.class);\n        Assert.assertEquals((byte) 0B1_0_0_0_0_0_0_0, -128);\n        Assert.assertEquals(0B1_0_0_0_0_0_0_0, 128);\n        Assert.assertThrows(RuntimeException.class, () -> ClassUtils.invoke(String.class, \"getName\"));\n\n        // 普通Class类实例(如String.class)：只处理其所表示类的静态方法，如String.valueOf(1)。不支持Class类中的实例方法，如String.class.getName()\n        Assert.assertEquals(ClassUtils.invoke(String.class, \"valueOf\", new Object[]{1}), \"1\");\n        Assert.assertEquals(ClassUtils.invoke(String.class, \"join\", new Object[]{\"|\", new String[]{\"a\", \"b\"}}), \"a|b\");\n        Assert.assertNull(ClassUtils.getMethod(String.class, \"getName\"));\n\n        // Class.class对象：只处理Class类中的实例方法，如Class.class.getName()。不支持Class类中的静态方法，如Class.forName(\"cn.ponfee.commons.base.tuple.Tuple0\");\n        Assert.assertEquals(ClassUtils.invoke(Class.class, \"getName\"), \"java.lang.Class\");\n        Assert.assertNull(ClassUtils.getMethod(Class.class, \"forName\", String.class));\n        //double a = 1;\n        //float a = 1;\n        //long a = 1;\n        int a = 1;\n        //short a = 1;\n        //char a = 1;\n        //byte a = 1;\n        //boolean a = true;\n        new A(a);\n        ClassUtils.newInstance(A.class, new Class[]{int.class}, new Object[]{a});\n        System.out.println(0B1_0_0_0_0_0_0_0);\n        System.out.println(0B1_1_1_1_1_1_1_1);\n    }\n\n    @Test\n    public void test00() throws Exception {\n        System.out.println(Arrays.toString(A.class.getInterfaces()));\n        System.out.println(Arrays.toString(Tuple.class.getInterfaces()));\n\n        Assert.assertEquals(31, Fields.get(Tuple.class, \"HASH_FACTOR\"));\n        Fields.put(Tuple.class, \"HASH_FACTOR\", 311); // final可以修改\n        Assert.assertEquals(311, Fields.get(Tuple.class, \"HASH_FACTOR\"));\n        System.out.println(B.INTF_1);\n\n        Assert.assertNull(Fields.get(A.class, \"static_2\"));\n        A.max(1, 2); // new A(1); 要初始化\n        Assert.assertEquals(10, Fields.get(A.class, \"static_2\"));\n        Fields.put(A.class, \"static_2\", 20);\n        Assert.assertEquals(20, Fields.get(A.class, \"static_2\"));\n        Assert.assertNotNull(null, ClassUtils.getStaticField(A.class, \"static_1\"));\n        B.static_2 = 123;\n        Assert.assertEquals(123, Fields.get(A.class, \"static_2\"));\n        Assert.assertEquals(123, Fields.get(B.class, \"static_2\"));\n\n\n        Assert.assertEquals(Fields.get(Tuple1.of(\"xx\"), \"a\"), \"xx\");\n        System.out.println(PrimitiveTypes.allPrimitiveTypes());\n\n        System.out.println(GenericUtils.getFieldActualType(A.class, ClassUtils.getField(A.class, \"member_1\")));// java.util.List\n        System.out.println(GenericUtils.getFieldActualType(A.class, ClassUtils.getField(A.class, \"member_2\"))); // java.util.List\n        System.out.println(GenericUtils.getFieldActualType(A.class, ClassUtils.getField(A.class, \"member_3\"))); // java.lang.Object\n\n        System.out.println(GenericUtils.getFieldActualType(B.class, ClassUtils.getField(B.class, \"member_1\"))); // java.lang.Object\n        System.out.println(GenericUtils.getFieldActualType(B.class, ClassUtils.getField(B.class, \"member_2\"))); // java.lang.Object\n        System.out.println(GenericUtils.getFieldActualType(B.class, ClassUtils.getField(B.class, \"member_3\"))); // java.lang.Integer\n\n        System.out.println(GenericUtils.getFieldActualType(A.class, ClassUtils.getStaticFieldInClassChain(A.class, \"static_1\").b));\n    }\n\n    @Test\n    public void test8() throws Exception {\n        A a = new B(1);\n        B b = new B(1);\n        Assert.assertEquals(\"A#func1\", a.func1());\n        Assert.assertEquals(\"A#func2\", a.func2());\n        Assert.assertEquals(\"B#func1\", b.func1());\n        Assert.assertEquals(\"A#func2\", b.func2());\n\n        Assert.assertEquals(\"test-static\", (A.get()).test());\n\n        Fields.put(Double.class, \"SIZE\", 999);\n        Assert.assertEquals(999, Fields.get(Double.class, \"SIZE\"));\n        Assert.assertEquals(64, Double.SIZE);\n        Assert.assertEquals(999, Fields.get(Double.class, \"SIZE\"));\n    }\n\n    @Test\n    public void test9() {\n        Assert.assertTrue(Collects.toArray(new Object[]{\"1\", \"2\"})[0] instanceof String);\n        Assert.assertEquals(String[].class, Collects.toArray(new String[]{\"1\", \"2\"}).getClass());\n\n        Assert.assertTrue(toArray(new Object[]{\"1\", \"2\"})[0] instanceof String);\n        Assert.assertEquals(String[].class, toArray(new String[]{\"1\", \"2\"}).getClass());\n\n        Assert.assertTrue(get(new Object[]{\"1\", \"2\"}, 0) instanceof String);\n        Assert.assertTrue(get(new String[]{\"1\", \"2\"}, 0) instanceof String);\n        Assert.assertTrue(get(new Integer[]{1, 2}, 0) instanceof Integer);\n    }\n\n    @Test\n    public void tes10() {\n        Object obj = new Object();\n        Assert.assertEquals(obj.hashCode(), System.identityHashCode(obj));\n        Assert.assertEquals(Fields.addressOf(obj), VM.current().addressOf(obj));\n        System.out.println(\"identityHashCode: \" + System.identityHashCode(obj));\n        System.out.println(\"Fields.addressOf: \" + Fields.addressOf(obj));\n        System.out.println(\"VM.addressOf: \" + VM.current().addressOf(obj));\n\n        obj = ClassUtilsTest.class;\n        Assert.assertEquals(obj.hashCode(), System.identityHashCode(obj));\n        Assert.assertEquals(Fields.addressOf(obj), VM.current().addressOf(obj));\n\n        Assert.assertNotEquals(\"123\".hashCode(), System.identityHashCode(new String(\"123\")));\n        Assert.assertNotEquals(Fields.addressOf(\"123\"), VM.current().addressOf(new String(\"123\")));\n\n        Assert.assertTrue(String.class == \"123\".getClass());\n\n\n        Assert.assertFalse(new Integer(1) == new Integer(1));\n        Assert.assertTrue(Integer.valueOf(1) == Integer.valueOf(1));\n        Assert.assertEquals(Fields.addressOf(1), VM.current().addressOf(1));\n        Assert.assertEquals(Fields.addressOf(1), VM.current().addressOf(1));\n        Assert.assertEquals(Fields.addressOf(1L), VM.current().addressOf(1L));\n        Assert.assertEquals(Fields.addressOf((byte) 1), VM.current().addressOf((byte) 1));\n\n\n        Assert.assertEquals(Fields.addressOf(new Object[]{1, 2, 3, obj}, 3), VM.current().addressOf(obj));\n        Assert.assertEquals(Fields.addressOf(new Object[]{1, 2, obj, 6, new Object(), \"abc\"}, 2), VM.current().addressOf(obj));\n\n        String str = \"fdsaf23\";\n        Assert.assertEquals(Fields.addressOf(new String[]{\"abc\", str, new String(), null, \"\"}, 1), VM.current().addressOf(str));\n\n        Byte a = 1;\n        Assert.assertEquals(Fields.addressOf(new Byte[]{127, a, 21, 4}, 1), VM.current().addressOf(a));\n\n    }\n\n    /*@Test\n    public void test() throws Exception {\n        Method method = ThreadPoolExecutors.class.getMethod(\"create\", int.class, int.class, long.class);\n        LocalizedMethodArgumentResolver resolver = new LocalizedMethodArgumentResolver(new ObjectMapper());\n        String args = Jsons.toJson(new Object[]{1, 2, 10});\n        Object[] objects = resolver.parseRequestBody(method, args);\n        Arrays.stream(objects).forEach(e -> System.out.println(e + \":\" + e.getClass()));\n    }*/\n\n    public static Object[] toArray(Object... args) {\n        return args;\n    }\n\n    public static Object get(Object[] args, int i) {\n        return args[i];\n    }\n\n    public static class A<T> {\n        public List<T> member_1;\n        public List<String> member_2;\n        public T member_3;\n        private int x;\n        public static List<A> static_1;\n        public static Integer static_2 = 10;\n\n        public A(int x) {\n            this.x = x;\n        }\n\n        public void a(int i) {\n\n        }\n\n        public int add(int i) {\n            return x + i;\n        }\n\n        public static String func1() {\n            return \"A#func1\";\n        }\n\n        public static String func2() {\n            return \"A#func2\";\n        }\n\n        public static int max(int a, int b) {\n            return a > b ? a : b;\n        }\n\n        /*public static void a(int i) {\n\n        }*/\n\n        public static String test() {\n            return \"test-static\";\n        }\n\n        public static A get() {\n            return null;\n        }\n    }\n\n    public static interface X {\n        String INTF_1 = \"test1\";\n    }\n\n    public static class B extends A<Integer> implements X {\n\n        public B(int x) {\n            super(x);\n        }\n\n        public static String func1() {\n            return \"B#func1\";\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/reflect/ProxyTest.java",
    "content": "package test.reflect;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Proxy;\n\nimport org.apache.commons.io.IOUtils;\n\nimport cn.ponfee.commons.util.Bytes;\n\npublic class ProxyTest {\n\n    @SuppressWarnings(\"restriction\")\n    public static void main(String[] args) throws IOException {\n        Echo target = new MountainEcho();\n        EchoInvocationHandler handler = new EchoInvocationHandler(target);\n        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { Echo.class }, handler);\n        String proxyRtn = ((Echo) proxy).echo(\"hello\");\n\n        System.out.println(proxy.getClass()); // com.sun.proxy.$Proxy0\n        System.out.println(proxyRtn); // return EchoInvocationHandler.invoke result\n\n        System.out.println(\"\\n==============================导出代理类\");\n\n        // 根据类信息和提供的代理类名称，生成字节码  \n        byte[] classBytes = sun.misc.ProxyGenerator.generateProxyClass(\"ProxyClass\", proxy.getClass().getInterfaces());\n\n        // 打印字节码\n        System.out.println(Bytes.dumpHex(classBytes));\n\n        // 导出到文件\n        IOUtils.write(classBytes, new FileOutputStream(new File(\"d:/ProxyClass.class\"))); // TODO 用jd-gui反编译ProxyClass.class文件\n    }\n\n    // -----------------------------------------------------------------------------------------------------------------------------\n    public static interface Echo {\n        String echo(String s);\n    }\n\n    public static class MountainEcho implements Echo {\n        @Override\n        public String echo(String saying) {\n            return saying + \" \" + saying + \" \" + saying + \" ...\";\n        }\n    }\n\n    public static class EchoInvocationHandler implements InvocationHandler {\n        private final Echo target;\n\n        public EchoInvocationHandler(Echo target) {\n            this.target = target;\n        }\n\n        @Override\n        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n            // proxy <= Proxy.newProxyInstance(loader, interfaces, h);\n            System.out.println(\"shout: \" + args[0]);\n            System.out.println(\"echo : \" + method.invoke(target, args) + \".\");\n            return \"return ((Echo) proxy).echo(\\\"hello\\\")\";\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/swing/SM2Crypto.java",
    "content": "package test.swing;\n\nimport java.awt.BorderLayout;\nimport java.awt.Color;\nimport java.awt.Font;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.util.Base64;\nimport java.util.Map;\n\nimport javax.swing.JFileChooser;\nimport javax.swing.JFrame;\nimport javax.swing.JLabel;\nimport javax.swing.JMenu;\nimport javax.swing.JMenuBar;\nimport javax.swing.JMenuItem;\nimport javax.swing.JOptionPane;\nimport javax.swing.JScrollPane;\nimport javax.swing.JTextArea;\nimport javax.swing.JTextField;\nimport javax.swing.JToolBar;\n\nimport cn.ponfee.commons.jce.ECParameters;\nimport cn.ponfee.commons.jce.sm.SM2;\n\npublic class SM2Crypto extends JFrame{\n    private static final long serialVersionUID = 1L;\n\n    Map<String, byte[]> map = SM2.generateKeyPair(ECParameters.SM2_BEST);\n    \n    StringBuffer str=new StringBuffer();\n    // 显示面板\n    JTextArea textArea; \n    // 文件URL输入栏\n    JTextField urlField=new JTextField();\n    public SM2Crypto() {\n        super(\"SM2加密\"); \n\n        /*// 新建显示HTML的面板，并设置它不可编辑\n        textPane = new JEditorPane(); */\n        //textPane.setEditable(false);\n\n        // 初始化菜单和工具栏\n        this.initMenu();\n        this.initToolbar();\n        textArea=new JTextArea();\n        textArea.setTabSize(4);\n        textArea.setFont(new Font(\"标楷体\", Font.BOLD, 16));\n        textArea.setLineWrap(true);// 激活自动换行功能\n        textArea.setWrapStyleWord(true);// 激活断行不断字功能\n        textArea.setBackground(Color.white);\n        \n        \n        // 将HTML显示面板放入主窗口，居中显示\n        this.add(new JScrollPane(textArea),BorderLayout.CENTER);\n    }\n    private void initToolbar() {\n        // 输入网址的文本框\n        urlField = new JTextField();\n        JToolBar toolbar = new JToolBar();\n\n        // 地址标签\n        toolbar.add(new JLabel(\"         地址：\"));\n        toolbar.add(urlField);\n        // 将工具栏放在主窗口的北部\n        this.getContentPane().add(toolbar, BorderLayout.NORTH);\n    }\n    private void initMenu() {\n        // 文件菜单，下面有两个菜单项：打开、退出\n        JMenu fileMenu = new JMenu(\"文件\");\n        JMenuItem openMenuItem = new JMenuItem(\"打开\");\n\n        openMenuItem.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                JFileChooser jfc=new JFileChooser();  \n                jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES );  \n                jfc.showDialog(new JLabel(), \"选择\");  \n                File file=jfc.getSelectedFile();  \n                if(file.isDirectory()){  \n                    urlField.setText(\"文件夹:\"+file.getAbsolutePath());  \n                }else if(file.isFile()){  \n                    urlField.setText(\"文件:\"+file.getAbsolutePath());  \n                }  \n                //建立数据的输入通道\n                FileInputStream fileInputStream = null;\n                try {\n                    fileInputStream = new FileInputStream(file);\n                } catch (FileNotFoundException e2) {\n                    e2.printStackTrace();\n                }\n                //建立缓冲数组配合循环读取文件的数据。\n                int length = 0; //保存每次读取到的字节个数。\n                byte[] buf = new byte[1024]; //存储读取到的数据    缓冲数组 的长度一般是1024的倍数，因为与计算机的处理单位。  理论上缓冲数组越大，效率越高\n                try {\n                    while((length = fileInputStream.read(buf))!=-1){ // read方法如果读取到了文件的末尾，那么会返回-1表示。\n                        try {\n                            str.append(new String(buf,0,length));   \n                            textArea.append(new String(buf,0,length));\n                            \n                        } catch (Exception e1) {\n                            e1.printStackTrace();\n                        }\n                        \n                    }\n                } catch (IOException e1) {\n                    e1.printStackTrace();\n                }\n            }\n        });\n\n        JMenuItem exitMenuItem = new JMenuItem(\"退出\");\n\n        // 当“退出”时退出应用程序\n        exitMenuItem.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                exit();\n            }\n        });\n\n        fileMenu.add(openMenuItem);\n        fileMenu.add(exitMenuItem);\n\n        //帮助菜单，就一个菜单项：关于\n        JMenu helpMenu = new JMenu(\"帮助\");\n        JMenu doMenu = new JMenu(\"操作\");\n        \n        JMenuItem pItem = new JMenuItem(\"加密\");\n        pItem.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                textArea.setText(Base64.getEncoder().encodeToString(SM2.encrypt(ECParameters.SM2_BEST, map.get(SM2.PUBLIC_KEY), urlField.getText().getBytes())));\n            }\n        });\n        JMenuItem cItem = new JMenuItem(\"解密\");\n        cItem.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                urlField.setText(new String(SM2.decrypt(ECParameters.SM2_BEST, map.get(SM2.PRIVATE_KEY), Base64.getDecoder().decode(textArea.getText()))));\n            }\n        });\n        doMenu.add(pItem);\n        doMenu.add(cItem);\n        JMenuItem aboutMenuItem = new JMenuItem(\"关于\");\n        helpMenu.add(aboutMenuItem);\n        JMenuBar menuBar = new JMenuBar();\n        menuBar.add(fileMenu);\n        menuBar.add(doMenu);\n        menuBar.add(helpMenu);\n        \n        \n        // 将菜单栏添加到主窗口\n        this.setJMenuBar(menuBar);\n\n    }\n    public void exit() {\n        // 弹出对话框，请求确认，如果确认退出，则退出应用程序\n        if ((JOptionPane.showConfirmDialog(this, \"你确定退出SM2加密器？\", \"退出\",\n                JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)){\n            System.exit(0);\n        }\n    }\n    public static void main(String[] args) {\n        // 设置浏览器，当所有浏览器窗口都被关闭时，退出应用程序\n\n        SM2Crypto window = new SM2Crypto(); \n        window.setSize(800, 600);\n        // 显示窗口\n        window.setVisible(true); \n\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/swing/WebBrowser.java",
    "content": "package test.swing;\nimport java.awt.BorderLayout;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.WindowAdapter;\nimport java.awt.event.WindowEvent;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javax.swing.JButton;\nimport javax.swing.JEditorPane;\nimport javax.swing.JFileChooser;\nimport javax.swing.JFrame;\nimport javax.swing.JLabel;\nimport javax.swing.JMenu;\nimport javax.swing.JMenuBar;\nimport javax.swing.JMenuItem;\nimport javax.swing.JOptionPane;\nimport javax.swing.JScrollPane;\nimport javax.swing.JTextField;\nimport javax.swing.JToolBar;\nimport javax.swing.event.HyperlinkEvent;\nimport javax.swing.event.HyperlinkListener;\nimport javax.swing.filechooser.FileFilter;\n\npublic class WebBrowser extends JFrame implements HyperlinkListener,\n        PropertyChangeListener {                          //超文本监听，属性改变监听\n    private static final long serialVersionUID = -7123717969723532732L;\n    // 显示HTML的面板\n    JEditorPane textPane; \n    // 最底下的状态栏\n    JLabel messageLine; \n    // 网址URL输入栏\n    JTextField urlField;\n    // 文件选择器\n    JFileChooser fileChooser;\n    // 后退和前进 按钮\n    JButton backButton;\n    JButton forwardButton;\n \n    // 保存历史记录的列表\n    List<URL> history = new ArrayList<>(); \n    // 在历史记录列表中位置\n    int currentHistoryPage = -1;  \n    // 历史记录超过MAX_HISTORY时，清除旧的历史\n    public static final int MAX_HISTORY = 50;\n \n    // 当前已经打开的浏览器窗口数\n    static int numBrowserWindows = 0;\n    // 标识当所有浏览器窗口都被关闭时，是否退出应用程序\n    static boolean exitWhenLastWindowClosed = false;\n \n    // 默认主页\n    String home = \"http://www.baidu.com\";\n \n   \n    public WebBrowser() {\n        super(\"WebBrowser\"); \n \n        // 新建显示HTML的面板，并设置它不可编辑\n        textPane = new JEditorPane(); \n        textPane.setEditable(false); \n \n        // 注册事件处理器，用于超连接事件。\n        textPane.addHyperlinkListener(this);\n        // 注册事件处理器，用于处理属性改变事件。当页面加载结束时，触发该事件\n        textPane.addPropertyChangeListener(this);\n \n        // 将HTML显示面板放入主窗口，居中显示\n        this.getContentPane().add(new JScrollPane(textPane),BorderLayout.CENTER);\n \n        // 创建状态栏标签，并放在主窗口底部\n        messageLine = new JLabel(\" \");\n        this.getContentPane().add(messageLine, BorderLayout.SOUTH);\n \n        // 初始化菜单和工具栏\n        this.initMenu();\n        this.initToolbar();\n         \n        // 将当前打开窗口数增加1\n        WebBrowser.numBrowserWindows++;\n         \n        // 当关闭窗口时，调用close方法处理，采用匿名内部类的方式窗口添加监听\n        this.addWindowListener(new WindowAdapter() {\n            public void windowClosing(WindowEvent e) {\n                close();\n            }\n        });\n    }\n     \n   \n    private void initMenu(){\n         \n        // 文件菜单，下面有四个菜单项：新建、打开、关闭窗口、退出\n        JMenu fileMenu = new JMenu(\"文件\");\n        JMenuItem newMenuItem = new JMenuItem(\"新建\");\n       \n        newMenuItem.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                newBrowser();\n            }\n        });\n         \n        JMenuItem openMenuItem = new JMenuItem(\"打开\");\n      \n        \n        openMenuItem.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                openLocalPage();\n            }\n        });\n        JMenuItem closeMenuItem = new JMenuItem(\"关闭窗口\");\n    \n        closeMenuItem.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                close();\n            }\n        });\n        JMenuItem exitMenuItem = new JMenuItem(\"退出\");\n       \n        // 当“退出”时退出应用程序\n        exitMenuItem.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                exit();\n            }\n        });\n        fileMenu.add(newMenuItem);\n        fileMenu.add(openMenuItem);\n        fileMenu.add(closeMenuItem);\n        fileMenu.add(exitMenuItem);\n         \n        //帮助菜单，就一个菜单项：关于\n        JMenu helpMenu = new JMenu(\"帮助\");\n      \n        JMenuItem aboutMenuItem = new JMenuItem(\"关于\");\n      \n        helpMenu.add(aboutMenuItem);\n         \n        JMenuBar menuBar = new JMenuBar();\n        menuBar.add(fileMenu);\n        menuBar.add(helpMenu);\n         \n        // 将菜单栏添加到主窗口\n        this.setJMenuBar(menuBar);\n    }\n     \n    \n    private void initToolbar(){\n        // 后退按钮，退到前一页面。初始情况下该按钮不可用\n        backButton = new JButton(\"后退\");\n        backButton.setEnabled(false);     //刚打开时按钮不可用\n        backButton.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                back();\n            }\n        });\n        // 前进按钮，进到前一页面。初始情况下该按钮不可用\n        forwardButton = new JButton(\"前进\");\n        forwardButton.setEnabled(false);\n        forwardButton.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                forward();\n            }\n        });\n        // 前进按钮，进到前一页面。\n        JButton refreshButton = new JButton(\"刷新\");\n        refreshButton.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                reload();\n            }\n        });\n         \n        // 主页按钮，打开主页\n        JButton homeButton = new JButton(\"主页\");\n        homeButton.addActionListener(new ActionListener() {\n            public void actionPerformed(ActionEvent e) {\n                home();\n            }\n        });\n        JToolBar toolbar = new JToolBar();\n        toolbar.add(backButton);\n        toolbar.add(forwardButton);\n        toolbar.add(refreshButton);\n        toolbar.add(homeButton);\n \n        // 输入网址的文本框\n        urlField = new JTextField();\n        // 当用户输入网址、按下回车键时，触发该事件\n        urlField.addActionListener(new ActionListener() {\n                public void actionPerformed(ActionEvent e) {\n                    displayPage(urlField.getText());\n                }\n            });\n \n        // 地址标签\n        toolbar.add(new JLabel(\"         地址：\"));\n        toolbar.add(urlField);\n        // 将工具栏放在主窗口的北部\n        this.getContentPane().add(toolbar, BorderLayout.NORTH);\n    }\n \n    /**\n     * 设置浏览器是否在所有窗口都关闭时退出\n     * @param b\n     */\n    public static void setExitWhenLastWindowClosed(boolean b) {\n        exitWhenLastWindowClosed = b;\n    }\n \n    public void setHome(String home) {\n        this.home = home;\n    }\n    /**\n     * 获取主页\n     */\n    public String getHome() {\n        return home;\n    }\n    \n    /**\n     * FIXME 访问网址URL\n     */\n    private boolean visit(URL url) {\n        try {\n            String href = url.toString();\n            // 启动动画，当网页被加载完成时，触发propertyChanged事件，动画停止。\n            startAnimation(\"加载 \" + href + \"...\");\n             \n            // 设置显示HTML面板的page属性为待访问的URL，\n            // 在这个方法里，将打开URL，将URL的流显示在textPane中。\n            // 当完全打开后，该方法才结束。.\n            /*String vNewReportFileName = \"D:\\\\temp.html\";\n\n            File f = new File(vNewReportFileName);\n            FileWriter fw = new FileWriter(f, false);\n            System.out.println(href);\n            \n            String html=new Httpcli().visit(href);\n            fw.write(new String(html.getBytes(\"iso-8859-1\"),\"gbk\"));\n            fw.flush();\n            fw.close();\n            textPane.setPage(new URL(\"file:///\"+vNewReportFileName)); */\n            textPane.setPage(url);\n            // 页面打开后，将浏览器窗口的标题设为URL\n            this.setTitle(href);  \n            // 网址输入框的内容也设置为URL\n            urlField.setText(href); \n            return true;\n        } catch (IOException ex) { \n            // 停止动画\n            stopAnimation();\n            // 状态栏中显示错误信息\n            messageLine.setText(\"不能打开页面：\" + ex.getMessage());\n            return false;\n        }\n    }\n \n    /**\n     * 浏览器打开URL指定的页面，如果成功，将URL放入历史列表中\n     */\n    public void displayPage(URL url) {\n        // 尝试访问页面\n        if (visit(url)) { \n            // 如果成功，则将URL放入历史列表中。\n            history.add(url); \n            int numentries = history.size();\n            if (numentries > MAX_HISTORY+10) { \n                history = history.subList(numentries-MAX_HISTORY, numentries);\n                numentries = MAX_HISTORY;\n            }\n            // 将当前页面下标置为numentries-1\n            currentHistoryPage = numentries - 1;\n            // 如果当前页面不是第一个页面，则可以后退，允许点击后退按钮。\n            if (currentHistoryPage > 0){\n                backButton.setEnabled(true);\n            }\n        }\n    }\n \n    /**\n     * 浏览器打开字符串指定的页面\n     * @param href  网址\n     */\n    public void displayPage(String href) {\n        try {\n            // 默认为HTTP协议\n            if (!href.startsWith(\"http://\")){\n                href = \"http://\" + href;\n            }\n            displayPage(new URL(href));\n        }\n        catch (MalformedURLException ex) {\n            messageLine.setText(\"错误的网址: \" + href);\n        }\n    }\n \n    /**\n     * 打开本地文件\n     */\n    public void openLocalPage() {\n        // 使用“懒创建”模式，当需要时，才创建文件选择器。\n        if (fileChooser == null) {\n            fileChooser = new JFileChooser();\n            // 使用文件过滤器限制只能够HTML和HTM文件\n            FileFilter filter = new FileFilter() {\n                    public boolean accept(File f) {\n                        String fn = f.getName();\n                        return fn.endsWith(\".html\") || fn.endsWith(\".htm\");\n                    }\n                    public String getDescription() { \n                        return \"HTML Files\"; \n                    }\n                };\n            fileChooser.setFileFilter(filter);\n            // 只允许选择HTML和HTM文件\n            fileChooser.addChoosableFileFilter(filter);\n        }\n \n        // 打开文件选择器\n        int result = fileChooser.showOpenDialog(this);\n        // 如果确定打开文件，则在当前窗口中打开选择的文件\n        if (result == JFileChooser.APPROVE_OPTION) {\n            File selectedFile = fileChooser.getSelectedFile( );\n            try {\n                displayPage(selectedFile.toURI().toURL());\n            } catch (MalformedURLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n    /**\n     * 后退，回到前一页\n     */\n    public void back() {\n        if (currentHistoryPage > 0){\n            // 访问前一页\n            visit(history.get(--currentHistoryPage));\n        }\n        // 如果当前页面下标大于0，允许后退\n        backButton.setEnabled((currentHistoryPage > 0));\n        // 如果当前页面下标不是最后，允许前进\n        forwardButton.setEnabled((currentHistoryPage < history.size()-1));\n    }\n    /**\n     * 前进，回到后一页\n     */\n    public void forward() {\n        if (currentHistoryPage < history.size( )-1){\n            visit(history.get(++currentHistoryPage));\n        }\n        // 如果当前页面下标大于0，允许后退\n        backButton.setEnabled((currentHistoryPage > 0));\n        // 如果当前页面下标不是最后，允许前进\n        forwardButton.setEnabled((currentHistoryPage < history.size()-1));\n    }\n  \n    public void reload() {\n        if (currentHistoryPage != -1) {\n            // 先显示为空白页\n            textPane.setDocument(new javax.swing.text.html.HTMLDocument());\n            // 再访问当前页\n            visit(history.get(currentHistoryPage));\n        }\n    }\n  \n    public void home() {\n        displayPage(getHome()); \n    }\n  \n    public void newBrowser() {\n        WebBrowser b = new WebBrowser();\n        // 新窗口大小和当前窗口一样大\n        b.setSize(this.getWidth(), this.getHeight());\n        b.setVisible(true);\n    }\n    /**\n     * 关闭当前窗口，如果所有窗口都关闭，则根据exitWhenLastWindowClosed属性，\n     * 判断是否退出应用程序\n     */\n    public void close() {\n        // 先隐藏当前窗口，销毁窗口中的组件。\n        this.setVisible(false);\n        this.dispose();\n        // 将当前打开窗口数减1。\n        // 如果所有窗口都已关闭，而且exitWhenLastWindowClosed为真，则退出\n        // 这里采用了synchronized关键字，保证线程安全\n        synchronized(WebBrowser.class) {    \n            WebBrowser.numBrowserWindows--; \n            if ((numBrowserWindows==0) && exitWhenLastWindowClosed){\n                System.exit(0);\n            }\n        }\n    }\n\n    public void exit() {\n        // 弹出对话框，请求确认，如果确认退出，则退出应用程序\n        if ((JOptionPane.showConfirmDialog(this, \"你确定退出Web浏览器？\", \"退出\",\n                JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)){\n            System.exit(0);\n        }\n    }\n    /**\n     * 实现HyperlinkListener接口。处理超连接事件\n     */\n    public void hyperlinkUpdate(HyperlinkEvent e) {\n        // 获取事件类型\n        HyperlinkEvent.EventType type = e.getEventType();\n        // 如果是点击了一个超连接，则显示被点击的连接\n        if (type == HyperlinkEvent.EventType.ACTIVATED) {\n            displayPage(e.getURL());\n        }\n        // 如果是鼠标移动到超连接上，则在状态栏中显示超连接的网址\n        else if (type == HyperlinkEvent.EventType.ENTERED) {\n            messageLine.setText(e.getURL().toString());  \n        }\n        // 如果是鼠标离开了超连接，则状态栏显示为空\n        else if (type == HyperlinkEvent.EventType.EXITED) { \n            messageLine.setText(\" \");\n        }\n    }\n \n    /**\n     * 实现PropertyChangeListener接口。处理属性改变事件。\n     * 显示HTML面板textPane的属性改变时，由该方法处理。当textPane调用完setPage方法时，page属性便改变了。\n     */\n    public void propertyChange(PropertyChangeEvent e) {\n        if (e.getPropertyName().equals(\"page\")) {\n            // 页面加载完毕时，textPane的page属性发生改变，此时停止动画。\n            stopAnimation();\n        }\n    }\n \n    // 动画消息，显示在最底下状态栏标签上，用于反馈浏览器的状态\n    String animationMessage;\n    // 动画当前的帧的索引\n    int animationFrame = 0;\n    // 动画所用到的帧，是一些字符。\n    String[] animationFrames = new String[] {\n        \"-\", \"\\\\\", \"|\", \"/\", \"-\", \"\\\\\", \"|\", \"/\", \n        \",\", \".\", \"o\", \"0\", \"O\", \"#\", \"*\", \"+\"\n    };\n \n    //定时器\n    javax.swing.Timer animator =\n        new javax.swing.Timer(125, new ActionListener() {\n                public void actionPerformed(ActionEvent e) {\n                    animate(); \n                }\n            });\n \n  \n    private void animate() {\n        String frame = animationFrames[animationFrame++];\n        messageLine.setText(animationMessage + \" \" + frame);\n        animationFrame = animationFrame % animationFrames.length;\n    }\n \n  \n    private void startAnimation(String msg) {\n        animationMessage = msg;\n        animationFrame = 0; \n        // 启动定时器\n        animator.start();\n    }\n \n \n    private void stopAnimation() {  \n        // 停止定时器\n        animator.stop();\n        messageLine.setText(\" \");\n    }\n     \n    public static void main(String[] args) {\n        // 设置浏览器，当所有浏览器窗口都被关闭时，退出应用程序\n        WebBrowser.setExitWhenLastWindowClosed(true);\n        WebBrowser browser = new WebBrowser(); \n        browser.setSize(800, 600);\n        // 显示窗口\n        browser.setVisible(true); \n        // 打开主页\n        browser.displayPage(browser.getHome());\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/tree/NodePathSerialTest.java",
    "content": "package test.tree;\n\nimport cn.ponfee.commons.collect.ImmutableArrayList;\nimport cn.ponfee.commons.serial.JdkSerializer;\nimport cn.ponfee.commons.serial.KryoSerializer;\nimport cn.ponfee.commons.tree.NodePath;\nimport com.google.common.collect.ImmutableList;\nimport org.junit.Ignore;\nimport org.junit.Test;\n\n/**\n * @author Ponfee\n */\npublic class NodePathSerialTest {\n\n    /**\n     * add method UnsupportedOperationException\n     */\n    @Test @Ignore\n    public void test1() {\n        byte[] serialized1 = KryoSerializer.INSTANCE.serialize(new ImmutableArrayList<>(new Integer[]{1, 2, 3, 4}));\n        System.out.println(KryoSerializer.INSTANCE.deserialize(serialized1, ImmutableArrayList.class));\n\n        byte[] serialized2 = KryoSerializer.INSTANCE.serialize(new NodePath<>(new Integer[]{1, 2, 3, 4}));\n        System.out.println(KryoSerializer.INSTANCE.deserialize(serialized2, NodePath.class));\n    }\n\n    @Test\n    public void test2() {\n        JdkSerializer jdkSerializer = new JdkSerializer();\n        byte[] serialized1 = jdkSerializer.serialize(new ImmutableArrayList<>(new Integer[]{1, 2, 3, 4}));\n        System.out.println(jdkSerializer.deserialize(serialized1, ImmutableArrayList.class).toArray().getClass());\n\n        byte[] serialized2 = jdkSerializer.serialize(new NodePath<>(new Integer[]{1, 2, 3, 4}));\n        System.out.println(jdkSerializer.deserialize(serialized2, NodePath.class).toArray().getClass());\n    }\n\n    @Test\n    public void test3() {\n        JdkSerializer jdkSerializer = new JdkSerializer();\n        byte[] serialized1 = jdkSerializer.serialize(ImmutableList.of(1,2,3));\n        System.out.println(jdkSerializer.deserialize(serialized1, ImmutableList.class).getClass());\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/tree/NodePathTest.java",
    "content": "package test.tree;\n\nimport cn.ponfee.commons.base.tuple.Tuple2;\nimport cn.ponfee.commons.collect.ImmutableArrayList;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.tree.NodePath;\nimport cn.ponfee.commons.tree.NodePath.FastjsonDeserializer;\nimport cn.ponfee.commons.util.Asserts;\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.annotation.JSONField;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Lists;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.collections4.ListUtils;\nimport org.junit.Assert;\nimport org.junit.Ignore;\nimport org.junit.Test;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author Ponfee\n */\npublic class NodePathTest {\n\n    @Test\n    public void test0() {\n        System.out.println(ImmutableList.of(new Integer[]{1,2}).get(0).getClass());\n        System.out.println(ImmutableArrayList.of(new Integer[]{1,2}).get(0).getClass());\n\n\n        for (Object o : Tuple2.of(1, 2)) {\n            System.out.println(o);\n        }\n\n        System.out.println();\n        System.out.println(new NodePath<>(1, 2, 3, 4));\n        System.out.println(new NodePath<>(new Integer[]{1, 2, 3, 4}, 5));\n\n        System.out.println(new NodePath<>(Arrays.asList(1, 2, 3)));\n        System.out.println(new NodePath<>(Arrays.asList(1, 2, 3), 4));\n\n        NodePath<Integer> ids = new NodePath<>(1, 2, 3, 4);\n        System.out.println(new NodePath<>(ids));\n        System.out.println(new NodePath<>(ids, 1));\n    }\n\n    @Test\n    public void test1() {\n        System.out.println(CollectionUtils.isEqualCollection(Arrays.asList(1, 2, 3), Arrays.asList(3, 2, 1)));\n        System.out.println(ListUtils.isEqualList(Arrays.asList(1, 2, 3), Arrays.asList(3, 2, 1)));\n        System.out.println(ListUtils.isEqualList(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3)));\n    }\n\n    @Test\n    public void test2() {\n        NodePath<Integer> p1 = new NodePath<>(1, 2, 3, 4);\n        NodePath<Integer> p2 = new NodePath<>(1, 2, 3, 4);\n        System.out.println(p1.equals(p2));\n        System.out.println(p1.compareTo(p2));\n\n        System.out.println(\"\\n\\n=========================\");\n        p1 = new NodePath<>(1, 2, 3, 4);\n        p2 = new NodePath<>(1, 2);\n        System.out.println(p1.equals(p2));\n        System.out.println(p1.compareTo(p2));\n\n        System.out.println(\"\\n\\n=========================\");\n        p1 = new NodePath<>(1, 2, 3, 4);\n        p2 = new NodePath<>(4, 2);\n        System.out.println(p1.equals(p2));\n        System.out.println(p1.compareTo(p2));\n    }\n\n    @Test\n    public void test3() {\n        Map<NodePath<Integer>, Object> map = new HashMap<>();\n        map.put(new NodePath<>(1, 2, 3, 4), \"xx\");\n        Assert.assertNotNull(map.get(new NodePath<>(1, 2, 3, 4)));\n        Assert.assertNull(map.get(new NodePath<>(1, 2, 3)));\n    }\n\n\n    @Test\n    public void test4() {\n        System.out.println(\"\\n\\n========================test4\");\n        List<Integer> list = Lists.newArrayList(1, 2, 3, 4);\n        String json = Jsons.toJson(list);\n        System.out.println(json);\n        System.out.println(Jsons.fromJson(json, List.class).get(0).getClass());\n        System.out.println(JSON.parseArray(json).get(0).getClass());\n    }\n\n    @Test\n    public void test5() {\n        System.out.println(\"\\n\\n========================test5\");\n        NodePath<Integer> ids = new NodePath<>(1, 2, 3, 4);\n        String json = Jsons.toJson(ids);\n        System.out.println(json);\n        Asserts.isTrue(json.equals(JSON.toJSONString(ids)));\n        System.out.println(Jsons.fromJson(json, NodePath.class).get(0).getClass());\n        System.out.println(JSON.parseObject(json, NodePath.class).get(0).getClass());\n        System.out.println(JSON.parseObject(\"[\\\"a\\\",\\\"b\\\"]\", NodePath.class).get(0).getClass());\n    }\n\n    // --------------------------------------------------------------\n    private static final String DATA = \"{\\\"id\\\":123,\\\"path\\\":[1,2,3,4]}\";\n\n    @Test\n    public void test6() {\n        System.out.println(Jsons.fromJson(DATA, NodePathBean1.class).getPath());\n        System.out.println(JSON.parseObject(DATA, NodePathBean1.class).getPath());\n    }\n\n    @Test\n    public void test7() {\n        System.out.println(Jsons.fromJson(DATA, NodePathBean2.class).getPath());\n        System.out.println(JSON.parseObject(DATA, NodePathBean2.class).getPath());\n    }\n\n    @Test\n    public void test8() {\n        System.out.println(Jsons.fromJson(DATA, NodePathBean3.class).getPath());\n        System.out.println(JSON.parseObject(DATA, NodePathBean3.class).getPath());\n    }\n\n    // -----------------------------------------------------------ERROR\n    @Test\n    public void test10() {\n        System.out.println(Jsons.fromJson(DATA, NodePathBean5.class).getPath().getClass()); // class java.util.ArrayList\n        System.out.println(JSON.parseObject(DATA, NodePathBean5.class).getPath().getClass()); // class java.util.ArrayList\n    }\n\n    @Test\n    public void test11() {\n        System.out.println(Jsons.fromJson(DATA, NodePathBean6.class).getPath());\n        System.out.println(JSON.parseObject(DATA, NodePathBean6.class).getPath());\n    }\n\n    @Test @Ignore\n    public void test12() {\n        System.out.println(Jsons.fromJson(DATA, NodePathBean4.class).getPath());\n        System.out.println(JSON.parseObject(DATA, NodePathBean4.class).getPath()); // add method UnsupportedOperationException\n    }\n\n    /**\n     * add method UnsupportedOperationException\n     */\n    @Test @Ignore\n    public void test13() {\n        System.out.println(Jsons.fromJson(DATA, NodePathBean7.class).getPath());\n        System.out.println(JSON.parseObject(DATA, NodePathBean7.class).getPath());\n    }\n\n    /**\n     * ERROR non-concrete Collection\n     */\n    @Test @Ignore\n    public void test14() {\n        System.out.println(ImmutableList.of().getClass());\n        System.out.println(ImmutableList.of(1).getClass());\n        System.out.println(ImmutableList.of(1,2).getClass());\n        System.out.println(Jsons.fromJson(\"[1,2,3,4]\", ImmutableList.class));\n        System.out.println(JSON.parseObject(\"[1,2,3,4]\", ImmutableList.class));\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    public static class NodePathBean1 implements java.io.Serializable {\n        private static final long serialVersionUID = 1L;\n        private int id;\n        private NodePath path;\n\n        public int getId() {\n            return id;\n        }\n\n        public void setId(int id) {\n            this.id = id;\n        }\n\n        public NodePath getPath() {\n            return path;\n        }\n\n        public void setPath(NodePath path) {\n            this.path = path;\n        }\n    }\n\n    public static class NodePathBean2<T extends Serializable & Comparable<T>> implements java.io.Serializable {\n        private static final long serialVersionUID = 1L;\n        private int id;\n        private NodePath<T> path; //\n\n        public int getId() {\n            return id;\n        }\n\n        public void setId(int id) {\n            this.id = id;\n        }\n\n        public NodePath<T> getPath() {\n            return path;\n        }\n\n        public void setPath(NodePath<T> path) {\n            this.path = path;\n        }\n    }\n\n    public static class NodePathBean3 implements java.io.Serializable {\n        private static final long serialVersionUID = 1L;\n        private int id;\n        @JSONField(deserializeUsing = FastjsonDeserializer.class)\n        private NodePath<Integer> path;\n\n        public int getId() {\n            return id;\n        }\n\n        public void setId(int id) {\n            this.id = id;\n        }\n\n        public NodePath<Integer> getPath() {\n            return path;\n        }\n\n        public void setPath(NodePath<Integer> path) {\n            this.path = path;\n        }\n    }\n\n    public static class NodePathBean4 implements java.io.Serializable {\n        private static final long serialVersionUID = 1L;\n        private int id;\n        // FIXME ERROR\n        // 当字段有泛型参数时的类型信息type为ParameterizedType，所以必须用JSONField注解，\n        // 否则当成Collection来解析（此字段的类型不是NodePath，而是ParameterizedType）\n        private NodePath<Integer> path;\n\n        public int getId() {\n            return id;\n        }\n\n        public void setId(int id) {\n            this.id = id;\n        }\n\n        public NodePath<Integer> getPath() {\n            return path;\n        }\n\n        public void setPath(NodePath<Integer> path) {\n            this.path = path;\n        }\n    }\n\n    public static class NodePathBean5 implements java.io.Serializable {\n        private static final long serialVersionUID = 1L;\n        private int id;\n        private List<Integer> path;\n\n        public int getId() {\n            return id;\n        }\n\n        public void setId(int id) {\n            this.id = id;\n        }\n\n        public List<Integer> getPath() {\n            return path;\n        }\n\n        public void setPath(List<Integer> path) {\n            this.path = path;\n        }\n    }\n\n    public static class NodePathBean6 implements java.io.Serializable {\n        private static final long serialVersionUID = 1L;\n        private int id;\n        private ArrayList<Integer> path;\n\n        public int getId() {\n            return id;\n        }\n\n        public void setId(int id) {\n            this.id = id;\n        }\n\n        public ArrayList<Integer> getPath() {\n            return path;\n        }\n\n        public void setPath(ArrayList<Integer> path) {\n            this.path = path;\n        }\n    }\n\n    public static class NodePathBean7 implements java.io.Serializable {\n        private static final long serialVersionUID = 1L;\n        private int id;\n        private ImmutableArrayList<Integer> path;\n\n        public int getId() {\n            return id;\n        }\n\n        public void setId(int id) {\n            this.id = id;\n        }\n\n        public ImmutableArrayList<Integer> getPath() {\n            return path;\n        }\n\n        public void setPath(ImmutableArrayList<Integer> path) {\n            this.path = path;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/tree/NodeTreeTest.java",
    "content": "package test.tree;\n\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.tree.*;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\n\n/**\n *\n * @author Ponfee\n */\npublic class NodeTreeTest {\n\n    @Test\n    public void test1() {\n        List<BaseNode<String, String>> list = new ArrayList<>();\n        list.add(new PlainNode<>(\"100000\", null, true, \"nid100000\"));\n        list.add(new PlainNode<>(\"100010\", \"100000\", true, \"nid100010\"));\n        list.add(new PlainNode<>(\"100011\", \"100010\", false, \"nid100011\"));\n        list.add(new PlainNode<>(\"100012\", \"100010\", true, \"nid100012\"));\n        list.add(new PlainNode<>(\"100020\", \"100000\", false, \"nid100020\"));\n        list.add(new PlainNode<>(\"100021\", \"100020\", true, \"nid100020\"));\n        list.add(new PlainNode<>(\"100022\", \"100020\", false, \"nid100022\"));\n\n        list.add(new PlainNode<>(\"200000\", null, true, \"nid200000\"));\n\n        list.add(new PlainNode<>(\"300000\", null, true, \"nid300000\"));\n\n        list.add(new PlainNode<>(\"400000\", null, true, \"nid400000\"));\n\n        // do mount first\n        TreeNode<String, String> subtree = TreeNode.<String, String>builder(\"400010\")\n            .siblingNodesComparator(Comparator.comparing(node -> ThreadLocalRandom.current().nextInt(10)))\n            .pid(\"400000\").enabled(true).build();\n\n        // do mount second\n        subtree.mount(Arrays.asList(\n            new PlainNode<>(\"400011\", \"400010\", true, \"nid400011\"),\n            new PlainNode<>(\"400012\", \"400010\", false, \"nid400012\")\n        ));\n        list.add(subtree); // add a tree node\n\n        list.add(new PlainNode<>(\"500000\", null, false, \"nid500000\"));\n        list.add(new PlainNode<>(\"500010\", \"500000\", true, \"nid500010\"));\n        list.add(new PlainNode<>(\"500011\", \"500010\", true, \"nid500011\"));\n\n        // do mount third\n        SiblingNodesComparator.comparing(e -> e.getChildren().size(), false, true).thenComparing(TreeNode::getNid, false, true).get();\n\n        TreeNode<String, String> root = TreeNode.builder(TreeNode.DEFAULT_ROOT_ID)\n            .siblingNodesComparator(SiblingNodesComparator.comparing(e -> e.getChildren().size(),false, false).thenComparing(TreeNode::getNid, false, false).get())\n            .build();\n\n        // do mount fourth\n        root.mount(list); // mount\n        System.out.println(\"sorted: \" + Jsons.toJson(root));\n        System.out.println(\"dfs: \" + Jsons.toJson(root.flatDFS()));\n        System.out.println(\"cfs: \" + Jsons.toJson(root.flatCFS()));\n        System.out.println(\"convert-true: \" + Jsons.toJson(root.convert(this::convert, true)));\n        System.out.println(\"convert-false: \" + Jsons.toJson(root.convert(this::convert, false)));\n    }\n\n    private MapTreeTrait<String, String> convert(TreeNode<String, String> node) {\n        MapTreeTrait<String, String> map = new MapTreeTrait<>();\n        map.put(\"nid\", node.getNid());\n        map.put(\"pid\", node.getPid());\n        map.put(\"attach\", node.getAttach());\n        map.put(\"path\", node.getPath());\n        map.put(\"enabled\", node.isEnabled());\n        map.put(\"available\", node.isAvailable());\n        return map;\n    }\n\n    @Test\n    public void test2() {\n        List<BaseNode<String, String>> list = new ArrayList<>();\n        list.add(new PlainNode<>(\"a\", \"b\", true, \"\")); // 节点循环依赖\n        list.add(new PlainNode<>(\"b\", \"a\", true, \"\"));\n\n        TreeNode<String, String> root = TreeNode.<String, String> builder(TreeNode.DEFAULT_ROOT_ID).build();\n        Assert.assertThrows(RuntimeException.class, ()->root.mount(list)) ;\n        System.out.println(Jsons.toJson(root));\n    }\n\n    @Test\n    public void test3() {\n        List<BaseNode<String, String>> list = new ArrayList<>();\n        list.add(new PlainNode<>(\"100001\", null, true, \"nid100010\"));\n        Assert.assertThrows(IllegalArgumentException.class, () -> list.add(new PlainNode<>(null, \"100001\", true, \"nid100010\")));// 节点编号不能为空\n        TreeNode<String, String> root = TreeNode.<String, String> builder(TreeNode.DEFAULT_ROOT_ID).build();\n        root.mount(list);\n        System.out.println(Jsons.toJson(root));\n    }\n\n    @Test\n    public void test4() {\n        List<BaseNode<String, String>> list = new ArrayList<>();\n        list.add(new PlainNode<>(\"100000\", \"notfound\", true, \"nid100000\")); // 无效的孤儿节点\n        list.add(new PlainNode<>(\"200000\", \"notfound\", true, \"nid200000\")); // 无效的孤儿节点\n\n        Assert.assertThrows(RuntimeException.class, () -> TreeNode.<String, String>builder(TreeNode.DEFAULT_ROOT_ID).build().mount(list));\n    }\n\n    @Test\n    public void test5() {\n        List<BaseNode<String, String>> list = new ArrayList<>();\n        list.add(new PlainNode<>(\"100000\", null, true, null));\n        list.add(new PlainNode<>(\"100010\", \"100000\", true, null));\n        list.add(new PlainNode<>(\"100011\", \"100010\", false, random()));\n        list.add(new PlainNode<>(\"100012\", \"100010\", true, random()));\n        list.add(new PlainNode<>(\"100020\", \"100000\", false, random()));\n        list.add(new PlainNode<>(\"100021\", \"100020\", true, random()));\n        list.add(new PlainNode<>(\"100022\", \"100020\", false, random()));\n\n        list.add(new PlainNode<>(\"200000\", null, true, random()));\n\n        list.add(new PlainNode<>(\"300000\", null, true, random()));\n\n        list.add(new PlainNode<>(\"400000\", null, true, random()));\n\n        // do mount first\n        TreeNode<String, String> subtree = TreeNode.<String, String> builder(\"400010\").pid(\"400000\").build();\n\n        // do mount second\n        subtree.mount(Arrays.asList(\n            new PlainNode<>(\"400011\", \"400010\", true, \"nid400011\"),\n            new PlainNode<>(\"400012\", \"400010\", false, \"nid400012\")\n        ));\n        list.add(subtree); // add a tree node\n\n        list.add(new PlainNode<>(\"500000\", null, false, random()));\n        list.add(new PlainNode<>(\"500010\", \"500000\", true, random()));\n        list.add(new PlainNode<>(\"500011\", \"500010\", true, random()));\n\n        // do mount third\n        TreeNode<String, String> root = TreeNode.<String, String>builder(TreeNode.DEFAULT_ROOT_ID).build();\n        System.out.println(Jsons.toJson(root));\n\n        // do mount fouth\n        root.mount(list); // mount\n        System.out.println(Jsons.toJson(root));\n        System.out.println(Jsons.toJson(root.flatDFS()));\n        System.out.println(Jsons.toJson(root.flatCFS()));\n    }\n\n    private String random() {\n        String[] s = new String[] { \"a\", null, \"b\", null, \"c\", \"d\", null };\n        return s[ThreadLocalRandom.current().nextInt(s.length)];\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/tree/TreeNodePrinterTest.java",
    "content": "package test.tree;\n\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.export.Thead;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.tree.*;\nimport cn.ponfee.commons.tree.print.BinaryTreePrinter;\nimport cn.ponfee.commons.tree.print.BinaryTreePrinterBuilder;\nimport cn.ponfee.commons.util.MavenProjects;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.junit.Test;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.ThreadLocalRandom;\n\n/**\n *\n * @author Ponfee\n */\npublic class TreeNodePrinterTest {\n\n    @Test\n    public void test1() throws IOException {\n        List<BaseNode<String, String>> list = new ArrayList<>();\n        list.add(new PlainNode<>(\"100000\", null, true, \"nid100000\"));\n        list.add(new PlainNode<>(\"100010\", \"100000\", true, \"nid100010\"));\n        list.add(new PlainNode<>(\"100011\", \"100010\", false, \"nid100011\"));\n        list.add(new PlainNode<>(\"100012\", \"100010\", true, \"nid100012\"));\n        list.add(new PlainNode<>(\"100020\", \"100000\", false, \"nid100020\"));\n        list.add(new PlainNode<>(\"100021\", \"100020\", true, \"nid100020\"));\n        list.add(new PlainNode<>(\"100022\", \"100020\", false, \"nid100022\"));\n\n        list.add(new PlainNode<>(\"200000\", null, true, \"nid200000\"));\n\n        list.add(new PlainNode<>(\"300000\", null, true, \"nid300000\"));\n\n        list.add(new PlainNode<>(\"400000\", null, true, \"nid400000\"));\n\n        // do mount first\n        TreeNode<String, String> subtree = TreeNode.<String, String>builder(\"400010\")\n            .siblingNodesComparator(Comparator.comparing(node -> ThreadLocalRandom.current().nextInt(10)))\n            .pid(\"400000\")\n            .enabled(true)\n            .build();\n\n        // do mount second\n        subtree.mount(Arrays.asList(\n            new PlainNode<>(\"400011\", \"400010\", true, \"nid400011\"),\n            new PlainNode<>(\"400012\", \"400010\", false, \"nid400012\")\n        ));\n        list.add(subtree); // add a tree node\n\n        list.add(new PlainNode<>(\"500000\", null, false, \"nid500000\"));\n        list.add(new PlainNode<>(\"500010\", \"500000\", true, \"nid500010\"));\n        list.add(new PlainNode<>(\"500011\", \"500010\", true, \"nid500011\"));\n\n        // do mount third\n        TreeNode<String, String> root = TreeNode.<String, String> builder(TreeNode.DEFAULT_ROOT_ID).build();\n        System.out.println(\"unmount: \"+Jsons.toJson(root));\n\n        // do mount fouth\n        root.mount(list); // mount\n        System.out.println(\"mounted: \"+Jsons.toJson(root));\n        System.out.println(\"dfs: \"+Jsons.toJson(root.flatDFS()));\n        System.out.println(\"cfs: \"+Jsons.toJson(root.flatCFS()));\n        System.out.println(\"bfs: \"+Jsons.toJson(root.flatBFS()));\n        System.out.println(\"convert-true: \"  + Jsons.toJson(root.convert(this::convert, true)));\n        System.out.println(\"convert-false: \" + Jsons.toJson(root.convert(this::convert, false)));\n\n        System.out.println(\"\\n\\n-----------------\\n\\n\");\n        System.out.println(root.print(TreeNode::getNid));\n    }\n\n    @Test\n    public void test2() throws IOException {\n        List<BaseNode<Integer, Thead>> list = new ArrayList<>();\n\n        list.add(new PlainNode<>(1, 0, new Thead(\"区域\")));\n        list.add(new PlainNode<>(2, 0, new Thead(\"分公司\")));\n\n        list.add(new PlainNode<>(3, 0, new Thead(\"昨天\")));\n        list.add(new PlainNode<>(4, 3, new Thead(\"项目数\")));\n        list.add(new PlainNode<>(5, 3, new Thead(\"项目应收(元)\")));\n        list.add(new PlainNode<>(6, 3, new Thead(\"成交套数\")));\n        list.add(new PlainNode<>(7, 3, new Thead(\"套均收入(元)\")));\n        list.add(new PlainNode<>(8, 3, new Thead(\"团购项目数\")));\n        list.add(new PlainNode<>(9, 3, new Thead(\"导客项目数\")));\n        list.add(new PlainNode<>(10, 3, new Thead(\"代收项目数\")));\n        list.add(new PlainNode<>(11, 3, new Thead(\"线上项目数\")));\n        list.add(new PlainNode<>(12, 0, new Thead(\"本月\")));\n        list.add(new PlainNode<>(13, 12,new Thead(\"应收(万)\")));\n        list.add(new PlainNode<>(14, 12,new Thead(\"实收(万)\")));\n        list.add(new PlainNode<>(15, 12,new Thead(\"成交套数\")));\n        list.add(new PlainNode<>(16, 12,new Thead(\"套均收入(元)\")));\n        list.add(new PlainNode<>(17, 12,new Thead(\"团购项目应收(万)\")));\n        list.add(new PlainNode<>(18, 12,new Thead(\"团购项目成交套数\")));\n        list.add(new PlainNode<>(19, 12,new Thead(\"团购项目经服成交套数\")));\n        list.add(new PlainNode<>(20, 12,new Thead(\"团购项目套均收入(元)\")));\n        list.add(new PlainNode<>(21, 12,new Thead(\"团购项目经服成交应收(万)\")));\n        list.add(new PlainNode<>(22, 12,new Thead(\"团购项目中介应付外佣(万)\")));\n        list.add(new PlainNode<>(23, 12,new Thead(\"团购项目经服成交套数占比\")));\n        list.add(new PlainNode<>(24, 12,new Thead(\"团购项目中介分佣比例\")));\n        list.add(new PlainNode<>(25, 12,new Thead(\"导客项目应收(万)\")));\n        list.add(new PlainNode<>(26, 12,new Thead(\"导客项目成交套数\")));\n        list.add(new PlainNode<>(27, 12,new Thead(\"导客项目套均收入(元)\")));\n        list.add(new PlainNode<>(28, 12,new Thead(\"导客项目中介应付外佣(万)\")));\n        list.add(new PlainNode<>(29, 12,new Thead(\"导客项目中介分佣比例\")));\n        list.add(new PlainNode<>(30, 12,new Thead(\"代收项目应收(万)\")));\n        list.add(new PlainNode<>(31, 12,new Thead(\"代收项目成交套数\")));\n        list.add(new PlainNode<>(32, 12,new Thead(\"线上项目应收(万)\")));\n        list.add(new PlainNode<>(33, 12,new Thead(\"线上项目成交套数\")));\n        list.add(new PlainNode<>(34, 12,new Thead(\"月指标(万)\")));\n        list.add(new PlainNode<>(35, 12,new Thead(\"指标完成率\")));\n\n        System.out.println(TreeNode.<Integer, Thead>builder(0).build().mount(list).print(e -> Optional.ofNullable(e.getAttach()).map(Thead::getName).orElse(null)));\n    }\n\n    @Test\n    public void test3() throws IOException {\n        System.out.println(\"\\n\\n\\n\");\n        System.out.println(\n            Files.listFiles(MavenProjects.getProjectBaseDir())\n                 .print(e -> e.getSiblingOrdinal() + \":\" + e.getChildrenCount() + \":\" + e.getAttach().getName())\n        );\n    }\n\n    @Test\n    public void test4() throws IOException {\n        System.out.println(\"\\n\\n\\n\");\n        TreeNode<Integer, File> files = Files.listFiles(MavenProjects.getProjectBaseDir());\n        BinaryTreePrinter printer = new BinaryTreePrinterBuilder<TreeNode<Integer, File>>(\n            e -> e.getAttach().getName(),\n            e -> CollectionUtils.isEmpty(e.getChildren()) ? null : Collects.getFirst(e.getChildren()),\n            e -> CollectionUtils.isEmpty(e.getChildren()) ? null :Collects.getLast(e.getChildren())\n        )\n        //.branch(BinaryTreePrinter.Branch.TRIANGLE)\n        .directed(false)\n        .build();\n\n        printer.print(files.getChildren(), 1000);\n        //printer.print(files.getChildren().get(5));\n    }\n\n    @Test\n    public void test5() throws IOException {\n        System.out.println(Files.tree(MavenProjects.getMainJavaPath(\"\")));\n    }\n\n    private MapTreeTrait<String, String> convert(TreeNode<String, String> node) {\n        MapTreeTrait<String, String> map = new MapTreeTrait<>();\n        map.put(\"nid\", node.getNid());\n        map.put(\"pid\", node.getPid());\n        map.put(\"attach\", node.getAttach());\n        map.put(\"path\", node.getPath());\n        map.put(\"enabled\", node.isEnabled());\n        map.put(\"available\", node.isAvailable());\n        return map;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/utils/AtomicStampedReferenceTest.java",
    "content": "package test.utils;\n\nimport java.util.concurrent.atomic.AtomicStampedReference;\n\n// ABA\npublic class AtomicStampedReferenceTest {\n    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(0, 0);\n    public static void main(String[] args) throws InterruptedException {\n        final int stamp = atomicStampedReference.getStamp();\n        final Integer reference = atomicStampedReference.getReference();\n        System.out.println(reference+\"============\"+stamp);\n        Thread t1 = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                System.out.println(reference + \"-\" + stamp + \"-\"\n                        + atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1));\n                System.out.println(reference + \"-\" + stamp + \"-\"\n                        + atomicStampedReference.compareAndSet(reference+10, reference, stamp+1, stamp + 2));\n            }\n        });\n\n        Thread t2 = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                Integer reference = atomicStampedReference.getReference();\n                System.out.println(reference + \"-\" + stamp + \"-\"\n                        + atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1));\n            }\n        });\n        t1.start();\n        t1.join();\n        t2.start();\n        t2.join();\n\n        System.out.println(atomicStampedReference.getReference());\n        System.out.println(atomicStampedReference.getStamp());\n    }\n}\n\n"
  },
  {
    "path": "src/test/java/test/utils/Base58Test.java",
    "content": "package test.utils;\n\nimport java.math.BigInteger;\nimport java.util.Arrays;\n\nimport org.junit.Test;\n\nimport cn.ponfee.commons.util.Base58;\nimport junit.framework.TestCase;\n\n/**\n * https://blog.csdn.net/qq_41185868/article/details/80806532\n * \n * @author Ponfee\n */\npublic class Base58Test extends TestCase {\n    @Test\n    public void testEncode() {\n        byte[] testbytes = \"Hello World\".getBytes();\n        assertEquals(\"JxF12TrwUP45BMd\", Base58.encode(testbytes));\n\n        BigInteger bi = BigInteger.valueOf(3471844090L);\n        assertEquals(\"16Ho7Hs\", Base58.encode(bi.toByteArray()));\n\n        byte[] zeroBytes1 = new byte[1];\n        assertEquals(\"1\", Base58.encode(zeroBytes1));\n\n        byte[] zeroBytes7 = new byte[7];\n        assertEquals(\"1111111\", Base58.encode(zeroBytes7));\n\n        // test empty encode\n        assertEquals(\"\", Base58.encode(new byte[0]));\n    }\n\n    @Test\n    public void testDecode() {\n        byte[] testbytes = \"Hello World\".getBytes();\n        byte[] actualbytes = Base58.decode(\"JxF12TrwUP45BMd\");\n        assertTrue(new String(actualbytes), Arrays.equals(testbytes, actualbytes));\n\n        assertTrue(\"1\", Arrays.equals(Base58.decode(\"1\"), new byte[1]));\n        assertTrue(\"1111\", Arrays.equals(Base58.decode(\"1111\"), new byte[4]));\n\n        try {\n            Base58.decode(\"This isn't valid base58\");\n            fail();\n        } catch (Exception e) {\n            // expected\n        }\n\n        Base58.decodeChecked(\"4stwEBjT6FYyVV\");\n\n        // Checksum should fail.\n        try {\n            Base58.decodeChecked(\"4stwEBjT6FYyVW\");\n            fail();\n        } catch (Exception e) {\n            // expected\n        }\n\n        // Input is too short.\n        try {\n            Base58.decodeChecked(\"4s\");\n            fail();\n        } catch (Exception e) {\n            // expected\n        }\n\n        // Test decode of empty String.\n        assertEquals(0, Base58.decode(\"\").length);\n\n        // Now check we can correctly decode the case where the high bit of the first byte is not zero, so BigInteger\n        // sign extends. Fix for a bug that stopped us parsing keys exported using sipas patch.\n        Base58.decodeChecked(\"93VYUMzRG9DdbRP72uQXjaWibbQwygnvaCu9DumcqDjGybD864T\");\n        Base58.decodeChecked(\"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa\");\n        Base58.decodeChecked(\"1XPTgDRhN8RFnzniWCddobD9iKZatrvH4\");\n        Base58.decodeChecked(\"14rE7Jqy4a6P27qWCCsngkUfBxtevZhPHB\");\n    }\n\n    @Test\n    public void testDecodeToBigInteger() {\n        byte[] input = Base58.decode(\"129\");\n        assertEquals(new BigInteger(1, input), Base58.decodeToBigInteger(\"129\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/Base64.java",
    "content": "package test.utils;\n\nimport java.io.UnsupportedEncodingException;\nimport java.util.Arrays;\n\n/**\n* <p>\n* Encodes and decodes to and from Base64 notation.\n* </p>\n* <p>\n* I am placing this code in the Public Domain. Do with it as you will. This\n* software comes with no guarantees or warranties but with plenty of\n* well-wishing instead! Please visit <a\n* href=\"http://iharder.net/base64\">http://iharder.net/base64</a> periodically\n* to check for updates or to contribute improvements.\n* </p>\n*\n* @author Robert Harder\n* @author rob@iharder.net\n* @version 2.3.7\n*/\npublic final class Base64 {\n\n    /** The equals sign (=) as a byte. */\n    private static final byte EQUALS_SIGN = (byte) '=';\n\n    /** Preferred encoding. */\n    private static final String PREFERRED_ENCODING = \"US-ASCII\";\n\n    /** The 64 valid Base64 values. */\n    private static final byte[] STANDARD_ALPHABET = { (byte) 'A', (byte) 'B',\n        (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H',\n        (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N',\n        (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T',\n        (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',\n        (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f',\n        (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l',\n        (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r',\n        (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x',\n        (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',\n        (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9',\n        (byte) '+', (byte) '/' };\n\n    /** Defeats instantiation. */\n    private Base64() {}\n\n    /**\n    * <p>\n    * Encodes up to three bytes of the array <var>source</var> and writes the\n    * resulting four Base64 bytes to <var>destination</var>. The source and\n    * destination arrays can be manipulated anywhere along their length by\n    * specifying <var>srcOffset</var> and <var>destOffset</var>. This method\n    * does not check to make sure your arrays are large enough to accomodate\n    * <var>srcOffset</var> + 3 for the <var>source</var> array or\n    * <var>destOffset</var> + 4 for the <var>destination</var> array. The\n    * actual number of significant bytes in your array is given by\n    * <var>numSigBytes</var>.\n    * </p>\n    * <p>\n    * This is the lowest level of the encoding methods with all possible\n    * parameters.\n    * </p>\n    *\n    * @param source\n    *          the array to convert\n    * @param srcOffset\n    *          the index where conversion begins\n    * @param numSigBytes\n    *          the number of significant bytes in your array\n    * @param destination\n    *          the array to hold the conversion\n    * @param destOffset\n    *          the index where output will be put\n    * @return the <var>destination</var> array\n    */\n    private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes,\n        byte[] destination, int destOffset) {\n\n        int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)\n            | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)\n            | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);\n\n        switch (numSigBytes) {\n            case 3:\n                destination[destOffset] = STANDARD_ALPHABET[(inBuff >>> 18)];\n                destination[destOffset + 1] = STANDARD_ALPHABET[(inBuff >>> 12) & 0x3f];\n                destination[destOffset + 2] = STANDARD_ALPHABET[(inBuff >>> 6) & 0x3f];\n                destination[destOffset + 3] = STANDARD_ALPHABET[(inBuff) & 0x3f];\n                return destination;\n\n            case 2:\n                destination[destOffset] = STANDARD_ALPHABET[(inBuff >>> 18)];\n                destination[destOffset + 1] = STANDARD_ALPHABET[(inBuff >>> 12) & 0x3f];\n                destination[destOffset + 2] = STANDARD_ALPHABET[(inBuff >>> 6) & 0x3f];\n                destination[destOffset + 3] = EQUALS_SIGN;\n                return destination;\n\n            case 1:\n                destination[destOffset] = STANDARD_ALPHABET[(inBuff >>> 18)];\n                destination[destOffset + 1] = STANDARD_ALPHABET[(inBuff >>> 12) & 0x3f];\n                destination[destOffset + 2] = EQUALS_SIGN;\n                destination[destOffset + 3] = EQUALS_SIGN;\n                return destination;\n\n            default:\n                return destination;\n        }\n    }\n\n    /**\n    * Encode string as a byte array in Base64 annotation.\n    *\n    * @param string\n    * @return The Base64-encoded data as a string\n    */\n    public static String encode(String string) {\n        byte[] bytes;\n        try {\n            bytes = string.getBytes(PREFERRED_ENCODING);\n        } catch (UnsupportedEncodingException e) {\n            bytes = string.getBytes();\n        }\n        return encodeBytes(bytes);\n    }\n\n    /**\n    * Encodes a byte array into Base64 notation.\n    *\n    * @param source\n    *          The data to convert\n    * @return The Base64-encoded data as a String\n    * @throws NullPointerException\n    *           if source array is null\n    * @throws IllegalArgumentException\n    *           if source array, offset, or length are invalid\n    * @since 2.0\n    */\n    public static String encodeBytes(byte[] source) {\n        return encodeBytes(source, 0, source.length);\n    }\n\n    /**\n    * Encodes a byte array into Base64 notation.\n    *\n    * @param source\n    *          The data to convert\n    * @param off\n    *          Offset in array where conversion should begin\n    * @param len\n    *          Length of data to convert\n    * @return The Base64-encoded data as a String\n    * @throws NullPointerException\n    *           if source array is null\n    * @throws IllegalArgumentException\n    *           if source array, offset, or length are invalid\n    * @since 2.0\n    */\n    public static String encodeBytes(byte[] source, int off, int len) {\n        byte[] encoded = encodeBytesToBytes(source, off, len);\n        try {\n            return new String(encoded, PREFERRED_ENCODING);\n        } catch (UnsupportedEncodingException uue) {\n            return new String(encoded);\n        }\n    }\n\n    /**\n    * Similar to {@link #encodeBytes(byte[], int, int)} but returns a byte\n    * array instead of instantiating a String. This is more efficient if you're\n    * working with I/O streams and have large data sets to encode.\n    *\n    *\n    * @param source\n    *          The data to convert\n    * @param off\n    *          Offset in array where conversion should begin\n    * @param len\n    *          Length of data to convert\n    * @return The Base64-encoded data as a String if there is an error\n    * @throws NullPointerException\n    *           if source array is null\n    * @throws IllegalArgumentException\n    *           if source array, offset, or length are invalid\n    * @since 2.3.1\n    */\n    public static byte[] encodeBytesToBytes(byte[] source, int off, int len) {\n        if (source == null) {\n            throw new NullPointerException(\"Cannot serialize a null array.\");\n        }\n        if (off < 0) {\n            throw new IllegalArgumentException(\"Cannot have negative offset: \" + off);\n        }\n        if (len < 0) {\n            throw new IllegalArgumentException(\"Cannot have length offset: \" + len);\n        }\n        if (off + len > source.length) {\n            throw new IllegalArgumentException(String.format(\"Cannot have offset of %d and length of %d with \"\n                + \"array of length %d\", off, len, source.length));\n        }\n\n        // Bytes needed for actual encoding\n        int encLen = ((len / 3) << 2) + (len % 3 > 0 ? 4 : 0);\n\n        byte[] outBuff = new byte[encLen];\n\n        int d = 0;\n        int e = 0;\n        int len2 = len - 2;\n        for (; d < len2; d += 3, e += 4) {\n            encode3to4(source, d + off, 3, outBuff, e);\n        }\n\n        if (d < len) {\n            encode3to4(source, d + off, len - d, outBuff, e);\n            e += 4;\n        }\n\n        if (e <= outBuff.length - 1) {\n            return Arrays.copyOf(outBuff, e);\n        } else {\n            return outBuff;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/BytesTest.java",
    "content": "package test.utils;\n\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport cn.ponfee.commons.util.UuidUtils;\nimport com.google.common.base.Stopwatch;\nimport org.apache.commons.codec.binary.Hex;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.nio.ByteBuffer;\nimport java.util.Base64;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class BytesTest {\n\n    @Test\n    public void test1() {\n        int n = 999999;\n        byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(9999));\n        Stopwatch watch = Stopwatch.createStarted();\n        for (int i = 0; i < n; i++) {\n            Base64.getEncoder().encodeToString(data);\n        }\n        System.out.println(\"Base64.getEncoder().encodeToString \" + watch.stop());\n\n        watch.reset().start();\n        for (int i = 0; i < n; i++) {\n            test.utils.Base64.encodeBytes(data);\n        }\n        System.out.println(\"test.utils.Base64.encodeBytes \" + watch.stop());\n\n        watch.reset().start();\n        for (int i = 0; i < n; i++) {\n            Bytes.encodeHex(data);\n        }\n        System.out.println(\"hexEncode \" + watch.stop());\n\n        watch.reset().start();\n        for (int i = 0; i < n; i++) {\n            Hex.encodeHexString(data);\n        }\n        System.out.println(\"Hex.encodeHexString \" + watch.stop());\n    }\n\n    @Test\n    public void test2() {\n        for (int i = 0; i < 10000; i++) {\n            byte b = (byte) SecureRandoms.nextInt();\n            if (!Integer.toBinaryString((b & 0xFF) + 0x100).equals(Integer.toBinaryString(b & 0xFF | 0x100))) {\n                System.err.println(i+\"fail\");\n            }\n        }\n    }\n    \n    @Test\n    public void test3() {\n        for (int i = 0; i < 100000; i++) {\n            byte[] data = SecureRandoms.nextBytes(ThreadLocalRandom.current().nextInt(999));\n            String hex = Hex.encodeHexString(data);\n            Assert.assertArrayEquals(data, Bytes.decodeHex(hex));\n        }\n    }\n\n    @Test\n    public void test4() {\n        byte[] uuid = UuidUtils.uuid();\n        long a = System.nanoTime();\n        long b = Thread.currentThread().getId();\n        long c = System.identityHashCode(SecureRandoms.class);\n\n        ByteBuffer buffer = ByteBuffer.allocate(40);\n        buffer.put(uuid);\n        buffer.putLong(a);\n        buffer.putLong(b);\n        buffer.putLong(c);\n        buffer.flip();\n        Assert.assertEquals(Bytes.encodeHex(buffer.array()), Bytes.encodeHex(uuid) + Bytes.encodeHex(Bytes.toBytes(a)) + Bytes.encodeHex(Bytes.toBytes(b)) + Bytes.encodeHex(Bytes.toBytes(c)));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/utils/FloatContent.java",
    "content": "package test.utils;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\n\nimport cn.ponfee.commons.util.Bytes;\n\n/**\n * http://blog.csdn.net/yezhubenyue/article/details/7436624\n * http://blog.csdn.net/xieyihua1994/article/details/51659379\n * http://blog.csdn.net/gaoshuang5678/article/details/50554131\n * https://blog.csdn.net/K346K346/article/details/50487127\n * \n * 规格化的浮点数是上述的第一种情况,对于单精度来说,也就是阶码位不为0且不为255的这种情况.\n * \n * \n *  整数部分除2取余，直到商为0停止，从最后的余数读起，一直到最前面的余数\n *  小数部分乘2取整，然后从前往后读\n *\n * Float: \n *   S:sign符号, \n *   E:exponent指数（0~2^8-1=255）\n *   e:移码（负数左移，正数右移），e=E-127\n *   M:mantissa尾数（0~2^23-1=8388607）\n *   \n *   SEEE EEEE E[1]MMM MMMM MMMM MMMM MMMM MMMM\n * \n *   1100 0001 0[1]100 1000 0000 0000 0000 0000   // -12.5\n *   S: 1\n *   E: 100 0001 0  // 130\n *   e: E-127 = 130-127 = 3\n *   M: [1]100 1000 0000 0000 0000 0000   // 即: 1.100 1000 0000 0000 0000 0000\n *   \n *   移码：如果指数e为负数，底数的小数点向左移，如果指数e为正数，底数的小数点向右移\n *   M(1.100 1000 0000 0000 0000 0000)向右移3位 \n *   --> 1100. 1000 0000 0000 0000 0000\n *   \n *   小数点左边的1100 表示为 (1 × 2^3) + (1 × 2^2) + (0 × 2^1) + (0 × 2^0), 其结果为 12\n *   小数点右边的 .100… 表示为 (1 × 2^-1) + (0 × 2^-2) + (0 × 2^-3) + ... ，其结果为.5\n *   以上二值的和为12.5， 由于S 为1，使用为负数，即-12.5 。\n * \n * \n *   将浮点数转成二进制：0.5->0.1, 0.25->0.01, 0.125->0.001, 0.0625->0.0001\n *   小数的二进制算法和整数的大致相反，就是不断的拿小数部分乘以2取积的整数部分，然后正序排列，比如求0.9的二进制：\n *        0.9*2=1.8 取 1 \n *        0.8*2=1.6 取 1 \n *        0.6*2=1.2 取 1 \n *        0.2*2=0.4 取 0 \n *        0.4*2=0.8 取 0 \n *        0.8*2=1.6 取 1 \n * \n *   17.625\n *     转二进制：1 0001.101\n *     偏移：右移N位，直到小数点前只剩一位：1.0001 101（即右移4位），M=1.0001 101\n *     则e=4，即E=127+e=131 ==> E=10000011\n *     S=0\n *     M省略小数点前面的1（向左或向右移后，最左边的数总是1）\n *     则其二进制为：0 10000011 000 1101 0000 0000 0000 0000\n * \n * 指数全为0（E=0）：\n *     1、尾数全0：则这个数的真值为±0（正负号和数符位有关）\n *     2、尾数不全为0：表明当前的float数是一个非规格化的数\n *                （小于2^-127，所以float的最小值指数位为0000 0001=1-127=-126，尾数最后一位为1，即00000...0001）\n * \n * 指数全为1（E=255）：\n *     1、尾数全0：值为±∞，+∞：0 11111111 000 0000 0000 0000 0000。-∞：1 11111111 000 0000 0000 0000 0000\n *     2、尾数不全为0：表明为NaN（所以float的最大值的指数位为1111 1110=254-127=127，尾数位全为1）\n * \n * Double:\n *   SEEE EEEE EEEE [1]MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM\n *   1+11+52\n *   e = E-1023\n * \n * \n *   0                       00000000000000000000000000000000\n *   Float.MIN_VALUE         00000000000000000000000000000001  // 非规格化的最小值\n *   Float.MIN_NORMAL        00000000100000000000000000000000  // 规格化的最小值\n *   Float.MAX_VALUE         01111111011111111111111111111111\n *   Float.NaN               01111111110000000000000000000000\n *   Float.NEGATIVE_INFINITY 11111111100000000000000000000000\n *   Float.POSITIVE_INFINITY 01111111100000000000000000000000\n * \n * @author Ponfee\n */\npublic class FloatContent {\n\n    public static void main(String[] args) {\n        System.out.println(Bytes.toBinary(Bytes.toBytes(Float.MAX_VALUE)));\n        System.out.println(Bytes.toBinary(Bytes.toBytes(Double.MAX_VALUE)));\n        System.out.println(Bytes.toFloat(new byte[] {127, (byte)0xff, (byte)0xff, (byte)0xff}));\n        \n        \n        System.out.println(Bytes.toBinary(Bytes.toBytes(-12.5f)));\n        System.out.println(Bytes.toBinary(ByteBuffer.allocate(4).putFloat(-12.5f).array()));\n\n        System.out.println();\n        System.out.println(Bytes.toBinary(Bytes.toBytes(17.625f)));\n        System.out.println(Bytes.toBinary(ByteBuffer.allocate(4).putFloat(17.625f).array()));\n\n        System.out.println();\n        System.out.println(Bytes.toBinary(Bytes.toBytes(6.9f)));\n        System.out.println(Bytes.toBinary(Bytes.toBytes(0.9f)));\n        \n        System.out.println();\n        System.out.println(ByteOrder.nativeOrder());\n        \n        System.out.println(Bytes.toFloat(new byte[] {\n            0, 0, 0, 127, 0, 0, 0, 0\n        }));\n\n        System.out.println(0x1.0p-3f); // 0x1.0*power(2,-3)\n        System.out.println(0x0.9p-3f); // 0x0.5*power(2,-3)\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/GuavaCacheRefreshTest.java",
    "content": "package test.utils;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.junit.Test;\n\nimport com.google.common.base.Preconditions;\nimport com.google.common.cache.CacheBuilder;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\nimport com.google.common.util.concurrent.Futures;\nimport com.google.common.util.concurrent.ListenableFuture;\n\npublic class GuavaCacheRefreshTest {\n    public class SkuCache {\n        private String skuId;\n        private String skuCode;\n        private Long realQuantity;\n        public String getSkuId() {\n            return skuId;\n        }\n        public void setSkuId(String skuId) {\n            this.skuId = skuId;\n        }\n        public String getSkuCode() {\n            return skuCode;\n        }\n        public void setSkuCode(String skuCode) {\n            this.skuCode = skuCode;\n        }\n        public Long getRealQuantity() {\n            return realQuantity;\n        }\n        public void setRealQuantity(Long realQuantity) {\n            this.realQuantity = realQuantity;\n        }\n    }\n\n    AtomicInteger loadTimes = new AtomicInteger(0);\n    AtomicInteger count = new AtomicInteger(0);\n\n    @Test\n    public void testCacheUse() throws Exception {\n        LoadingCache<String, SkuCache> loadingCache = CacheBuilder.newBuilder()\n                .refreshAfterWrite(1000, TimeUnit.MILLISECONDS)\n                //Prevent data reloading from failing, but the value of memory remains the same\n                .expireAfterWrite(1500, TimeUnit.MILLISECONDS)\n                .build(new CacheLoader<String, SkuCache>() {\n                    @Override\n                    public SkuCache load(String key) {\n                        SkuCache skuCache = new SkuCache();\n                        skuCache.setSkuCode(key + \"---\" + (loadTimes.incrementAndGet()));\n                        skuCache.setSkuId(key);\n                        skuCache.setRealQuantity(100L);\n                        System.out.println(\"load...\" + key);\n                        return skuCache;\n                    }\n\n                    @Override\n                    public ListenableFuture<SkuCache> reload(String key, SkuCache oldValue) throws Exception {\n                        Preconditions.checkNotNull(key);\n                        Preconditions.checkNotNull(oldValue);\n                        System.out.println(\"reload...\");\n                        //Simulate time consuming operation\n//                        Thread.sleep(1000);\n                        return Futures.immediateFuture(load(key));\n                    }\n                });\n\n\n        for (int i = 0; i < 1000; i++) {\n            new Thread(() -> {\n                try {\n                    getValue(loadingCache);\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }).start();\n        }\n\n        System.in.read();\n        System.out.println(\"finish\");\n    }\n\n\n    private void getValue(LoadingCache<String, SkuCache> loadingCache) throws Exception {\n        for (int i = 0; i < 10; i++) {\n            Thread.sleep(300l);\n            System.out.println(loadingCache.get(\"sku\").toString() + \" - \" + count.incrementAndGet());\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/GuavaCacheTest.java",
    "content": "package test.utils;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.Test;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\n\npublic class GuavaCacheTest {\n\n    @Test\n    public void create1() throws Exception {\n        LoadingCache<String, Long> cache = CacheBuilder.newBuilder()\n            .maximumSize(1000)\n            .weakValues()\n            .weakKeys()\n            .softValues()\n            .expireAfterAccess(30L, TimeUnit.SECONDS)\n            .build(new CacheLoader<String, Long>() {\n                public @Override Long load(String key) {\n                    return -1L; // 缓存未命中，会调用此方法加载(不能为null)\n                }\n            }\n        );\n\n        cache.put(\"abc\", 123L);\n        System.out.println(cache.get(\"abc\"));\n        System.out.println(cache.get(\"def\"));\n        System.out.println(cache.getUnchecked(\"123\"));\n    }\n    \n    @Test\n    public void create2() throws Exception {\n        Cache<String, Long> cache = CacheBuilder.newBuilder()\n            .maximumSize(1000)\n            .build(); // look Ma, no CacheLoader\n        \n        // If the key wasn't in the \"easy to compute\" group, we need to\n        // do things the hard way.\n        Long result = cache.get(\"123\", new Callable<Long>() {\n            public @Override Long call() {\n                return -1L;  // 缓存未命中，会调用此方法加载\n            }\n        });\n        System.out.println(result);\n        System.out.println(cache.getIfPresent(\"456\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/Java8DateTimeTester.java",
    "content": "package test.utils;\n\nimport java.time.Instant;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.format.DateTimeFormatter;\n\npublic class Java8DateTimeTester {\n\n    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\");\n\n    @org.junit.Test\n    public void testFormat() {\n        System.out.println(DATE_FORMATTER.format(LocalDateTime.now()));\n        //System.out.println(DATE_FORMATTER.format(Instant.now())); // error\n        System.out.println(LocalDateTime.now().format(DATE_FORMATTER));\n\n        System.out.println(DateTimeFormatter.ISO_INSTANT.format(Instant.now()));\n    }\n\n    @org.junit.Test\n    public void testLocalDate() {\n        System.out.println(LocalDateTime.now());\n        System.out.println(LocalDateTime.now().toLocalDate());\n        System.out.println(LocalDateTime.now().toLocalTime());\n\n        System.out.println(LocalDate.now());\n        System.out.println(LocalDate.now().atStartOfDay());\n\n        System.out.println(LocalTime.now());\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/MapToObjTest.java",
    "content": "package test.utils;\n\nimport com.google.common.base.CaseFormat;\nimport com.google.common.collect.ImmutableMap;\n\nimport cn.ponfee.commons.jce.DigestAlgorithms;\nimport cn.ponfee.commons.reflect.BeanMaps;\n\npublic class MapToObjTest {\n\n    public static class A {\n        private int a_b;\n        private String str;\n        private DigestAlgorithms mode;\n\n        public A() {}\n\n        public A(int a_b, String str) {\n            super();\n            this.a_b = a_b;\n            this.str = str;\n        }\n\n        public int getA_b() {\n            return a_b;\n        }\n\n        public void setA_b(int a_b) {\n            this.a_b = a_b;\n        }\n\n        public String getStr() {\n            return str;\n        }\n\n        public void setStr(String str) {\n            this.str = str;\n        }\n\n        public DigestAlgorithms getMode() {\n            return mode;\n        }\n\n        public void setMode(DigestAlgorithms mode) {\n            this.mode = mode;\n        }\n\n        @Override\n        public String toString() {\n            return \"A [a_b=\" + a_b + \", str=\" + str + \", mode=\" +( mode == null ? \"null\" : mode.name()) + \"]\";\n        }\n\n    }\n\n    public static void main(String[] args) {\n        System.out.println(CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, \"test-data\"));//testData\n        System.out.println(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, \"test_data\"));//testData\n        System.out.println(CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, \"test_data\"));//TestData\n\n        System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, \"testdata\"));//testdata\n        System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, \"TestData\"));//test_data\n        System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, \"testData\"));//test_data\n        System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, \"testData\"));//test-data\n\n        A a = new A(1, \"aaa\");\n        //Map<String, Object> map = ObjectUtils.bean2map(a);\n        //System.out.println(map);\n\n        //a = ObjectUtils.map2bean(map, A.class);\n        //System.out.println(a);\n\n        a = BeanMaps.PROPS.toBean(ImmutableMap.of(\"aB\", 123, \"str\", \"abc\", \"mode\", \"RipeMD128\"), A.class);\n        System.out.println(a);\n        System.out.println(BeanMaps.PROPS.toMap(a));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/utils/NumbersTest.java",
    "content": "package test.utils;\n\nimport org.junit.Test;\n\nimport java.util.Arrays;\nimport java.util.stream.LongStream;\n\n/**\n * @author ponfee.fu\n */\npublic class NumbersTest {\n\n    @Test\n    public void testSplit() {\n        long[] oldBillsPaid = {543L, -560L, 20L, 3200L, 20L, 0L};\n        long[] oldBillsAmt = {100L, 3200L, 100L, -10000L};\n\n        long total = LongStream.of(oldBillsPaid).sum();\n\n        long[] split = split(oldBillsAmt, total);\n        System.out.println(\"total: \" + total);\n        System.out.println(\"bills: \" + Arrays.toString(oldBillsAmt));\n        System.out.println(\"paid: \" + Arrays.toString(split));\n    }\n\n    public static long[] split(long[] bills, long value) {\n        long total = LongStream.of(bills).map(Math::abs).sum();\n\n        long[] result = new long[bills.length];\n        if (bills.length == 0 || value == 0) {\n            return result;\n        }\n\n        double rate;\n        int i = 0, n = bills.length - 1;\n        for (; i < n; i++) {\n            // rate <= 1.0\n            rate = value / (double) total;\n\n            // 因为result[i]是ceil后的结果，所以按比率上来算value减得会更多，即rate只会递减，所以不会出现溢出(后面的费用项不够抵扣)的情况\n            result[i] = Math.min((int) Math.ceil(bills[i] * rate), value);\n            value -= result[i];\n            total -= bills[i];\n\n            if (value == 0) {\n                break;\n            }\n        }\n\n        // the last bill item\n        if (i == n) {\n            result[i] = value;\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/utils/ObjectUtilsTest.java",
    "content": "package test.utils;\n\nimport java.util.UUID;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.util.Bytes;\n\npublic class ObjectUtilsTest {\n\n    static int n = 100000000;\n    UUID uuid = UUID.randomUUID();\n    long most  = uuid.getMostSignificantBits(), \n        least = uuid.getLeastSignificantBits();\n    \n    @Test\n    public void test1() {\n        char[] chars = new char[32];\n        Bytes.encodeHex(chars, 0, (byte) (most >>> 56));\n        Bytes.encodeHex(chars, 2, (byte) (most >>> 48));\n        Bytes.encodeHex(chars, 4, (byte) (most >>> 40));\n        Bytes.encodeHex(chars, 6, (byte) (most >>> 32));\n        Bytes.encodeHex(chars, 8, (byte) (most >>> 24));\n        Bytes.encodeHex(chars, 10, (byte) (most >>> 16));\n        Bytes.encodeHex(chars, 12, (byte) (most >>> 8));\n        Bytes.encodeHex(chars, 14, (byte) (most));\n\n        Bytes.encodeHex(chars, 16, (byte) (least >>> 56));\n        Bytes.encodeHex(chars, 18, (byte) (least >>> 48));\n        Bytes.encodeHex(chars, 20, (byte) (least >>> 40));\n        Bytes.encodeHex(chars, 22, (byte) (least >>> 32));\n        Bytes.encodeHex(chars, 24, (byte) (least >>> 24));\n        Bytes.encodeHex(chars, 26, (byte) (least >>> 16));\n        Bytes.encodeHex(chars, 28, (byte) (least >>> 8));\n        Bytes.encodeHex(chars, 30, (byte) (least));\n        String s1 = new String(chars);\n\n        System.out.println(s1);\n        byte[] bytes = new byte[] {\n            (byte) (most >>> 56), (byte) (most >>> 48),\n            (byte) (most >>> 40), (byte) (most >>> 32),\n            (byte) (most >>> 24), (byte) (most >>> 16),\n            (byte) (most >>> 8), (byte) (most),\n\n            (byte) (least >>> 56), (byte) (least >>> 48),\n            (byte) (least >>> 40), (byte) (least >>> 32),\n            (byte) (least >>> 24), (byte) (least >>> 16),\n            (byte) (least >>> 8), (byte) (least)\n        };\n        String s2 = Bytes.encodeHex(bytes);\n        System.out.println(s2);\n\n        Assert.assertEquals(s1, s2);\n    }\n\n    @Test\n    public  void test2() {\n        for (int i= 0; i < n; i++) {\n            char[] chars = new char[32];\n            Bytes.encodeHex(chars,  0, (byte) (most  >>> 56));\n            Bytes.encodeHex(chars,  2, (byte) (most  >>> 48));\n            Bytes.encodeHex(chars,  4, (byte) (most  >>> 40));\n            Bytes.encodeHex(chars,  6, (byte) (most  >>> 32));\n            Bytes.encodeHex(chars,  8, (byte) (most  >>> 24));\n            Bytes.encodeHex(chars, 10, (byte) (most  >>> 16));\n            Bytes.encodeHex(chars, 12, (byte) (most  >>>  8));\n            Bytes.encodeHex(chars, 14, (byte) (most        ));\n\n            Bytes.encodeHex(chars, 16, (byte) (least >>> 56));\n            Bytes.encodeHex(chars, 18, (byte) (least >>> 48));\n            Bytes.encodeHex(chars, 20, (byte) (least >>> 40));\n            Bytes.encodeHex(chars, 22, (byte) (least >>> 32));\n            Bytes.encodeHex(chars, 24, (byte) (least >>> 24));\n            Bytes.encodeHex(chars, 26, (byte) (least >>> 16));\n            Bytes.encodeHex(chars, 28, (byte) (least >>>  8));\n            Bytes.encodeHex(chars, 30, (byte) (least       ));\n            new String(chars);\n        }\n    }\n    \n    @Test\n    public  void test3() {\n\n        for (int i= 0; i < n; i++) {\n            byte[] bytes = new byte[] {\n                (byte) (most  >>> 56), (byte) (most  >>> 48),\n                (byte) (most  >>> 40), (byte) (most  >>> 32),\n                (byte) (most  >>> 24), (byte) (most  >>> 16),\n                (byte) (most  >>>  8), (byte) (most        ),\n\n                (byte) (least >>> 56), (byte) (least >>> 48),\n                (byte) (least >>> 40), (byte) (least >>> 32),\n                (byte) (least >>> 24), (byte) (least >>> 16),\n                (byte) (least >>>  8), (byte) (least       )\n            };\n            Bytes.encodeHex(bytes);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/OptionalTest.java",
    "content": "package test.utils;\n\nimport java.util.NoSuchElementException;\nimport java.util.Optional;\n\npublic class OptionalTest {\n\n    public static void main(String[] args) {\n        //创建Optional实例，也可以通过方法返回值得到。\n        Optional<String> name = Optional.of(\"Sanaulla\");\n\n        //创建没有值的Optional实例，例如值为'null'\n        Optional<String> empty = Optional.ofNullable(null);\n\n        //isPresent方法用来检查Optional实例是否有值。\n        if (name.isPresent()) {\n            //调用get()返回Optional值。\n            System.out.println(name.get());\n        }\n\n        try {\n            //在Optional实例上调用get()抛出NoSuchElementException。\n            System.out.println(empty.get());\n        } catch (NoSuchElementException ex) {\n            System.out.println(ex.getMessage());\n        }\n\n        //ifPresent方法接受lambda表达式参数。\n        //如果Optional值不为空，lambda表达式会处理并在其上执行操作。\n        name.ifPresent((value) -> {\n            System.out.println(\"The length of the value is: \" + value.length());\n        });\n\n        //如果有值orElse方法会返回Optional实例，否则返回传入的错误信息。\n        System.out.println(empty.orElse(\"There is no value present!\"));\n        System.out.println(name.orElse(\"There is some value!\"));\n\n        //orElseGet与orElse类似，区别在于传入的默认值。\n        //orElseGet接受lambda表达式生成默认值。\n        System.out.println(empty.orElseGet(() -> \"Default Value\"));\n        System.out.println(name.orElseGet(() -> \"Default Value\"));\n\n        try {\n            //orElseThrow与orElse方法类似，区别在于返回值。\n            //orElseThrow抛出由传入的lambda表达式/方法生成异常。\n            empty.orElseThrow(RuntimeException::new);\n        } catch (Throwable ex) {\n            System.out.println(ex.toString());\n        }\n\n        //map方法通过传入的lambda表达式修改Optonal实例默认值。 \n        //lambda表达式返回值会包装为Optional实例。\n        Optional<String> upperName = name.map((value) -> value.toUpperCase());\n        System.out.println(upperName.orElse(\"No value found\"));\n\n        //flatMap与map（Funtion）非常相似，区别在于lambda表达式的返回值。\n        //map方法的lambda表达式返回值可以是任何类型，但是返回值会包装成Optional实例。\n        //但是flatMap方法的lambda返回值总是Optional类型。\n        upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));\n        System.out.println(upperName.orElse(\"No value found\"));\n\n        //filter方法检查Optiona值是否满足给定条件。\n        //如果满足返回Optional实例值，否则返回空Optional。\n        Optional<String> longName = name.filter((value) -> value.length() > 6);\n        System.out.println(longName.orElse(\"The name is less than 6 characters\"));\n\n        //另一个示例，Optional值不满足给定条件。\n        Optional<String> anotherName = Optional.of(\"Sana\");\n        Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);\n        System.out.println(shortName.orElse(\"The name is less than 6 characters\"));\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/ProjectFileUtilsTester.java",
    "content": "package test.utils;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.util.Scanner;\n\nimport org.apache.commons.io.FilenameUtils;\nimport org.junit.Test;\n\nimport cn.ponfee.commons.util.MavenProjects;\n\npublic class ProjectFileUtilsTester {\n\n    @Test\n    public void testGetMainJavaFile() {\n        File file = MavenProjects.getMainJavaFile(MavenProjects.class);\n        printFile(file);\n    }\n\n    @Test\n    public void testGetTestJavaFile() {\n        File file = MavenProjects.getTestJavaFile(ProjectFileUtilsTester.class);\n        printFile(file);\n    }\n\n    @Test\n    public void testGetMainJavaPath() {\n        String Path = MavenProjects.getMainJavaPath(\"test\", \"TestUtils.java\");\n        printFile(Path);\n    }\n\n    @Test\n    public void testGetMainResourcesPath() {\n        String Path = MavenProjects.getMainResourcesPath(\"log4j.properties\");\n        printFile(Path);\n    }\n\n    @Test\n    public void testGetTestResourcesPath() {\n        String Path = MavenProjects.getTestResourcesPath(\"redis-script-node.lua\");\n        printFile(Path);\n    }\n\n    private void printFile(String filepath) {\n        printFile(new File(filepath));\n    }\n\n    private void printFile(File file) {\n        try {\n            Scanner scanner = new Scanner(file);\n            while (scanner.hasNextLine()) {\n                System.out.println(scanner.nextLine());\n            }\n            scanner.close();\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static void main(String[] args) {\n        /*String s = new String(toByteArray(new URL(\"file:///d:/github/springmvc-demo/src/test/resources/ftl/macro.ftl\").openStream()));\n        System.out.println(s);*/\n        String filename = \"D:\\\\github\\\\jedis-clients\\\\src\\\\main\\\\java\\\\test\\\\TestUtils.java\";\n        System.out.println(FilenameUtils.getBaseName(filename));\n        System.out.println(FilenameUtils.getExtension(filename));\n        System.out.println(FilenameUtils.getFullPath(filename));\n        System.out.println(FilenameUtils.getFullPathNoEndSeparator(filename));\n        System.out.println(FilenameUtils.getName(filename));\n        System.out.println(FilenameUtils.getPath(filename));\n        System.out.println(FilenameUtils.getPathNoEndSeparator(filename));\n        System.out.println(FilenameUtils.getPrefix(filename));\n        System.out.println(FilenameUtils.getPrefixLength(filename));\n    }\n\n    public static byte[] toByteArray(InputStream in) throws IOException {\n        ByteArrayOutputStream out = new ByteArrayOutputStream();\n        byte[] buff = new byte[4096];\n        int n;\n        while (-1 != (n = in.read(buff))) {\n            out.write(buff, 0, n);\n        }\n        return out.toByteArray();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/utils/RepeatableAnn.java",
    "content": "package test.utils;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Repeatable;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.Arrays;\n\n\npublic class RepeatableAnn {\n\n    public static void main(String[] args) {\n        Annotation[] annotations = RepeatAnn.class.getAnnotations();\n        System.out.println(annotations.length); //1\n        Arrays.stream(annotations).forEach(System.out::println);\n\n        Annotation[] annotations2 = Annotations.class.getAnnotations();\n        System.out.println(annotations2.length);//1\n        Arrays.stream(annotations2).forEach(System.out::println);\n    }\n\n    /**\n     * The same annotation can be applied to a declaration or type more than\n     * once, given that each annotation is marked as @Repeatable. In the\n     * following code, the @Repeatable annotation is used to develop an\n     * annotation that can be repeated, rather than grouped together as in\n     * previous releases of Java. In this situation, an annotation named Role is\n     * being created, and it will be used to signify a role for an annotated\n     * class or method.\n     */\n    @Repeatable(value = Roles.class)\n    public static @interface Role {\n        String name() default \"doctor\";\n    }\n\n    @Target(ElementType.TYPE)\n    @Retention(RetentionPolicy.RUNTIME)\n    public static @interface Roles {\n        Role[] value();\n    }\n\n    @Role(name = \"doctor\")\n    @Role(name = \"who\")\n    public static class RepeatAnn{\n\n    }\n\n    @Roles({@Role(name=\"doctor\"),\n            @Role(name=\"who\")})\n    public static class Annotations{\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/SimpleXmlHandlerTest.java",
    "content": "package test.utils;\n\nimport cn.ponfee.commons.resource.ResourceLoaderFacade;\nimport cn.ponfee.commons.util.MavenProjects;\nimport cn.ponfee.commons.xml.SimpleXmlHandler;\nimport org.apache.commons.io.IOUtils;\n\nimport java.io.FileReader;\nimport java.io.IOException;\n\npublic class SimpleXmlHandlerTest {\n\n    public static void main(String[] args) throws IOException {\n        System.out.println(SimpleXmlHandlerTest.class.getResourceAsStream(\"signer.xsd\"));\n        System.out.println(SimpleXmlHandlerTest.class.getClassLoader().getResourceAsStream(\"/signer.xsd\"));\n        System.out.println(ClassLoader.getSystemResourceAsStream(\"/signer.xsd\"));\n        System.out.println(Thread.currentThread().getContextClassLoader().getResourceAsStream(\"/signer.xsd\"));\n\n        System.out.println(SimpleXmlHandlerTest.class.getResourceAsStream(\"/signer.xsd\"));\n        System.out.println(SimpleXmlHandlerTest.class.getClassLoader().getResourceAsStream(\"signer.xsd\"));\n        System.out.println(ClassLoader.getSystemResourceAsStream(\"signer.xsd\"));\n        System.out.println(Thread.currentThread().getContextClassLoader().getResourceAsStream(\"signers.xml\"));\n\n        System.out.println(ResourceLoaderFacade.getResource(\"classpath:/signer.xsd\"));\n        System.out.println(ResourceLoaderFacade.getResource(\"classpath:/signers.xml\"));\n\n        System.out.println(\"----validate\");\n        SimpleXmlHandler.validate(\n            IOUtils.toString(new FileReader(MavenProjects.getTestResourcesPath(\"signers.xml\"))),\n            IOUtils.toString(new FileReader(MavenProjects.getTestResourcesPath(\"signer.xsd\")))\n        );\n        System.out.println(\"----validate done\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/TempTest.java",
    "content": "package test.utils;\n\nimport cn.ponfee.commons.jce.digest.DigestUtils;\nimport cn.ponfee.commons.reflect.ClassUtils;\nimport cn.ponfee.commons.reflect.Fields;\nimport cn.ponfee.commons.reflect.GenericUtils;\nimport cn.ponfee.commons.resource.ResourceScanner;\nimport cn.ponfee.commons.util.Bytes;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.SecureRandoms;\nimport cn.ponfee.commons.util.UuidUtils;\nimport com.google.common.base.Stopwatch;\nimport org.apache.commons.codec.binary.Hex;\nimport org.joda.time.DateTime;\nimport org.junit.Assert;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.math.BigInteger;\nimport java.nio.ByteBuffer;\nimport java.util.Calendar;\nimport java.util.UUID;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class TempTest {\n\n    @org.junit.Test\n    public void test0() throws IOException {\n        //Files.touch(\"d:/xampp\");\n        //Files.touch(\"d:/123.xlsx\");\n        //Files.touch(\"d:/1233.xlsx\");\n\n        com.google.common.io.Files.touch(new File(\"d:/12345.xlsx\"));\n        com.google.common.io.Files.touch(new File(\"d:/xampp\"));\n    }\n\n    @org.junit.Test\n    public void test2() {\n        System.out.println(DigestUtils.sha256Hex(DigestUtils.sha256(\"cn.ponfee.commons.jce.hash.HashUtils\".getBytes())));\n    }\n\n    @org.junit.Test\n    public void test3() {\n        String str = \"123\";\n        String str2 = \"123\";\n        Fields.put(str, \"value\", \"abc\".toCharArray());\n        System.out.println(str2);\n        char[] chars = (char[]) Fields.get(str, \"value\");\n        chars[0] = '1';\n        System.out.println(str2);\n    }\n\n    @org.junit.Test\n    public void test4() {\n        for (Class<?> clazz : new ResourceScanner(\"/cn/ponfee/commons/base/**/*.class\").scan4class()) {\n            for (Method method : clazz.getMethods()) {\n                try {\n                    Class<?> c = GenericUtils.getActualArgTypeArgument(method, 0);\n                    if (c != Object.class) {\n                        System.out.println(clazz.getSimpleName() + \"-->\" + method.getName() + \"-->\" + c);\n                    }\n                } catch (Exception e) {\n                }\n            }\n        }\n    }\n\n    @org.junit.Test\n    public void test41() {\n        //new ResourceScanner(\"/**/rmid_fr.properties\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"log4j2.xml.template\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/cn/ponfee/commons/jce/*.class\").scan4bytes().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/cn/ponfee/commons/jce/**/*.class\").scan4bytes().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/*.template\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/log4j2.xml.template\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"log4j2.xml.template\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/**/*.xml\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/cn/ponfee/commons/base/**/*.class\").scan4class().forEach(System.out::println);\n        //new ResourceScanner(\"/cn/ponfee/commons/**/*.class\").scan4class(null, new Class[] {Service.class}).forEach(System.out::println);\n        //new ResourceScanner(\"/cn/ponfee/commons/**/*.class\").scan4class(null, new Class[] {Component.class}).forEach(System.out::println);\n        //new ResourceScanner(\"/cn/ponfee/commons/**/*.class\").scan4class(new Class[]{Tuple.class}, null).forEach(System.out::println);\n\n        //new ResourceScanner(\"/*.template\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/log4j2.xml.template\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"log4j2.xml.template\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/**/tika*.xml\").scan4text().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/cn/ponfee/commons/jce/*.class\").scan4bytes().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n        //new ResourceScanner(\"/cn/ponfee/commons/jce/**/*.class\").scan4bytes().forEach((k, v) -> System.out.println(k + \" -> \" + v));\n\n        //new ResourceScanner(\"/cn/ponfee/commons/base/**/*.class\").scan4class().forEach(System.out::println);\n        //new ResourceScanner(\"/cn/ponfee/commons/**/*.class\").scan4class(null, new Class[] {Service.class}).forEach(System.out::println);\n        //new ResourceScanner(\"/cn/ponfee/commons/**/*.class\").scan4class(null, new Class[] {Component.class}).forEach(System.out::println);\n        //new ResourceScanner(\"/cn/ponfee/commons/**/*.class\").scan4class(new Class[]{Tuple.class}, null).forEach(System.out::println);\n\n        //new ResourceScanner(\"*\").scan4bytes().entrySet().stream().filter(e -> e.getKey().endsWith(\".xml\")).forEach(e -> System.out.println(e.getKey() + \" -> \" + e.getValue()));\n        //new ResourceScanner(\"/*.xml\").scan4bytes().entrySet().stream().filter(e -> e.getKey().endsWith(\".xml\")).forEach(e -> System.out.println(e.getKey() + \" -> \" + e.getValue()));\n        //new ResourceScanner(\"**/*.xml\").scan4bytes().entrySet().stream().filter(e -> e.getKey().endsWith(\".xml\")).forEach(e -> System.out.println(e.getKey() + \" -> \" + e.getValue()));\n        new ResourceScanner(\"/**/*.xml\").scan4bytes().entrySet().stream().filter(e -> e.getKey().endsWith(\".xml\")).forEach(e -> System.out.println(e.getKey() + \" -> \" + e.getValue()));\n    }\n\n    @org.junit.Test\n    public void test5() {\n        for (Method method : ClassUtils.class.getMethods()) {\n            System.out.println(ClassUtils.getMethodSignature(method) + \" --> \"+method.toGenericString());\n        }\n    }\n\n    @org.junit.Test\n    public void test6() {\n        byte[] bytes = SecureRandoms.nextBytes(32);\n        BigInteger big = new BigInteger(1, bytes);\n        long start = System.currentTimeMillis();\n        for (int i = 0; i < 1000000; i++) {\n            big.toString(16);\n        }\n        System.out.println(System.currentTimeMillis()-start);\n    }\n\n    @org.junit.Test\n    public void test7() {\n        byte[] bytes = SecureRandoms.nextBytes(32);\n        BigInteger big = new BigInteger(1, bytes);\n        long start = System.currentTimeMillis();\n        for (int i = 0; i < 1000000; i++) {\n            Hex.encodeHexString(big.toByteArray());\n        }\n        System.out.println(System.currentTimeMillis()-start);\n    }\n\n    @org.junit.Test\n    public void test8() {\n        System.out.println(new DateTime().millisOfDay().withMinimumValue());\n        System.out.println(new DateTime().withTimeAtStartOfDay());\n\n        Calendar calendar = Calendar.getInstance();\n        DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);\n        System.out.println(dateTime.plusDays(45).plusMonths(1).dayOfWeek()\n          .withMaximumValue().toString(\"E MM/dd/yyyy HH:mm:ss.SSS\"));\n        calendar.setTime(dateTime.toDate());\n        System.out.println(calendar.getTime());\n    }\n\n    @org.junit.Test\n    public void test9() {\n        short svalue;\n        for (int i = 0; i < 10000; i++) {\n            svalue = (short) SecureRandoms.nextInt();\n            Assert.assertArrayEquals(Bytes.toBytes(svalue), fromShort(svalue));\n        }\n        byte[] bytes;\n        for (int i = 0; i < 10000; i++) {\n            bytes = SecureRandoms.nextBytes(2);\n            Assert.assertEquals(Bytes.toShort(bytes), toShort(bytes, 0));\n        }\n\n        int ivalue;\n        for (int i = 0; i < 10000; i++) {\n            ivalue = SecureRandoms.nextInt();\n            Assert.assertArrayEquals(Bytes.toBytes(ivalue), fromInt(ivalue));\n        }\n        for (int i = 0; i < 10000; i++) {\n            bytes = SecureRandoms.nextBytes(4);\n            Assert.assertEquals(Bytes.toInt(bytes), toInt(bytes, 0));\n        }\n\n        long lvalue;\n        for (int i = 0; i < 10000; i++) {\n            lvalue = SecureRandoms.nextLong();\n            Assert.assertArrayEquals(Bytes.toBytes(lvalue), fromLong(lvalue));\n        }\n        for (int i = 0; i < 10000; i++) {\n            bytes = SecureRandoms.nextBytes(8);\n            Assert.assertEquals(Bytes.toLong(bytes), toLong(bytes, 0));\n        }\n    }\n\n    @org.junit.Test\n    public void test10() {\n        long value = SecureRandoms.nextLong();\n        byte[] bytes = SecureRandoms.nextBytes(8);\n        long round = 999999999L;\n        Stopwatch watch = Stopwatch.createStarted();\n        for (long i = 0; i < round; i++) {\n            Bytes.toBytes(value);\n        }\n        System.out.println(\"Bytes.fromLong: \" + watch.stop());\n\n        watch.reset().start();\n        for (long i = 0; i < round; i++) {\n            fromLong(value);\n        }\n        System.out.println(\"fromLong: \" + watch.stop());\n\n        watch.reset().start();\n        for (long i = 0; i < round; i++) {\n            Bytes.toLong(bytes);\n        }\n        System.out.println(\"Bytes.toLong: \" + watch.stop());\n\n        watch.reset().start();\n        for (long i = 0; i < round; i++) {\n            toLong(bytes, 0);\n        }\n        System.out.println(\"toLong: \" + watch.stop());\n    }\n\n    @org.junit.Test\n    public void test100() {\n        System.out.println(UuidUtils.uuid32());\n        UUID uuid = UUID.randomUUID();\n        long most = uuid.getMostSignificantBits();\n        long least = uuid.getLeastSignificantBits();\n        long round = 99999999L;\n        String s;\n        Stopwatch watch = Stopwatch.createStarted();\n        for (long i = 0; i < round; i++) {\n            s = Long.toHexString(most) + Long.toHexString(least);\n        }\n        System.out.println(\"Long.toHexString: \" + watch.stop());\n\n        watch.reset().start();\n        for (long i = 0; i < round; i++) {\n            s = Bytes.encodeHex(Bytes.toBytes(most))+Bytes.encodeHex(Bytes.toBytes(least));\n        }\n        System.out.println(\"Bytes.hexEncode: \" + watch.stop());\n\n        watch.reset().start();\n        for (long i = 0; i < round; i++) {\n            s = Bytes.encodeHex(new byte[] {\n               (byte) (most  >>> 56), (byte) (most  >>> 48),\n               (byte) (most  >>> 40), (byte) (most  >>> 32),\n               (byte) (most  >>> 24), (byte) (most  >>> 16),\n               (byte) (most  >>>  8), (byte) (most        ),\n\n               (byte) (least >>> 56), (byte) (least >>> 48),\n               (byte) (least >>> 40), (byte) (least >>> 32),\n               (byte) (least >>> 24), (byte) (least >>> 16),\n               (byte) (least >>>  8), (byte) (least       )\n            });\n        }\n        System.out.println(\"uuid32: \" + watch.stop());\n    }\n\n    @org.junit.Test\n    public void test11() {\n        int value = tableSizeFor(ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE));\n        System.out.println(Integer.toBinaryString(value));\n    }\n\n    @org.junit.Test\n    public void test12() {\n        System.out.println(Bytes.toBinary(Bytes.toBytes(MAXIMUM_CAPACITY)));\n        System.out.println(Bytes.toBinary(Bytes.toBytes(Integer.MAX_VALUE)));\n    }\n\n    static final int MAXIMUM_CAPACITY = 1 << 30;\n    static final int tableSizeFor(int cap) {\n        int n = cap - 1;\n        n |= n >>> 1;\n        n |= n >>> 2;\n        n |= n >>> 4;\n        n |= n >>> 8;\n        n |= n >>> 16;\n        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;\n    }\n\n\n    public static byte[] fromShort(short value) {\n        return ByteBuffer.allocate(Short.BYTES).putShort(value).array();\n    }\n\n    public static short toShort(byte[] bytes, int fromIdx) {\n        ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);\n        buffer.put(bytes, fromIdx, Short.BYTES).flip();\n        return buffer.getShort();\n    }\n\n    public static byte[] fromInt(int value) {\n        return ByteBuffer.allocate(Integer.BYTES).putInt(value).array();\n    }\n\n    public static int toInt(byte[] bytes, int fromIdx) {\n        ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);\n        buffer.put(bytes, fromIdx, Integer.BYTES).flip();\n        return buffer.getInt();\n    }\n\n    public static byte[] fromLong(long number) {\n        return ByteBuffer.allocate(Long.BYTES).putLong(number).array();\n    }\n\n    public static long toLong(byte[] bytes, int fromIdx) {\n        ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);\n        buffer.put(bytes, fromIdx, Long.BYTES).flip();\n        return buffer.getLong();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/utils/Test1.java",
    "content": "package test.utils;\n\nimport cn.ponfee.commons.util.SecureRandoms;\nimport org.apache.commons.codec.binary.Base32;\nimport org.apache.commons.codec.binary.Hex;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class Test1 {\n    private int age;\n\n    private class Nested {\n\n    }\n\n    public boolean compare(Test1 t) {\n        return this.age > t.age;\n    }\n\n    public static void main(String[] args) {\n        Test1 t1 = new Test1();\n        Test1 t2 = new Test1();\n        t1.compare(t2);\n\n        Nested n = t1.new Nested();\n\n        System.out.println(new ArrayList<>().equals(null));\n        System.out.println(3&0x01);\n        System.out.println(2&0x01);\n        System.out.println(1&0x01);\n        System.out.println(0&0x01);\n        System.out.println(new Double((double)Long.MAX_VALUE).longValue() == Long.MAX_VALUE);\n        System.out.println(new Double((double)Long.MIN_VALUE).longValue() == Long.MIN_VALUE);\n        byte[] b = SecureRandoms.nextBytes(11);\n        System.out.println(new Base32().encodeToString(b));\n        System.out.println(Hex.encodeHexString(b));\n        System.out.println(Integer.toBinaryString(tableSizeFor(1 << 30)));\n        for (int i = 0; i < 1; i++) {\n            System.out.println(Integer.toBinaryString(tableSizeFor(ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE))));\n        }\n    }\n\n    static final int tableSizeFor(int cap) {\n        int n = cap - 1;\n        n |= n >>> 1;\n        n |= n >>> 2;\n        n |= n >>> 4;\n        n |= n >>> 8;\n        n |= n >>> 16;\n        return (n < 0) ? 1 : (n >= 1 << 30) ? 1 << 30 : n + 1;\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/Test2.java",
    "content": "package test.utils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.springframework.util.Base64Utils;\n\nimport cn.ponfee.commons.collect.Collects;\nimport cn.ponfee.commons.io.Files;\nimport cn.ponfee.commons.util.Base64UrlSafe;\nimport cn.ponfee.commons.util.SecureRandoms;\n\n/**\n * @author Ponfee\n */\npublic class Test2 {\n    private static final byte[] data = SecureRandoms.nextBytes(10001);\n\n    @Test\n    public void test1() {\n        for (int i = 0; i < 999999; i++) {\n            java.util.Base64.getEncoder().encodeToString(data);\n        }\n    }\n    \n    @Test\n    public void test2() {\n        for (int i = 0; i < 999999; i++) {\n            Base64Utils.encodeToString(data);\n        }\n    }\n\n    @Test\n    public void test3() {\n        String s = Base64UrlSafe.encode(data);\n        System.out.println(Base64UrlSafe.decode(s));\n    }\n    \n    @Test\n    public void test4() {\n        String[] s1 = {\"1\",\"2\"};\n        String[] s2 = {\"2\",\"3\"};\n        String[] s3 = Collects.intersect(s1, s2);\n        System.out.println(Arrays.toString(s3));\n    }\n    \n    @Test\n    public void test5() throws InterruptedException {\n        System.out.println(Stream.of(1,2,3,4).flatMap(i-> Stream.of(i*i, i*i*i)).collect(Collectors.toList()));\n        \n        System.out.println(Arrays.asList(\"word\", \"count\").stream().map(w -> w.split(\"\")).collect(Collectors.toList()));\n        System.out.println(Arrays.asList(\"word\", \"count\").stream().flatMap(w -> Stream.of(w.split(\"\"))).collect(Collectors.toList()));\n        \n        \n        /*SynchronousQueue<String> queue = new SynchronousQueue<>();\n        System.out.println(queue.size());\n        queue.put(\"xx\");\n        System.out.println(queue.size());*/\n        \n        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(1);\n        System.out.println(queue.size());\n        queue.put(\"xx\");\n        queue.put(\"xx\");\n        System.out.println(queue.size());\n    }\n    \n    @Test\n    public void test6() {\n        new ConcurrentHashMap<>();\n        StringBuilder builder = new StringBuilder();\n        builder.append(\"xfdeafds\");\n        builder.deleteCharAt(builder.length()-1);\n        \n        StringBuilder builder2 = new StringBuilder();\n        builder2.append(\"XX\");\n        builder2.append(builder);\n        System.out.println(builder2.toString());\n        \n        builder = new StringBuilder();\n        builder.append(\"xfdeafds\");\n        builder.setLength(builder.length()-1);\n        builder2 = new StringBuilder();\n        builder2.append(\"XX\");\n        builder2.append(builder);\n        System.out.println(builder2.toString());\n        \n        System.out.println(\"|\\0|\");\n        System.out.println(\"|\\u0000|\");\n        Assert.assertEquals('\\0', '\\u0000');\n    }\n\n    @Test\n    public void test7() throws IOException {\n        System.out.println(Files.toString(new File(\"src/test/resources/test.txt\")));\n        System.out.println(Files.toString(new File(\"src/test/java/test/test1.java\")));\n        System.out.println(Files.toString(new File(\"src/main/resources/log4j2.xml\")));\n        System.out.println(Files.toString(new File(\"src/main/java/code/ponfee/commons/util/Asserts.java\")));\n    }\n    \n    public static int leastFire(int num, int shotDegrade, int remDegrade, int health) {\n        assert shotDegrade >= remDegrade;\n\n        int[] healths = new int[num];\n        Arrays.fill(healths, health);\n        return fire(healths, shotDegrade, remDegrade);\n    }\n\n    private static int fire(int[] healths, int shotDegrade, int remDegrade) {\n        Arrays.sort(healths);\n        if (healths[healths.length - 1] == 0) {\n            return 0;\n        }\n\n        for (int i = 0; i < healths.length; i++) {\n            if (i == healths.length - 1) {\n                healths[i] = Math.max(0, healths[i] - shotDegrade);\n            } else {\n                healths[i] = Math.max(0, healths[i] - remDegrade);\n            }\n        }\n        return 1 + fire(healths, shotDegrade, remDegrade);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/utils/TestBeanCopy.java",
    "content": "package test.utils;\n\nimport cn.ponfee.commons.json.Jsons;\nimport cn.ponfee.commons.model.Result;\nimport cn.ponfee.commons.reflect.BeanMaps;\nimport cn.ponfee.commons.reflect.BeanCopiers;\nimport cn.ponfee.commons.reflect.Fields;\nimport org.junit.Test;\nimport org.springframework.cglib.beans.BeanCopier;\n\nimport java.util.Map;\n\npublic class TestBeanCopy {\n\n    public static void main(String[] args) {\n        Object obj = new Object();\n        System.out.println(Fields.addressOf(obj));\n        System.out.println(System.identityHashCode(obj));\n    }\n\n    static int round = 9999999;\n\n    @Test\n    public void test0() {\n        Result<Void> result1 = Result.failure(-1, \"error\");\n        Result<Void> result2 = new Result<>();\n        for (int i = 0; i < round; i++) {\n            org.springframework.beans.BeanUtils.copyProperties(result1, result2);\n        }\n    }\n\n    @Test\n    public void test1() {\n        Result<Void> result1 = Result.failure(-1, \"error\");\n        Result<Void> result2 = new Result<>();\n        for (int i = 0; i < round; i++) {\n            BeanCopiers.copy(result1, result2);\n        }\n    }\n\n    @Test\n    public void test2() {\n        Result<Void> result1 = Result.failure(-1, \"error\");\n        Result<Void> result2 = new Result<>();\n        for (int i = 0; i < round; i++) {\n            BeanCopier.create(Result.class, Result.class, false).copy(result1, result2, null);\n        }\n    }\n\n    @Test\n    public void test3() {\n        BeanCopier copier = BeanCopier.create(Result.class, Result.class, false);\n        Result<Void> result1 = Result.failure(-1, \"error\");\n        Result<Void> result2 = new Result<>();\n        for (int i = 0; i < round; i++) {\n            copier.copy(result1, result2, null);\n        }\n    }\n\n    // ------------------------------------------------------toMap\n    @Test\n    public void test4() {\n        Result<Void> result1 = Result.failure(-1, \"error\");\n        System.out.println(BeanMaps.PROPS.toMap(result1));\n        for (int i = 0; i < round; i++) {\n            BeanMaps.FIELDS.toMap(result1);\n        }\n    }\n\n    @Test\n    public void test5() {\n        Result<Void> result1 = Result.failure(-1, \"error\");\n        System.out.println(Jsons.toJson(BeanMaps.CGLIB.toMap(result1)));\n        for (int i = 0; i < round; i++) {\n            BeanMaps.CGLIB.toMap(result1);\n        }\n    }\n\n    @Test\n    public void test6() {\n        Result<Void> result1 = Result.failure(-1, \"error\");\n        System.out.println(BeanMaps.PROPS.toMap(result1));\n        for (int i = 0; i < round; i++) {\n            BeanMaps.PROPS.toMap(result1);\n        }\n    }\n\n    @Test\n    public void test7() {\n        TestBean bean = new TestBean();\n        Map<String, Object> map = BeanMaps.PROPS.toMap(bean);\n        map.remove(\"failure\");\n        map.remove(\"success\");\n        System.out.println(map);\n        System.out.println(Jsons.toJson(BeanMaps.PROPS.toBean(map, TestBean.class)));\n        for (int i = 0; i < round; i++) {\n            BeanMaps.CGLIB.toBean(map, TestBean.class);\n        }\n    }\n\n    @Test\n    public void test8() {\n        TestBean bean = new TestBean();\n        Map<String, Object> map = BeanMaps.CGLIB.toMap(bean);\n        System.out.println(map);\n        System.out.println(Jsons.toJson(BeanMaps.CGLIB.toBean(map, TestBean.class)));\n        for (int i = 0; i < round; i++) {\n            BeanMaps.CGLIB.toBean(map, TestBean.class);\n        }\n    }\n    \n    @Test\n    public void test9() {\n        TestBean bean = new TestBean();\n        bean.setAge(20);\n        bean.set_id(\"xxx\");\n        Map<String, Object> map = BeanMaps.PROPS.toMap(bean);\n        System.out.println(map);\n\n        map.put(\"_ip\", \"yyyy\");\n\n        System.out.println(Jsons.toJson(BeanMaps.CGLIB.toBean(map, TestBean.class)));\n    }\n\n    public static class TestBean {\n        private int age;\n        private String _id;\n\n        public int getAge() {\n            return age;\n        }\n\n        public void setAge(int age) {\n            this.age = age;\n        }\n\n        public String get_id() {\n            return _id;\n        }\n\n        public void set_id(String _id) {\n            this._id = _id;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/TestCache.java",
    "content": "package test.utils;\n\nimport java.util.Date;\nimport java.util.Random;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport cn.ponfee.commons.cache.Cache;\nimport cn.ponfee.commons.cache.CacheBuilder;\nimport cn.ponfee.commons.util.ObjectUtils;\nimport cn.ponfee.commons.util.UuidUtils;\n\npublic class TestCache {\n\n    public static void main(String[] args) throws InterruptedException {\n        Random random = new Random();\n        Cache<String, Void> cache = CacheBuilder.<String, Void>newBuilder()\n            .caseSensitiveKey(false).compressKey(true).autoReleaseInSeconds(2).build();\n        AtomicBoolean flag = new AtomicBoolean(true);\n        int n = 10;\n        Thread[] threads = new Thread[n];\n        for (int i = 0; i < n; i++) {\n            threads[i] = new Thread(() -> {\n                while (flag.get()) {\n                    if (cache.isDestroy()) break;\n                    cache.put(UuidUtils.uuid32(), null, new Date().getTime() + random.nextInt(1000));\n                }\n            });\n        }\n\n        for (Thread thread : threads) {\n            thread.start();\n        }\n        for (int i = 0; i < 15; i++) {\n            System.out.println(cache.size());\n            Thread.sleep(1000);\n        }\n        flag.set(false);\n        for (Thread thread : threads) {\n            thread.join();\n        }\n        cache.destroy();\n        System.out.println(cache.size());\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/TestCost.java",
    "content": "package test.utils;\n\nimport java.security.SecureRandom;\n\nimport org.junit.Test;\n\nimport cn.ponfee.commons.util.SecureRandoms;\n\npublic class TestCost {\n\n    @Test\n    public void test1() {\n        byte b1 = 127, b2 = 127;\n        long start = System.currentTimeMillis();\n        for (long i = 0; i < 99999999999L; i++) {\n            if (b1 != b2) {\n                System.out.println(\"fail!\");\n                break;\n            }\n        }\n        System.out.println(\"test1 cost: \" + (System.currentTimeMillis() - start) / 1000);\n    }\n    \n    @Test\n    public void test2() {\n        byte b1 = 127, b2 = 127;\n        long start = System.currentTimeMillis();\n        for (long i = 0; i < 99999999999L; i++) {\n            if ((b1 ^ b2) != 0) {\n                System.out.println(\"fail!\");\n                break;\n            }\n        }\n        System.out.println(\"test2 cost: \" + (System.currentTimeMillis() - start) / 1000);\n    }\n    \n    @Test\n    public void test3() {\n        long start = System.currentTimeMillis();\n        for (long i = 0; i < 99999999999L; i++) {\n        }\n        System.out.println(\"test2 cost: \" + (System.currentTimeMillis() - start) / 1000);\n    }\n    \n    @Test\n    public void test4() {\n        long start = System.currentTimeMillis();\n        for (long i = 0; i != 99999999999L; i++) {\n        }\n        System.out.println(\"test2 cost: \" + (System.currentTimeMillis() - start) / 1000);\n    }\n    \n    @Test\n    public void test6() {\n        byte[] b = SecureRandoms.nextBytes(1024);\n        long start = System.currentTimeMillis();\n        for (long i = 0; i < 999999L; i++) {\n            org.apache.commons.codec.binary.Base64.encodeBase64String(b);\n            //java.util.Base64.getEncoder().encodeToString(b);\n        }\n        System.out.println(\"test6 cost: \" + (System.currentTimeMillis() - start) / 1000);\n    }\n\n    @Test\n    public void test5() {\n        SecureRandom random = new SecureRandom();\n        byte[] bytes = new byte[16];\n        random.nextBytes(bytes);\n        long start = System.currentTimeMillis();\n        for (long i = 0; i != 99999999L; i++) {\n            //org.apache.commons.codec.binary.Hex.encodeHex(bytes);\n            org.bouncycastle.util.encoders.Hex.toHexString(bytes);\n        }\n        System.out.println(\"test2 cost: \" + (System.currentTimeMillis() - start) / 1000);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/test/utils/TestInterrupt.java",
    "content": "package test.utils;\n\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.LockSupport;\nimport java.util.concurrent.locks.ReentrantLock;\n\npublic class TestInterrupt {\n\n    public static void main(String[] args) throws InterruptedException {\n        Lock lock = new ReentrantLock();\n\n\n        Thread t1 = new Thread(() -> {\n            lock.lock();\n            try {\n                System.out.println(\"t1 start..\");\n                Thread.sleep(3000);\n                System.out.println(\"t1 end..\");\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            } finally {\n                //lock.unlock();\n            }\n        });\n\n        Thread t2 = new Thread(() -> {\n            System.out.println(\"t2 start...\"+Thread.currentThread().isInterrupted());\n            try {\n                //lock.lockInterruptibly(); // 会感知中断，会抛出InterruptedException，会重置中断状态（false）\n                LockSupport.park(); // 会感知中断，不会抛异常，不会重置中断状态（true）\n                /*long start = System.currentTimeMillis();\n                while (System.currentTimeMillis() - start < 2000) {\n                    // 正常执行情况（非阻塞）\n                }*/\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n            System.out.println(\"t2 end...\"+Thread.currentThread().isInterrupted());\n        });\n\n        t1.start();\n        Thread.sleep(100);\n        t2.start();\n        Thread.sleep(100);\n\n        t2.interrupt();\n        t1.join();\n        t2.join();\n        System.out.println(t2.isInterrupted());\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/TestLock.java",
    "content": "package test.utils;\n\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\npublic class TestLock {\n    public static void main(String[] args) throws InterruptedException {\n        Lock lock = new ReentrantLock();\n\n        Thread t1 = new Thread(() -> {\n            lock.lock();\n            try {\n                System.out.println(\"t1...\");\n                //Thread.sleep(150);\n                Thread.sleep(60000);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            } finally {\n                lock.unlock();\n            }\n        }, \"t1\");\n\n        Thread t2 = new Thread(() -> {\n            try {\n                Thread.sleep(100);\n                lock.lock();\n                System.out.println(\"t2...\");\n                Thread.sleep(60000);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            } finally {\n                lock.unlock();\n            }\n        }, \"t2\");\n\n        Thread t3 = new Thread(() -> {\n            try {\n                Thread.sleep(200);\n                lock.lock();\n                System.out.println(\"t3...\");\n                Thread.sleep(60000);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            } finally {\n                lock.unlock();\n            }\n        }, \"t3\");\n\n        Thread t4 = new Thread(() -> {\n            try {\n                Thread.sleep(300);\n                lock.lock();\n                System.out.println(\"t4...\");\n                Thread.sleep(60000);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            } finally {\n                lock.unlock();\n            }\n        }, \"t4\");\n\n        Thread t5 = new Thread(() -> {\n            try {\n                Thread.sleep(400);\n                lock.lock();\n                System.out.println(\"t5...\");\n                Thread.sleep(60000);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            } finally {\n                lock.unlock();\n            }\n        }, \"t5\");\n\n        Thread t6 = new Thread(() -> {\n            try {\n                Thread.sleep(500);\n                lock.lock();\n                System.out.println(\"t6...\");\n                Thread.sleep(60000);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            } finally {\n                lock.unlock();\n            }\n        }, \"t6\");\n\n        t1.start();\n        t2.start();\n        t3.start();\n        t4.start();\n        t5.start();\n        t6.start();\n\n\n        t1.join();\n        t2.join();\n        t3.join();\n        t4.join();\n        t5.join();\n        t6.join();\n    }\n\n\n\n}\n"
  },
  {
    "path": "src/test/java/test/utils/TestXmlReader.java",
    "content": "package test.utils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport cn.ponfee.commons.xml.XmlReader;\n\npublic class TestXmlReader {\n\n    public static void main(String[] args) throws InterruptedException {\n        XmlReader reader = XmlReader.create(\"<alipay><is_success>T</is_success><request><param name=\\\"trade_no\\\">2015031800001000970051218861</param><param name=\\\"_input_charset\\\">UTF-8</param><param name=\\\"service\\\">single_trade_query</param><param name=\\\"partner\\\">2088411293539364</param><param name=\\\"out_trade_no\\\">0515abcdbcd3831256</param></request><response><trade><body>abcd</body><buyer_email>1034792318@qq.com</buyer_email><buyer_id>2088212560609971</buyer_id><discount>0.00</discount><flag_trade_locked>0</flag_trade_locked><gmt_create>2015-03-18 15:22:26</gmt_create><gmt_last_modified_time>2015-03-18 15:43:11</gmt_last_modified_time><gmt_payment>2015-03-18 15:22:36</gmt_payment><gmt_refund>2015-03-18 15:43:11</gmt_refund><is_total_fee_adjust>F</is_total_fee_adjust><operator_role>B</operator_role><out_trade_no>20150318151412944</out_trade_no><payment_type>8</payment_type><price>0.10</price><quantity>1</quantity><refund_fee>0.03</refund_fee><refund_flow_type>1</refund_flow_type><refund_id>97622780097</refund_id><refund_status>REFUND_SUCCESS</refund_status><seller_email>piaokuan03@dfasfds.com</seller_email><seller_id>2088411293539364</seller_id><subject>abcd全国汽车票</subject><to_buyer_fee>0.03</to_buyer_fee><to_seller_fee>0.10</to_seller_fee><total_fee>0.10</total_fee><trade_no>2015031800001000970051218861</trade_no><trade_status>TRADE_SUCCESS</trade_status><use_coupon>F</use_coupon></trade></response><sign>39350dc1dd1a85815bfc2f153ae436e1</sign><sign_type>MD5</sign_type></alipay>\");\n        System.out.println(reader.getNodeFloat(\"price\"));\n        AtomicInteger flag = new AtomicInteger();\n        List<Thread> list = new ArrayList<>();\n        for (int i = 0; i < 1; i++) {\n            list.add(new Thread(() -> {\n                while (flag.getAndIncrement() < 50000) {\n                    reader.evaluate(\"//alipay/request/param/@name\");\n                    reader.evaluate(\"//alipay/request/param[@name='trade_no']\");\n                    reader.evaluate(\"//alipay/response/trade/seller_email\");\n                    //System.out.println(reader.evaluate(\"//alipay/request/param/@name\"));\n                    //System.out.println(reader.evaluate(\"//alipay/request/param[@name='trade_no']\"));\n                    //System.out.println(reader.evaluate(\"//alipay/response/trade/seller_email\"));\n                }\n            }));\n        }\n        long start = System.currentTimeMillis();\n        for (Thread thread : list) {\n            thread.start();\n        }\n        for (Thread thread : list) {\n            thread.join();\n        }\n        System.out.println(System.currentTimeMillis() - start);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/utils/Ztzip.java",
    "content": "package test.utils;\n\nimport cn.ponfee.commons.util.MavenProjects;\nimport cn.ponfee.commons.util.ZipUtils;\n\npublic class Ztzip {\n\n    public static void main(String[] args) throws Exception {\n        //org.zeroturnaround.zip.ZipUtil.pack(new File(\"D:\\\\tmp\"), new File(\"d:/demo.zip\"));\n        //org.zeroturnaround.zip.ZipUtil.unexplode(new File(\"D:\\\\Recv Files.zip\"));\n\n        //org.zeroturnaround.zip.ZipUtil.addOrReplaceEntries(new File(\"d:/demo.zip\"), new ZipEntrySource[] {new ByteSource(\"README.md\", \"readme!!!!!!!!!!!!!!!!!!!\".getBytes())});\n        //jodd.io.ZipUtil.unzip(\"D:\\\\sql script\", \"d:/demo1.zip\");\n        //jodd.io.ZipUtil.zip(\"D:\\\\sql script\");\n        //jodd.io.ZipUtil.gzip(\"D:\\\\demo.zip\");\n        \n        \n        ZipUtils.zip(MavenProjects.getProjectBaseDir(), MavenProjects.getProjectBaseDir() + \"\\\\..\\\\commons-code.zip\", true, \"123456\", \"test123\");\n        //ZipUtils.unzip(\"E:\\\\common、s-code\\\\commons-code.zip\", \"d:\\\\commons-code\", \"123456\");\n    }\n}\n"
  },
  {
    "path": "src/test/resources/cacert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEgDCCA2igAwIBAgIJAKtU1mIQGJmSMA0GCSqGSIb3DQEBBAUAMIGGMQswCQYD\nVQQGEwJDTjESMBAGA1UECBMJR1VBTkdET05HMREwDwYDVQQHEwhTSEVOWkhFTjEQ\nMA4GA1UEChMHVEVOQ0VOVDEMMAoGA1UECxMDRklUMQwwCgYDVQQDEwNDRlQxIjAg\nBgkqhkiG9w0BCQEWE3BlbmdsaXVAdGVuY2VudC5jb20wHhcNMTUxMjIxMDIxNzM2\nWhcNMzUxMjE2MDIxNzM2WjCBhjELMAkGA1UEBhMCQ04xEjAQBgNVBAgTCUdVQU5H\nRE9ORzERMA8GA1UEBxMIU0hFTlpIRU4xEDAOBgNVBAoTB1RFTkNFTlQxDDAKBgNV\nBAsTA0ZJVDEMMAoGA1UEAxMDQ0ZUMSIwIAYJKoZIhvcNAQkBFhNwZW5nbGl1QHRl\nbmNlbnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu49T5R5F\nFjVHJSZ38QUn6EyU24W4jDWCkiJvaheJcMSGhA6X/UYNRFnqwvcZORrofy6AsFRX\nicVgYAdMniHe2d1YobZF/l+85y7grELn/4chuZUNHfVdNBvIo1R4tC9kcdkfoArk\nf7g8mFvcJ/iUryP6M3KlXLVS2HD7NnsQ6wYp4/Z0CWQ2fBZMZQPeMGfDRC/TXy/K\nUdpBu5vQao4cJabMWJA++w+TbFsM8r3RN8A/hidWKBCDThmDYzExolSXB6Bl2BWS\nKMxCTgRB+Bz9ekLxE3ZdyYU0pCw3nSFv35REY/v7b5gM7f8lSZM3FpNOmnhIf1zK\n63HDDOTqjEZRIQIDAQABo4HuMIHrMB0GA1UdDgQWBBRSd12VBD61NL12eknBIPo3\nY7ikBTCBuwYDVR0jBIGzMIGwgBRSd12VBD61NL12eknBIPo3Y7ikBaGBjKSBiTCB\nhjELMAkGA1UEBhMCQ04xEjAQBgNVBAgTCUdVQU5HRE9ORzERMA8GA1UEBxMIU0hF\nTlpIRU4xEDAOBgNVBAoTB1RFTkNFTlQxDDAKBgNVBAsTA0ZJVDEMMAoGA1UEAxMD\nQ0ZUMSIwIAYJKoZIhvcNAQkBFhNwZW5nbGl1QHRlbmNlbnQuY29tggkAq1TWYhAY\nmZIwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOCAQEAS26cvtHrXksD4wfs\nO2C912AdngV303lUcLjtQpyfUFEJwtfHiIHqCw+gJfedJxYLyhA5twTE1RY1bym3\nFJEnKkIHcj7sid3bHbco0caryP+praHLpQPWkOLggfUy+c28fFZb1DXtkPAhzQs0\nNscBnxqf6Cd+6uqbplg1nzTX8CeMXD2lajY1g12BM0f47bc3vP1/j3ab7Qa//0iJ\ncnnFbHOJ8qRLO4/B4Wwi7EeYPTJwSfc28Rj/TXYxODEDE22IaNO/kv6F8SAKNbQe\nL7h6QKbL+Gbhl6OkZH456MVflKK3uCiKe2dChE3D4JS4UbXSTbfuZtQY+tZlQf0M\nUy5hlw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/copy-right.txt",
    "content": "/* __________              _____                                                *\\\n** \\______   \\____   _____/ ____\\____   ____    Copyright (c) 2017-2023 Ponfee  **\n**  |     ___/  _ \\ /    \\   __\\/ __ \\_/ __ \\   http://www.ponfee.cn            **\n**  |    |  (  <_> )   |  \\  | \\  ___/\\  ___/   Apache License Version 2.0      **\n**  |____|   \\____/|___|  /__|  \\___  >\\___  >  http://www.apache.org/licenses/ **\n**                      \\/          \\/     \\/                                   **\n\\*                                                                              */\n\n"
  },
  {
    "path": "src/test/resources/csv.csv",
    "content": "﻿区域,分公司,项目数,项目应收(元),成交套数,套均收入(元),团购项目数,导客项目数,代收项目数,线上项目数,应收(万),实收(万),成交套数,套均收入(元),团购项目应收(万),团购项目成交套数,团购项目经服成交套数,团购项目套均收入(元),团购项目经服成交应收(万),团购项目中介应付外佣(万),团购项目经服成交套数占比,团购项目中介分佣比例,导客项目应收(万),导客项目成交套数,导客项目套均收入(元),导客项目中介应付外佣(万),导客项目中介分佣比例,代收项目应收(万),代收项目成交套数,线上项目应收(万),线上项目成交套数,月指标(万),指标完成率\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n1234563.918%,2017-02-03,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n,合计,abd111111,abd111111,ab1111111d,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd,abd\r\n"
  },
  {
    "path": "src/test/resources/log/log4j.properties",
    "content": "################################################################################\n#  Licensed to the Apache Software Foundation (ASF) under one\n#  or more contributor license agreements.  See the NOTICE file\n#  distributed with this work for additional information\n#  regarding copyright ownership.  The ASF licenses this file\n#  to you under the Apache License, Version 2.0 (the\n#  \"License\"); you may not use this file except in compliance\n#  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\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################################################################################\n\nlog4j.rootLogger=WARN, console, file\nlog4j.logger.cn.ponfee.flink=INFO\n\n\n\n# \\u65e5\\u5fd7\\u6253\\u5370\\u5230\\u63a7\\u5236\\u53f0\nlog4j.appender.console=org.apache.log4j.ConsoleAppender\nlog4j.appender.console.Target=System.out\nlog4j.appender.console.layout=org.apache.log4j.PatternLayout\nlog4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss} %-5p %-60c %x - %m%n\n\n\n\n# \\u65e5\\u5fd7\\u5199\\u5165\\u6587\\u4ef6\nlog4j.appender.file=org.apache.log4j.RollingFileAppender\n\n# log4j\\u4e0d\\u652f\\u6301\\u201c${variable_name:-default_value}\\u201d\\u7684\\u9ed8\\u8ba4\\u503c\\u914d\\u7f6e\\u65b9\\u5f0f\n#log4j.appender.file.File=${log.home:-.}/logs/log4j/flink.log\n\n# System.getProperty(\"log.home\")\n#log4j.appender.file.File=${log.home}/logs/log4j/flink.log\nlog4j.appender.file.File=logs/log4j/flink.log\n\nlog4j.appender.file.MaxFileSize=100MB\nlog4j.appender.file.MaxBackupIndex=10\nlog4j.appender.file.Threshold=WARN\nlog4j.appender.file.layout=org.apache.log4j.PatternLayout\nlog4j.appender.file.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %l%n%m%n\nlog4j.appender.file.Encoding=UTF-8\n"
  },
  {
    "path": "src/test/resources/log/log4j.properties.bak",
    "content": "log4j.rootLogger=warn,console,file\n\nlog4j.logger.base=warn\nlog4j.logger.org.apache.commons=warn\nlog4j.logger.org.springframework=warn\nlog4j.logger.org.apache.ibatis=warn\nlog4j.logger.org.mybatis.spring=warn\nlog4j.logger.java.sql=warn\nlog4j.logger.java.sql.Connection=warn\nlog4j.logger.java.sql.PreparedStatement=warn\nlog4j.logger.java.sql.ResultSet=warn\nlog4j.logger.java.sql.Statement=warn\nlog4j.logger.org.quartz=warn\n\n# \\u63a7\\u5236\\u53f0\nlog4j.appender.console=org.apache.log4j.ConsoleAppender\nlog4j.appender.console.target=System.out\nlog4j.appender.console.Threshold=debug\nlog4j.appender.console.layout=org.apache.log4j.PatternLayout\nlog4j.appender.console.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n\n\n# \\u6587\\u4ef6\nlog4j.appender.file=org.apache.log4j.RollingFileAppender\nlog4j.appender.file.File=../commons-code.log\nlog4j.appender.file.MaxFileSize=100KB\nlog4j.appender.file.MaxBackupIndex=5\nlog4j.appender.file.Threshold=warn\nlog4j.appender.file.layout=org.apache.log4j.PatternLayout\nlog4j.appender.file.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %l%n%m%n\nlog4j.appender.file.Encoding=UTF-8\n"
  },
  {
    "path": "src/test/resources/log/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/2002/xmlspec/dtd/2.10/xmlspec.dtd\">\n\n<!-- status : 这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,会看到log4j2内部各种详细输出 monitorInterval \n    : Log4j能够自动检测修改配置文件和重新配置本身, 设置间隔秒数。 注：本配置文件的目标是将不同级别的日志输出到不同文件，最大2MB一个文件， \n    文件数据达到最大值时，旧数据会被压缩并放进指定文件夹 -->\n<Configuration status=\"WARN\" monitorInterval=\"600\">\n\n    <Properties> <!-- 配置日志文件输出目录，此配置将日志输出到tomcat根目录下的指定文件夹 -->\n        <Property name=\"LOG_HOME\">${sys:catalina.home}/WebAppLogs/HHServices\n        </Property>\n    </Properties>\n\n    <Appenders>\n\n        <!-- 优先级从高到低分别是 OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL -->\n        <!-- 单词解释： Match：匹配 DENY：拒绝 Mismatch：不匹配 ACCEPT：接受 -->\n        <!-- DENY，日志将立即被抛弃不再经过其他过滤器； NEUTRAL，有序列表里的下个过滤器过接着处理日志； ACCEPT，日志会被立即处理，不再经过剩余过滤器。 -->\n        <!--输出日志的格式\n         %d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间\n         %p : 日志输出格式\n         %c : logger的名称 \n         %m : 日志内容，即 logger.info(\"message\") \n         %n : 换行符 \n         %C : Java类名\n         %L : 日志输出所在行数 \n         %M : 日志输出所在方法名 \n         hostName : 本地机器名 \n         hostAddress : 本地ip地址 -->\n\n        <!--这个输出控制台的配置，这里输出除了warn和error级别的信息到System.out -->\n        <Console name=\"console_out_appender\" target=\"SYSTEM_OUT\">\n            <!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) . -->\n            <ThresholdFilter level=\"DEBUG\" onMatch=\"ACCEPT\"\n                onMismatch=\"DENY\" />\n            <!-- 输出日志的格式 -->\n            <PatternLayout pattern=\"%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n\" />\n        </Console>\n        <!-- 这个输出控制台的配置，这里输出error级别的信息到System.err，在eclipse控制台上看到的是红色文字 -->\n        <Console name=\"console_err_appender\" target=\"SYSTEM_ERR\">\n            <ThresholdFilter level=\"ERROR\" onMatch=\"ACCEPT\"\n                onMismatch=\"DENY\" />\n            <PatternLayout pattern=\"%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n\" />\n        </Console>\n\n\n        <!-- TRACE级别日志 ; 设置日志格式并配置日志压缩格式，压缩文件独立放在一个文件夹内， 日期格式不能为冒号，否则无法生成，因为文件名不允许有冒号，此appender只输出trace级别的数据到trace.log -->\n        <RollingFile name=\"trace_appender\" immediateFlush=\"true\"\n            fileName=\"${LOG_HOME}/trace.log\" filePattern=\"${LOG_HOME}/trace/trace - %d{yyyy-MM-dd HH_mm_ss}.log.gz\">\n            <PatternLayout>\n                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>\n            </PatternLayout>\n            <Policies>\n                <!-- 每个日志文件最大2MB -->\n                <SizeBasedTriggeringPolicy size=\"2MB\" />\n\n            </Policies>\n            <Filters>\n                <!-- 此Filter意思是，只输出TRACE级别的数据 DENY，日志将立即被抛弃不再经过其他过滤器； NEUTRAL，有序列表里的下个过滤器过接着处理日志； \n                    ACCEPT，日志会被立即处理，不再经过剩余过滤器。 -->\n                <ThresholdFilter level=\"debug\" onMatch=\"DENY\"\n                    onMismatch=\"NEUTRAL\" />\n                <ThresholdFilter level=\"trace\" onMatch=\"ACCEPT\"\n                    onMismatch=\"DENY\" />\n            </Filters>\n        </RollingFile>\n\n        <!-- DEBUG级别日志 设置日志格式并配置日志压缩格式，压缩文件独立放在一个文件夹内， 日期格式不能为冒号，否则无法生成，因为文件名不允许有冒号，此appender只输出debug级别的数据到debug.log; -->\n        <RollingFile name=\"debug_appender\" immediateFlush=\"true\"\n            fileName=\"${LOG_HOME}/debug.log\" filePattern=\"${LOG_HOME}/debug/debug - %d{yyyy-MM-dd HH_mm_ss}.log.gz\">\n            <PatternLayout>\n                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>\n            </PatternLayout>\n            <Policies><!-- 每个日志文件最大2MB ; -->\n                <SizeBasedTriggeringPolicy size=\"2MB\" />\n\n                <!-- 如果启用此配置，则日志会按文件名生成新压缩文件， 即如果filePattern配置的日期格式为 %d{yyyy-MM-dd HH} \n                    ，则每小时生成一个压缩文件， 如果filePattern配置的日期格式为 %d{yyyy-MM-dd} ，则天生成一个压缩文件 -->\n                <TimeBasedTriggeringPolicy interval=\"1\"\n                    modulate=\"true\" />\n\n            </Policies>\n            <Filters><!-- 此Filter意思是，只输出debug级别的数据 -->\n                <ThresholdFilter level=\"info\" onMatch=\"DENY\"\n                    onMismatch=\"NEUTRAL\" />\n                <ThresholdFilter level=\"debug\" onMatch=\"ACCEPT\"\n                    onMismatch=\"DENY\" />\n            </Filters>\n        </RollingFile>\n\n        <!-- INFO级别日志 -->\n        <RollingFile name=\"info_appender\" immediateFlush=\"true\"\n            fileName=\"${LOG_HOME}/info.log\" filePattern=\"${LOG_HOME}/info/info - %d{yyyy-MM-dd HH_mm_ss}.log.gz\">\n            <PatternLayout>\n                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>\n            </PatternLayout>\n            <Policies>\n                <SizeBasedTriggeringPolicy size=\"2MB\" />\n            </Policies>\n            <Filters>\n                <ThresholdFilter level=\"warn\" onMatch=\"DENY\"\n                    onMismatch=\"NEUTRAL\" />\n                <ThresholdFilter level=\"info\" onMatch=\"ACCEPT\"\n                    onMismatch=\"DENY\" />\n            </Filters>\n        </RollingFile>\n\n        <!-- WARN级别日志 -->\n        <RollingFile name=\"warn_appender\" immediateFlush=\"true\"\n            fileName=\"${LOG_HOME}/warn.log\" filePattern=\"${LOG_HOME}/warn/warn - %d{yyyy-MM-dd HH_mm_ss}.log.gz\">\n            <PatternLayout>\n                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>\n            </PatternLayout>\n            <Policies>\n                <SizeBasedTriggeringPolicy size=\"2MB\" />\n            </Policies>\n            <Filters>\n                <ThresholdFilter level=\"error\" onMatch=\"DENY\"\n                    onMismatch=\"NEUTRAL\" />\n                <ThresholdFilter level=\"warn\" onMatch=\"ACCEPT\"\n                    onMismatch=\"DENY\" />\n            </Filters>\n        </RollingFile>\n\n        <!-- ERROR级别日志 -->\n        <RollingFile name=\"error_appender\" immediateFlush=\"true\"\n            fileName=\"${LOG_HOME}/error.log\" filePattern=\"${LOG_HOME}/error/error - %d{yyyy-MM-dd HH_mm_ss}.log.gz\">\n            <PatternLayout>\n                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>\n            </PatternLayout>\n            <Policies>\n                <SizeBasedTriggeringPolicy size=\"2MB\" />\n            </Policies>\n            <Filters>\n                <ThresholdFilter level=\"error\" onMatch=\"ACCEPT\"\n                    onMismatch=\"DENY\" />\n            </Filters>\n        </RollingFile>\n    </Appenders>\n\n    <Loggers>\n        <!-- 配置日志的根节点 -->\n        <!-- 定义logger，只有定义了logger并引入了appender，appender才会生效 -->\n        <root level=\"trace\">\n            <appender-ref ref=\"console_out_appender\" />\n            <appender-ref ref=\"console_err_appender\" />\n            <appender-ref ref=\"trace_appender\" />\n            <appender-ref ref=\"debug_appender\" />\n            <appender-ref ref=\"info_appender\" />\n            <appender-ref ref=\"warn_appender\" />\n            <appender-ref ref=\"error_appender\" />\n        </root>\n\n        <!-- 第三方日志系统 -->\n        <logger name=\"org.springframework.core\" level=\"info\" />\n        <logger name=\"org.springframework.beans\" level=\"info\" />\n        <logger name=\"org.springframework.context\" level=\"info\" />\n        <logger name=\"org.springframework.web\" level=\"info\" />\n        <logger name=\"org.jboss.netty\" level=\"warn\" />\n        <logger name=\"org.apache.http\" level=\"warn\" />\n\n    </Loggers>\n\n</Configuration>\n"
  },
  {
    "path": "src/test/resources/log/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\n<!-- scan=\"true\"  当此属性设置为true时，配置文件如果发生改变，将会被重新加载，默认值为true。 -->\n<!-- scanPeriod=\"30 seconds\"  设置每30秒自动扫描,若没有指定具体单位则以milliseconds为标准(单位:milliseconds, seconds, minutes or hours)  -->\n<!-- debug=\"false\"  当此属性设置为true时，将打印出logback内部日志信息，实时查看logback运行状态。默认值为false。-->\n<configuration  scan=\"true\" scanPeriod=\"30 seconds\">\n\n    <contextName>FLINK-LOG</contextName>\n\n    <!-- 存放日志文件路径：${catalina.base:-.}/logs，${logback.home:-.}/logs -->\n    <property name=\"Log_Home\" value=\"${log.home:-.}/logs/logback\" />\n\n    <!-- 控制台输出 -->\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder charset=\"UTF-8\">\n            <!--格式化输出：%d表示日期，%thread表示线程名，%-5level：级别从左显示5个字符宽度%msg：日志消息，%n是换行符 -->\n            <pattern>%d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <!-- DEBUG级别 -->\n    <appender name=\"FILE_DEBUG\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 级别过滤器 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <!-- 设置过滤级别 -->\n            <level>DEBUG</level>\n            <!-- 用于配置符合过滤条件的操作 -->\n            <onMatch>ACCEPT</onMatch>\n            <!-- 用于配置不符合过滤条件的操作 -->\n            <onMismatch>DENY</onMismatch>\n        </filter>\n        <!-- <Encoding>UTF-8</Encoding> -->\n        <File>${Log_Home}/debug/debug.log</File>\n        <!-- 根据时间来制定滚动策略 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <FileNamePattern>${Log_Home}/debug/debug.%d{yyyy-MM-dd}.%i.log</FileNamePattern>\n            <!-- 多久后自动清楚旧的日志文件,单位:日 -->\n            <MaxHistory>30</MaxHistory>\n            <!-- 默认值是 10MB,文档最大值 -->\n            <MaxFileSize>200MB</MaxFileSize>\n        </rollingPolicy>\n        <encoder>\n            <Pattern>%d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n</Pattern>\n        </encoder>\n    </appender>\n\n    <!-- INFO级别 -->\n    <appender name=\"FILE_INFO\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>INFO</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n        <File>${Log_Home}/info/info.log</File>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <FileNamePattern>${Log_Home}/info/info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>\n            <MaxHistory>30</MaxHistory>\n            <MaxFileSize>200MB</MaxFileSize>\n        </rollingPolicy>\n        <encoder>\n            <Pattern>%d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n</Pattern>\n        </encoder>\n    </appender>\n\n    <!-- WARN级别 -->\n    <appender name=\"FILE_WARN\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>WARN</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n        <File>${Log_Home}/warn/warn.log</File>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <FileNamePattern>${Log_Home}/warn/warn.%d{yyyy-MM-dd}.%i.log</FileNamePattern>\n            <MaxHistory>30</MaxHistory>\n            <MaxFileSize>200MB</MaxFileSize>\n        </rollingPolicy>\n        <encoder>\n            <Pattern>%d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n</Pattern>\n        </encoder>\n    </appender>\n\n    <!-- ERROR级别 -->\n    <appender name=\"FILE_ERROR\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>ERROR</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n        <File>${Log_Home}/error/error.log</File>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <FileNamePattern>${Log_Home}/error/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>\n            <MaxHistory>30</MaxHistory>\n            <MaxFileSize>200MB</MaxFileSize>\n        </rollingPolicy>\n        <encoder>\n            <Pattern>%d{yyyy-MM-dd HH:mm:ss}| %-5level|%thread| %logger{50}| %msg%n</Pattern>\n        </encoder>\n    </appender>\n\n\n    <!-- ========================================logger======================================== -->\n    <!-- instead by log4jdbc: jdbc.sqltiming -->\n    <!-- <logger name=\"dao\" additivity=\"false\" level=\"DEBUG\" >\n        <appender-ref ref=\"STDOUT\"/>\n    </logger> -->\n    <!-- net.sf.log4jdbc.Slf4jSpyLogDelegator -->\n    <logger name=\"jdbc.audit\" additivity=\"false\" level=\"OFF\">\n        <appender-ref ref=\"STDOUT\" />\n    </logger>\n    <logger name=\"jdbc.resultset\" additivity=\"false\" level=\"OFF\">\n        <appender-ref ref=\"STDOUT\" />\n    </logger>\n    <logger name=\"jdbc.connection\" additivity=\"false\" level=\"OFF\">\n        <appender-ref ref=\"STDOUT\" />\n    </logger>\n    <logger name=\"jdbc.sqlonly\" additivity=\"false\" level=\"OFF\">\n        <appender-ref ref=\"STDOUT\" />\n    </logger>\n    <logger name=\"log4jdbc.debug\" additivity=\"false\" level=\"OFF\">\n        <appender-ref ref=\"STDOUT\" />\n    </logger>\n    <logger name=\"jdbc.sqltiming\" additivity=\"false\" level=\"WARN\">\n        <appender-ref ref=\"STDOUT\" />\n    </logger>\n\n    <!-- 控制java下面包的打印,没设置等级,将继承上级root的等级 -->\n    <logger name=\"cn.ponfee.flink\" additivity=\"false\" level=\"INFO\">\n        <appender-ref ref=\"STDOUT\" />\n        <appender-ref ref=\"FILE_DEBUG\" />\n        <appender-ref ref=\"FILE_INFO\" />\n        <appender-ref ref=\"FILE_WARN\" />\n        <appender-ref ref=\"FILE_ERROR\" />\n    </logger>\n\n    <!-- 当前日志总级别为：TRACE、DEBUG、INFO、 WARN、ERROR、ALL和 OFF -->\n    <!-- the level of the root level is set to DEBUG by default. -->\n    <root level=\"WARN\">\n        <appender-ref ref=\"STDOUT\" />\n        <appender-ref ref=\"FILE_DEBUG\" />\n        <appender-ref ref=\"FILE_INFO\" />\n        <appender-ref ref=\"FILE_WARN\" />\n        <appender-ref ref=\"FILE_ERROR\" />\n    </root>\n\n</configuration>\n"
  },
  {
    "path": "src/test/resources/signer.xsd",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" \n  targetNamespace=\"http://cn.ponfee/signer\" \n  xmlns:tns=\"http://cn.ponfee/signer\"\n  elementFormDefault=\"qualified\" \n  attributeFormDefault=\"unqualified\">\n\n  <element name=\"signers\">\n    <complexType>\n      <sequence>\n        <element name=\"signer\" type=\"tns:signerType\" maxOccurs=\"unbounded\" />\n      </sequence>\n    </complexType>\n  </element>\n\n  <complexType name=\"signerType\">\n    <sequence>\n      <element name=\"signerId\" type=\"string\" />\n      <element name=\"keyStore\" type=\"tns:resourceType\" />\n      <element name=\"storePass\" type=\"string\" />\n      <element name=\"keyPass\" type=\"string\" />\n      <element name=\"storeType\" type=\"tns:storeTypeType\" minOccurs=\"0\" />\n      <element name=\"alias\" type=\"string\" minOccurs=\"0\" />\n      <element name=\"stamp\" type=\"tns:resourceType\" />\n    </sequence>\n  </complexType>\n\n  <simpleType name=\"storeTypeType\">\n    <restriction base=\"string\">\n      <enumeration value=\"jks\" />\n      <enumeration value=\"pfx\" />\n    </restriction>\n  </simpleType>\n\n  <simpleType name=\"resourceType\">\n    <restriction base=\"string\">\n      <pattern value=\"(classpath:|classpath\\*:|file:(([c-zC-Z]:)(/|\\\\\\\\)){0,1}|webapp:){0,1}[^:\\?\\|\\*]*\" />\n    </restriction>\n  </simpleType>\n\n  <!-- <complexType name=\"stampType\">\n    <simpleContent>\n      <extension base=\"string\">\n        <attribute name=\"stampClasspath\" type=\"tns:classpathType\" use=\"optional\" />\n      </extension>\n    </simpleContent>\n  </complexType> -->\n\n</schema>\n"
  },
  {
    "path": "src/test/resources/signers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<signers xmlns=\"http://cn.ponfee/signer\" \n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n  xsi:schemaLocation=\"http://cn.ponfee/signer signer.xsd\">\n  <!-- <signers xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"signer.xsd\"> -->\n\n  <signer>\n    <signerId>99</signerId><!-- require -->\n    <keyStore><![CDATA[classpath:META-INF/sign/signers/1.pfx]]></keyStore><!--元素：require；规则：以（classpath:或classpath*:或file:或context:开头，默认以classpath开头 ）-->\n    <storePass>1234</storePass><!-- require -->\n    <keyPass>1234</keyPass><!-- require -->\n    <storeType>pfx</storeType><!-- optional[可选]，值：[pfx|jks] -->\n    <alias>45e4ea70a3589e96cd670c8e5c8c7be5_28766470-a40c-4c9e-b312-d7a5618db23b</alias><!-- optional[可选]，不填默认选第1个密钥对 -->\n    <stamp><![CDATA[classpath:META-INF/sign/signers/1.bmp]]></stamp><!--元素：require；规则：以（classpath:或classpath*:或file:或context:开头，默认以classpath开头 ）-->\n  </signer>\n\n  <signer>\n    <signerId>1</signerId>\n    <keyStore><![CDATA[META-INF/sign/signers/1.pfx]]></keyStore>\n    <storePass>1234</storePass>\n    <keyPass>1234</keyPass>\n    <stamp><![CDATA[META-INF/sign/signers/1.jpg]]></stamp>\n  </signer>\n\n  <signer>\n    <signerId>2</signerId>\n    <keyStore><![CDATA[META-INF/sign/signers/2.pfx]]></keyStore>\n    <storePass>1234</storePass>\n    <keyPass>1234</keyPass>\n    <stamp><![CDATA[META-INF/sign/signers/2.jpg]]></stamp>\n  </signer>\n\n  <signer>\n    <signerId>3</signerId>\n    <keyStore><![CDATA[META-INF/sign/signers/3.pfx]]></keyStore>\n    <storePass>1234</storePass>\n    <keyPass>1234</keyPass>\n    <stamp><![CDATA[META-INF/sign/signers/3.jpg]]></stamp>\n  </signer>\n\n  <signer>\n    <signerId>4</signerId>\n    <keyStore><![CDATA[META-INF/sign/signers/4.pfx]]></keyStore>\n    <storePass>1234</storePass>\n    <keyPass>1234</keyPass>\n    <stamp><![CDATA[META-INF/sign/signers/4.jpg]]></stamp>\n  </signer>\n\n  <signer>\n    <signerId>5</signerId>\n    <keyStore><![CDATA[META-INF/sign/signers/5.pfx]]></keyStore>\n    <storePass>1234</storePass>\n    <keyPass>1234</keyPass>\n    <stamp><![CDATA[META-INF/sign/signers/5.jpg]]></stamp>\n  </signer>\n\n  <signer>\n    <signerId>6</signerId>\n    <keyStore><![CDATA[META-INF/sign/signers/6.pfx]]></keyStore>\n    <storePass>1234</storePass>\n    <keyPass>1234</keyPass>\n    <stamp><![CDATA[META-INF/sign/signers/6.jpg]]></stamp>\n  </signer>\n\n  <signer>\n    <signerId>7</signerId>\n    <keyStore><![CDATA[META-INF/sign/signers/7.pfx]]></keyStore>\n    <storePass>1234</storePass>\n    <keyPass>1234</keyPass>\n    <stamp><![CDATA[META-INF/sign/signers/7.jpg]]></stamp>\n  </signer>\n\n  <signer>\n    <signerId>8</signerId>\n    <keyStore><![CDATA[META-INF/sign/signers/8.pfx]]></keyStore>\n    <storePass>1234</storePass>\n    <keyPass>1234</keyPass>\n    <stamp><![CDATA[META-INF/sign/signers/8.jpg]]></stamp>\n  </signer>\n\n  <signer>\n    <signerId>9</signerId>\n    <keyStore><![CDATA[META-INF/sign/signers/9.pfx]]></keyStore>\n    <storePass>1234</storePass>\n    <keyPass>1234</keyPass>\n    <stamp><![CDATA[META-INF/sign/signers/9.jpg]]></stamp>\n  </signer>\n\n</signers>\n"
  },
  {
    "path": "src/test/resources/sm2-crypto.cer",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDYjCCAwagAwIBAgIIYGX/NheSnJ4wDAYIKoEcz1UBg3UFADCBgjELMAkGA1UE\nBhMCQ04xEjAQBgNVBAgMCUd1YW5nZG9uZzERMA8GA1UEBwwIU2hlbnpoZW4xJzAl\nBgNVBAoMHlNoZW5aaGVuIENlcnRpZmljYXRlIEF1dGhvcml0eTENMAsGA1UECwwE\nc3pjYTEUMBIGA1UEAwwLU1pDQSBTTTIgQ0EwHhcNMTQxMjI0MDg1NDQ1WhcNMTUx\nMjI0MDg1NDQ1WjB5MQswCQYDVQQGEwJDTjESMBAGA1UECAwJ5bm/5Lic55yBMRIw\nEAYDVQQHDAnkuK3lsbHluIIxEjAQBgNVBAsMCeadjuWbm+WbmzEUMBIGA1UECwwL\nNTU1NTU1NTU1NTUxGDAWBgNVBAMMD+a1i+ivleS4k+eUqDg4NzBZMBMGByqGSM49\nAgEGCCqBHM9VAYItA0IABGbrwB3NL0ABrqJ2cmsPzSfmb4RZzy7Bv7pB41i+AsMP\nCsa0cGGbFI/JrBf4voyvwQklC1J3KQ6KGMMIociSul+jggFqMIIBZjAMBgNVHRME\nBTADAQEAMFwGCCsGAQUFBwEBBFAwTjAoBggrBgEFBQcwAoYcaHR0cDovLzEyNy4w\nLjAuMS9jYWlzc3VlLmh0bTAiBggrBgEFBQcwAYYWaHR0cDovLzEyNy4wLjAuMToy\nMDQ0MzAfBggqVgsHg8zpfwQTDBE2MzU0NDQ0NDQ1MjI1Nzc3NTALBgNVHQ8EBAMC\nAzgwHQYDVR0OBBYEFInkTvIZtFp4lXBGViPI3slWaqL4MBAGCCpWCweDzOl8BAQM\nAjU0MBEGCCpWCweDzOl5BAUMAzQyMjAfBgNVHSMEGDAWgBRF6jeNcj+1pgRvwyrJ\nrd3u5stOjTA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0cDov\nLzEyNy4wLjAuMS9jcHMuaHRtMCoGA1UdHwQjMCEwH6AdoBuGGWh0dHA6Ly8xMjcu\nMC4wLjEvY3JsMS5jcmwwDAYIKoEcz1UBg3UFAANIADBFAiAE+O+vf8ICszfBamzk\nJloHZ7CoC2jNnImNQcEJ3grr4AIhALRyg2keou896I22NEQiZhrtToYT9WxEKJBz\nBdK8TGIJ\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/test.txt",
    "content": "\\xAC\\xED\\x00\\x05t\\x00\\x0A2018-02-09"
  }
]